Skip to content

Commit c75610b

Browse files
Merge pull request #7484 from Shopify/tester-refactor-replay-test-fs-mock-removal-7909328793477070431
[Tests] Replace filesystem mocks with real temp dir in replay.test.ts
2 parents 7540a0f + db57b0c commit c75610b

1 file changed

Lines changed: 95 additions & 68 deletions

File tree

packages/app/src/cli/services/function/replay.test.ts

Lines changed: 95 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,13 @@ import {ExtensionInstance} from '../../models/extensions/extension-instance.js'
66
import {FunctionConfigType} from '../../models/extensions/specifications/function.js'
77
import {selectFunctionRunPrompt} from '../../prompts/function/replay.js'
88
import {randomUUID} from '@shopify/cli-kit/node/crypto'
9-
import {readFile} from '@shopify/cli-kit/node/fs'
10-
import {describe, expect, beforeAll, test, vi} from 'vitest'
9+
import {writeFile, mkdir} from '@shopify/cli-kit/node/fs'
10+
import {joinPath} from '@shopify/cli-kit/node/path'
11+
import {describe, expect, beforeAll, vi} from 'vitest'
1112
import {AbortError} from '@shopify/cli-kit/node/error'
1213
import {outputInfo} from '@shopify/cli-kit/node/output'
13-
import {getLogsDir} from '@shopify/cli-kit/node/logs'
14+
import {testWithTempDir} from '@shopify/cli-kit/node/testing/test-with-temp-dir'
1415

15-
import {existsSync, readdirSync} from 'fs'
16-
17-
vi.mock('fs')
18-
vi.mock('@shopify/cli-kit/node/fs')
1916
vi.mock('../generate-schema.js')
2017
vi.mock('../../prompts/function/replay.js')
2118
vi.mock('../dev/extension/bundler.js')
@@ -45,17 +42,19 @@ describe('replay', () => {
4542
extension = await testFunctionExtension({config: defaultConfig})
4643
})
4744

