@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,711 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sparkleideas/testing - Mock Factory
|
|
3
|
+
*
|
|
4
|
+
* Factory functions for creating comprehensive mocks of V3 services and components.
|
|
5
|
+
* Implements London School TDD patterns with behavior verification.
|
|
6
|
+
*/
|
|
7
|
+
import { vi, type Mock } from 'vitest';
|
|
8
|
+
import { createMock, createMockWithBehavior, type MockedInterface } from './create-mock.js';
|
|
9
|
+
import type { V3AgentType, AgentInstance, AgentMetrics } from '../fixtures/agent-fixtures.js';
|
|
10
|
+
import type { MemoryEntry, SearchResult, VectorQuery } from '../fixtures/memory-fixtures.js';
|
|
11
|
+
import type { SwarmState, SwarmConfig, SwarmTask, SwarmTaskResult, CoordinationResult } from '../fixtures/swarm-fixtures.js';
|
|
12
|
+
import type { MCPTool, MCPToolResult, MCPServerConfig, MCPSessionContext } from '../fixtures/mcp-fixtures.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Event bus interface for DDD events
|
|
16
|
+
*/
|
|
17
|
+
export interface IEventBus {
|
|
18
|
+
publish(event: DomainEvent): Promise<void>;
|
|
19
|
+
subscribe(eventType: string, handler: EventHandler): () => void;
|
|
20
|
+
unsubscribe(eventType: string, handler: EventHandler): void;
|
|
21
|
+
getSubscriberCount(eventType: string): number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Domain event interface
|
|
26
|
+
*/
|
|
27
|
+
export interface DomainEvent {
|
|
28
|
+
id: string;
|
|
29
|
+
type: string;
|
|
30
|
+
payload: unknown;
|
|
31
|
+
timestamp: Date;
|
|
32
|
+
correlationId?: string;
|
|
33
|
+
causationId?: string;
|
|
34
|
+
metadata?: Record<string, unknown>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Event handler type
|
|
39
|
+
*/
|
|
40
|
+
export type EventHandler = (event: DomainEvent) => Promise<void>;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Task manager interface
|
|
44
|
+
*/
|
|
45
|
+
export interface ITaskManager {
|
|
46
|
+
create(definition: TaskDefinition): Promise<Task>;
|
|
47
|
+
execute(taskId: string): Promise<TaskResult>;
|
|
48
|
+
cancel(taskId: string): Promise<void>;
|
|
49
|
+
getStatus(taskId: string): Promise<TaskStatus>;
|
|
50
|
+
getTask(taskId: string): Promise<Task | null>;
|
|
51
|
+
listTasks(filters?: TaskFilters): Promise<Task[]>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Task definition interface
|
|
56
|
+
*/
|
|
57
|
+
export interface TaskDefinition {
|
|
58
|
+
name: string;
|
|
59
|
+
type: string;
|
|
60
|
+
payload: unknown;
|
|
61
|
+
priority?: number;
|
|
62
|
+
dependencies?: string[];
|
|
63
|
+
deadline?: Date;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Task interface
|
|
68
|
+
*/
|
|
69
|
+
export interface Task {
|
|
70
|
+
id: string;
|
|
71
|
+
name: string;
|
|
72
|
+
type: string;
|
|
73
|
+
status: TaskStatus;
|
|
74
|
+
payload: unknown;
|
|
75
|
+
priority: number;
|
|
76
|
+
createdAt: Date;
|
|
77
|
+
startedAt?: Date;
|
|
78
|
+
completedAt?: Date;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Task status type
|
|
83
|
+
*/
|
|
84
|
+
export type TaskStatus = 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Task result interface
|
|
88
|
+
*/
|
|
89
|
+
export interface TaskResult {
|
|
90
|
+
taskId: string;
|
|
91
|
+
success: boolean;
|
|
92
|
+
output?: unknown;
|
|
93
|
+
error?: Error;
|
|
94
|
+
duration: number;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Task filters interface
|
|
99
|
+
*/
|
|
100
|
+
export interface TaskFilters {
|
|
101
|
+
status?: TaskStatus;
|
|
102
|
+
type?: string;
|
|
103
|
+
assignedTo?: string;
|
|
104
|
+
limit?: number;
|
|
105
|
+
offset?: number;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Agent lifecycle interface
|
|
110
|
+
*/
|
|
111
|
+
export interface IAgentLifecycle {
|
|
112
|
+
spawn(config: AgentConfig): Promise<AgentSpawnResult>;
|
|
113
|
+
terminate(agentId: string, options?: TerminateOptions): Promise<void>;
|
|
114
|
+
getAgent(agentId: string): Promise<AgentInstance | null>;
|
|
115
|
+
listAgents(filters?: AgentFilters): Promise<AgentInstance[]>;
|
|
116
|
+
getMetrics(agentId: string): Promise<AgentMetrics>;
|
|
117
|
+
healthCheck(agentId: string): Promise<AgentHealthCheck>;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Agent config interface
|
|
122
|
+
*/
|
|
123
|
+
export interface AgentConfig {
|
|
124
|
+
type: V3AgentType;
|
|
125
|
+
name: string;
|
|
126
|
+
capabilities?: string[];
|
|
127
|
+
priority?: number;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Agent spawn result interface
|
|
132
|
+
*/
|
|
133
|
+
export interface AgentSpawnResult {
|
|
134
|
+
agent: AgentInstance;
|
|
135
|
+
sessionId: string;
|
|
136
|
+
startupTime: number;
|
|
137
|
+
success: boolean;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Terminate options interface
|
|
142
|
+
*/
|
|
143
|
+
export interface TerminateOptions {
|
|
144
|
+
graceful?: boolean;
|
|
145
|
+
timeout?: number;
|
|
146
|
+
cancelTasks?: boolean;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Agent filters interface
|
|
151
|
+
*/
|
|
152
|
+
export interface AgentFilters {
|
|
153
|
+
type?: V3AgentType;
|
|
154
|
+
status?: string;
|
|
155
|
+
capability?: string;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Agent health check interface
|
|
160
|
+
*/
|
|
161
|
+
export interface AgentHealthCheck {
|
|
162
|
+
healthy: boolean;
|
|
163
|
+
issues?: string[];
|
|
164
|
+
lastActivity: Date;
|
|
165
|
+
metrics: AgentMetrics;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Memory service interface
|
|
170
|
+
*/
|
|
171
|
+
export interface IMemoryService {
|
|
172
|
+
store(key: string, value: unknown, metadata?: Record<string, unknown>): Promise<void>;
|
|
173
|
+
retrieve(key: string): Promise<unknown>;
|
|
174
|
+
search(query: VectorQuery): Promise<SearchResult[]>;
|
|
175
|
+
delete(key: string): Promise<void>;
|
|
176
|
+
clear(): Promise<void>;
|
|
177
|
+
getStats(): Promise<MemoryStats>;
|
|
178
|
+
createIndex(name: string, config: IndexConfig): Promise<void>;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Memory stats interface
|
|
183
|
+
*/
|
|
184
|
+
export interface MemoryStats {
|
|
185
|
+
totalEntries: number;
|
|
186
|
+
totalSizeBytes: number;
|
|
187
|
+
vectorCount: number;
|
|
188
|
+
cacheHitRate: number;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Index config interface
|
|
193
|
+
*/
|
|
194
|
+
export interface IndexConfig {
|
|
195
|
+
dimensions: number;
|
|
196
|
+
metric: 'cosine' | 'euclidean' | 'dot';
|
|
197
|
+
M?: number;
|
|
198
|
+
efConstruction?: number;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Security service interface
|
|
203
|
+
*/
|
|
204
|
+
export interface ISecurityService {
|
|
205
|
+
validatePath(path: string): boolean;
|
|
206
|
+
validateInput(input: string, options?: InputValidationOptions): { valid: boolean; errors?: string[] };
|
|
207
|
+
hashPassword(password: string): Promise<string>;
|
|
208
|
+
verifyPassword(password: string, hash: string): Promise<boolean>;
|
|
209
|
+
generateToken(payload: Record<string, unknown>, expiresIn?: number): Promise<string>;
|
|
210
|
+
verifyToken(token: string): Promise<Record<string, unknown>>;
|
|
211
|
+
executeSecurely(command: string, options?: ExecuteOptions): Promise<ExecuteResult>;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Input validation options
|
|
216
|
+
*/
|
|
217
|
+
export interface InputValidationOptions {
|
|
218
|
+
maxLength?: number;
|
|
219
|
+
allowedChars?: RegExp;
|
|
220
|
+
sanitize?: boolean;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Execute options interface
|
|
225
|
+
*/
|
|
226
|
+
export interface ExecuteOptions {
|
|
227
|
+
timeout?: number;
|
|
228
|
+
cwd?: string;
|
|
229
|
+
shell?: boolean;
|
|
230
|
+
allowedCommands?: string[];
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Execute result interface
|
|
235
|
+
*/
|
|
236
|
+
export interface ExecuteResult {
|
|
237
|
+
stdout: string;
|
|
238
|
+
stderr: string;
|
|
239
|
+
exitCode: number;
|
|
240
|
+
duration: number;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Swarm coordinator interface
|
|
245
|
+
*/
|
|
246
|
+
export interface ISwarmCoordinator {
|
|
247
|
+
initialize(config: SwarmConfig): Promise<SwarmState>;
|
|
248
|
+
coordinate(agents: string[], task: SwarmTask): Promise<CoordinationResult>;
|
|
249
|
+
shutdown(graceful?: boolean): Promise<void>;
|
|
250
|
+
getState(): SwarmState;
|
|
251
|
+
addAgent(agentId: string): Promise<void>;
|
|
252
|
+
removeAgent(agentId: string): Promise<void>;
|
|
253
|
+
broadcast(message: unknown): Promise<void>;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* MCP client interface
|
|
258
|
+
*/
|
|
259
|
+
export interface IMCPClient {
|
|
260
|
+
connect(): Promise<void>;
|
|
261
|
+
disconnect(): Promise<void>;
|
|
262
|
+
callTool(name: string, params: Record<string, unknown>): Promise<MCPToolResult>;
|
|
263
|
+
listTools(): Promise<MCPTool[]>;
|
|
264
|
+
isConnected(): boolean;
|
|
265
|
+
getSession(): MCPSessionContext | null;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Logger interface
|
|
270
|
+
*/
|
|
271
|
+
export interface ILogger {
|
|
272
|
+
debug(message: string, context?: Record<string, unknown>): void;
|
|
273
|
+
info(message: string, context?: Record<string, unknown>): void;
|
|
274
|
+
warn(message: string, context?: Record<string, unknown>): void;
|
|
275
|
+
error(message: string, error?: Error, context?: Record<string, unknown>): void;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Create mock event bus with behavior tracking
|
|
280
|
+
*/
|
|
281
|
+
export function createMockEventBus(): MockedInterface<IEventBus> & { publishedEvents: DomainEvent[] } {
|
|
282
|
+
const publishedEvents: DomainEvent[] = [];
|
|
283
|
+
const subscribers = new Map<string, Set<EventHandler>>();
|
|
284
|
+
|
|
285
|
+
const mock = createMock<IEventBus>();
|
|
286
|
+
|
|
287
|
+
mock.publish.mockImplementation(async (event: DomainEvent) => {
|
|
288
|
+
publishedEvents.push(event);
|
|
289
|
+
const handlers = subscribers.get(event.type);
|
|
290
|
+
if (handlers) {
|
|
291
|
+
for (const handler of handlers) {
|
|
292
|
+
await handler(event);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
mock.subscribe.mockImplementation((eventType: string, handler: EventHandler) => {
|
|
298
|
+
if (!subscribers.has(eventType)) {
|
|
299
|
+
subscribers.set(eventType, new Set());
|
|
300
|
+
}
|
|
301
|
+
subscribers.get(eventType)!.add(handler);
|
|
302
|
+
return () => subscribers.get(eventType)?.delete(handler);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
mock.unsubscribe.mockImplementation((eventType: string, handler: EventHandler) => {
|
|
306
|
+
subscribers.get(eventType)?.delete(handler);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
mock.getSubscriberCount.mockImplementation((eventType: string) => {
|
|
310
|
+
return subscribers.get(eventType)?.size ?? 0;
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
return Object.assign(mock, { publishedEvents });
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Create mock task manager with realistic behavior
|
|
318
|
+
*/
|
|
319
|
+
export function createMockTaskManager(): MockedInterface<ITaskManager> & { tasks: Map<string, Task> } {
|
|
320
|
+
const tasks = new Map<string, Task>();
|
|
321
|
+
let taskCounter = 0;
|
|
322
|
+
|
|
323
|
+
const mock = createMock<ITaskManager>();
|
|
324
|
+
|
|
325
|
+
mock.create.mockImplementation(async (definition: TaskDefinition) => {
|
|
326
|
+
const task: Task = {
|
|
327
|
+
id: `task-${++taskCounter}`,
|
|
328
|
+
name: definition.name,
|
|
329
|
+
type: definition.type,
|
|
330
|
+
status: 'pending',
|
|
331
|
+
payload: definition.payload,
|
|
332
|
+
priority: definition.priority ?? 50,
|
|
333
|
+
createdAt: new Date(),
|
|
334
|
+
};
|
|
335
|
+
tasks.set(task.id, task);
|
|
336
|
+
return task;
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
mock.execute.mockImplementation(async (taskId: string) => {
|
|
340
|
+
const task = tasks.get(taskId);
|
|
341
|
+
if (!task) {
|
|
342
|
+
throw new Error(`Task ${taskId} not found`);
|
|
343
|
+
}
|
|
344
|
+
task.status = 'running';
|
|
345
|
+
task.startedAt = new Date();
|
|
346
|
+
|
|
347
|
+
// Simulate execution
|
|
348
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
349
|
+
|
|
350
|
+
task.status = 'completed';
|
|
351
|
+
task.completedAt = new Date();
|
|
352
|
+
|
|
353
|
+
return {
|
|
354
|
+
taskId,
|
|
355
|
+
success: true,
|
|
356
|
+
duration: task.completedAt.getTime() - task.startedAt.getTime(),
|
|
357
|
+
};
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
mock.cancel.mockImplementation(async (taskId: string) => {
|
|
361
|
+
const task = tasks.get(taskId);
|
|
362
|
+
if (task) {
|
|
363
|
+
task.status = 'cancelled';
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
mock.getStatus.mockImplementation(async (taskId: string) => {
|
|
368
|
+
const task = tasks.get(taskId);
|
|
369
|
+
return task?.status ?? 'pending';
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
mock.getTask.mockImplementation(async (taskId: string) => {
|
|
373
|
+
return tasks.get(taskId) ?? null;
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
mock.listTasks.mockImplementation(async (filters?: TaskFilters) => {
|
|
377
|
+
let result = Array.from(tasks.values());
|
|
378
|
+
|
|
379
|
+
if (filters?.status) {
|
|
380
|
+
result = result.filter(t => t.status === filters.status);
|
|
381
|
+
}
|
|
382
|
+
if (filters?.type) {
|
|
383
|
+
result = result.filter(t => t.type === filters.type);
|
|
384
|
+
}
|
|
385
|
+
if (filters?.limit) {
|
|
386
|
+
result = result.slice(filters.offset ?? 0, (filters.offset ?? 0) + filters.limit);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return result;
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
return Object.assign(mock, { tasks });
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Create mock agent lifecycle
|
|
397
|
+
*/
|
|
398
|
+
export function createMockAgentLifecycle(): MockedInterface<IAgentLifecycle> & { agents: Map<string, AgentInstance> } {
|
|
399
|
+
const agents = new Map<string, AgentInstance>();
|
|
400
|
+
let agentCounter = 0;
|
|
401
|
+
|
|
402
|
+
const mock = createMock<IAgentLifecycle>();
|
|
403
|
+
|
|
404
|
+
mock.spawn.mockImplementation(async (config: AgentConfig) => {
|
|
405
|
+
const agent: AgentInstance = {
|
|
406
|
+
id: `agent-${config.type}-${++agentCounter}`,
|
|
407
|
+
type: config.type,
|
|
408
|
+
name: config.name,
|
|
409
|
+
status: 'idle',
|
|
410
|
+
capabilities: config.capabilities ?? [],
|
|
411
|
+
createdAt: new Date(),
|
|
412
|
+
};
|
|
413
|
+
agents.set(agent.id, agent);
|
|
414
|
+
|
|
415
|
+
return {
|
|
416
|
+
agent,
|
|
417
|
+
sessionId: `session-${Date.now()}`,
|
|
418
|
+
startupTime: Math.random() * 100 + 50,
|
|
419
|
+
success: true,
|
|
420
|
+
};
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
mock.terminate.mockImplementation(async (agentId: string) => {
|
|
424
|
+
const agent = agents.get(agentId);
|
|
425
|
+
if (agent) {
|
|
426
|
+
agent.status = 'terminated';
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
mock.getAgent.mockImplementation(async (agentId: string) => {
|
|
431
|
+
return agents.get(agentId) ?? null;
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
mock.listAgents.mockImplementation(async (filters?: AgentFilters) => {
|
|
435
|
+
let result = Array.from(agents.values());
|
|
436
|
+
|
|
437
|
+
if (filters?.type) {
|
|
438
|
+
result = result.filter(a => a.type === filters.type);
|
|
439
|
+
}
|
|
440
|
+
if (filters?.status) {
|
|
441
|
+
result = result.filter(a => a.status === filters.status);
|
|
442
|
+
}
|
|
443
|
+
if (filters?.capability) {
|
|
444
|
+
result = result.filter(a => a.capabilities.includes(filters.capability!));
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
return result;
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
mock.getMetrics.mockImplementation(async () => ({
|
|
451
|
+
tasksCompleted: Math.floor(Math.random() * 100),
|
|
452
|
+
tasksFailed: Math.floor(Math.random() * 10),
|
|
453
|
+
avgTaskDuration: Math.random() * 1000,
|
|
454
|
+
totalDuration: Math.random() * 10000,
|
|
455
|
+
errorRate: Math.random() * 0.1,
|
|
456
|
+
memoryUsageMb: Math.random() * 256,
|
|
457
|
+
}));
|
|
458
|
+
|
|
459
|
+
mock.healthCheck.mockImplementation(async (agentId: string) => {
|
|
460
|
+
const agent = agents.get(agentId);
|
|
461
|
+
return {
|
|
462
|
+
healthy: agent?.status !== 'error' && agent?.status !== 'terminated',
|
|
463
|
+
lastActivity: new Date(),
|
|
464
|
+
metrics: {
|
|
465
|
+
tasksCompleted: 50,
|
|
466
|
+
tasksFailed: 1,
|
|
467
|
+
avgTaskDuration: 200,
|
|
468
|
+
totalDuration: 10000,
|
|
469
|
+
errorRate: 0.02,
|
|
470
|
+
memoryUsageMb: 128,
|
|
471
|
+
},
|
|
472
|
+
};
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
return Object.assign(mock, { agents });
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Create mock memory service
|
|
480
|
+
*/
|
|
481
|
+
export function createMockMemoryService(): MockedInterface<IMemoryService> & { entries: Map<string, { value: unknown; metadata?: Record<string, unknown> }> } {
|
|
482
|
+
const entries = new Map<string, { value: unknown; metadata?: Record<string, unknown> }>();
|
|
483
|
+
|
|
484
|
+
const mock = createMock<IMemoryService>();
|
|
485
|
+
|
|
486
|
+
mock.store.mockImplementation(async (key: string, value: unknown, metadata?: Record<string, unknown>) => {
|
|
487
|
+
entries.set(key, { value, metadata });
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
mock.retrieve.mockImplementation(async (key: string) => {
|
|
491
|
+
return entries.get(key)?.value ?? null;
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
mock.search.mockImplementation(async () => []);
|
|
495
|
+
|
|
496
|
+
mock.delete.mockImplementation(async (key: string) => {
|
|
497
|
+
entries.delete(key);
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
mock.clear.mockImplementation(async () => {
|
|
501
|
+
entries.clear();
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
mock.getStats.mockImplementation(async () => ({
|
|
505
|
+
totalEntries: entries.size,
|
|
506
|
+
totalSizeBytes: entries.size * 100,
|
|
507
|
+
vectorCount: 0,
|
|
508
|
+
cacheHitRate: 0.85,
|
|
509
|
+
}));
|
|
510
|
+
|
|
511
|
+
mock.createIndex.mockResolvedValue(undefined);
|
|
512
|
+
|
|
513
|
+
return Object.assign(mock, { entries });
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Create mock security service
|
|
518
|
+
*/
|
|
519
|
+
export function createMockSecurityService(): MockedInterface<ISecurityService> {
|
|
520
|
+
const mock = createMock<ISecurityService>();
|
|
521
|
+
|
|
522
|
+
mock.validatePath.mockImplementation((path: string) => {
|
|
523
|
+
const blocked = ['../', '~/', '/etc/', '/tmp/', '/var/'];
|
|
524
|
+
return !blocked.some(pattern => path.includes(pattern));
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
mock.validateInput.mockImplementation((input: string, options?: InputValidationOptions) => {
|
|
528
|
+
const maxLength = options?.maxLength ?? 10000;
|
|
529
|
+
if (input.length > maxLength) {
|
|
530
|
+
return { valid: false, errors: [`Input exceeds maximum length of ${maxLength}`] };
|
|
531
|
+
}
|
|
532
|
+
return { valid: true };
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
mock.hashPassword.mockImplementation(async (password: string) => {
|
|
536
|
+
return `hashed:${Buffer.from(password).toString('base64')}`;
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
mock.verifyPassword.mockImplementation(async (password: string, hash: string) => {
|
|
540
|
+
return hash === `hashed:${Buffer.from(password).toString('base64')}`;
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
mock.generateToken.mockImplementation(async (payload: Record<string, unknown>) => {
|
|
544
|
+
return `token:${Buffer.from(JSON.stringify(payload)).toString('base64')}`;
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
mock.verifyToken.mockImplementation(async (token: string) => {
|
|
548
|
+
if (!token.startsWith('token:')) {
|
|
549
|
+
throw new Error('Invalid token');
|
|
550
|
+
}
|
|
551
|
+
return JSON.parse(Buffer.from(token.slice(6), 'base64').toString());
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
mock.executeSecurely.mockImplementation(async () => ({
|
|
555
|
+
stdout: '',
|
|
556
|
+
stderr: '',
|
|
557
|
+
exitCode: 0,
|
|
558
|
+
duration: 100,
|
|
559
|
+
}));
|
|
560
|
+
|
|
561
|
+
return mock;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Create mock swarm coordinator
|
|
566
|
+
*/
|
|
567
|
+
export function createMockSwarmCoordinator(): MockedInterface<ISwarmCoordinator> & { state: SwarmState } {
|
|
568
|
+
const state: SwarmState = {
|
|
569
|
+
id: 'swarm-test',
|
|
570
|
+
topology: 'hierarchical-mesh',
|
|
571
|
+
status: 'active',
|
|
572
|
+
agentCount: 0,
|
|
573
|
+
activeAgentCount: 0,
|
|
574
|
+
createdAt: new Date(),
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
const mock = createMock<ISwarmCoordinator>();
|
|
578
|
+
|
|
579
|
+
mock.initialize.mockImplementation(async (config: SwarmConfig) => {
|
|
580
|
+
state.topology = config.topology;
|
|
581
|
+
state.status = 'active';
|
|
582
|
+
return state;
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
mock.coordinate.mockImplementation(async (agents: string[], task: SwarmTask) => ({
|
|
586
|
+
success: true,
|
|
587
|
+
completedTasks: 1,
|
|
588
|
+
failedTasks: 0,
|
|
589
|
+
totalDuration: 1000,
|
|
590
|
+
agentMetrics: new Map(),
|
|
591
|
+
}));
|
|
592
|
+
|
|
593
|
+
mock.shutdown.mockImplementation(async () => {
|
|
594
|
+
state.status = 'shutdown';
|
|
595
|
+
state.activeAgentCount = 0;
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
mock.getState.mockImplementation(() => state);
|
|
599
|
+
|
|
600
|
+
mock.addAgent.mockImplementation(async () => {
|
|
601
|
+
state.agentCount++;
|
|
602
|
+
state.activeAgentCount++;
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
mock.removeAgent.mockImplementation(async () => {
|
|
606
|
+
state.agentCount--;
|
|
607
|
+
state.activeAgentCount--;
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
mock.broadcast.mockResolvedValue(undefined);
|
|
611
|
+
|
|
612
|
+
return Object.assign(mock, { state });
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Create mock MCP client
|
|
617
|
+
*/
|
|
618
|
+
export function createMockMCPClient(): MockedInterface<IMCPClient> & { connected: boolean } {
|
|
619
|
+
let connected = false;
|
|
620
|
+
|
|
621
|
+
const mock = createMock<IMCPClient>();
|
|
622
|
+
|
|
623
|
+
mock.connect.mockImplementation(async () => {
|
|
624
|
+
connected = true;
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
mock.disconnect.mockImplementation(async () => {
|
|
628
|
+
connected = false;
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
mock.callTool.mockImplementation(async () => ({
|
|
632
|
+
content: [{ type: 'text', text: 'Success' }],
|
|
633
|
+
}));
|
|
634
|
+
|
|
635
|
+
mock.listTools.mockResolvedValue([]);
|
|
636
|
+
|
|
637
|
+
mock.isConnected.mockImplementation(() => connected);
|
|
638
|
+
|
|
639
|
+
mock.getSession.mockReturnValue(null);
|
|
640
|
+
|
|
641
|
+
return Object.assign(mock, { connected });
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* Create mock logger with captured logs
|
|
646
|
+
*/
|
|
647
|
+
export function createMockLogger(): MockedInterface<ILogger> & { logs: Array<{ level: string; message: string; context?: Record<string, unknown>; error?: Error }> } {
|
|
648
|
+
const logs: Array<{ level: string; message: string; context?: Record<string, unknown>; error?: Error }> = [];
|
|
649
|
+
|
|
650
|
+
const mock = createMock<ILogger>();
|
|
651
|
+
|
|
652
|
+
mock.debug.mockImplementation((message: string, context?: Record<string, unknown>) => {
|
|
653
|
+
logs.push({ level: 'debug', message, context });
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
mock.info.mockImplementation((message: string, context?: Record<string, unknown>) => {
|
|
657
|
+
logs.push({ level: 'info', message, context });
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
mock.warn.mockImplementation((message: string, context?: Record<string, unknown>) => {
|
|
661
|
+
logs.push({ level: 'warn', message, context });
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
mock.error.mockImplementation((message: string, error?: Error, context?: Record<string, unknown>) => {
|
|
665
|
+
logs.push({ level: 'error', message, context, error });
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
return Object.assign(mock, { logs });
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* Create a complete test application with all mock services
|
|
673
|
+
*/
|
|
674
|
+
export function createMockApplication(): MockApplication {
|
|
675
|
+
return {
|
|
676
|
+
eventBus: createMockEventBus(),
|
|
677
|
+
taskManager: createMockTaskManager(),
|
|
678
|
+
agentLifecycle: createMockAgentLifecycle(),
|
|
679
|
+
memoryService: createMockMemoryService(),
|
|
680
|
+
securityService: createMockSecurityService(),
|
|
681
|
+
swarmCoordinator: createMockSwarmCoordinator(),
|
|
682
|
+
mcpClient: createMockMCPClient(),
|
|
683
|
+
logger: createMockLogger(),
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/**
|
|
688
|
+
* Mock application type
|
|
689
|
+
*/
|
|
690
|
+
export interface MockApplication {
|
|
691
|
+
eventBus: ReturnType<typeof createMockEventBus>;
|
|
692
|
+
taskManager: ReturnType<typeof createMockTaskManager>;
|
|
693
|
+
agentLifecycle: ReturnType<typeof createMockAgentLifecycle>;
|
|
694
|
+
memoryService: ReturnType<typeof createMockMemoryService>;
|
|
695
|
+
securityService: ReturnType<typeof createMockSecurityService>;
|
|
696
|
+
swarmCoordinator: ReturnType<typeof createMockSwarmCoordinator>;
|
|
697
|
+
mcpClient: ReturnType<typeof createMockMCPClient>;
|
|
698
|
+
logger: ReturnType<typeof createMockLogger>;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
* Reset all mocks in the application
|
|
703
|
+
*/
|
|
704
|
+
export function resetMockApplication(app: MockApplication): void {
|
|
705
|
+
vi.clearAllMocks();
|
|
706
|
+
app.eventBus.publishedEvents.length = 0;
|
|
707
|
+
app.taskManager.tasks.clear();
|
|
708
|
+
app.agentLifecycle.agents.clear();
|
|
709
|
+
app.memoryService.entries.clear();
|
|
710
|
+
app.logger.logs.length = 0;
|
|
711
|
+
}
|