@node-llm/testing 0.2.2 → 0.3.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/Scrubber.d.ts.map +1 -1
- package/dist/Scrubber.js +21 -3
- package/dist/Time.d.ts +33 -0
- package/dist/Time.d.ts.map +1 -0
- package/dist/Time.js +49 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/package.json +2 -2
- package/src/Scrubber.ts +22 -3
- package/src/Time.ts +52 -0
- package/src/index.ts +1 -0
- package/test/unit/Time.test.ts +50 -0
- package/test/unit/auto_scrubbing_enhanced.test.ts +69 -0
package/dist/Scrubber.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Scrubber.d.ts","sourceRoot":"","sources":["../src/Scrubber.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,uBAAuB,
|
|
1
|
+
{"version":3,"file":"Scrubber.d.ts","sourceRoot":"","sources":["../src/Scrubber.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,uBAAuB,UAYnC,CAAC;AAEF,MAAM,WAAW,eAAe;IAC9B,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC;IAC5C,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,qBAAa,QAAQ;IACnB,OAAO,CAAC,cAAc,CAAC,CAA6B;IACpD,OAAO,CAAC,iBAAiB,CAAW;IACpC,OAAO,CAAC,aAAa,CAAc;gBAEvB,OAAO,GAAE,eAAoB;IAwBzC;;OAEG;IACI,KAAK,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO;IAYpC,OAAO,CAAC,SAAS;CAqElB"}
|
package/dist/Scrubber.js
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
export const DEFAULT_SECRET_PATTERNS = [
|
|
2
|
-
|
|
3
|
-
/
|
|
4
|
-
/[a-zA-Z0-9]{
|
|
2
|
+
// API Keys & Auth Tokens
|
|
3
|
+
/sk-[a-zA-Z0-9]{20,}/g, // OpenAI/Anthropic keys
|
|
4
|
+
/x-[a-zA-Z0-9]{20,}/g, // Generic key patterns
|
|
5
|
+
/ey[a-zA-Z0-9-_=]+\.ey[a-zA-Z0-9-_=]+\.?[a-zA-Z0-9-_.+/=]*/g, // JWT tokens
|
|
6
|
+
/\b(AIza[0-9A-Za-z-_]{35})\b/g, // Google API keys
|
|
7
|
+
/\b[0-9a-f]{32,}\b/gi, // Long hex hashes/IDs (often keys)
|
|
8
|
+
// PII (Personal Identifiable Information)
|
|
9
|
+
/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, // Emails
|
|
10
|
+
/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g, // IPv4 Addresses
|
|
11
|
+
/\b(access_key|secret_key|aws_access_key_id|aws_secret_access_key):\s*[^\s,{}"]+/gi // AWS-like pairs
|
|
5
12
|
];
|
|
6
13
|
export class Scrubber {
|
|
7
14
|
customScrubber;
|
|
@@ -13,9 +20,20 @@ export class Scrubber {
|
|
|
13
20
|
this.sensitiveKeys = new Set([
|
|
14
21
|
"key",
|
|
15
22
|
"api_key",
|
|
23
|
+
"apikey",
|
|
16
24
|
"token",
|
|
17
25
|
"auth",
|
|
18
26
|
"authorization",
|
|
27
|
+
"password",
|
|
28
|
+
"secret",
|
|
29
|
+
"cookie",
|
|
30
|
+
"set-cookie",
|
|
31
|
+
"x-api-key",
|
|
32
|
+
"x-auth-token",
|
|
33
|
+
"access_key",
|
|
34
|
+
"secret_key",
|
|
35
|
+
"bearer",
|
|
36
|
+
"credential",
|
|
19
37
|
...(options.sensitiveKeys || []).map((k) => k.toLowerCase())
|
|
20
38
|
]);
|
|
21
39
|
}
|
package/dist/Time.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Time utility for deterministic testing.
|
|
3
|
+
* Provides a clean API for freezing, advancing, and restoring system time.
|
|
4
|
+
* Built on top of Vitest's timer system.
|
|
5
|
+
*/
|
|
6
|
+
export declare class Time {
|
|
7
|
+
/**
|
|
8
|
+
* Freezes time at a specific date for the duration of the provided function.
|
|
9
|
+
* Automatically restores real time even if the function throws an error.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* await Time.frozen('2024-05-20', async () => {
|
|
13
|
+
* const now = new Date();
|
|
14
|
+
* expect(now.toISOString()).toContain('2024-05-20');
|
|
15
|
+
* });
|
|
16
|
+
*/
|
|
17
|
+
static frozen<T>(date: string | Date | number, fn: () => Promise<T> | T): Promise<T>;
|
|
18
|
+
/**
|
|
19
|
+
* Freezes system time.
|
|
20
|
+
* Use this in beforeEach or for sequential time manipulation.
|
|
21
|
+
* Remember to call Time.restore() in afterEach!
|
|
22
|
+
*/
|
|
23
|
+
static freeze(date: string | Date | number): void;
|
|
24
|
+
/**
|
|
25
|
+
* Advances the frozen time by a specific amount of milliseconds.
|
|
26
|
+
*/
|
|
27
|
+
static advance(ms: number): void;
|
|
28
|
+
/**
|
|
29
|
+
* Restores system time to reality.
|
|
30
|
+
*/
|
|
31
|
+
static restore(): void;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=Time.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Time.d.ts","sourceRoot":"","sources":["../src/Time.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,qBAAa,IAAI;IACf;;;;;;;;;OASG;WACU,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAS1F;;;;OAIG;IACH,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI;IAMjD;;OAEG;IACH,MAAM,CAAC,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAIhC;;OAEG;IACH,MAAM,CAAC,OAAO,IAAI,IAAI;CAGvB"}
|
package/dist/Time.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { vi } from "vitest";
|
|
2
|
+
/**
|
|
3
|
+
* Time utility for deterministic testing.
|
|
4
|
+
* Provides a clean API for freezing, advancing, and restoring system time.
|
|
5
|
+
* Built on top of Vitest's timer system.
|
|
6
|
+
*/
|
|
7
|
+
export class Time {
|
|
8
|
+
/**
|
|
9
|
+
* Freezes time at a specific date for the duration of the provided function.
|
|
10
|
+
* Automatically restores real time even if the function throws an error.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* await Time.frozen('2024-05-20', async () => {
|
|
14
|
+
* const now = new Date();
|
|
15
|
+
* expect(now.toISOString()).toContain('2024-05-20');
|
|
16
|
+
* });
|
|
17
|
+
*/
|
|
18
|
+
static async frozen(date, fn) {
|
|
19
|
+
this.freeze(date);
|
|
20
|
+
try {
|
|
21
|
+
return await fn();
|
|
22
|
+
}
|
|
23
|
+
finally {
|
|
24
|
+
this.restore();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Freezes system time.
|
|
29
|
+
* Use this in beforeEach or for sequential time manipulation.
|
|
30
|
+
* Remember to call Time.restore() in afterEach!
|
|
31
|
+
*/
|
|
32
|
+
static freeze(date) {
|
|
33
|
+
const targetDate = new Date(date);
|
|
34
|
+
vi.useFakeTimers();
|
|
35
|
+
vi.setSystemTime(targetDate);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Advances the frozen time by a specific amount of milliseconds.
|
|
39
|
+
*/
|
|
40
|
+
static advance(ms) {
|
|
41
|
+
vi.advanceTimersByTime(ms);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Restores system time to reality.
|
|
45
|
+
*/
|
|
46
|
+
static restore() {
|
|
47
|
+
vi.useRealTimers();
|
|
48
|
+
}
|
|
49
|
+
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC"}
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@node-llm/testing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Deterministic testing for NodeLLM powered AI systems",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"devDependencies": {
|
|
9
9
|
"vitest": "^1.0.0",
|
|
10
10
|
"typescript": "^5.0.0",
|
|
11
|
-
"@node-llm/core": "1.
|
|
11
|
+
"@node-llm/core": "1.9.0"
|
|
12
12
|
},
|
|
13
13
|
"scripts": {
|
|
14
14
|
"build": "tsc",
|
package/src/Scrubber.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
export const DEFAULT_SECRET_PATTERNS = [
|
|
2
|
-
|
|
3
|
-
/
|
|
4
|
-
/[a-zA-Z0-9]{
|
|
2
|
+
// API Keys & Auth Tokens
|
|
3
|
+
/sk-[a-zA-Z0-9]{20,}/g, // OpenAI/Anthropic keys
|
|
4
|
+
/x-[a-zA-Z0-9]{20,}/g, // Generic key patterns
|
|
5
|
+
/ey[a-zA-Z0-9-_=]+\.ey[a-zA-Z0-9-_=]+\.?[a-zA-Z0-9-_.+/=]*/g, // JWT tokens
|
|
6
|
+
/\b(AIza[0-9A-Za-z-_]{35})\b/g, // Google API keys
|
|
7
|
+
/\b[0-9a-f]{32,}\b/gi, // Long hex hashes/IDs (often keys)
|
|
8
|
+
|
|
9
|
+
// PII (Personal Identifiable Information)
|
|
10
|
+
/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, // Emails
|
|
11
|
+
/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g, // IPv4 Addresses
|
|
12
|
+
/\b(access_key|secret_key|aws_access_key_id|aws_secret_access_key):\s*[^\s,{}"]+/gi // AWS-like pairs
|
|
5
13
|
];
|
|
6
14
|
|
|
7
15
|
export interface ScrubberOptions {
|
|
@@ -21,9 +29,20 @@ export class Scrubber {
|
|
|
21
29
|
this.sensitiveKeys = new Set([
|
|
22
30
|
"key",
|
|
23
31
|
"api_key",
|
|
32
|
+
"apikey",
|
|
24
33
|
"token",
|
|
25
34
|
"auth",
|
|
26
35
|
"authorization",
|
|
36
|
+
"password",
|
|
37
|
+
"secret",
|
|
38
|
+
"cookie",
|
|
39
|
+
"set-cookie",
|
|
40
|
+
"x-api-key",
|
|
41
|
+
"x-auth-token",
|
|
42
|
+
"access_key",
|
|
43
|
+
"secret_key",
|
|
44
|
+
"bearer",
|
|
45
|
+
"credential",
|
|
27
46
|
...(options.sensitiveKeys || []).map((k) => k.toLowerCase())
|
|
28
47
|
]);
|
|
29
48
|
}
|
package/src/Time.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Time utility for deterministic testing.
|
|
5
|
+
* Provides a clean API for freezing, advancing, and restoring system time.
|
|
6
|
+
* Built on top of Vitest's timer system.
|
|
7
|
+
*/
|
|
8
|
+
export class Time {
|
|
9
|
+
/**
|
|
10
|
+
* Freezes time at a specific date for the duration of the provided function.
|
|
11
|
+
* Automatically restores real time even if the function throws an error.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* await Time.frozen('2024-05-20', async () => {
|
|
15
|
+
* const now = new Date();
|
|
16
|
+
* expect(now.toISOString()).toContain('2024-05-20');
|
|
17
|
+
* });
|
|
18
|
+
*/
|
|
19
|
+
static async frozen<T>(date: string | Date | number, fn: () => Promise<T> | T): Promise<T> {
|
|
20
|
+
this.freeze(date);
|
|
21
|
+
try {
|
|
22
|
+
return await fn();
|
|
23
|
+
} finally {
|
|
24
|
+
this.restore();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Freezes system time.
|
|
30
|
+
* Use this in beforeEach or for sequential time manipulation.
|
|
31
|
+
* Remember to call Time.restore() in afterEach!
|
|
32
|
+
*/
|
|
33
|
+
static freeze(date: string | Date | number): void {
|
|
34
|
+
const targetDate = new Date(date);
|
|
35
|
+
vi.useFakeTimers();
|
|
36
|
+
vi.setSystemTime(targetDate);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Advances the frozen time by a specific amount of milliseconds.
|
|
41
|
+
*/
|
|
42
|
+
static advance(ms: number): void {
|
|
43
|
+
vi.advanceTimersByTime(ms);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Restores system time to reality.
|
|
48
|
+
*/
|
|
49
|
+
static restore(): void {
|
|
50
|
+
vi.useRealTimers();
|
|
51
|
+
}
|
|
52
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { Time } from "../../src/Time.js";
|
|
3
|
+
|
|
4
|
+
describe("Testing Utility: Time", () => {
|
|
5
|
+
describe("Time.frozen", () => {
|
|
6
|
+
it("freezes time and restores it automatically", async () => {
|
|
7
|
+
const realStart = new Date().getTime();
|
|
8
|
+
const target = "2024-05-20T10:00:00.000Z";
|
|
9
|
+
|
|
10
|
+
await Time.frozen(target, async () => {
|
|
11
|
+
expect(new Date().toISOString()).toBe(target);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
// After restoration, time should represent reality again
|
|
15
|
+
const realEnd = new Date().getTime();
|
|
16
|
+
expect(realEnd).toBeGreaterThanOrEqual(realStart);
|
|
17
|
+
expect(new Date().toISOString()).not.toBe(target);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("restores time even on error", async () => {
|
|
21
|
+
const target = "2024-01-01T00:00:00.000Z";
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
await Time.frozen(target, () => {
|
|
25
|
+
throw new Error("BOOM");
|
|
26
|
+
});
|
|
27
|
+
} catch {
|
|
28
|
+
// Ignored
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
expect(new Date().toISOString()).not.toBe(target);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe("Stateful API (freeze / advance / restore)", () => {
|
|
36
|
+
it("allows manual manipulation of time flow", () => {
|
|
37
|
+
const start = "2024-12-31T23:59:59.000Z";
|
|
38
|
+
Time.freeze(start);
|
|
39
|
+
|
|
40
|
+
expect(new Date().toISOString()).toBe(start);
|
|
41
|
+
|
|
42
|
+
// Advance by 2 seconds
|
|
43
|
+
Time.advance(2000);
|
|
44
|
+
expect(new Date().toISOString()).toBe("2025-01-01T00:00:01.000Z");
|
|
45
|
+
|
|
46
|
+
Time.restore();
|
|
47
|
+
expect(new Date().toISOString()).not.toBe("2025-01-01T00:00:01.000Z");
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { Scrubber } from "../../src/Scrubber.js";
|
|
3
|
+
|
|
4
|
+
describe("Enhanced Auto-Scrubbing", () => {
|
|
5
|
+
const scrubber = new Scrubber();
|
|
6
|
+
|
|
7
|
+
it("should scrub email addresses from strings", () => {
|
|
8
|
+
const input = "Contact me at user@example.com or support@company.co.uk";
|
|
9
|
+
const result = scrubber.scrub(input);
|
|
10
|
+
expect(result).toBe("Contact me at [REDACTED] or [REDACTED]");
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("should scrub JWT tokens", () => {
|
|
14
|
+
const jwt =
|
|
15
|
+
"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
|
|
16
|
+
const result = scrubber.scrub(jwt);
|
|
17
|
+
expect(result).toBe("Bearer [REDACTED]");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("should scrub IPv4 addresses", () => {
|
|
21
|
+
const input = "Server IP is 192.168.1.1 and gateway is 10.0.0.1";
|
|
22
|
+
const result = scrubber.scrub(input);
|
|
23
|
+
expect(result).toBe("Server IP is [REDACTED] and gateway is [REDACTED]");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("should scrub Google API keys", () => {
|
|
27
|
+
const input = "key=AIzaSyA1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6Q";
|
|
28
|
+
const result = scrubber.scrub(input);
|
|
29
|
+
expect(result).toBe("key=[REDACTED]");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("should scrub sensitive keys in nested objects", () => {
|
|
33
|
+
const input = {
|
|
34
|
+
user: {
|
|
35
|
+
email: "test@example.com",
|
|
36
|
+
password: "secretpassword123",
|
|
37
|
+
settings: {
|
|
38
|
+
apiKey: "sk-1234567890abcdef1234567890"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
status: "ok"
|
|
42
|
+
};
|
|
43
|
+
const result = scrubber.scrub(input) as any;
|
|
44
|
+
|
|
45
|
+
expect(result.user.email).toBe("[REDACTED]");
|
|
46
|
+
expect(result.user.password).toBe("[REDACTED]");
|
|
47
|
+
expect(result.user.settings.apiKey).toBe("[REDACTED]");
|
|
48
|
+
expect(result.status).toBe("ok");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("should scrub custom sensitive keys like 'cookie'", () => {
|
|
52
|
+
const input = {
|
|
53
|
+
headers: {
|
|
54
|
+
Cookie: "sessionid=12345; user=abc",
|
|
55
|
+
"Content-Type": "application/json"
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
const result = scrubber.scrub(input) as any;
|
|
59
|
+
expect(result.headers.Cookie).toBe("[REDACTED]");
|
|
60
|
+
expect(result.headers["Content-Type"]).toBe("application/json");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("should scrub AWS-like credential pairs in strings", () => {
|
|
64
|
+
const input =
|
|
65
|
+
"Using access_key: AKIA1234567890EXAMPLE and secret_key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY";
|
|
66
|
+
const result = scrubber.scrub(input);
|
|
67
|
+
expect(result).toBe("Using [REDACTED] and [REDACTED]");
|
|
68
|
+
});
|
|
69
|
+
});
|