@outputai/core 0.14.0 → 0.14.1-next.2947341.0

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.
Files changed (100) hide show
  1. package/dist/credentials/credentials.d.ts +6 -0
  2. package/dist/credentials/credentials.js +87 -0
  3. package/dist/credentials/encrypted_yaml_provider.d.ts +2 -0
  4. package/dist/credentials/encrypted_yaml_provider.js +77 -0
  5. package/dist/credentials/encryption.d.ts +3 -0
  6. package/dist/credentials/encryption.js +6 -0
  7. package/dist/credentials/errors.d.ts +13 -0
  8. package/dist/credentials/errors.js +39 -0
  9. package/dist/credentials/index.d.ts +6 -0
  10. package/dist/credentials/index.js +5 -0
  11. package/dist/credentials/init.d.ts +1 -0
  12. package/dist/credentials/init.js +7 -0
  13. package/dist/credentials/paths.d.ts +7 -0
  14. package/dist/credentials/paths.js +14 -0
  15. package/dist/credentials/provider_registry.d.ts +3 -0
  16. package/dist/credentials/provider_registry.js +6 -0
  17. package/dist/credentials/types.d.ts +12 -0
  18. package/dist/credentials/types.js +1 -0
  19. package/package.json +25 -14
  20. package/src/async_storage.d.ts +4 -0
  21. package/src/consts.js +0 -2
  22. package/src/helpers/object.d.ts +2 -0
  23. package/src/hooks/index.d.ts +0 -8
  24. package/src/hooks/index.js +0 -3
  25. package/src/logger/index.d.ts +4 -0
  26. package/src/worker/index.js +3 -1
  27. package/bin/healthcheck.spec.js +0 -90
  28. package/src/bus.spec.js +0 -196
  29. package/src/errors.spec.js +0 -19
  30. package/src/helpers/activity_options.spec.js +0 -55
  31. package/src/helpers/component.spec.js +0 -54
  32. package/src/helpers/error_serializer.spec.js +0 -479
  33. package/src/helpers/errors.spec.js +0 -189
  34. package/src/helpers/fetch.spec.js +0 -292
  35. package/src/helpers/function.spec.js +0 -24
  36. package/src/helpers/object.spec.js +0 -425
  37. package/src/helpers/promise.spec.js +0 -62
  38. package/src/helpers/redact.spec.js +0 -66
  39. package/src/helpers/string.spec.js +0 -99
  40. package/src/helpers/temporal_context.spec.ts +0 -83
  41. package/src/helpers/trace_info.spec.js +0 -45
  42. package/src/helpers/workflow_context.spec.js +0 -46
  43. package/src/hooks/index.spec.js +0 -265
  44. package/src/hooks/pending_hooks.spec.js +0 -62
  45. package/src/interface/evaluator.spec.js +0 -685
  46. package/src/interface/logger.spec.js +0 -172
  47. package/src/interface/step.spec.js +0 -128
  48. package/src/interface/validations/index.spec.js +0 -197
  49. package/src/interface/validations/schemas.spec.js +0 -229
  50. package/src/interface/webhook.spec.js +0 -170
  51. package/src/interface/workflow.spec.js +0 -710
  52. package/src/interface/workflow_utils.spec.js +0 -271
  53. package/src/interface/zod_integration.spec.js +0 -646
  54. package/src/internal_activities/index.spec.js +0 -186
  55. package/src/logger/development.spec.js +0 -87
  56. package/src/logger/index.spec.js +0 -27
  57. package/src/logger/production.spec.js +0 -71
  58. package/src/sdk/helpers/component_metadata.spec.js +0 -30
  59. package/src/sdk/helpers/objects.spec.js +0 -16
  60. package/src/sdk/helpers/path.spec.js +0 -102
  61. package/src/sdk/runtime/context.spec.js +0 -37
  62. package/src/sdk/runtime/events.spec.js +0 -40
  63. package/src/temporal/client.spec.js +0 -124
  64. package/src/tracing/processors/local/index.spec.js +0 -204
  65. package/src/tracing/processors/s3/configs.spec.js +0 -64
  66. package/src/tracing/processors/s3/index.spec.js +0 -185
  67. package/src/tracing/processors/s3/redis_client.spec.js +0 -179
  68. package/src/tracing/processors/s3/s3_client.spec.js +0 -68
  69. package/src/tracing/tools/build_trace_tree.spec.js +0 -216
  70. package/src/tracing/trace_engine.spec.js +0 -299
  71. package/src/worker/bundle.spec.js +0 -53
  72. package/src/worker/bundler_options.spec.js +0 -62
  73. package/src/worker/catalog_workflow/catalog_publisher.spec.js +0 -317
  74. package/src/worker/catalog_workflow/index.spec.js +0 -222
  75. package/src/worker/configs.spec.js +0 -213
  76. package/src/worker/configs_tuner_schema.spec.js +0 -127
  77. package/src/worker/connection_monitor.spec.js +0 -328
  78. package/src/worker/global_functions.spec.js +0 -55
  79. package/src/worker/index.spec.js +0 -526
  80. package/src/worker/interceptors/activity.spec.js +0 -369
  81. package/src/worker/interceptors/headers.spec.js +0 -73
  82. package/src/worker/interceptors/workflow.spec.js +0 -357
  83. package/src/worker/interruption.spec.js +0 -177
  84. package/src/worker/loader/activities.spec.js +0 -212
  85. package/src/worker/loader/hooks.spec.js +0 -64
  86. package/src/worker/loader/matchers.spec.js +0 -140
  87. package/src/worker/loader/tools.spec.js +0 -595
  88. package/src/worker/loader/workflows.spec.js +0 -287
  89. package/src/worker/log_hooks.spec.js +0 -303
  90. package/src/worker/shutdown.spec.js +0 -197
  91. package/src/worker/sinks.spec.js +0 -220
  92. package/src/worker/telemetry.spec.js +0 -80
  93. package/src/worker/temporal_logger.spec.js +0 -90
  94. package/src/worker/webpack_loaders/tools.spec.js +0 -314
  95. package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.spec.js +0 -134
  96. package/src/worker/webpack_loaders/workflow_rewriter/index.spec.js +0 -154
  97. package/src/worker/webpack_loaders/workflow_rewriter/rewrite_activity_calls.spec.js +0 -165
  98. package/src/worker/webpack_loaders/workflow_validator/index.spec.js +0 -202
  99. package/src/worker/webpack_loaders/workflow_validator/npm_workflow_export_resolve.spec.js +0 -374
  100. package/src/worker/worker_runner.spec.js +0 -220
