@forgehive/task 0.1.5 → 0.1.7

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