@artemiskit/sdk 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/CHANGELOG.md +134 -0
- package/README.md +173 -0
- package/adapters/openai/dist/index.js +5625 -0
- package/dist/index.js +42577 -0
- package/dist/matchers/index.js +224 -0
- package/dist/matchers/jest.js +257 -0
- package/dist/matchers/vitest.js +257 -0
- package/package.json +78 -0
- package/src/__tests__/artemiskit.test.ts +425 -0
- package/src/__tests__/matchers.test.ts +450 -0
- package/src/artemiskit.ts +791 -0
- package/src/guardian/action-validator.ts +585 -0
- package/src/guardian/circuit-breaker.ts +655 -0
- package/src/guardian/guardian.ts +497 -0
- package/src/guardian/guardrails.ts +536 -0
- package/src/guardian/index.ts +142 -0
- package/src/guardian/intent-classifier.ts +378 -0
- package/src/guardian/interceptor.ts +381 -0
- package/src/guardian/policy.ts +446 -0
- package/src/guardian/types.ts +436 -0
- package/src/index.ts +164 -0
- package/src/matchers/core.ts +315 -0
- package/src/matchers/index.ts +26 -0
- package/src/matchers/jest.ts +112 -0
- package/src/matchers/vitest.ts +84 -0
- package/src/types.ts +259 -0
- package/tsconfig.json +11 -0
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @artemiskit/sdk
|
|
3
|
+
* Core matcher implementations for Jest/Vitest integration
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { CaseResult, RunManifest } from '@artemiskit/core';
|
|
7
|
+
import type { RedTeamResult, RunResult, StressResult } from '../types';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Matcher result interface compatible with Jest/Vitest
|
|
11
|
+
*/
|
|
12
|
+
export interface MatcherResult {
|
|
13
|
+
pass: boolean;
|
|
14
|
+
message: () => string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Format a manifest summary for error messages
|
|
19
|
+
*/
|
|
20
|
+
function formatManifestSummary(manifest: RunManifest): string {
|
|
21
|
+
const { metrics } = manifest;
|
|
22
|
+
return `
|
|
23
|
+
Scenario: ${manifest.config.scenario}
|
|
24
|
+
Total Cases: ${metrics.total_cases}
|
|
25
|
+
Passed: ${metrics.passed_cases}
|
|
26
|
+
Failed: ${metrics.failed_cases}
|
|
27
|
+
Success Rate: ${(metrics.success_rate * 100).toFixed(1)}%
|
|
28
|
+
`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Format failed cases for error messages
|
|
33
|
+
*/
|
|
34
|
+
function formatFailedCases(cases: CaseResult[]): string {
|
|
35
|
+
const failed = cases.filter((c) => !c.ok);
|
|
36
|
+
if (failed.length === 0) return '';
|
|
37
|
+
|
|
38
|
+
return `
|
|
39
|
+
Failed Cases:
|
|
40
|
+
${failed
|
|
41
|
+
.slice(0, 5) // Show first 5 failures
|
|
42
|
+
.map((c) => ` - ${c.name ?? c.id}: ${c.reason ?? 'No reason provided'}`)
|
|
43
|
+
.join('\n')}${failed.length > 5 ? `\n ... and ${failed.length - 5} more` : ''}
|
|
44
|
+
`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ==========================================================================
|
|
48
|
+
// Run Result Matchers
|
|
49
|
+
// ==========================================================================
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Check if a run result passed all test cases
|
|
53
|
+
*/
|
|
54
|
+
export function toPassAllCases(result: RunResult): MatcherResult {
|
|
55
|
+
const pass = result.success;
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
pass,
|
|
59
|
+
message: () =>
|
|
60
|
+
pass
|
|
61
|
+
? `Expected test run to fail, but all ${result.manifest.metrics.total_cases} cases passed`
|
|
62
|
+
: `Expected test run to pass, but ${result.manifest.metrics.failed_cases} out of ${result.manifest.metrics.total_cases} cases failed
|
|
63
|
+
${formatManifestSummary(result.manifest)}${formatFailedCases(result.cases)}`,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Check if success rate meets threshold
|
|
69
|
+
*/
|
|
70
|
+
export function toHaveSuccessRate(result: RunResult, expectedRate: number): MatcherResult {
|
|
71
|
+
const actualRate = result.manifest.metrics.success_rate;
|
|
72
|
+
const pass = actualRate >= expectedRate;
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
pass,
|
|
76
|
+
message: () =>
|
|
77
|
+
pass
|
|
78
|
+
? `Expected success rate to be less than ${(expectedRate * 100).toFixed(1)}%, but got ${(actualRate * 100).toFixed(1)}%`
|
|
79
|
+
: `Expected success rate to be at least ${(expectedRate * 100).toFixed(1)}%, but got ${(actualRate * 100).toFixed(1)}%
|
|
80
|
+
${formatManifestSummary(result.manifest)}${formatFailedCases(result.cases)}`,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Check if all cases with specific tags passed
|
|
86
|
+
*/
|
|
87
|
+
export function toPassCasesWithTag(result: RunResult, tag: string): MatcherResult {
|
|
88
|
+
const taggedCases = result.cases.filter((c) => c.tags.includes(tag));
|
|
89
|
+
const failedTaggedCases = taggedCases.filter((c) => !c.ok);
|
|
90
|
+
const pass = failedTaggedCases.length === 0;
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
pass,
|
|
94
|
+
message: () =>
|
|
95
|
+
pass
|
|
96
|
+
? `Expected cases with tag "${tag}" to fail, but all ${taggedCases.length} passed`
|
|
97
|
+
: `Expected all cases with tag "${tag}" to pass, but ${failedTaggedCases.length} out of ${taggedCases.length} failed
|
|
98
|
+
${formatFailedCases(failedTaggedCases)}`,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Check if median latency is within threshold
|
|
104
|
+
*/
|
|
105
|
+
export function toHaveMedianLatencyBelow(result: RunResult, maxLatencyMs: number): MatcherResult {
|
|
106
|
+
const actualLatency = result.manifest.metrics.median_latency_ms;
|
|
107
|
+
const pass = actualLatency <= maxLatencyMs;
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
pass,
|
|
111
|
+
message: () =>
|
|
112
|
+
pass
|
|
113
|
+
? `Expected median latency to be above ${maxLatencyMs}ms, but got ${actualLatency}ms`
|
|
114
|
+
: `Expected median latency to be at most ${maxLatencyMs}ms, but got ${actualLatency}ms`,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Check if P95 latency is within threshold
|
|
120
|
+
*/
|
|
121
|
+
export function toHaveP95LatencyBelow(result: RunResult, maxLatencyMs: number): MatcherResult {
|
|
122
|
+
const actualLatency = result.manifest.metrics.p95_latency_ms;
|
|
123
|
+
const pass = actualLatency <= maxLatencyMs;
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
pass,
|
|
127
|
+
message: () =>
|
|
128
|
+
pass
|
|
129
|
+
? `Expected P95 latency to be above ${maxLatencyMs}ms, but got ${actualLatency}ms`
|
|
130
|
+
: `Expected P95 latency to be at most ${maxLatencyMs}ms, but got ${actualLatency}ms`,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ==========================================================================
|
|
135
|
+
// Red Team Result Matchers
|
|
136
|
+
// ==========================================================================
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Check if defense rate meets threshold
|
|
140
|
+
*/
|
|
141
|
+
export function toHaveDefenseRate(result: RedTeamResult, expectedRate: number): MatcherResult {
|
|
142
|
+
const actualRate = result.defenseRate;
|
|
143
|
+
const pass = actualRate >= expectedRate;
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
pass,
|
|
147
|
+
message: () =>
|
|
148
|
+
pass
|
|
149
|
+
? `Expected defense rate to be less than ${(expectedRate * 100).toFixed(1)}%, but got ${(actualRate * 100).toFixed(1)}%`
|
|
150
|
+
: `Expected defense rate to be at least ${(expectedRate * 100).toFixed(1)}%, but got ${(actualRate * 100).toFixed(1)}%
|
|
151
|
+
|
|
152
|
+
Unsafe responses: ${result.unsafeCount}
|
|
153
|
+
Total tests: ${result.manifest.metrics.total_tests}`,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Check that no critical vulnerabilities were found
|
|
159
|
+
*/
|
|
160
|
+
export function toHaveNoCriticalVulnerabilities(result: RedTeamResult): MatcherResult {
|
|
161
|
+
const criticalCount = result.manifest.metrics.by_severity.critical;
|
|
162
|
+
const pass = criticalCount === 0;
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
pass,
|
|
166
|
+
message: () =>
|
|
167
|
+
pass
|
|
168
|
+
? 'Expected critical vulnerabilities, but none were found'
|
|
169
|
+
: `Expected no critical vulnerabilities, but found ${criticalCount}`,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Check that no high or critical vulnerabilities were found
|
|
175
|
+
*/
|
|
176
|
+
export function toHaveNoHighSeverityVulnerabilities(result: RedTeamResult): MatcherResult {
|
|
177
|
+
const highCount = result.manifest.metrics.by_severity.high;
|
|
178
|
+
const criticalCount = result.manifest.metrics.by_severity.critical;
|
|
179
|
+
const totalHighSeverity = highCount + criticalCount;
|
|
180
|
+
const pass = totalHighSeverity === 0;
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
pass,
|
|
184
|
+
message: () =>
|
|
185
|
+
pass
|
|
186
|
+
? 'Expected high/critical vulnerabilities, but none were found'
|
|
187
|
+
: `Expected no high/critical vulnerabilities, but found ${totalHighSeverity} (${highCount} high, ${criticalCount} critical)`,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Check if red team test passed (default 95% defense rate)
|
|
193
|
+
*/
|
|
194
|
+
export function toPassRedTeam(result: RedTeamResult): MatcherResult {
|
|
195
|
+
const pass = result.success;
|
|
196
|
+
const { metrics } = result.manifest;
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
pass,
|
|
200
|
+
message: () =>
|
|
201
|
+
pass
|
|
202
|
+
? `Expected red team test to fail, but passed with ${(result.defenseRate * 100).toFixed(1)}% defense rate`
|
|
203
|
+
: `Expected red team test to pass, but defense rate was only ${(result.defenseRate * 100).toFixed(1)}%
|
|
204
|
+
|
|
205
|
+
Total tests: ${metrics.total_tests}
|
|
206
|
+
Safe: ${metrics.safe_responses}
|
|
207
|
+
Blocked: ${metrics.blocked_responses}
|
|
208
|
+
Unsafe: ${metrics.unsafe_responses}
|
|
209
|
+
Errors: ${metrics.error_responses}`,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ==========================================================================
|
|
214
|
+
// Stress Test Result Matchers
|
|
215
|
+
// ==========================================================================
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Check if stress test success rate meets threshold
|
|
219
|
+
*/
|
|
220
|
+
export function toHaveStressSuccessRate(result: StressResult, expectedRate: number): MatcherResult {
|
|
221
|
+
const actualRate = result.successRate;
|
|
222
|
+
const pass = actualRate >= expectedRate;
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
pass,
|
|
226
|
+
message: () =>
|
|
227
|
+
pass
|
|
228
|
+
? `Expected stress test success rate to be less than ${(expectedRate * 100).toFixed(1)}%, but got ${(actualRate * 100).toFixed(1)}%`
|
|
229
|
+
: `Expected stress test success rate to be at least ${(expectedRate * 100).toFixed(1)}%, but got ${(actualRate * 100).toFixed(1)}%`,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Check if stress test achieved target RPS
|
|
235
|
+
*/
|
|
236
|
+
export function toAchieveRPS(result: StressResult, targetRPS: number): MatcherResult {
|
|
237
|
+
const actualRPS = result.rps;
|
|
238
|
+
const pass = actualRPS >= targetRPS;
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
pass,
|
|
242
|
+
message: () =>
|
|
243
|
+
pass
|
|
244
|
+
? `Expected RPS to be less than ${targetRPS}, but achieved ${actualRPS.toFixed(1)} RPS`
|
|
245
|
+
: `Expected to achieve at least ${targetRPS} RPS, but only got ${actualRPS.toFixed(1)} RPS`,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Check if stress test P95 latency is within threshold
|
|
251
|
+
*/
|
|
252
|
+
export function toHaveStressP95LatencyBelow(
|
|
253
|
+
result: StressResult,
|
|
254
|
+
maxLatencyMs: number
|
|
255
|
+
): MatcherResult {
|
|
256
|
+
const actualLatency = result.p95LatencyMs;
|
|
257
|
+
const pass = actualLatency <= maxLatencyMs;
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
pass,
|
|
261
|
+
message: () =>
|
|
262
|
+
pass
|
|
263
|
+
? `Expected P95 latency to be above ${maxLatencyMs}ms, but got ${actualLatency}ms`
|
|
264
|
+
: `Expected P95 latency to be at most ${maxLatencyMs}ms, but got ${actualLatency}ms`,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Check if stress test passed (default 95% success rate)
|
|
270
|
+
*/
|
|
271
|
+
export function toPassStressTest(result: StressResult): MatcherResult {
|
|
272
|
+
const pass = result.success;
|
|
273
|
+
const { metrics } = result.manifest;
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
pass,
|
|
277
|
+
message: () =>
|
|
278
|
+
pass
|
|
279
|
+
? `Expected stress test to fail, but passed with ${(result.successRate * 100).toFixed(1)}% success rate`
|
|
280
|
+
: `Expected stress test to pass, but success rate was only ${(result.successRate * 100).toFixed(1)}%
|
|
281
|
+
|
|
282
|
+
Total requests: ${metrics.total_requests}
|
|
283
|
+
Successful: ${metrics.successful_requests}
|
|
284
|
+
Failed: ${metrics.failed_requests}
|
|
285
|
+
RPS: ${metrics.requests_per_second.toFixed(1)}
|
|
286
|
+
P95 Latency: ${metrics.p95_latency_ms}ms`,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// ==========================================================================
|
|
291
|
+
// Export all matchers
|
|
292
|
+
// ==========================================================================
|
|
293
|
+
|
|
294
|
+
export const artemiskitMatchers = {
|
|
295
|
+
// Run matchers
|
|
296
|
+
toPassAllCases,
|
|
297
|
+
toHaveSuccessRate,
|
|
298
|
+
toPassCasesWithTag,
|
|
299
|
+
toHaveMedianLatencyBelow,
|
|
300
|
+
toHaveP95LatencyBelow,
|
|
301
|
+
|
|
302
|
+
// Red team matchers
|
|
303
|
+
toHaveDefenseRate,
|
|
304
|
+
toHaveNoCriticalVulnerabilities,
|
|
305
|
+
toHaveNoHighSeverityVulnerabilities,
|
|
306
|
+
toPassRedTeam,
|
|
307
|
+
|
|
308
|
+
// Stress test matchers
|
|
309
|
+
toHaveStressSuccessRate,
|
|
310
|
+
toAchieveRPS,
|
|
311
|
+
toHaveStressP95LatencyBelow,
|
|
312
|
+
toPassStressTest,
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
export type ArtemisKitMatchers = typeof artemiskitMatchers;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @artemiskit/sdk
|
|
3
|
+
* Test matchers - core implementations without framework bindings
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export {
|
|
7
|
+
artemiskitMatchers,
|
|
8
|
+
type ArtemisKitMatchers,
|
|
9
|
+
type MatcherResult,
|
|
10
|
+
// Run matchers
|
|
11
|
+
toPassAllCases,
|
|
12
|
+
toHaveSuccessRate,
|
|
13
|
+
toPassCasesWithTag,
|
|
14
|
+
toHaveMedianLatencyBelow,
|
|
15
|
+
toHaveP95LatencyBelow,
|
|
16
|
+
// Red team matchers
|
|
17
|
+
toHaveDefenseRate,
|
|
18
|
+
toHaveNoCriticalVulnerabilities,
|
|
19
|
+
toHaveNoHighSeverityVulnerabilities,
|
|
20
|
+
toPassRedTeam,
|
|
21
|
+
// Stress test matchers
|
|
22
|
+
toHaveStressSuccessRate,
|
|
23
|
+
toAchieveRPS,
|
|
24
|
+
toHaveStressP95LatencyBelow,
|
|
25
|
+
toPassStressTest,
|
|
26
|
+
} from './core';
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @artemiskit/sdk
|
|
3
|
+
* Jest integration - custom matchers for ArtemisKit
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { RedTeamResult, RunResult, StressResult } from '../types';
|
|
7
|
+
import {
|
|
8
|
+
toAchieveRPS,
|
|
9
|
+
toHaveDefenseRate,
|
|
10
|
+
toHaveMedianLatencyBelow,
|
|
11
|
+
toHaveNoCriticalVulnerabilities,
|
|
12
|
+
toHaveNoHighSeverityVulnerabilities,
|
|
13
|
+
toHaveP95LatencyBelow,
|
|
14
|
+
toHaveStressP95LatencyBelow,
|
|
15
|
+
toHaveStressSuccessRate,
|
|
16
|
+
toHaveSuccessRate,
|
|
17
|
+
toPassAllCases,
|
|
18
|
+
toPassCasesWithTag,
|
|
19
|
+
toPassRedTeam,
|
|
20
|
+
toPassStressTest,
|
|
21
|
+
} from './core';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Jest matchers implementation
|
|
25
|
+
*/
|
|
26
|
+
export const jestMatchers = {
|
|
27
|
+
toPassAllCases(received: RunResult) {
|
|
28
|
+
return toPassAllCases(received);
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
toHaveSuccessRate(received: RunResult, expectedRate: number) {
|
|
32
|
+
return toHaveSuccessRate(received, expectedRate);
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
toPassCasesWithTag(received: RunResult, tag: string) {
|
|
36
|
+
return toPassCasesWithTag(received, tag);
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
toHaveMedianLatencyBelow(received: RunResult, maxLatencyMs: number) {
|
|
40
|
+
return toHaveMedianLatencyBelow(received, maxLatencyMs);
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
toHaveP95LatencyBelow(received: RunResult, maxLatencyMs: number) {
|
|
44
|
+
return toHaveP95LatencyBelow(received, maxLatencyMs);
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
toHaveDefenseRate(received: RedTeamResult, expectedRate: number) {
|
|
48
|
+
return toHaveDefenseRate(received, expectedRate);
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
toHaveNoCriticalVulnerabilities(received: RedTeamResult) {
|
|
52
|
+
return toHaveNoCriticalVulnerabilities(received);
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
toHaveNoHighSeverityVulnerabilities(received: RedTeamResult) {
|
|
56
|
+
return toHaveNoHighSeverityVulnerabilities(received);
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
toPassRedTeam(received: RedTeamResult) {
|
|
60
|
+
return toPassRedTeam(received);
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
toHaveStressSuccessRate(received: StressResult, expectedRate: number) {
|
|
64
|
+
return toHaveStressSuccessRate(received, expectedRate);
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
toAchieveRPS(received: StressResult, targetRPS: number) {
|
|
68
|
+
return toAchieveRPS(received, targetRPS);
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
toHaveStressP95LatencyBelow(received: StressResult, maxLatencyMs: number) {
|
|
72
|
+
return toHaveStressP95LatencyBelow(received, maxLatencyMs);
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
toPassStressTest(received: StressResult) {
|
|
76
|
+
return toPassStressTest(received);
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// Extend Jest's expect if available
|
|
81
|
+
declare const expect: { extend: (matchers: object) => void } | undefined;
|
|
82
|
+
if (typeof expect !== 'undefined' && typeof expect.extend === 'function') {
|
|
83
|
+
expect.extend(jestMatchers);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* TypeScript declarations for Jest matchers
|
|
88
|
+
*/
|
|
89
|
+
declare global {
|
|
90
|
+
namespace jest {
|
|
91
|
+
interface Matchers<R> {
|
|
92
|
+
// Run matchers
|
|
93
|
+
toPassAllCases(): R;
|
|
94
|
+
toHaveSuccessRate(expectedRate: number): R;
|
|
95
|
+
toPassCasesWithTag(tag: string): R;
|
|
96
|
+
toHaveMedianLatencyBelow(maxLatencyMs: number): R;
|
|
97
|
+
toHaveP95LatencyBelow(maxLatencyMs: number): R;
|
|
98
|
+
|
|
99
|
+
// Red team matchers
|
|
100
|
+
toHaveDefenseRate(expectedRate: number): R;
|
|
101
|
+
toHaveNoCriticalVulnerabilities(): R;
|
|
102
|
+
toHaveNoHighSeverityVulnerabilities(): R;
|
|
103
|
+
toPassRedTeam(): R;
|
|
104
|
+
|
|
105
|
+
// Stress test matchers
|
|
106
|
+
toHaveStressSuccessRate(expectedRate: number): R;
|
|
107
|
+
toAchieveRPS(targetRPS: number): R;
|
|
108
|
+
toHaveStressP95LatencyBelow(maxLatencyMs: number): R;
|
|
109
|
+
toPassStressTest(): R;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @artemiskit/sdk
|
|
3
|
+
* Vitest integration - custom matchers for ArtemisKit
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { RedTeamResult, RunResult, StressResult } from '../types';
|
|
7
|
+
import {
|
|
8
|
+
toAchieveRPS,
|
|
9
|
+
toHaveDefenseRate,
|
|
10
|
+
toHaveMedianLatencyBelow,
|
|
11
|
+
toHaveNoCriticalVulnerabilities,
|
|
12
|
+
toHaveNoHighSeverityVulnerabilities,
|
|
13
|
+
toHaveP95LatencyBelow,
|
|
14
|
+
toHaveStressP95LatencyBelow,
|
|
15
|
+
toHaveStressSuccessRate,
|
|
16
|
+
toHaveSuccessRate,
|
|
17
|
+
toPassAllCases,
|
|
18
|
+
toPassCasesWithTag,
|
|
19
|
+
toPassRedTeam,
|
|
20
|
+
toPassStressTest,
|
|
21
|
+
} from './core';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Vitest matchers implementation
|
|
25
|
+
*/
|
|
26
|
+
export const vitestMatchers = {
|
|
27
|
+
toPassAllCases(received: RunResult) {
|
|
28
|
+
return toPassAllCases(received);
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
toHaveSuccessRate(received: RunResult, expectedRate: number) {
|
|
32
|
+
return toHaveSuccessRate(received, expectedRate);
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
toPassCasesWithTag(received: RunResult, tag: string) {
|
|
36
|
+
return toPassCasesWithTag(received, tag);
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
toHaveMedianLatencyBelow(received: RunResult, maxLatencyMs: number) {
|
|
40
|
+
return toHaveMedianLatencyBelow(received, maxLatencyMs);
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
toHaveP95LatencyBelow(received: RunResult, maxLatencyMs: number) {
|
|
44
|
+
return toHaveP95LatencyBelow(received, maxLatencyMs);
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
toHaveDefenseRate(received: RedTeamResult, expectedRate: number) {
|
|
48
|
+
return toHaveDefenseRate(received, expectedRate);
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
toHaveNoCriticalVulnerabilities(received: RedTeamResult) {
|
|
52
|
+
return toHaveNoCriticalVulnerabilities(received);
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
toHaveNoHighSeverityVulnerabilities(received: RedTeamResult) {
|
|
56
|
+
return toHaveNoHighSeverityVulnerabilities(received);
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
toPassRedTeam(received: RedTeamResult) {
|
|
60
|
+
return toPassRedTeam(received);
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
toHaveStressSuccessRate(received: StressResult, expectedRate: number) {
|
|
64
|
+
return toHaveStressSuccessRate(received, expectedRate);
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
toAchieveRPS(received: StressResult, targetRPS: number) {
|
|
68
|
+
return toAchieveRPS(received, targetRPS);
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
toHaveStressP95LatencyBelow(received: StressResult, maxLatencyMs: number) {
|
|
72
|
+
return toHaveStressP95LatencyBelow(received, maxLatencyMs);
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
toPassStressTest(received: StressResult) {
|
|
76
|
+
return toPassStressTest(received);
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// Extend Vitest's expect if available
|
|
81
|
+
declare const expect: { extend: (matchers: object) => void } | undefined;
|
|
82
|
+
if (typeof expect !== 'undefined' && typeof expect.extend === 'function') {
|
|
83
|
+
expect.extend(vitestMatchers);
|
|
84
|
+
}
|