@@ -1,425 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { assignImmutableProperty, clone, deepMerge, deepMergeWithResolver, isPlainObject, shuffleArray } from './object.js';
3
-
4
- describe( 'clone', () => {
5
- it( 'produces a deep copy without shared references', () => {
6
- const original = { a: 1, nested: { b: 2 } };
7
- const copied = clone( original );
8
-
9
- copied.nested.b = 3;
10
-
11
- expect( original.nested.b ).toBe( 2 );
12
- expect( copied.nested.b ).toBe( 3 );
13
- expect( copied ).not.toBe( original );
14
- } );
15
-
16
- it( 'deep copies JSON-compatible arrays and objects', () => {
17
- const original = {
18
- arr: [ 1, { nested: true } ],
19
- str: 'value',
20
- bool: false,
21
- nil: null
22
- };
23
- const copied = clone( original );
24
-
25
- copied.arr[1].nested = false;
26
-
27
- expect( copied ).toEqual( {
28
- arr: [ 1, { nested: false } ],
29
- str: 'value',
30
- bool: false,
31
- nil: null
32
- } );
33
- expect( original.arr[1].nested ).toBe( true );
34
- expect( copied ).not.toBe( original );
35
- expect( copied.arr ).not.toBe( original.arr );
36
- } );
37
-
38
- it( 'returns primitive JSON values when they can be parsed', () => {
39
- expect( clone( null ) ).toBeNull();
40
- expect( clone( true ) ).toBe( true );
41
- expect( clone( false ) ).toBe( false );
42
- expect( clone( 123 ) ).toBe( 123 );
43
- expect( clone( 'hello' ) ).toBe( 'hello' );
44
- } );
45
-
46
- it( 'returns original values when JSON serialization produces no parseable payload', () => {
47
- const sym = Symbol( 'x' );
48
- const fn = () => {};
49
- class Foo {}
50
-
51
- expect( clone( undefined ) ).toBeUndefined();
52
- expect( clone( sym ) ).toBe( sym );
53
- expect( clone( fn ) ).toBe( fn );
54
- expect( clone( Foo ) ).toBe( Foo );
55
- expect( clone( Date ) ).toBe( Date );
56
- expect( clone( Object ) ).toBe( Object );
57
- expect( clone( Number ) ).toBe( Number );
58
- } );
59
-
60
- it( 'returns original values when JSON serialization throws', () => {
61
- const circular = { name: 'circular' };
62
- circular.self = circular;
63
- const bigint = 1n;
64
-
65
- expect( clone( circular ) ).toBe( circular );
66
- expect( clone( bigint ) ).toBe( bigint );
67
- } );
68
-
69
- it( 'keeps JSON.stringify semantics for special numeric values', () => {
70
- expect( clone( NaN ) ).toBeNull();
71
- expect( clone( Infinity ) ).toBeNull();
72
- expect( clone( -Infinity ) ).toBeNull();
73
- } );
74
-
75
- it( 'keeps JSON.stringify semantics for non-plain object instances', () => {
76
- const date = new Date( '2025-01-01T00:00:00.000Z' );
77
-
78
- expect( clone( date ) ).toBe( '2025-01-01T00:00:00.000Z' );
79
- expect( clone( /abc/ ) ).toEqual( {} );
80
- expect( clone( new Map( [ [ 'a', 1 ] ] ) ) ).toEqual( {} );
81
- expect( clone( new Set( [ 1, 2 ] ) ) ).toEqual( {} );
82
- } );
83
-
84
- it( 'drops object properties that JSON.stringify omits', () => {
85
- const sym = Symbol( 'x' );
86
- const original = {
87
- kept: 'yes',
88
- missing: undefined,
89
- fn: () => {},
90
- sym
91
- };
92
-
93
- expect( clone( original ) ).toEqual( { kept: 'yes' } );
94
- } );
95
- } );
96
-
97
- describe( 'deepMerge', () => {
98
- it( 'returns a clone when only the base object is provided', () => {
99
- const a = {
100
- nested: { value: 1 }
101
- };
102
- const result = deepMerge( a );
103
-
104
- a.nested.value = 2;
105
-
106
- expect( result ).toEqual( {
107
- nested: { value: 1 }
108
- } );
109
- } );
110
-
111
- it( 'Throws when first argument is not a plain object', () => {
112
- expect( () => deepMerge( null ) ).toThrow( Error );
113
- } );
114
-
115
- it( 'merges multiple objects from left to right using rightmost values', () => {
116
- expect( deepMerge(
117
- {
118
- a: 1,
119
- nested: {
120
- a: 'base',
121
- kept: true
122
- }
123
- },
124
- {
125
- b: 2,
126
- nested: {
127
- a: 'first',
128
- b: 'first'
129
- }
130
- },
131
- {
132
- a: 3,
133
- nested: {
134
- b: 'second',
135
- c: 'second'
136
- }
137
- }
138
- ) ).toEqual( {
139
- a: 3,
140
- b: 2,
141
- nested: {
142
- a: 'first',
143
- b: 'second',
144
- c: 'second',
145
- kept: true
146
- }
147
- } );
148
- } );
149
-
150
- it( 'ignores non-object values among multiple overlays', () => {
151
- expect( deepMerge(
152
- { a: 1 },
153
- null,
154
- { b: 2 },
155
- undefined,
156
- { a: 3 }
157
- ) ).toEqual( {
158
- a: 3,
159
- b: 2
160
- } );
161
- } );
162
- } );
163
-
164
- describe( 'deepMergeWithResolver', () => {
165
- it( 'returns a clone when only the base object and resolver are provided', () => {
166
- const a = {
167
- nested: { value: 1 }
168
- };
169
- const result = deepMergeWithResolver( a, ( x, y ) => x + y );
170
-
171
- a.nested.value = 2;
172
-
173
- expect( result ).toEqual( {
174
- nested: { value: 1 }
175
- } );
176
- } );
177
-
178
- it( 'uses resolver for existing leaf values, including nested leaves', () => {
179
- const a = {
180
- cost: { total: 1 },
181
- tokens: { total: 2, input: 3 }
182
- };
183
- const b = {
184
- cost: { total: 4 },
185
- tokens: { total: 5, input: 6, output: 7 }
186
- };
187
-
188
- expect( deepMergeWithResolver( a, b, ( x, y ) => x + y ) ).toEqual( {
189
- cost: { total: 5 },
190
- tokens: { total: 7, input: 9, output: 7 }
191
- } );
192
- } );
193
-
194
- it( 'copies values from "b" when they do not exist in "a"', () => {
195
- const resolver = vi.fn( ( x, y ) => x + y );
196
-
197
- expect( deepMergeWithResolver( { a: 1 }, { b: 2, nested: { c: 3 } }, resolver ) ).toEqual( {
198
- a: 1,
199
- b: 2,
200
- nested: { c: 3 }
201
- } );
202
- expect( resolver ).not.toHaveBeenCalled();
203
- } );
204
-
205
- it( 'keeps extra values from "a" when absent from "b"', () => {
206
- expect( deepMergeWithResolver( { a: 1, nested: { kept: 2 } }, { b: 3 }, ( x, y ) => x + y ) ).toEqual( {
207
- a: 1,
208
- nested: { kept: 2 },
209
- b: 3
210
- } );
211
- } );
212
-
213
- it( 'returns a clone of "a" when "b" is not an object', () => {
214
- const a = { nested: { value: 1 } };
215
- const result = deepMergeWithResolver( a, null, ( x, y ) => x + y );
216
-
217
- a.nested.value = 2;
218
- expect( result ).toEqual( { nested: { value: 1 } } );
219
- } );
220
-
221
- it( 'throws when first argument is not a plain object', () => {
222
- expect( () => deepMergeWithResolver( null, {}, ( x, y ) => x + y ) ).toThrow( Error );
223
- expect( () => deepMergeWithResolver( [], {}, ( x, y ) => x + y ) ).toThrow( Error );
224
- expect( () => deepMergeWithResolver( 'a', {}, ( x, y ) => x + y ) ).toThrow( Error );
225
- } );
226
-
227
- it( 'throws when last argument is not a resolver function', () => {
228
- expect( () => deepMergeWithResolver( { a: 1 }, { a: 2 } ) )
229
- .toThrow( 'Last argument (resolver) is not a function.' );
230
- } );
231
-
232
- it( 'merges multiple objects using the resolver from left to right', () => {
233
- const resolver = vi.fn( ( x, y ) => `${ x }:${ y }` );
234
-
235
- expect( deepMergeWithResolver(
236
- {
237
- a: 'base',
238
- nested: {
239
- count: 1,
240
- kept: 'yes'
241
- }
242
- },
243
- {
244
- a: 'first',
245
- nested: {
246
- count: 2,
247
- firstOnly: true
248
- }
249
- },
250
- {
251
- a: 'second',
252
- nested: {
253
- count: 3,
254
- secondOnly: true
255
- }
256
- },
257
- resolver
258
- ) ).toEqual( {
259
- a: 'base:first:second',
260
- nested: {
261
- count: '1:2:3',
262
- firstOnly: true,
263
- kept: 'yes',
264
- secondOnly: true
265
- }
266
- } );
267
- expect( resolver ).toHaveBeenCalledTimes( 4 );
268
- } );
269
-
270
- it( 'ignores non-object values among multiple resolver overlays', () => {
271
- const resolver = vi.fn( ( x, y ) => x + y );
272
-
273
- expect( deepMergeWithResolver(
274
- { a: 1 },
275
- null,
276
- { a: 2 },
277
- undefined,
278
- { b: 3 },
279
- resolver
280
- ) ).toEqual( {
281
- a: 3,
282
- b: 3
283
- } );
284
- expect( resolver ).toHaveBeenCalledOnce();
285
- } );
286
- } );
287
-
288
- describe( 'isPlainObject', () => {
289
- it( 'Detects plain objects', () => {
290
- expect( isPlainObject( {} ) ).toBe( true );
291
- expect( isPlainObject( { a: 1 } ) ).toBe( true );
292
- expect( isPlainObject( new Object() ) ).toBe( true );
293
- expect( isPlainObject( new Object( { foo: 'bar' } ) ) ).toBe( true );
294
- expect( isPlainObject( Object.create( {}.constructor.prototype ) ) ).toBe( true );
295
- expect( isPlainObject( Object.create( Object.prototype ) ) ).toBe( true );
296
- } );
297
-
298
- it( 'Detects plain objects with different prototypes than Object.prototype', () => {
299
- // Object with null prototype
300
- expect( isPlainObject( Object.create( null ) ) ).toBe( true );
301
- } );
302
-
303
- it( 'Detects non plain objects that had their __proto__ mutated to Object.prototype or null', () => {
304
- class Foo {}
305
- const x = new Foo();
306
- x.__proto__ = Object.prototype;
307
- expect( isPlainObject( x ) ).toBe( true );
308
-
309
- const y = new Foo();
310
- y.__proto__ = null;
311
- expect( isPlainObject( y ) ).toBe( true );
312
- } );
313
-
314
- it( 'Returns false for object which the prototype is not Object.prototype or null', () => {
315
- // Object which the prototype is a plain {}
316
- expect( isPlainObject( Object.create( {} ) ) ).toBe( false );
317
- // Object which prototype is a another object with null prototype
318
- expect( isPlainObject( Object.create( Object.create( null ) ) ) ).toBe( false );
319
- } );
320
-
321
- it( 'Returns false for functions', () => {
322
- expect( isPlainObject( Function ) ).toBe( false );
323
- expect( isPlainObject( () => {} ) ).toBe( false );
324
- expect( isPlainObject( class Foo {} ) ).toBe( false );
325
- expect( isPlainObject( Number.constructor ) ).toBe( false );
326
- expect( isPlainObject( Number.constructor.prototype ) ).toBe( false );
327
- } );
328
-
329
- it( 'Returns false for arrays', () => {
330
- expect( isPlainObject( [ 1, 2, 3 ] ) ).toBe( false );
331
- expect( isPlainObject( [] ) ).toBe( false );
332
- expect( isPlainObject( Array( 3 ) ) ).toBe( false );
333
- } );
334
-
335
- it( 'Returns false for primitives', () => {
336
- expect( isPlainObject( null ) ).toBe( false );
337
- expect( isPlainObject( undefined ) ).toBe( false );
338
- expect( isPlainObject( false ) ).toBe( false );
339
- expect( isPlainObject( true ) ).toBe( false );
340
- expect( isPlainObject( 1 ) ).toBe( false );
341
- expect( isPlainObject( 0 ) ).toBe( false );
342
- expect( isPlainObject( '' ) ).toBe( false );
343
- expect( isPlainObject( 'foo' ) ).toBe( false );
344
- expect( isPlainObject( Symbol( 'foo' ) ) ).toBe( false );
345
- expect( isPlainObject( Symbol.for( 'foo' ) ) ).toBe( false );
346
- } );
347
-
348
- it( 'Returns true for built in objects', () => {
349
- expect( isPlainObject( Math ) ).toBe( true );
350
- expect( isPlainObject( JSON ) ).toBe( true );
351
- } );
352
-
353
- it( 'Returns false for built in types', () => {
354
- expect( isPlainObject( String ) ).toBe( false );
355
- expect( isPlainObject( Number ) ).toBe( false );
356
- expect( isPlainObject( Date ) ).toBe( false );
357
- } );
358
-
359
- it( 'Returns false for other instance where prototype is not object or null', () => {
360
- expect( isPlainObject( /foo/ ) ).toBe( false );
361
- expect( isPlainObject( new RegExp( 'foo' ) ) ).toBe( false );
362
- expect( isPlainObject( new Date() ) ).toBe( false );
363
- class Foo {}
364
- expect( isPlainObject( new Foo() ) ).toBe( false );
365
- expect( isPlainObject( Object.create( ( class Foo {} ).prototype ) ) ).toBe( false );
366
- } );
367
-
368
- it( 'Returns false if tries to change the prototype to simulate an object', () => {
369
- function Bar() {}
370
- Bar.prototype = Object.create( null );
371
- expect( isPlainObject( new Bar() ) ).toBe( false );
372
- } );
373
-
374
- it( 'Returns false if object proto was mutated to anything else than object or null', () => {
375
- const zum = {};
376
- zum.__proto__ = Number.prototype;
377
- expect( isPlainObject( zum ) ).toBe( false );
378
- } );
379
- } );
380
-
381
- describe( 'assignImmutableProperty', () => {
382
- it( 'defines a non-writable, non-configurable, non-enumerable property', () => {
383
- const obj = {};
384
- const key = Symbol( 'metadata' );
385
- const value = { name: 'test' };
386
-
387
- expect( assignImmutableProperty( obj, key, value ) ).toBe( obj );
388
- expect( obj[key] ).toBe( value );
389
- expect( Object.getOwnPropertyDescriptor( obj, key ) ).toEqual( {
390
- value,
391
- writable: false,
392
- configurable: false,
393
- enumerable: false
394
- } );
395
- expect( Object.keys( obj ) ).toEqual( [] );
396
- } );
397
-
398
- it( 'prevents reassignment and redefinition', () => {
399
- const obj = {};
400
- const key = 'metadata';
401
-
402
- assignImmutableProperty( obj, key, 'original' );
403
-
404
- expect( () => {
405
- obj[key] = 'updated';
406
- } ).toThrow( TypeError );
407
- expect( () => {
408
- Object.defineProperty( obj, key, { value: 'updated' } );
409
- } ).toThrow( TypeError );
410
- expect( obj[key] ).toBe( 'original' );
411
- } );
412
- } );
413
-
414
- describe( 'shuffleArray', () => {
415
- it( 'returns a shuffled copy based on random sort keys', () => {
416
- vi.spyOn( Math, 'random' )
417
- .mockReturnValueOnce( 0.4 )
418
- .mockReturnValueOnce( 0.1 )
419
- .mockReturnValueOnce( 0.3 );
420
- const arr = [ 'a', 'b', 'c' ];
421
-
422
- expect( shuffleArray( arr ) ).toEqual( [ 'b', 'c', 'a' ] );
423
- expect( arr ).toEqual( [ 'a', 'b', 'c' ] );
424
- } );
425
- } );
@@ -1,62 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { sleepCancellable } from './promise.js';
3
-
4
- // Long enough that a sleep reaching it means the abort was not honored and the test times out.
5
- const neverElapses = 60000;
6
-
7
- describe( 'sleepCancellable', () => {
8
- it( 'resolves only once the timeout elapses', async () => {
9
- const order = [];
10
- const sleeping = sleepCancellable( 10 ).then( () => order.push( 'slept' ) );
11
-
12
- await Promise.resolve();
13
- expect( order ).toEqual( [] );
14
-
15
- await sleeping;
16
- expect( order ).toEqual( [ 'slept' ] );
17
- } );
18
-
19
- it( 'resolves without a signal', async () => {
20
- await expect( sleepCancellable( 10 ) ).resolves.toBeUndefined();
21
- } );
22
-
23
- it( 'resolves immediately when the signal is already aborted', async () => {
24
- const controller = new AbortController();
25
- controller.abort();
26
-
27
- await expect( sleepCancellable( neverElapses, controller.signal ) ).resolves.toBeUndefined();
28
- } );
29
-
30
- it( 'resolves as soon as the signal aborts mid sleep', async () => {
31
- const controller = new AbortController();
32
- const sleeping = sleepCancellable( neverElapses, controller.signal );
33
-
34
- controller.abort();
35
-
36
- await expect( sleeping ).resolves.toBeUndefined();
37
- } );
38
-
39
- it( 'swallows the abort reason instead of rejecting with it', async () => {
40
- const controller = new AbortController();
41
- const sleeping = sleepCancellable( neverElapses, controller.signal );
42
-
43
- controller.abort( new Error( 'shutting down' ) );
44
-
45
- await expect( sleeping ).resolves.toBeUndefined();
46
- } );
47
-
48
- it( 'rethrows a rejection that is not an abort', async () => {
49
- const failure = new Error( 'timer failed' );
50
- vi.doMock( 'node:timers/promises', () => ( { setTimeout: () => Promise.reject( failure ) } ) );
51
- vi.resetModules();
52
-
53
- try {
54
- const { sleepCancellable: sleeping } = await import( './promise.js' );
55
-
56
- await expect( sleeping( 10 ) ).rejects.toBe( failure );
57
- } finally {
58
- vi.doUnmock( 'node:timers/promises' );
59
- vi.resetModules();
60
- }
61
- } );
62
- } );
@@ -1,66 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { redactHeaders, redactUrl } from './redact.js';
3
-
4
- describe( 'redactUrl', () => {
5
- it( 'redacts credentials, query values, and the fragment while preserving URL structure', () => {
6
- const result = redactUrl( 'https://user:password@example.com/models?model=gpt-5&token=secret#debug' );
7
-
8
- expect( result ).toBe( 'https://***:***@example.com/models?model=***&token=***#***' );
9
- } );
10
-
11
- it( 'preserves duplicate query parameters and their order', () => {
12
- const result = redactUrl( 'https://example.com/search?tag=first&tag=second&empty=#results' );
13
-
14
- expect( result ).toBe( 'https://example.com/search?tag=***&tag=***&empty=***#***' );
15
- } );
16
-
17
- it( 'returns URLs without redacted components unchanged', () => {
18
- expect( redactUrl( 'https://example.com/path' ) ).toBe( 'https://example.com/path' );
19
- } );
20
-
21
- it( 'does not mutate URL instances', () => {
22
- const originalValue = 'ftp://user:password@example.com/file.txt?download=true#details';
23
- const url = new URL( originalValue );
24
-
25
- expect( redactUrl( url ) ).toBe( 'ftp://***:***@example.com/file.txt?download=***#***' );
26
- expect( url.toString() ).toBe( originalValue );
27
- } );
28
- } );
29
-
30
- describe( 'redactHeaders', () => {
31
- it( 'redacts sensitive header names', () => {
32
- const result = redactHeaders( {
33
- Authorization: 'Bearer token',
34
- 'X-Api-Key': 'api-key',
35
- Cookie: 'session=id',
36
- 'x-client-secret': 'secret'
37
- } );
38
-
39
- expect( result ).toEqual( {
40
- Authorization: '[REDACTED]',
41
- 'X-Api-Key': '[REDACTED]',
42
- Cookie: '[REDACTED]',
43
- 'x-client-secret': '[REDACTED]'
44
- } );
45
- } );
46
-
47
- it( 'preserves non-sensitive header names and ignored false positives', () => {
48
- const result = redactHeaders( {
49
- Accept: 'application/json',
50
- 'Content-Type': 'application/json',
51
- 'x-csrf-token': 'csrf-token',
52
- 'public-key-pins': 'pin'
53
- } );
54
-
55
- expect( result ).toEqual( {
56
- Accept: 'application/json',
57
- 'Content-Type': 'application/json',
58
- 'x-csrf-token': 'csrf-token',
59
- 'public-key-pins': 'pin'
60
- } );
61
- } );
62
-
63
- it( 'handles empty headers', () => {
64
- expect( redactHeaders( {} ) ).toEqual( {} );
65
- } );
66
- } );
@@ -1,99 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { isUrl, toUrlSafeBase64, rxEscape } from './string.js';
3
-
4
- describe( 'toUrlSafeBase64', () => {
5
- const urlSafeAlphabet = /^[A-Za-z0-9_-]+$/;
6
-
7
- it( 'returns a string for a valid UUID', () => {
8
- const uuid = '550e8400-e29b-41d4-a716-446655440000';
9
- expect( typeof toUrlSafeBase64( uuid ) ).toBe( 'string' );
10
- expect( toUrlSafeBase64( uuid ).length ).toBeGreaterThan( 0 );
11
- } );
12
-
13
- it( 'output length is 21 or 22 for a standard UUID', () => {
14
- const uuid = '550e8400-e29b-41d4-a716-446655440000';
15
- const out = toUrlSafeBase64( uuid );
16
- expect( out.length ).toBeGreaterThanOrEqual( 21 );
17
- expect( out.length ).toBeLessThanOrEqual( 22 );
18
- } );
19
-
20
- it( 'output contains only url-safe alphabet characters', () => {
21
- const uuid = '550e8400-e29b-41d4-a716-446655440000';
22
- const out = toUrlSafeBase64( uuid );
23
- expect( out ).toMatch( urlSafeAlphabet );
24
- } );
25
-
26
- it( 'is deterministic for the same UUID', () => {
27
- const uuid = '6ba7b810-9dad-11d1-80b4-00c04fd430c8';
28
- expect( toUrlSafeBase64( uuid ) ).toBe( toUrlSafeBase64( uuid ) );
29
- } );
30
-
31
- it( 'different UUIDs produce different strings', () => {
32
- const a = toUrlSafeBase64( '550e8400-e29b-41d4-a716-446655440000' );
33
- const b = toUrlSafeBase64( '6ba7b810-9dad-11d1-80b4-00c04fd430c8' );
34
- expect( a ).not.toBe( b );
35
- } );
36
-
37
- it( 'strips hyphens and encodes hex (same as 32-char hex)', () => {
38
- const withHyphens = '550e8400-e29b-41d4-a716-446655440000';
39
- const hexOnly = '550e8400e29b41d4a716446655440000';
40
- expect( toUrlSafeBase64( withHyphens ) ).toBe( toUrlSafeBase64( hexOnly ) );
41
- } );
42
- } );
43
-
44
- describe( 'rxEscape', () => {
45
- it( 'escapes all regexp metacharacters', () => {
46
- expect( rxEscape( '.*+?^${}()|[]\\' ) ).toBe( '\\.\\*\\+\\?\\^\\$\\{\\}\\(\\)\\|\\[\\]\\\\' );
47
- } );
48
-
49
- it( 'keeps file URL paths matchable as literal regexp input', () => {
50
- const path = 'file://foo/bar';
51
- const rx = new RegExp( `^${rxEscape( path )}$` );
52
-
53
- expect( rx.test( path ) ).toBe( true );
54
- expect( rx.test( 'file://foo/bar/baz' ) ).toBe( false );
55
- } );
56
-
57
- it( 'keeps Windows paths matchable as literal regexp input', () => {
58
- const path = String.raw`C:\foo\bar`;
59
- const rx = new RegExp( `^${rxEscape( path )}$` );
60
-
61
- expect( rx.test( path ) ).toBe( true );
62
- expect( rx.test( String.raw`C:\foo\bar\baz` ) ).toBe( false );
63
- } );
64
- } );
65
-
66
- describe( 'isUrl', () => {
67
- it.each( [
68
- 'https://example.com/path?token=secret#section',
69
- 'ftp://user:password@example.com/file.txt',
70
- 'ftps://example.com/file.txt',
71
- 'git+ssh://example.com/repository',
72
- 'custom.protocol-v2://example.com/resource'
73
- ] )( 'accepts a valid URL starting with a protocol: %s', value => {
74
- expect( isUrl( value ) ).toBe( true );
75
- } );
76
-
77
- it.each( [
78
- 'Error: fetch failed',
79
- 'TypeError: invalid input',
80
- 'mailto:user@example.com',
81
- 'urn:isbn:978014',
82
- 'data:text/plain,hello'
83
- ] )( 'rejects non-authority URI-like strings: %s', value => {
84
- expect( isUrl( value ) ).toBe( false );
85
- } );
86
-
87
- it.each( [
88
- '//example.com/path',
89
- '/relative/path',
90
- 'https://',
91
- 'not a URL'
92
- ] )( 'rejects malformed or relative values: %s', value => {
93
- expect( isUrl( value ) ).toBe( false );
94
- } );
95
-
96
- it.each( [ undefined, null, 42, new URL( 'https://example.com' ) ] )( 'rejects non-string values', value => {
97
- expect( isUrl( value ) ).toBe( false );
98
- } );
99
- } );