feat: add agent API and operational analytics

This commit is contained in:
jjbrunton committed 2026-08-08 22:08:14 +01:00
1 parent 720ea0652a
commit e8b730b592
65 files changed
+2650 -1214

No files matched your search

+55 -2
View File
@@ -6,6 +6,7 @@ import { afterEach, describe, expect, it } from 'vitest';
import type { BrowserClient } from '../src/browser/client.js';
import { ActivityLogRepository } from '../src/database/repositories.js';
import { WebServer } from '../src/web/server.js';
import { OverviewResponseSchema } from '../src/web/agent-api-contracts.js';
import { createMockLogger } from './mockLogger.js';
import { createTestDb, createTestRepositories, getTestConfig } from './testUtils.js';
@@ -31,13 +32,25 @@ function createWorkspace(includeReactBuild = true): string {
return workspace;
}
async function startServer(options: { debugEnabled?: boolean; includeReactBuild?: boolean } = {}): Promise<RunningServer> {
async function startServer(options: {
debugEnabled?: boolean;
includeReactBuild?: boolean;
agentReadToken?: string;
agentControlToken?: string;
} = {}): Promise<RunningServer> {
const database = createTestDb();
const repositories = createTestRepositories(database);
const workspace = createWorkspace(options.includeReactBuild);
const server = new WebServer(
0,
getTestConfig({ web: { enabled: true, port: 0, authToken: AUTH_TOKEN, debugEnabled: options.debugEnabled ?? false } }),
getTestConfig({ web: {
enabled: true,
port: 0,
authToken: AUTH_TOKEN,
debugEnabled: options.debugEnabled ?? false,
agentReadToken: options.agentReadToken,
agentControlToken: options.agentControlToken,
} }),
repositories.postsRepo,
repositories.messagesRepo,
repositories.successfulMessagesRepo,
@@ -110,4 +123,44 @@ describe('authenticated React dashboard cutover', () => {
expect(upload.status).toBe(404);
});
it('exposes public liveness and scoped discovery/control APIs', async () => {
const running = await startServer({
agentReadToken: 'agent-read-token',
agentControlToken: 'agent-control-token',
});
runningServers.push(running);
running.server.setAccountAuth({
isFullyAuthenticated: () => true,
} as never);
const health = await fetch(`${running.baseUrl}/healthz`);
const openapi = await fetch(`${running.baseUrl}/api/openapi.json`, {
headers: { Authorization: 'Bearer agent-read-token' },
});
const overview = await fetch(`${running.baseUrl}/api/v1/overview`, {
headers: { Authorization: 'Bearer agent-read-token' },
});
const forbiddenStart = await fetch(`${running.baseUrl}/api/v1/control/start`, {
method: 'POST',
headers: { Authorization: 'Bearer agent-read-token' },
});
const allowedStart = await fetch(`${running.baseUrl}/api/v1/control/start`, {
method: 'POST',
headers: { Authorization: 'Bearer agent-control-token' },
});
const allowedStop = await fetch(`${running.baseUrl}/api/v1/control/stop`, {
method: 'POST',
headers: { Authorization: 'Bearer agent-control-token' },
});
expect(health.status).toBe(200);
expect(await health.json()).toMatchObject({ status: 'ok', service: 'reddit-bot' });
expect(openapi.status).toBe(200);
expect(await openapi.json()).toMatchObject({ openapi: '3.1.0' });
expect(OverviewResponseSchema.safeParse(await overview.json()).success).toBe(true);
expect(forbiddenStart.status).toBe(403);
expect(await allowedStart.json()).toMatchObject({ success: true, running: true, changed: true });
expect(await allowedStop.json()).toMatchObject({ success: true, running: false, changed: true });
});
});