a11oy / packages /receipt-substrate /src /serve.test.ts
betterwithage's picture
sync(space): complete build context — fix BUILD_ERROR (CTO)
518343a verified
Raw
History Blame
8.12 kB
// SPDX-License-Identifier: Apache-2.0
// © 2026 Lutar, Stephen P. — SZL Holdings
// ORCID: 0009-0001-0110-4173
//
// serve.test.ts — integration tests for `a11oy serve`.
//
// Each test boots the real HTTP server on an ephemeral port (port 0), seeds a
// real receipt ledger on disk using the real emitReceipt/appendReceiptJsonl
// path, and hits each route over real TCP with fetch(). Nothing is mocked: the
// /v1/verify route runs the real verifyChain, and /v1/policy/evaluate runs the
// real threshold gate (real HMAC-SHA-256 receipt).
//
// Run: node --experimental-strip-types --test src/serve.test.ts
//
// Authored for SZL Holdings. Signed-off per repository DCO.
import { test, before, after } from "node:test";
import assert from "node:assert/strict";
import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import type { AddressInfo } from "node:net";
import type { Server } from "node:http";
import {
appendReceiptJsonl,
createToolEnvelope,
emitReceipt,
type OperationalReceipt,
} from "./index.ts";
import { createServer, parseServeConfig, handleRoute, brokenIndex } from "./serve.ts";
const policy = {
algorithm: "SHA3-256" as const,
chaining: "hash_chain" as const,
quorum: "1-of-1",
nodes: ["test-node"],
};
function mkEnvelope(seq: number) {
return createToolEnvelope({
protocol: "mcp",
actor_id: "did:example:serve-test",
tool_name: "receipted_retrieval",
lambda_axes: ["Λ7"],
payload: { test: true, seq },
metadata: { source: "a11oy-serve-test" },
});
}
/** Seed a real 3-receipt chain on disk. Returns the chain. */
function seedLedger(ledgerPath: string): OperationalReceipt[] {
const chain: OperationalReceipt[] = [];
let prev: OperationalReceipt | null = null;
const t0 = Date.now();
for (let i = 0; i < 3; i += 1) {
// Distinct, monotonically increasing timestamps so the real verifyChain
// timestamp-regression check is satisfied (it compares tai64n at ms
// resolution).
const r = emitReceipt(mkEnvelope(i), {
previousReceipt: prev,
policy,
timestamp: new Date(t0 + i * 1000),
});
appendReceiptJsonl(ledgerPath, r);
chain.push(r);
prev = r;
}
return chain;
}
let dir: string;
let ledgerPath: string;
let server: Server;
let base: string;
let seeded: OperationalReceipt[];
before(async () => {
dir = fs.mkdtempSync(path.join(os.tmpdir(), "a11oy-serve-test-"));
ledgerPath = path.join(dir, "proof.jsonl");
seeded = seedLedger(ledgerPath);
const config = parseServeConfig(["--port", "0", "--host", "127.0.0.1", "--ledger", ledgerPath]);
server = createServer(config);
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
const addr = server.address() as AddressInfo;
base = `http://127.0.0.1:${addr.port}`;
});
after(async () => {
await new Promise<void>((resolve) => server.close(() => resolve()));
fs.rmSync(dir, { recursive: true, force: true });
});
test("GET /healthz returns 200 with status/sha/ts", async () => {
const res = await fetch(`${base}/healthz`);
assert.equal(res.status, 200);
const body = await res.json();
assert.equal(body.status, "ok");
assert.equal(typeof body.sha, "string");
assert.ok(body.sha.length > 0);
// ts must parse as a valid ISO timestamp.
assert.ok(!Number.isNaN(Date.parse(body.ts)));
});
test("GET /readyz returns 200 when the ledger is readable", async () => {
const res = await fetch(`${base}/readyz`);
assert.equal(res.status, 200);
const body = await res.json();
assert.equal(body.status, "ready");
});
test("GET /readyz returns 503 with a reason when the ledger is unreadable", async () => {
// Point a fresh server at a path inside a non-existent directory chain that
// also can't be created as a plain readReceiptJsonl target. Use a directory
// as the ledger path so readReceiptJsonl throws (EISDIR on read).
const badConfig = parseServeConfig(["--ledger", dir]); // dir is a directory
const r = handleRoute("GET", new URL("http://x/readyz"), "", badConfig);
assert.equal(r.status, 503);
const body = r.body as { status: string; reason: string };
assert.equal(body.status, "not_ready");
assert.ok(body.reason.length > 0);
});
test("GET /v1/ledger?limit=2 returns the last 2 real receipts", async () => {
const res = await fetch(`${base}/v1/ledger?limit=2`);
assert.equal(res.status, 200);
const body = await res.json();
assert.equal(body.total, 3);
assert.equal(body.count, 2);
assert.equal(body.receipts.length, 2);
// Last two by sequence.
assert.equal(body.receipts[0].sequence, 1);
assert.equal(body.receipts[1].sequence, 2);
// Real receipt_id/merkle_root match what we seeded.
assert.equal(body.receipts[1].merkle_root, seeded[2].merkle_root);
});
test("GET /v1/ledger/{hash} returns the matching receipt by merkle_root", async () => {
const target = seeded[1];
const res = await fetch(`${base}/v1/ledger/${encodeURIComponent(target.merkle_root)}`);
assert.equal(res.status, 200);
const body = await res.json();
assert.equal(body.receipt.receipt_id, target.receipt_id);
assert.equal(body.receipt.merkle_root, target.merkle_root);
});
test("GET /v1/ledger/{hash} returns 404 for an unknown hash", async () => {
const res = await fetch(`${base}/v1/ledger/${"0".repeat(64)}`);
assert.equal(res.status, 404);
});
test("POST /v1/verify accepts a valid real chain (broken_at null)", async () => {
const res = await fetch(`${base}/v1/verify`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ ledger: seeded }),
});
assert.equal(res.status, 200);
const body = await res.json();
assert.equal(body.valid, true);
assert.equal(body.broken_at, null);
});
test("POST /v1/verify flags a tampered chain with broken_at index", async () => {
// Tamper the second receipt's merkle_root — real verifyChain must catch it.
const tampered = seeded.map((r) => ({ ...r }));
tampered[1] = { ...tampered[1], merkle_root: "0".repeat(tampered[1].merkle_root.length) };
const res = await fetch(`${base}/v1/verify`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ ledger: tampered }),
});
assert.equal(res.status, 200);
const body = await res.json();
assert.equal(body.valid, false);
assert.equal(typeof body.broken_at, "number");
assert.ok(body.broken_at >= 1);
});
test("POST /v1/policy/evaluate allows a well-attested action with a real receipt_hash", async () => {
const res = await fetch(`${base}/v1/policy/evaluate`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
action: {
actionId: "deploy-123",
severity: "low",
confidence: 0.95,
witnesses: [
{ id: "w1", role: "operator", attested: true },
{ id: "w2", role: "reviewer", attested: true },
],
},
}),
});
assert.equal(res.status, 200);
const body = await res.json();
assert.equal(body.decision, "allow");
assert.equal(body.gate, "ThresholdPolicySeverity");
// Real HMAC-SHA-256 signature is non-empty on allow.
assert.equal(typeof body.receipt_hash, "string");
assert.ok(body.receipt_hash.length > 0);
});
test("POST /v1/policy/evaluate denies an under-attested high-severity action", async () => {
const res = await fetch(`${base}/v1/policy/evaluate`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
action: {
actionId: "wire-funds-1",
severity: "critical",
confidence: 0.5,
witnesses: [{ id: "w1", role: "operator", attested: true }],
},
}),
});
assert.equal(res.status, 200);
const body = await res.json();
assert.equal(body.decision, "deny");
assert.equal(body.gate, "ThresholdPolicySeverity");
});
test("brokenIndex maps verifyChain position errors to the earliest index", () => {
assert.equal(brokenIndex(["position 2: merkle_root mismatch", "position 5: x"]), 2);
assert.equal(brokenIndex([]), null);
});