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>
This commit is contained in:
arkon
2026-03-01 14:33:18 +01:00
co-authored by Claude Opus 4.6
parent 97ca13916a
commit 442cc19c3c
13 changed files with 2421 additions and 428 deletions
+40
View File
@@ -0,0 +1,40 @@
/**
* Shared utilities for route testing.
*
* Creates minimal Fastify instances with just the route module under test
* and a mock context. Uses app.inject() for HTTP testing without real ports.
*/
import Fastify, { type FastifyInstance } from 'fastify';
import fastifyCookie from '@fastify/cookie';
import { createMockRouteContext, type MockRouteContext } from '../mocks/index.js';
export interface RouteTestHarness {
app: FastifyInstance;
ctx: MockRouteContext;
}
/**
* Creates a Fastify instance with a route module registered against a mock context.
*
* @param registerFn - The route registration function (e.g., registerSessionRoutes).
* Uses `any` for ctx parameter because route functions expect typed port intersections
* that MockRouteContext satisfies structurally but not nominally.
* @param ctxOptions - Optional overrides for the mock context
*/
export async function createRouteTestHarness(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
registerFn: (app: FastifyInstance, ctx: any) => void,
ctxOptions?: { sessionId?: string },
): Promise<RouteTestHarness> {
const app = Fastify({ logger: false });
// Register cookie plugin — some routes access req.cookies
await app.register(fastifyCookie);
const ctx = createMockRouteContext(ctxOptions);
registerFn(app, ctx);
await app.ready();
return { app, ctx };
}
+228
View File
@@ -0,0 +1,228 @@
/**
* @fileoverview Tests for respawn route handlers.
*
* Uses app.inject() — no real HTTP ports needed.
* Port: N/A (app.inject doesn't open ports)
*/
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { createRouteTestHarness, type RouteTestHarness } from './_route-test-utils.js';
import { registerRespawnRoutes } from '../../src/web/routes/respawn-routes.js';
describe('respawn-routes', () => {
let harness: RouteTestHarness;
beforeEach(async () => {
harness = await createRouteTestHarness(registerRespawnRoutes);
});
afterEach(async () => {
await harness.app.close();
});
// ========== GET /api/sessions/:id/respawn ==========
describe('GET /api/sessions/:id/respawn', () => {
it('returns disabled status when no controller exists', async () => {
const res = await harness.app.inject({
method: 'GET',
url: `/api/sessions/${harness.ctx._sessionId}/respawn`,
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.enabled).toBe(false);
expect(body.status).toBeNull();
});
it('returns status when controller exists', async () => {
const mockController = {
getStatus: vi.fn(() => ({
state: 'watching',
health: 100,
cycleCount: 0,
})),
getConfig: vi.fn(() => ({})),
start: vi.fn(),
stop: vi.fn(),
updateConfig: vi.fn(),
};
harness.ctx.respawnControllers.set(harness.ctx._sessionId, mockController as never);
const res = await harness.app.inject({
method: 'GET',
url: `/api/sessions/${harness.ctx._sessionId}/respawn`,
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.enabled).toBe(true);
expect(body.state).toBe('watching');
});
});
// ========== GET /api/sessions/:id/respawn/config ==========
describe('GET /api/sessions/:id/respawn/config', () => {
it('returns null config when no controller and no pre-saved config', async () => {
const res = await harness.app.inject({
method: 'GET',
url: `/api/sessions/${harness.ctx._sessionId}/respawn/config`,
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(true);
expect(body.config).toBeNull();
expect(body.active).toBe(false);
});
it('returns active config when controller exists', async () => {
const mockConfig = { idleTimeoutMs: 5000, enabled: true };
const mockController = {
getConfig: vi.fn(() => mockConfig),
getStatus: vi.fn(() => ({})),
};
harness.ctx.respawnControllers.set(harness.ctx._sessionId, mockController as never);
const res = await harness.app.inject({
method: 'GET',
url: `/api/sessions/${harness.ctx._sessionId}/respawn/config`,
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(true);
expect(body.active).toBe(true);
expect(body.config.idleTimeoutMs).toBe(5000);
});
});
// ========== POST /api/sessions/:id/respawn/stop ==========
describe('POST /api/sessions/:id/respawn/stop', () => {
it('returns error when no controller exists', async () => {
const res = await harness.app.inject({
method: 'POST',
url: `/api/sessions/${harness.ctx._sessionId}/respawn/stop`,
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(false);
});
it('stops existing controller and cleans up', async () => {
const mockController = {
stop: vi.fn(),
getConfig: vi.fn(() => ({})),
getStatus: vi.fn(() => ({})),
};
harness.ctx.respawnControllers.set(harness.ctx._sessionId, mockController as never);
const res = await harness.app.inject({
method: 'POST',
url: `/api/sessions/${harness.ctx._sessionId}/respawn/stop`,
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(true);
expect(mockController.stop).toHaveBeenCalled();
expect(harness.ctx.respawnControllers.has(harness.ctx._sessionId)).toBe(false);
expect(harness.ctx.broadcast).toHaveBeenCalledWith('respawn:stopped', {
sessionId: harness.ctx._sessionId,
});
});
});
// ========== POST /api/sessions/:id/respawn/start ==========
describe('POST /api/sessions/:id/respawn/start', () => {
it('returns 404 for unknown session', async () => {
const res = await harness.app.inject({
method: 'POST',
url: '/api/sessions/nonexistent/respawn/start',
});
// findSessionOrFail throws with statusCode 404
expect(res.statusCode).toBe(404);
});
it('rejects opencode sessions', async () => {
harness.ctx._session.mode = 'opencode';
const res = await harness.app.inject({
method: 'POST',
url: `/api/sessions/${harness.ctx._sessionId}/respawn/start`,
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(false);
});
});
// ========== PUT /api/sessions/:id/respawn/config ==========
describe('PUT /api/sessions/:id/respawn/config', () => {
it('returns 404 for unknown session', async () => {
const res = await harness.app.inject({
method: 'PUT',
url: '/api/sessions/nonexistent/respawn/config',
payload: { idleTimeoutMs: 5000 },
});
expect(res.statusCode).toBe(404);
});
it('rejects invalid config', async () => {
const res = await harness.app.inject({
method: 'PUT',
url: `/api/sessions/${harness.ctx._sessionId}/respawn/config`,
payload: { idleTimeoutMs: 'not-a-number' },
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(false);
});
it('updates running controller config', async () => {
const mockController = {
updateConfig: vi.fn(),
getConfig: vi.fn(() => ({ idleTimeoutMs: 5000 })),
getStatus: vi.fn(() => ({})),
};
harness.ctx.respawnControllers.set(harness.ctx._sessionId, mockController as never);
const res = await harness.app.inject({
method: 'PUT',
url: `/api/sessions/${harness.ctx._sessionId}/respawn/config`,
payload: { idleTimeoutMs: 5000 },
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(true);
expect(mockController.updateConfig).toHaveBeenCalled();
expect(harness.ctx.broadcast).toHaveBeenCalledWith(
'respawn:configUpdated',
expect.objectContaining({ sessionId: harness.ctx._sessionId }),
);
});
it('saves pre-config when no controller running', async () => {
const res = await harness.app.inject({
method: 'PUT',
url: `/api/sessions/${harness.ctx._sessionId}/respawn/config`,
payload: { idleTimeoutMs: 5000 },
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(true);
expect(harness.ctx.mux.updateRespawnConfig).toHaveBeenCalled();
});
});
// ========== POST /api/sessions/:id/respawn/enable ==========
describe('POST /api/sessions/:id/respawn/enable', () => {
it('returns 404 for unknown session', async () => {
const res = await harness.app.inject({
method: 'POST',
url: '/api/sessions/nonexistent/respawn/enable',
});
// findSessionOrFail not used here — let's check the actual response
expect(res.statusCode).toBe(404);
});
});
});
+465
View File
@@ -0,0 +1,465 @@
/**
* @fileoverview Tests for session route handlers.
*
* Uses app.inject() (Fastify's built-in test helper) — no real HTTP ports needed.
* Port: N/A (app.inject doesn't open ports)
*/
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { createRouteTestHarness, type RouteTestHarness } from './_route-test-utils.js';
import { registerSessionRoutes } from '../../src/web/routes/session-routes.js';
describe('session-routes', () => {
let harness: RouteTestHarness;
beforeEach(async () => {
harness = await createRouteTestHarness(registerSessionRoutes);
});
afterEach(async () => {
await harness.app.close();
});
// ========== GET /api/sessions ==========
describe('GET /api/sessions', () => {
it('returns session list when sessions exist', async () => {
const res = await harness.app.inject({ method: 'GET', url: '/api/sessions' });
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(Array.isArray(body)).toBe(true);
expect(body).toHaveLength(1);
});
it('returns empty array when no sessions', async () => {
harness.ctx.sessions.clear();
const res = await harness.app.inject({ method: 'GET', url: '/api/sessions' });
expect(res.statusCode).toBe(200);
expect(JSON.parse(res.body)).toEqual([]);
});
});
// ========== GET /api/sessions/:id ==========
describe('GET /api/sessions/:id', () => {
it('returns session state for existing session', async () => {
const res = await harness.app.inject({
method: 'GET',
url: `/api/sessions/${harness.ctx._sessionId}`,
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.id).toBe(harness.ctx._sessionId);
});
it('returns error for unknown session', async () => {
const res = await harness.app.inject({
method: 'GET',
url: '/api/sessions/nonexistent',
});
expect(res.statusCode).toBe(200); // returns error in body, not HTTP 404
const body = JSON.parse(res.body);
expect(body.success).toBe(false);
expect(body.error).toBeDefined();
});
});
// ========== DELETE /api/sessions/:id ==========
describe('DELETE /api/sessions/:id', () => {
it('deletes existing session', async () => {
const res = await harness.app.inject({
method: 'DELETE',
url: `/api/sessions/${harness.ctx._sessionId}`,
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(true);
expect(harness.ctx.cleanupSession).toHaveBeenCalledWith(
harness.ctx._sessionId,
true,
'user_delete',
);
});
it('returns error for unknown session', async () => {
const res = await harness.app.inject({
method: 'DELETE',
url: '/api/sessions/nonexistent',
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(false);
});
});
// ========== DELETE /api/sessions (delete all) ==========
describe('DELETE /api/sessions', () => {
it('deletes all sessions', async () => {
const res = await harness.app.inject({
method: 'DELETE',
url: '/api/sessions',
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(true);
expect(body.data.killed).toBe(1);
expect(harness.ctx.cleanupSession).toHaveBeenCalled();
});
});
// ========== PUT /api/sessions/:id/name ==========
describe('PUT /api/sessions/:id/name', () => {
it('renames session', async () => {
const res = await harness.app.inject({
method: 'PUT',
url: `/api/sessions/${harness.ctx._sessionId}/name`,
payload: { name: 'new-name' },
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(true);
expect(body.name).toBe('new-name');
expect(harness.ctx.persistSessionState).toHaveBeenCalled();
expect(harness.ctx.broadcast).toHaveBeenCalledWith(
'session:updated',
expect.anything(),
);
});
it('returns error for unknown session', async () => {
const res = await harness.app.inject({
method: 'PUT',
url: '/api/sessions/nonexistent/name',
payload: { name: 'test' },
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(false);
});
});
// ========== PUT /api/sessions/:id/color ==========
describe('PUT /api/sessions/:id/color', () => {
it('sets session color', async () => {
const res = await harness.app.inject({
method: 'PUT',
url: `/api/sessions/${harness.ctx._sessionId}/color`,
payload: { color: 'blue' },
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(true);
expect(body.color).toBe('blue');
});
it('rejects invalid color', async () => {
const res = await harness.app.inject({
method: 'PUT',
url: `/api/sessions/${harness.ctx._sessionId}/color`,
payload: { color: 'neon-rainbow' },
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(false);
});
});
// ========== POST /api/sessions/:id/input ==========
describe('POST /api/sessions/:id/input', () => {
it('sends input to session', async () => {
const res = await harness.app.inject({
method: 'POST',
url: `/api/sessions/${harness.ctx._sessionId}/input`,
payload: { input: 'hello' },
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(true);
});
it('returns error for unknown session', async () => {
const res = await harness.app.inject({
method: 'POST',
url: '/api/sessions/nonexistent/input',
payload: { input: 'hello' },
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(false);
});
it('rejects empty payload', async () => {
const res = await harness.app.inject({
method: 'POST',
url: `/api/sessions/${harness.ctx._sessionId}/input`,
payload: {},
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(false);
});
});
// ========== POST /api/sessions/:id/resize ==========
describe('POST /api/sessions/:id/resize', () => {
it('resizes session terminal', async () => {
const res = await harness.app.inject({
method: 'POST',
url: `/api/sessions/${harness.ctx._sessionId}/resize`,
payload: { cols: 120, rows: 40 },
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(true);
expect(harness.ctx._session.resize).toHaveBeenCalledWith(120, 40);
});
it('rejects cols exceeding max (500)', async () => {
const res = await harness.app.inject({
method: 'POST',
url: `/api/sessions/${harness.ctx._sessionId}/resize`,
payload: { cols: 501, rows: 24 },
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(false);
});
it('rejects rows exceeding max (200)', async () => {
const res = await harness.app.inject({
method: 'POST',
url: `/api/sessions/${harness.ctx._sessionId}/resize`,
payload: { cols: 80, rows: 201 },
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(false);
});
it('rejects zero dimensions', async () => {
const res = await harness.app.inject({
method: 'POST',
url: `/api/sessions/${harness.ctx._sessionId}/resize`,
payload: { cols: 0, rows: 24 },
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(false);
});
});
// ========== GET /api/sessions/:id/terminal ==========
describe('GET /api/sessions/:id/terminal', () => {
it('returns terminal buffer', async () => {
harness.ctx._session.terminalBuffer = 'hello world';
const res = await harness.app.inject({
method: 'GET',
url: `/api/sessions/${harness.ctx._sessionId}/terminal`,
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.terminalBuffer).toBeDefined();
});
it('returns error for unknown session', async () => {
const res = await harness.app.inject({
method: 'GET',
url: '/api/sessions/nonexistent/terminal',
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(false);
});
});
// ========== POST /api/sessions/:id/run ==========
describe('POST /api/sessions/:id/run', () => {
it('runs prompt on session', async () => {
const res = await harness.app.inject({
method: 'POST',
url: `/api/sessions/${harness.ctx._sessionId}/run`,
payload: { prompt: 'do something' },
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(true);
});
it('rejects empty prompt', async () => {
const res = await harness.app.inject({
method: 'POST',
url: `/api/sessions/${harness.ctx._sessionId}/run`,
payload: { prompt: '' },
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(false);
});
it('returns error for unknown session', async () => {
const res = await harness.app.inject({
method: 'POST',
url: '/api/sessions/nonexistent/run',
payload: { prompt: 'test' },
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(false);
});
it('returns error if session is busy', async () => {
harness.ctx._session.isBusy.mockReturnValue(true);
const res = await harness.app.inject({
method: 'POST',
url: `/api/sessions/${harness.ctx._sessionId}/run`,
payload: { prompt: 'test' },
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(false);
});
});
// ========== POST /api/sessions/:id/interactive ==========
describe('POST /api/sessions/:id/interactive', () => {
it('starts interactive mode', async () => {
const res = await harness.app.inject({
method: 'POST',
url: `/api/sessions/${harness.ctx._sessionId}/interactive`,
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(true);
expect(harness.ctx._session.startInteractive).toHaveBeenCalled();
});
it('returns error for unknown session', async () => {
const res = await harness.app.inject({
method: 'POST',
url: '/api/sessions/nonexistent/interactive',
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(false);
});
it('returns error if session is busy', async () => {
harness.ctx._session.isBusy.mockReturnValue(true);
const res = await harness.app.inject({
method: 'POST',
url: `/api/sessions/${harness.ctx._sessionId}/interactive`,
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(false);
});
});
// ========== POST /api/sessions/:id/shell ==========
describe('POST /api/sessions/:id/shell', () => {
it('starts shell mode', async () => {
const res = await harness.app.inject({
method: 'POST',
url: `/api/sessions/${harness.ctx._sessionId}/shell`,
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(true);
expect(harness.ctx._session.startShell).toHaveBeenCalled();
});
it('returns error if session is busy', async () => {
harness.ctx._session.isBusy.mockReturnValue(true);
const res = await harness.app.inject({
method: 'POST',
url: `/api/sessions/${harness.ctx._sessionId}/shell`,
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(false);
});
});
// ========== GET /api/sessions/:id/output ==========
describe('GET /api/sessions/:id/output', () => {
it('returns session output data', async () => {
const res = await harness.app.inject({
method: 'GET',
url: `/api/sessions/${harness.ctx._sessionId}/output`,
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(true);
expect(body.data).toHaveProperty('textOutput');
expect(body.data).toHaveProperty('messages');
expect(body.data).toHaveProperty('errorBuffer');
});
it('returns error for unknown session', async () => {
const res = await harness.app.inject({
method: 'GET',
url: '/api/sessions/nonexistent/output',
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(false);
});
});
// ========== GET /api/sessions/:id/ralph-state ==========
describe('GET /api/sessions/:id/ralph-state', () => {
it('returns ralph state data', async () => {
const res = await harness.app.inject({
method: 'GET',
url: `/api/sessions/${harness.ctx._sessionId}/ralph-state`,
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(true);
expect(body.data).toHaveProperty('loop');
expect(body.data).toHaveProperty('todos');
expect(body.data).toHaveProperty('todoStats');
});
});
// ========== GET /api/sessions/:id/active-tools ==========
describe('GET /api/sessions/:id/active-tools', () => {
it('returns active tools', async () => {
const res = await harness.app.inject({
method: 'GET',
url: `/api/sessions/${harness.ctx._sessionId}/active-tools`,
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(true);
expect(body.data).toHaveProperty('tools');
});
});
// ========== POST /api/logout ==========
describe('POST /api/logout', () => {
it('returns success', async () => {
const res = await harness.app.inject({
method: 'POST',
url: '/api/logout',
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(true);
});
});
});
+166
View File
@@ -0,0 +1,166 @@
/**
* @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);
});
});
});