Files
Codeman/test/routes/system-routes.test.ts
T
arkonandClaude Opus 4.6 442cc19c3c test: add shared mock infrastructure and route tests (phase 7)
Consolidate duplicated MockSession/MockStateStore into test/mocks/,
migrate respawn tests to shared mocks, and add 58 route tests for
session, system, and respawn endpoints using Fastify app.inject().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 14:33:18 +01:00

167 lines
5.4 KiB
TypeScript

/**
* @fileoverview Tests for system route handlers.
*
* Uses app.inject() — no real HTTP ports needed.
* Port: N/A (app.inject doesn't open ports)
*
* Note: Some system routes access singleton modules (subagentWatcher, imageWatcher)
* directly. Those routes are tested lightly here since we can't easily mock singletons
* without vi.mock(). Focus is on routes that use the port context.
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { createRouteTestHarness, type RouteTestHarness } from './_route-test-utils.js';
import { registerSystemRoutes } from '../../src/web/routes/system-routes.js';
describe('system-routes', () => {
let harness: RouteTestHarness;
beforeEach(async () => {
harness = await createRouteTestHarness(registerSystemRoutes);
});
afterEach(async () => {
await harness.app.close();
});
// ========== GET /api/status ==========
describe('GET /api/status', () => {
it('returns server status', async () => {
const res = await harness.app.inject({ method: 'GET', url: '/api/status' });
expect(res.statusCode).toBe(200);
expect(harness.ctx.getLightState).toHaveBeenCalled();
});
});
// ========== GET /api/config ==========
describe('GET /api/config', () => {
it('returns config', async () => {
const res = await harness.app.inject({ method: 'GET', url: '/api/config' });
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(true);
expect(body.config).toBeDefined();
});
});
// ========== PUT /api/config ==========
describe('PUT /api/config', () => {
it('updates config with valid payload', async () => {
const res = await harness.app.inject({
method: 'PUT',
url: '/api/config',
payload: { maxConcurrentSessions: 10 },
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(true);
expect(harness.ctx.store.setConfig).toHaveBeenCalled();
});
it('rejects unknown config fields (strict schema)', async () => {
const res = await harness.app.inject({
method: 'PUT',
url: '/api/config',
payload: { unknownField: 'invalid' },
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(false);
});
});
// ========== GET /api/stats ==========
describe('GET /api/stats', () => {
it('returns aggregate stats', async () => {
const res = await harness.app.inject({ method: 'GET', url: '/api/stats' });
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(true);
expect(body.stats).toBeDefined();
});
});
// ========== GET /api/token-stats ==========
describe('GET /api/token-stats', () => {
it('returns daily and aggregate token stats', async () => {
const res = await harness.app.inject({ method: 'GET', url: '/api/token-stats' });
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(true);
expect(body.daily).toBeDefined();
expect(body.totals).toBeDefined();
});
});
// ========== GET /api/debug/memory ==========
describe('GET /api/debug/memory', () => {
it('returns memory and map usage info', async () => {
const res = await harness.app.inject({ method: 'GET', url: '/api/debug/memory' });
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.memory).toBeDefined();
expect(body.memory.rssMB).toBeGreaterThan(0);
expect(body.mapSizes).toBeDefined();
expect(body.uptime).toBeDefined();
});
});
// ========== GET /api/system/stats ==========
describe('GET /api/system/stats', () => {
it('returns CPU and memory stats', async () => {
const res = await harness.app.inject({ method: 'GET', url: '/api/system/stats' });
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body).toHaveProperty('cpu');
expect(body).toHaveProperty('memory');
});
});
// ========== POST /api/cleanup-state ==========
describe('POST /api/cleanup-state', () => {
it('cleans up stale session state', async () => {
const res = await harness.app.inject({ method: 'POST', url: '/api/cleanup-state' });
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(true);
expect(body.cleanedSessions).toBe(0);
expect(harness.ctx.store.cleanupStaleSessions).toHaveBeenCalled();
});
});
// ========== GET /api/subagents ==========
describe('GET /api/subagents', () => {
it('returns subagent list', async () => {
const res = await harness.app.inject({ method: 'GET', url: '/api/subagents' });
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(true);
expect(body.data).toBeDefined();
});
});
// ========== POST /api/auth/revoke ==========
describe('POST /api/auth/revoke', () => {
it('returns success even without auth sessions', async () => {
const res = await harness.app.inject({
method: 'POST',
url: '/api/auth/revoke',
payload: {},
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(true);
});
});
});