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'
45import { AppLinkedInterface } from '../../models/app/app.js'
5- import { renderSuccess , renderWarning } from '@shopify/cli-kit/node/ui'
6+ import { BulkOperationRunQueryMutation } from '../../api/graphql/bulk-operations/generated/bulk-operation-run-query.js'
7+ import { BulkOperationRunMutationMutation } from '../../api/graphql/bulk-operations/generated/bulk-operation-run-mutation.js'
8+ import { renderSuccess , renderWarning , renderError } from '@shopify/cli-kit/node/ui'
69import { ensureAuthenticatedAdmin } from '@shopify/cli-kit/node/session'
710import { inTemporaryDirectory , writeFile } from '@shopify/cli-kit/node/fs'
811import { joinPath } from '@shopify/cli-kit/node/path'
912import { describe , test , expect , vi , beforeEach } from 'vitest'
1013
1114vi . mock ( './run-query.js' )
1215vi . mock ( './run-mutation.js' )
16+ vi . mock ( './watch-bulk-operation.js' )
1317vi . mock ( '@shopify/cli-kit/node/ui' )
1418vi . mock ( '@shopify/cli-kit/node/session' )
1519
@@ -21,14 +25,21 @@ describe('executeBulkOperation', () => {
2125 const storeFqdn = 'test-store.myshopify.com'
2226 const mockAdminSession = { token : 'test-token' , storeFqdn}
2327
24- const successfulBulkOperation = {
28+ const createdBulkOperation : NonNullable <
29+ NonNullable < BulkOperationRunQueryMutation [ 'bulkOperationRunQuery' ] > [ 'bulkOperation' ]
30+ > = {
2531 id : 'gid://shopify/BulkOperation/123' ,
2632 status : 'CREATED' ,
2733 errorCode : null ,
2834 createdAt : '2024-01-01T00:00:00Z' ,
2935 objectCount : '0' ,
3036 fileSize : '0' ,
3137 url : null ,
38+ query : '{ products { edges { node { id } } } }' ,
39+ rootObjectCount : '0' ,
40+ type : 'QUERY' ,
41+ completedAt : null ,
42+ partialDataUrl : null ,
3243 }
3344
3445 beforeEach ( ( ) => {
@@ -37,11 +48,11 @@ describe('executeBulkOperation', () => {
3748
3849 test ( 'runs query operation when GraphQL document starts with query' , async ( ) => {
3950 const query = 'query { products { edges { node { id } } } }'
40- const mockResponse = {
41- bulkOperation : successfulBulkOperation ,
51+ const mockResponse : BulkOperationRunQueryMutation [ 'bulkOperationRunQuery' ] = {
52+ bulkOperation : createdBulkOperation ,
4253 userErrors : [ ] ,
4354 }
44- vi . mocked ( runBulkOperationQuery ) . mockResolvedValue ( mockResponse as any )
55+ vi . mocked ( runBulkOperationQuery ) . mockResolvedValue ( mockResponse )
4556
4657 await executeBulkOperation ( {
4758 app : mockApp ,
@@ -58,11 +69,11 @@ describe('executeBulkOperation', () => {
5869
5970 test ( 'runs query operation when GraphQL document starts with curly brace' , async ( ) => {
6071 const query = '{ products { edges { node { id } } } }'
61- const mockResponse = {
62- bulkOperation : successfulBulkOperation ,
72+ const mockResponse : BulkOperationRunQueryMutation [ 'bulkOperationRunQuery' ] = {
73+ bulkOperation : createdBulkOperation ,
6374 userErrors : [ ] ,
6475 }
65- vi . mocked ( runBulkOperationQuery ) . mockResolvedValue ( mockResponse as any )
76+ vi . mocked ( runBulkOperationQuery ) . mockResolvedValue ( mockResponse )
6677
6778 await executeBulkOperation ( {
6879 app : mockApp ,
@@ -79,11 +90,11 @@ describe('executeBulkOperation', () => {
7990
8091 test ( 'runs mutation operation when GraphQL document starts with mutation' , async ( ) => {
8192 const mutation = 'mutation productUpdate($input: ProductInput!) { productUpdate(input: $input) { product { id } } }'
82- const mockResponse = {
83- bulkOperation : successfulBulkOperation ,
93+ const mockResponse : BulkOperationRunMutationMutation [ 'bulkOperationRunMutation' ] = {
94+ bulkOperation : createdBulkOperation ,
8495 userErrors : [ ] ,
8596 }
86- vi . mocked ( runBulkOperationMutation ) . mockResolvedValue ( mockResponse as any )
97+ vi . mocked ( runBulkOperationMutation ) . mockResolvedValue ( mockResponse )
8798
8899 await executeBulkOperation ( {
89100 app : mockApp ,
@@ -102,11 +113,11 @@ describe('executeBulkOperation', () => {
102113 test ( 'passes variables parameter to runBulkOperationMutation when variables are provided' , async ( ) => {
103114 const mutation = 'mutation productUpdate($input: ProductInput!) { productUpdate(input: $input) { product { id } } }'
104115 const variables = [ '{"input":{"id":"gid://shopify/Product/123","tags":["test"]}}' ]
105- const mockResponse = {
106- bulkOperation : successfulBulkOperation ,
116+ const mockResponse : BulkOperationRunMutationMutation [ 'bulkOperationRunMutation' ] = {
117+ bulkOperation : createdBulkOperation ,
107118 userErrors : [ ] ,
108119 }
109- vi . mocked ( runBulkOperationMutation ) . mockResolvedValue ( mockResponse as any )
120+ vi . mocked ( runBulkOperationMutation ) . mockResolvedValue ( mockResponse )
110121
111122 await executeBulkOperation ( {
112123 app : mockApp ,
@@ -124,33 +135,34 @@ describe('executeBulkOperation', () => {
124135
125136 test ( 'renders success message when bulk operation returns without user errors' , async ( ) => {
126137 const query = '{ products { edges { node { id } } } }'
127- const mockResponse = {
128- bulkOperation : successfulBulkOperation ,
138+ const mockResponse : BulkOperationRunQueryMutation [ 'bulkOperationRunQuery' ] = {
139+ bulkOperation : createdBulkOperation ,
129140 userErrors : [ ] ,
130141 }
131- vi . mocked ( runBulkOperationQuery ) . mockResolvedValue ( mockResponse as any )
142+ vi . mocked ( runBulkOperationQuery ) . mockResolvedValue ( mockResponse )
132143 await executeBulkOperation ( {
133144 app : mockApp ,
134145 storeFqdn,
135146 query,
136147 } )
137148
138- expect ( renderSuccess ) . toHaveBeenCalledWith ( {
139- headline : 'Bulk operation started successfully!' ,
140- body : 'Congrats!' ,
141- } )
149+ expect ( renderSuccess ) . toHaveBeenCalledWith (
150+ expect . objectContaining ( {
151+ headline : 'Bulk operation started.' ,
152+ } ) ,
153+ )
142154 } )
143155
144156 test ( 'renders warning with formatted field errors when bulk operation returns user errors' , async ( ) => {
145157 const query = '{ products { edges { node { id } } } }'
146- const mockResponse = {
158+ const mockResponse : BulkOperationRunQueryMutation [ 'bulkOperationRunQuery' ] = {
147159 bulkOperation : null ,
148160 userErrors : [
149- { field : [ 'query' ] , message : 'Invalid query syntax' } ,
150- { field : null , message : 'Another error' } ,
161+ { field : [ 'query' ] , message : 'Invalid query syntax' , code : null } ,
162+ { field : null , message : 'Another error' , code : null } ,
151163 ] ,
152164 }
153- vi . mocked ( runBulkOperationQuery ) . mockResolvedValue ( mockResponse as any )
165+ vi . mocked ( runBulkOperationQuery ) . mockResolvedValue ( mockResponse )
154166
155167 await executeBulkOperation ( {
156168 app : mockApp ,
@@ -229,7 +241,7 @@ describe('executeBulkOperation', () => {
229241 const mutation =
230242 'mutation productUpdate($input: ProductInput!) { productUpdate(input: $input) { product { id } } }'
231243 const mockResponse = {
232- bulkOperation : successfulBulkOperation ,
244+ bulkOperation : createdBulkOperation ,
233245 userErrors : [ ] ,
234246 }
235247 vi . mocked ( runBulkOperationMutation ) . mockResolvedValue ( mockResponse as any )
@@ -306,4 +318,70 @@ describe('executeBulkOperation', () => {
306318 expect ( runBulkOperationMutation ) . not . toHaveBeenCalled ( )
307319 } )
308320 } )
321+
322+ test ( 'waits for operation to finish and renders success when watch is provided and operation finishes with COMPLETED status' , async ( ) => {
323+ const query = '{ products { edges { node { id } } } }'
324+ const initialResponse : BulkOperationRunQueryMutation [ 'bulkOperationRunQuery' ] = {
325+ bulkOperation : createdBulkOperation ,
326+ userErrors : [ ] ,
327+ }
328+ const completedOperation = {
329+ ...createdBulkOperation ,
330+ status : 'COMPLETED' as const ,
331+ url : 'https://example.com/download' ,
332+ objectCount : '650' ,
333+ }
334+
335+ vi . mocked ( runBulkOperationQuery ) . mockResolvedValue ( initialResponse )
336+ vi . mocked ( watchBulkOperation ) . mockResolvedValue ( completedOperation )
337+
338+ await executeBulkOperation ( {
339+ app : mockApp ,
340+ storeFqdn,
341+ query,
342+ watch : true ,
343+ } )
344+
345+ expect ( watchBulkOperation ) . toHaveBeenCalledWith ( mockAdminSession , createdBulkOperation . id )
346+ expect ( renderSuccess ) . toHaveBeenCalledWith (
347+ expect . objectContaining ( {
348+ headline : expect . stringContaining ( 'Bulk operation succeeded.' ) ,
349+ body : expect . arrayContaining ( [ expect . stringContaining ( 'https://example.com/download' ) ] ) ,
350+ } ) ,
351+ )
352+ } )
353+
354+ test . each ( [ 'FAILED' , 'CANCELED' , 'EXPIRED' ] as const ) (
355+ 'waits for operation to finish and renders error when watch is provided and operation finishes with %s status' ,
356+ async ( status ) => {
357+ const query = '{ products { edges { node { id } } } }'
358+ const initialResponse : BulkOperationRunQueryMutation [ 'bulkOperationRunQuery' ] = {
359+ bulkOperation : createdBulkOperation ,
360+ userErrors : [ ] ,
361+ }
362+ const finishedOperation = {
363+ ...createdBulkOperation ,
364+ status,
365+ objectCount : '100' ,
366+ }
367+
368+ vi . mocked ( runBulkOperationQuery ) . mockResolvedValue ( initialResponse )
369+ vi . mocked ( watchBulkOperation ) . mockResolvedValue ( finishedOperation )
370+
371+ await executeBulkOperation ( {
372+ app : mockApp ,
373+ storeFqdn,
374+ query,
375+ watch : true ,
376+ } )
377+
378+ expect ( watchBulkOperation ) . toHaveBeenCalledWith ( mockAdminSession , createdBulkOperation . id )
379+ expect ( renderError ) . toHaveBeenCalledWith (
380+ expect . objectContaining ( {
381+ headline : expect . any ( String ) ,
382+ customSections : expect . any ( Array ) ,
383+ } ) ,
384+ )
385+ } ,
386+ )
309387} )
0 commit comments