@lssm/lib.testing 0.0.0-canary-20251217063201 → 0.0.0-canary-20251217072406

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.
@@ -1,13 +1,27 @@
1
- import{serialize as e}from"../generator/assertion-builder.js";function t(t){let n=t.cases.map(n=>{let r=e(n.input),i=e(n.metadata??{}),a=`const result = await ${t.runnerFunction}(input${n.id}, metadata${n.id});
2
- expect(result).toEqual(${e(n.expectedOutput??null)});`,o=`await expect(${t.runnerFunction}(input${n.id}, metadata${n.id})).rejects.toMatchObject(${e(n.expectedError??{message:`expected failure`})});`;return`
3
- test('${n.name}', async () => {
4
- const input${n.id} = ${r};
5
- const metadata${n.id} = ${i};
6
- ${n.success?a:o}
7
- });`}).join(`
8
- `);return`
9
- import { ${t.runnerFunction} } from '${t.runnerImport}';
1
+ import { serialize } from "../generator/assertion-builder.js";
10
2
 
11
- describe('${t.suiteName}', () => {${n}
3
+ //#region src/adapters/jest-adapter.ts
4
+ function generateJestSuite(options) {
5
+ const caseBlocks = options.cases.map((testCase) => {
6
+ const inputConst = serialize(testCase.input);
7
+ const metadataConst = serialize(testCase.metadata ?? {});
8
+ const successBlock = `const result = await ${options.runnerFunction}(input${testCase.id}, metadata${testCase.id});
9
+ expect(result).toEqual(${serialize(testCase.expectedOutput ?? null)});`;
10
+ const failureBlock = `await expect(${options.runnerFunction}(input${testCase.id}, metadata${testCase.id})).rejects.toMatchObject(${serialize(testCase.expectedError ?? { message: "expected failure" })});`;
11
+ return `
12
+ test('${testCase.name}', async () => {
13
+ const input${testCase.id} = ${inputConst};
14
+ const metadata${testCase.id} = ${metadataConst};
15
+ ${testCase.success ? successBlock : failureBlock}
16
+ });`;
17
+ }).join("\n");
18
+ return `
19
+ import { ${options.runnerFunction} } from '${options.runnerImport}';
20
+
21
+ describe('${options.suiteName}', () => {${caseBlocks}
12
22
  });
13
- `.trim()}export{t as generateJestSuite};
23
+ `.trim();
24
+ }
25
+
26
+ //#endregion
27
+ export { generateJestSuite };
@@ -1,14 +1,26 @@
1
- import{serialize as e}from"../generator/assertion-builder.js";function t(t){let n=t.cases.map(n=>{let r=e(n.input),i=e(n.metadata??{}),a=n.success?[`const result = await ${t.runnerFunction}(input${n.id}, metadata${n.id});`,`expect(result).toEqual(${e(n.expectedOutput??null)});`]:[`await expect(${t.runnerFunction}(input${n.id}, metadata${n.id})).rejects.toMatchObject(${e(n.expectedError??{message:`expected failure`})});`];return`
2
- it('${n.name}', async () => {
3
- const input${n.id} = ${r};
4
- const metadata${n.id} = ${i};
5
- ${a.join(`
6
- `)}
7
- });`}).join(`
8
- `);return`
1
+ import { serialize } from "../generator/assertion-builder.js";
2
+
3
+ //#region src/adapters/vitest-adapter.ts
4
+ function generateVitestSuite(options) {
5
+ const caseBlocks = options.cases.map((testCase) => {
6
+ const inputConst = serialize(testCase.input);
7
+ const metadataConst = serialize(testCase.metadata ?? {});
8
+ const assertions = testCase.success ? [`const result = await ${options.runnerFunction}(input${testCase.id}, metadata${testCase.id});`, `expect(result).toEqual(${serialize(testCase.expectedOutput ?? null)});`] : [`await expect(${options.runnerFunction}(input${testCase.id}, metadata${testCase.id})).rejects.toMatchObject(${serialize(testCase.expectedError ?? { message: "expected failure" })});`];
9
+ return `
10
+ it('${testCase.name}', async () => {
11
+ const input${testCase.id} = ${inputConst};
12
+ const metadata${testCase.id} = ${metadataConst};
13
+ ${assertions.join("\n ")}
14
+ });`;
15
+ }).join("\n");
16
+ return `
9
17
  import { describe, it, expect } from 'bun:test';
10
- import { ${t.runnerFunction} } from '${t.runnerImport}';
18
+ import { ${options.runnerFunction} } from '${options.runnerImport}';
11
19
 
12
- describe('${t.suiteName}', () => {${n}
20
+ describe('${options.suiteName}', () => {${caseBlocks}
13
21
  });
14
- `.trim()}export{t as generateVitestSuite};
22
+ `.trim();
23
+ }
24
+
25
+ //#endregion
26
+ export { generateVitestSuite };
@@ -1,2 +1,15 @@
1
- function e(e,n){return e.success?[`const result = await ${n.runnerCall};`,`expect(result).toEqual(${t(e.expectedOutput??null)});`].join(`
2
- `):`await expect(${n.runnerCall}).rejects.toMatchObject(${t(e.expectedError??{message:`expected failure`})});`}function t(e){return JSON.stringify(e,(e,t)=>t instanceof Date?t.toISOString():t===void 0?null:t,2)}export{e as buildAssertions,t as serialize};
1
+ //#region src/generator/assertion-builder.ts
2
+ function buildAssertions(testCase, ctx) {
3
+ if (testCase.success) return [`const result = await ${ctx.runnerCall};`, `expect(result).toEqual(${serialize(testCase.expectedOutput ?? null)});`].join("\n ");
4
+ return `await expect(${ctx.runnerCall}).rejects.toMatchObject(${serialize(testCase.expectedError ?? { message: "expected failure" })});`;
5
+ }
6
+ function serialize(value) {
7
+ return JSON.stringify(value, (_key, val) => {
8
+ if (val instanceof Date) return val.toISOString();
9
+ if (typeof val === "undefined") return null;
10
+ return val;
11
+ }, 2);
12
+ }
13
+
14
+ //#endregion
15
+ export { buildAssertions, serialize };
@@ -1 +1,86 @@
1
- import{generateVitestSuite as e}from"../adapters/vitest-adapter.js";import{generateJestSuite as t}from"../adapters/jest-adapter.js";import{randomUUID as n}from"node:crypto";import{performance as r}from"node:perf_hooks";var i=class{constructor(e=e=>({tenantId:e.tenantId,userId:e.userId,channel:e.channel})){this.serializeMetadata=e}createCases(e){return e.map((e,t)=>({id:e.id??n(),name:e.success?`case-${t+1}-success`:`case-${t+1}-failure`,input:e.input,expectedOutput:e.output,expectedError:e.error,success:e.success,metadata:this.serializeMetadata?.(e)}))}generate(n,r){let i=this.createCases(n);return r.framework===`jest`?t({suiteName:r.suiteName,cases:i,runnerImport:r.runnerImport,runnerFunction:r.runnerFunction}):e({suiteName:r.suiteName,cases:i,runnerImport:r.runnerImport,runnerFunction:r.runnerFunction})}};async function a(e,t){let n=[];for(let i of e){let e=r.now();try{let a=await t(i.input,i.metadata);if(!i.success){n.push({caseId:i.id,passed:!1,durationMs:r.now()-e,error:Error(`Expected failure but runner resolved`)});continue}let o=JSON.stringify(a)===JSON.stringify(i.expectedOutput??null);n.push({caseId:i.id,passed:o,durationMs:r.now()-e,error:o?void 0:{expected:i.expectedOutput,received:a}})}catch(t){let a=r.now()-e;i.success?n.push({caseId:i.id,passed:!1,durationMs:a,error:t}):n.push({caseId:i.id,passed:!0,durationMs:a})}}return n}export{i as GoldenTestGenerator,a as runGoldenTests};
1
+ import { generateVitestSuite } from "../adapters/vitest-adapter.js";
2
+ import { generateJestSuite } from "../adapters/jest-adapter.js";
3
+ import { randomUUID } from "node:crypto";
4
+ import { performance } from "node:perf_hooks";
5
+
6
+ //#region src/generator/golden-test-generator.ts
7
+ var GoldenTestGenerator = class {
8
+ constructor(serializeMetadata = (snapshot) => ({
9
+ tenantId: snapshot.tenantId,
10
+ userId: snapshot.userId,
11
+ channel: snapshot.channel
12
+ })) {
13
+ this.serializeMetadata = serializeMetadata;
14
+ }
15
+ createCases(snapshots) {
16
+ return snapshots.map((snapshot, index) => ({
17
+ id: snapshot.id ?? randomUUID(),
18
+ name: snapshot.success ? `case-${index + 1}-success` : `case-${index + 1}-failure`,
19
+ input: snapshot.input,
20
+ expectedOutput: snapshot.output,
21
+ expectedError: snapshot.error,
22
+ success: snapshot.success,
23
+ metadata: this.serializeMetadata?.(snapshot)
24
+ }));
25
+ }
26
+ generate(snapshots, options) {
27
+ const cases = this.createCases(snapshots);
28
+ if (options.framework === "jest") return generateJestSuite({
29
+ suiteName: options.suiteName,
30
+ cases,
31
+ runnerImport: options.runnerImport,
32
+ runnerFunction: options.runnerFunction
33
+ });
34
+ return generateVitestSuite({
35
+ suiteName: options.suiteName,
36
+ cases,
37
+ runnerImport: options.runnerImport,
38
+ runnerFunction: options.runnerFunction
39
+ });
40
+ }
41
+ };
42
+ async function runGoldenTests(cases, runner) {
43
+ const results = [];
44
+ for (const testCase of cases) {
45
+ const startedAt = performance.now();
46
+ try {
47
+ const output = await runner(testCase.input, testCase.metadata);
48
+ if (!testCase.success) {
49
+ results.push({
50
+ caseId: testCase.id,
51
+ passed: false,
52
+ durationMs: performance.now() - startedAt,
53
+ error: /* @__PURE__ */ new Error("Expected failure but runner resolved")
54
+ });
55
+ continue;
56
+ }
57
+ const matches = JSON.stringify(output) === JSON.stringify(testCase.expectedOutput ?? null);
58
+ results.push({
59
+ caseId: testCase.id,
60
+ passed: matches,
61
+ durationMs: performance.now() - startedAt,
62
+ error: matches ? void 0 : {
63
+ expected: testCase.expectedOutput,
64
+ received: output
65
+ }
66
+ });
67
+ } catch (error) {
68
+ const durationMs = performance.now() - startedAt;
69
+ if (!testCase.success) results.push({
70
+ caseId: testCase.id,
71
+ passed: true,
72
+ durationMs
73
+ });
74
+ else results.push({
75
+ caseId: testCase.id,
76
+ passed: false,
77
+ durationMs,
78
+ error
79
+ });
80
+ }
81
+ }
82
+ return results;
83
+ }
84
+
85
+ //#endregion
86
+ export { GoldenTestGenerator, runGoldenTests };
package/dist/index.js CHANGED
@@ -1 +1,7 @@
1
- import{InMemoryTrafficStore as e,TrafficRecorder as t}from"./recorder/traffic-recorder.js";import{buildAssertions as n,serialize as r}from"./generator/assertion-builder.js";import{generateVitestSuite as i}from"./adapters/vitest-adapter.js";import{generateJestSuite as a}from"./adapters/jest-adapter.js";import{GoldenTestGenerator as o,runGoldenTests as s}from"./generator/golden-test-generator.js";export{o as GoldenTestGenerator,e as InMemoryTrafficStore,t as TrafficRecorder,n as buildAssertions,a as generateJestSuite,i as generateVitestSuite,s as runGoldenTests,r as serialize};
1
+ import { InMemoryTrafficStore, TrafficRecorder } from "./recorder/traffic-recorder.js";
2
+ import { buildAssertions, serialize } from "./generator/assertion-builder.js";
3
+ import { generateVitestSuite } from "./adapters/vitest-adapter.js";
4
+ import { generateJestSuite } from "./adapters/jest-adapter.js";
5
+ import { GoldenTestGenerator, runGoldenTests } from "./generator/golden-test-generator.js";
6
+
7
+ export { GoldenTestGenerator, InMemoryTrafficStore, TrafficRecorder, buildAssertions, generateJestSuite, generateVitestSuite, runGoldenTests, serialize };
@@ -1 +1,59 @@
1
- import{randomUUID as e}from"node:crypto";var t=class{items=[];async save(e){this.items.push(e)}async list(e){return e?this.items.filter(t=>t.operation.name===e):[...this.items]}},n=class{store;sampleRate;sanitize;constructor(e){this.store=e.store,this.sampleRate=e.sampleRate??1,this.sanitize=e.sanitize}async record(t){if(!this.shouldSample())return;let n={id:e(),operation:t.operation,input:r(t.input),output:r(t.output),error:t.error?r(t.error):void 0,success:t.success,timestamp:new Date,durationMs:t.durationMs,tenantId:t.tenantId,userId:t.userId,channel:t.channel,metadata:t.metadata},i=this.sanitize?this.sanitize(n):n;await this.store.save(i)}shouldSample(){return this.sampleRate>=1?!0:Math.random()<=this.sampleRate}};function r(e){if(e==null)return e??void 0;try{let t=globalThis.structuredClone;return typeof t==`function`?t(e):JSON.parse(JSON.stringify(e))}catch{return}}export{t as InMemoryTrafficStore,n as TrafficRecorder};
1
+ import { randomUUID } from "node:crypto";
2
+
3
+ //#region src/recorder/traffic-recorder.ts
4
+ var InMemoryTrafficStore = class {
5
+ items = [];
6
+ async save(snapshot) {
7
+ this.items.push(snapshot);
8
+ }
9
+ async list(operation) {
10
+ if (!operation) return [...this.items];
11
+ return this.items.filter((item) => item.operation.name === operation);
12
+ }
13
+ };
14
+ var TrafficRecorder = class {
15
+ store;
16
+ sampleRate;
17
+ sanitize;
18
+ constructor(options) {
19
+ this.store = options.store;
20
+ this.sampleRate = options.sampleRate ?? 1;
21
+ this.sanitize = options.sanitize;
22
+ }
23
+ async record(input) {
24
+ if (!this.shouldSample()) return;
25
+ const snapshot = {
26
+ id: randomUUID(),
27
+ operation: input.operation,
28
+ input: structuredCloneSafe(input.input),
29
+ output: structuredCloneSafe(input.output),
30
+ error: input.error ? structuredCloneSafe(input.error) : void 0,
31
+ success: input.success,
32
+ timestamp: /* @__PURE__ */ new Date(),
33
+ durationMs: input.durationMs,
34
+ tenantId: input.tenantId,
35
+ userId: input.userId,
36
+ channel: input.channel,
37
+ metadata: input.metadata
38
+ };
39
+ const sanitized = this.sanitize ? this.sanitize(snapshot) : snapshot;
40
+ await this.store.save(sanitized);
41
+ }
42
+ shouldSample() {
43
+ if (this.sampleRate >= 1) return true;
44
+ return Math.random() <= this.sampleRate;
45
+ }
46
+ };
47
+ function structuredCloneSafe(value) {
48
+ if (value == null) return value ?? void 0;
49
+ try {
50
+ const clone = globalThis.structuredClone;
51
+ if (typeof clone === "function") return clone(value);
52
+ return JSON.parse(JSON.stringify(value));
53
+ } catch {
54
+ return;
55
+ }
56
+ }
57
+
58
+ //#endregion
59
+ export { InMemoryTrafficStore, TrafficRecorder };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lssm/lib.testing",
3
- "version": "0.0.0-canary-20251217063201",
3
+ "version": "0.0.0-canary-20251217072406",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -23,11 +23,11 @@
23
23
  "test": "bun run"
24
24
  },
25
25
  "dependencies": {
26
- "@lssm/lib.contracts": "0.0.0-canary-20251217063201"
26
+ "@lssm/lib.contracts": "0.0.0-canary-20251217072406"
27
27
  },
28
28
  "devDependencies": {
29
- "@lssm/tool.tsdown": "0.0.0-canary-20251217063201",
30
- "@lssm/tool.typescript": "0.0.0-canary-20251217063201",
29
+ "@lssm/tool.tsdown": "0.0.0-canary-20251217072406",
30
+ "@lssm/tool.typescript": "0.0.0-canary-20251217072406",
31
31
  "tsdown": "^0.17.4",
32
32
  "typescript": "^5.9.3"
33
33
  },