11import { handler } from '../export' ;
22import { BatchHelper , Key } from '../../../lib/StorageClient' ;
3- import * as XLSX from 'xlsx ' ;
3+ import ExcelJS from 'exceljs ' ;
44
55// Mock dependencies
66jest . mock ( '../../../lib/StorageClient' , ( ) => ( {
@@ -14,13 +14,32 @@ jest.mock('../../../lib/StorageClient', () => ({
1414 } ) ,
1515 } ,
1616} ) ) ;
17- jest . mock ( 'xlsx' , ( ) => ( {
18- utils : {
19- book_new : jest . fn ( ) ,
20- json_to_sheet : jest . fn ( ) . mockReturnValue ( { } ) ,
21- book_append_sheet : jest . fn ( ) ,
17+ const mockCells = new Map < string , { value ?: unknown ; font ?: unknown } > ( ) ;
18+ const mockWorksheet = {
19+ columns : [ ] as unknown [ ] ,
20+ addRows : jest . fn ( ) ,
21+ getRow : jest . fn ( ( rowNumber : number ) => ( {
22+ getCell : jest . fn ( ( columnNumber : number ) => {
23+ const key = `${ rowNumber } :${ columnNumber } ` ;
24+ if ( ! mockCells . has ( key ) ) {
25+ mockCells . set ( key , { } ) ;
26+ }
27+ return mockCells . get ( key ) ! ;
28+ } ) ,
29+ } ) ) ,
30+ } ;
31+ const writeBufferMock = jest . fn ( ) . mockResolvedValue ( Buffer . from ( 'mock-excel-content' ) ) ;
32+ const mockWorkbook = {
33+ addWorksheet : jest . fn ( ) . mockReturnValue ( mockWorksheet ) ,
34+ xlsx : {
35+ writeBuffer : writeBufferMock ,
36+ } ,
37+ } ;
38+ jest . mock ( 'exceljs' , ( ) => ( {
39+ __esModule : true ,
40+ default : {
41+ Workbook : jest . fn ( ) . mockImplementation ( ( ) => mockWorkbook ) ,
2242 } ,
23- write : jest . fn ( ) . mockReturnValue ( Buffer . from ( 'mock-excel-content' ) ) ,
2443} ) ) ;
2544
2645describe ( 'export handler' , ( ) => {
@@ -31,6 +50,9 @@ describe('export handler', () => {
3150
3251 beforeEach ( ( ) => {
3352 jest . clearAllMocks ( ) ;
53+ mockCells . clear ( ) ;
54+ mockWorksheet . columns = [ ] ;
55+ process . env . PORTAL_CASE_URL = 'https://portal.example.com/search-results' ;
3456 } ) ;
3557
3658 it ( 'should return 400 if body is missing' , async ( ) => {
@@ -68,6 +90,7 @@ describe('export handler', () => {
6890 } ;
6991
7092 const mockZipCase = {
93+ caseId : 'case-id-123' ,
7194 fetchStatus : { status : 'complete' } ,
7295 } ;
7396
@@ -89,9 +112,11 @@ describe('export handler', () => {
89112 } ,
90113 isBase64Encoded : true ,
91114 } ) ;
115+ expect ( ExcelJS . Workbook ) . toHaveBeenCalledTimes ( 1 ) ;
116+ expect ( writeBufferMock ) . toHaveBeenCalledTimes ( 1 ) ;
92117
93- // Verify XLSX calls
94- expect ( XLSX . utils . json_to_sheet ) . toHaveBeenCalledWith ( [
118+ // Verify worksheet rows
119+ expect ( mockWorksheet . addRows ) . toHaveBeenCalledWith ( [
95120 {
96121 'Case Number' : 'CASE123' ,
97122 'Court Name' : 'Test Court' ,
@@ -124,7 +149,7 @@ describe('export handler', () => {
124149
125150 await handler ( mockEvent ( { caseNumbers : mockCaseNumbers } ) , { } as any , { } as any ) ;
126151
127- expect ( XLSX . utils . json_to_sheet ) . toHaveBeenCalledWith ( [
152+ expect ( mockWorksheet . addRows ) . toHaveBeenCalledWith ( [
128153 expect . objectContaining ( {
129154 'Case Number' : 'CASE_FAILED' ,
130155 Notes : 'Failed to load case data' ,
@@ -155,7 +180,7 @@ describe('export handler', () => {
155180
156181 await handler ( mockEvent ( { caseNumbers : mockCaseNumbers } ) , { } as any , { } as any ) ;
157182
158- expect ( XLSX . utils . json_to_sheet ) . toHaveBeenCalledWith ( [
183+ expect ( mockWorksheet . addRows ) . toHaveBeenCalledWith ( [
159184 expect . objectContaining ( {
160185 'Case Number' : 'CASE_NO_CHARGES' ,
161186 Notes : 'No charges found' ,
@@ -191,7 +216,7 @@ describe('export handler', () => {
191216
192217 await handler ( mockEvent ( { caseNumbers : mockCaseNumbers } ) , { } as any , { } as any ) ;
193218
194- const calls = ( XLSX . utils . json_to_sheet as jest . Mock ) . mock . calls [ 0 ] [ 0 ] ;
219+ const calls = ( mockWorksheet . addRows as jest . Mock ) . mock . calls [ 0 ] [ 0 ] ;
195220 const levels = calls . map ( ( row : any ) => row [ 'Offense Level' ] ) ;
196221
197222 expect ( levels ) . toEqual ( [ 'M1' , '' , 'GL M' , 'T' , 'INF' ] ) ;
@@ -220,4 +245,55 @@ describe('export handler', () => {
220245 } ,
221246 } ) ;
222247 } ) ;
248+
249+ it ( 'should create clickable hyperlink for case number cells' , async ( ) => {
250+ const mockCaseNumbers = [ 'CASE123' ] ;
251+
252+ const mockSummary = { charges : [ ] } ;
253+ const mockZipCase = { caseId : 'case-id-123' , fetchStatus : { status : 'complete' } } ;
254+ ( BatchHelper . getMany as jest . Mock ) . mockImplementation ( async ( keys : any [ ] ) => {
255+ const map = new Map ( ) ;
256+ keys . forEach ( key => {
257+ if ( key . PK === 'CASE#CASE123' && key . SK === 'SUMMARY' ) map . set ( key , mockSummary ) ;
258+ if ( key . PK === 'CASE#CASE123' && key . SK === 'ID' ) map . set ( key , mockZipCase ) ;
259+ } ) ;
260+ return map ;
261+ } ) ;
262+
263+ await handler ( mockEvent ( { caseNumbers : mockCaseNumbers } ) , { } as any , { } as any ) ;
264+
265+ expect ( mockWorksheet . getRow ) . toHaveBeenCalledWith ( 2 ) ;
266+ const caseNumberCell = mockCells . get ( '2:1' ) ;
267+ expect ( caseNumberCell ?. value ) . toEqual ( {
268+ text : 'CASE123' ,
269+ hyperlink : 'https://portal.example.com/search-results/#/case-id-123' ,
270+ } ) ;
271+ expect ( caseNumberCell ?. font ) . toMatchObject ( {
272+ color : { argb : 'FF0563C1' } ,
273+ underline : true ,
274+ } ) ;
275+ } ) ;
276+
277+ it ( 'should keep text value and hyperlink relationship for quoted case numbers' , async ( ) => {
278+ const mockCaseNumbers = [ 'CASE"123' ] ;
279+
280+ const mockSummary = { charges : [ ] } ;
281+ const mockZipCase = { caseId : 'case"id-123' , fetchStatus : { status : 'complete' } } ;
282+ ( BatchHelper . getMany as jest . Mock ) . mockImplementation ( async ( keys : any [ ] ) => {
283+ const map = new Map ( ) ;
284+ keys . forEach ( key => {
285+ if ( key . PK === 'CASE#CASE"123' && key . SK === 'SUMMARY' ) map . set ( key , mockSummary ) ;
286+ if ( key . PK === 'CASE#CASE"123' && key . SK === 'ID' ) map . set ( key , mockZipCase ) ;
287+ } ) ;
288+ return map ;
289+ } ) ;
290+
291+ await handler ( mockEvent ( { caseNumbers : mockCaseNumbers } ) , { } as any , { } as any ) ;
292+
293+ const caseNumberCell = mockCells . get ( '2:1' ) ;
294+ expect ( caseNumberCell ?. value ) . toEqual ( {
295+ text : 'CASE"123' ,
296+ hyperlink : 'https://portal.example.com/search-results/#/case"id-123' ,
297+ } ) ;
298+ } ) ;
223299} ) ;
0 commit comments