Skip to content

Commit e7baa3a

Browse files
authored
Keep cancellation coverage focused on its own output behavior
1 parent 01fdcfe commit e7baa3a

4 files changed

Lines changed: 77 additions & 55 deletions

File tree

‎packages/app/src/cli/commands/app/bulk/output.test.ts‎

Lines changed: 49 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
1-
import {testBulkOperation, testBulkOperationContext} from '../../../services/bulk-operations/bulk-operation.test-data.js'
21
import BulkStatus from './status.js'
32
import BulkCancel from './cancel.js'
3+
import {
4+
testBulkOperation,
5+
testBulkOperationContext,
6+
} from '../../../services/bulk-operations/bulk-operation.test-data.js'
47
import {prepareAppStoreContext} from '../../../utilities/execute-command-helpers.js'
58
import {createAdminSessionAsApp, resolveApiVersion} from '../../../services/graphql/common.js'
69
import {
710
bulkOperationStatusJsonOutputSchema,
811
cancelBulkOperationJsonOutputSchema,
9-
1012
} from '../../../services/bulk-operations/types.js'
11-
import {fetchBulkOperationById, fetchRecentBulkOperations, cancelBulkOperationRequest} from '@shopify/cli-kit/node/api/bulk-operations'
13+
import {
14+
fetchBulkOperationById,
15+
fetchRecentBulkOperations,
16+
cancelBulkOperationRequest,
17+
} from '@shopify/cli-kit/node/api/bulk-operations'
1218
import {ensureAuthenticatedAdminAsApp} from '@shopify/cli-kit/node/session'
1319
import {runWithCommandEventsForCommand} from '@shopify/cli-kit/node/command-events'
1420
import {withCapturedStandardStreams} from '@shopify/cli-kit/node/testing/output'
@@ -45,7 +51,11 @@ afterEach(() => {
4551
})
4652

4753
function bulkOperation(overrides: Partial<BulkOperation> = {}): BulkOperation {
48-
return testBulkOperation({objectCount: '900719925474099312345', createdAt: '2026-09-01T02:00:00.789+02:00', ...overrides})
54+
return testBulkOperation({
55+
objectCount: '900719925474099312345',
56+
createdAt: '2026-09-01T02:00:00.789+02:00',
57+
...overrides,
58+
})
4959
}
5060

5161
function expectedOperation() {
@@ -146,47 +156,57 @@ test('cancellation emits one result through the real service, codec, presenter,
146156
})
147157
})
148158

149-
test.each([
150-
{name: 'cancel', Command: BulkCancel},
151-
])('$name user errors reach the fatal envelope without a success document', async ({Command}) => {
152-
const userErrors = [{field: ['id'], message: 'Operation rejected'}]
153-
vi.mocked(cancelBulkOperationRequest).mockResolvedValue({bulkOperation: null, userErrors})
154-
155-
vi.stubEnv('SHOPIFY_FLAG_JSON', '1')
156-
await withCapturedStandardStreams(async ({stdout, stderr}) => {
157-
const argv = ['--id', '123']
158-
try {
159-
await runCommand(Command, [...argv, '--json'])
160-
expect.fail('The command must reject the operation.')
161-
} catch (error) {
162-
if (!(error instanceof AbortError)) throw error
163-
expect(error).toMatchObject({details: {userErrors}})
164-
expect(stdout()).toBe('')
165-
await handler(error)
166-
}
167-
expect(JSON.parse(stdout())).toMatchObject({error: {type: 'abort', details: {userErrors}}})
168-
assertDiagnosticEvents(stderr(), 'Canceling bulk operation.')
169-
})
170-
})
159+
test.each([{name: 'cancel', Command: BulkCancel}])(
160+
'$name user errors reach the fatal envelope without a success document',
161+
async ({Command}) => {
162+
const userErrors = [{field: ['id'], message: 'Operation rejected'}]
163+
vi.mocked(cancelBulkOperationRequest).mockResolvedValue({bulkOperation: null, userErrors})
164+
165+
vi.stubEnv('SHOPIFY_FLAG_JSON', '1')
166+
await withCapturedStandardStreams(async ({stdout, stderr}) => {
167+
const argv = ['--id', '123']
168+
try {
169+
await runCommand(Command, [...argv, '--json'])
170+
expect.fail('The command must reject the operation.')
171+
} catch (error) {
172+
if (!(error instanceof AbortError)) throw error
173+
expect(error).toMatchObject({details: {userErrors}})
174+
expect(stdout()).toBe('')
175+
await handler(error)
176+
}
177+
expect(JSON.parse(stdout())).toMatchObject({error: {type: 'abort', details: {userErrors}}})
178+
assertDiagnosticEvents(stderr(), 'Canceling bulk operation.')
179+
})
180+
},
181+
)
171182

