@sparkleideas/testing 3.0.0-alpha.7
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/README.md +547 -0
- package/__tests__/framework.test.ts +21 -0
- package/package.json +61 -0
- package/src/fixtures/agent-fixtures.ts +793 -0
- package/src/fixtures/agents.ts +212 -0
- package/src/fixtures/configurations.ts +491 -0
- package/src/fixtures/index.ts +21 -0
- package/src/fixtures/mcp-fixtures.ts +1030 -0
- package/src/fixtures/memory-entries.ts +328 -0
- package/src/fixtures/memory-fixtures.ts +750 -0
- package/src/fixtures/swarm-fixtures.ts +837 -0
- package/src/fixtures/tasks.ts +309 -0
- package/src/helpers/assertion-helpers.ts +616 -0
- package/src/helpers/assertions.ts +286 -0
- package/src/helpers/create-mock.ts +200 -0
- package/src/helpers/index.ts +182 -0
- package/src/helpers/mock-factory.ts +711 -0
- package/src/helpers/setup-teardown.ts +678 -0
- package/src/helpers/swarm-instance.ts +326 -0
- package/src/helpers/test-application.ts +310 -0
- package/src/helpers/test-utils.ts +670 -0
- package/src/index.ts +232 -0
- package/src/mocks/index.ts +29 -0
- package/src/mocks/mock-mcp-client.ts +723 -0
- package/src/mocks/mock-services.ts +793 -0
- package/src/regression/api-contract.ts +473 -0
- package/src/regression/index.ts +46 -0
- package/src/regression/integration-regression.ts +416 -0
- package/src/regression/performance-baseline.ts +356 -0
- package/src/regression/regression-runner.ts +339 -0
- package/src/regression/security-regression.ts +331 -0
- package/src/setup.ts +127 -0
- package/src/v2-compat/api-compat.test.ts +590 -0
- package/src/v2-compat/cli-compat.test.ts +484 -0
- package/src/v2-compat/compatibility-validator.ts +1072 -0
- package/src/v2-compat/hooks-compat.test.ts +602 -0
- package/src/v2-compat/index.ts +58 -0
- package/src/v2-compat/mcp-compat.test.ts +557 -0
- package/src/v2-compat/report-generator.ts +441 -0
- package/tmp.json +0 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +12 -0
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Performance Baseline System
|
|
3
|
+
*
|
|
4
|
+
* Captures and compares performance metrics to detect regressions.
|
|
5
|
+
*
|
|
6
|
+
* @module v3/testing/regression/performance-baseline
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { readFile, writeFile, mkdir } from 'fs/promises';
|
|
10
|
+
import { join, dirname } from 'path';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Baseline metric definition
|
|
14
|
+
*/
|
|
15
|
+
export interface BaselineMetric {
|
|
16
|
+
name: string;
|
|
17
|
+
value: number;
|
|
18
|
+
unit: string;
|
|
19
|
+
category: 'latency' | 'throughput' | 'memory' | 'cpu' | 'startup';
|
|
20
|
+
timestamp: number;
|
|
21
|
+
metadata?: Record<string, unknown>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Baseline comparison result
|
|
26
|
+
*/
|
|
27
|
+
export interface BaselineComparison {
|
|
28
|
+
metric: string;
|
|
29
|
+
baseline: number;
|
|
30
|
+
current: number;
|
|
31
|
+
unit: string;
|
|
32
|
+
degradation: number; // Percentage difference
|
|
33
|
+
regression: boolean; // Is this a significant regression?
|
|
34
|
+
improvement: boolean; // Is this an improvement?
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Baseline configuration
|
|
39
|
+
*/
|
|
40
|
+
export interface BaselineConfig {
|
|
41
|
+
baselinePath: string;
|
|
42
|
+
performanceThreshold: number; // Percentage allowed degradation
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Stored baseline data
|
|
47
|
+
*/
|
|
48
|
+
interface BaselineData {
|
|
49
|
+
version: string;
|
|
50
|
+
capturedAt: number;
|
|
51
|
+
metrics: BaselineMetric[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Performance Baseline Manager
|
|
56
|
+
*
|
|
57
|
+
* Manages performance baselines for regression detection.
|
|
58
|
+
*/
|
|
59
|
+
export class PerformanceBaseline {
|
|
60
|
+
private readonly baselinePath: string;
|
|
61
|
+
private readonly threshold: number;
|
|
62
|
+
private cachedBaseline: BaselineData | null = null;
|
|
63
|
+
|
|
64
|
+
constructor(config: BaselineConfig) {
|
|
65
|
+
this.baselinePath = join(config.baselinePath, 'performance.json');
|
|
66
|
+
this.threshold = config.performanceThreshold;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Capture current performance as baseline
|
|
71
|
+
*/
|
|
72
|
+
async captureBaseline(): Promise<BaselineData> {
|
|
73
|
+
const metrics = await this.measureCurrentPerformance();
|
|
74
|
+
|
|
75
|
+
const baseline: BaselineData = {
|
|
76
|
+
version: '1.0.0',
|
|
77
|
+
capturedAt: Date.now(),
|
|
78
|
+
metrics,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
await this.saveBaseline(baseline);
|
|
82
|
+
this.cachedBaseline = baseline;
|
|
83
|
+
|
|
84
|
+
return baseline;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Compare current performance against baseline
|
|
89
|
+
*/
|
|
90
|
+
async compare(): Promise<BaselineComparison[]> {
|
|
91
|
+
const baseline = await this.loadBaseline();
|
|
92
|
+
if (!baseline) {
|
|
93
|
+
console.warn('No baseline found. Capturing initial baseline...');
|
|
94
|
+
await this.captureBaseline();
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const currentMetrics = await this.measureCurrentPerformance();
|
|
99
|
+
const comparisons: BaselineComparison[] = [];
|
|
100
|
+
|
|
101
|
+
for (const current of currentMetrics) {
|
|
102
|
+
const baselineMetric = baseline.metrics.find((m) => m.name === current.name);
|
|
103
|
+
if (!baselineMetric) continue;
|
|
104
|
+
|
|
105
|
+
const isHigherBetter = current.category === 'throughput';
|
|
106
|
+
const diff = current.value - baselineMetric.value;
|
|
107
|
+
const percentChange = (diff / baselineMetric.value) * 100;
|
|
108
|
+
|
|
109
|
+
// For latency/memory, higher is worse. For throughput, lower is worse.
|
|
110
|
+
const degradation = isHigherBetter ? -percentChange : percentChange;
|
|
111
|
+
|
|
112
|
+
comparisons.push({
|
|
113
|
+
metric: current.name,
|
|
114
|
+
baseline: baselineMetric.value,
|
|
115
|
+
current: current.value,
|
|
116
|
+
unit: current.unit,
|
|
117
|
+
degradation,
|
|
118
|
+
regression: degradation > this.threshold,
|
|
119
|
+
improvement: degradation < -this.threshold,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return comparisons;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Measure current performance metrics
|
|
128
|
+
*/
|
|
129
|
+
private async measureCurrentPerformance(): Promise<BaselineMetric[]> {
|
|
130
|
+
const metrics: BaselineMetric[] = [];
|
|
131
|
+
const timestamp = Date.now();
|
|
132
|
+
|
|
133
|
+
// Memory metrics
|
|
134
|
+
const memUsage = process.memoryUsage();
|
|
135
|
+
metrics.push({
|
|
136
|
+
name: 'heap_used',
|
|
137
|
+
value: memUsage.heapUsed / 1024 / 1024,
|
|
138
|
+
unit: 'MB',
|
|
139
|
+
category: 'memory',
|
|
140
|
+
timestamp,
|
|
141
|
+
});
|
|
142
|
+
metrics.push({
|
|
143
|
+
name: 'heap_total',
|
|
144
|
+
value: memUsage.heapTotal / 1024 / 1024,
|
|
145
|
+
unit: 'MB',
|
|
146
|
+
category: 'memory',
|
|
147
|
+
timestamp,
|
|
148
|
+
});
|
|
149
|
+
metrics.push({
|
|
150
|
+
name: 'rss',
|
|
151
|
+
value: memUsage.rss / 1024 / 1024,
|
|
152
|
+
unit: 'MB',
|
|
153
|
+
category: 'memory',
|
|
154
|
+
timestamp,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Startup time simulation
|
|
158
|
+
const startupStart = performance.now();
|
|
159
|
+
await this.simulateStartup();
|
|
160
|
+
const startupTime = performance.now() - startupStart;
|
|
161
|
+
metrics.push({
|
|
162
|
+
name: 'startup_time',
|
|
163
|
+
value: startupTime,
|
|
164
|
+
unit: 'ms',
|
|
165
|
+
category: 'startup',
|
|
166
|
+
timestamp,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Latency benchmarks
|
|
170
|
+
const latencyMetrics = await this.measureLatency();
|
|
171
|
+
metrics.push(...latencyMetrics);
|
|
172
|
+
|
|
173
|
+
// Throughput benchmarks
|
|
174
|
+
const throughputMetrics = await this.measureThroughput();
|
|
175
|
+
metrics.push(...throughputMetrics);
|
|
176
|
+
|
|
177
|
+
return metrics;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Simulate startup to measure initialization time
|
|
182
|
+
*/
|
|
183
|
+
private async simulateStartup(): Promise<void> {
|
|
184
|
+
// Import key modules to simulate startup
|
|
185
|
+
await import('@sparkleideas/shared');
|
|
186
|
+
await import('@sparkleideas/memory');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Measure operation latency
|
|
191
|
+
*/
|
|
192
|
+
private async measureLatency(): Promise<BaselineMetric[]> {
|
|
193
|
+
const metrics: BaselineMetric[] = [];
|
|
194
|
+
const timestamp = Date.now();
|
|
195
|
+
|
|
196
|
+
// Event bus latency
|
|
197
|
+
const eventLatency = await this.benchmarkEventBus();
|
|
198
|
+
metrics.push({
|
|
199
|
+
name: 'event_bus_latency',
|
|
200
|
+
value: eventLatency,
|
|
201
|
+
unit: 'μs',
|
|
202
|
+
category: 'latency',
|
|
203
|
+
timestamp,
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// Memory operation latency
|
|
207
|
+
const memLatency = await this.benchmarkMemoryOps();
|
|
208
|
+
metrics.push({
|
|
209
|
+
name: 'memory_op_latency',
|
|
210
|
+
value: memLatency,
|
|
211
|
+
unit: 'μs',
|
|
212
|
+
category: 'latency',
|
|
213
|
+
timestamp,
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
return metrics;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Measure throughput
|
|
221
|
+
*/
|
|
222
|
+
private async measureThroughput(): Promise<BaselineMetric[]> {
|
|
223
|
+
const metrics: BaselineMetric[] = [];
|
|
224
|
+
const timestamp = Date.now();
|
|
225
|
+
|
|
226
|
+
// Events per second
|
|
227
|
+
const eventsPerSec = await this.benchmarkEventThroughput();
|
|
228
|
+
metrics.push({
|
|
229
|
+
name: 'events_per_second',
|
|
230
|
+
value: eventsPerSec,
|
|
231
|
+
unit: 'ops/sec',
|
|
232
|
+
category: 'throughput',
|
|
233
|
+
timestamp,
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// Memory operations per second
|
|
237
|
+
const memOpsPerSec = await this.benchmarkMemoryThroughput();
|
|
238
|
+
metrics.push({
|
|
239
|
+
name: 'memory_ops_per_second',
|
|
240
|
+
value: memOpsPerSec,
|
|
241
|
+
unit: 'ops/sec',
|
|
242
|
+
category: 'throughput',
|
|
243
|
+
timestamp,
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
return metrics;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Benchmark event bus operations
|
|
251
|
+
*/
|
|
252
|
+
private async benchmarkEventBus(): Promise<number> {
|
|
253
|
+
const { EventBus, createAgentSpawnedEvent } = await import('@sparkleideas/shared');
|
|
254
|
+
const eventBus = new EventBus();
|
|
255
|
+
|
|
256
|
+
const iterations = 1000;
|
|
257
|
+
const start = performance.now();
|
|
258
|
+
|
|
259
|
+
for (let i = 0; i < iterations; i++) {
|
|
260
|
+
const event = createAgentSpawnedEvent(`bench-${i}`, 'worker', 'default', ['test']);
|
|
261
|
+
await eventBus.emit(event);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const elapsed = performance.now() - start;
|
|
265
|
+
return (elapsed / iterations) * 1000; // Convert to microseconds
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Benchmark memory operations
|
|
270
|
+
*/
|
|
271
|
+
private async benchmarkMemoryOps(): Promise<number> {
|
|
272
|
+
// Simulate memory operations with a simple Map
|
|
273
|
+
const map = new Map<string, unknown>();
|
|
274
|
+
const iterations = 10000;
|
|
275
|
+
const start = performance.now();
|
|
276
|
+
|
|
277
|
+
for (let i = 0; i < iterations; i++) {
|
|
278
|
+
map.set(`key-${i}`, { data: `value-${i}`, timestamp: Date.now() });
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
for (let i = 0; i < iterations; i++) {
|
|
282
|
+
map.get(`key-${i}`);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const elapsed = performance.now() - start;
|
|
286
|
+
return (elapsed / (iterations * 2)) * 1000; // Convert to microseconds
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Benchmark event throughput
|
|
291
|
+
*/
|
|
292
|
+
private async benchmarkEventThroughput(): Promise<number> {
|
|
293
|
+
const { EventBus } = await import('@sparkleideas/shared');
|
|
294
|
+
const eventBus = new EventBus();
|
|
295
|
+
|
|
296
|
+
let count = 0;
|
|
297
|
+
eventBus.subscribe('agent:spawned', () => { count++; });
|
|
298
|
+
|
|
299
|
+
const { createAgentSpawnedEvent } = await import('@sparkleideas/shared');
|
|
300
|
+
const duration = 1000; // 1 second
|
|
301
|
+
const start = Date.now();
|
|
302
|
+
|
|
303
|
+
while (Date.now() - start < duration) {
|
|
304
|
+
const event = createAgentSpawnedEvent('bench-agent', 'worker', 'default', ['test']);
|
|
305
|
+
await eventBus.emit(event);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return count;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Benchmark memory throughput
|
|
313
|
+
*/
|
|
314
|
+
private async benchmarkMemoryThroughput(): Promise<number> {
|
|
315
|
+
const map = new Map<string, unknown>();
|
|
316
|
+
let count = 0;
|
|
317
|
+
|
|
318
|
+
const duration = 1000; // 1 second
|
|
319
|
+
const start = Date.now();
|
|
320
|
+
|
|
321
|
+
while (Date.now() - start < duration) {
|
|
322
|
+
const key = `key-${count}`;
|
|
323
|
+
map.set(key, { data: count, timestamp: Date.now() });
|
|
324
|
+
map.get(key);
|
|
325
|
+
map.delete(key);
|
|
326
|
+
count++;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return count;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Load baseline from file
|
|
334
|
+
*/
|
|
335
|
+
private async loadBaseline(): Promise<BaselineData | null> {
|
|
336
|
+
if (this.cachedBaseline) {
|
|
337
|
+
return this.cachedBaseline;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
try {
|
|
341
|
+
const content = await readFile(this.baselinePath, 'utf-8');
|
|
342
|
+
this.cachedBaseline = JSON.parse(content);
|
|
343
|
+
return this.cachedBaseline;
|
|
344
|
+
} catch {
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Save baseline to file
|
|
351
|
+
*/
|
|
352
|
+
private async saveBaseline(baseline: BaselineData): Promise<void> {
|
|
353
|
+
await mkdir(dirname(this.baselinePath), { recursive: true });
|
|
354
|
+
await writeFile(this.baselinePath, JSON.stringify(baseline, null, 2));
|
|
355
|
+
}
|
|
356
|
+
}
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression Test Runner
|
|
3
|
+
*
|
|
4
|
+
* Orchestrates all regression tests and generates comprehensive reports.
|
|
5
|
+
*
|
|
6
|
+
* @module v3/testing/regression/regression-runner
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { PerformanceBaseline, type BaselineComparison } from './performance-baseline.js';
|
|
10
|
+
import { SecurityRegressionChecker, type SecurityReport } from './security-regression.js';
|
|
11
|
+
import { APIContractValidator, type ContractValidation } from './api-contract.js';
|
|
12
|
+
import { IntegrationRegressionSuite, type IntegrationResult } from './integration-regression.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Regression test configuration
|
|
16
|
+
*/
|
|
17
|
+
export interface RegressionConfig {
|
|
18
|
+
/** Enable performance regression tests */
|
|
19
|
+
performanceTests: boolean;
|
|
20
|
+
|
|
21
|
+
/** Enable security regression tests */
|
|
22
|
+
securityTests: boolean;
|
|
23
|
+
|
|
24
|
+
/** Enable API contract tests */
|
|
25
|
+
contractTests: boolean;
|
|
26
|
+
|
|
27
|
+
/** Enable integration tests */
|
|
28
|
+
integrationTests: boolean;
|
|
29
|
+
|
|
30
|
+
/** Performance threshold (percentage allowed degradation) */
|
|
31
|
+
performanceThreshold: number;
|
|
32
|
+
|
|
33
|
+
/** Path to baseline data */
|
|
34
|
+
baselinePath: string;
|
|
35
|
+
|
|
36
|
+
/** Output path for reports */
|
|
37
|
+
reportPath: string;
|
|
38
|
+
|
|
39
|
+
/** Fail on any regression */
|
|
40
|
+
failOnRegression: boolean;
|
|
41
|
+
|
|
42
|
+
/** Verbose output */
|
|
43
|
+
verbose: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Individual test result
|
|
48
|
+
*/
|
|
49
|
+
export interface RegressionResult {
|
|
50
|
+
category: 'performance' | 'security' | 'contract' | 'integration';
|
|
51
|
+
name: string;
|
|
52
|
+
passed: boolean;
|
|
53
|
+
message: string;
|
|
54
|
+
details?: Record<string, unknown>;
|
|
55
|
+
duration: number;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Complete regression report
|
|
60
|
+
*/
|
|
61
|
+
export interface RegressionReport {
|
|
62
|
+
timestamp: Date;
|
|
63
|
+
duration: number;
|
|
64
|
+
passed: boolean;
|
|
65
|
+
totalTests: number;
|
|
66
|
+
passedTests: number;
|
|
67
|
+
failedTests: number;
|
|
68
|
+
skippedTests: number;
|
|
69
|
+
|
|
70
|
+
performance: {
|
|
71
|
+
tested: boolean;
|
|
72
|
+
results: BaselineComparison[];
|
|
73
|
+
regressions: string[];
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
security: {
|
|
77
|
+
tested: boolean;
|
|
78
|
+
report: SecurityReport | null;
|
|
79
|
+
newVulnerabilities: string[];
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
contract: {
|
|
83
|
+
tested: boolean;
|
|
84
|
+
results: ContractValidation[];
|
|
85
|
+
breakingChanges: string[];
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
integration: {
|
|
89
|
+
tested: boolean;
|
|
90
|
+
results: IntegrationResult[];
|
|
91
|
+
failures: string[];
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
summary: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Default configuration
|
|
99
|
+
*/
|
|
100
|
+
const DEFAULT_CONFIG: RegressionConfig = {
|
|
101
|
+
performanceTests: true,
|
|
102
|
+
securityTests: true,
|
|
103
|
+
contractTests: true,
|
|
104
|
+
integrationTests: true,
|
|
105
|
+
performanceThreshold: 10, // 10% degradation allowed
|
|
106
|
+
baselinePath: './.regression-baselines',
|
|
107
|
+
reportPath: './.regression-reports',
|
|
108
|
+
failOnRegression: true,
|
|
109
|
+
verbose: false,
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Regression Test Runner
|
|
114
|
+
*
|
|
115
|
+
* Coordinates all regression testing activities.
|
|
116
|
+
*/
|
|
117
|
+
export class RegressionTestRunner {
|
|
118
|
+
private readonly config: RegressionConfig;
|
|
119
|
+
private readonly performanceBaseline: PerformanceBaseline;
|
|
120
|
+
private readonly securityChecker: SecurityRegressionChecker;
|
|
121
|
+
private readonly contractValidator: APIContractValidator;
|
|
122
|
+
private readonly integrationSuite: IntegrationRegressionSuite;
|
|
123
|
+
|
|
124
|
+
constructor(config: Partial<RegressionConfig> = {}) {
|
|
125
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
126
|
+
this.performanceBaseline = new PerformanceBaseline(this.config);
|
|
127
|
+
this.securityChecker = new SecurityRegressionChecker();
|
|
128
|
+
this.contractValidator = new APIContractValidator();
|
|
129
|
+
this.integrationSuite = new IntegrationRegressionSuite();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Run all configured regression tests
|
|
134
|
+
*/
|
|
135
|
+
async runAll(): Promise<RegressionReport> {
|
|
136
|
+
const startTime = Date.now();
|
|
137
|
+
const results: RegressionResult[] = [];
|
|
138
|
+
|
|
139
|
+
const report: RegressionReport = {
|
|
140
|
+
timestamp: new Date(),
|
|
141
|
+
duration: 0,
|
|
142
|
+
passed: true,
|
|
143
|
+
totalTests: 0,
|
|
144
|
+
passedTests: 0,
|
|
145
|
+
failedTests: 0,
|
|
146
|
+
skippedTests: 0,
|
|
147
|
+
performance: { tested: false, results: [], regressions: [] },
|
|
148
|
+
security: { tested: false, report: null, newVulnerabilities: [] },
|
|
149
|
+
contract: { tested: false, results: [], breakingChanges: [] },
|
|
150
|
+
integration: { tested: false, results: [], failures: [] },
|
|
151
|
+
summary: '',
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// Run performance tests
|
|
155
|
+
if (this.config.performanceTests) {
|
|
156
|
+
this.log('Running performance regression tests...');
|
|
157
|
+
report.performance.tested = true;
|
|
158
|
+
report.performance.results = await this.performanceBaseline.compare();
|
|
159
|
+
report.performance.regressions = report.performance.results
|
|
160
|
+
.filter((r) => r.regression && r.degradation > this.config.performanceThreshold)
|
|
161
|
+
.map((r) => `${r.metric}: ${r.degradation.toFixed(1)}% degradation`);
|
|
162
|
+
|
|
163
|
+
if (report.performance.regressions.length > 0) {
|
|
164
|
+
report.passed = false;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Run security tests
|
|
169
|
+
if (this.config.securityTests) {
|
|
170
|
+
this.log('Running security regression tests...');
|
|
171
|
+
report.security.tested = true;
|
|
172
|
+
report.security.report = await this.securityChecker.check();
|
|
173
|
+
report.security.newVulnerabilities = report.security.report?.newIssues || [];
|
|
174
|
+
|
|
175
|
+
if (report.security.newVulnerabilities.length > 0) {
|
|
176
|
+
report.passed = false;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Run contract tests
|
|
181
|
+
if (this.config.contractTests) {
|
|
182
|
+
this.log('Running API contract tests...');
|
|
183
|
+
report.contract.tested = true;
|
|
184
|
+
report.contract.results = await this.contractValidator.validateAll();
|
|
185
|
+
report.contract.breakingChanges = report.contract.results
|
|
186
|
+
.filter((r) => !r.valid && r.breaking)
|
|
187
|
+
.map((r) => `${r.endpoint}: ${r.message}`);
|
|
188
|
+
|
|
189
|
+
if (report.contract.breakingChanges.length > 0) {
|
|
190
|
+
report.passed = false;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Run integration tests
|
|
195
|
+
if (this.config.integrationTests) {
|
|
196
|
+
this.log('Running integration regression tests...');
|
|
197
|
+
report.integration.tested = true;
|
|
198
|
+
report.integration.results = await this.integrationSuite.runAll();
|
|
199
|
+
report.integration.failures = report.integration.results
|
|
200
|
+
.filter((r) => !r.passed)
|
|
201
|
+
.map((r) => `${r.name}: ${r.error}`);
|
|
202
|
+
|
|
203
|
+
if (report.integration.failures.length > 0) {
|
|
204
|
+
report.passed = false;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Calculate summary
|
|
209
|
+
report.duration = Date.now() - startTime;
|
|
210
|
+
report.totalTests =
|
|
211
|
+
report.performance.results.length +
|
|
212
|
+
(report.security.report?.checks.length || 0) +
|
|
213
|
+
report.contract.results.length +
|
|
214
|
+
report.integration.results.length;
|
|
215
|
+
|
|
216
|
+
report.passedTests = report.totalTests - (
|
|
217
|
+
report.performance.regressions.length +
|
|
218
|
+
report.security.newVulnerabilities.length +
|
|
219
|
+
report.contract.breakingChanges.length +
|
|
220
|
+
report.integration.failures.length
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
report.failedTests = report.totalTests - report.passedTests;
|
|
224
|
+
|
|
225
|
+
report.summary = this.generateSummary(report);
|
|
226
|
+
|
|
227
|
+
return report;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Run only performance regression tests
|
|
232
|
+
*/
|
|
233
|
+
async runPerformance(): Promise<BaselineComparison[]> {
|
|
234
|
+
return this.performanceBaseline.compare();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Run only security regression tests
|
|
239
|
+
*/
|
|
240
|
+
async runSecurity(): Promise<SecurityReport> {
|
|
241
|
+
return this.securityChecker.check();
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Run only API contract tests
|
|
246
|
+
*/
|
|
247
|
+
async runContracts(): Promise<ContractValidation[]> {
|
|
248
|
+
return this.contractValidator.validateAll();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Run only integration tests
|
|
253
|
+
*/
|
|
254
|
+
async runIntegration(): Promise<IntegrationResult[]> {
|
|
255
|
+
return this.integrationSuite.runAll();
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Update baselines with current values
|
|
260
|
+
*/
|
|
261
|
+
async updateBaselines(): Promise<void> {
|
|
262
|
+
await this.performanceBaseline.captureBaseline();
|
|
263
|
+
await this.contractValidator.captureContracts();
|
|
264
|
+
this.log('Baselines updated successfully');
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Generate human-readable summary
|
|
269
|
+
*/
|
|
270
|
+
private generateSummary(report: RegressionReport): string {
|
|
271
|
+
const lines: string[] = [
|
|
272
|
+
'═══════════════════════════════════════════════════════════════',
|
|
273
|
+
' REGRESSION TEST REPORT ',
|
|
274
|
+
'═══════════════════════════════════════════════════════════════',
|
|
275
|
+
'',
|
|
276
|
+
`Status: ${report.passed ? '✅ PASSED' : '❌ FAILED'}`,
|
|
277
|
+
`Duration: ${report.duration}ms`,
|
|
278
|
+
`Tests: ${report.passedTests}/${report.totalTests} passed`,
|
|
279
|
+
'',
|
|
280
|
+
];
|
|
281
|
+
|
|
282
|
+
if (report.performance.tested) {
|
|
283
|
+
lines.push('📊 Performance:');
|
|
284
|
+
if (report.performance.regressions.length === 0) {
|
|
285
|
+
lines.push(' ✅ No performance regressions detected');
|
|
286
|
+
} else {
|
|
287
|
+
lines.push(` ❌ ${report.performance.regressions.length} regressions:`);
|
|
288
|
+
report.performance.regressions.forEach((r) => lines.push(` - ${r}`));
|
|
289
|
+
}
|
|
290
|
+
lines.push('');
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (report.security.tested) {
|
|
294
|
+
lines.push('🔒 Security:');
|
|
295
|
+
if (report.security.newVulnerabilities.length === 0) {
|
|
296
|
+
lines.push(' ✅ No new security vulnerabilities');
|
|
297
|
+
} else {
|
|
298
|
+
lines.push(` ❌ ${report.security.newVulnerabilities.length} new vulnerabilities:`);
|
|
299
|
+
report.security.newVulnerabilities.forEach((v) => lines.push(` - ${v}`));
|
|
300
|
+
}
|
|
301
|
+
lines.push('');
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (report.contract.tested) {
|
|
305
|
+
lines.push('📋 API Contracts:');
|
|
306
|
+
if (report.contract.breakingChanges.length === 0) {
|
|
307
|
+
lines.push(' ✅ No breaking changes detected');
|
|
308
|
+
} else {
|
|
309
|
+
lines.push(` ❌ ${report.contract.breakingChanges.length} breaking changes:`);
|
|
310
|
+
report.contract.breakingChanges.forEach((c) => lines.push(` - ${c}`));
|
|
311
|
+
}
|
|
312
|
+
lines.push('');
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (report.integration.tested) {
|
|
316
|
+
lines.push('🔗 Integration:');
|
|
317
|
+
if (report.integration.failures.length === 0) {
|
|
318
|
+
lines.push(' ✅ All integration tests passed');
|
|
319
|
+
} else {
|
|
320
|
+
lines.push(` ❌ ${report.integration.failures.length} failures:`);
|
|
321
|
+
report.integration.failures.forEach((f) => lines.push(` - ${f}`));
|
|
322
|
+
}
|
|
323
|
+
lines.push('');
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
lines.push('═══════════════════════════════════════════════════════════════');
|
|
327
|
+
|
|
328
|
+
return lines.join('\n');
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Log message if verbose mode is enabled
|
|
333
|
+
*/
|
|
334
|
+
private log(message: string): void {
|
|
335
|
+
if (this.config.verbose) {
|
|
336
|
+
console.log(`[Regression] ${message}`);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|