@ontrails/core 1.0.0-beta.4 → 1.0.0-beta.41
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/CHANGELOG.md +412 -12
- package/README.md +112 -28
- package/package.json +11 -1
- package/src/activation-provenance.ts +116 -0
- package/src/activation-source-compatibility.ts +430 -0
- package/src/activation-source-projection.ts +227 -0
- package/src/activation-source.ts +93 -0
- package/src/blob-ref.ts +51 -0
- package/src/branded.ts +1 -1
- package/src/compose-batch.ts +69 -0
- package/src/compose-schema.ts +36 -0
- package/src/context.ts +59 -11
- package/src/derive.ts +271 -19
- package/src/detours.ts +8 -0
- package/src/diagnostics.ts +21 -0
- package/src/draft.ts +350 -0
- package/src/entity.ts +346 -0
- package/src/error-projection.ts +51 -0
- package/src/errors.ts +335 -48
- package/src/execute.ts +1577 -0
- package/src/fire.ts +1172 -0
- package/src/glob.ts +81 -0
- package/src/index.ts +568 -32
- package/src/internal/fork-ctx.ts +69 -0
- package/src/layer-projection.ts +193 -0
- package/src/layer.ts +43 -6
- package/src/observe.ts +361 -0
- package/src/path-scope.ts +66 -0
- package/src/path-security.ts +11 -3
- package/src/permits.ts +12 -0
- package/src/queue.ts +163 -0
- package/src/redaction/patterns.ts +6 -3
- package/src/resilience.ts +11 -11
- package/src/resource-config.ts +804 -0
- package/src/resource.ts +194 -0
- package/src/result.ts +18 -0
- package/src/run.ts +76 -0
- package/src/runtime-builtins.ts +69 -0
- package/src/schedule-runtime.ts +689 -0
- package/src/schedule.ts +326 -0
- package/src/serialization.ts +92 -55
- package/src/sha256.ts +136 -0
- package/src/signal-diagnostics.ts +633 -0
- package/src/signal-ref.ts +111 -0
- package/src/signal.ts +104 -0
- package/src/store/accessor-protocol.ts +56 -0
- package/src/store/index.ts +4 -0
- package/src/structured-examples.ts +248 -0
- package/src/surface-derivation.ts +91 -0
- package/src/surface-filter.ts +101 -0
- package/src/surface-overlay.ts +694 -0
- package/src/surface-versioning.ts +42 -0
- package/src/topo.ts +762 -38
- package/src/tracing.ts +346 -0
- package/src/trail-id-glob.ts +15 -0
- package/src/trail.ts +1244 -34
- package/src/trails/derive-trail.ts +835 -0
- package/src/trails/index.ts +9 -0
- package/src/trails/ingest.ts +152 -0
- package/src/trails-db.ts +212 -0
- package/src/transport-error-map.ts +174 -0
- package/src/type-utils.ts +87 -0
- package/src/types.ts +254 -12
- package/src/validate-established-topo.ts +73 -0
- package/src/validate-topo.ts +569 -55
- package/src/validation.ts +152 -4
- package/src/version-marker.ts +716 -0
- package/src/version-resolution.ts +308 -0
- package/src/version-runtime.ts +120 -0
- package/src/webhook.ts +461 -0
- package/src/workspace.ts +183 -16
- package/src/zod-wrappers.ts +72 -0
- package/.turbo/turbo-build.log +0 -1
- package/.turbo/turbo-lint.log +0 -3
- package/.turbo/turbo-typecheck.log +0 -1
- package/dist/adapters.d.ts +0 -39
- package/dist/adapters.d.ts.map +0 -1
- package/dist/adapters.js +0 -2
- package/dist/adapters.js.map +0 -1
- package/dist/blob-ref.d.ts +0 -20
- package/dist/blob-ref.d.ts.map +0 -1
- package/dist/blob-ref.js +0 -22
- package/dist/blob-ref.js.map +0 -1
- package/dist/branded.d.ts +0 -36
- package/dist/branded.d.ts.map +0 -1
- package/dist/branded.js +0 -89
- package/dist/branded.js.map +0 -1
- package/dist/collections.d.ts +0 -31
- package/dist/collections.d.ts.map +0 -1
- package/dist/collections.js +0 -60
- package/dist/collections.js.map +0 -1
- package/dist/context.d.ts +0 -10
- package/dist/context.d.ts.map +0 -1
- package/dist/context.js +0 -15
- package/dist/context.js.map +0 -1
- package/dist/derive.d.ts +0 -33
- package/dist/derive.d.ts.map +0 -1
- package/dist/derive.js +0 -125
- package/dist/derive.js.map +0 -1
- package/dist/errors.d.ts +0 -83
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js +0 -142
- package/dist/errors.js.map +0 -1
- package/dist/event.d.ts +0 -45
- package/dist/event.d.ts.map +0 -1
- package/dist/event.js +0 -17
- package/dist/event.js.map +0 -1
- package/dist/fetch.d.ts +0 -15
- package/dist/fetch.d.ts.map +0 -1
- package/dist/fetch.js +0 -102
- package/dist/fetch.js.map +0 -1
- package/dist/guards.d.ts +0 -17
- package/dist/guards.d.ts.map +0 -1
- package/dist/guards.js +0 -25
- package/dist/guards.js.map +0 -1
- package/dist/health.d.ts +0 -18
- package/dist/health.d.ts.map +0 -1
- package/dist/health.js +0 -5
- package/dist/health.js.map +0 -1
- package/dist/index.d.ts +0 -34
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -38
- package/dist/index.js.map +0 -1
- package/dist/job.d.ts +0 -24
- package/dist/job.d.ts.map +0 -1
- package/dist/job.js +0 -17
- package/dist/job.js.map +0 -1
- package/dist/layer.d.ts +0 -17
- package/dist/layer.d.ts.map +0 -1
- package/dist/layer.js +0 -21
- package/dist/layer.js.map +0 -1
- package/dist/path-security.d.ts +0 -28
- package/dist/path-security.d.ts.map +0 -1
- package/dist/path-security.js +0 -63
- package/dist/path-security.js.map +0 -1
- package/dist/patterns/bulk.d.ts +0 -15
- package/dist/patterns/bulk.d.ts.map +0 -1
- package/dist/patterns/bulk.js +0 -14
- package/dist/patterns/bulk.js.map +0 -1
- package/dist/patterns/change.d.ts +0 -10
- package/dist/patterns/change.d.ts.map +0 -1
- package/dist/patterns/change.js +0 -10
- package/dist/patterns/change.js.map +0 -1
- package/dist/patterns/date-range.d.ts +0 -10
- package/dist/patterns/date-range.d.ts.map +0 -1
- package/dist/patterns/date-range.js +0 -10
- package/dist/patterns/date-range.js.map +0 -1
- package/dist/patterns/index.d.ts +0 -9
- package/dist/patterns/index.d.ts.map +0 -1
- package/dist/patterns/index.js +0 -9
- package/dist/patterns/index.js.map +0 -1
- package/dist/patterns/pagination.d.ts +0 -18
- package/dist/patterns/pagination.d.ts.map +0 -1
- package/dist/patterns/pagination.js +0 -18
- package/dist/patterns/pagination.js.map +0 -1
- package/dist/patterns/progress.d.ts +0 -11
- package/dist/patterns/progress.d.ts.map +0 -1
- package/dist/patterns/progress.js +0 -11
- package/dist/patterns/progress.js.map +0 -1
- package/dist/patterns/sorting.d.ts +0 -13
- package/dist/patterns/sorting.d.ts.map +0 -1
- package/dist/patterns/sorting.js +0 -10
- package/dist/patterns/sorting.js.map +0 -1
- package/dist/patterns/status.d.ts +0 -15
- package/dist/patterns/status.d.ts.map +0 -1
- package/dist/patterns/status.js +0 -9
- package/dist/patterns/status.js.map +0 -1
- package/dist/patterns/timestamps.d.ts +0 -10
- package/dist/patterns/timestamps.d.ts.map +0 -1
- package/dist/patterns/timestamps.js +0 -10
- package/dist/patterns/timestamps.js.map +0 -1
- package/dist/redaction/index.d.ts +0 -4
- package/dist/redaction/index.d.ts.map +0 -1
- package/dist/redaction/index.js +0 -3
- package/dist/redaction/index.js.map +0 -1
- package/dist/redaction/patterns.d.ts +0 -9
- package/dist/redaction/patterns.d.ts.map +0 -1
- package/dist/redaction/patterns.js +0 -39
- package/dist/redaction/patterns.js.map +0 -1
- package/dist/redaction/redactor.d.ts +0 -27
- package/dist/redaction/redactor.d.ts.map +0 -1
- package/dist/redaction/redactor.js +0 -89
- package/dist/redaction/redactor.js.map +0 -1
- package/dist/resilience.d.ts +0 -34
- package/dist/resilience.d.ts.map +0 -1
- package/dist/resilience.js +0 -164
- package/dist/resilience.js.map +0 -1
- package/dist/result.d.ts +0 -57
- package/dist/result.d.ts.map +0 -1
- package/dist/result.js +0 -156
- package/dist/result.js.map +0 -1
- package/dist/serialization.d.ts +0 -27
- package/dist/serialization.d.ts.map +0 -1
- package/dist/serialization.js +0 -153
- package/dist/serialization.js.map +0 -1
- package/dist/topo.d.ts +0 -16
- package/dist/topo.d.ts.map +0 -1
- package/dist/topo.js +0 -66
- package/dist/topo.js.map +0 -1
- package/dist/trail.d.ts +0 -89
- package/dist/trail.d.ts.map +0 -1
- package/dist/trail.js +0 -18
- package/dist/trail.js.map +0 -1
- package/dist/types.d.ts +0 -46
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -2
- package/dist/types.js.map +0 -1
- package/dist/validate-topo.d.ts +0 -24
- package/dist/validate-topo.d.ts.map +0 -1
- package/dist/validate-topo.js +0 -158
- package/dist/validate-topo.js.map +0 -1
- package/dist/validation.d.ts +0 -27
- package/dist/validation.d.ts.map +0 -1
- package/dist/validation.js +0 -134
- package/dist/validation.js.map +0 -1
- package/dist/workspace.d.ts +0 -25
- package/dist/workspace.d.ts.map +0 -1
- package/dist/workspace.js +0 -57
- package/dist/workspace.js.map +0 -1
- package/src/__tests__/blob-ref.test.ts +0 -103
- package/src/__tests__/branded.test.ts +0 -148
- package/src/__tests__/collections.test.ts +0 -126
- package/src/__tests__/context.test.ts +0 -66
- package/src/__tests__/derive.test.ts +0 -203
- package/src/__tests__/errors.test.ts +0 -309
- package/src/__tests__/event.test.ts +0 -82
- package/src/__tests__/fetch.test.ts +0 -217
- package/src/__tests__/guards.test.ts +0 -102
- package/src/__tests__/job.test.ts +0 -98
- package/src/__tests__/layer.test.ts +0 -212
- package/src/__tests__/path-security.test.ts +0 -114
- package/src/__tests__/patterns.test.ts +0 -273
- package/src/__tests__/redaction.test.ts +0 -244
- package/src/__tests__/resilience.test.ts +0 -246
- package/src/__tests__/result.test.ts +0 -155
- package/src/__tests__/serialization.test.ts +0 -401
- package/src/__tests__/topo.test.ts +0 -181
- package/src/__tests__/trail.test.ts +0 -217
- package/src/__tests__/validate-topo.test.ts +0 -278
- package/src/__tests__/validation.test.ts +0 -283
- package/src/__tests__/workspace.test.ts +0 -183
- package/src/adapters.ts +0 -68
- package/src/event.ts +0 -77
- package/src/health.ts +0 -23
- package/src/job.ts +0 -20
- package/tsconfig.json +0 -9
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from 'bun:test';
|
|
2
|
-
import { resolve, join } from 'node:path';
|
|
3
|
-
|
|
4
|
-
import { PermissionError } from '../errors.js';
|
|
5
|
-
import { securePath, isPathSafe, resolveSafePath } from '../path-security.js';
|
|
6
|
-
|
|
7
|
-
// ---------------------------------------------------------------------------
|
|
8
|
-
// securePath
|
|
9
|
-
// ---------------------------------------------------------------------------
|
|
10
|
-
|
|
11
|
-
describe('securePath', () => {
|
|
12
|
-
const base = '/project/workspace';
|
|
13
|
-
|
|
14
|
-
test('resolves a simple relative path', () => {
|
|
15
|
-
const result = securePath(base, 'src/index.ts');
|
|
16
|
-
expect(result.isOk()).toBe(true);
|
|
17
|
-
expect(result.unwrap()).toBe(resolve(base, 'src/index.ts'));
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
test('resolves nested paths', () => {
|
|
21
|
-
const result = securePath(base, 'src/../lib/utils.ts');
|
|
22
|
-
expect(result.isOk()).toBe(true);
|
|
23
|
-
expect(result.unwrap()).toBe(resolve(base, 'lib/utils.ts'));
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
test('accepts the base directory itself', () => {
|
|
27
|
-
const result = securePath(base, '.');
|
|
28
|
-
expect(result.isOk()).toBe(true);
|
|
29
|
-
expect(result.unwrap()).toBe(resolve(base));
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
test('rejects path traversal with ..', () => {
|
|
33
|
-
const result = securePath(base, '../../etc/passwd');
|
|
34
|
-
expect(result.isErr()).toBe(true);
|
|
35
|
-
const err = result as unknown as { error: PermissionError };
|
|
36
|
-
expect(err.error).toBeInstanceOf(PermissionError);
|
|
37
|
-
expect(err.error.message).toContain('Path traversal detected');
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
test('rejects absolute paths outside base', () => {
|
|
41
|
-
const result = securePath(base, '/etc/passwd');
|
|
42
|
-
expect(result.isErr()).toBe(true);
|
|
43
|
-
const err = result as unknown as { error: PermissionError };
|
|
44
|
-
expect(err.error).toBeInstanceOf(PermissionError);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
test('rejects sneaky traversal (subdir/../../..)', () => {
|
|
48
|
-
const result = securePath(base, 'src/../../..');
|
|
49
|
-
expect(result.isErr()).toBe(true);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test('allows paths that mention .. but stay inside', () => {
|
|
53
|
-
const result = securePath(base, 'src/../src/file.ts');
|
|
54
|
-
expect(result.isOk()).toBe(true);
|
|
55
|
-
expect(result.unwrap()).toBe(resolve(base, 'src/file.ts'));
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
// ---------------------------------------------------------------------------
|
|
60
|
-
// isPathSafe
|
|
61
|
-
// ---------------------------------------------------------------------------
|
|
62
|
-
|
|
63
|
-
describe('isPathSafe', () => {
|
|
64
|
-
const base = '/project/workspace';
|
|
65
|
-
|
|
66
|
-
test('returns true for child paths', () => {
|
|
67
|
-
expect(isPathSafe(base, 'src/index.ts')).toBe(true);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
test('returns true for the base directory', () => {
|
|
71
|
-
expect(isPathSafe(base, '.')).toBe(true);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
test('returns false for traversal', () => {
|
|
75
|
-
expect(isPathSafe(base, '../secret')).toBe(false);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
test('returns false for absolute paths outside base', () => {
|
|
79
|
-
expect(isPathSafe(base, '/tmp/evil')).toBe(false);
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
// ---------------------------------------------------------------------------
|
|
84
|
-
// resolveSafePath
|
|
85
|
-
// ---------------------------------------------------------------------------
|
|
86
|
-
|
|
87
|
-
describe('resolveSafePath', () => {
|
|
88
|
-
const base = '/project/workspace';
|
|
89
|
-
|
|
90
|
-
test('joins multiple segments safely', () => {
|
|
91
|
-
const result = resolveSafePath(base, 'src', 'lib', 'utils.ts');
|
|
92
|
-
expect(result.isOk()).toBe(true);
|
|
93
|
-
expect(result.unwrap()).toBe(join(resolve(base), 'src', 'lib', 'utils.ts'));
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
test('rejects when segments escape the base', () => {
|
|
97
|
-
const result = resolveSafePath(base, 'src', '../../../etc');
|
|
98
|
-
expect(result.isErr()).toBe(true);
|
|
99
|
-
const err = result as unknown as { error: PermissionError };
|
|
100
|
-
expect(err.error).toBeInstanceOf(PermissionError);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
test('handles single segment', () => {
|
|
104
|
-
const result = resolveSafePath(base, 'file.txt');
|
|
105
|
-
expect(result.isOk()).toBe(true);
|
|
106
|
-
expect(result.unwrap()).toBe(resolve(base, 'file.txt'));
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
test('handles empty segments', () => {
|
|
110
|
-
const result = resolveSafePath(base);
|
|
111
|
-
expect(result.isOk()).toBe(true);
|
|
112
|
-
expect(result.unwrap()).toBe(resolve(base));
|
|
113
|
-
});
|
|
114
|
-
});
|
|
@@ -1,273 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from 'bun:test';
|
|
2
|
-
|
|
3
|
-
import { z } from 'zod';
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
paginationFields,
|
|
7
|
-
paginatedOutput,
|
|
8
|
-
bulkOutput,
|
|
9
|
-
timestampFields,
|
|
10
|
-
dateRangeFields,
|
|
11
|
-
sortFields,
|
|
12
|
-
statusFields,
|
|
13
|
-
changeOutput,
|
|
14
|
-
progressFields,
|
|
15
|
-
} from '../patterns/index.js';
|
|
16
|
-
|
|
17
|
-
// ---------------------------------------------------------------------------
|
|
18
|
-
// paginationFields
|
|
19
|
-
// ---------------------------------------------------------------------------
|
|
20
|
-
|
|
21
|
-
describe('paginationFields', () => {
|
|
22
|
-
const schema = paginationFields();
|
|
23
|
-
|
|
24
|
-
test('applies defaults for empty input', () => {
|
|
25
|
-
const result = schema.parse({});
|
|
26
|
-
expect(result).toEqual({ limit: 20, offset: 0 });
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
test('accepts explicit values', () => {
|
|
30
|
-
const result = schema.parse({ cursor: 'abc', limit: 50, offset: 10 });
|
|
31
|
-
expect(result).toEqual({ cursor: 'abc', limit: 50, offset: 10 });
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
test('rejects non-number limit', () => {
|
|
35
|
-
expect(() => schema.parse({ limit: 'ten' })).toThrow();
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test('rejects non-string cursor', () => {
|
|
39
|
-
expect(() => schema.parse({ cursor: 123 })).toThrow();
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
// ---------------------------------------------------------------------------
|
|
44
|
-
// paginatedOutput
|
|
45
|
-
// ---------------------------------------------------------------------------
|
|
46
|
-
|
|
47
|
-
describe('paginatedOutput', () => {
|
|
48
|
-
const schema = paginatedOutput(z.string());
|
|
49
|
-
|
|
50
|
-
test('parses valid paginated data', () => {
|
|
51
|
-
const data = {
|
|
52
|
-
hasMore: true,
|
|
53
|
-
items: ['a', 'b'],
|
|
54
|
-
nextCursor: 'x',
|
|
55
|
-
total: 10,
|
|
56
|
-
};
|
|
57
|
-
expect(schema.parse(data)).toEqual(data);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
test('allows missing nextCursor', () => {
|
|
61
|
-
const data = { hasMore: false, items: [], total: 0 };
|
|
62
|
-
expect(schema.parse(data)).toEqual(data);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
test('rejects wrong item type', () => {
|
|
66
|
-
expect(() =>
|
|
67
|
-
schema.parse({ hasMore: false, items: [1], total: 1 })
|
|
68
|
-
).toThrow();
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
test('rejects missing total', () => {
|
|
72
|
-
expect(() => schema.parse({ hasMore: false, items: [] })).toThrow();
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
// ---------------------------------------------------------------------------
|
|
77
|
-
// bulkOutput
|
|
78
|
-
// ---------------------------------------------------------------------------
|
|
79
|
-
|
|
80
|
-
describe('bulkOutput', () => {
|
|
81
|
-
const schema = bulkOutput(z.object({ id: z.number() }));
|
|
82
|
-
|
|
83
|
-
test('parses valid bulk result', () => {
|
|
84
|
-
const data = {
|
|
85
|
-
failed: 0,
|
|
86
|
-
items: [{ id: 1 }, { id: 2 }],
|
|
87
|
-
succeeded: 2,
|
|
88
|
-
};
|
|
89
|
-
expect(schema.parse(data)).toEqual(data);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
test('parses bulk result with errors', () => {
|
|
93
|
-
const data = {
|
|
94
|
-
errors: [{ index: 1, message: 'duplicate' }],
|
|
95
|
-
failed: 1,
|
|
96
|
-
items: [{ id: 1 }],
|
|
97
|
-
succeeded: 1,
|
|
98
|
-
};
|
|
99
|
-
expect(schema.parse(data)).toEqual(data);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
test('rejects missing succeeded field', () => {
|
|
103
|
-
expect(() => schema.parse({ failed: 0, items: [] })).toThrow();
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
test('rejects invalid error shape', () => {
|
|
107
|
-
expect(() =>
|
|
108
|
-
schema.parse({
|
|
109
|
-
errors: [{ bad: true }],
|
|
110
|
-
failed: 0,
|
|
111
|
-
items: [],
|
|
112
|
-
succeeded: 0,
|
|
113
|
-
})
|
|
114
|
-
).toThrow();
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
// ---------------------------------------------------------------------------
|
|
119
|
-
// timestampFields
|
|
120
|
-
// ---------------------------------------------------------------------------
|
|
121
|
-
|
|
122
|
-
describe('timestampFields', () => {
|
|
123
|
-
const schema = timestampFields();
|
|
124
|
-
|
|
125
|
-
test('parses valid timestamps', () => {
|
|
126
|
-
const data = {
|
|
127
|
-
createdAt: '2024-01-01T00:00:00Z',
|
|
128
|
-
updatedAt: '2024-06-01T00:00:00Z',
|
|
129
|
-
};
|
|
130
|
-
expect(schema.parse(data)).toEqual(data);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
test('rejects missing createdAt', () => {
|
|
134
|
-
expect(() => schema.parse({ updatedAt: '2024-01-01' })).toThrow();
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
test('rejects non-string values', () => {
|
|
138
|
-
expect(() => schema.parse({ createdAt: 123, updatedAt: 456 })).toThrow();
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
// ---------------------------------------------------------------------------
|
|
143
|
-
// dateRangeFields
|
|
144
|
-
// ---------------------------------------------------------------------------
|
|
145
|
-
|
|
146
|
-
describe('dateRangeFields', () => {
|
|
147
|
-
const schema = dateRangeFields();
|
|
148
|
-
|
|
149
|
-
test('parses empty object (both optional)', () => {
|
|
150
|
-
expect(schema.parse({})).toEqual({});
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
test('parses with both fields', () => {
|
|
154
|
-
const data = { since: '2024-01-01', until: '2024-12-31' };
|
|
155
|
-
expect(schema.parse(data)).toEqual(data);
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
test('parses with only since', () => {
|
|
159
|
-
expect(schema.parse({ since: '2024-01-01' })).toEqual({
|
|
160
|
-
since: '2024-01-01',
|
|
161
|
-
});
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
test('rejects non-string values', () => {
|
|
165
|
-
expect(() => schema.parse({ since: 42 })).toThrow();
|
|
166
|
-
});
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
// ---------------------------------------------------------------------------
|
|
170
|
-
// sortFields
|
|
171
|
-
// ---------------------------------------------------------------------------
|
|
172
|
-
|
|
173
|
-
describe('sortFields', () => {
|
|
174
|
-
const schema = sortFields(['name', 'createdAt', 'updatedAt']);
|
|
175
|
-
|
|
176
|
-
test('applies default sortOrder', () => {
|
|
177
|
-
const result = schema.parse({});
|
|
178
|
-
expect(result).toEqual({ sortOrder: 'asc' });
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
test('accepts valid sortBy and sortOrder', () => {
|
|
182
|
-
const result = schema.parse({ sortBy: 'name', sortOrder: 'desc' });
|
|
183
|
-
expect(result).toEqual({ sortBy: 'name', sortOrder: 'desc' });
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
test('rejects sortBy not in allowed list', () => {
|
|
187
|
-
expect(() => schema.parse({ sortBy: 'email' })).toThrow();
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
test('rejects invalid sortOrder', () => {
|
|
191
|
-
expect(() => schema.parse({ sortOrder: 'up' })).toThrow();
|
|
192
|
-
});
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
// ---------------------------------------------------------------------------
|
|
196
|
-
// statusFields
|
|
197
|
-
// ---------------------------------------------------------------------------
|
|
198
|
-
|
|
199
|
-
describe('statusFields', () => {
|
|
200
|
-
const schema = statusFields();
|
|
201
|
-
|
|
202
|
-
test('parses each valid status', () => {
|
|
203
|
-
for (const s of [
|
|
204
|
-
'pending',
|
|
205
|
-
'running',
|
|
206
|
-
'completed',
|
|
207
|
-
'failed',
|
|
208
|
-
'cancelled',
|
|
209
|
-
] as const) {
|
|
210
|
-
expect(schema.parse({ status: s })).toEqual({ status: s });
|
|
211
|
-
}
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
test('rejects invalid status', () => {
|
|
215
|
-
expect(() => schema.parse({ status: 'unknown' })).toThrow();
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
test('rejects missing status', () => {
|
|
219
|
-
expect(() => schema.parse({})).toThrow();
|
|
220
|
-
});
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
// ---------------------------------------------------------------------------
|
|
224
|
-
// changeOutput
|
|
225
|
-
// ---------------------------------------------------------------------------
|
|
226
|
-
|
|
227
|
-
describe('changeOutput', () => {
|
|
228
|
-
const schema = changeOutput(z.object({ name: z.string() }));
|
|
229
|
-
|
|
230
|
-
test('parses with both before and after', () => {
|
|
231
|
-
const data = { after: { name: 'new' }, before: { name: 'old' } };
|
|
232
|
-
expect(schema.parse(data)).toEqual(data);
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
test('parses with only after (before is optional)', () => {
|
|
236
|
-
const data = { after: { name: 'created' } };
|
|
237
|
-
expect(schema.parse(data)).toEqual(data);
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
test('rejects missing after', () => {
|
|
241
|
-
expect(() => schema.parse({ before: { name: 'old' } })).toThrow();
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
test('rejects invalid schema shape', () => {
|
|
245
|
-
expect(() => schema.parse({ after: { name: 123 } })).toThrow();
|
|
246
|
-
});
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
// ---------------------------------------------------------------------------
|
|
250
|
-
// progressFields
|
|
251
|
-
// ---------------------------------------------------------------------------
|
|
252
|
-
|
|
253
|
-
describe('progressFields', () => {
|
|
254
|
-
const schema = progressFields();
|
|
255
|
-
|
|
256
|
-
test('parses valid progress', () => {
|
|
257
|
-
const data = { current: 5, percentage: 50, total: 10 };
|
|
258
|
-
expect(schema.parse(data)).toEqual(data);
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
test('allows missing percentage', () => {
|
|
262
|
-
const data = { current: 3, total: 10 };
|
|
263
|
-
expect(schema.parse(data)).toEqual(data);
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
test('rejects missing current', () => {
|
|
267
|
-
expect(() => schema.parse({ total: 10 })).toThrow();
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
test('rejects non-number values', () => {
|
|
271
|
-
expect(() => schema.parse({ current: 'five', total: 10 })).toThrow();
|
|
272
|
-
});
|
|
273
|
-
});
|
|
@@ -1,244 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from 'bun:test';
|
|
2
|
-
|
|
3
|
-
import { DEFAULT_SENSITIVE_KEYS, createRedactor } from '../redaction/index.js';
|
|
4
|
-
|
|
5
|
-
// ---------------------------------------------------------------------------
|
|
6
|
-
// DEFAULT_PATTERNS
|
|
7
|
-
// ---------------------------------------------------------------------------
|
|
8
|
-
|
|
9
|
-
describe('DEFAULT_PATTERNS', () => {
|
|
10
|
-
test('matches credit card numbers', () => {
|
|
11
|
-
const input = 'card: 4111-1111-1111-1111 end';
|
|
12
|
-
const redactor = createRedactor();
|
|
13
|
-
expect(redactor.redact(input)).toBe('card: [REDACTED] end');
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
test('matches credit card numbers with spaces', () => {
|
|
17
|
-
const input = 'card: 4111 1111 1111 1111 end';
|
|
18
|
-
const redactor = createRedactor();
|
|
19
|
-
expect(redactor.redact(input)).toBe('card: [REDACTED] end');
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
test('matches SSNs', () => {
|
|
23
|
-
const input = 'ssn is 123-45-6789';
|
|
24
|
-
const redactor = createRedactor();
|
|
25
|
-
expect(redactor.redact(input)).toBe('ssn is [REDACTED]');
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
test('matches bearer tokens', () => {
|
|
29
|
-
const input = 'Authorization: Bearer eyJhbGciOiJIUzI1NiJ9';
|
|
30
|
-
const redactor = createRedactor();
|
|
31
|
-
expect(redactor.redact(input)).toBe('Authorization: [REDACTED]');
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
test('matches basic auth', () => {
|
|
35
|
-
const input = 'Authorization: Basic dXNlcjpwYXNz';
|
|
36
|
-
const redactor = createRedactor();
|
|
37
|
-
expect(redactor.redact(input)).toBe('Authorization: [REDACTED]');
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
test('matches API keys with sk- prefix', () => {
|
|
41
|
-
const input = 'key: sk-abc123def456ghi';
|
|
42
|
-
const redactor = createRedactor();
|
|
43
|
-
expect(redactor.redact(input)).toBe('key: [REDACTED]');
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
test('matches API keys with pk_ prefix', () => {
|
|
47
|
-
const input = 'key: pk_live_abc123def456';
|
|
48
|
-
const redactor = createRedactor();
|
|
49
|
-
expect(redactor.redact(input)).toBe('key: [REDACTED]');
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test('matches API keys with sk_ prefix', () => {
|
|
53
|
-
const input = 'key: sk_test_abc123def456';
|
|
54
|
-
const redactor = createRedactor();
|
|
55
|
-
expect(redactor.redact(input)).toBe('key: [REDACTED]');
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test('matches JWT tokens', () => {
|
|
59
|
-
const input =
|
|
60
|
-
'jwt: eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.abc123def456';
|
|
61
|
-
const redactor = createRedactor();
|
|
62
|
-
expect(redactor.redact(input)).toBe('jwt: [REDACTED]');
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
// ---------------------------------------------------------------------------
|
|
67
|
-
// DEFAULT_SENSITIVE_KEYS
|
|
68
|
-
// ---------------------------------------------------------------------------
|
|
69
|
-
|
|
70
|
-
describe('DEFAULT_SENSITIVE_KEYS', () => {
|
|
71
|
-
test('includes expected keys', () => {
|
|
72
|
-
const expected = [
|
|
73
|
-
'password',
|
|
74
|
-
'secret',
|
|
75
|
-
'token',
|
|
76
|
-
'apiKey',
|
|
77
|
-
'api_key',
|
|
78
|
-
'authorization',
|
|
79
|
-
'cookie',
|
|
80
|
-
'ssn',
|
|
81
|
-
'creditCard',
|
|
82
|
-
'credit_card',
|
|
83
|
-
];
|
|
84
|
-
for (const key of expected) {
|
|
85
|
-
expect(DEFAULT_SENSITIVE_KEYS).toContain(key);
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
// ---------------------------------------------------------------------------
|
|
91
|
-
// createRedactor().redact()
|
|
92
|
-
// ---------------------------------------------------------------------------
|
|
93
|
-
|
|
94
|
-
describe('createRedactor().redact()', () => {
|
|
95
|
-
test('replaces all matching patterns in a string', () => {
|
|
96
|
-
const redactor = createRedactor();
|
|
97
|
-
const input = 'cc: 4111-1111-1111-1111 ssn: 123-45-6789';
|
|
98
|
-
const result = redactor.redact(input);
|
|
99
|
-
expect(result).toBe('cc: [REDACTED] ssn: [REDACTED]');
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
test('returns string unchanged when no patterns match', () => {
|
|
103
|
-
const redactor = createRedactor();
|
|
104
|
-
const input = 'just a normal string';
|
|
105
|
-
expect(redactor.redact(input)).toBe('just a normal string');
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
test('works correctly when called multiple times (regex lastIndex reset)', () => {
|
|
109
|
-
const redactor = createRedactor();
|
|
110
|
-
const input = 'key: sk-abc123def456ghi';
|
|
111
|
-
expect(redactor.redact(input)).toBe('key: [REDACTED]');
|
|
112
|
-
expect(redactor.redact(input)).toBe('key: [REDACTED]');
|
|
113
|
-
});
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
// ---------------------------------------------------------------------------
|
|
117
|
-
// createRedactor().redactObject()
|
|
118
|
-
// ---------------------------------------------------------------------------
|
|
119
|
-
|
|
120
|
-
describe('createRedactor().redactObject()', () => {
|
|
121
|
-
test('redacts values for sensitive keys', () => {
|
|
122
|
-
const redactor = createRedactor();
|
|
123
|
-
const obj = { password: 's3cret', username: 'alice' };
|
|
124
|
-
const result = redactor.redactObject(obj);
|
|
125
|
-
expect(result.password).toBe('[REDACTED]');
|
|
126
|
-
expect(result.username).toBe('alice');
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
test('matches sensitive keys case-insensitively', () => {
|
|
130
|
-
const redactor = createRedactor();
|
|
131
|
-
const obj = { Authorization: 'Bearer xyz', PASSWORD: 's3cret' };
|
|
132
|
-
const result = redactor.redactObject(obj);
|
|
133
|
-
expect(result.PASSWORD).toBe('[REDACTED]');
|
|
134
|
-
expect(result.Authorization).toBe('[REDACTED]');
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
test('applies pattern matching to non-sensitive string values', () => {
|
|
138
|
-
const redactor = createRedactor();
|
|
139
|
-
const obj = { message: 'card 4111-1111-1111-1111 found' };
|
|
140
|
-
const result = redactor.redactObject(obj);
|
|
141
|
-
expect(result.message).toBe('card [REDACTED] found');
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
test('does not modify the original object', () => {
|
|
145
|
-
const redactor = createRedactor();
|
|
146
|
-
const obj = { password: 's3cret' };
|
|
147
|
-
redactor.redactObject(obj);
|
|
148
|
-
expect(obj.password).toBe('s3cret');
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
test('preserves non-string values', () => {
|
|
152
|
-
const redactor = createRedactor();
|
|
153
|
-
const obj = { count: 42, enabled: true, tags: null as unknown };
|
|
154
|
-
const result = redactor.redactObject(obj);
|
|
155
|
-
expect(result.count).toBe(42);
|
|
156
|
-
expect(result.enabled).toBe(true);
|
|
157
|
-
expect(result.tags).toBeNull();
|
|
158
|
-
});
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
// ---------------------------------------------------------------------------
|
|
162
|
-
// Nested objects
|
|
163
|
-
// ---------------------------------------------------------------------------
|
|
164
|
-
|
|
165
|
-
describe('nested objects', () => {
|
|
166
|
-
test('redacts deeply nested sensitive keys', () => {
|
|
167
|
-
const redactor = createRedactor();
|
|
168
|
-
const obj = {
|
|
169
|
-
user: {
|
|
170
|
-
credentials: {
|
|
171
|
-
password: 's3cret',
|
|
172
|
-
token: 'tok_abc123',
|
|
173
|
-
},
|
|
174
|
-
name: 'alice',
|
|
175
|
-
},
|
|
176
|
-
};
|
|
177
|
-
const result = redactor.redactObject(obj);
|
|
178
|
-
expect(result.user.name).toBe('alice');
|
|
179
|
-
expect(result.user.credentials.password).toBe('[REDACTED]');
|
|
180
|
-
expect(result.user.credentials.token).toBe('[REDACTED]');
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
test('redacts values inside arrays', () => {
|
|
184
|
-
const redactor = createRedactor();
|
|
185
|
-
const obj = {
|
|
186
|
-
headers: [
|
|
187
|
-
{ key: 'Authorization', value: 'Bearer abc123' },
|
|
188
|
-
{ key: 'Content-Type', value: 'application/json' },
|
|
189
|
-
],
|
|
190
|
-
};
|
|
191
|
-
const result = redactor.redactObject(obj);
|
|
192
|
-
expect(result.headers).toHaveLength(2);
|
|
193
|
-
expect(result.headers[0]?.value).toBe('[REDACTED]');
|
|
194
|
-
expect(result.headers[1]?.value).toBe('application/json');
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
test('handles deeply nested pattern matches', () => {
|
|
198
|
-
const redactor = createRedactor();
|
|
199
|
-
const obj = {
|
|
200
|
-
data: {
|
|
201
|
-
nested: {
|
|
202
|
-
info: 'ssn: 123-45-6789',
|
|
203
|
-
},
|
|
204
|
-
},
|
|
205
|
-
};
|
|
206
|
-
const result = redactor.redactObject(obj);
|
|
207
|
-
expect(result.data.nested.info).toBe('ssn: [REDACTED]');
|
|
208
|
-
});
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
// ---------------------------------------------------------------------------
|
|
212
|
-
// Custom config
|
|
213
|
-
// ---------------------------------------------------------------------------
|
|
214
|
-
|
|
215
|
-
describe('custom config', () => {
|
|
216
|
-
test('custom patterns work', () => {
|
|
217
|
-
const redactor = createRedactor({
|
|
218
|
-
patterns: [/secret-\w+/g],
|
|
219
|
-
});
|
|
220
|
-
expect(redactor.redact('found secret-banana here')).toBe(
|
|
221
|
-
'found [REDACTED] here'
|
|
222
|
-
);
|
|
223
|
-
// Default patterns should not apply
|
|
224
|
-
expect(redactor.redact('4111-1111-1111-1111')).toBe('4111-1111-1111-1111');
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
test('custom replacement string works', () => {
|
|
228
|
-
const redactor = createRedactor({ replacement: '***' });
|
|
229
|
-
const obj = { password: 's3cret' };
|
|
230
|
-
const result = redactor.redactObject(obj);
|
|
231
|
-
expect(result.password).toBe('***');
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
test('custom sensitive keys work', () => {
|
|
235
|
-
const redactor = createRedactor({
|
|
236
|
-
sensitiveKeys: ['myCustomKey'],
|
|
237
|
-
});
|
|
238
|
-
const obj = { myCustomKey: 'hidden', password: 'visible' };
|
|
239
|
-
const result = redactor.redactObject(obj);
|
|
240
|
-
expect(result.myCustomKey).toBe('[REDACTED]');
|
|
241
|
-
// "password" is not in custom keys, so not redacted by key match
|
|
242
|
-
expect(result.password).toBe('visible');
|
|
243
|
-
});
|
|
244
|
-
});
|