openclaw/extensions/boltbot/src/__tests__/api.test.ts
duy 1096cc16e6 feat: add boltbot extension — EigenCloud verification layer
Trustless hosting extension for Moltbot via EigenCloud infrastructure:
- EigenAI provider with x-api-key auth and configPatch registration
- Action tier classification for all 23 canonical tools
- Receipt logging on after_tool_call hook (medium/high tier)
- Anomaly detection (BCC, outbound curl, process, gateway)
- SQLite receipt store with EigenDA proxy backend
- Dashboard API endpoints (/boltbot/receipts, /receipt, /stats)
- EigenCompute TEE deploy script and Dockerfile
2026-01-29 12:55:24 -08:00

148 lines
4.7 KiB
TypeScript

import { describe, it, expect, beforeEach } from "vitest";
import { registerBoltbotApi } from "../api.js";
import type { ActionReceipt, ReceiptStore } from "../receipt-store.js";
import type { IncomingMessage, ServerResponse } from "node:http";
// Mock ReceiptStore
function makeStore(receipts: ActionReceipt[] = []): ReceiptStore {
return {
async put(r: ActionReceipt) { receipts.push(r); },
async get(id: string) { return receipts.find((r) => r.id === id) ?? null; },
async list(opts: { limit: number; offset: number }) {
return receipts.slice(opts.offset, opts.offset + opts.limit);
},
async stats() {
const byTier: Record<string, number> = {};
let anomalyCount = 0;
for (const r of receipts) {
byTier[r.tier] = (byTier[r.tier] ?? 0) + 1;
if (r.anomalies.length > 0) anomalyCount++;
}
return { total: receipts.length, byTier, anomalyCount };
},
};
}
// Mock API and capture registered routes
type Route = { path: string; handler: (req: IncomingMessage, res: ServerResponse) => void | Promise<void> };
function makeApi() {
const routes: Route[] = [];
return {
routes,
registerHttpRoute(route: Route) { routes.push(route); },
};
}
// Mock HTTP request/response
function mockReq(url: string): IncomingMessage {
return { url } as any;
}
function mockRes() {
let statusCode = 200;
let headers: Record<string, string> = {};
let body = "";
return {
writeHead(code: number, h: Record<string, string>) { statusCode = code; headers = h; },
end(data?: string) { body = data ?? ""; },
get statusCode() { return statusCode; },
get headers() { return headers; },
get body() { return body; },
json() { return JSON.parse(body); },
} as any;
}
const sampleReceipt: ActionReceipt = {
id: "test-id-1",
timestamp: "2026-01-29T00:00:00.000Z",
sessionKey: "sess-1",
tier: "medium",
toolName: "message",
argumentsHash: "abc",
resultHash: "def",
success: true,
durationMs: 100,
anomalies: [],
};
describe("Dashboard API", () => {
let api: ReturnType<typeof makeApi>;
let store: ReceiptStore;
beforeEach(() => {
api = makeApi();
store = makeStore([sampleReceipt]);
registerBoltbotApi(api as any, store);
});
it("registers 3 routes", () => {
expect(api.routes).toHaveLength(3);
});
describe("/boltbot/receipts", () => {
it("returns receipts list", async () => {
const route = api.routes.find((r) => r.path === "/boltbot/receipts")!;
const res = mockRes();
await route.handler(mockReq("/boltbot/receipts"), res);
expect(res.statusCode).toBe(200);
const data = res.json();
expect(data.receipts).toHaveLength(1);
});
it("respects pagination params", async () => {
const route = api.routes.find((r) => r.path === "/boltbot/receipts")!;
const res = mockRes();
await route.handler(mockReq("/boltbot/receipts?limit=5&offset=0"), res);
expect(res.statusCode).toBe(200);
});
});
describe("/boltbot/receipt", () => {
it("returns receipt by id", async () => {
const route = api.routes.find((r) => r.path === "/boltbot/receipt")!;
const res = mockRes();
await route.handler(mockReq("/boltbot/receipt?id=test-id-1"), res);
expect(res.statusCode).toBe(200);
expect(res.json().receipt.id).toBe("test-id-1");
});
it("returns 400 for missing id", async () => {
const route = api.routes.find((r) => r.path === "/boltbot/receipt")!;
const res = mockRes();
await route.handler(mockReq("/boltbot/receipt"), res);
expect(res.statusCode).toBe(400);
expect(res.json().error).toBe("missing_id");
});
it("returns 404 for nonexistent id", async () => {
const route = api.routes.find((r) => r.path === "/boltbot/receipt")!;
const res = mockRes();
await route.handler(mockReq("/boltbot/receipt?id=nonexistent"), res);
expect(res.statusCode).toBe(404);
expect(res.json().error).toBe("not_found");
});
});
describe("/boltbot/stats", () => {
it("returns stats", async () => {
const route = api.routes.find((r) => r.path === "/boltbot/stats")!;
const res = mockRes();
await route.handler(mockReq("/boltbot/stats"), res);
expect(res.statusCode).toBe(200);
const data = res.json();
expect(data.total).toBe(1);
expect(data.byTier).toEqual({ medium: 1 });
expect(data.anomalyCount).toBe(0);
});
});
it("all responses have Content-Type application/json", async () => {
for (const route of api.routes) {
const res = mockRes();
await route.handler(mockReq(route.path), res);
expect(res.headers["Content-Type"]).toBe("application/json");
}
});
});