@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,678 @@
1
+ /**
2
+ * @sparkleideas/testing - Setup & Teardown Helpers
3
+ *
4
+ * Global setup and teardown utilities for V3 module testing.
5
+ * Provides test isolation, resource cleanup, and environment management.
6
+ */
7
+ import { vi, beforeEach, afterEach, beforeAll, afterAll } from 'vitest';
8
+
9
+ /**
10
+ * Setup context for managing test resources
11
+ */
12
+ export interface SetupContext {
13
+ /**
14
+ * Register a cleanup function to be called during teardown
15
+ */
16
+ addCleanup(cleanup: CleanupFunction): void;
17
+
18
+ /**
19
+ * Register a resource that needs to be closed/disposed
20
+ */
21
+ registerResource<T extends Disposable>(resource: T): T;
22
+
23
+ /**
24
+ * Get a registered resource by name
25
+ */
26
+ getResource<T>(name: string): T | undefined;
27
+
28
+ /**
29
+ * Set a named resource
30
+ */
31
+ setResource<T>(name: string, resource: T): void;
32
+
33
+ /**
34
+ * Run all cleanup functions
35
+ */
36
+ runCleanup(): Promise<void>;
37
+ }
38
+
39
+ /**
40
+ * Cleanup function type
41
+ */
42
+ export type CleanupFunction = () => void | Promise<void>;
43
+
44
+ /**
45
+ * Disposable interface
46
+ */
47
+ export interface Disposable {
48
+ dispose?(): void | Promise<void>;
49
+ close?(): void | Promise<void>;
50
+ destroy?(): void | Promise<void>;
51
+ shutdown?(): void | Promise<void>;
52
+ }
53
+
54
+ /**
55
+ * Create a setup context for managing test resources
56
+ *
57
+ * @example
58
+ * const ctx = createSetupContext();
59
+ * ctx.addCleanup(() => server.close());
60
+ * ctx.registerResource(database);
61
+ * // ... run tests
62
+ * await ctx.runCleanup();
63
+ */
64
+ export function createSetupContext(): SetupContext {
65
+ const cleanups: CleanupFunction[] = [];
66
+ const resources = new Map<string, unknown>();
67
+ const disposables: Disposable[] = [];
68
+
69
+ return {
70
+ addCleanup(cleanup: CleanupFunction): void {
71
+ cleanups.push(cleanup);
72
+ },
73
+
74
+ registerResource<T extends Disposable>(resource: T): T {
75
+ disposables.push(resource);
76
+ return resource;
77
+ },
78
+
79
+ getResource<T>(name: string): T | undefined {
80
+ return resources.get(name) as T | undefined;
81
+ },
82
+
83
+ setResource<T>(name: string, resource: T): void {
84
+ resources.set(name, resource);
85
+ },
86
+
87
+ async runCleanup(): Promise<void> {
88
+ // Run cleanups in reverse order
89
+ for (const cleanup of cleanups.reverse()) {
90
+ try {
91
+ await cleanup();
92
+ } catch (error) {
93
+ console.error('Cleanup error:', error);
94
+ }
95
+ }
96
+
97
+ // Dispose resources
98
+ for (const resource of disposables) {
99
+ try {
100
+ if (resource.dispose) {
101
+ await resource.dispose();
102
+ } else if (resource.close) {
103
+ await resource.close();
104
+ } else if (resource.destroy) {
105
+ await resource.destroy();
106
+ } else if (resource.shutdown) {
107
+ await resource.shutdown();
108
+ }
109
+ } catch (error) {
110
+ console.error('Resource disposal error:', error);
111
+ }
112
+ }
113
+
114
+ cleanups.length = 0;
115
+ resources.clear();
116
+ disposables.length = 0;
117
+ },
118
+ };
119
+ }
120
+
121
+ /**
122
+ * Global test context that persists across test files
123
+ */
124
+ let globalContext: SetupContext | null = null;
125
+
126
+ /**
127
+ * Get or create the global test context
128
+ */
129
+ export function getGlobalContext(): SetupContext {
130
+ if (!globalContext) {
131
+ globalContext = createSetupContext();
132
+ }
133
+ return globalContext;
134
+ }
135
+
136
+ /**
137
+ * Reset the global test context
138
+ */
139
+ export async function resetGlobalContext(): Promise<void> {
140
+ if (globalContext) {
141
+ await globalContext.runCleanup();
142
+ globalContext = null;
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Test environment configuration
148
+ */
149
+ export interface TestEnvironmentConfig {
150
+ /**
151
+ * Reset all mocks before each test
152
+ */
153
+ resetMocks?: boolean;
154
+
155
+ /**
156
+ * Use fake timers
157
+ */
158
+ fakeTimers?: boolean;
159
+
160
+ /**
161
+ * Initial fake time
162
+ */
163
+ initialTime?: Date | number;
164
+
165
+ /**
166
+ * Environment variables to set
167
+ */
168
+ env?: Record<string, string>;
169
+
170
+ /**
171
+ * Suppress console output during tests
172
+ */
173
+ suppressConsole?: boolean | ('log' | 'warn' | 'error' | 'info')[];
174
+
175
+ /**
176
+ * Timeout for async operations
177
+ */
178
+ timeout?: number;
179
+ }
180
+
181
+ /**
182
+ * Configure test environment with standard settings
183
+ *
184
+ * @example
185
+ * configureTestEnvironment({
186
+ * resetMocks: true,
187
+ * fakeTimers: true,
188
+ * suppressConsole: ['log', 'warn'],
189
+ * });
190
+ */
191
+ export function configureTestEnvironment(config: TestEnvironmentConfig = {}): void {
192
+ const {
193
+ resetMocks = true,
194
+ fakeTimers = false,
195
+ initialTime,
196
+ env = {},
197
+ suppressConsole = false,
198
+ } = config;
199
+
200
+ const originalEnv: Record<string, string | undefined> = {};
201
+ const originalConsole: Partial<Console> = {};
202
+
203
+ beforeAll(() => {
204
+ // Set environment variables
205
+ for (const [key, value] of Object.entries(env)) {
206
+ originalEnv[key] = process.env[key];
207
+ process.env[key] = value;
208
+ }
209
+
210
+ // Suppress console
211
+ if (suppressConsole) {
212
+ const methods = suppressConsole === true
213
+ ? ['log', 'warn', 'error', 'info'] as const
214
+ : suppressConsole;
215
+
216
+ for (const method of methods) {
217
+ originalConsole[method] = console[method];
218
+ console[method] = vi.fn();
219
+ }
220
+ }
221
+ });
222
+
223
+ afterAll(() => {
224
+ // Restore environment variables
225
+ for (const [key, value] of Object.entries(originalEnv)) {
226
+ if (value === undefined) {
227
+ delete process.env[key];
228
+ } else {
229
+ process.env[key] = value;
230
+ }
231
+ }
232
+
233
+ // Restore console
234
+ for (const [method, original] of Object.entries(originalConsole)) {
235
+ if (original) {
236
+ (console as unknown as Record<string, unknown>)[method] = original;
237
+ }
238
+ }
239
+ });
240
+
241
+ beforeEach(() => {
242
+ if (resetMocks) {
243
+ vi.clearAllMocks();
244
+ }
245
+
246
+ if (fakeTimers) {
247
+ vi.useFakeTimers();
248
+ if (initialTime) {
249
+ vi.setSystemTime(initialTime);
250
+ }
251
+ }
252
+ });
253
+
254
+ afterEach(() => {
255
+ if (fakeTimers) {
256
+ vi.useRealTimers();
257
+ }
258
+
259
+ vi.restoreAllMocks();
260
+ });
261
+ }
262
+
263
+ /**
264
+ * Create a test suite with automatic setup/teardown
265
+ *
266
+ * @example
267
+ * const { beforeEachTest, afterEachTest, getContext } = createTestSuite({
268
+ * resetMocks: true,
269
+ * });
270
+ */
271
+ export function createTestSuite(config: TestEnvironmentConfig = {}): TestSuiteHelpers {
272
+ const context = createSetupContext();
273
+
274
+ configureTestEnvironment(config);
275
+
276
+ return {
277
+ beforeEachTest: (fn: (ctx: SetupContext) => void | Promise<void>) => {
278
+ beforeEach(async () => {
279
+ await fn(context);
280
+ });
281
+ },
282
+
283
+ afterEachTest: (fn: (ctx: SetupContext) => void | Promise<void>) => {
284
+ afterEach(async () => {
285
+ await fn(context);
286
+ await context.runCleanup();
287
+ });
288
+ },
289
+
290
+ getContext: () => context,
291
+ };
292
+ }
293
+
294
+ /**
295
+ * Test suite helpers interface
296
+ */
297
+ export interface TestSuiteHelpers {
298
+ beforeEachTest: (fn: (ctx: SetupContext) => void | Promise<void>) => void;
299
+ afterEachTest: (fn: (ctx: SetupContext) => void | Promise<void>) => void;
300
+ getContext: () => SetupContext;
301
+ }
302
+
303
+ /**
304
+ * Create isolated test scope
305
+ *
306
+ * @example
307
+ * const scope = createTestScope();
308
+ * scope.addMock(mockService);
309
+ * await scope.run(async () => {
310
+ * // test code
311
+ * });
312
+ */
313
+ export function createTestScope(): TestScope {
314
+ const mocks: ReturnType<typeof vi.fn>[] = [];
315
+ const cleanups: CleanupFunction[] = [];
316
+
317
+ return {
318
+ addMock<T extends ReturnType<typeof vi.fn>>(mock: T): T {
319
+ mocks.push(mock);
320
+ return mock;
321
+ },
322
+
323
+ addCleanup(cleanup: CleanupFunction): void {
324
+ cleanups.push(cleanup);
325
+ },
326
+
327
+ async run<T>(fn: () => Promise<T>): Promise<T> {
328
+ try {
329
+ return await fn();
330
+ } finally {
331
+ // Clear all mocks
332
+ for (const mock of mocks) {
333
+ mock.mockClear();
334
+ }
335
+
336
+ // Run cleanups
337
+ for (const cleanup of cleanups.reverse()) {
338
+ await cleanup();
339
+ }
340
+ }
341
+ },
342
+
343
+ clear(): void {
344
+ for (const mock of mocks) {
345
+ mock.mockClear();
346
+ }
347
+ },
348
+
349
+ reset(): void {
350
+ for (const mock of mocks) {
351
+ mock.mockReset();
352
+ }
353
+ },
354
+ };
355
+ }
356
+
357
+ /**
358
+ * Test scope interface
359
+ */
360
+ export interface TestScope {
361
+ addMock<T extends ReturnType<typeof vi.fn>>(mock: T): T;
362
+ addCleanup(cleanup: CleanupFunction): void;
363
+ run<T>(fn: () => Promise<T>): Promise<T>;
364
+ clear(): void;
365
+ reset(): void;
366
+ }
367
+
368
+ /**
369
+ * Database test helper for memory/agentdb testing
370
+ */
371
+ export interface DatabaseTestHelper {
372
+ setup(): Promise<void>;
373
+ teardown(): Promise<void>;
374
+ clear(): Promise<void>;
375
+ seed(data: Record<string, unknown[]>): Promise<void>;
376
+ }
377
+
378
+ /**
379
+ * Create in-memory database helper for testing
380
+ *
381
+ * @example
382
+ * const db = createInMemoryDatabaseHelper();
383
+ * await db.setup();
384
+ * await db.seed({ users: [{ id: 1, name: 'Test' }] });
385
+ * // ... run tests
386
+ * await db.teardown();
387
+ */
388
+ export function createInMemoryDatabaseHelper(): DatabaseTestHelper {
389
+ const data = new Map<string, unknown[]>();
390
+
391
+ return {
392
+ async setup(): Promise<void> {
393
+ data.clear();
394
+ },
395
+
396
+ async teardown(): Promise<void> {
397
+ data.clear();
398
+ },
399
+
400
+ async clear(): Promise<void> {
401
+ data.clear();
402
+ },
403
+
404
+ async seed(seedData: Record<string, unknown[]>): Promise<void> {
405
+ for (const [table, records] of Object.entries(seedData)) {
406
+ data.set(table, [...records]);
407
+ }
408
+ },
409
+ };
410
+ }
411
+
412
+ /**
413
+ * Network test helper for mocking HTTP/WebSocket
414
+ */
415
+ export interface NetworkTestHelper {
416
+ mockFetch(responses: MockFetchResponse[]): void;
417
+ mockWebSocket(handler: (message: unknown) => unknown): void;
418
+ clearMocks(): void;
419
+ }
420
+
421
+ /**
422
+ * Mock fetch response
423
+ */
424
+ export interface MockFetchResponse {
425
+ url: string | RegExp;
426
+ method?: string;
427
+ status?: number;
428
+ body?: unknown;
429
+ headers?: Record<string, string>;
430
+ delay?: number;
431
+ }
432
+
433
+ /**
434
+ * Create network test helper
435
+ *
436
+ * @example
437
+ * const network = createNetworkTestHelper();
438
+ * network.mockFetch([
439
+ * { url: '/api/users', body: [{ id: 1 }] },
440
+ * ]);
441
+ */
442
+ export function createNetworkTestHelper(): NetworkTestHelper {
443
+ const fetchResponses: MockFetchResponse[] = [];
444
+ let originalFetch: typeof global.fetch;
445
+
446
+ return {
447
+ mockFetch(responses: MockFetchResponse[]): void {
448
+ fetchResponses.push(...responses);
449
+
450
+ if (!originalFetch) {
451
+ originalFetch = global.fetch;
452
+
453
+ global.fetch = vi.fn(async (input: string | URL | Request, init?: RequestInit) => {
454
+ const url = typeof input === 'string' ? input : input.toString();
455
+ const method = init?.method ?? 'GET';
456
+
457
+ const match = fetchResponses.find(r => {
458
+ const urlMatch = typeof r.url === 'string'
459
+ ? url.includes(r.url)
460
+ : r.url.test(url);
461
+ const methodMatch = !r.method || r.method === method;
462
+ return urlMatch && methodMatch;
463
+ });
464
+
465
+ if (!match) {
466
+ throw new Error(`No mock found for ${method} ${url}`);
467
+ }
468
+
469
+ if (match.delay) {
470
+ await new Promise(resolve => setTimeout(resolve, match.delay));
471
+ }
472
+
473
+ return new Response(JSON.stringify(match.body), {
474
+ status: match.status ?? 200,
475
+ headers: match.headers ?? { 'Content-Type': 'application/json' },
476
+ });
477
+ });
478
+ }
479
+ },
480
+
481
+ mockWebSocket(handler: (message: unknown) => unknown): void {
482
+ // WebSocket mocking would require more setup
483
+ // This is a placeholder for the interface
484
+ console.warn('WebSocket mocking not yet implemented');
485
+ },
486
+
487
+ clearMocks(): void {
488
+ fetchResponses.length = 0;
489
+ if (originalFetch) {
490
+ global.fetch = originalFetch;
491
+ }
492
+ },
493
+ };
494
+ }
495
+
496
+ /**
497
+ * File system test helper
498
+ */
499
+ export interface FileSystemTestHelper {
500
+ createTempDir(): Promise<string>;
501
+ createFile(path: string, content: string): Promise<void>;
502
+ readFile(path: string): Promise<string>;
503
+ cleanup(): Promise<void>;
504
+ }
505
+
506
+ /**
507
+ * Create in-memory file system helper
508
+ *
509
+ * @example
510
+ * const fs = createInMemoryFileSystemHelper();
511
+ * await fs.createFile('/test.txt', 'content');
512
+ * const content = await fs.readFile('/test.txt');
513
+ */
514
+ export function createInMemoryFileSystemHelper(): FileSystemTestHelper {
515
+ const files = new Map<string, string>();
516
+ const tempDirs: string[] = [];
517
+
518
+ return {
519
+ async createTempDir(): Promise<string> {
520
+ const dir = `/tmp/test-${Date.now()}-${Math.random().toString(36).slice(2)}`;
521
+ tempDirs.push(dir);
522
+ return dir;
523
+ },
524
+
525
+ async createFile(path: string, content: string): Promise<void> {
526
+ files.set(path, content);
527
+ },
528
+
529
+ async readFile(path: string): Promise<string> {
530
+ const content = files.get(path);
531
+ if (content === undefined) {
532
+ throw new Error(`File not found: ${path}`);
533
+ }
534
+ return content;
535
+ },
536
+
537
+ async cleanup(): Promise<void> {
538
+ files.clear();
539
+ tempDirs.length = 0;
540
+ },
541
+ };
542
+ }
543
+
544
+ /**
545
+ * Performance test helper
546
+ */
547
+ export interface PerformanceTestHelper {
548
+ startMeasurement(name: string): void;
549
+ endMeasurement(name: string): number;
550
+ getMeasurements(): Record<string, number[]>;
551
+ getStats(name: string): { min: number; max: number; avg: number; p95: number };
552
+ clear(): void;
553
+ }
554
+
555
+ /**
556
+ * Create performance test helper
557
+ *
558
+ * @example
559
+ * const perf = createPerformanceTestHelper();
560
+ * perf.startMeasurement('search');
561
+ * await search();
562
+ * const duration = perf.endMeasurement('search');
563
+ */
564
+ export function createPerformanceTestHelper(): PerformanceTestHelper {
565
+ const measurements = new Map<string, number[]>();
566
+ const starts = new Map<string, number>();
567
+
568
+ return {
569
+ startMeasurement(name: string): void {
570
+ starts.set(name, performance.now());
571
+ },
572
+
573
+ endMeasurement(name: string): number {
574
+ const start = starts.get(name);
575
+ if (start === undefined) {
576
+ throw new Error(`No measurement started for: ${name}`);
577
+ }
578
+
579
+ const duration = performance.now() - start;
580
+ starts.delete(name);
581
+
582
+ if (!measurements.has(name)) {
583
+ measurements.set(name, []);
584
+ }
585
+ measurements.get(name)!.push(duration);
586
+
587
+ return duration;
588
+ },
589
+
590
+ getMeasurements(): Record<string, number[]> {
591
+ return Object.fromEntries(measurements);
592
+ },
593
+
594
+ getStats(name: string): { min: number; max: number; avg: number; p95: number } {
595
+ const values = measurements.get(name);
596
+ if (!values || values.length === 0) {
597
+ return { min: 0, max: 0, avg: 0, p95: 0 };
598
+ }
599
+
600
+ const sorted = [...values].sort((a, b) => a - b);
601
+ const sum = sorted.reduce((a, b) => a + b, 0);
602
+
603
+ return {
604
+ min: sorted[0],
605
+ max: sorted[sorted.length - 1],
606
+ avg: sum / sorted.length,
607
+ p95: sorted[Math.floor(sorted.length * 0.95)],
608
+ };
609
+ },
610
+
611
+ clear(): void {
612
+ measurements.clear();
613
+ starts.clear();
614
+ },
615
+ };
616
+ }
617
+
618
+ /**
619
+ * Standard V3 test setup
620
+ *
621
+ * @example
622
+ * // In your test file:
623
+ * setupV3Tests();
624
+ *
625
+ * describe('MyModule', () => {
626
+ * // tests...
627
+ * });
628
+ */
629
+ export function setupV3Tests(config: V3TestConfig = {}): void {
630
+ configureTestEnvironment({
631
+ resetMocks: true,
632
+ suppressConsole: config.suppressConsole ?? false,
633
+ env: {
634
+ NODE_ENV: 'test',
635
+ CLAUDE_FLOW_MODE: 'test',
636
+ ...config.env,
637
+ },
638
+ });
639
+ }
640
+
641
+ /**
642
+ * V3 test configuration
643
+ */
644
+ export interface V3TestConfig {
645
+ suppressConsole?: boolean | ('log' | 'warn' | 'error' | 'info')[];
646
+ env?: Record<string, string>;
647
+ timeout?: number;
648
+ }
649
+
650
+ /**
651
+ * Wait for all pending promises to resolve
652
+ *
653
+ * @example
654
+ * await flushPromises();
655
+ */
656
+ export function flushPromises(): Promise<void> {
657
+ return new Promise(resolve => setImmediate(resolve));
658
+ }
659
+
660
+ /**
661
+ * Run with timeout
662
+ *
663
+ * @example
664
+ * await withTimeout(async () => {
665
+ * await longRunningOperation();
666
+ * }, 5000);
667
+ */
668
+ export async function withTestTimeout<T>(
669
+ fn: () => Promise<T>,
670
+ timeoutMs: number = 5000
671
+ ): Promise<T> {
672
+ return Promise.race([
673
+ fn(),
674
+ new Promise<never>((_, reject) =>
675
+ setTimeout(() => reject(new Error(`Test timed out after ${timeoutMs}ms`)), timeoutMs)
676
+ ),
677
+ ]);
678
+ }