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,62 @@ 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+ remoteApp : mockRemoteApp ,
395+ storeFqdn,
396+ query,
397+ watch : false ,
398+ } )
399+
400+ expect ( shortBulkOperationPoll ) . toHaveBeenCalledWith ( mockAdminSession , createdBulkOperation . id )
401+ expect ( watchBulkOperation ) . not . toHaveBeenCalled ( )
402+ } )
403+
404+ test ( 'renders info message when quickWatchBulkOperation returns RUNNING status' , async ( ) => {
405+ const query = '{ products { edges { node { id } } } }'
406+ const runningOperation = {
407+ ...createdBulkOperation ,
408+ status : 'RUNNING' as const ,
409+ objectCount : '50' ,
410+ }
411+ const mockResponse : BulkOperationRunQueryMutation [ 'bulkOperationRunQuery' ] = {
412+ bulkOperation : createdBulkOperation ,
413+ userErrors : [ ] ,
414+ }
415+
416+ vi . mocked ( runBulkOperationQuery ) . mockResolvedValue ( mockResponse )
417+ vi . mocked ( shortBulkOperationPoll ) . mockResolvedValue ( runningOperation )
418+
419+ await executeBulkOperation ( {
420+ remoteApp : mockRemoteApp ,
421+ storeFqdn,
422+ query,
423+ watch : false ,
424+ } )
425+
426+ expect ( renderSuccess ) . toHaveBeenCalledWith (
427+ expect . objectContaining ( {
428+ headline : 'Bulk operation is running.' ,
429+ body : [ 'Monitor its progress with:' , { command : expect . stringContaining ( 'shopify app bulk status' ) } ] ,
430+ } ) ,
431+ )
432+ } )
433+
373434 test ( 'writes results to file when --output-file flag is provided' , async ( ) => {
374435 const query = '{ products { edges { node { id } } } }'
375436 const outputFile = '/tmp/results.jsonl'
376- const resultsContent = '{"id":"gid://shopify/Product/123"}\n{"id":"gid://shopify/Product/456"}'
437+ const resultsContent =
438+ '{"data":{"productCreate":{"product":{"id":"gid://shopify/Product/123"},"userErrors":[]}},"__lineNumber":0}\n{"data":{"productCreate":{"product":{"id":"gid://shopify/Product/456"},"userErrors":[]}},"__lineNumber":1}'
377439
378440 const initialResponse : BulkOperationRunQueryMutation [ 'bulkOperationRunQuery' ] = {
379441 bulkOperation : createdBulkOperation ,
@@ -404,7 +466,8 @@ describe('executeBulkOperation', () => {
404466
405467 test ( 'writes results to stdout when --output-file flag is not provided' , async ( ) => {
406468 const query = '{ products { edges { node { id } } } }'
407- const resultsContent = '{"id":"gid://shopify/Product/123"}\n{"id":"gid://shopify/Product/456"}'
469+ const resultsContent =
470+ '{"data":{"productCreate":{"product":{"id":"gid://shopify/Product/123"},"userErrors":[]}},"__lineNumber":0}\n{"data":{"productCreate":{"product":{"id":"gid://shopify/Product/456"},"userErrors":[]}},"__lineNumber":1}'
408471
409472 const initialResponse : BulkOperationRunQueryMutation [ 'bulkOperationRunQuery' ] = {
410473 bulkOperation : createdBulkOperation ,
@@ -537,4 +600,110 @@ describe('executeBulkOperation', () => {
537600
538601 expect ( validateApiVersion ) . not . toHaveBeenCalled ( )
539602 } )
603+
604+ test ( 'renders warning when completed operation results contain userErrors' , async ( ) => {
605+ const query = '{ products { edges { node { id } } } }'
606+ const resultsWithErrors = '{"data":{"productUpdate":{"userErrors":[{"message":"invalid input"}]}},"__lineNumber":0}'
607+
608+ const initialResponse : BulkOperationRunQueryMutation [ 'bulkOperationRunQuery' ] = {
609+ bulkOperation : createdBulkOperation ,
610+ userErrors : [ ] ,
611+ }
612+ const completedOperation = {
613+ ...createdBulkOperation ,
614+ status : 'COMPLETED' as const ,
615+ url : 'https://example.com/download' ,
616+ objectCount : '1' ,
617+ }
618+
619+ vi . mocked ( runBulkOperationQuery ) . mockResolvedValue ( initialResponse )
620+ vi . mocked ( watchBulkOperation ) . mockResolvedValue ( completedOperation )
621+ vi . mocked ( downloadBulkOperationResults ) . mockResolvedValue ( resultsWithErrors )
622+
623+ await executeBulkOperation ( {
624+ remoteApp : mockRemoteApp ,
625+ storeFqdn,
626+ query,
627+ watch : true ,
628+ } )
629+
630+ expect ( renderWarning ) . toHaveBeenCalledWith (
631+ expect . objectContaining ( {
632+ headline : 'Bulk operation completed with errors.' ,
633+ body : 'Check results for error details.' ,
634+ } ) ,
635+ )
636+ expect ( renderSuccess ) . not . toHaveBeenCalled ( )
637+ } )
638+
639+ test ( 'renders success when completed operation results have no userErrors' , async ( ) => {
640+ const query = '{ products { edges { node { id } } } }'
641+ const resultsWithoutErrors = '{"data":{"productUpdate":{"product":{"id":"123"},"userErrors":[]}},"__lineNumber":0}'
642+
643+ const initialResponse : BulkOperationRunQueryMutation [ 'bulkOperationRunQuery' ] = {
644+ bulkOperation : createdBulkOperation ,
645+ userErrors : [ ] ,
646+ }
647+ const completedOperation = {
648+ ...createdBulkOperation ,
649+ status : 'COMPLETED' as const ,
650+ url : 'https://example.com/download' ,
651+ objectCount : '1' ,
652+ }
653+
654+ vi . mocked ( runBulkOperationQuery ) . mockResolvedValue ( initialResponse )
655+ vi . mocked ( watchBulkOperation ) . mockResolvedValue ( completedOperation )
656+ vi . mocked ( downloadBulkOperationResults ) . mockResolvedValue ( resultsWithoutErrors )
657+
658+ await executeBulkOperation ( {
659+ remoteApp : mockRemoteApp ,
660+ storeFqdn,
661+ query,
662+ watch : true ,
663+ } )
664+
665+ expect ( renderSuccess ) . toHaveBeenCalledWith (
666+ expect . objectContaining ( {
667+ headline : expect . stringContaining ( 'Bulk operation succeeded' ) ,
668+ } ) ,
669+ )
670+ expect ( renderWarning ) . not . toHaveBeenCalled ( )
671+ } )
672+
673+ test ( 'renders warning when results written to file contain userErrors' , async ( ) => {
674+ const query = '{ products { edges { node { id } } } }'
675+ const outputFile = '/tmp/results.jsonl'
676+ const resultsWithErrors = '{"data":{"productUpdate":{"userErrors":[{"message":"invalid input"}]}},"__lineNumber":0}'
677+
678+ const initialResponse : BulkOperationRunQueryMutation [ 'bulkOperationRunQuery' ] = {
679+ bulkOperation : createdBulkOperation ,
680+ userErrors : [ ] ,
681+ }
682+ const completedOperation = {
683+ ...createdBulkOperation ,
684+ status : 'COMPLETED' as const ,
685+ url : 'https://example.com/download' ,
686+ objectCount : '1' ,
687+ }
688+
689+ vi . mocked ( runBulkOperationQuery ) . mockResolvedValue ( initialResponse )
690+ vi . mocked ( watchBulkOperation ) . mockResolvedValue ( completedOperation )
691+ vi . mocked ( downloadBulkOperationResults ) . mockResolvedValue ( resultsWithErrors )
692+
693+ await executeBulkOperation ( {
694+ remoteApp : mockRemoteApp ,
695+ storeFqdn,
696+ query,
697+ watch : true ,
698+ outputFile,
699+ } )
700+
701+ expect ( writeFile ) . toHaveBeenCalledWith ( outputFile , resultsWithErrors )
702+ expect ( renderWarning ) . toHaveBeenCalledWith (
703+ expect . objectContaining ( {
704+ headline : 'Bulk operation completed with errors.' ,
705+ body : `Results written to ${ outputFile } . Check file for error details.` ,
706+ } ) ,
707+ )
708+ } )
540709} )
0 commit comments