@forgehive/task 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +27 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +160 -73
- package/dist/index.js.map +1 -1
- package/dist/test/add-listener.test.js +3 -1
- package/dist/test/add-listener.test.js.map +1 -1
- package/dist/test/safe-run.test.d.ts +2 -0
- package/dist/test/safe-run.test.d.ts.map +1 -0
- package/dist/test/safe-run.test.js +159 -0
- package/dist/test/safe-run.test.js.map +1 -0
- package/dist/test/task-boundary-mocking.test.d.ts +2 -0
- package/dist/test/task-boundary-mocking.test.d.ts.map +1 -0
- package/dist/test/task-boundary-mocking.test.js +87 -0
- package/dist/test/task-boundary-mocking.test.js.map +1 -0
- package/dist/test/task-with-boundaries.test.js +135 -0
- package/dist/test/task-with-boundaries.test.js.map +1 -1
- package/dist/test/validation.test.js +177 -19
- package/dist/test/validation.test.js.map +1 -1
- package/dist/utils/mock.d.ts +17 -0
- package/dist/utils/mock.d.ts.map +1 -0
- package/dist/utils/mock.js +34 -0
- package/dist/utils/mock.js.map +1 -0
- package/package.json +1 -1
- package/src/index.ts +193 -93
- package/src/test/add-listener.test.ts +3 -1
- package/src/test/safe-run.test.ts +214 -0
- package/src/test/task-boundary-mocking.test.ts +116 -0
- package/src/test/task-with-boundaries.test.ts +198 -1
- package/src/test/validation.test.ts +197 -18
- package/src/utils/mock.ts +45 -0
package/src/index.ts
CHANGED
|
@@ -23,6 +23,7 @@ export interface TaskConfig<B extends Boundaries = Boundaries> {
|
|
|
23
23
|
boundariesData?: Record<string, unknown>
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
// ToDo: Add a type for the boundaries data
|
|
26
27
|
/**
|
|
27
28
|
* Represents the record passed to task listeners
|
|
28
29
|
*/
|
|
@@ -60,9 +61,14 @@ export interface TaskInstanceType<Func extends BaseFunction = BaseFunction, B ex
|
|
|
60
61
|
getBoundaries: () => WrappedBoundaries<B>
|
|
61
62
|
setBoundariesData: (boundariesData: Record<string, unknown>) => void
|
|
62
63
|
getBondariesData: () => Record<string, unknown>
|
|
63
|
-
|
|
64
|
-
|
|
64
|
+
|
|
65
|
+
// Mocking methods for testing
|
|
66
|
+
mockBoundary: <K extends keyof B>(name: K, mockFn: WrappedBoundaryFunction) => void
|
|
67
|
+
resetMock: <K extends keyof B>(name: K) => void
|
|
68
|
+
resetMocks: () => void
|
|
69
|
+
|
|
65
70
|
run: (argv?: Parameters<Func>[0]) => Promise<ReturnType<Func>>
|
|
71
|
+
safeRun: (argv?: Parameters<Func>[0]) => Promise<[Error | null, ReturnType<Func> | null, Record<string, unknown> | null]>
|
|
66
72
|
}
|
|
67
73
|
|
|
68
74
|
// Helper type to infer schema type
|
|
@@ -74,6 +80,9 @@ export type TaskFunction<S, B extends Boundaries> =
|
|
|
74
80
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
75
81
|
(argv: InferSchemaType<S>, boundaries: WrappedBoundaries<B>) => Promise<any>;
|
|
76
82
|
|
|
83
|
+
// Define a type for the accumulated boundary data
|
|
84
|
+
type BoundaryData = Array<{input: unknown[], output?: unknown}>
|
|
85
|
+
|
|
77
86
|
export const Task = class Task<
|
|
78
87
|
B extends Boundaries = Boundaries,
|
|
79
88
|
Func extends BaseFunction = BaseFunction
|
|
@@ -84,8 +93,11 @@ export const Task = class Task<
|
|
|
84
93
|
_description?: string
|
|
85
94
|
|
|
86
95
|
_boundariesDefinition: B
|
|
87
|
-
_boundaries: WrappedBoundaries<B>
|
|
88
96
|
_boundariesData: Record<string, unknown> | null
|
|
97
|
+
_accumulatedBoundariesData: Record<string, BoundaryData> = {}
|
|
98
|
+
|
|
99
|
+
// For storing mocks
|
|
100
|
+
_boundaryMocks: Record<string, WrappedBoundaryFunction> = {}
|
|
89
101
|
|
|
90
102
|
_schema: Schema<Record<string, SchemaType>> | undefined
|
|
91
103
|
_listener?: ((record: TaskRecord<Parameters<Func>[0], ReturnType<Func>>) => void) | undefined
|
|
@@ -110,13 +122,25 @@ export const Task = class Task<
|
|
|
110
122
|
// Cool down time before killing the process on cli runner
|
|
111
123
|
this._coolDown = 1000
|
|
112
124
|
|
|
113
|
-
//
|
|
125
|
+
// Initialize boundaries data
|
|
114
126
|
this._boundariesData = conf.boundariesData ?? null
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
127
|
+
|
|
128
|
+
// Initialize empty accumulated boundaries data structure
|
|
129
|
+
for (const name in this._boundariesDefinition) {
|
|
130
|
+
this._accumulatedBoundariesData[name] = []
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Initialize accumulated boundaries data from initial boundaries data
|
|
134
|
+
if (this._boundariesData) {
|
|
135
|
+
// Type assertion to handle initial data safely
|
|
136
|
+
for (const name in this._boundariesData) {
|
|
137
|
+
if (Array.isArray(this._boundariesData[name])) {
|
|
138
|
+
this._accumulatedBoundariesData[name] = this._boundariesData[name] as BoundaryData
|
|
139
|
+
} else {
|
|
140
|
+
this._accumulatedBoundariesData[name] = []
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
120
144
|
}
|
|
121
145
|
|
|
122
146
|
getMode (): Mode {
|
|
@@ -124,12 +148,6 @@ export const Task = class Task<
|
|
|
124
148
|
}
|
|
125
149
|
|
|
126
150
|
setMode (mode: Mode): void {
|
|
127
|
-
for (const name in this._boundaries) {
|
|
128
|
-
const boundary = this._boundaries[name]
|
|
129
|
-
|
|
130
|
-
boundary.setMode(mode)
|
|
131
|
-
}
|
|
132
|
-
|
|
133
151
|
this._mode = mode
|
|
134
152
|
}
|
|
135
153
|
|
|
@@ -188,45 +206,60 @@ export const Task = class Task<
|
|
|
188
206
|
emit (data: Partial<TaskRecord>): void {
|
|
189
207
|
if (typeof this._listener === 'undefined') { return }
|
|
190
208
|
|
|
191
|
-
|
|
192
|
-
...data,
|
|
193
|
-
boundaries: this.getBondariesRunLog()
|
|
194
|
-
} as TaskRecord<Parameters<Func>[0], ReturnType<Func>>
|
|
195
|
-
|
|
196
|
-
this._listener(event)
|
|
209
|
+
this._listener(data as TaskRecord<Parameters<Func>[0], ReturnType<Func>>)
|
|
197
210
|
}
|
|
198
211
|
|
|
199
212
|
getBoundaries (): WrappedBoundaries<B> {
|
|
200
|
-
|
|
213
|
+
// Create fresh boundaries when requested
|
|
214
|
+
return this._createBounderies({
|
|
215
|
+
definition: this._boundariesDefinition,
|
|
216
|
+
baseData: this._boundariesData,
|
|
217
|
+
mode: this._mode
|
|
218
|
+
})
|
|
201
219
|
}
|
|
202
220
|
|
|
203
221
|
setBoundariesData (boundariesData: Record<string, unknown>): void {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
214
|
-
boundary.setTape(tape as any)
|
|
222
|
+
this._boundariesData = boundariesData
|
|
223
|
+
|
|
224
|
+
// Update accumulated data as well
|
|
225
|
+
// Type assertion to handle provided data safely
|
|
226
|
+
for (const name in boundariesData) {
|
|
227
|
+
if (Array.isArray(boundariesData[name])) {
|
|
228
|
+
this._accumulatedBoundariesData[name] = boundariesData[name] as BoundaryData
|
|
229
|
+
} else {
|
|
230
|
+
this._accumulatedBoundariesData[name] = []
|
|
215
231
|
}
|
|
216
232
|
}
|
|
217
233
|
}
|
|
218
234
|
|
|
219
235
|
getBondariesData (): Record<string, unknown> {
|
|
220
|
-
|
|
221
|
-
|
|
236
|
+
return this._accumulatedBoundariesData
|
|
237
|
+
}
|
|
222
238
|
|
|
223
|
-
|
|
224
|
-
|
|
239
|
+
/**
|
|
240
|
+
* Mocks a specific boundary function for testing
|
|
241
|
+
* @param name The name of the boundary to mock
|
|
242
|
+
* @param mockFn The mock function to use
|
|
243
|
+
*/
|
|
244
|
+
mockBoundary<K extends keyof B>(name: K, mockFn: WrappedBoundaryFunction): void {
|
|
245
|
+
this._boundaryMocks[name as string] = mockFn
|
|
246
|
+
}
|
|
225
247
|
|
|
226
|
-
|
|
248
|
+
/**
|
|
249
|
+
* Resets a specific mocked boundary back to its original function
|
|
250
|
+
* @param name The name of the boundary to reset
|
|
251
|
+
*/
|
|
252
|
+
resetMock<K extends keyof B>(name: K): void {
|
|
253
|
+
if (this._boundaryMocks[name as string]) {
|
|
254
|
+
delete this._boundaryMocks[name as string]
|
|
227
255
|
}
|
|
256
|
+
}
|
|
228
257
|
|
|
229
|
-
|
|
258
|
+
/**
|
|
259
|
+
* Resets all mocked boundaries back to their original functions
|
|
260
|
+
*/
|
|
261
|
+
resetMocks(): void {
|
|
262
|
+
this._boundaryMocks = {}
|
|
230
263
|
}
|
|
231
264
|
|
|
232
265
|
_createBounderies ({
|
|
@@ -241,6 +274,13 @@ export const Task = class Task<
|
|
|
241
274
|
const boundariesFns: Record<string, WrappedBoundaryFunction> = {}
|
|
242
275
|
|
|
243
276
|
for (const name in definition) {
|
|
277
|
+
// Check if we have a mock for this boundary
|
|
278
|
+
if (this._boundaryMocks[name]) {
|
|
279
|
+
boundariesFns[name] = this._boundaryMocks[name]
|
|
280
|
+
continue
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Otherwise create the normal boundary
|
|
244
284
|
const boundary = createBoundary(definition[name])
|
|
245
285
|
|
|
246
286
|
if (baseData !== null && typeof baseData[name] !== 'undefined') {
|
|
@@ -257,77 +297,137 @@ export const Task = class Task<
|
|
|
257
297
|
return boundariesFns as WrappedBoundaries<B>
|
|
258
298
|
}
|
|
259
299
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
for (const name in boundaries) {
|
|
265
|
-
const boundary = boundaries[name]
|
|
266
|
-
|
|
267
|
-
boundariesRunLog[name] = boundary.getRunData()
|
|
300
|
+
asBoundary (): (args: Parameters<Func>[0]) => Promise<ReturnType<Func>> {
|
|
301
|
+
return async (args: Parameters<Func>[0]): Promise<ReturnType<Func>> => {
|
|
302
|
+
return await this.run(args)
|
|
268
303
|
}
|
|
269
|
-
|
|
270
|
-
return boundariesRunLog
|
|
271
304
|
}
|
|
272
305
|
|
|
273
|
-
|
|
274
|
-
|
|
306
|
+
async safeRun (argv?: Parameters<Func>[0]): Promise<[Error | null, ReturnType<Func> | null, Record<string, unknown> | null]> {
|
|
307
|
+
// Handle schema validation
|
|
308
|
+
if (this._schema) {
|
|
309
|
+
try {
|
|
310
|
+
const validation = this._schema.safeParse(argv)
|
|
311
|
+
if (!validation.success) {
|
|
312
|
+
const errorDetails = validation.error?.errors.map(err =>
|
|
313
|
+
`${err.path.join('.')}: ${err.message}`
|
|
314
|
+
).join(', ')
|
|
315
|
+
|
|
316
|
+
const errorMessage = errorDetails
|
|
317
|
+
? `Invalid input on: ${errorDetails}`
|
|
318
|
+
: 'Invalid input'
|
|
319
|
+
|
|
320
|
+
// Emit the validation error
|
|
321
|
+
this.emit({
|
|
322
|
+
input: argv,
|
|
323
|
+
error: errorMessage
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
return [new Error(errorMessage), null, null]
|
|
327
|
+
}
|
|
328
|
+
} catch (error) {
|
|
329
|
+
return [error instanceof Error ? error : new Error(String(error)), null, null]
|
|
330
|
+
}
|
|
331
|
+
}
|
|
275
332
|
|
|
276
|
-
|
|
277
|
-
|
|
333
|
+
// Create fresh boundaries for this execution
|
|
334
|
+
const executionBoundaries = this._createBounderies({
|
|
335
|
+
definition: this._boundariesDefinition,
|
|
336
|
+
baseData: this._boundariesData,
|
|
337
|
+
mode: this._mode
|
|
338
|
+
})
|
|
278
339
|
|
|
340
|
+
// Start run for each boundary
|
|
341
|
+
for (const name in executionBoundaries) {
|
|
342
|
+
const boundary = executionBoundaries[name]
|
|
279
343
|
boundary.startRun()
|
|
280
344
|
}
|
|
281
|
-
}
|
|
282
345
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
346
|
+
try {
|
|
347
|
+
// Execute the task function
|
|
348
|
+
const output = await this._fn(
|
|
349
|
+
argv as Parameters<Func>[0],
|
|
350
|
+
executionBoundaries as unknown as Parameters<Func>[1]
|
|
351
|
+
)
|
|
288
352
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
353
|
+
// Process boundary data after successful execution
|
|
354
|
+
const boundariesRunLog: Record<string, unknown> = {}
|
|
355
|
+
|
|
356
|
+
for (const name in executionBoundaries) {
|
|
357
|
+
const boundary = executionBoundaries[name]
|
|
358
|
+
const runData = boundary.getRunData()
|
|
359
|
+
|
|
360
|
+
// Add to the run log
|
|
361
|
+
boundariesRunLog[name] = runData
|
|
293
362
|
|
|
294
|
-
|
|
295
|
-
|
|
363
|
+
// Accumulate in the task's total boundaries data
|
|
364
|
+
if (!this._accumulatedBoundariesData[name]) {
|
|
365
|
+
this._accumulatedBoundariesData[name] = []
|
|
366
|
+
}
|
|
296
367
|
|
|
297
|
-
|
|
298
|
-
this.
|
|
299
|
-
input: argv,
|
|
300
|
-
error: 'Invalid input'
|
|
301
|
-
})
|
|
368
|
+
// Get the current accumulated data for this boundary
|
|
369
|
+
const currentData = this._accumulatedBoundariesData[name]
|
|
302
370
|
|
|
303
|
-
|
|
371
|
+
// Add the new run data
|
|
372
|
+
if (Array.isArray(runData) && runData.length > 0) {
|
|
373
|
+
// Cast the run data to the correct type
|
|
374
|
+
this._accumulatedBoundariesData[name] = [...currentData, ...(runData as BoundaryData)]
|
|
375
|
+
}
|
|
304
376
|
}
|
|
305
377
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
})().then((output) => {
|
|
312
|
-
this.emit({
|
|
313
|
-
input: argv,
|
|
314
|
-
output
|
|
315
|
-
})
|
|
316
|
-
|
|
317
|
-
resolve(output)
|
|
318
|
-
}).catch((error) => {
|
|
319
|
-
this.emit({
|
|
320
|
-
input: argv,
|
|
321
|
-
error: error.message
|
|
322
|
-
})
|
|
323
|
-
|
|
324
|
-
reject(error)
|
|
378
|
+
// Emit the success event with boundary data
|
|
379
|
+
this.emit({
|
|
380
|
+
input: argv,
|
|
381
|
+
output,
|
|
382
|
+
boundaries: boundariesRunLog
|
|
325
383
|
})
|
|
326
|
-
})
|
|
327
384
|
|
|
328
|
-
|
|
385
|
+
return [null, output, boundariesRunLog]
|
|
386
|
+
} catch (error) {
|
|
387
|
+
// Process boundary data after error
|
|
388
|
+
const boundariesRunLog: Record<string, unknown> = {}
|
|
329
389
|
|
|
330
|
-
|
|
390
|
+
for (const name in executionBoundaries) {
|
|
391
|
+
const boundary = executionBoundaries[name]
|
|
392
|
+
const runData = boundary.getRunData()
|
|
393
|
+
|
|
394
|
+
// Add to the run log
|
|
395
|
+
boundariesRunLog[name] = runData
|
|
396
|
+
|
|
397
|
+
// Accumulate in the task's total boundaries data
|
|
398
|
+
if (!this._accumulatedBoundariesData[name]) {
|
|
399
|
+
this._accumulatedBoundariesData[name] = []
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Get the current accumulated data for this boundary
|
|
403
|
+
const currentData = this._accumulatedBoundariesData[name]
|
|
404
|
+
|
|
405
|
+
// Add the new run data
|
|
406
|
+
if (Array.isArray(runData) && runData.length > 0) {
|
|
407
|
+
// Cast the run data to the correct type
|
|
408
|
+
this._accumulatedBoundariesData[name] = [...currentData, ...(runData as BoundaryData)]
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Emit the error event with boundary data
|
|
413
|
+
this.emit({
|
|
414
|
+
input: argv,
|
|
415
|
+
error: error instanceof Error ? error.message : String(error),
|
|
416
|
+
boundaries: boundariesRunLog
|
|
417
|
+
})
|
|
418
|
+
|
|
419
|
+
return [error instanceof Error ? error : new Error(String(error)), null, boundariesRunLog]
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
async run (argv?: Parameters<Func>[0]): Promise<ReturnType<Func>> {
|
|
424
|
+
const [error, result] = await this.safeRun(argv)
|
|
425
|
+
|
|
426
|
+
if (error) {
|
|
427
|
+
throw error
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return result as ReturnType<Func>
|
|
331
431
|
}
|
|
332
432
|
}
|
|
333
433
|
|
|
@@ -64,7 +64,9 @@ describe('Listener tests', () => {
|
|
|
64
64
|
|
|
65
65
|
expect(tape.length).toBe(1)
|
|
66
66
|
expect(tape[0].input).toEqual({ value: 3 })
|
|
67
|
-
expect(tape[0].error).
|
|
67
|
+
expect(tape[0].error).toContain('Invalid input on:')
|
|
68
|
+
expect(tape[0].error).toContain('value:')
|
|
69
|
+
expect(tape[0].error).toContain('Number must be greater than or equal to 5')
|
|
68
70
|
})
|
|
69
71
|
|
|
70
72
|
it('Should record multiple records', async () => {
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { createTask, Schema } from '../index'
|
|
2
|
+
|
|
3
|
+
describe('Task safeRun tests', () => {
|
|
4
|
+
it('returns [null, result, boundaryLogs] on successful execution', async () => {
|
|
5
|
+
// Create a simple schema
|
|
6
|
+
const schema = new Schema({
|
|
7
|
+
value: Schema.number()
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
// Define the boundaries
|
|
11
|
+
const boundaries = {
|
|
12
|
+
fetchData: async (value: number): Promise<number> => {
|
|
13
|
+
return value * 2
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Create the task
|
|
18
|
+
const successTask = createTask(
|
|
19
|
+
schema,
|
|
20
|
+
boundaries,
|
|
21
|
+
async function ({ value }, { fetchData }) {
|
|
22
|
+
const result = await fetchData(value)
|
|
23
|
+
return { result, success: true }
|
|
24
|
+
}
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
// Call safeRun with valid input
|
|
28
|
+
const [error, result, boundaryLogs] = await successTask.safeRun({ value: 5 })
|
|
29
|
+
|
|
30
|
+
// Verify success case
|
|
31
|
+
expect(error).toBeNull()
|
|
32
|
+
expect(result).toEqual({ result: 10, success: true })
|
|
33
|
+
expect(boundaryLogs).not.toBeNull()
|
|
34
|
+
expect(boundaryLogs).toHaveProperty('fetchData')
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('returns [error, null, boundaryLogs] on failed execution', async () => {
|
|
38
|
+
// Create a simple schema
|
|
39
|
+
const schema = new Schema({
|
|
40
|
+
value: Schema.number()
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
// Define the boundaries with a function that will throw an error
|
|
44
|
+
const boundaries = {
|
|
45
|
+
fetchData: async (value: number): Promise<number> => {
|
|
46
|
+
if (value < 0) {
|
|
47
|
+
throw new Error('Value cannot be negative')
|
|
48
|
+
}
|
|
49
|
+
return value * 2
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Create the task
|
|
54
|
+
const errorTask = createTask(
|
|
55
|
+
schema,
|
|
56
|
+
boundaries,
|
|
57
|
+
async function ({ value }, { fetchData }) {
|
|
58
|
+
const result = await fetchData(value)
|
|
59
|
+
return { result, success: true }
|
|
60
|
+
}
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
// Call safeRun with problematic input that will cause an error
|
|
64
|
+
const [error, result, boundaryLogs] = await errorTask.safeRun({ value: -5 })
|
|
65
|
+
|
|
66
|
+
// Verify error case
|
|
67
|
+
expect(error).toBeInstanceOf(Error)
|
|
68
|
+
expect(error?.message).toContain('Value cannot be negative')
|
|
69
|
+
expect(result).toBeNull()
|
|
70
|
+
expect(boundaryLogs).not.toBeNull()
|
|
71
|
+
expect(boundaryLogs).toHaveProperty('fetchData')
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('returns [error, null, null] on schema validation failure', async () => {
|
|
75
|
+
// Create a schema that requires a positive number
|
|
76
|
+
const schema = new Schema({
|
|
77
|
+
value: Schema.number().min(1, 'Value must be positive')
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
// Define the boundaries
|
|
81
|
+
const boundaries = {
|
|
82
|
+
fetchData: async (value: number): Promise<number> => {
|
|
83
|
+
return value * 2
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Create the task
|
|
88
|
+
const validationTask = createTask(
|
|
89
|
+
schema,
|
|
90
|
+
boundaries,
|
|
91
|
+
async function ({ value }, { fetchData }) {
|
|
92
|
+
const result = await fetchData(value)
|
|
93
|
+
return { result, success: true }
|
|
94
|
+
}
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
// Call safeRun with invalid input that will fail schema validation
|
|
98
|
+
const [error, result, boundaryLogs] = await validationTask.safeRun({ value: 0 })
|
|
99
|
+
|
|
100
|
+
// Verify validation error case
|
|
101
|
+
expect(error).toBeInstanceOf(Error)
|
|
102
|
+
expect(error?.message).toContain('Value must be positive')
|
|
103
|
+
expect(result).toBeNull()
|
|
104
|
+
expect(boundaryLogs).toBeNull() // No boundary calls were made due to validation failure
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('properly calls the listener with safeRun and run', async () => {
|
|
108
|
+
// Create a schema
|
|
109
|
+
const schema = new Schema({
|
|
110
|
+
value: Schema.number()
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
// Define the boundaries
|
|
114
|
+
const boundaries = {
|
|
115
|
+
fetchData: async (value: number): Promise<number> => {
|
|
116
|
+
return value * 2
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Create the task
|
|
121
|
+
const listenerTask = createTask(
|
|
122
|
+
schema,
|
|
123
|
+
boundaries,
|
|
124
|
+
async function ({ value }, { fetchData }) {
|
|
125
|
+
const result = await fetchData(value)
|
|
126
|
+
return result
|
|
127
|
+
}
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
// Create a mock listener
|
|
131
|
+
const originalListener = jest.fn()
|
|
132
|
+
listenerTask.addListener(originalListener)
|
|
133
|
+
|
|
134
|
+
// Call safeRun - this should call the listener once
|
|
135
|
+
await listenerTask.safeRun({ value: 10 })
|
|
136
|
+
|
|
137
|
+
// Run the task normally - this should call the listener again through safeRun
|
|
138
|
+
await listenerTask.run({ value: 20 })
|
|
139
|
+
|
|
140
|
+
// The original listener should have been called for both runs
|
|
141
|
+
expect(originalListener).toHaveBeenCalledTimes(2)
|
|
142
|
+
|
|
143
|
+
// First call should be for safeRun with value 10
|
|
144
|
+
expect(originalListener).toHaveBeenNthCalledWith(
|
|
145
|
+
1,
|
|
146
|
+
expect.objectContaining({
|
|
147
|
+
input: { value: 10 },
|
|
148
|
+
output: 20
|
|
149
|
+
})
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
// Second call should be for run with value 20
|
|
153
|
+
expect(originalListener).toHaveBeenNthCalledWith(
|
|
154
|
+
2,
|
|
155
|
+
expect.objectContaining({
|
|
156
|
+
input: { value: 20 },
|
|
157
|
+
output: 40
|
|
158
|
+
})
|
|
159
|
+
)
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
it('handles multiple boundary calls correctly', async () => {
|
|
163
|
+
// Create a schema
|
|
164
|
+
const schema = new Schema({
|
|
165
|
+
values: Schema.array(Schema.number())
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
// Define multiple boundaries
|
|
169
|
+
const boundaries = {
|
|
170
|
+
doubleValue: async (value: number): Promise<number> => {
|
|
171
|
+
return value * 2
|
|
172
|
+
},
|
|
173
|
+
sumValues: async (values: number[]): Promise<number> => {
|
|
174
|
+
return values.reduce((sum, val) => sum + val, 0)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Create a task that uses multiple boundaries
|
|
179
|
+
const multiBoundaryTask = createTask(
|
|
180
|
+
schema,
|
|
181
|
+
boundaries,
|
|
182
|
+
async function ({ values }, { doubleValue, sumValues }) {
|
|
183
|
+
const doubled = await Promise.all(values.map(value => doubleValue(value)))
|
|
184
|
+
const total = await sumValues(doubled)
|
|
185
|
+
return { doubled, total }
|
|
186
|
+
}
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
// Call safeRun
|
|
190
|
+
const [error, result, boundaryLogs] = await multiBoundaryTask.safeRun({ values: [1, 2, 3] })
|
|
191
|
+
|
|
192
|
+
// Verify success
|
|
193
|
+
expect(error).toBeNull()
|
|
194
|
+
expect(result).toEqual({
|
|
195
|
+
doubled: [2, 4, 6],
|
|
196
|
+
total: 12
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
// Verify boundary logs for both boundaries
|
|
200
|
+
expect(boundaryLogs).not.toBeNull()
|
|
201
|
+
expect(boundaryLogs).toHaveProperty('doubleValue')
|
|
202
|
+
expect(boundaryLogs).toHaveProperty('sumValues')
|
|
203
|
+
|
|
204
|
+
// Check that doubleValue was called 3 times (once for each input value)
|
|
205
|
+
// @ts-expect-error - we know the boundaryLogs is not null here
|
|
206
|
+
expect(boundaryLogs.doubleValue).toHaveLength(3)
|
|
207
|
+
|
|
208
|
+
// Check that sumValues was called once with the doubled values
|
|
209
|
+
// @ts-expect-error - we know the boundaryLogs is not null here
|
|
210
|
+
expect(boundaryLogs.sumValues).toHaveLength(1)
|
|
211
|
+
// @ts-expect-error - we know the boundaryLogs is not null here
|
|
212
|
+
expect(boundaryLogs.sumValues[0].input).toEqual([[2, 4, 6]])
|
|
213
|
+
})
|
|
214
|
+
})
|