@@ -62,7 +62,11 @@ describe('executeOperation', () => {
6262 session : mockAdminSession ,
6363 variables : undefined ,
6464 version : '2024-07' ,
65- responseOptions : { handleErrors : false } ,
65+ responseOptions : {
66+ handleErrors : false ,
67+ onResponse : expect . any ( Function ) ,
68+ } ,
69+ addedHeaders : undefined ,
6670 } )
6771 } )
6872
@@ -251,4 +255,205 @@ describe('executeOperation', () => {
251255 } ) ,
252256 )
253257 } )
258+
259+ test ( 'passes custom headers correctly when provided' , async ( ) => {
260+ const query = 'query { shop { name } }'
261+ const headers = [ 'X-Custom-Header: custom-value' , 'Authorization: Bearer token123' ]
262+ const mockResult = { data : { shop : { name : 'Test Shop' } } }
263+ vi . mocked ( adminRequestDoc ) . mockResolvedValue ( mockResult )
264+
265+ await executeOperation ( {
266+ organization : mockOrganization ,
267+ remoteApp : mockRemoteApp ,
268+ storeFqdn,
269+ query,
270+ headers,
271+ } )
272+
273+ expect ( adminRequestDoc ) . toHaveBeenCalledWith (
274+ expect . objectContaining ( {
275+ addedHeaders : {
276+ 'X-Custom-Header' : 'custom-value' ,
277+ Authorization : 'Bearer token123' ,
278+ } ,
279+ } ) ,
280+ )
281+ } )
282+
283+ test ( 'throws AbortError when header format is invalid (missing colon)' , async ( ) => {
284+ const query = 'query { shop { name } }'
285+ const headers = [ 'InvalidHeader' ]
286+
287+ await expect (
288+ executeOperation ( {
289+ organization : mockOrganization ,
290+ remoteApp : mockRemoteApp ,
291+ storeFqdn,
292+ query,
293+ headers,
294+ } ) ,
295+ ) . rejects . toThrow ( / I n v a l i d h e a d e r f o r m a t / )
296+ } )
297+
298+ test ( 'throws AbortError when header key is empty' , async ( ) => {
299+ const query = 'query { shop { name } }'
300+ const headers = [ ': value-only' ]
301+
302+ await expect (
303+ executeOperation ( {
304+ organization : mockOrganization ,
305+ remoteApp : mockRemoteApp ,
306+ storeFqdn,
307+ query,
308+ headers,
309+ } ) ,
310+ ) . rejects . toThrow ( / I n v a l i d h e a d e r f o r m a t / )
311+ } )
312+
313+ test ( 'handles headers with whitespace correctly' , async ( ) => {
314+ const query = 'query { shop { name } }'
315+ const headers = [ ' X-Header : value with spaces ' ]
316+ const mockResult = { data : { shop : { name : 'Test Shop' } } }
317+ vi . mocked ( adminRequestDoc ) . mockResolvedValue ( mockResult )
318+
319+ await executeOperation ( {
320+ organization : mockOrganization ,
321+ remoteApp : mockRemoteApp ,
322+ storeFqdn,
323+ query,
324+ headers,
325+ } )
326+
327+ expect ( adminRequestDoc ) . toHaveBeenCalledWith (
328+ expect . objectContaining ( {
329+ addedHeaders : {
330+ 'X-Header' : 'value with spaces' ,
331+ } ,
332+ } ) ,
333+ )
334+ } )
335+
336+ test ( 'allows empty header value' , async ( ) => {
337+ const query = 'query { shop { name } }'
338+ const headers = [ 'X-Empty-Header:' ]
339+ const mockResult = { data : { shop : { name : 'Test Shop' } } }
340+ vi . mocked ( adminRequestDoc ) . mockResolvedValue ( mockResult )
341+
342+ await executeOperation ( {
343+ organization : mockOrganization ,
344+ remoteApp : mockRemoteApp ,
345+ storeFqdn,
346+ query,
347+ headers,
348+ } )
349+
350+ expect ( adminRequestDoc ) . toHaveBeenCalledWith (
351+ expect . objectContaining ( {
352+ addedHeaders : {
353+ 'X-Empty-Header' : '' ,
354+ } ,
355+ } ) ,
356+ )
357+ } )
358+
359+ test ( 'includes response extensions in output when present' , async ( ) => {
360+ const query = 'query { shop { name } }'
361+ const mockResult = { shop : { name : 'Test Shop' } }
362+ const mockExtensions = { cost : { requestedQueryCost : 1 , actualQueryCost : 1 } }
363+
364+ vi . mocked ( adminRequestDoc ) . mockImplementation ( async ( options ) => {
365+ // Simulate the onResponse callback being called with extensions
366+ if ( options . responseOptions ?. onResponse ) {
367+ options . responseOptions . onResponse ( {
368+ data : mockResult ,
369+ extensions : mockExtensions ,
370+ headers : new Headers ( ) ,
371+ status : 200 ,
372+ } as any )
373+ }
374+ return mockResult
375+ } )
376+
377+ const mockOutput = mockAndCaptureOutput ( )
378+
379+ await executeOperation ( {
380+ organization : mockOrganization ,
381+ remoteApp : mockRemoteApp ,
382+ storeFqdn,
383+ query,
384+ } )
385+
386+ const output = mockOutput . info ( )
387+ const parsedOutput = JSON . parse ( output )
388+
389+ expect ( parsedOutput ) . toEqual ( {
390+ data : mockResult ,
391+ extensions : mockExtensions ,
392+ } )
393+ } )
394+
395+ test ( 'outputs only data when no extensions are present' , async ( ) => {
396+ const query = 'query { shop { name } }'
397+ const mockResult = { shop : { name : 'Test Shop' } }
398+
399+ vi . mocked ( adminRequestDoc ) . mockImplementation ( async ( options ) => {
400+ // Simulate the onResponse callback being called without extensions
401+ if ( options . responseOptions ?. onResponse ) {
402+ options . responseOptions . onResponse ( {
403+ data : mockResult ,
404+ extensions : undefined ,
405+ headers : new Headers ( ) ,
406+ status : 200 ,
407+ } as any )
408+ }
409+ return mockResult
410+ } )
411+
412+ const mockOutput = mockAndCaptureOutput ( )
413+
414+ await executeOperation ( {
415+ organization : mockOrganization ,
416+ remoteApp : mockRemoteApp ,
417+ storeFqdn,
418+ query,
419+ } )
420+
421+ const output = mockOutput . info ( )
422+ const parsedOutput = JSON . parse ( output )
423+
424+ // Should output just the result, not wrapped in {data: ...}
425+ expect ( parsedOutput ) . toEqual ( mockResult )
426+ } )
427+
428+ test ( 'includes extensions in file output when present' , async ( ) => {
429+ await inTemporaryDirectory ( async ( tmpDir ) => {
430+ const outputFile = joinPath ( tmpDir , 'results.json' )
431+ const query = 'query { shop { name } }'
432+ const mockResult = { shop : { name : 'Test Shop' } }
433+ const mockExtensions = { cost : { requestedQueryCost : 1 , actualQueryCost : 1 } }
434+
435+ vi . mocked ( adminRequestDoc ) . mockImplementation ( async ( options ) => {
436+ if ( options . responseOptions ?. onResponse ) {
437+ options . responseOptions . onResponse ( {
438+ data : mockResult ,
439+ extensions : mockExtensions ,
440+ headers : new Headers ( ) ,
441+ status : 200 ,
442+ } as any )
443+ }
444+ return mockResult
445+ } )
446+
447+ await executeOperation ( {
448+ organization : mockOrganization ,
449+ remoteApp : mockRemoteApp ,
450+ storeFqdn,
451+ query,
452+ outputFile,
453+ } )
454+
455+ const expectedContent = JSON . stringify ( { data : mockResult , extensions : mockExtensions } , null , 2 )
456+ expect ( writeFile ) . toHaveBeenCalledWith ( outputFile , expectedContent )
457+ } )
458+ } )
254459} )
0 commit comments