172183
test.each([
173184
{name: 'status', Command: BulkStatus, schema: bulkOperationStatusJsonOutputSchema},
174185
{name: 'cancel', Command: BulkCancel, schema: cancelBulkOperationJsonOutputSchema},
175-
176186
] as const)('exposes the schema, JSON flag, and help for $name', ({Command, schema}) => {
177187
expect(Command.jsonOutputSchema).toBe(schema)
178188
expect(Command.flags.json).toBeDefined()
179189
expect(Command.baseFlags).toHaveProperty('json-schema')
180190
expect(Command.descriptionForHelp()).toContain(schema.name)
181191
})
182-
test.each([{json: true, noInput: false}, {json: true, noInput: true}, {json: false, noInput: false}, {json: false, noInput: true}])('status keeps output mode $json independent from input policy $noInput', async ({json, noInput}) => {
192+
test.each([
193+
{json: true, noInput: false},
194+
{json: true, noInput: true},
195+
{json: false, noInput: false},
196+
{json: false, noInput: true},
197+
])('status keeps output mode $json independent from input policy $noInput', async ({json, noInput}) => {
183198
vi.mocked(fetchBulkOperationById).mockResolvedValue(bulkOperation())
184199
const argv = ['--id', '123', ...(json ? ['--json'] : []), ...(noInput ? ['--no-input'] : [])]
185200
await withCapturedStandardStreams(async ({stdout, stderr}) => {
186201
const result = await runCommand(BulkStatus, argv)
187202
expect(result.app).toBeDefined()
188203
if (json) {
189-
expect(JSON.parse(stdout())).toEqual({storeDomain: 'shop.myshopify.com', apiVersion: '2026-01', operationGid: 'gid://shopify/BulkOperation/123', operation: expectedOperation()})
204+
expect(JSON.parse(stdout())).toEqual({
205+
storeDomain: 'shop.myshopify.com',
206+
apiVersion: '2026-01',
207+
operationGid: 'gid://shopify/BulkOperation/123',
208+
operation: expectedOperation(),
209+
})
190210
assertDiagnosticEvents(stderr(), 'Checking bulk operation status.')
191211
} else {
192212
expect(stdout()).toBe('')

‎packages/app/src/cli/services/bulk-operations/result.test.ts‎

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {testBulkOperation} from './bulk-operation.test-data.js'
22
import {renderCancelBulkOperationResult} from './cancel-result.js'
33
import {renderBulkOperationStatusResult} from './status-result.js'
44
import {afterEach, beforeEach, expect, test, vi} from 'vitest'
5-
import {renderSuccess, renderError, renderTable, renderWarning} from '@shopify/cli-kit/node/ui'
5+
import {renderError} from '@shopify/cli-kit/node/ui'
66
import {AbortError} from '@shopify/cli-kit/node/error'
77
import {mockAndCaptureOutput} from '@shopify/cli-kit/node/testing/output'
88

@@ -20,26 +20,14 @@ afterEach(() => {
2020

2121
function completedResult() {
2222
return {
23-
operation: testBulkOperation({status: 'COMPLETED', completedAt: '2026-09-01T00:01:00Z', url: 'https://example.com/results.jsonl'}),
23+
operation: testBulkOperation({
24+
status: 'COMPLETED',
25+
completedAt: '2026-09-01T00:01:00Z',
26+
url: 'https://example.com/results.jsonl',
27+
}),
2428
}
2529
}
2630

27-
test('outputs successful cancellation with the same operation projection', () => {
28-
const output = mockAndCaptureOutput()
29-
renderCancelBulkOperationResult(
30-
{operation: completedResult().operation, userErrors: []},
31-
'gid://shopify/BulkOperation/123',
32-
'json',
33-
)
34-
expect(JSON.parse(output.output())).toEqual({
35-
storeDomain: null,
36-
apiVersion: null,
37-
status: 'success',
38-
operation: expectedOperation(),
39-
})
40-
expect(renderWarning).not.toHaveBeenCalled()
41-
})
42-
4331
test('routes cancellation user errors through the global fatal-error path', () => {
4432
expect(() =>
4533
renderCancelBulkOperationResult(
@@ -53,7 +41,7 @@ test('routes cancellation user errors through the global fatal-error path', () =
5341

5442
test('does not invent a list operation type or claim completeness for a capped list', () => {
5543
const output = mockAndCaptureOutput()
56-
const {type: _type, ...operation} = completedResult().operation!
44+
const {type: _type, ...operation} = completedResult().operation
5745
renderBulkOperationStatusResult({operations: Array.from({length: 100}, () => operation)}, 'json')
5846
const result = JSON.parse(output.output())
5947
expect(result.operations[0]).not.toHaveProperty('type')
@@ -63,6 +51,8 @@ test('does not invent a list operation type or claim completeness for a capped l
6351

6452
test('keeps missing cancellation nonfatal in text mode', () => {
6553
renderCancelBulkOperationResult({operation: null, userErrors: []}, '123', 'text')
66-
expect(renderError).toHaveBeenCalledWith(expect.objectContaining({headline: 'Bulk operation not found or could not be canceled.'}))
54+
expect(renderError).toHaveBeenCalledWith(
55+
expect.objectContaining({headline: 'Bulk operation not found or could not be canceled.'}),
56+
)
6757
expect(process.exitCode).toBe(originalExitCode)
6858
})

‎packages/app/src/cli/services/bulk-operations/types.test.ts‎

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,25 @@ import {expect, test} from 'vitest'
55

66
test('status result rejects unselected fields and lists without pagination metadata', () => {
77
const context = {storeDomain: 'shop.myshopify.com', apiVersion: '2026-01'}
8-
const value = {...context, operationGid: 'gid://shopify/BulkOperation/123', operation: toBulkOperationJson(testBulkOperation())}
8+
const value = {
9+
...context,
10+
operationGid: 'gid://shopify/BulkOperation/123',
11+
operation: toBulkOperationJson(testBulkOperation()),
12+
}
913
expect(() => bulkOperationStatusJsonOutputSchema.validate({...value, token: 'secret'})).toThrow()
1014
expect(() => bulkOperationStatusJsonOutputSchema.validate({...context, operations: []})).toThrow()
1115
expect(bulkOperationStatusJsonOutputSchema.jsonSchema.definitions?.BulkOperation).toBeDefined()
1216
})
1317

14-
test.each([{name: 'cancel', schema: cancelBulkOperationJsonOutputSchema}])('$name uses the common operation projection', ({schema}) => {
15-
const value = {storeDomain: 'shop.myshopify.com', apiVersion: '2026-01', status: 'success' as const, operation: toBulkOperationJson(testBulkOperation())}
16-
expect(JSON.parse(schema.encode(value))).toEqual(value)
17-
})
18+
test.each([{name: 'cancel', schema: cancelBulkOperationJsonOutputSchema}])(
19+
'$name uses the common operation projection',
20+
({schema}) => {
21+
const value = {
22+
storeDomain: 'shop.myshopify.com',
23+
apiVersion: '2026-01',
24+
status: 'success' as const,
25+
operation: toBulkOperationJson(testBulkOperation()),
26+
}
27+
expect(JSON.parse(schema.encode(value))).toEqual(value)
28+
},
29+
)

‎packages/app/src/cli/services/bulk-operations/types.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {BulkOperationGidSchema, BulkOperationSchema, BulkOperationContextSchema} from './common.js'
2-
import type {BulkOperationContext} from './common.js'
32
import {defineJsonOutputSchema} from '@shopify/cli-kit/node/json-output-schema'
43
import {zod} from '@shopify/cli-kit/node/schema'
4+
import type {BulkOperationContext} from './common.js'
55
import type {BulkOperation} from '@shopify/cli-kit/node/api/bulk-operations'
66

77
const ListedBulkOperationSchema = BulkOperationSchema.omit({type: true})

0 commit comments

Comments
 (0)