@contractspec/lib.testing 3.7.16 → 3.7.18
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/index.js +21 -213
- package/dist/node/index.js +21 -213
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -1,220 +1,28 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
|
|
3
|
-
function
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
function serialize(value) {
|
|
14
|
-
return JSON.stringify(value, (_key, val) => {
|
|
15
|
-
if (val instanceof Date)
|
|
16
|
-
return val.toISOString();
|
|
17
|
-
if (typeof val === "undefined")
|
|
18
|
-
return null;
|
|
19
|
-
return val;
|
|
20
|
-
}, 2);
|
|
21
|
-
}
|
|
2
|
+
function T(e,r){if(e.success)return[`const result = await ${r.runnerCall};`,`expect(result).toEqual(${s(e.expectedOutput??null)});`].join(`
|
|
3
|
+
`);return`await expect(${r.runnerCall}).rejects.toMatchObject(${s(e.expectedError??{message:"expected failure"})});`}function s(e){return JSON.stringify(e,(r,t)=>{if(t instanceof Date)return t.toISOString();if(typeof t>"u")return null;return t},2)}function p(e){let r=e.cases.map((t)=>{let n=s(t.input),i=s(t.metadata??{}),a=`const result = await ${e.runnerFunction}(input${t.id}, metadata${t.id});
|
|
4
|
+
expect(result).toEqual(${s(t.expectedOutput??null)});`,o=`await expect(${e.runnerFunction}(input${t.id}, metadata${t.id})).rejects.toMatchObject(${s(t.expectedError??{message:"expected failure"})});`;return`
|
|
5
|
+
test('${t.name}', async () => {
|
|
6
|
+
const input${t.id} = ${n};
|
|
7
|
+
const metadata${t.id} = ${i};
|
|
8
|
+
${t.success?a:o}
|
|
9
|
+
});`}).join(`
|
|
10
|
+
`);return`
|
|
11
|
+
import { ${e.runnerFunction} } from '${e.runnerImport}';
|
|
22
12
|
|
|
23
|
-
|
|
24
|
-
function generateJestSuite(options) {
|
|
25
|
-
const caseBlocks = options.cases.map((testCase) => {
|
|
26
|
-
const inputConst = serialize(testCase.input);
|
|
27
|
-
const metadataConst = serialize(testCase.metadata ?? {});
|
|
28
|
-
const successBlock = `const result = await ${options.runnerFunction}(input${testCase.id}, metadata${testCase.id});
|
|
29
|
-
expect(result).toEqual(${serialize(testCase.expectedOutput ?? null)});`;
|
|
30
|
-
const failureBlock = `await expect(${options.runnerFunction}(input${testCase.id}, metadata${testCase.id})).rejects.toMatchObject(${serialize(testCase.expectedError ?? { message: "expected failure" })});`;
|
|
31
|
-
return `
|
|
32
|
-
test('${testCase.name}', async () => {
|
|
33
|
-
const input${testCase.id} = ${inputConst};
|
|
34
|
-
const metadata${testCase.id} = ${metadataConst};
|
|
35
|
-
${testCase.success ? successBlock : failureBlock}
|
|
36
|
-
});`;
|
|
37
|
-
}).join(`
|
|
38
|
-
`);
|
|
39
|
-
return `
|
|
40
|
-
import { ${options.runnerFunction} } from '${options.runnerImport}';
|
|
41
|
-
|
|
42
|
-
describe('${options.suiteName}', () => {${caseBlocks}
|
|
13
|
+
describe('${e.suiteName}', () => {${r}
|
|
43
14
|
});
|
|
44
|
-
`.trim();
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const inputConst = serialize(testCase.input);
|
|
50
|
-
const metadataConst = serialize(testCase.metadata ?? {});
|
|
51
|
-
const assertions = testCase.success ? [
|
|
52
|
-
`const result = await ${options.runnerFunction}(input${testCase.id}, metadata${testCase.id});`,
|
|
53
|
-
`expect(result).toEqual(${serialize(testCase.expectedOutput ?? null)});`
|
|
54
|
-
] : [
|
|
55
|
-
`await expect(${options.runnerFunction}(input${testCase.id}, metadata${testCase.id})).rejects.toMatchObject(${serialize(testCase.expectedError ?? { message: "expected failure" })});`
|
|
56
|
-
];
|
|
57
|
-
return `
|
|
58
|
-
it('${testCase.name}', async () => {
|
|
59
|
-
const input${testCase.id} = ${inputConst};
|
|
60
|
-
const metadata${testCase.id} = ${metadataConst};
|
|
61
|
-
${assertions.join(`
|
|
15
|
+
`.trim()}function d(e){let r=e.cases.map((t)=>{let n=s(t.input),i=s(t.metadata??{}),a=t.success?[`const result = await ${e.runnerFunction}(input${t.id}, metadata${t.id});`,`expect(result).toEqual(${s(t.expectedOutput??null)});`]:[`await expect(${e.runnerFunction}(input${t.id}, metadata${t.id})).rejects.toMatchObject(${s(t.expectedError??{message:"expected failure"})});`];return`
|
|
16
|
+
it('${t.name}', async () => {
|
|
17
|
+
const input${t.id} = ${n};
|
|
18
|
+
const metadata${t.id} = ${i};
|
|
19
|
+
${a.join(`
|
|
62
20
|
`)}
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
`);
|
|
66
|
-
return `
|
|
21
|
+
});`}).join(`
|
|
22
|
+
`);return`
|
|
67
23
|
import { describe, it, expect } from 'bun:test';
|
|
68
|
-
import { ${
|
|
24
|
+
import { ${e.runnerFunction} } from '${e.runnerImport}';
|
|
69
25
|
|
|
70
|
-
describe('${
|
|
26
|
+
describe('${e.suiteName}', () => {${r}
|
|
71
27
|
});
|
|
72
|
-
`.trim();
|
|
73
|
-
}
|
|
74
|
-
// src/generator/golden-test-generator.ts
|
|
75
|
-
import { performance } from "perf_hooks";
|
|
76
|
-
import { randomUUID } from "crypto";
|
|
77
|
-
class GoldenTestGenerator {
|
|
78
|
-
serializeMetadata;
|
|
79
|
-
constructor(serializeMetadata = (snapshot) => ({
|
|
80
|
-
tenantId: snapshot.tenantId,
|
|
81
|
-
userId: snapshot.userId,
|
|
82
|
-
channel: snapshot.channel
|
|
83
|
-
})) {
|
|
84
|
-
this.serializeMetadata = serializeMetadata;
|
|
85
|
-
}
|
|
86
|
-
createCases(snapshots) {
|
|
87
|
-
return snapshots.map((snapshot, index) => ({
|
|
88
|
-
id: snapshot.id ?? randomUUID(),
|
|
89
|
-
name: snapshot.success ? `case-${index + 1}-success` : `case-${index + 1}-failure`,
|
|
90
|
-
input: snapshot.input,
|
|
91
|
-
expectedOutput: snapshot.output,
|
|
92
|
-
expectedError: snapshot.error,
|
|
93
|
-
success: snapshot.success,
|
|
94
|
-
metadata: this.serializeMetadata?.(snapshot)
|
|
95
|
-
}));
|
|
96
|
-
}
|
|
97
|
-
generate(snapshots, options) {
|
|
98
|
-
const cases = this.createCases(snapshots);
|
|
99
|
-
if (options.framework === "jest") {
|
|
100
|
-
return generateJestSuite({
|
|
101
|
-
suiteName: options.suiteName,
|
|
102
|
-
cases,
|
|
103
|
-
runnerImport: options.runnerImport,
|
|
104
|
-
runnerFunction: options.runnerFunction
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
return generateVitestSuite({
|
|
108
|
-
suiteName: options.suiteName,
|
|
109
|
-
cases,
|
|
110
|
-
runnerImport: options.runnerImport,
|
|
111
|
-
runnerFunction: options.runnerFunction
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
async function runGoldenTests(cases, runner) {
|
|
116
|
-
const results = [];
|
|
117
|
-
for (const testCase of cases) {
|
|
118
|
-
const startedAt = performance.now();
|
|
119
|
-
try {
|
|
120
|
-
const output = await runner(testCase.input, testCase.metadata);
|
|
121
|
-
if (!testCase.success) {
|
|
122
|
-
results.push({
|
|
123
|
-
caseId: testCase.id,
|
|
124
|
-
passed: false,
|
|
125
|
-
durationMs: performance.now() - startedAt,
|
|
126
|
-
error: new Error("Expected failure but runner resolved")
|
|
127
|
-
});
|
|
128
|
-
continue;
|
|
129
|
-
}
|
|
130
|
-
const matches = JSON.stringify(output) === JSON.stringify(testCase.expectedOutput ?? null);
|
|
131
|
-
results.push({
|
|
132
|
-
caseId: testCase.id,
|
|
133
|
-
passed: matches,
|
|
134
|
-
durationMs: performance.now() - startedAt,
|
|
135
|
-
error: matches ? undefined : { expected: testCase.expectedOutput, received: output }
|
|
136
|
-
});
|
|
137
|
-
} catch (error) {
|
|
138
|
-
const durationMs = performance.now() - startedAt;
|
|
139
|
-
if (!testCase.success) {
|
|
140
|
-
results.push({ caseId: testCase.id, passed: true, durationMs });
|
|
141
|
-
} else {
|
|
142
|
-
results.push({ caseId: testCase.id, passed: false, durationMs, error });
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
return results;
|
|
147
|
-
}
|
|
148
|
-
// src/recorder/traffic-recorder.ts
|
|
149
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
150
|
-
|
|
151
|
-
class InMemoryTrafficStore {
|
|
152
|
-
items = [];
|
|
153
|
-
async save(snapshot) {
|
|
154
|
-
this.items.push(snapshot);
|
|
155
|
-
}
|
|
156
|
-
async list(operation) {
|
|
157
|
-
if (!operation)
|
|
158
|
-
return [...this.items];
|
|
159
|
-
return this.items.filter((item) => item.operation.name === operation);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
class TrafficRecorder {
|
|
164
|
-
store;
|
|
165
|
-
sampleRate;
|
|
166
|
-
sanitize;
|
|
167
|
-
constructor(options) {
|
|
168
|
-
this.store = options.store;
|
|
169
|
-
this.sampleRate = options.sampleRate ?? 1;
|
|
170
|
-
this.sanitize = options.sanitize;
|
|
171
|
-
}
|
|
172
|
-
async record(input) {
|
|
173
|
-
if (!this.shouldSample())
|
|
174
|
-
return;
|
|
175
|
-
const snapshot = {
|
|
176
|
-
id: randomUUID2(),
|
|
177
|
-
operation: input.operation,
|
|
178
|
-
input: structuredCloneSafe(input.input),
|
|
179
|
-
output: structuredCloneSafe(input.output),
|
|
180
|
-
error: input.error ? structuredCloneSafe(input.error) : undefined,
|
|
181
|
-
success: input.success,
|
|
182
|
-
timestamp: new Date,
|
|
183
|
-
durationMs: input.durationMs,
|
|
184
|
-
tenantId: input.tenantId,
|
|
185
|
-
userId: input.userId,
|
|
186
|
-
channel: input.channel,
|
|
187
|
-
metadata: input.metadata
|
|
188
|
-
};
|
|
189
|
-
const sanitized = this.sanitize ? this.sanitize(snapshot) : snapshot;
|
|
190
|
-
await this.store.save(sanitized);
|
|
191
|
-
}
|
|
192
|
-
shouldSample() {
|
|
193
|
-
if (this.sampleRate >= 1)
|
|
194
|
-
return true;
|
|
195
|
-
return Math.random() <= this.sampleRate;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
function structuredCloneSafe(value) {
|
|
199
|
-
if (value == null)
|
|
200
|
-
return value ?? undefined;
|
|
201
|
-
try {
|
|
202
|
-
const clone = globalThis.structuredClone;
|
|
203
|
-
if (typeof clone === "function") {
|
|
204
|
-
return clone(value);
|
|
205
|
-
}
|
|
206
|
-
return JSON.parse(JSON.stringify(value));
|
|
207
|
-
} catch {
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
export {
|
|
212
|
-
serialize,
|
|
213
|
-
runGoldenTests,
|
|
214
|
-
generateVitestSuite,
|
|
215
|
-
generateJestSuite,
|
|
216
|
-
buildAssertions,
|
|
217
|
-
TrafficRecorder,
|
|
218
|
-
InMemoryTrafficStore,
|
|
219
|
-
GoldenTestGenerator
|
|
220
|
-
};
|
|
28
|
+
`.trim()}import{performance as c}from"perf_hooks";import{randomUUID as m}from"crypto";class f{serializeMetadata;constructor(e=(r)=>({tenantId:r.tenantId,userId:r.userId,channel:r.channel})){this.serializeMetadata=e}createCases(e){return e.map((r,t)=>({id:r.id??m(),name:r.success?`case-${t+1}-success`:`case-${t+1}-failure`,input:r.input,expectedOutput:r.output,expectedError:r.error,success:r.success,metadata:this.serializeMetadata?.(r)}))}generate(e,r){let t=this.createCases(e);if(r.framework==="jest")return p({suiteName:r.suiteName,cases:t,runnerImport:r.runnerImport,runnerFunction:r.runnerFunction});return d({suiteName:r.suiteName,cases:t,runnerImport:r.runnerImport,runnerFunction:r.runnerFunction})}}async function G(e,r){let t=[];for(let n of e){let i=c.now();try{let a=await r(n.input,n.metadata);if(!n.success){t.push({caseId:n.id,passed:!1,durationMs:c.now()-i,error:Error("Expected failure but runner resolved")});continue}let o=JSON.stringify(a)===JSON.stringify(n.expectedOutput??null);t.push({caseId:n.id,passed:o,durationMs:c.now()-i,error:o?void 0:{expected:n.expectedOutput,received:a}})}catch(a){let o=c.now()-i;if(!n.success)t.push({caseId:n.id,passed:!0,durationMs:o});else t.push({caseId:n.id,passed:!1,durationMs:o,error:a})}}return t}import{randomUUID as l}from"crypto";class h{items=[];async save(e){this.items.push(e)}async list(e){if(!e)return[...this.items];return this.items.filter((r)=>r.operation.name===e)}}class g{store;sampleRate;sanitize;constructor(e){this.store=e.store,this.sampleRate=e.sampleRate??1,this.sanitize=e.sanitize}async record(e){if(!this.shouldSample())return;let r={id:l(),operation:e.operation,input:u(e.input),output:u(e.output),error:e.error?u(e.error):void 0,success:e.success,timestamp:new Date,durationMs:e.durationMs,tenantId:e.tenantId,userId:e.userId,channel:e.channel,metadata:e.metadata},t=this.sanitize?this.sanitize(r):r;await this.store.save(t)}shouldSample(){if(this.sampleRate>=1)return!0;return Math.random()<=this.sampleRate}}function u(e){if(e==null)return e??void 0;try{let r=globalThis.structuredClone;if(typeof r==="function")return r(e);return JSON.parse(JSON.stringify(e))}catch{return}}export{s as serialize,G as runGoldenTests,d as generateVitestSuite,p as generateJestSuite,T as buildAssertions,g as TrafficRecorder,h as InMemoryTrafficStore,f as GoldenTestGenerator};
|
package/dist/node/index.js
CHANGED
|
@@ -1,219 +1,27 @@
|
|
|
1
|
-
|
|
2
|
-
function
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
function serialize(value) {
|
|
13
|
-
return JSON.stringify(value, (_key, val) => {
|
|
14
|
-
if (val instanceof Date)
|
|
15
|
-
return val.toISOString();
|
|
16
|
-
if (typeof val === "undefined")
|
|
17
|
-
return null;
|
|
18
|
-
return val;
|
|
19
|
-
}, 2);
|
|
20
|
-
}
|
|
1
|
+
function T(e,r){if(e.success)return[`const result = await ${r.runnerCall};`,`expect(result).toEqual(${s(e.expectedOutput??null)});`].join(`
|
|
2
|
+
`);return`await expect(${r.runnerCall}).rejects.toMatchObject(${s(e.expectedError??{message:"expected failure"})});`}function s(e){return JSON.stringify(e,(r,t)=>{if(t instanceof Date)return t.toISOString();if(typeof t>"u")return null;return t},2)}function p(e){let r=e.cases.map((t)=>{let n=s(t.input),i=s(t.metadata??{}),a=`const result = await ${e.runnerFunction}(input${t.id}, metadata${t.id});
|
|
3
|
+
expect(result).toEqual(${s(t.expectedOutput??null)});`,o=`await expect(${e.runnerFunction}(input${t.id}, metadata${t.id})).rejects.toMatchObject(${s(t.expectedError??{message:"expected failure"})});`;return`
|
|
4
|
+
test('${t.name}', async () => {
|
|
5
|
+
const input${t.id} = ${n};
|
|
6
|
+
const metadata${t.id} = ${i};
|
|
7
|
+
${t.success?a:o}
|
|
8
|
+
});`}).join(`
|
|
9
|
+
`);return`
|
|
10
|
+
import { ${e.runnerFunction} } from '${e.runnerImport}';
|
|
21
11
|
|
|
22
|
-
|
|
23
|
-
function generateJestSuite(options) {
|
|
24
|
-
const caseBlocks = options.cases.map((testCase) => {
|
|
25
|
-
const inputConst = serialize(testCase.input);
|
|
26
|
-
const metadataConst = serialize(testCase.metadata ?? {});
|
|
27
|
-
const successBlock = `const result = await ${options.runnerFunction}(input${testCase.id}, metadata${testCase.id});
|
|
28
|
-
expect(result).toEqual(${serialize(testCase.expectedOutput ?? null)});`;
|
|
29
|
-
const failureBlock = `await expect(${options.runnerFunction}(input${testCase.id}, metadata${testCase.id})).rejects.toMatchObject(${serialize(testCase.expectedError ?? { message: "expected failure" })});`;
|
|
30
|
-
return `
|
|
31
|
-
test('${testCase.name}', async () => {
|
|
32
|
-
const input${testCase.id} = ${inputConst};
|
|
33
|
-
const metadata${testCase.id} = ${metadataConst};
|
|
34
|
-
${testCase.success ? successBlock : failureBlock}
|
|
35
|
-
});`;
|
|
36
|
-
}).join(`
|
|
37
|
-
`);
|
|
38
|
-
return `
|
|
39
|
-
import { ${options.runnerFunction} } from '${options.runnerImport}';
|
|
40
|
-
|
|
41
|
-
describe('${options.suiteName}', () => {${caseBlocks}
|
|
12
|
+
describe('${e.suiteName}', () => {${r}
|
|
42
13
|
});
|
|
43
|
-
`.trim();
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const inputConst = serialize(testCase.input);
|
|
49
|
-
const metadataConst = serialize(testCase.metadata ?? {});
|
|
50
|
-
const assertions = testCase.success ? [
|
|
51
|
-
`const result = await ${options.runnerFunction}(input${testCase.id}, metadata${testCase.id});`,
|
|
52
|
-
`expect(result).toEqual(${serialize(testCase.expectedOutput ?? null)});`
|
|
53
|
-
] : [
|
|
54
|
-
`await expect(${options.runnerFunction}(input${testCase.id}, metadata${testCase.id})).rejects.toMatchObject(${serialize(testCase.expectedError ?? { message: "expected failure" })});`
|
|
55
|
-
];
|
|
56
|
-
return `
|
|
57
|
-
it('${testCase.name}', async () => {
|
|
58
|
-
const input${testCase.id} = ${inputConst};
|
|
59
|
-
const metadata${testCase.id} = ${metadataConst};
|
|
60
|
-
${assertions.join(`
|
|
14
|
+
`.trim()}function d(e){let r=e.cases.map((t)=>{let n=s(t.input),i=s(t.metadata??{}),a=t.success?[`const result = await ${e.runnerFunction}(input${t.id}, metadata${t.id});`,`expect(result).toEqual(${s(t.expectedOutput??null)});`]:[`await expect(${e.runnerFunction}(input${t.id}, metadata${t.id})).rejects.toMatchObject(${s(t.expectedError??{message:"expected failure"})});`];return`
|
|
15
|
+
it('${t.name}', async () => {
|
|
16
|
+
const input${t.id} = ${n};
|
|
17
|
+
const metadata${t.id} = ${i};
|
|
18
|
+
${a.join(`
|
|
61
19
|
`)}
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
`);
|
|
65
|
-
return `
|
|
20
|
+
});`}).join(`
|
|
21
|
+
`);return`
|
|
66
22
|
import { describe, it, expect } from 'bun:test';
|
|
67
|
-
import { ${
|
|
23
|
+
import { ${e.runnerFunction} } from '${e.runnerImport}';
|
|
68
24
|
|
|
69
|
-
describe('${
|
|
25
|
+
describe('${e.suiteName}', () => {${r}
|
|
70
26
|
});
|
|
71
|
-
`.trim();
|
|
72
|
-
}
|
|
73
|
-
// src/generator/golden-test-generator.ts
|
|
74
|
-
import { performance } from "node:perf_hooks";
|
|
75
|
-
import { randomUUID } from "crypto";
|
|
76
|
-
class GoldenTestGenerator {
|
|
77
|
-
serializeMetadata;
|
|
78
|
-
constructor(serializeMetadata = (snapshot) => ({
|
|
79
|
-
tenantId: snapshot.tenantId,
|
|
80
|
-
userId: snapshot.userId,
|
|
81
|
-
channel: snapshot.channel
|
|
82
|
-
})) {
|
|
83
|
-
this.serializeMetadata = serializeMetadata;
|
|
84
|
-
}
|
|
85
|
-
createCases(snapshots) {
|
|
86
|
-
return snapshots.map((snapshot, index) => ({
|
|
87
|
-
id: snapshot.id ?? randomUUID(),
|
|
88
|
-
name: snapshot.success ? `case-${index + 1}-success` : `case-${index + 1}-failure`,
|
|
89
|
-
input: snapshot.input,
|
|
90
|
-
expectedOutput: snapshot.output,
|
|
91
|
-
expectedError: snapshot.error,
|
|
92
|
-
success: snapshot.success,
|
|
93
|
-
metadata: this.serializeMetadata?.(snapshot)
|
|
94
|
-
}));
|
|
95
|
-
}
|
|
96
|
-
generate(snapshots, options) {
|
|
97
|
-
const cases = this.createCases(snapshots);
|
|
98
|
-
if (options.framework === "jest") {
|
|
99
|
-
return generateJestSuite({
|
|
100
|
-
suiteName: options.suiteName,
|
|
101
|
-
cases,
|
|
102
|
-
runnerImport: options.runnerImport,
|
|
103
|
-
runnerFunction: options.runnerFunction
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
return generateVitestSuite({
|
|
107
|
-
suiteName: options.suiteName,
|
|
108
|
-
cases,
|
|
109
|
-
runnerImport: options.runnerImport,
|
|
110
|
-
runnerFunction: options.runnerFunction
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
async function runGoldenTests(cases, runner) {
|
|
115
|
-
const results = [];
|
|
116
|
-
for (const testCase of cases) {
|
|
117
|
-
const startedAt = performance.now();
|
|
118
|
-
try {
|
|
119
|
-
const output = await runner(testCase.input, testCase.metadata);
|
|
120
|
-
if (!testCase.success) {
|
|
121
|
-
results.push({
|
|
122
|
-
caseId: testCase.id,
|
|
123
|
-
passed: false,
|
|
124
|
-
durationMs: performance.now() - startedAt,
|
|
125
|
-
error: new Error("Expected failure but runner resolved")
|
|
126
|
-
});
|
|
127
|
-
continue;
|
|
128
|
-
}
|
|
129
|
-
const matches = JSON.stringify(output) === JSON.stringify(testCase.expectedOutput ?? null);
|
|
130
|
-
results.push({
|
|
131
|
-
caseId: testCase.id,
|
|
132
|
-
passed: matches,
|
|
133
|
-
durationMs: performance.now() - startedAt,
|
|
134
|
-
error: matches ? undefined : { expected: testCase.expectedOutput, received: output }
|
|
135
|
-
});
|
|
136
|
-
} catch (error) {
|
|
137
|
-
const durationMs = performance.now() - startedAt;
|
|
138
|
-
if (!testCase.success) {
|
|
139
|
-
results.push({ caseId: testCase.id, passed: true, durationMs });
|
|
140
|
-
} else {
|
|
141
|
-
results.push({ caseId: testCase.id, passed: false, durationMs, error });
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
return results;
|
|
146
|
-
}
|
|
147
|
-
// src/recorder/traffic-recorder.ts
|
|
148
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
149
|
-
|
|
150
|
-
class InMemoryTrafficStore {
|
|
151
|
-
items = [];
|
|
152
|
-
async save(snapshot) {
|
|
153
|
-
this.items.push(snapshot);
|
|
154
|
-
}
|
|
155
|
-
async list(operation) {
|
|
156
|
-
if (!operation)
|
|
157
|
-
return [...this.items];
|
|
158
|
-
return this.items.filter((item) => item.operation.name === operation);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
class TrafficRecorder {
|
|
163
|
-
store;
|
|
164
|
-
sampleRate;
|
|
165
|
-
sanitize;
|
|
166
|
-
constructor(options) {
|
|
167
|
-
this.store = options.store;
|
|
168
|
-
this.sampleRate = options.sampleRate ?? 1;
|
|
169
|
-
this.sanitize = options.sanitize;
|
|
170
|
-
}
|
|
171
|
-
async record(input) {
|
|
172
|
-
if (!this.shouldSample())
|
|
173
|
-
return;
|
|
174
|
-
const snapshot = {
|
|
175
|
-
id: randomUUID2(),
|
|
176
|
-
operation: input.operation,
|
|
177
|
-
input: structuredCloneSafe(input.input),
|
|
178
|
-
output: structuredCloneSafe(input.output),
|
|
179
|
-
error: input.error ? structuredCloneSafe(input.error) : undefined,
|
|
180
|
-
success: input.success,
|
|
181
|
-
timestamp: new Date,
|
|
182
|
-
durationMs: input.durationMs,
|
|
183
|
-
tenantId: input.tenantId,
|
|
184
|
-
userId: input.userId,
|
|
185
|
-
channel: input.channel,
|
|
186
|
-
metadata: input.metadata
|
|
187
|
-
};
|
|
188
|
-
const sanitized = this.sanitize ? this.sanitize(snapshot) : snapshot;
|
|
189
|
-
await this.store.save(sanitized);
|
|
190
|
-
}
|
|
191
|
-
shouldSample() {
|
|
192
|
-
if (this.sampleRate >= 1)
|
|
193
|
-
return true;
|
|
194
|
-
return Math.random() <= this.sampleRate;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
function structuredCloneSafe(value) {
|
|
198
|
-
if (value == null)
|
|
199
|
-
return value ?? undefined;
|
|
200
|
-
try {
|
|
201
|
-
const clone = globalThis.structuredClone;
|
|
202
|
-
if (typeof clone === "function") {
|
|
203
|
-
return clone(value);
|
|
204
|
-
}
|
|
205
|
-
return JSON.parse(JSON.stringify(value));
|
|
206
|
-
} catch {
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
export {
|
|
211
|
-
serialize,
|
|
212
|
-
runGoldenTests,
|
|
213
|
-
generateVitestSuite,
|
|
214
|
-
generateJestSuite,
|
|
215
|
-
buildAssertions,
|
|
216
|
-
TrafficRecorder,
|
|
217
|
-
InMemoryTrafficStore,
|
|
218
|
-
GoldenTestGenerator
|
|
219
|
-
};
|
|
27
|
+
`.trim()}import{performance as c}from"node:perf_hooks";import{randomUUID as m}from"crypto";class f{serializeMetadata;constructor(e=(r)=>({tenantId:r.tenantId,userId:r.userId,channel:r.channel})){this.serializeMetadata=e}createCases(e){return e.map((r,t)=>({id:r.id??m(),name:r.success?`case-${t+1}-success`:`case-${t+1}-failure`,input:r.input,expectedOutput:r.output,expectedError:r.error,success:r.success,metadata:this.serializeMetadata?.(r)}))}generate(e,r){let t=this.createCases(e);if(r.framework==="jest")return p({suiteName:r.suiteName,cases:t,runnerImport:r.runnerImport,runnerFunction:r.runnerFunction});return d({suiteName:r.suiteName,cases:t,runnerImport:r.runnerImport,runnerFunction:r.runnerFunction})}}async function G(e,r){let t=[];for(let n of e){let i=c.now();try{let a=await r(n.input,n.metadata);if(!n.success){t.push({caseId:n.id,passed:!1,durationMs:c.now()-i,error:Error("Expected failure but runner resolved")});continue}let o=JSON.stringify(a)===JSON.stringify(n.expectedOutput??null);t.push({caseId:n.id,passed:o,durationMs:c.now()-i,error:o?void 0:{expected:n.expectedOutput,received:a}})}catch(a){let o=c.now()-i;if(!n.success)t.push({caseId:n.id,passed:!0,durationMs:o});else t.push({caseId:n.id,passed:!1,durationMs:o,error:a})}}return t}import{randomUUID as l}from"crypto";class h{items=[];async save(e){this.items.push(e)}async list(e){if(!e)return[...this.items];return this.items.filter((r)=>r.operation.name===e)}}class g{store;sampleRate;sanitize;constructor(e){this.store=e.store,this.sampleRate=e.sampleRate??1,this.sanitize=e.sanitize}async record(e){if(!this.shouldSample())return;let r={id:l(),operation:e.operation,input:u(e.input),output:u(e.output),error:e.error?u(e.error):void 0,success:e.success,timestamp:new Date,durationMs:e.durationMs,tenantId:e.tenantId,userId:e.userId,channel:e.channel,metadata:e.metadata},t=this.sanitize?this.sanitize(r):r;await this.store.save(t)}shouldSample(){if(this.sampleRate>=1)return!0;return Math.random()<=this.sampleRate}}function u(e){if(e==null)return e??void 0;try{let r=globalThis.structuredClone;if(typeof r==="function")return r(e);return JSON.parse(JSON.stringify(e))}catch{return}}export{s as serialize,G as runGoldenTests,d as generateVitestSuite,p as generateJestSuite,T as buildAssertions,g as TrafficRecorder,h as InMemoryTrafficStore,f as GoldenTestGenerator};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contractspec/lib.testing",
|
|
3
|
-
"version": "3.7.
|
|
3
|
+
"version": "3.7.18",
|
|
4
4
|
"description": "Contract-aware testing utilities and runners",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"contractspec",
|
|
@@ -23,20 +23,20 @@
|
|
|
23
23
|
"dev": "contractspec-bun-build dev",
|
|
24
24
|
"clean": "rimraf dist .turbo",
|
|
25
25
|
"lint": "bun lint:fix",
|
|
26
|
-
"lint:fix": "biome check --write --unsafe --only=nursery/useSortedClasses . && biome check --write .",
|
|
27
|
-
"lint:check": "biome check .",
|
|
26
|
+
"lint:fix": "node ../../../scripts/biome.cjs check --write --unsafe --only=nursery/useSortedClasses . && node ../../../scripts/biome.cjs check --write .",
|
|
27
|
+
"lint:check": "node ../../../scripts/biome.cjs check .",
|
|
28
28
|
"test": "bun test",
|
|
29
29
|
"prebuild": "contractspec-bun-build prebuild",
|
|
30
30
|
"typecheck": "tsc --noEmit"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@contractspec/lib.schema": "3.7.
|
|
34
|
-
"@contractspec/lib.contracts-spec": "5.0
|
|
33
|
+
"@contractspec/lib.schema": "3.7.14",
|
|
34
|
+
"@contractspec/lib.contracts-spec": "5.2.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@contractspec/tool.typescript": "3.7.
|
|
37
|
+
"@contractspec/tool.typescript": "3.7.13",
|
|
38
38
|
"typescript": "^5.9.3",
|
|
39
|
-
"@contractspec/tool.bun": "3.7.
|
|
39
|
+
"@contractspec/tool.bun": "3.7.14"
|
|
40
40
|
},
|
|
41
41
|
"exports": {
|
|
42
42
|
".": {
|