@forgehive/task 0.1.5 → 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 +149 -69
- package/dist/index.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/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 +183 -93
- 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/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,44 +297,17 @@ export const Task = class Task<
|
|
|
257
297
|
return boundariesFns as WrappedBoundaries<B>
|
|
258
298
|
}
|
|
259
299
|
|
|
260
|
-
getBondariesRunLog (): Record<string, unknown> {
|
|
261
|
-
const boundaries = this._boundaries
|
|
262
|
-
const boundariesRunLog: Record<string, unknown> = {}
|
|
263
|
-
|
|
264
|
-
for (const name in boundaries) {
|
|
265
|
-
const boundary = boundaries[name]
|
|
266
|
-
|
|
267
|
-
boundariesRunLog[name] = boundary.getRunData()
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
return boundariesRunLog
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
startRunLog (): void {
|
|
274
|
-
const boundaries = this._boundaries
|
|
275
|
-
|
|
276
|
-
for (const name in boundaries) {
|
|
277
|
-
const boundary = boundaries[name]
|
|
278
|
-
|
|
279
|
-
boundary.startRun()
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
300
|
asBoundary (): (args: Parameters<Func>[0]) => Promise<ReturnType<Func>> {
|
|
284
301
|
return async (args: Parameters<Func>[0]): Promise<ReturnType<Func>> => {
|
|
285
302
|
return await this.run(args)
|
|
286
303
|
}
|
|
287
304
|
}
|
|
288
305
|
|
|
289
|
-
async
|
|
290
|
-
//
|
|
291
|
-
this.
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
const q = new Promise<ReturnType<Func>>((resolve, reject) => {
|
|
295
|
-
if (this._schema) {
|
|
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 {
|
|
296
310
|
const validation = this._schema.safeParse(argv)
|
|
297
|
-
|
|
298
311
|
if (!validation.success) {
|
|
299
312
|
const errorDetails = validation.error?.errors.map(err =>
|
|
300
313
|
`${err.path.join('.')}: ${err.message}`
|
|
@@ -304,40 +317,117 @@ export const Task = class Task<
|
|
|
304
317
|
? `Invalid input on: ${errorDetails}`
|
|
305
318
|
: 'Invalid input'
|
|
306
319
|
|
|
320
|
+
// Emit the validation error
|
|
307
321
|
this.emit({
|
|
308
322
|
input: argv,
|
|
309
323
|
error: errorMessage
|
|
310
324
|
})
|
|
311
325
|
|
|
312
|
-
|
|
326
|
+
return [new Error(errorMessage), null, null]
|
|
313
327
|
}
|
|
328
|
+
} catch (error) {
|
|
329
|
+
return [error instanceof Error ? error : new Error(String(error)), null, null]
|
|
314
330
|
}
|
|
331
|
+
}
|
|
315
332
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
})().then((output) => {
|
|
322
|
-
this.emit({
|
|
323
|
-
input: argv,
|
|
324
|
-
output
|
|
325
|
-
})
|
|
326
|
-
|
|
327
|
-
resolve(output)
|
|
328
|
-
}).catch((error) => {
|
|
329
|
-
this.emit({
|
|
330
|
-
input: argv,
|
|
331
|
-
error: error.message
|
|
332
|
-
})
|
|
333
|
-
|
|
334
|
-
reject(error)
|
|
335
|
-
})
|
|
333
|
+
// Create fresh boundaries for this execution
|
|
334
|
+
const executionBoundaries = this._createBounderies({
|
|
335
|
+
definition: this._boundariesDefinition,
|
|
336
|
+
baseData: this._boundariesData,
|
|
337
|
+
mode: this._mode
|
|
336
338
|
})
|
|
337
339
|
|
|
338
|
-
|
|
340
|
+
// Start run for each boundary
|
|
341
|
+
for (const name in executionBoundaries) {
|
|
342
|
+
const boundary = executionBoundaries[name]
|
|
343
|
+
boundary.startRun()
|
|
344
|
+
}
|
|
339
345
|
|
|
340
|
-
|
|
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
|
+
)
|
|
352
|
+
|
|
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
|
|
362
|
+
|
|
363
|
+
// Accumulate in the task's total boundaries data
|
|
364
|
+
if (!this._accumulatedBoundariesData[name]) {
|
|
365
|
+
this._accumulatedBoundariesData[name] = []
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Get the current accumulated data for this boundary
|
|
369
|
+
const currentData = this._accumulatedBoundariesData[name]
|
|
370
|
+
|
|
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
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Emit the success event with boundary data
|
|
379
|
+
this.emit({
|
|
380
|
+
input: argv,
|
|
381
|
+
output,
|
|
382
|
+
boundaries: boundariesRunLog
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
return [null, output, boundariesRunLog]
|
|
386
|
+
} catch (error) {
|
|
387
|
+
// Process boundary data after error
|
|
388
|
+
const boundariesRunLog: Record<string, unknown> = {}
|
|
389
|
+
|
|
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>
|
|
341
431
|
}
|
|
342
432
|
}
|
|
343
433
|
|
|
@@ -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
|
+
})
|