48-
test('runs selected function', async () => {
45+
testWithTempDir('runs selected function', async ({tempDir}) => {
4946
// Given
50-
const file1 = createFunctionRunFile({handle: extension.handle})
51-
const file2 = createFunctionRunFile({handle: extension.handle})
52-
mockFileOperations([file1, file2])
47+
const app = testAppLinked({directory: tempDir})
48+
const logsDir = app.getLogsDir()
49+
const file1 = createFunctionRunFile({handle: extension.handle, index: 1})
50+
const file2 = createFunctionRunFile({handle: extension.handle, index: 2})
51+
await writeFunctionRunFiles(logsDir, [file1, file2])
5352

5453
vi.mocked(selectFunctionRunPrompt).mockResolvedValue(file1.run)
5554

5655
// When
5756
await replay({
58-
app: testAppLinked(),
57+
app,
5958
extension,
6059
stdout: false,
6160
path: 'test-path',
@@ -64,20 +63,24 @@ describe('replay', () => {
6463
})
6564

6665
// Then
67-
expect(selectFunctionRunPrompt).toHaveBeenCalledWith([file1.run, file2.run])
66+
expect(selectFunctionRunPrompt).toHaveBeenCalledWith([file2.run, file1.run])
6867
expectFunctionRun(extension, file1.run.payload.input)
6968
expect(outputInfo).not.toHaveBeenCalled()
7069
})
7170

72-
test('only allows selection of the most recent 100 runs', async () => {
71+
testWithTempDir('only allows selection of the most recent 100 runs', async ({tempDir}) => {
7372
// Given
74-
const files = new Array(101).fill(undefined).map((_) => createFunctionRunFile({handle: extension.handle}))
75-
mockFileOperations(files)
73+
const app = testAppLinked({directory: tempDir})
74+
const logsDir = app.getLogsDir()
75+
const files = new Array(101)
76+
.fill(undefined)
77+
.map((_, i) => createFunctionRunFile({handle: extension.handle, index: i}))
78+
await writeFunctionRunFiles(logsDir, files)
7679
vi.mocked(selectFunctionRunPrompt).mockResolvedValue(files[100]!.run)
7780

7881
// When
7982
await replay({
80-
app: testAppLinked(),
83+
app,
8184
extension,
8285
stdout: false,
8386
path: 'test-path',
@@ -86,20 +89,27 @@ describe('replay', () => {
8689
})
8790

8891
// Then
89-
expect(selectFunctionRunPrompt).toHaveBeenCalledWith(files.map(({run}) => run).slice(0, 100))
92+
expect(selectFunctionRunPrompt).toHaveBeenCalledWith(
93+
files
94+
.map(({run}) => run)
95+
.reverse()
96+
.slice(0, 100),
97+
)
9098
})
9199

92-
test('does not allow selection of runs for other functions', async () => {
100+
testWithTempDir('does not allow selection of runs for other functions', async ({tempDir}) => {
93101
// Given
94-
const file1 = createFunctionRunFile({handle: extension.handle})
95-
const file2 = createFunctionRunFile({handle: 'another-function-handle'})
96-
mockFileOperations([file1, file2])
102+
const app = testAppLinked({directory: tempDir})
103+
const logsDir = app.getLogsDir()
104+
const file1 = createFunctionRunFile({handle: extension.handle, index: 1})
105+
const file2 = createFunctionRunFile({handle: 'another-function-handle', index: 2})
106+
await writeFunctionRunFiles(logsDir, [file1, file2])
97107

98108
vi.mocked(selectFunctionRunPrompt).mockResolvedValue(file1.run)
99109

100110
// When
101111
await replay({
102-
app: testAppLinked(),
112+
app,
103113
extension,
104114
stdout: false,
105115
path: 'test-path',
@@ -111,51 +121,57 @@ describe('replay', () => {
111121
expect(selectFunctionRunPrompt).toHaveBeenCalledWith([file1.run])
112122
})
113123

114-
test('throws error if no logs available', async () => {
124+
testWithTempDir('throws error if no logs available', async ({tempDir}) => {
115125
// Given
116-
mockFileOperations([])
126+
const app = testAppLinked({directory: tempDir})
127+
const logsDir = app.getLogsDir()
128+
await mkdir(logsDir)
117129

118130
// When/Then
119131
await expect(async () => {
120132
await replay({
121-
app: testAppLinked(),
133+
app,
122134
extension,
123135
stdout: false,
124136
path: 'test-path',
125137
json: true,
126138
watch: false,
127139
})
128-
}).rejects.toThrow(new AbortError(`No logs found in ${getLogsDir()}`))
140+
}).rejects.toThrow(new AbortError(`No logs found in ${logsDir}`))
129141
})
130142

131-
test('throws error if log directory does not exist', async () => {
143+
testWithTempDir('throws error if log directory does not exist', async ({tempDir}) => {
132144
// Given
133-
vi.mocked(existsSync).mockReturnValue(false)
145+
const app = testAppLinked({directory: tempDir})
146+
const logsDir = app.getLogsDir()
147+
// logsDir does not exist
134148

135149
// When/Then
136150
await expect(async () => {
137151
await replay({
138-
app: testAppLinked(),
152+
app,
139153
extension,
140154
stdout: false,
141155
path: 'test-path',
142156
json: true,
143157
watch: false,
144158
})
145-
}).rejects.toThrow(new AbortError(`No logs found in ${getLogsDir()}`))
159+
}).rejects.toThrow(new AbortError(`No logs found in ${logsDir}`))
146160
})
147161

148-
test('delegates to renderReplay when watch is true', async () => {
162+
testWithTempDir('delegates to renderReplay when watch is true', async ({tempDir}) => {
149163
// Given
164+
const app = testAppLinked({directory: tempDir})
165+
const logsDir = app.getLogsDir()
150166
const file = createFunctionRunFile({handle: extension.handle})
151-
mockFileOperations([file])
167+
await writeFunctionRunFiles(logsDir, [file])
152168
vi.mocked(selectFunctionRunPrompt).mockResolvedValue(file.run)
153169

154170
vi.mocked(renderReplay)
155171

156172
// When
157173
await replay({
158-
app: testAppLinked(),
174+
app,
159175
extension,
160176
stdout: false,
161177
path: 'test-path',
@@ -166,18 +182,20 @@ describe('replay', () => {
166182
expect(renderReplay).toHaveBeenCalledOnce()
167183
})
168184

169-
test('aborts on error', async () => {
185+
testWithTempDir('aborts on error', async ({tempDir}) => {
170186
// Given
187+
const app = testAppLinked({directory: tempDir})
188+
const logsDir = app.getLogsDir()
171189
const file = createFunctionRunFile({handle: extension.handle})
172-
mockFileOperations([file])
190+
await writeFunctionRunFiles(logsDir, [file])
173191

174192
vi.mocked(selectFunctionRunPrompt).mockResolvedValue(file.run)
175193
vi.mocked(renderReplay).mockRejectedValueOnce('failure')
176194

177195
// When
178196
await expect(async () =>
179197
replay({
180-
app: testAppLinked(),
198+
app,
181199
extension,
182200
stdout: false,
183201
path: 'test-path',
@@ -192,18 +210,20 @@ describe('replay', () => {
192210
expect(abortSignal.aborted).toBeTruthy()
193211
})
194212

195-
test('runs the log specified by the --log flag for the current function', async () => {
213+
testWithTempDir('runs the log specified by the --log flag for the current function', async ({tempDir}) => {
196214
// Given
215+
const app = testAppLinked({directory: tempDir})
216+
const logsDir = app.getLogsDir()
197217
const identifier = '000000'
198-
const file1 = createFunctionRunFile({handle: extension.handle})
199-
const file2 = createFunctionRunFile({handle: extension.handle, identifier})
200-
const file3 = createFunctionRunFile({handle: extension.handle})
201-
const file4 = createFunctionRunFile({handle: 'another-extension', identifier})
202-
mockFileOperations([file1, file2, file3, file4])
218+
const file1 = createFunctionRunFile({handle: extension.handle, index: 1})
219+
const file2 = createFunctionRunFile({handle: extension.handle, identifier, index: 2})
220+
const file3 = createFunctionRunFile({handle: extension.handle, index: 3})
221+
const file4 = createFunctionRunFile({handle: 'another-extension', identifier, index: 4})
222+
await writeFunctionRunFiles(logsDir, [file1, file2, file3, file4])
203223

204224
// When
205225
await replay({
206-
app: testAppLinked(),
226+
app,
207227
extension,
208228
stdout: false,
209229
path: 'test-path',
@@ -216,17 +236,19 @@ describe('replay', () => {
216236
expectFunctionRun(extension, file2.run.payload.input)
217237
})
218238

219-
test('throws error if the log specified by the --log flag is not found', async () => {
239+
testWithTempDir('throws error if the log specified by the --log flag is not found', async ({tempDir}) => {
220240
// Given
241+
const app = testAppLinked({directory: tempDir})
242+
const logsDir = app.getLogsDir()
221243
const identifier = '000000'
222-
const file1 = createFunctionRunFile({handle: extension.handle})
223-
const file2 = createFunctionRunFile({handle: extension.handle})
224-
mockFileOperations([file1, file2])
244+
const file1 = createFunctionRunFile({handle: extension.handle, index: 1})
245+
const file2 = createFunctionRunFile({handle: extension.handle, index: 2})
246+
await writeFunctionRunFiles(logsDir, [file1, file2])
225247

226248
// When
227249
await expect(async () =>
228250
replay({
229-
app: testAppLinked(),
251+
app,
230252
extension,
231253
stdout: false,
232254
path: 'test-path',
@@ -237,19 +259,25 @@ describe('replay', () => {
237259
).rejects.toThrow()
238260
})
239261

240-
test('ignores runs with no input and keeps reading chunks until past the threshold', async () => {
262+
testWithTempDir('ignores runs with no input and keeps reading chunks until past the threshold', async ({tempDir}) => {
241263
// Given
242-
const filesWithInput = new Array(99).fill(undefined).map((_) => createFunctionRunFile({handle: extension.handle}))
243-
const fileWithoutInput = createFunctionRunFile({handle: extension.handle, partialPayload: {input: null}})
244-
const additionalFiles = new Array(199).fill(undefined).map((_) => createFunctionRunFile({handle: extension.handle}))
245-
246-
mockFileOperations([...filesWithInput, fileWithoutInput, ...additionalFiles])
264+
const app = testAppLinked({directory: tempDir})
265+
const logsDir = app.getLogsDir()
266+
const filesWithInput = new Array(99)
267+
.fill(undefined)
268+
.map((_, i) => createFunctionRunFile({handle: extension.handle, index: i}))
269+
const fileWithoutInput = createFunctionRunFile({handle: extension.handle, partialPayload: {input: null}, index: 99})
270+
const additionalFiles = new Array(199)
271+
.fill(undefined)
272+
.map((_, i) => createFunctionRunFile({handle: extension.handle, index: 100 + i}))
273+
274+
await writeFunctionRunFiles(logsDir, [...filesWithInput, fileWithoutInput, ...additionalFiles])
247275

248276
vi.mocked(selectFunctionRunPrompt).mockResolvedValue(filesWithInput[0]!.run)
249277

250278
// When
251279
await replay({
252-
app: testAppLinked(),
280+
app,
253281
extension,
254282
stdout: false,
255283
path: 'test-path',
@@ -259,7 +287,7 @@ describe('replay', () => {
259287

260288
// Then
261289
expect(selectFunctionRunPrompt).toHaveBeenCalledWith(
262-
[...filesWithInput, ...additionalFiles.slice(0, 100)].map(({run}) => run),
290+
[...additionalFiles.reverse(), ...filesWithInput.reverse()].slice(0, 100).map(({run}) => run),
263291
)
264292
})
265293
})
@@ -268,12 +296,15 @@ interface FunctionRunFileOptions {
268296
handle: string
269297
identifier?: string
270298
partialPayload?: object
299+
index?: number
271300
}
272301
function createFunctionRunFile(options: FunctionRunFileOptions) {
273302
const handle = options.handle
274303
const identifier = options.identifier ?? randomUUID().substring(0, 6)
275304
const partialPayload = options.partialPayload ?? {}
276-
const path = `20240522_150641_827Z_extensions_${handle}_${identifier}.json`
305+
const index = options.index ?? 0
306+
const seconds = index.toString().padStart(6, '0')
307+
const path = `20240522_00${seconds}_827Z_extensions_${handle}_${identifier}.json`
277308
const run: FunctionRunData = {
278309
identifier,
279310
shopId: 1,
@@ -304,14 +335,10 @@ function expectFunctionRun(functionExtension: ExtensionInstance<FunctionConfigTy
304335
expect(runFunction).toHaveBeenCalledWith({functionExtension, json: true, export: 'run', input: JSON.stringify(input)})
305336
}
306337

307-
function mockFileOperations(data: {run: FunctionRunData; path: string}[]) {
308-
vi.mocked(existsSync).mockReturnValue(true)
309-
vi.mocked(readdirSync).mockReturnValue([...data].reverse().map(({path}) => path) as any)
310-
vi.mocked(readFile).mockImplementation((path) => {
311-
const run = data.find((file) => path.endsWith(file.path))
312-
if (!run) {
313-
throw new AbortError(`Mock file not found: ${path}`)
314-
}
315-
return Promise.resolve(Buffer.from(JSON.stringify(run.run), 'utf8'))
316-
})
338+
async function writeFunctionRunFiles(logsDir: string, data: {run: FunctionRunData; path: string}[]) {
339+
await mkdir(logsDir)
340+
for (const file of data) {
341+
// eslint-disable-next-line no-await-in-loop
342+
await writeFile(joinPath(logsDir, file.path), JSON.stringify(file.run))
343+
}
317344
}

0 commit comments

Comments
 (0)