11import { executeBulkOperation } from './execute-bulk-operation.js'
22import { runBulkOperationQuery } from './run-query.js'
33import { runBulkOperationMutation } from './run-mutation.js'
4- import { watchBulkOperation } from './watch-bulk-operation.js'
4+ import { watchBulkOperation , shortBulkOperationPoll } from './watch-bulk-operation.js'
55import { downloadBulkOperationResults } from './download-bulk-operation-results.js'
66import { validateApiVersion } from '../graphql/common.js'
77import { BulkOperationRunQueryMutation } from '../../api/graphql/bulk-operations/generated/bulk-operation-run-query.js'
@@ -67,6 +67,7 @@ describe('executeBulkOperation', () => {
6767
6868 beforeEach ( ( ) => {
6969 vi . mocked ( ensureAuthenticatedAdminAsApp ) . mockResolvedValue ( mockAdminSession )
70+ vi . mocked ( shortBulkOperationPoll ) . mockResolvedValue ( createdBulkOperation )
7071 } )
7172
7273 afterEach ( ( ) => {
@@ -305,7 +306,7 @@ describe('executeBulkOperation', () => {
305306 } )
306307 } )
307308
308- test ( 'waits for operation to finish and renders success when watch is provided and operation finishes with COMPLETED status ' , async ( ) => {
309+ test ( 'uses watchBulkOperation (not quickWatchBulkOperation) when watch flag is true ' , async ( ) => {
309310 const query = '{ products { edges { node { id } } } }'
310311 const initialResponse : BulkOperationRunQueryMutation [ 'bulkOperationRunQuery' ] = {
311312 bulkOperation : createdBulkOperation ,
@@ -320,7 +321,9 @@ describe('executeBulkOperation', () => {
320321
321322 vi . mocked ( runBulkOperationQuery ) . mockResolvedValue ( initialResponse )
322323 vi . mocked ( watchBulkOperation ) . mockResolvedValue ( completedOperation )
323- vi . mocked ( downloadBulkOperationResults ) . mockResolvedValue ( '{"id":"gid://shopify/Product/123"}' )
324+ vi . mocked ( downloadBulkOperationResults ) . mockResolvedValue (
325+ '{"data":{"products":{"edges":[{"node":{"id":"gid://shopify/Product/123"}}],"userErrors":[]}},"__lineNumber":0}' ,
326+ )
324327
325328 await executeBulkOperation ( {
326329 organization : mockOrganization ,
@@ -330,6 +333,13 @@ describe('executeBulkOperation', () => {
330333 watch : true ,
331334 } )
332335
336+ expect ( watchBulkOperation ) . toHaveBeenCalledWith (
337+ mockAdminSession ,
338+ createdBulkOperation . id ,
339+ expect . any ( Object ) ,
340+ expect . any ( Function ) ,
341+ )
342+ expect ( shortBulkOperationPoll ) . not . toHaveBeenCalled ( )
333343 expect ( renderSuccess ) . toHaveBeenCalledWith (
334344 expect . objectContaining ( {
335345 headline : expect . stringContaining ( 'Bulk operation succeeded:' ) ,
@@ -370,10 +380,64 @@ describe('executeBulkOperation', () => {
370380 expect ( downloadBulkOperationResults ) . not . toHaveBeenCalled ( )
371381 } )
372382
383+ test ( 'uses quickWatchBulkOperation (not watchBulkOperation) when watch flag is false' , async ( ) => {
384+ const query = '{ products { edges { node { id } } } }'
385+ const mockResponse : BulkOperationRunQueryMutation [ 'bulkOperationRunQuery' ] = {
386+ bulkOperation : createdBulkOperation ,
387+ userErrors : [ ] ,
388+ }
389+
390+ vi . mocked ( runBulkOperationQuery ) . mockResolvedValue ( mockResponse )
391+ vi . mocked ( shortBulkOperationPoll ) . mockResolvedValue ( createdBulkOperation )
392+
393+ await executeBulkOperation ( {
394+ organization : mockOrganization ,
395+ remoteApp : mockRemoteApp ,
396+ storeFqdn,
397+ query,
398+ watch : false ,
399+ } )
400+
401+ expect ( shortBulkOperationPoll ) . toHaveBeenCalledWith ( mockAdminSession , createdBulkOperation . id )
402+ expect ( watchBulkOperation ) . not . toHaveBeenCalled ( )
403+ } )
404+
405+ test ( 'renders info message when quickWatchBulkOperation returns RUNNING status' , async ( ) => {
406+ const query = '{ products { edges { node { id } } } }'
407+ const runningOperation = {
408+ ...createdBulkOperation ,
409+ status : 'RUNNING' as const ,
410+ objectCount : '50' ,
411+ }
412+ const mockResponse : BulkOperationRunQueryMutation [ 'bulkOperationRunQuery' ] = {
413+ bulkOperation : createdBulkOperation ,
414+ userErrors : [ ] ,
415+ }
416+
417+ vi . mocked ( runBulkOperationQuery ) . mockResolvedValue ( mockResponse )
418+ vi . mocked ( shortBulkOperationPoll ) . mockResolvedValue ( runningOperation )
419+
420+ await executeBulkOperation ( {
421+ organization : mockOrganization ,
422+ remoteApp : mockRemoteApp ,
423+ storeFqdn,
424+ query,
425+ watch : false ,
426+ } )
427+
428+ expect ( renderSuccess ) . toHaveBeenCalledWith (
429+ expect . objectContaining ( {
430+ headline : 'Bulk operation is running.' ,
431+ body : [ 'Monitor its progress with:\n' , { command : expect . stringContaining ( 'shopify app bulk status' ) } ] ,
432+ } ) ,
433+ )
434+ } )
435+
373436 test ( 'writes results to file when --output-file flag is provided' , async ( ) => {
374437 const query = '{ products { edges { node { id } } } }'
375438 const outputFile = '/tmp/results.jsonl'
376- const resultsContent = '{"id":"gid://shopify/Product/123"}\n{"id":"gid://shopify/Product/456"}'
439+ const resultsContent =
440+ '{"data":{"productCreate":{"product":{"id":"gid://shopify/Product/123"},"userErrors":[]}},"__lineNumber":0}\n{"data":{"productCreate":{"product":{"id":"gid://shopify/Product/456"},"userErrors":[]}},"__lineNumber":1}'
377441
378442 const initialResponse : BulkOperationRunQueryMutation [ 'bulkOperationRunQuery' ] = {
379443 bulkOperation : createdBulkOperation ,
@@ -404,7 +468,8 @@ describe('executeBulkOperation', () => {
404468
405469 test ( 'writes results to stdout when --output-file flag is not provided' , async ( ) => {
406470 const query = '{ products { edges { node { id } } } }'
407- const resultsContent = '{"id":"gid://shopify/Product/123"}\n{"id":"gid://shopify/Product/456"}'
471+ const resultsContent =
472+ '{"data":{"productCreate":{"product":{"id":"gid://shopify/Product/123"},"userErrors":[]}},"__lineNumber":0}\n{"data":{"productCreate":{"product":{"id":"gid://shopify/Product/456"},"userErrors":[]}},"__lineNumber":1}'
408473
409474 const initialResponse : BulkOperationRunQueryMutation [ 'bulkOperationRunQuery' ] = {
410475 bulkOperation : createdBulkOperation ,
@@ -537,4 +602,113 @@ describe('executeBulkOperation', () => {
537602
538603 expect ( validateApiVersion ) . not . toHaveBeenCalled ( )
539604 } )
605+
606+ test ( 'renders warning when completed operation results contain userErrors' , async ( ) => {
607+ const query = '{ products { edges { node { id } } } }'
608+ const resultsWithErrors = '{"data":{"productUpdate":{"userErrors":[{"message":"invalid input"}]}},"__lineNumber":0}'
609+
610+ const initialResponse : BulkOperationRunQueryMutation [ 'bulkOperationRunQuery' ] = {
611+ bulkOperation : createdBulkOperation ,
612+ userErrors : [ ] ,
613+ }
614+ const completedOperation = {
615+ ...createdBulkOperation ,
616+ status : 'COMPLETED' as const ,
617+ url : 'https://example.com/download' ,
618+ objectCount : '1' ,
619+ }
620+
621+ vi . mocked ( runBulkOperationQuery ) . mockResolvedValue ( initialResponse )
622+ vi . mocked ( watchBulkOperation ) . mockResolvedValue ( completedOperation )
623+ vi . mocked ( downloadBulkOperationResults ) . mockResolvedValue ( resultsWithErrors )
624+
625+ await executeBulkOperation ( {
626+ organization : mockOrganization ,
627+ remoteApp : mockRemoteApp ,
628+ storeFqdn,
629+ query,
630+ watch : true ,
631+ } )
632+
633+ expect ( renderWarning ) . toHaveBeenCalledWith (
634+ expect . objectContaining ( {
635+ headline : 'Bulk operation completed with errors.' ,
636+ body : 'Check results for error details.' ,
637+ } ) ,
638+ )
639+ expect ( renderSuccess ) . not . toHaveBeenCalled ( )
640+ } )
641+
642+ test ( 'renders success when completed operation results have no userErrors' , async ( ) => {
643+ const query = '{ products { edges { node { id } } } }'
644+ const resultsWithoutErrors = '{"data":{"productUpdate":{"product":{"id":"123"},"userErrors":[]}},"__lineNumber":0}'
645+
646+ const initialResponse : BulkOperationRunQueryMutation [ 'bulkOperationRunQuery' ] = {
647+ bulkOperation : createdBulkOperation ,
648+ userErrors : [ ] ,
649+ }
650+ const completedOperation = {
651+ ...createdBulkOperation ,
652+ status : 'COMPLETED' as const ,
653+ url : 'https://example.com/download' ,
654+ objectCount : '1' ,
655+ }
656+
657+ vi . mocked ( runBulkOperationQuery ) . mockResolvedValue ( initialResponse )
658+ vi . mocked ( watchBulkOperation ) . mockResolvedValue ( completedOperation )
659+ vi . mocked ( downloadBulkOperationResults ) . mockResolvedValue ( resultsWithoutErrors )
660+
661+ await executeBulkOperation ( {
662+ organization : mockOrganization ,
663+ remoteApp : mockRemoteApp ,
664+ storeFqdn,
665+ query,
666+ watch : true ,
667+ } )
668+
669+ expect ( renderSuccess ) . toHaveBeenCalledWith (
670+ expect . objectContaining ( {
671+ headline : expect . stringContaining ( 'Bulk operation succeeded' ) ,
672+ } ) ,
673+ )
674+ expect ( renderWarning ) . not . toHaveBeenCalled ( )
675+ } )
676+
677+ test ( 'renders warning when results written to file contain userErrors' , async ( ) => {
678+ const query = '{ products { edges { node { id } } } }'
679+ const outputFile = '/tmp/results.jsonl'
680+ const resultsWithErrors = '{"data":{"productUpdate":{"userErrors":[{"message":"invalid input"}]}},"__lineNumber":0}'
681+
682+ const initialResponse : BulkOperationRunQueryMutation [ 'bulkOperationRunQuery' ] = {
683+ bulkOperation : createdBulkOperation ,
684+ userErrors : [ ] ,
685+ }
686+ const completedOperation = {
687+ ...createdBulkOperation ,
688+ status : 'COMPLETED' as const ,
689+ url : 'https://example.com/download' ,
690+ objectCount : '1' ,
691+ }
692+
693+ vi . mocked ( runBulkOperationQuery ) . mockResolvedValue ( initialResponse )
694+ vi . mocked ( watchBulkOperation ) . mockResolvedValue ( completedOperation )
695+ vi . mocked ( downloadBulkOperationResults ) . mockResolvedValue ( resultsWithErrors )
696+
697+ await executeBulkOperation ( {
698+ organization : mockOrganization ,
699+ remoteApp : mockRemoteApp ,
700+ storeFqdn,
701+ query,
702+ watch : true ,
703+ outputFile,
704+ } )
705+
706+ expect ( writeFile ) . toHaveBeenCalledWith ( outputFile , resultsWithErrors )
707+ expect ( renderWarning ) . toHaveBeenCalledWith (
708+ expect . objectContaining ( {
709+ headline : 'Bulk operation completed with errors.' ,
710+ body : `Results written to ${ outputFile } . Check file for error details.` ,
711+ } ) ,
712+ )
713+ } )
540714} )
0 commit comments