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>
396 lines
12 KiB
TypeScript
396 lines
12 KiB
TypeScript
/**
|
|
* Web App Tests
|
|
*
|
|
* Tests for the @ruvector/edge web dashboard.
|
|
*
|
|
* @module tests/edge/webapp/webapp.test
|
|
*/
|
|
|
|
// Jest test suite
|
|
import {
|
|
dashboardReducer,
|
|
initialState,
|
|
actions,
|
|
selectors,
|
|
} from '../../../src/edge/webapp/store/dashboardReducer';
|
|
import type {
|
|
DashboardState,
|
|
AgentInfo,
|
|
PeerInfo,
|
|
PatternStats,
|
|
CRDTState,
|
|
SystemMetrics,
|
|
} from '../../../src/edge/webapp/types';
|
|
|
|
// ============================================
|
|
// Dashboard Reducer Tests
|
|
// ============================================
|
|
|
|
describe('Dashboard Reducer', () => {
|
|
describe('initialState', () => {
|
|
it('should have correct initial values', () => {
|
|
expect(initialState.connectionStatus).toBe('disconnected');
|
|
expect(initialState.localAgent).toBeNull();
|
|
expect(initialState.peers).toHaveLength(0);
|
|
expect(initialState.patterns.total).toBe(0);
|
|
expect(initialState.crdt.stores).toHaveLength(0);
|
|
expect(initialState.tests.running).toHaveLength(0);
|
|
});
|
|
});
|
|
|
|
describe('SET_CONNECTION_STATUS', () => {
|
|
it('should update connection status', () => {
|
|
const newState = dashboardReducer(
|
|
initialState,
|
|
actions.setConnectionStatus('connected')
|
|
);
|
|
expect(newState.connectionStatus).toBe('connected');
|
|
});
|
|
|
|
it('should preserve other state', () => {
|
|
const stateWithPeers: DashboardState = {
|
|
...initialState,
|
|
peers: [{ id: 'test-peer' } as PeerInfo],
|
|
};
|
|
const newState = dashboardReducer(
|
|
stateWithPeers,
|
|
actions.setConnectionStatus('connected')
|
|
);
|
|
expect(newState.peers).toHaveLength(1);
|
|
});
|
|
});
|
|
|
|
describe('SET_LOCAL_AGENT', () => {
|
|
it('should set local agent info', () => {
|
|
const agent: AgentInfo = {
|
|
id: 'agent-123',
|
|
publicKey: 'pk-abc',
|
|
createdAt: Date.now(),
|
|
capabilities: ['p2p', 'crdt'],
|
|
};
|
|
const newState = dashboardReducer(
|
|
initialState,
|
|
actions.setLocalAgent(agent)
|
|
);
|
|
expect(newState.localAgent).toEqual(agent);
|
|
});
|
|
});
|
|
|
|
describe('Peer Management', () => {
|
|
const testPeer: PeerInfo = {
|
|
id: 'peer-1',
|
|
publicKey: 'pk-peer-1',
|
|
connectionState: 'connected',
|
|
latencyMs: 25,
|
|
lastSeen: Date.now(),
|
|
patternsShared: 5,
|
|
};
|
|
|
|
it('should add peer', () => {
|
|
const newState = dashboardReducer(
|
|
initialState,
|
|
actions.addPeer(testPeer)
|
|
);
|
|
expect(newState.peers).toHaveLength(1);
|
|
expect(newState.peers[0].id).toBe('peer-1');
|
|
});
|
|
|
|
it('should update peer', () => {
|
|
const stateWithPeer: DashboardState = {
|
|
...initialState,
|
|
peers: [testPeer],
|
|
};
|
|
const newState = dashboardReducer(
|
|
stateWithPeer,
|
|
actions.updatePeer('peer-1', { latencyMs: 50, patternsShared: 10 })
|
|
);
|
|
expect(newState.peers[0].latencyMs).toBe(50);
|
|
expect(newState.peers[0].patternsShared).toBe(10);
|
|
});
|
|
|
|
it('should remove peer', () => {
|
|
const stateWithPeer: DashboardState = {
|
|
...initialState,
|
|
peers: [testPeer],
|
|
};
|
|
const newState = dashboardReducer(
|
|
stateWithPeer,
|
|
actions.removePeer('peer-1')
|
|
);
|
|
expect(newState.peers).toHaveLength(0);
|
|
});
|
|
|
|
it('should not update non-existent peer', () => {
|
|
const stateWithPeer: DashboardState = {
|
|
...initialState,
|
|
peers: [testPeer],
|
|
};
|
|
const newState = dashboardReducer(
|
|
stateWithPeer,
|
|
actions.updatePeer('non-existent', { latencyMs: 100 })
|
|
);
|
|
expect(newState.peers[0].latencyMs).toBe(25);
|
|
});
|
|
});
|
|
|
|
describe('UPDATE_PATTERNS', () => {
|
|
it('should update pattern stats', () => {
|
|
const stats: PatternStats = {
|
|
total: 100,
|
|
local: 80,
|
|
synced: 20,
|
|
pending: 5,
|
|
categories: { test: 50, security: 30, performance: 20 },
|
|
};
|
|
const newState = dashboardReducer(
|
|
initialState,
|
|
actions.updatePatterns(stats)
|
|
);
|
|
expect(newState.patterns.total).toBe(100);
|
|
expect(newState.patterns.categories.test).toBe(50);
|
|
});
|
|
});
|
|
|
|
describe('UPDATE_CRDT', () => {
|
|
it('should update CRDT state', () => {
|
|
const crdtState: CRDTState = {
|
|
stores: [
|
|
{ id: 'store-1', type: 'GCounter', size: 100, version: 5 },
|
|
],
|
|
totalOperations: 50,
|
|
conflictsResolved: 2,
|
|
lastSync: Date.now(),
|
|
};
|
|
const newState = dashboardReducer(
|
|
initialState,
|
|
actions.updateCRDT(crdtState)
|
|
);
|
|
expect(newState.crdt.stores).toHaveLength(1);
|
|
expect(newState.crdt.totalOperations).toBe(50);
|
|
});
|
|
});
|
|
|
|
describe('Test Management', () => {
|
|
it('should add test', () => {
|
|
const test = {
|
|
id: 'test-1',
|
|
name: 'Test Suite 1',
|
|
status: 'running' as const,
|
|
progress: 50,
|
|
startedAt: Date.now(),
|
|
};
|
|
const newState = dashboardReducer(
|
|
initialState,
|
|
actions.addTest(test)
|
|
);
|
|
expect(newState.tests.running).toHaveLength(1);
|
|
});
|
|
|
|
it('should complete test', () => {
|
|
const runningTest = {
|
|
id: 'test-1',
|
|
name: 'Test Suite 1',
|
|
status: 'running' as const,
|
|
progress: 100,
|
|
startedAt: Date.now() - 5000,
|
|
};
|
|
const stateWithTest: DashboardState = {
|
|
...initialState,
|
|
tests: {
|
|
...initialState.tests,
|
|
running: [runningTest],
|
|
},
|
|
};
|
|
const result = {
|
|
id: 'test-1',
|
|
name: 'Test Suite 1',
|
|
status: 'passed' as const,
|
|
duration: 5000,
|
|
passed: 10,
|
|
failed: 0,
|
|
total: 10,
|
|
};
|
|
const newState = dashboardReducer(
|
|
stateWithTest,
|
|
actions.completeTest(result)
|
|
);
|
|
expect(newState.tests.running).toHaveLength(0);
|
|
expect(newState.tests.completed).toHaveLength(1);
|
|
expect(newState.tests.completed[0].status).toBe('passed');
|
|
});
|
|
});
|
|
|
|
describe('UPDATE_METRICS', () => {
|
|
it('should update system metrics', () => {
|
|
const metrics: SystemMetrics = {
|
|
memoryUsage: 256,
|
|
cpuUsage: 45,
|
|
networkLatency: 30,
|
|
messagesPerSecond: 100,
|
|
uptime: 3600000,
|
|
};
|
|
const newState = dashboardReducer(
|
|
initialState,
|
|
actions.updateMetrics(metrics)
|
|
);
|
|
expect(newState.metrics.memoryUsage).toBe(256);
|
|
expect(newState.metrics.uptime).toBe(3600000);
|
|
});
|
|
});
|
|
|
|
describe('RESET', () => {
|
|
it('should reset to initial state', () => {
|
|
const modifiedState: DashboardState = {
|
|
...initialState,
|
|
connectionStatus: 'connected',
|
|
peers: [{ id: 'test' } as PeerInfo],
|
|
patterns: { ...initialState.patterns, total: 100 },
|
|
};
|
|
const newState = dashboardReducer(modifiedState, actions.reset());
|
|
expect(newState).toEqual(initialState);
|
|
});
|
|
});
|
|
});
|
|
|
|
// ============================================
|
|
// Selectors Tests
|
|
// ============================================
|
|
|
|
describe('Dashboard Selectors', () => {
|
|
const connectedState: DashboardState = {
|
|
...initialState,
|
|
connectionStatus: 'connected',
|
|
peers: [
|
|
{ id: 'peer-1', connectionState: 'connected' } as PeerInfo,
|
|
{ id: 'peer-2', connectionState: 'disconnected' } as PeerInfo,
|
|
],
|
|
patterns: { total: 100, local: 80, synced: 20, pending: 5, categories: {} },
|
|
crdt: { stores: [{ id: 's1' } as any], totalOperations: 10, conflictsResolved: 1, lastSync: 0 },
|
|
metrics: { ...initialState.metrics, uptime: 3661000, memoryUsage: 256 },
|
|
};
|
|
|
|
describe('isConnected', () => {
|
|
it('should return true when connected', () => {
|
|
expect(selectors.isConnected(connectedState)).toBe(true);
|
|
});
|
|
|
|
it('should return false when disconnected', () => {
|
|
expect(selectors.isConnected(initialState)).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('getPeerCount', () => {
|
|
it('should return total peer count', () => {
|
|
expect(selectors.getPeerCount(connectedState)).toBe(2);
|
|
});
|
|
|
|
it('should return 0 for no peers', () => {
|
|
expect(selectors.getPeerCount(initialState)).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('getConnectedPeers', () => {
|
|
it('should return only connected peers', () => {
|
|
const connected = selectors.getConnectedPeers(connectedState);
|
|
expect(connected).toHaveLength(1);
|
|
expect(connected[0].id).toBe('peer-1');
|
|
});
|
|
});
|
|
|
|
describe('getTotalPatterns', () => {
|
|
it('should return total pattern count', () => {
|
|
expect(selectors.getTotalPatterns(connectedState)).toBe(100);
|
|
});
|
|
});
|
|
|
|
describe('getSyncedPatterns', () => {
|
|
it('should return synced pattern count', () => {
|
|
expect(selectors.getSyncedPatterns(connectedState)).toBe(20);
|
|
});
|
|
});
|
|
|
|
describe('getCRDTStoreCount', () => {
|
|
it('should return CRDT store count', () => {
|
|
expect(selectors.getCRDTStoreCount(connectedState)).toBe(1);
|
|
});
|
|
});
|
|
|
|
describe('getUptime', () => {
|
|
it('should format hours and minutes', () => {
|
|
expect(selectors.getUptime(connectedState)).toBe('1h 1m');
|
|
});
|
|
|
|
it('should format minutes and seconds', () => {
|
|
const state = { ...initialState, metrics: { ...initialState.metrics, uptime: 65000 } };
|
|
expect(selectors.getUptime(state)).toBe('1m 5s');
|
|
});
|
|
|
|
it('should format seconds only', () => {
|
|
const state = { ...initialState, metrics: { ...initialState.metrics, uptime: 45000 } };
|
|
expect(selectors.getUptime(state)).toBe('45s');
|
|
});
|
|
});
|
|
|
|
describe('getHealthStatus', () => {
|
|
it('should return healthy when connected and low memory', () => {
|
|
expect(selectors.getHealthStatus(connectedState)).toBe('healthy');
|
|
});
|
|
|
|
it('should return error when connection status is error', () => {
|
|
const errorState = { ...connectedState, connectionStatus: 'error' as const };
|
|
expect(selectors.getHealthStatus(errorState)).toBe('error');
|
|
});
|
|
|
|
it('should return warning when not connected', () => {
|
|
expect(selectors.getHealthStatus(initialState)).toBe('warning');
|
|
});
|
|
|
|
it('should return warning when memory usage is high', () => {
|
|
const highMemState = {
|
|
...connectedState,
|
|
metrics: { ...connectedState.metrics, memoryUsage: 600 },
|
|
};
|
|
expect(selectors.getHealthStatus(highMemState)).toBe('warning');
|
|
});
|
|
});
|
|
});
|
|
|
|
// ============================================
|
|
// Type Tests
|
|
// ============================================
|
|
|
|
describe('Types', () => {
|
|
describe('DEFAULT_SETTINGS', () => {
|
|
it('should have correct default values', async () => {
|
|
const { DEFAULT_SETTINGS } = await import('../../../src/edge/webapp/types');
|
|
expect(DEFAULT_SETTINGS.theme).toBe('system');
|
|
expect(DEFAULT_SETTINGS.autoConnect).toBe(true);
|
|
expect(DEFAULT_SETTINGS.notifications).toBe(true);
|
|
expect(DEFAULT_SETTINGS.syncInterval).toBe(30);
|
|
expect(DEFAULT_SETTINGS.maxPeers).toBe(10);
|
|
expect(DEFAULT_SETTINGS.debug).toBe(false);
|
|
});
|
|
});
|
|
});
|
|
|
|
// ============================================
|
|
// Integration Tests
|
|
// ============================================
|
|
|
|
describe('Webapp Module Exports', () => {
|
|
it('should export all required components', async () => {
|
|
const webapp = await import('../../../src/edge/webapp');
|
|
|
|
expect(webapp.WEBAPP_CAPABILITIES).toBeDefined();
|
|
expect(webapp.WEBAPP_CAPABILITIES.version).toBe('1.0.0');
|
|
expect(webapp.WEBAPP_CAPABILITIES.features.dashboard).toBe(true);
|
|
|
|
expect(webapp.dashboardReducer).toBeDefined();
|
|
expect(webapp.initialState).toBeDefined();
|
|
expect(webapp.actions).toBeDefined();
|
|
expect(webapp.selectors).toBeDefined();
|
|
|
|
expect(webapp.DEFAULT_SETTINGS).toBeDefined();
|
|
});
|
|
});
|