@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.
- 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,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V3 Claude-Flow Custom Assertions
|
|
3
|
+
*
|
|
4
|
+
* London School TDD Custom Assertions
|
|
5
|
+
* - Behavior-focused assertions
|
|
6
|
+
* - Interaction verification helpers
|
|
7
|
+
* - Contract testing utilities
|
|
8
|
+
*/
|
|
9
|
+
import { expect, type Mock } from 'vitest';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Assert that a mock was called in a specific sequence
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* assertCallSequence([
|
|
16
|
+
* [mockRepo.findById, ['123']],
|
|
17
|
+
* [mockValidator.validate, [user]],
|
|
18
|
+
* [mockRepo.save, [user]],
|
|
19
|
+
* ]);
|
|
20
|
+
*/
|
|
21
|
+
export function assertCallSequence(
|
|
22
|
+
sequence: Array<[Mock, unknown[]]>
|
|
23
|
+
): void {
|
|
24
|
+
const calls: Array<{ mock: Mock; args: unknown[]; order: number }> = [];
|
|
25
|
+
|
|
26
|
+
for (const [mock, expectedArgs] of sequence) {
|
|
27
|
+
const mockCalls = mock.mock.calls;
|
|
28
|
+
const invocationOrder = mock.mock.invocationCallOrder;
|
|
29
|
+
|
|
30
|
+
for (let i = 0; i < mockCalls.length; i++) {
|
|
31
|
+
if (JSON.stringify(mockCalls[i]) === JSON.stringify(expectedArgs)) {
|
|
32
|
+
calls.push({
|
|
33
|
+
mock,
|
|
34
|
+
args: mockCalls[i],
|
|
35
|
+
order: invocationOrder[i],
|
|
36
|
+
});
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
expect(calls.length).toBe(sequence.length);
|
|
43
|
+
|
|
44
|
+
for (let i = 1; i < calls.length; i++) {
|
|
45
|
+
expect(calls[i].order).toBeGreaterThan(calls[i - 1].order);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Assert that a mock was NOT called with specific arguments
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* assertNotCalledWith(mockRepo.save, [invalidUser]);
|
|
54
|
+
*/
|
|
55
|
+
export function assertNotCalledWith(mock: Mock, args: unknown[]): void {
|
|
56
|
+
const wasCalledWith = mock.mock.calls.some(
|
|
57
|
+
(call: unknown[]) => JSON.stringify(call) === JSON.stringify(args)
|
|
58
|
+
);
|
|
59
|
+
expect(wasCalledWith).toBe(false);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Assert interaction count for behavior verification
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* assertInteractionCount(mockService.process, 3);
|
|
67
|
+
*/
|
|
68
|
+
export function assertInteractionCount(mock: Mock, expectedCount: number): void {
|
|
69
|
+
expect(mock.mock.calls.length).toBe(expectedCount);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Assert that all mocks in a group were called
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* assertAllCalled([mockA.method, mockB.method, mockC.method]);
|
|
77
|
+
*/
|
|
78
|
+
export function assertAllCalled(mocks: Mock[]): void {
|
|
79
|
+
for (const mock of mocks) {
|
|
80
|
+
expect(mock).toHaveBeenCalled();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Assert that none of the mocks were called
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* assertNoneCalled([mockA.method, mockB.method]);
|
|
89
|
+
*/
|
|
90
|
+
export function assertNoneCalled(mocks: Mock[]): void {
|
|
91
|
+
for (const mock of mocks) {
|
|
92
|
+
expect(mock).not.toHaveBeenCalled();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Assert contract compliance
|
|
98
|
+
* Verifies that an object implements expected interface
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* assertContractCompliance(userService, UserServiceContract);
|
|
102
|
+
*/
|
|
103
|
+
export function assertContractCompliance<T extends object>(
|
|
104
|
+
implementation: T,
|
|
105
|
+
contract: ContractDefinition<T>
|
|
106
|
+
): void {
|
|
107
|
+
for (const [method, spec] of Object.entries(contract.methods)) {
|
|
108
|
+
expect(typeof (implementation as Record<string, unknown>)[method]).toBe('function');
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Contract definition for interface compliance
|
|
114
|
+
*/
|
|
115
|
+
export interface ContractDefinition<T> {
|
|
116
|
+
methods: {
|
|
117
|
+
[K in keyof T]?: {
|
|
118
|
+
params: Array<{ name: string; type: string }>;
|
|
119
|
+
returns: string;
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Assert async operation timing
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* await assertTimingWithin(async () => service.process(), 100);
|
|
129
|
+
*/
|
|
130
|
+
export async function assertTimingWithin(
|
|
131
|
+
operation: () => Promise<unknown>,
|
|
132
|
+
maxMs: number
|
|
133
|
+
): Promise<void> {
|
|
134
|
+
const start = Date.now();
|
|
135
|
+
await operation();
|
|
136
|
+
const duration = Date.now() - start;
|
|
137
|
+
expect(duration).toBeLessThanOrEqual(maxMs);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Assert that an async operation completes within expected time range
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* await assertTimingRange(async () => service.process(), 50, 150);
|
|
145
|
+
*/
|
|
146
|
+
export async function assertTimingRange(
|
|
147
|
+
operation: () => Promise<unknown>,
|
|
148
|
+
minMs: number,
|
|
149
|
+
maxMs: number
|
|
150
|
+
): Promise<void> {
|
|
151
|
+
const start = Date.now();
|
|
152
|
+
await operation();
|
|
153
|
+
const duration = Date.now() - start;
|
|
154
|
+
expect(duration).toBeGreaterThanOrEqual(minMs);
|
|
155
|
+
expect(duration).toBeLessThanOrEqual(maxMs);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Assert error type and message
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
* await assertThrowsWithMessage(
|
|
163
|
+
* async () => service.validate(invalid),
|
|
164
|
+
* ValidationError,
|
|
165
|
+
* 'Invalid input'
|
|
166
|
+
* );
|
|
167
|
+
*/
|
|
168
|
+
export async function assertThrowsWithMessage(
|
|
169
|
+
operation: () => Promise<unknown>,
|
|
170
|
+
ErrorType: new (...args: unknown[]) => Error,
|
|
171
|
+
expectedMessage: string | RegExp
|
|
172
|
+
): Promise<void> {
|
|
173
|
+
let error: Error | null = null;
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
await operation();
|
|
177
|
+
} catch (e) {
|
|
178
|
+
error = e as Error;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
expect(error).toBeInstanceOf(ErrorType);
|
|
182
|
+
if (typeof expectedMessage === 'string') {
|
|
183
|
+
expect(error?.message).toContain(expectedMessage);
|
|
184
|
+
} else {
|
|
185
|
+
expect(error?.message).toMatch(expectedMessage);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Assert domain event was published
|
|
191
|
+
*
|
|
192
|
+
* @example
|
|
193
|
+
* await assertEventPublished(mockEventBus, 'UserCreated', { userId: '123' });
|
|
194
|
+
*/
|
|
195
|
+
export function assertEventPublished(
|
|
196
|
+
eventBusMock: Mock,
|
|
197
|
+
eventType: string,
|
|
198
|
+
expectedPayload?: Record<string, unknown>
|
|
199
|
+
): void {
|
|
200
|
+
const publishCalls = eventBusMock.mock.calls;
|
|
201
|
+
|
|
202
|
+
const matchingEvent = publishCalls.find((call: unknown[]) => {
|
|
203
|
+
const event = call[0] as { type: string; payload: unknown };
|
|
204
|
+
return event.type === eventType;
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
expect(matchingEvent).toBeDefined();
|
|
208
|
+
|
|
209
|
+
if (expectedPayload && matchingEvent) {
|
|
210
|
+
const actualPayload = (matchingEvent[0] as { payload: unknown }).payload;
|
|
211
|
+
expect(actualPayload).toMatchObject(expectedPayload);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Assert that mock returns expected values in sequence
|
|
217
|
+
*
|
|
218
|
+
* @example
|
|
219
|
+
* assertMockSequence(mockCounter.next, [1, 2, 3]);
|
|
220
|
+
*/
|
|
221
|
+
export async function assertMockSequence(
|
|
222
|
+
mock: Mock,
|
|
223
|
+
expectedValues: unknown[]
|
|
224
|
+
): Promise<void> {
|
|
225
|
+
for (const expected of expectedValues) {
|
|
226
|
+
const result = await mock();
|
|
227
|
+
expect(result).toEqual(expected);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Security assertion - verify sensitive data handling
|
|
233
|
+
*
|
|
234
|
+
* @example
|
|
235
|
+
* assertNoSensitiveDataLogged(mockLogger, ['password', 'token', 'secret']);
|
|
236
|
+
*/
|
|
237
|
+
export function assertNoSensitiveDataLogged(
|
|
238
|
+
loggerMock: Mock,
|
|
239
|
+
sensitivePatterns: string[]
|
|
240
|
+
): void {
|
|
241
|
+
const allCalls = loggerMock.mock.calls;
|
|
242
|
+
|
|
243
|
+
for (const call of allCalls) {
|
|
244
|
+
const logContent = JSON.stringify(call);
|
|
245
|
+
for (const pattern of sensitivePatterns) {
|
|
246
|
+
expect(logContent.toLowerCase()).not.toContain(pattern.toLowerCase());
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Performance assertion - verify operation meets target
|
|
253
|
+
*
|
|
254
|
+
* @example
|
|
255
|
+
* await assertPerformanceTarget(
|
|
256
|
+
* async () => await search.execute(query),
|
|
257
|
+
* { targetMs: 100, iterations: 10 }
|
|
258
|
+
* );
|
|
259
|
+
*/
|
|
260
|
+
export async function assertPerformanceTarget(
|
|
261
|
+
operation: () => Promise<unknown>,
|
|
262
|
+
config: { targetMs: number; iterations: number; warmupIterations?: number }
|
|
263
|
+
): Promise<{ averageMs: number; minMs: number; maxMs: number }> {
|
|
264
|
+
const { targetMs, iterations, warmupIterations = 3 } = config;
|
|
265
|
+
|
|
266
|
+
// Warmup
|
|
267
|
+
for (let i = 0; i < warmupIterations; i++) {
|
|
268
|
+
await operation();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const timings: number[] = [];
|
|
272
|
+
|
|
273
|
+
for (let i = 0; i < iterations; i++) {
|
|
274
|
+
const start = performance.now();
|
|
275
|
+
await operation();
|
|
276
|
+
timings.push(performance.now() - start);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const averageMs = timings.reduce((a, b) => a + b, 0) / timings.length;
|
|
280
|
+
const minMs = Math.min(...timings);
|
|
281
|
+
const maxMs = Math.max(...timings);
|
|
282
|
+
|
|
283
|
+
expect(averageMs).toBeLessThanOrEqual(targetMs);
|
|
284
|
+
|
|
285
|
+
return { averageMs, minMs, maxMs };
|
|
286
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V3 Claude-Flow Mock Factory
|
|
3
|
+
*
|
|
4
|
+
* London School TDD Mock Creation Utilities
|
|
5
|
+
* - Creates type-safe mocks for behavior verification
|
|
6
|
+
* - Supports deep mocking for complex objects
|
|
7
|
+
* - Enables interaction tracking for behavior testing
|
|
8
|
+
*/
|
|
9
|
+
import { vi, type Mock } from 'vitest';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Type for a fully mocked interface
|
|
13
|
+
*/
|
|
14
|
+
export type MockedInterface<T> = {
|
|
15
|
+
[K in keyof T]: T[K] extends (...args: infer A) => infer R
|
|
16
|
+
? Mock<(...args: A) => R>
|
|
17
|
+
: T[K];
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Create a shallow mock of an interface
|
|
22
|
+
* Each method becomes a vi.fn() for behavior verification
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* const mockRepo = createMock<UserRepository>();
|
|
26
|
+
* mockRepo.findById.mockResolvedValue(user);
|
|
27
|
+
* expect(mockRepo.save).toHaveBeenCalledWith(user);
|
|
28
|
+
*/
|
|
29
|
+
export function createMock<T extends object>(): MockedInterface<T> {
|
|
30
|
+
return new Proxy({} as MockedInterface<T>, {
|
|
31
|
+
get: (target, prop: string | symbol) => {
|
|
32
|
+
if (typeof prop === 'string' && !(prop in target)) {
|
|
33
|
+
(target as Record<string | symbol, unknown>)[prop] = vi.fn();
|
|
34
|
+
}
|
|
35
|
+
return (target as Record<string | symbol, unknown>)[prop];
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Create a deep mock that handles nested objects
|
|
42
|
+
* Useful for complex interfaces with nested dependencies
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* const mockService = createDeepMock<ComplexService>();
|
|
46
|
+
* mockService.nested.method.mockReturnValue(result);
|
|
47
|
+
*/
|
|
48
|
+
export function createDeepMock<T extends object>(): MockedInterface<T> {
|
|
49
|
+
const cache = new Map<string | symbol, unknown>();
|
|
50
|
+
|
|
51
|
+
return new Proxy({} as MockedInterface<T>, {
|
|
52
|
+
get: (target, prop: string | symbol) => {
|
|
53
|
+
if (!cache.has(prop)) {
|
|
54
|
+
const mock = vi.fn();
|
|
55
|
+
// Allow chaining for nested access
|
|
56
|
+
(mock as unknown as Record<string, unknown>).mockReturnValue =
|
|
57
|
+
mock.mockReturnValue.bind(mock);
|
|
58
|
+
cache.set(prop, mock);
|
|
59
|
+
}
|
|
60
|
+
return cache.get(prop);
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Create a spy mock that wraps an existing object
|
|
67
|
+
* Preserves original behavior while enabling verification
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* const spied = createSpyMock(realService);
|
|
71
|
+
* await spied.process();
|
|
72
|
+
* expect(spied.process).toHaveBeenCalled();
|
|
73
|
+
*/
|
|
74
|
+
export function createSpyMock<T extends object>(target: T): MockedInterface<T> {
|
|
75
|
+
const spied = { ...target } as MockedInterface<T>;
|
|
76
|
+
|
|
77
|
+
for (const key of Object.keys(target) as Array<keyof T>) {
|
|
78
|
+
const value = target[key];
|
|
79
|
+
if (typeof value === 'function') {
|
|
80
|
+
(spied as Record<keyof T, unknown>)[key] = vi.fn(value.bind(target));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return spied;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Create a mock with predefined behavior
|
|
89
|
+
* Useful for common test scenarios
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* const mockRepo = createMockWithBehavior<UserRepository>({
|
|
93
|
+
* findById: async (id) => ({ id, name: 'Test' }),
|
|
94
|
+
* save: async (user) => user,
|
|
95
|
+
* });
|
|
96
|
+
*/
|
|
97
|
+
export function createMockWithBehavior<T extends object>(
|
|
98
|
+
implementations: Partial<{ [K in keyof T]: T[K] }>
|
|
99
|
+
): MockedInterface<T> {
|
|
100
|
+
const mock = createMock<T>();
|
|
101
|
+
|
|
102
|
+
for (const [key, impl] of Object.entries(implementations)) {
|
|
103
|
+
if (typeof impl === 'function') {
|
|
104
|
+
(mock as Record<string, Mock>)[key].mockImplementation(impl as (...args: unknown[]) => unknown);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return mock;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Create a mock that fails on first call, succeeds on retry
|
|
113
|
+
* Useful for testing retry logic and error handling
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* const mockApi = createRetryMock<ApiClient>('fetch', new Error('Network'), data);
|
|
117
|
+
*/
|
|
118
|
+
export function createRetryMock<T extends object>(
|
|
119
|
+
methodName: keyof T,
|
|
120
|
+
firstError: Error,
|
|
121
|
+
successValue: unknown
|
|
122
|
+
): MockedInterface<T> {
|
|
123
|
+
const mock = createMock<T>();
|
|
124
|
+
|
|
125
|
+
(mock as Record<string, Mock>)[methodName as string]
|
|
126
|
+
.mockRejectedValueOnce(firstError)
|
|
127
|
+
.mockResolvedValue(successValue);
|
|
128
|
+
|
|
129
|
+
return mock;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Create a sequence mock that returns different values per call
|
|
134
|
+
* Useful for testing stateful interactions
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* const mockCounter = createSequenceMock<Counter>('next', [1, 2, 3, 4, 5]);
|
|
138
|
+
*/
|
|
139
|
+
export function createSequenceMock<T extends object>(
|
|
140
|
+
methodName: keyof T,
|
|
141
|
+
values: unknown[]
|
|
142
|
+
): MockedInterface<T> {
|
|
143
|
+
const mock = createMock<T>();
|
|
144
|
+
const fn = (mock as Record<string, Mock>)[methodName as string];
|
|
145
|
+
|
|
146
|
+
values.forEach((value, index) => {
|
|
147
|
+
if (index === values.length - 1) {
|
|
148
|
+
fn.mockReturnValue(value);
|
|
149
|
+
} else {
|
|
150
|
+
fn.mockReturnValueOnce(value);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
return mock;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Interaction recorder for complex behavior verification
|
|
159
|
+
* Tracks all calls across multiple mocks
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
* const recorder = new InteractionRecorder();
|
|
163
|
+
* recorder.track('repo', mockRepo);
|
|
164
|
+
* recorder.track('notifier', mockNotifier);
|
|
165
|
+
* await service.process();
|
|
166
|
+
* expect(recorder.getInteractionOrder()).toEqual(['repo.save', 'notifier.notify']);
|
|
167
|
+
*/
|
|
168
|
+
export class InteractionRecorder {
|
|
169
|
+
private interactions: Array<{ name: string; method: string; args: unknown[]; timestamp: number }> = [];
|
|
170
|
+
|
|
171
|
+
track<T extends object>(name: string, mock: MockedInterface<T>): void {
|
|
172
|
+
for (const key of Object.keys(mock)) {
|
|
173
|
+
const method = (mock as Record<string, Mock>)[key];
|
|
174
|
+
if (typeof method?.mockImplementation === 'function') {
|
|
175
|
+
const original = method.getMockImplementation();
|
|
176
|
+
method.mockImplementation((...args: unknown[]) => {
|
|
177
|
+
this.interactions.push({
|
|
178
|
+
name,
|
|
179
|
+
method: key,
|
|
180
|
+
args,
|
|
181
|
+
timestamp: Date.now(),
|
|
182
|
+
});
|
|
183
|
+
return original?.(...args);
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
getInteractions(): Array<{ name: string; method: string; args: unknown[] }> {
|
|
190
|
+
return this.interactions.map(({ name, method, args }) => ({ name, method, args }));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
getInteractionOrder(): string[] {
|
|
194
|
+
return this.interactions.map(({ name, method }) => `${name}.${method}`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
clear(): void {
|
|
198
|
+
this.interactions = [];
|
|
199
|
+
}
|
|
200
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V3 Claude-Flow Test Helpers Index
|
|
3
|
+
*
|
|
4
|
+
* Central export for all test helpers
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Mock factory utilities
|
|
8
|
+
export {
|
|
9
|
+
createMock,
|
|
10
|
+
createDeepMock,
|
|
11
|
+
createSpyMock,
|
|
12
|
+
createMockWithBehavior,
|
|
13
|
+
createRetryMock,
|
|
14
|
+
createSequenceMock,
|
|
15
|
+
InteractionRecorder,
|
|
16
|
+
type MockedInterface,
|
|
17
|
+
} from './create-mock.js';
|
|
18
|
+
|
|
19
|
+
// Test application builder
|
|
20
|
+
export {
|
|
21
|
+
createTestApplication,
|
|
22
|
+
type TestApplication,
|
|
23
|
+
type IEventBus,
|
|
24
|
+
type ITaskManager,
|
|
25
|
+
type IAgentLifecycle,
|
|
26
|
+
type IMemoryService,
|
|
27
|
+
type ISecurityService,
|
|
28
|
+
type ISwarmCoordinator,
|
|
29
|
+
} from './test-application.js';
|
|
30
|
+
|
|
31
|
+
// Swarm test instance
|
|
32
|
+
export {
|
|
33
|
+
createSwarmTestInstance,
|
|
34
|
+
SwarmTestInstance,
|
|
35
|
+
type V3AgentType,
|
|
36
|
+
type SwarmTopology,
|
|
37
|
+
type SwarmAgent,
|
|
38
|
+
type SwarmMessage,
|
|
39
|
+
type SwarmTask,
|
|
40
|
+
type SwarmTaskResult,
|
|
41
|
+
} from './swarm-instance.js';
|
|
42
|
+
|
|
43
|
+
// Custom assertions (legacy)
|
|
44
|
+
export {
|
|
45
|
+
assertCallSequence,
|
|
46
|
+
assertNotCalledWith,
|
|
47
|
+
assertInteractionCount,
|
|
48
|
+
assertAllCalled,
|
|
49
|
+
assertNoneCalled,
|
|
50
|
+
assertContractCompliance,
|
|
51
|
+
assertTimingWithin,
|
|
52
|
+
assertTimingRange,
|
|
53
|
+
assertThrowsWithMessage,
|
|
54
|
+
assertEventPublished,
|
|
55
|
+
assertMockSequence,
|
|
56
|
+
assertNoSensitiveDataLogged,
|
|
57
|
+
assertPerformanceTarget,
|
|
58
|
+
type ContractDefinition,
|
|
59
|
+
} from './assertions.js';
|
|
60
|
+
|
|
61
|
+
// Test utilities (waitFor, retry, timeout, etc.)
|
|
62
|
+
export {
|
|
63
|
+
waitFor,
|
|
64
|
+
waitUntilChanged,
|
|
65
|
+
retry,
|
|
66
|
+
withTimeout,
|
|
67
|
+
sleep,
|
|
68
|
+
createDeferred,
|
|
69
|
+
parallelLimit,
|
|
70
|
+
measureTime,
|
|
71
|
+
createMockClock,
|
|
72
|
+
createTestEmitter,
|
|
73
|
+
createCallSpy,
|
|
74
|
+
createMockStream,
|
|
75
|
+
collectStream,
|
|
76
|
+
generateTestId,
|
|
77
|
+
createTestContext,
|
|
78
|
+
expectToReject,
|
|
79
|
+
createTrackedMock,
|
|
80
|
+
TimeoutError,
|
|
81
|
+
type WaitForOptions,
|
|
82
|
+
type WaitUntilChangedOptions,
|
|
83
|
+
type RetryOptions,
|
|
84
|
+
type Deferred,
|
|
85
|
+
type MockClock,
|
|
86
|
+
type TestEmitter,
|
|
87
|
+
type CallSpy,
|
|
88
|
+
type MockStreamOptions,
|
|
89
|
+
type TestContext,
|
|
90
|
+
type TrackedMock,
|
|
91
|
+
} from './test-utils.js';
|
|
92
|
+
|
|
93
|
+
// Mock factory (comprehensive service mocks)
|
|
94
|
+
export {
|
|
95
|
+
createMockEventBus,
|
|
96
|
+
createMockTaskManager,
|
|
97
|
+
createMockAgentLifecycle,
|
|
98
|
+
createMockMemoryService,
|
|
99
|
+
createMockSecurityService,
|
|
100
|
+
createMockSwarmCoordinator,
|
|
101
|
+
createMockMCPClient,
|
|
102
|
+
createMockLogger,
|
|
103
|
+
createMockApplication,
|
|
104
|
+
resetMockApplication,
|
|
105
|
+
type MockApplication,
|
|
106
|
+
type ILogger,
|
|
107
|
+
type IMCPClient,
|
|
108
|
+
type DomainEvent,
|
|
109
|
+
type EventHandler,
|
|
110
|
+
type Task,
|
|
111
|
+
type TaskDefinition,
|
|
112
|
+
type TaskResult,
|
|
113
|
+
type TaskStatus,
|
|
114
|
+
type TaskFilters,
|
|
115
|
+
type AgentConfig,
|
|
116
|
+
type AgentSpawnResult,
|
|
117
|
+
type TerminateOptions,
|
|
118
|
+
type AgentFilters,
|
|
119
|
+
type AgentHealthCheck,
|
|
120
|
+
type MemoryStats,
|
|
121
|
+
type IndexConfig,
|
|
122
|
+
type InputValidationOptions,
|
|
123
|
+
type ExecuteOptions,
|
|
124
|
+
type ExecuteResult,
|
|
125
|
+
} from './mock-factory.js';
|
|
126
|
+
|
|
127
|
+
// Assertion helpers (enhanced)
|
|
128
|
+
export {
|
|
129
|
+
assertCalledWithPattern,
|
|
130
|
+
assertEventOrder,
|
|
131
|
+
assertEventNotPublished,
|
|
132
|
+
assertMocksCalledInOrder,
|
|
133
|
+
assertCalledNTimesWith,
|
|
134
|
+
assertCompletesWithin,
|
|
135
|
+
assertThrowsError,
|
|
136
|
+
assertNoSensitiveData,
|
|
137
|
+
assertMatchesSnapshot,
|
|
138
|
+
assertV3PerformanceTargets,
|
|
139
|
+
assertValidDomainObject,
|
|
140
|
+
assertOnlyCalledWithAllowed,
|
|
141
|
+
assertPartialOrder,
|
|
142
|
+
assertAllPass,
|
|
143
|
+
assertNonePass,
|
|
144
|
+
assertSameElements,
|
|
145
|
+
assertMockReturnsSequence,
|
|
146
|
+
assertValidStateTransition,
|
|
147
|
+
assertRetryPattern,
|
|
148
|
+
assertDependencyInjected,
|
|
149
|
+
registerCustomMatchers,
|
|
150
|
+
type SnapshotOptions,
|
|
151
|
+
type V3PerformanceMetrics,
|
|
152
|
+
type RetryPatternOptions,
|
|
153
|
+
} from './assertion-helpers.js';
|
|
154
|
+
|
|
155
|
+
// Setup and teardown helpers
|
|
156
|
+
export {
|
|
157
|
+
createSetupContext,
|
|
158
|
+
getGlobalContext,
|
|
159
|
+
resetGlobalContext,
|
|
160
|
+
configureTestEnvironment,
|
|
161
|
+
createTestSuite,
|
|
162
|
+
createTestScope,
|
|
163
|
+
createInMemoryDatabaseHelper,
|
|
164
|
+
createNetworkTestHelper,
|
|
165
|
+
createInMemoryFileSystemHelper,
|
|
166
|
+
createPerformanceTestHelper,
|
|
167
|
+
setupV3Tests,
|
|
168
|
+
flushPromises,
|
|
169
|
+
withTestTimeout,
|
|
170
|
+
type SetupContext,
|
|
171
|
+
type CleanupFunction,
|
|
172
|
+
type Disposable,
|
|
173
|
+
type TestEnvironmentConfig,
|
|
174
|
+
type TestSuiteHelpers,
|
|
175
|
+
type TestScope,
|
|
176
|
+
type DatabaseTestHelper,
|
|
177
|
+
type NetworkTestHelper,
|
|
178
|
+
type MockFetchResponse,
|
|
179
|
+
type FileSystemTestHelper,
|
|
180
|
+
type PerformanceTestHelper,
|
|
181
|
+
type V3TestConfig,
|
|
182
|
+
} from './setup-teardown.js';
|