Skip to content
Closed
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
316 changes: 172 additions & 144 deletions packages/app/src/cli/services/dev/processes/app-logs-polling.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ import {
testFunctionExtension,
} from '../../../models/app/app.test-data.js'
import {pollAppLogs} from '../../app-logs/dev/poll-app-logs.js'
import {DeveloperPlatformClient} from '../../../utilities/developer-platform-client.js'
import * as appLogsUtils from '../../app-logs/utils.js'
import {AppEventWatcher} from '../app-events/app-event-watcher.js'
import {AbortSignal} from '@shopify/cli-kit/node/abort'
import {mkdir} from '@shopify/cli-kit/node/fs'
import {inTemporaryDirectory, fileExists} from '@shopify/cli-kit/node/fs'
import {joinPath} from '@shopify/cli-kit/node/path'
import {outputDebug} from '@shopify/cli-kit/node/output'
import {describe, expect, vi, Mock, beforeEach, test} from 'vitest'
import {describe, expect, vi, test} from 'vitest'
import {Writable} from 'stream'

vi.mock('@shopify/cli-kit/node/fs')
vi.mock('@shopify/cli-kit/node/output')
vi.mock('../../app-logs/dev/poll-app-logs.js')

Expand All @@ -32,36 +32,44 @@ const DEFAULT_FUNCTION_CONFIG = {
describe('app-logs-polling', () => {
describe('setupAppLogsPollingProcess', () => {
test('returns process metadata', async () => {
// Given
const developerPlatformClient = testDeveloperPlatformClient()
const session = await developerPlatformClient.session()
const localApp = testAppWithConfig()
const appWatcher = new AppEventWatcher(localApp)

// When
const result = await setupAppLogsPollingProcess({
developerPlatformClient,
subscription: {shopIds: SHOP_IDS, apiKey: API_KEY},
storeName: 'storeName',
organizationId: 'organizationId',
localApp,
appWatcher,
})
await inTemporaryDirectory(async (tmpDir) => {
// Given
const developerPlatformClient = testDeveloperPlatformClient()
const session = await developerPlatformClient.session()
const localApp = testAppWithConfig({
app: {
directory: tmpDir,
configPath: joinPath(tmpDir, 'shopify.app.toml'),
},
config: {},
})
const appWatcher = new AppEventWatcher(localApp)

// Then
expect(result).toMatchObject({
type: 'app-logs-subscribe',
prefix: 'app-logs',
function: subscribeAndStartPolling,
options: {
// When
const result = await setupAppLogsPollingProcess({
developerPlatformClient,
appLogsSubscribeVariables: {
shopIds: SHOP_IDS,
apiKey: API_KEY,
},
subscription: {shopIds: SHOP_IDS, apiKey: API_KEY},
storeName: 'storeName',
organizationId: 'organizationId',
localApp,
appWatcher,
},
})

// Then
expect(result).toMatchObject({
type: 'app-logs-subscribe',
prefix: 'app-logs',
function: subscribeAndStartPolling,
options: {
developerPlatformClient,
appLogsSubscribeVariables: {
shopIds: SHOP_IDS,
apiKey: API_KEY,
},
localApp,
appWatcher,
},
})
})
})
})
Expand All @@ -73,129 +81,149 @@ describe('app-logs-polling', () => {
token: TOKEN,
}

let subscribeToAppLogs: Mock
let developerPlatformClient: DeveloperPlatformClient
let stdout: any
let stderr: any
let abortSignal: AbortSignal
let localApp: any
let appWatcher: any

beforeEach(async () => {
stdout = {write: vi.fn()}
stderr = {write: vi.fn()}
abortSignal = new AbortSignal()
subscribeToAppLogs = vi.fn()

// Create function extension
localApp = testAppWithConfig({
config: {},
app: {
allExtensions: [await testFunctionExtension({config: DEFAULT_FUNCTION_CONFIG})],
},
})

appWatcher = {
onEvent: vi.fn().mockReturnThis(),
onStart: vi.fn().mockReturnThis(),
}

developerPlatformClient = testDeveloperPlatformClient({subscribeToAppLogs})

vi.mocked(mkdir).mockResolvedValue()
vi.mocked(pollAppLogs).mockResolvedValue()
vi.spyOn(appLogsUtils, 'subscribeToAppLogs').mockResolvedValue(JWT_TOKEN)
})

test('sets up app log polling', async () => {
// Given
subscribeToAppLogs.mockResolvedValue({appLogsSubscribe: {jwtToken: JWT_TOKEN, success: true}})

// When
await subscribeAndStartPolling(
{stdout, stderr, abortSignal},
{
await inTemporaryDirectory(async (tmpDir) => {
// Given
const stdout = {write: vi.fn()} as unknown as Writable
const stderr = {write: vi.fn()} as unknown as Writable
const abortSignal = new AbortSignal()
const subscribeToAppLogs = vi.fn()

const localApp = testAppWithConfig({
app: {
directory: tmpDir,
configPath: joinPath(tmpDir, 'shopify.app.toml'),
allExtensions: [await testFunctionExtension({dir: joinPath(tmpDir, 'extensions', 'my-function')})],
},
config: {},
})

const onEvent = vi.fn().mockReturnThis()
const onStart = vi.fn().mockReturnThis()
const appWatcher = {
onEvent,
onStart,
} as unknown as AppEventWatcher

const developerPlatformClient = testDeveloperPlatformClient({subscribeToAppLogs})

vi.mocked(pollAppLogs).mockResolvedValue()
vi.spyOn(appLogsUtils, 'subscribeToAppLogs').mockResolvedValue(JWT_TOKEN)

subscribeToAppLogs.mockResolvedValue({appLogsSubscribe: {jwtToken: JWT_TOKEN, success: true}})

// When
await subscribeAndStartPolling(
{stdout, stderr, abortSignal},
{
developerPlatformClient,
appLogsSubscribeVariables,
storeName: 'storeName',
organizationId: 'organizationId',
localApp,
appWatcher,
},
)

expect(onStart).toHaveBeenCalledOnce()
expect(onEvent).toHaveBeenCalledOnce()

const startCallback = onStart.mock.calls[0]![0]
const appEvent = {
app: localApp,
extensionEvents: [],
startTime: [0, 0],
path: '',
}
await startCallback(appEvent)

// Then
expect(outputDebug).toHaveBeenCalledWith('Function extensions detected, starting logs polling')
expect(appLogsUtils.subscribeToAppLogs).toHaveBeenCalledWith(
developerPlatformClient,
appLogsSubscribeVariables,
storeName: 'storeName',
organizationId: 'organizationId',
localApp,
appWatcher,
},
)

expect(appWatcher.onStart).toHaveBeenCalledOnce()
expect(appWatcher.onEvent).toHaveBeenCalledOnce()

const startCallback = appWatcher.onStart.mock.calls[0][0]
const appEvent = {
app: localApp,
extensionEvents: [],
startTime: [0, 0],
path: '',
}
await startCallback(appEvent)

// Then
expect(outputDebug).toHaveBeenCalledWith('Function extensions detected, starting logs polling')
expect(appLogsUtils.subscribeToAppLogs).toHaveBeenCalledWith(
developerPlatformClient,
appLogsSubscribeVariables,
'organizationId',
stdout,
)
expect(mkdir).toHaveBeenCalledWith(localApp.getLogsDir())
expect(pollAppLogs).toHaveBeenCalledOnce()
expect(vi.mocked(pollAppLogs).mock.calls[0]?.[0]).toMatchObject({
stdout,
appLogsFetchInput: {jwtToken: JWT_TOKEN},
logsDir: localApp.getLogsDir(),
'organizationId',
stdout,
)
await expect(fileExists(localApp.getLogsDir())).resolves.toBe(true)
expect(pollAppLogs).toHaveBeenCalledOnce()
expect(vi.mocked(pollAppLogs).mock.calls[0]![0]).toMatchObject({
stdout,
appLogsFetchInput: {jwtToken: JWT_TOKEN},
logsDir: localApp.getLogsDir(),
})

const eventCallback = onEvent.mock.calls[0]![0]
expect(startCallback).toBe(eventCallback)
})

const eventCallback = appWatcher.onEvent.mock.calls[0][0]
expect(startCallback).toBe(eventCallback)
})

test('prints error and returns on query errors', async () => {
// Given
vi.spyOn(appLogsUtils, 'subscribeToAppLogs').mockImplementation(() => {
throw new Error('uh oh, another error')
})

// When
await subscribeAndStartPolling(
{stdout, stderr, abortSignal},
{
await inTemporaryDirectory(async (tmpDir) => {
// Given
const stdout = {write: vi.fn()} as unknown as Writable
const stderr = {write: vi.fn()} as unknown as Writable
const abortSignal = new AbortSignal()
const subscribeToAppLogs = vi.fn()

const localApp = testAppWithConfig({
app: {
directory: tmpDir,
configPath: joinPath(tmpDir, 'shopify.app.toml'),
allExtensions: [await testFunctionExtension({dir: joinPath(tmpDir, 'extensions', 'my-function')})],
},
config: {},
})

const onEvent = vi.fn().mockReturnThis()
const onStart = vi.fn().mockReturnThis()
const appWatcher = {
onEvent,
onStart,
} as unknown as AppEventWatcher

const developerPlatformClient = testDeveloperPlatformClient({subscribeToAppLogs})

vi.mocked(pollAppLogs).mockResolvedValue()
vi.spyOn(appLogsUtils, 'subscribeToAppLogs').mockImplementation(() => {
throw new Error('uh oh, another error')
})

// When
await subscribeAndStartPolling(
{stdout, stderr, abortSignal},
{
developerPlatformClient,
appLogsSubscribeVariables,
storeName: 'storeName',
organizationId: 'organizationId',
localApp,
appWatcher,
},
)

expect(onStart).toHaveBeenCalledOnce()
expect(onEvent).toHaveBeenCalledOnce()

const startCallback = onStart.mock.calls[0]![0]
const appEvent = {
app: localApp,
extensionEvents: [],
startTime: [0, 0],
path: '',
}
await startCallback(appEvent)

// Then
expect(appLogsUtils.subscribeToAppLogs).toHaveBeenCalledWith(
developerPlatformClient,
appLogsSubscribeVariables,
storeName: 'storeName',
organizationId: 'organizationId',
localApp,
appWatcher,
},
)

expect(appWatcher.onStart).toHaveBeenCalledOnce()
expect(appWatcher.onEvent).toHaveBeenCalledOnce()

const startCallback = appWatcher.onStart.mock.calls[0][0]
const appEvent = {
app: localApp,
extensionEvents: [],
startTime: [0, 0],
path: '',
}
await startCallback(appEvent)

// Then
expect(appLogsUtils.subscribeToAppLogs).toHaveBeenCalledWith(
developerPlatformClient,
appLogsSubscribeVariables,
'organizationId',
stdout,
)
expect(outputDebug).toHaveBeenCalledWith('Failed to start function logs: Error: uh oh, another error', stderr)
expect(pollAppLogs).not.toHaveBeenCalled()
'organizationId',
stdout,
)
expect(outputDebug).toHaveBeenCalledWith('Failed to start function logs: Error: uh oh, another error', stderr)
expect(pollAppLogs).not.toHaveBeenCalled()
})
})
})
})
Loading