@sparkleideas/testing 3.0.0-alpha.10

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.
Files changed (42) hide show
  1. package/README.md +547 -0
  2. package/__tests__/framework.test.ts +21 -0
  3. package/package.json +61 -0
  4. package/src/fixtures/agent-fixtures.ts +793 -0
  5. package/src/fixtures/agents.ts +212 -0
  6. package/src/fixtures/configurations.ts +491 -0
  7. package/src/fixtures/index.ts +21 -0
  8. package/src/fixtures/mcp-fixtures.ts +1030 -0
  9. package/src/fixtures/memory-entries.ts +328 -0
  10. package/src/fixtures/memory-fixtures.ts +750 -0
  11. package/src/fixtures/swarm-fixtures.ts +837 -0
  12. package/src/fixtures/tasks.ts +309 -0
  13. package/src/helpers/assertion-helpers.ts +616 -0
  14. package/src/helpers/assertions.ts +286 -0
  15. package/src/helpers/create-mock.ts +200 -0
  16. package/src/helpers/index.ts +182 -0
  17. package/src/helpers/mock-factory.ts +711 -0
  18. package/src/helpers/setup-teardown.ts +678 -0
  19. package/src/helpers/swarm-instance.ts +326 -0
  20. package/src/helpers/test-application.ts +310 -0
  21. package/src/helpers/test-utils.ts +670 -0
  22. package/src/index.ts +232 -0
  23. package/src/mocks/index.ts +29 -0
  24. package/src/mocks/mock-mcp-client.ts +723 -0
  25. package/src/mocks/mock-services.ts +793 -0
  26. package/src/regression/api-contract.ts +473 -0
  27. package/src/regression/index.ts +46 -0
  28. package/src/regression/integration-regression.ts +416 -0
  29. package/src/regression/performance-baseline.ts +356 -0
  30. package/src/regression/regression-runner.ts +339 -0
  31. package/src/regression/security-regression.ts +331 -0
  32. package/src/setup.ts +127 -0
  33. package/src/v2-compat/api-compat.test.ts +590 -0
  34. package/src/v2-compat/cli-compat.test.ts +484 -0
  35. package/src/v2-compat/compatibility-validator.ts +1072 -0
  36. package/src/v2-compat/hooks-compat.test.ts +602 -0
  37. package/src/v2-compat/index.ts +58 -0
  38. package/src/v2-compat/mcp-compat.test.ts +557 -0
  39. package/src/v2-compat/report-generator.ts +441 -0
  40. package/tmp.json +0 -0
  41. package/tsconfig.json +20 -0
  42. 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
+ }