mirror of
https://github.com/proffesor-for-testing/agentic-qe.git
synced 2026-09-19 08:45:47 +08:00
f13eb30ce1
Phase 4 Implementation: - SignalingServer: WebSocket server for WebRTC peer discovery - AgentSpawnAPI: REST API for spawning QE agents from dashboard - EdgeServer: Combined HTTP + WebSocket server entry point CLI Enhancements: - Added `aqe agent spawn <type>` command with task execution - Added `aqe agent list` to view spawned agents - Agent state persists to .agentic-qe/agents/ directory P2PService Fixes (from brutal honesty review): - Integrated real SignalingClient for WebRTC signaling - Added RTCPeerConnection management with ICE handling - Replaced mock connection with actual WebRTC setup - Fixed PatternCategory enum mapping Dashboard Fixes: - Added error state display instead of silent failures - Shows red error banner when agent spawn fails Tests: - AgentSpawnAPI.test.ts (17 tests) - SignalingServer.test.ts (7 tests) - Webapp component tests 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
292 lines
8.4 KiB
TypeScript
292 lines
8.4 KiB
TypeScript
/**
|
|
* Agent Spawn API Tests
|
|
*
|
|
* Tests for the QE agent spawn service that manages agent lifecycle.
|
|
*
|
|
* @module tests/edge/server/AgentSpawnAPI.test
|
|
*/
|
|
|
|
import { AgentSpawnService } from '../../../src/edge/server/AgentSpawnAPI';
|
|
|
|
describe('AgentSpawnService', () => {
|
|
let service: AgentSpawnService;
|
|
|
|
beforeEach(() => {
|
|
service = new AgentSpawnService({
|
|
maxAgents: 5,
|
|
projectPath: process.cwd(),
|
|
});
|
|
});
|
|
|
|
afterEach(() => {
|
|
// Cancel any running agents
|
|
for (const agent of service.list({ status: 'running' })) {
|
|
service.cancel(agent.id);
|
|
}
|
|
});
|
|
|
|
describe('spawn', () => {
|
|
it('should spawn an agent with dry run', async () => {
|
|
const result = await service.spawn({
|
|
agentType: 'qe-test-generator',
|
|
task: 'Generate unit tests for UserService',
|
|
options: { dryRun: true },
|
|
});
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.agentId).toBeDefined();
|
|
|
|
const status = service.getStatus(result.agentId!);
|
|
expect(status).toBeDefined();
|
|
expect(status!.agentType).toBe('qe-test-generator');
|
|
expect(status!.status).toBe('completed');
|
|
});
|
|
|
|
it('should enforce max agents limit', async () => {
|
|
// Spawn max agents with dry run
|
|
const agents: string[] = [];
|
|
|
|
for (let i = 0; i < 5; i++) {
|
|
const result = await service.spawn({
|
|
agentType: 'qe-test-generator',
|
|
task: `Task ${i}`,
|
|
options: { dryRun: true },
|
|
});
|
|
expect(result.success).toBe(true);
|
|
agents.push(result.agentId!);
|
|
}
|
|
|
|
// All agents completed, so we can spawn more
|
|
const extraResult = await service.spawn({
|
|
agentType: 'qe-coverage-analyzer',
|
|
task: 'Analyze coverage',
|
|
options: { dryRun: true },
|
|
});
|
|
|
|
expect(extraResult.success).toBe(true);
|
|
});
|
|
|
|
it('should map agent types correctly', async () => {
|
|
const testCases = [
|
|
{ input: 'qe-test-generator', expected: 'test-generator' },
|
|
{ input: 'qe-coverage-analyzer', expected: 'coverage-analyzer' },
|
|
{ input: 'qe-security-scanner', expected: 'security-scanner' },
|
|
];
|
|
|
|
for (const { input, expected } of testCases) {
|
|
const result = await service.spawn({
|
|
agentType: input,
|
|
task: 'Test task',
|
|
options: { dryRun: true },
|
|
});
|
|
|
|
expect(result.success).toBe(true);
|
|
|
|
const output = service.getOutput(result.agentId!);
|
|
expect(output).toBeDefined();
|
|
expect(output!.length).toBeGreaterThan(0);
|
|
expect(output![0]).toContain(expected);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('getStatus', () => {
|
|
it('should return null for unknown agent', () => {
|
|
const status = service.getStatus('unknown-agent');
|
|
expect(status).toBeNull();
|
|
});
|
|
|
|
it('should return correct status for spawned agent', async () => {
|
|
const result = await service.spawn({
|
|
agentType: 'qe-test-writer',
|
|
task: 'Write failing tests',
|
|
options: { dryRun: true },
|
|
});
|
|
|
|
const status = service.getStatus(result.agentId!);
|
|
|
|
expect(status).toBeDefined();
|
|
expect(status!.id).toBe(result.agentId);
|
|
expect(status!.agentType).toBe('qe-test-writer');
|
|
expect(status!.task).toBe('Write failing tests');
|
|
expect(status!.status).toBe('completed');
|
|
});
|
|
});
|
|
|
|
describe('getOutput', () => {
|
|
it('should return null for unknown agent', () => {
|
|
const output = service.getOutput('unknown-agent');
|
|
expect(output).toBeNull();
|
|
});
|
|
|
|
it('should return output lines', async () => {
|
|
const result = await service.spawn({
|
|
agentType: 'qe-flaky-investigator',
|
|
task: 'Find flaky tests',
|
|
options: { dryRun: true },
|
|
});
|
|
|
|
const output = service.getOutput(result.agentId!);
|
|
|
|
expect(output).toBeDefined();
|
|
expect(Array.isArray(output)).toBe(true);
|
|
});
|
|
|
|
it('should limit output lines with lastN', async () => {
|
|
const result = await service.spawn({
|
|
agentType: 'qe-code-reviewer',
|
|
task: 'Review code quality',
|
|
options: { dryRun: true },
|
|
});
|
|
|
|
const fullOutput = service.getOutput(result.agentId!);
|
|
const lastOutput = service.getOutput(result.agentId!, 1);
|
|
|
|
expect(fullOutput).toBeDefined();
|
|
expect(lastOutput).toBeDefined();
|
|
expect(lastOutput!.length).toBeLessThanOrEqual(1);
|
|
});
|
|
});
|
|
|
|
describe('list', () => {
|
|
it('should list all agents', async () => {
|
|
await service.spawn({
|
|
agentType: 'qe-test-generator',
|
|
task: 'Task 1',
|
|
options: { dryRun: true },
|
|
});
|
|
|
|
await service.spawn({
|
|
agentType: 'qe-coverage-analyzer',
|
|
task: 'Task 2',
|
|
options: { dryRun: true },
|
|
});
|
|
|
|
const agents = service.list();
|
|
|
|
expect(agents.length).toBeGreaterThanOrEqual(2);
|
|
});
|
|
|
|
it('should filter by status', async () => {
|
|
await service.spawn({
|
|
agentType: 'qe-test-generator',
|
|
task: 'Task 1',
|
|
options: { dryRun: true },
|
|
});
|
|
|
|
const completed = service.list({ status: 'completed' });
|
|
const running = service.list({ status: 'running' });
|
|
|
|
expect(completed.every((a) => a.status === 'completed')).toBe(true);
|
|
expect(running.every((a) => a.status === 'running')).toBe(true);
|
|
});
|
|
|
|
it('should filter by agent type', async () => {
|
|
await service.spawn({
|
|
agentType: 'qe-test-generator',
|
|
task: 'Task 1',
|
|
options: { dryRun: true },
|
|
});
|
|
|
|
await service.spawn({
|
|
agentType: 'qe-security-scanner',
|
|
task: 'Task 2',
|
|
options: { dryRun: true },
|
|
});
|
|
|
|
const generators = service.list({ agentType: 'qe-test-generator' });
|
|
const scanners = service.list({ agentType: 'qe-security-scanner' });
|
|
|
|
expect(generators.every((a) => a.agentType === 'qe-test-generator')).toBe(true);
|
|
expect(scanners.every((a) => a.agentType === 'qe-security-scanner')).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('cancel', () => {
|
|
it('should return false for unknown agent', () => {
|
|
const cancelled = service.cancel('unknown-agent');
|
|
expect(cancelled).toBe(false);
|
|
});
|
|
|
|
it('should return false for completed agent', async () => {
|
|
const result = await service.spawn({
|
|
agentType: 'qe-test-generator',
|
|
task: 'Task 1',
|
|
options: { dryRun: true },
|
|
});
|
|
|
|
// Agent is already completed (dry run)
|
|
const cancelled = service.cancel(result.agentId!);
|
|
expect(cancelled).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('cleanup', () => {
|
|
it('should clean up old completed agents', async () => {
|
|
await service.spawn({
|
|
agentType: 'qe-test-generator',
|
|
task: 'Old task',
|
|
options: { dryRun: true },
|
|
});
|
|
|
|
// Wait a tiny bit so the timestamp difference is > 0
|
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
|
|
// With maxAge=1ms, just-completed agents should be cleaned
|
|
const cleaned = service.cleanup(1);
|
|
|
|
expect(cleaned).toBeGreaterThanOrEqual(1);
|
|
expect(service.list().length).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('getAvailableTypes', () => {
|
|
it('should return list of available agent types', () => {
|
|
const types = service.getAvailableTypes();
|
|
|
|
expect(Array.isArray(types)).toBe(true);
|
|
expect(types.length).toBeGreaterThan(0);
|
|
|
|
const testGenerator = types.find((t) => t.id === 'qe-test-generator');
|
|
expect(testGenerator).toBeDefined();
|
|
expect(testGenerator!.cliType).toBe('test-generator');
|
|
});
|
|
});
|
|
|
|
describe('events', () => {
|
|
it('should emit agent:created event', async () => {
|
|
const created: { agentId: string; agentType: string }[] = [];
|
|
|
|
service.on('agent:created', (data) => {
|
|
created.push(data);
|
|
});
|
|
|
|
await service.spawn({
|
|
agentType: 'qe-performance-tester',
|
|
task: 'Run performance tests',
|
|
options: { dryRun: true },
|
|
});
|
|
|
|
expect(created.length).toBe(1);
|
|
expect(created[0].agentType).toBe('qe-performance-tester');
|
|
});
|
|
|
|
it('should emit agent:completed event', async () => {
|
|
const completed: { agentId: string; exitCode: number }[] = [];
|
|
|
|
service.on('agent:completed', (data) => {
|
|
completed.push(data);
|
|
});
|
|
|
|
await service.spawn({
|
|
agentType: 'qe-api-validator',
|
|
task: 'Validate API contracts',
|
|
options: { dryRun: true },
|
|
});
|
|
|
|
expect(completed.length).toBe(1);
|
|
expect(completed[0].exitCode).toBe(0);
|
|
});
|
|
});
|
|
});
|