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 , quickWatchBulkOperation } 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'
@@ -60,6 +60,7 @@ describe('executeBulkOperation', () => {
6060
6161 beforeEach ( ( ) => {
6262 vi . mocked ( ensureAuthenticatedAdminAsApp ) . mockResolvedValue ( mockAdminSession )
63+ vi . mocked ( quickWatchBulkOperation ) . mockResolvedValue ( createdBulkOperation )
6364 } )
6465
6566 afterEach ( ( ) => {
@@ -288,7 +289,7 @@ describe('executeBulkOperation', () => {
288289 } )
289290 } )
290291
291- test ( 'waits for operation to finish and renders success when watch is provided and operation finishes with COMPLETED status ' , async ( ) => {
292+ test ( 'uses watchBulkOperation (not quickWatchBulkOperation) when watch flag is true ' , async ( ) => {
292293 const query = '{ products { edges { node { id } } } }'
293294 const initialResponse : BulkOperationRunQueryMutation [ 'bulkOperationRunQuery' ] = {
294295 bulkOperation : createdBulkOperation ,
@@ -303,7 +304,9 @@ describe('executeBulkOperation', () => {
303304
304305 vi . mocked ( runBulkOperationQuery ) . mockResolvedValue ( initialResponse )
305306 vi . mocked ( watchBulkOperation ) . mockResolvedValue ( completedOperation )
306- vi . mocked ( downloadBulkOperationResults ) . mockResolvedValue ( '{"id":"gid://shopify/Product/123"}' )
307+ vi . mocked ( downloadBulkOperationResults ) . mockResolvedValue (
308+ '{"data":{"products":{"edges":[{"node":{"id":"gid://shopify/Product/123"}}],"userErrors":[]}},"__lineNumber":0}' ,
309+ )
307310
308311 await executeBulkOperation ( {
309312 remoteApp : mockRemoteApp ,
@@ -312,6 +315,13 @@ describe('executeBulkOperation', () => {
312315 watch : true ,
313316 } )
314317
318+ expect ( watchBulkOperation ) . toHaveBeenCalledWith (
319+ mockAdminSession ,
320+ createdBulkOperation . id ,
321+ expect . any ( Object ) ,
322+ expect . any ( Function ) ,
323+ )
324+ expect ( quickWatchBulkOperation ) . not . toHaveBeenCalled ( )
315325 expect ( renderSuccess ) . toHaveBeenCalledWith (
316326 expect . objectContaining ( {
317327 headline : expect . stringContaining ( 'Bulk operation succeeded:' ) ,
@@ -351,10 +361,62 @@ describe('executeBulkOperation', () => {
351361 expect ( downloadBulkOperationResults ) . not . toHaveBeenCalled ( )
352362 } )
353363
364+ test ( 'uses quickWatchBulkOperation (not watchBulkOperation) when watch flag is false' , async ( ) => {
365+ const query = '{ products { edges { node { id } } } }'
366+ const mockResponse : BulkOperationRunQueryMutation [ 'bulkOperationRunQuery' ] = {
367+ bulkOperation : createdBulkOperation ,
368+ userErrors : [ ] ,
369+ }
370+
371+ vi . mocked ( runBulkOperationQuery ) . mockResolvedValue ( mockResponse )
372+ vi . mocked ( quickWatchBulkOperation ) . mockResolvedValue ( createdBulkOperation )
373+
374+ await executeBulkOperation ( {
375+ remoteApp : mockRemoteApp ,
376+ storeFqdn,
377+ query,
378+ watch : false ,
379+ } )
380+
381+ expect ( quickWatchBulkOperation ) . toHaveBeenCalledWith ( mockAdminSession , createdBulkOperation . id )
382+ expect ( watchBulkOperation ) . not . toHaveBeenCalled ( )
383+ } )
384+
385+ test ( 'renders info message when quickWatchBulkOperation returns RUNNING status' , async ( ) => {
386+ const query = '{ products { edges { node { id } } } }'
387+ const runningOperation = {
388+ ...createdBulkOperation ,
389+ status : 'RUNNING' as const ,
390+ objectCount : '50' ,
391+ }
392+ const mockResponse : BulkOperationRunQueryMutation [ 'bulkOperationRunQuery' ] = {
393+ bulkOperation : createdBulkOperation ,
394+ userErrors : [ ] ,
395+ }
396+
397+ vi . mocked ( runBulkOperationQuery ) . mockResolvedValue ( mockResponse )
398+ vi . mocked ( quickWatchBulkOperation ) . mockResolvedValue ( runningOperation )
399+
400+ await executeBulkOperation ( {
401+ remoteApp : mockRemoteApp ,
402+ storeFqdn,
403+ query,
404+ watch : false ,
405+ } )
406+
407+ expect ( renderSuccess ) . toHaveBeenCalledWith (
408+ expect . objectContaining ( {
409+ headline : 'Bulk operation is running.' ,
410+ body : [ 'Monitor its progress with:' , { command : expect . stringContaining ( 'shopify app bulk status' ) } ] ,
411+ } ) ,
412+ )
413+ } )
414+
354415 test ( 'writes results to file when --output-file flag is provided' , async ( ) => {
355416 const query = '{ products { edges { node { id } } } }'
356417 const outputFile = '/tmp/results.jsonl'
357- const resultsContent = '{"id":"gid://shopify/Product/123"}\n{"id":"gid://shopify/Product/456"}'
418+ const resultsContent =
419+ '{"data":{"productCreate":{"product":{"id":"gid://shopify/Product/123"},"userErrors":[]}},"__lineNumber":0}\n{"data":{"productCreate":{"product":{"id":"gid://shopify/Product/456"},"userErrors":[]}},"__lineNumber":1}'
358420
359421 const initialResponse : BulkOperationRunQueryMutation [ 'bulkOperationRunQuery' ] = {
360422 bulkOperation : createdBulkOperation ,
@@ -384,7 +446,8 @@ describe('executeBulkOperation', () => {
384446
385447 test ( 'writes results to stdout when --output-file flag is not provided' , async ( ) => {
386448 const query = '{ products { edges { node { id } } } }'
387- const resultsContent = '{"id":"gid://shopify/Product/123"}\n{"id":"gid://shopify/Product/456"}'
449+ const resultsContent =
450+ '{"data":{"productCreate":{"product":{"id":"gid://shopify/Product/123"},"userErrors":[]}},"__lineNumber":0}\n{"data":{"productCreate":{"product":{"id":"gid://shopify/Product/456"},"userErrors":[]}},"__lineNumber":1}'
388451
389452 const initialResponse : BulkOperationRunQueryMutation [ 'bulkOperationRunQuery' ] = {
390453 bulkOperation : createdBulkOperation ,
@@ -512,4 +575,110 @@ describe('executeBulkOperation', () => {
512575
513576 expect ( validateApiVersion ) . not . toHaveBeenCalled ( )
514577 } )
578+
579+ test ( 'renders warning when completed operation results contain userErrors' , async ( ) => {
580+ const query = '{ products { edges { node { id } } } }'
581+ const resultsWithErrors = '{"data":{"productUpdate":{"userErrors":[{"message":"invalid input"}]}},"__lineNumber":0}'
582+
583+ const initialResponse : BulkOperationRunQueryMutation [ 'bulkOperationRunQuery' ] = {
584+ bulkOperation : createdBulkOperation ,
585+ userErrors : [ ] ,
586+ }
587+ const completedOperation = {
588+ ...createdBulkOperation ,
589+ status : 'COMPLETED' as const ,
590+ url : 'https://example.com/download' ,
591+ objectCount : '1' ,
592+ }
593+
594+ vi . mocked ( runBulkOperationQuery ) . mockResolvedValue ( initialResponse )
595+ vi . mocked ( watchBulkOperation ) . mockResolvedValue ( completedOperation )
596+ vi . mocked ( downloadBulkOperationResults ) . mockResolvedValue ( resultsWithErrors )
597+
598+ await executeBulkOperation ( {
599+ remoteApp : mockRemoteApp ,
600+ storeFqdn,
601+ query,
602+ watch : true ,
603+ } )
604+
605+ expect ( renderWarning ) . toHaveBeenCalledWith (
606+ expect . objectContaining ( {
607+ headline : 'Bulk operation completed with errors.' ,
608+ body : 'Check results for error details.' ,
609+ } ) ,
610+ )
611+ expect ( renderSuccess ) . not . toHaveBeenCalled ( )
612+ } )
613+
614+ test ( 'renders success when completed operation results have no userErrors' , async ( ) => {
615+ const query = '{ products { edges { node { id } } } }'
616+ const resultsWithoutErrors = '{"data":{"productUpdate":{"product":{"id":"123"},"userErrors":[]}},"__lineNumber":0}'
617+
618+ const initialResponse : BulkOperationRunQueryMutation [ 'bulkOperationRunQuery' ] = {
619+ bulkOperation : createdBulkOperation ,
620+ userErrors : [ ] ,
621+ }
622+ const completedOperation = {
623+ ...createdBulkOperation ,
624+ status : 'COMPLETED' as const ,
625+ url : 'https://example.com/download' ,
626+ objectCount : '1' ,
627+ }
628+
629+ vi . mocked ( runBulkOperationQuery ) . mockResolvedValue ( initialResponse )
630+ vi . mocked ( watchBulkOperation ) . mockResolvedValue ( completedOperation )
631+ vi . mocked ( downloadBulkOperationResults ) . mockResolvedValue ( resultsWithoutErrors )
632+
633+ await executeBulkOperation ( {
634+ remoteApp : mockRemoteApp ,
635+ storeFqdn,
636+ query,
637+ watch : true ,
638+ } )
639+
640+ expect ( renderSuccess ) . toHaveBeenCalledWith (
641+ expect . objectContaining ( {
642+ headline : expect . stringContaining ( 'Bulk operation succeeded' ) ,
643+ } ) ,
644+ )
645+ expect ( renderWarning ) . not . toHaveBeenCalled ( )
646+ } )
647+
648+ test ( 'renders warning when results written to file contain userErrors' , async ( ) => {
649+ const query = '{ products { edges { node { id } } } }'
650+ const outputFile = '/tmp/results.jsonl'
651+ const resultsWithErrors = '{"data":{"productUpdate":{"userErrors":[{"message":"invalid input"}]}},"__lineNumber":0}'
652+
653+ const initialResponse : BulkOperationRunQueryMutation [ 'bulkOperationRunQuery' ] = {
654+ bulkOperation : createdBulkOperation ,
655+ userErrors : [ ] ,
656+ }
657+ const completedOperation = {
658+ ...createdBulkOperation ,
659+ status : 'COMPLETED' as const ,
660+ url : 'https://example.com/download' ,
661+ objectCount : '1' ,
662+ }
663+
664+ vi . mocked ( runBulkOperationQuery ) . mockResolvedValue ( initialResponse )
665+ vi . mocked ( watchBulkOperation ) . mockResolvedValue ( completedOperation )
666+ vi . mocked ( downloadBulkOperationResults ) . mockResolvedValue ( resultsWithErrors )
667+
668+ await executeBulkOperation ( {
669+ remoteApp : mockRemoteApp ,
670+ storeFqdn,
671+ query,
672+ watch : true ,
673+ outputFile,
674+ } )
675+
676+ expect ( writeFile ) . toHaveBeenCalledWith ( outputFile , resultsWithErrors )
677+ expect ( renderWarning ) . toHaveBeenCalledWith (
678+ expect . objectContaining ( {
679+ headline : 'Bulk operation completed with errors.' ,
680+ body : `Results written to ${ outputFile } . Check file for error details.` ,
681+ } ) ,
682+ )
683+ } )
515684} )
0 commit comments