@outputai/core 0.14.0 → 0.14.1-next.51e8690.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.
- package/dist/credentials/credentials.d.ts +6 -0
- package/dist/credentials/credentials.js +87 -0
- package/dist/credentials/encrypted_yaml_provider.d.ts +2 -0
- package/dist/credentials/encrypted_yaml_provider.js +77 -0
- package/dist/credentials/encryption.d.ts +3 -0
- package/dist/credentials/encryption.js +6 -0
- package/dist/credentials/errors.d.ts +13 -0
- package/dist/credentials/errors.js +39 -0
- package/dist/credentials/index.d.ts +6 -0
- package/dist/credentials/index.js +5 -0
- package/dist/credentials/init.d.ts +1 -0
- package/dist/credentials/init.js +7 -0
- package/dist/credentials/paths.d.ts +7 -0
- package/dist/credentials/paths.js +14 -0
- package/dist/credentials/provider_registry.d.ts +3 -0
- package/dist/credentials/provider_registry.js +6 -0
- package/dist/credentials/types.d.ts +12 -0
- package/dist/credentials/types.js +1 -0
- package/package.json +14 -3
- package/src/async_storage.d.ts +4 -0
- package/src/consts.js +0 -2
- package/src/helpers/object.d.ts +2 -0
- package/src/hooks/index.d.ts +0 -8
- package/src/hooks/index.js +0 -3
- package/src/logger/index.d.ts +4 -0
- package/src/worker/index.js +3 -1
- package/bin/healthcheck.spec.js +0 -90
- package/src/bus.spec.js +0 -196
- package/src/errors.spec.js +0 -19
- package/src/helpers/activity_options.spec.js +0 -55
- package/src/helpers/component.spec.js +0 -54
- package/src/helpers/error_serializer.spec.js +0 -479
- package/src/helpers/errors.spec.js +0 -189
- package/src/helpers/fetch.spec.js +0 -292
- package/src/helpers/function.spec.js +0 -24
- package/src/helpers/object.spec.js +0 -425
- package/src/helpers/promise.spec.js +0 -62
- package/src/helpers/redact.spec.js +0 -66
- package/src/helpers/string.spec.js +0 -99
- package/src/helpers/temporal_context.spec.ts +0 -83
- package/src/helpers/trace_info.spec.js +0 -45
- package/src/helpers/workflow_context.spec.js +0 -46
- package/src/hooks/index.spec.js +0 -265
- package/src/hooks/pending_hooks.spec.js +0 -62
- package/src/interface/evaluator.spec.js +0 -685
- package/src/interface/logger.spec.js +0 -172
- package/src/interface/step.spec.js +0 -128
- package/src/interface/validations/index.spec.js +0 -197
- package/src/interface/validations/schemas.spec.js +0 -229
- package/src/interface/webhook.spec.js +0 -170
- package/src/interface/workflow.spec.js +0 -710
- package/src/interface/workflow_utils.spec.js +0 -271
- package/src/interface/zod_integration.spec.js +0 -646
- package/src/internal_activities/index.spec.js +0 -186
- package/src/logger/development.spec.js +0 -87
- package/src/logger/index.spec.js +0 -27
- package/src/logger/production.spec.js +0 -71
- package/src/sdk/helpers/component_metadata.spec.js +0 -30
- package/src/sdk/helpers/objects.spec.js +0 -16
- package/src/sdk/helpers/path.spec.js +0 -102
- package/src/sdk/runtime/context.spec.js +0 -37
- package/src/sdk/runtime/events.spec.js +0 -40
- package/src/temporal/client.spec.js +0 -124
- package/src/tracing/processors/local/index.spec.js +0 -204
- package/src/tracing/processors/s3/configs.spec.js +0 -64
- package/src/tracing/processors/s3/index.spec.js +0 -185
- package/src/tracing/processors/s3/redis_client.spec.js +0 -179
- package/src/tracing/processors/s3/s3_client.spec.js +0 -68
- package/src/tracing/tools/build_trace_tree.spec.js +0 -216
- package/src/tracing/trace_engine.spec.js +0 -299
- package/src/worker/bundle.spec.js +0 -53
- package/src/worker/bundler_options.spec.js +0 -62
- package/src/worker/catalog_workflow/catalog_publisher.spec.js +0 -317
- package/src/worker/catalog_workflow/index.spec.js +0 -222
- package/src/worker/configs.spec.js +0 -213
- package/src/worker/configs_tuner_schema.spec.js +0 -127
- package/src/worker/connection_monitor.spec.js +0 -328
- package/src/worker/global_functions.spec.js +0 -55
- package/src/worker/index.spec.js +0 -526
- package/src/worker/interceptors/activity.spec.js +0 -369
- package/src/worker/interceptors/headers.spec.js +0 -73
- package/src/worker/interceptors/workflow.spec.js +0 -357
- package/src/worker/interruption.spec.js +0 -177
- package/src/worker/loader/activities.spec.js +0 -212
- package/src/worker/loader/hooks.spec.js +0 -64
- package/src/worker/loader/matchers.spec.js +0 -140
- package/src/worker/loader/tools.spec.js +0 -595
- package/src/worker/loader/workflows.spec.js +0 -287
- package/src/worker/log_hooks.spec.js +0 -303
- package/src/worker/shutdown.spec.js +0 -197
- package/src/worker/sinks.spec.js +0 -220
- package/src/worker/telemetry.spec.js +0 -80
- package/src/worker/temporal_logger.spec.js +0 -90
- package/src/worker/webpack_loaders/tools.spec.js +0 -314
- package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.spec.js +0 -134
- package/src/worker/webpack_loaders/workflow_rewriter/index.spec.js +0 -154
- package/src/worker/webpack_loaders/workflow_rewriter/rewrite_activity_calls.spec.js +0 -165
- package/src/worker/webpack_loaders/workflow_validator/index.spec.js +0 -202
- package/src/worker/webpack_loaders/workflow_validator/npm_workflow_export_resolve.spec.js +0 -374
- 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
|
-
} );
|