Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions packages/app/src/cli/commands/app/function/schema.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import FetchSchema from './schema.js'
import {linkedAppContext} from '../../../services/app-context.js'
import {generateSchemaService} from '../../../services/generate-schema.js'
import {
testAppWithConfig,
testDeveloperPlatformClient,
testFunctionExtension,
testOrganization,
testOrganizationApp,
testProject,
} from '../../../models/app/app.test-data.js'
import {inTemporaryDirectory} from '@shopify/cli-kit/node/fs'
import {describe, expect, test, vi} from 'vitest'

vi.mock('../../../services/app-context.js')
vi.mock('../../../services/generate-schema.js')

describe('app function schema command', () => {
test.each([
{
name: 'explicit client ID and stdout',
flags: ['--client-id', 'selected-client-id', '--stdout'],
clientId: 'selected-client-id',
userProvidedConfigName: undefined,
forceRelink: false,
stdout: true,
},
{
name: 'config and file output',
flags: ['--config', 'staging'],
clientId: undefined,
userProvidedConfigName: 'staging',
forceRelink: false,
stdout: false,
},
{
name: 'reset and file output',
flags: ['--reset'],
clientId: undefined,
userProvidedConfigName: undefined,
forceRelink: true,
stdout: false,
},
])('uses the selected remote app ID with $name', async (scenario) => {
await inTemporaryDirectory(async (directory) => {
const extension = await testFunctionExtension({dir: directory})
const app = testAppWithConfig({app: {allExtensions: [extension]}, config: {client_id: 'local-client-id'}})
const remoteApp = testOrganizationApp({
id: 'gid://shopify/App/987',
apiKey: 'remote-client-id',
organizationId: '417',
})
const organization = {...testOrganization(), id: '417'}
const developerPlatformClient = testDeveloperPlatformClient()
vi.mocked(linkedAppContext).mockResolvedValue({
app,
remoteApp,
organization,
developerPlatformClient,
specifications: [],
project: testProject(),
activeConfig: {} as never,
})

const result = await FetchSchema.run([...scenario.flags, '--path', directory], import.meta.url)

expect(linkedAppContext).toHaveBeenCalledWith({
directory,
clientId: scenario.clientId,
forceRelink: scenario.forceRelink,
userProvidedConfigName: scenario.userProvidedConfigName,
})
expect(generateSchemaService).toHaveBeenCalledWith({
appId: remoteApp.id,
extension,
stdout: scenario.stdout,
developerPlatformClient,
orgId: organization.id,
})
expect(result).toEqual({app})
})
})
})
4 changes: 2 additions & 2 deletions packages/app/src/cli/commands/app/function/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export default class FetchSchema extends AppLinkedCommand {
public async run(): Promise<AppLinkedCommandOutput> {
const {flags} = await this.parse(FetchSchema)

const {app, developerPlatformClient, organization} = await linkedAppContext({
const {app, remoteApp, developerPlatformClient, organization} = await linkedAppContext({
directory: flags.path,
clientId: flags['client-id'],
forceRelink: flags.reset,
Expand All @@ -40,7 +40,7 @@ export default class FetchSchema extends AppLinkedCommand {
const ourFunction = await chooseFunction(app, flags.path)

await generateSchemaService({
app,
appId: remoteApp.id,
extension: ourFunction,
stdout: flags.stdout,
developerPlatformClient,
Expand Down
4 changes: 2 additions & 2 deletions packages/app/src/cli/models/app/app.test-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1363,9 +1363,9 @@ export function testDeveloperPlatformClient(
migrateFlowExtension: (_input: MigrateFlowExtensionVariables) => Promise.resolve(migrateFlowExtensionResponse),
migrateAppModule: (_input: MigrateAppModuleVariables) => Promise.resolve(migrateAppModuleResponse),
updateURLs: (_input: UpdateURLsVariables) => Promise.resolve(updateURLsResponse),
targetSchemaDefinition: (_input: SchemaDefinitionByTargetQueryVariables & {apiKey?: string}, _orgId: string) =>
targetSchemaDefinition: (_input: SchemaDefinitionByTargetQueryVariables, _appId: string, _orgId: string) =>
Promise.resolve('schema'),
apiSchemaDefinition: (_input: SchemaDefinitionByApiTypeQueryVariables & {apiKey?: string}, _orgId: string) =>
apiSchemaDefinition: (_input: SchemaDefinitionByApiTypeQueryVariables, _appId: string, _orgId: string) =>
Promise.resolve('schema'),
migrateToUiExtension: (_input: MigrateToUiExtensionVariables) => Promise.resolve(migrateToUiExtensionResponse),
toExtensionGraphQLType: (input: string) => input,
Expand Down
39 changes: 35 additions & 4 deletions packages/app/src/cli/services/function/common.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {AppLinkedInterface} from '../../models/app/app.js'
import {ExtensionInstance} from '../../models/extensions/extension-instance.js'
import {FunctionConfigType} from '../../models/extensions/specifications/function.js'
import {generateSchemaService} from '../generate-schema.js'
import {DeveloperPlatformClient} from '../../utilities/developer-platform-client.js'
import {linkedAppContext} from '../app-context.js'
import {describe, vi, expect, beforeEach, test} from 'vitest'
import {renderAutocompletePrompt, renderFatalError} from '@shopify/cli-kit/node/ui'
Expand All @@ -33,7 +32,7 @@ beforeEach(async () => {
app = testAppLinked({allExtensions: [ourFunction]})
vi.mocked(linkedAppContext).mockResolvedValue({
app,
remoteApp: testOrganizationApp(),
remoteApp: testOrganizationApp({id: 'gid://shopify/App/731', apiKey: 'remote-client-id'}),
developerPlatformClient: testDeveloperPlatformClient(),
specifications: [],
organization: testOrganization(),
Expand All @@ -47,10 +46,8 @@ beforeEach(async () => {

describe('getOrGenerateSchemaPath', () => {
let app: AppLinkedInterface
let developerPlatformClient: DeveloperPlatformClient
beforeEach(() => {
app = testAppLinked()
developerPlatformClient = testDeveloperPlatformClient()
})

test('returns the path if the schema file exists', async () => {
Expand All @@ -69,6 +66,8 @@ describe('getOrGenerateSchemaPath', () => {

// Then
expect(result).toBe(expectedPath)
expect(linkedAppContext).not.toHaveBeenCalled()
expect(generateSchemaService).not.toHaveBeenCalled()
})
})

Expand All @@ -91,6 +90,38 @@ describe('getOrGenerateSchemaPath', () => {

// Then
expect(result).toBe(expectedPath)
expect(linkedAppContext).toHaveBeenCalledWith({
directory: app.directory,
clientId: '123',
forceRelink: false,
userProvidedConfigName: undefined,
})
expect(generateSchemaService).toHaveBeenCalledWith({
appId: 'gid://shopify/App/731',
developerPlatformClient: expect.anything(),
extension,
stdout: false,
orgId: '1',
})
})
})

test('returns undefined when the fetched schema is not written', async () => {
await inTemporaryDirectory(async (tmpDir) => {
const extension = {directory: tmpDir} as ExtensionInstance<FunctionConfigType>

const result = await getOrGenerateSchemaPath(extension, app.directory, undefined, true, 'staging')

expect(result).toBeUndefined()
expect(linkedAppContext).toHaveBeenCalledWith({
directory: app.directory,
clientId: undefined,
forceRelink: true,
userProvidedConfigName: 'staging',
})
expect(generateSchemaService).toHaveBeenCalledWith(
expect.objectContaining({appId: 'gid://shopify/App/731', extension, stdout: false}),
)
})
})
})
Expand Down
4 changes: 2 additions & 2 deletions packages/app/src/cli/services/function/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,15 @@ export async function getOrGenerateSchemaPath(
return path
}

const {app, developerPlatformClient, organization} = await linkedAppContext({
const {remoteApp, developerPlatformClient, organization} = await linkedAppContext({
directory: appDirectory,
clientId,
forceRelink,
userProvidedConfigName,
})

await generateSchemaService({
app,
appId: remoteApp.id,
developerPlatformClient,
extension,
stdout: false,
Expand Down
78 changes: 52 additions & 26 deletions packages/app/src/cli/services/generate-schema.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {generateSchemaService} from './generate-schema.js'
import {testAppLinked, testDeveloperPlatformClient, testFunctionExtension} from '../models/app/app.test-data.js'
import {testDeveloperPlatformClient, testFunctionExtension} from '../models/app/app.test-data.js'
import {describe, expect, vi, test} from 'vitest'
import {AbortError} from '@shopify/cli-kit/node/error'
import {inTemporaryDirectory, readFile, mkdir} from '@shopify/cli-kit/node/fs'
import {inTemporaryDirectory, readFile} from '@shopify/cli-kit/node/fs'
import {joinPath} from '@shopify/cli-kit/node/path'
import * as output from '@shopify/cli-kit/node/output'

Expand All @@ -28,17 +28,14 @@ describe('generateSchemaService', () => {
await inTemporaryDirectory(async (tmpDir) => {
// Given
const orgId = 'test'
const extensionDir = joinPath(tmpDir, 'extensions', 'my-function')
await mkdir(extensionDir)

const app = testAppLinked()
const appId = 'gid://shopify/App/42'
const extension = await testFunctionExtension({
dir: tmpDir,
})

// When
await generateSchemaService({
app,
appId,
extension,
stdout: false,
developerPlatformClient: testDeveloperPlatformClient(),
Expand All @@ -54,17 +51,16 @@ describe('generateSchemaService', () => {
test('Print the latest GraphQL schema to stdout when stdout flag is PRESENT', async () => {
await inTemporaryDirectory(async (tmpDir) => {
// Given
const app = testAppLinked()
const extension = await testFunctionExtension()
const path = tmpDir
const appId = 'gid://shopify/App/42'
const extension = await testFunctionExtension({dir: tmpDir})
const stdout = true
const orgId = '123'
const mockOutput = vi.fn()
vi.spyOn(output, 'outputResult').mockImplementation(mockOutput)

// When
await generateSchemaService({
app,
appId,
extension,
stdout,
developerPlatformClient: testDeveloperPlatformClient(),
Expand All @@ -79,10 +75,7 @@ describe('generateSchemaService', () => {
describe('GraphQL query', () => {
test('Uses ApiSchemaDefinitionQuery when not using targets', async () => {
await inTemporaryDirectory(async (tmpDir) => {
const extensionDir = joinPath(tmpDir, 'extensions', 'my-function')
await mkdir(extensionDir)

const app = testAppLinked()
const appId = 'gid://shopify/App/731'
const extension = await testFunctionExtension({
dir: tmpDir,
config: {
Expand All @@ -99,12 +92,11 @@ describe('generateSchemaService', () => {
})

const orgId = 'test'
const path = tmpDir
const version = extension.configuration.api_version
const developerPlatformClient = testDeveloperPlatformClient()

await generateSchemaService({
app,
appId,
extension,
stdout: false,
developerPlatformClient,
Expand All @@ -116,18 +108,16 @@ describe('generateSchemaService', () => {
version,
type: extension.configuration.type,
},
app.configuration.client_id,
appId,
orgId,
)
expect(developerPlatformClient.targetSchemaDefinition).not.toHaveBeenCalled()
})
})

test('Uses TargetSchemaDefinitionQuery when targets present', async () => {
await inTemporaryDirectory(async (tmpDir) => {
const extensionDir = joinPath(tmpDir, 'extensions', 'my-function')
await mkdir(extensionDir)

const app = testAppLinked()
const appId = 'gid://shopify/App/732'
const extension = await testFunctionExtension({
dir: tmpDir,
config: {
Expand Down Expand Up @@ -157,7 +147,7 @@ describe('generateSchemaService', () => {
const developerPlatformClient = testDeveloperPlatformClient()

await generateSchemaService({
app,
appId,
extension,
stdout: false,
developerPlatformClient,
Expand All @@ -169,16 +159,17 @@ describe('generateSchemaService', () => {
handle: expectedTarget,
version,
},
app.configuration.client_id,
appId,
orgId,
)
expect(developerPlatformClient.apiSchemaDefinition).not.toHaveBeenCalled()
})
})
})

test('aborts if a schema could not be generated', async () => {
// Given
const app = testAppLinked()
const appId = 'gid://shopify/App/42'
const extension = await testFunctionExtension()
const orgId = '123'
const developerPlatformClient = testDeveloperPlatformClient({
Expand All @@ -187,7 +178,7 @@ describe('generateSchemaService', () => {

// When
const result = generateSchemaService({
app,
appId,
extension,
stdout: true,
developerPlatformClient,
Expand All @@ -196,5 +187,40 @@ describe('generateSchemaService', () => {

// Then
await expect(result).rejects.toThrow(AbortError)
await expect(result).rejects.toThrow(`A schema could not be generated for ${extension.localIdentifier}`)
})

test('aborts if a target schema could not be generated', async () => {
const appId = 'gid://shopify/App/732'
const extension = await testFunctionExtension({
config: {
name: 'test function extension',
description: 'description',
type: 'function',
targeting: [{target: 'cart.transform.run'}],
build: {command: 'echo "hello world"', wasm_opt: true},
api_version: '2025-07',
configuration_ui: true,
},
})
const developerPlatformClient = testDeveloperPlatformClient({
targetSchemaDefinition: () => Promise.resolve(null),
})

const result = generateSchemaService({
appId,
extension,
stdout: true,
developerPlatformClient,
orgId: '123',
})

await expect(result).rejects.toThrow(AbortError)
await expect(result).rejects.toThrow(`A schema could not be generated for ${extension.localIdentifier}`)
expect(developerPlatformClient.targetSchemaDefinition).toHaveBeenCalledWith(
{handle: 'cart.transform.run', version: '2025-07'},
appId,
'123',
)
})
})
Loading
Loading