Skip to content

Commit 76ff289

Browse files
Add tests
1 parent 0d9cb9f commit 76ff289

File tree

5 files changed

+296
-63
lines changed

5 files changed

+296
-63
lines changed

packages/app/src/cli/commands/app/import-extensions.ts

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import {appFlags} from '../../flags.js'
2-
import {importExtensions, pendingExtensions} from '../../services/import-extensions.js'
2+
import {allExtensionTypes, importExtensions} from '../../services/import-extensions.js'
33
import AppLinkedCommand, {AppLinkedCommandOutput} from '../../utilities/app-linked-command.js'
44
import {linkedAppContext} from '../../services/app-context.js'
5-
import {selectMigrationChoice} from '../../prompts/import-extensions.js'
5+
import {getMigrationChoices, selectMigrationChoice} from '../../prompts/import-extensions.js'
6+
import {getExtensions} from '../../services/fetch-extensions.js'
67
import {Flags} from '@oclif/core'
78
import {globalFlags} from '@shopify/cli-kit/node/cli'
89
import {renderSuccess} from '@shopify/cli-kit/node/ui'
@@ -30,18 +31,22 @@ export default class ImportExtensions extends AppLinkedCommand {
3031
userProvidedConfigName: flags.config,
3132
})
3233

33-
const {extensions, extensionRegistrations} = await pendingExtensions(
34-
appContext.remoteApp,
35-
appContext.developerPlatformClient,
36-
)
37-
if (extensions.length === 0) {
34+
const extensions = await getExtensions({
35+
developerPlatformClient: appContext.developerPlatformClient,
36+
apiKey: appContext.remoteApp.apiKey,
37+
organizationId: appContext.remoteApp.organizationId,
38+
extensionTypes: allExtensionTypes,
39+
})
40+
41+
const migrationChoices = getMigrationChoices(extensions)
42+
43+
if (migrationChoices.length === 0) {
3844
renderSuccess({headline: ['No extensions to migrate.']})
3945
} else {
40-
const migrationChoice = await selectMigrationChoice(extensions)
46+
const migrationChoice = await selectMigrationChoice(migrationChoices)
4147
await importExtensions({
4248
...appContext,
4349
extensions,
44-
extensionRegistrations,
4550
extensionTypes: migrationChoice.extensionTypes,
4651
buildTomlObject: migrationChoice.buildTomlObject,
4752
})
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
import {getMigrationChoices, selectMigrationChoice, allMigrationChoices, MigrationChoice} from './import-extensions.js'
2+
import {ExtensionRegistration} from '../api/graphql/all_app_extension_registrations.js'
3+
import {describe, expect, test, vi} from 'vitest'
4+
import {renderSelectPrompt} from '@shopify/cli-kit/node/ui'
5+
import {AbortError} from '@shopify/cli-kit/node/error'
6+
7+
vi.mock('@shopify/cli-kit/node/ui')
8+
9+
describe('allMigrationChoices', () => {
10+
test('contains all expected migration choices', () => {
11+
expect(allMigrationChoices).toHaveLength(5)
12+
13+
const values = allMigrationChoices.map((choice) => choice.value)
14+
expect(values).toContain('payments')
15+
expect(values).toContain('flow')
16+
expect(values).toContain('marketing activity')
17+
expect(values).toContain('subscription link')
18+
expect(values).toContain('link extension')
19+
})
20+
21+
test('each migration choice has required properties', () => {
22+
allMigrationChoices.forEach((choice) => {
23+
expect(choice).toHaveProperty('label')
24+
expect(choice).toHaveProperty('value')
25+
expect(choice).toHaveProperty('extensionTypes')
26+
expect(choice).toHaveProperty('buildTomlObject')
27+
expect(Array.isArray(choice.extensionTypes)).toBe(true)
28+
expect(choice.extensionTypes.length).toBeGreaterThan(0)
29+
expect(typeof choice.buildTomlObject).toBe('function')
30+
})
31+
})
32+
33+
test('payments migration choice has correct extension types', () => {
34+
const paymentsChoice = allMigrationChoices.find((choice) => choice.value === 'payments')
35+
expect(paymentsChoice?.extensionTypes).toEqual([
36+
'payments_app',
37+
'payments_app_credit_card',
38+
'payments_app_custom_credit_card',
39+
'payments_app_custom_onsite',
40+
'payments_app_redeemable',
41+
'payments_extension',
42+
])
43+
})
44+
45+
test('flow migration choice has correct extension types', () => {
46+
const flowChoice = allMigrationChoices.find((choice) => choice.value === 'flow')
47+
expect(flowChoice?.extensionTypes).toEqual([
48+
'flow_action_definition',
49+
'flow_trigger_definition',
50+
'flow_trigger_discovery_webhook',
51+
])
52+
})
53+
54+
test('marketing activity migration choice has correct extension types', () => {
55+
const marketingChoice = allMigrationChoices.find((choice) => choice.value === 'marketing activity')
56+
expect(marketingChoice?.extensionTypes).toEqual(['marketing_activity_extension'])
57+
})
58+
59+
test('subscription link migration choice has correct extension types', () => {
60+
const subscriptionChoice = allMigrationChoices.find((choice) => choice.value === 'subscription link')
61+
expect(subscriptionChoice?.extensionTypes).toEqual(['subscription_link', 'subscription_link_extension'])
62+
})
63+
64+
test('admin link migration choice has correct extension types', () => {
65+
const adminLinkChoice = allMigrationChoices.find((choice) => choice.value === 'link extension')
66+
expect(adminLinkChoice?.extensionTypes).toEqual(['app_link', 'bulk_action'])
67+
})
68+
})
69+
70+
describe('getMigrationChoices', () => {
71+
const mockExtension = (type: string): ExtensionRegistration => ({
72+
id: '1',
73+
uuid: 'uuid',
74+
type,
75+
title: 'Extension',
76+
})
77+
78+
test('returns empty array when no extensions match', () => {
79+
const extensions = [mockExtension('unknown_type')]
80+
const result = getMigrationChoices(extensions)
81+
expect(result).toEqual([])
82+
})
83+
84+
test('returns payment migration choice when payment extension is present', () => {
85+
const extensions = [mockExtension('payments_app')]
86+
const result = getMigrationChoices(extensions)
87+
expect(result).toHaveLength(1)
88+
expect(result[0]?.value).toBe('payments')
89+
})
90+
91+
test('returns flow migration choice when flow extension is present', () => {
92+
const extensions = [mockExtension('flow_action_definition')]
93+
const result = getMigrationChoices(extensions)
94+
expect(result).toHaveLength(1)
95+
expect(result[0]?.value).toBe('flow')
96+
})
97+
98+
test('returns multiple migration choices when different extension types are present', () => {
99+
const extensions = [
100+
mockExtension('payments_app'),
101+
mockExtension('flow_trigger_definition'),
102+
mockExtension('app_link'),
103+
]
104+
const result = getMigrationChoices(extensions)
105+
expect(result).toHaveLength(3)
106+
const values = result.map((choice) => choice.value)
107+
expect(values).toContain('payments')
108+
expect(values).toContain('flow')
109+
expect(values).toContain('link extension')
110+
})
111+
112+
test('handles case insensitive extension type matching', () => {
113+
const extensions = [mockExtension('PAYMENTS_APP')]
114+
const result = getMigrationChoices(extensions)
115+
expect(result).toHaveLength(1)
116+
expect(result[0]?.value).toBe('payments')
117+
})
118+
119+
test('returns unique migration choices even with multiple extensions of same type', () => {
120+
const extensions = [
121+
mockExtension('payments_app'),
122+
mockExtension('payments_app_credit_card'),
123+
mockExtension('payments_extension'),
124+
]
125+
const result = getMigrationChoices(extensions)
126+
expect(result).toHaveLength(1)
127+
expect(result[0]?.value).toBe('payments')
128+
})
129+
})
130+
131+
describe('selectMigrationChoice', () => {
132+
test('returns the only choice when there is exactly one migration choice', async () => {
133+
const singleChoice: MigrationChoice = {
134+
label: 'Test Extension',
135+
value: 'test',
136+
extensionTypes: ['test_type'],
137+
buildTomlObject: vi.fn(),
138+
}
139+
const result = await selectMigrationChoice([singleChoice])
140+
expect(result).toBe(singleChoice)
141+
expect(renderSelectPrompt).not.toHaveBeenCalled()
142+
})
143+
144+
test('prompts user when there are multiple migration choices', async () => {
145+
const choices: MigrationChoice[] = [
146+
{
147+
label: 'Choice 1',
148+
value: 'choice1',
149+
extensionTypes: ['type1'],
150+
buildTomlObject: vi.fn(),
151+
},
152+
{
153+
label: 'Choice 2',
154+
value: 'choice2',
155+
extensionTypes: ['type2'],
156+
buildTomlObject: vi.fn(),
157+
},
158+
]
159+
160+
vi.mocked(renderSelectPrompt).mockResolvedValue('choice1')
161+
162+
const result = await selectMigrationChoice(choices)
163+
164+
expect(renderSelectPrompt).toHaveBeenCalledWith({
165+
message: 'Extension type to migrate',
166+
choices: [
167+
{label: 'Choice 1', value: 'choice1'},
168+
{label: 'Choice 2', value: 'choice2'},
169+
],
170+
})
171+
expect(result).toBe(choices[0])
172+
})
173+
174+
test('throws AbortError when prompt returns invalid choice', async () => {
175+
const choices: MigrationChoice[] = [
176+
{
177+
label: 'Choice 1',
178+
value: 'choice1',
179+
extensionTypes: ['type1'],
180+
buildTomlObject: vi.fn(),
181+
},
182+
{
183+
label: 'Choice 2',
184+
value: 'choice2',
185+
extensionTypes: ['type2'],
186+
buildTomlObject: vi.fn(),
187+
},
188+
]
189+
190+
vi.mocked(renderSelectPrompt).mockResolvedValue('invalid_choice')
191+
192+
await expect(selectMigrationChoice(choices)).rejects.toThrow(AbortError)
193+
await expect(selectMigrationChoice(choices)).rejects.toThrow('Invalid migration choice')
194+
})
195+
196+
test('throws AbortError when passed empty array', async () => {
197+
await expect(selectMigrationChoice([])).rejects.toThrow(AbortError)
198+
await expect(selectMigrationChoice([])).rejects.toThrow('Invalid migration choice')
199+
})
200+
201+
test('correctly maps choices for prompt', async () => {
202+
const choices: MigrationChoice[] = [
203+
{
204+
label: 'Payments Extensions',
205+
value: 'payments',
206+
extensionTypes: ['payments_app'],
207+
buildTomlObject: vi.fn(),
208+
},
209+
{
210+
label: 'Flow Extensions',
211+
value: 'flow',
212+
extensionTypes: ['flow_action_definition'],
213+
buildTomlObject: vi.fn(),
214+
},
215+
{
216+
label: 'Marketing Activity Extensions',
217+
value: 'marketing activity',
218+
extensionTypes: ['marketing_activity_extension'],
219+
buildTomlObject: vi.fn(),
220+
},
221+
]
222+
223+
vi.mocked(renderSelectPrompt).mockResolvedValue('flow')
224+
225+
const result = await selectMigrationChoice(choices)
226+
227+
expect(renderSelectPrompt).toHaveBeenCalledWith({
228+
message: 'Extension type to migrate',
229+
choices: [
230+
{label: 'Payments Extensions', value: 'payments'},
231+
{label: 'Flow Extensions', value: 'flow'},
232+
{label: 'Marketing Activity Extensions', value: 'marketing activity'},
233+
],
234+
})
235+
expect(result).toBe(choices[1])
236+
})
237+
})

packages/app/src/cli/prompts/import-extensions.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,7 @@ export function getMigrationChoices(extensions: ExtensionRegistration[]): Migrat
6565
)
6666
}
6767

68-
export async function selectMigrationChoice(extensions: ExtensionRegistration[]): Promise<MigrationChoice> {
69-
const migrationChoices = getMigrationChoices(extensions)
68+
export async function selectMigrationChoice(migrationChoices: MigrationChoice[]): Promise<MigrationChoice> {
7069
if (migrationChoices.length === 1 && migrationChoices[0]) {
7170
return migrationChoices[0]
7271
}

0 commit comments

Comments
 (0)