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 { describe , test , expect , vi , beforeEach } from 'vitest'
811
912vi . mock ( './run-query.js' )
1013vi . mock ( './run-mutation.js' )
14+ vi . mock ( './watch-bulk-operation.js' )
1115vi . mock ( '@shopify/cli-kit/node/ui' )
1216vi . mock ( '@shopify/cli-kit/node/session' )
1317
@@ -19,14 +23,21 @@ describe('executeBulkOperation', () => {
1923 const storeFqdn = 'test-store.myshopify.com'
2024 const mockAdminSession = { token : 'test-token' , storeFqdn}
2125
22- const successfulBulkOperation = {
26+ const createdBulkOperation : NonNullable <
27+ NonNullable < BulkOperationRunQueryMutation [ 'bulkOperationRunQuery' ] > [ 'bulkOperation' ]
28+ > = {
2329 id : 'gid://shopify/BulkOperation/123' ,
2430 status : 'CREATED' ,
2531 errorCode : null ,
2632 createdAt : '2024-01-01T00:00:00Z' ,
2733 objectCount : '0' ,
2834 fileSize : '0' ,
2935 url : null ,
36+ query : '{ products { edges { node { id } } } }' ,
37+ rootObjectCount : '0' ,
38+ type : 'QUERY' ,
39+ completedAt : null ,
40+ partialDataUrl : null ,
3041 }
3142
3243 beforeEach ( ( ) => {
@@ -35,11 +46,11 @@ describe('executeBulkOperation', () => {
3546
3647 test ( 'runs query operation when GraphQL document starts with query' , async ( ) => {
3748 const query = 'query { products { edges { node { id } } } }'
38- const mockResponse = {
39- bulkOperation : successfulBulkOperation ,
49+ const mockResponse : BulkOperationRunQueryMutation [ 'bulkOperationRunQuery' ] = {
50+ bulkOperation : createdBulkOperation ,
4051 userErrors : [ ] ,
4152 }
42- vi . mocked ( runBulkOperationQuery ) . mockResolvedValue ( mockResponse as any )
53+ vi . mocked ( runBulkOperationQuery ) . mockResolvedValue ( mockResponse )
4354
4455 await executeBulkOperation ( {
4556 app : mockApp ,
@@ -57,11 +68,11 @@ describe('executeBulkOperation', () => {
5768
5869 test ( 'runs query operation when GraphQL document starts with curly brace' , async ( ) => {
5970 const query = '{ products { edges { node { id } } } }'
60- const mockResponse = {
61- bulkOperation : successfulBulkOperation ,
71+ const mockResponse : BulkOperationRunQueryMutation [ 'bulkOperationRunQuery' ] = {
72+ bulkOperation : createdBulkOperation ,
6273 userErrors : [ ] ,
6374 }
64- vi . mocked ( runBulkOperationQuery ) . mockResolvedValue ( mockResponse as any )
75+ vi . mocked ( runBulkOperationQuery ) . mockResolvedValue ( mockResponse )
6576
6677 await executeBulkOperation ( {
6778 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 to mutation when provided with `--variables` flag' , 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 is created' , 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 when user errors are present' , 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 ,
@@ -165,4 +177,70 @@ describe('executeBulkOperation', () => {
165177
166178 expect ( renderSuccess ) . not . toHaveBeenCalled ( )
167179 } )
180+
181+ test ( 'waits for operation to finish and renders success when watch is provided and operation finishes with COMPLETED status' , async ( ) => {
182+ const query = '{ products { edges { node { id } } } }'
183+ const initialResponse : BulkOperationRunQueryMutation [ 'bulkOperationRunQuery' ] = {
184+ bulkOperation : createdBulkOperation ,
185+ userErrors : [ ] ,
186+ }
187+ const completedOperation = {
188+ ...createdBulkOperation ,
189+ status : 'COMPLETED' as const ,
190+ url : 'https://example.com/download' ,
191+ objectCount : '650' ,
192+ }
193+
194+ vi . mocked ( runBulkOperationQuery ) . mockResolvedValue ( initialResponse )
195+ vi . mocked ( watchBulkOperation ) . mockResolvedValue ( completedOperation )
196+
197+ await executeBulkOperation ( {
198+ app : mockApp ,
199+ storeFqdn,
200+ query,
201+ watch : true ,
202+ } )
203+
204+ expect ( watchBulkOperation ) . toHaveBeenCalledWith ( mockAdminSession , createdBulkOperation . id )
205+ expect ( renderSuccess ) . toHaveBeenCalledWith (
206+ expect . objectContaining ( {
207+ headline : expect . stringContaining ( 'Bulk operation succeeded.' ) ,
208+ body : expect . arrayContaining ( [ expect . stringContaining ( 'https://example.com/download' ) ] ) ,
209+ } ) ,
210+ )
211+ } )
212+
213+ test . each ( [ 'FAILED' , 'CANCELED' , 'EXPIRED' ] as const ) (
214+ 'waits for operation to finish and renders error when watch is provided and operation finishes with %s status' ,
215+ async ( status ) => {
216+ const query = '{ products { edges { node { id } } } }'
217+ const initialResponse : BulkOperationRunQueryMutation [ 'bulkOperationRunQuery' ] = {
218+ bulkOperation : createdBulkOperation ,
219+ userErrors : [ ] ,
220+ }
221+ const finishedOperation = {
222+ ...createdBulkOperation ,
223+ status,
224+ objectCount : '100' ,
225+ }
226+
227+ vi . mocked ( runBulkOperationQuery ) . mockResolvedValue ( initialResponse )
228+ vi . mocked ( watchBulkOperation ) . mockResolvedValue ( finishedOperation )
229+
230+ await executeBulkOperation ( {
231+ app : mockApp ,
232+ storeFqdn,
233+ query,
234+ watch : true ,
235+ } )
236+
237+ expect ( watchBulkOperation ) . toHaveBeenCalledWith ( mockAdminSession , createdBulkOperation . id )
238+ expect ( renderError ) . toHaveBeenCalledWith (
239+ expect . objectContaining ( {
240+ headline : expect . any ( String ) ,
241+ customSections : expect . any ( Array ) ,
242+ } ) ,
243+ )
244+ } ,
245+ )
168246} )
0 commit comments