/*
Unit tests for handover validate covering its documented public contract and regression-sensitive edge cases.
- Not a contract source of truth — the module under test owns the authoritative behavior; fixtures are minimal scaffolding only.
- RFC-0968: initial handover validate unit tests.
*/
import { test, expect, beforeEach, afterEach } from "vitest";
import { mkdtemp, rm, mkdir } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { runSternsystemValidate } from "./sternsystem-validate.ts";
import { makeInput, makeContext, writeSystemConfig, BASE_SETUP } from "./test-helpers.ts";
import { writeAuthorization } from "./handover.ts";
import { generateKeyPair, toHex } from "@warpgogol/werkstatt-engine/signing";
import { buildPassportPayload, signPassport, type SignedSitePassport } from "./passport.ts";
import { writePassport } from "./registry-io.ts";
import { appendBordbuchEntry } from "../bordbuch/bordbuch-io.ts";
import type { HandoverAuthorizationV1 } from "./handover.ts";
let testRoot: string;
let workspaceRoot: string;
beforeEach(async () => {
testRoot = await mkdtemp(join(tmpdir(), "handover-validate-test-"));
workspaceRoot = join(testRoot, "workspace");
await mkdir(workspaceRoot, { recursive: true });
await BASE_SETUP(workspaceRoot);
});
afterEach(async () => {
await rm(testRoot, { recursive: true, force: true });
});
async function setupSystemWithPassport(
root: string,
systemId: string,
): Promise<{
passport: SignedSitePassport;
keyPair: { privateKey: Uint8Array; publicKey: Uint8Array };
}> {
await writeSystemConfig(root, [{ path: "./systems/test-bundle", storageType: "non-bare" }]);
const keyPair = await generateKeyPair();
const publicKeyHex = toHex(keyPair.publicKey);
const payload = await buildPassportPayload({
systemId,
werkstattRoot: root,
creatorIdentity: "human:sender",
creatorPublicKey: publicKeyHex,
});
const { passportHash, signature } = await signPassport(payload, keyPair.privateKey);
const passport: SignedSitePassport = { payload, passportHash, signature };
await writePassport(root, systemId, passport);
return { passport, keyPair };
}
async function writeAuthFile(
root: string,
systemId: string,
overrides?: Partial,
): Promise<{ authorizationHash: string }> {
const senderKeyPair = await generateKeyPair();
const recipientKeyPair = await generateKeyPair();
const passport = await buildPassportPayload({
systemId,
werkstattRoot: root,
creatorIdentity: "human:sender",
creatorPublicKey: toHex(senderKeyPair.publicKey),
});
const { passportHash, signature } = await signPassport(passport, senderKeyPair.privateKey);
await writePassport(root, systemId, { payload: passport, passportHash, signature });
const now = new Date();
const expires = new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000);
const payload: HandoverAuthorizationV1 = {
schema: "handover-authorization/v1",
systemId,
sender: {
identity: "human:sender",
publicKey: toHex(senderKeyPair.publicKey),
},
recipient: {
identity: "human:recipient",
publicKey: toHex(recipientKeyPair.publicKey),
},
passportHash,
bordbuchHead: "",
authorizedAt: now.toISOString(),
expiresAt: expires.toISOString(),
...overrides,
};
const { signAuthorization } = await import("./handover.ts");
const doc = await signAuthorization(payload, senderKeyPair.privateKey);
await writeAuthorization(root, systemId, doc);
return { authorizationHash: doc.authorizationHash };
}
test("HANDOVER-01: authorization file exists → validate reports HANDOVER-01", async () => {
await setupSystemWithPassport(workspaceRoot, "test-bundle");
await writeAuthFile(workspaceRoot, "test-bundle");
const result = await runSternsystemValidate(makeInput({}), makeContext(workspaceRoot));
const handover01 = result.data!.violations.filter((v) => v.rule === "HANDOVER-01");
expect(handover01).toHaveLength(1);
expect(handover01[0].message).toContain("pending handover");
});
test("HANDOVER-02: expired authorization → validate reports HANDOVER-02", async () => {
await setupSystemWithPassport(workspaceRoot, "test-bundle");
const pastDate = new Date(Date.now() - 60_000).toISOString();
await writeAuthFile(workspaceRoot, "test-bundle", { expiresAt: pastDate });
const result = await runSternsystemValidate(makeInput({}), makeContext(workspaceRoot));
const handover02 = result.data!.violations.filter((v) => v.rule === "HANDOVER-02");
expect(handover02).toHaveLength(1);
expect(handover02[0].message).toContain("expired");
});
test("HANDOVER-03: passport hash mismatch → validate reports HANDOVER-03", async () => {
await setupSystemWithPassport(workspaceRoot, "test-bundle");
await writeAuthFile(workspaceRoot, "test-bundle", {
passportHash: "sha256:wronghash",
});
const result = await runSternsystemValidate(makeInput({}), makeContext(workspaceRoot));
const handover03 = result.data!.violations.filter((v) => v.rule === "HANDOVER-03");
expect(handover03).toHaveLength(1);
expect(handover03[0].message).toContain("passport changed");
});
test("HANDOVER-05: passport hash mismatch with bordbuch handover event → validate reports HANDOVER-05", async () => {
await setupSystemWithPassport(workspaceRoot, "test-bundle");
// Append a handover bordbuch event with a specific newPassportHash
const metadata = {
from: "human:sender",
to: "human:recipient",
authorizationHash: "sha256:auth123",
newPassportHash: "sha256:expected-hash",
};
await appendBordbuchEntry(
workspaceRoot,
"test-bundle",
"handover",
"handover from sender to recipient",
"human:recipient",
{ missionId: null, releaseId: null, metadata: metadata as Record },
);
// The current passport has a different hash than what's in the bordbuch event
const result = await runSternsystemValidate(makeInput({}), makeContext(workspaceRoot));
const handover05 = result.data!.violations.filter((v) => v.rule === "HANDOVER-05");
expect(handover05).toHaveLength(1);
expect(handover05[0].message).toContain("incomplete handover");
});
test("PASSPORT-03: freshly generated passport → no content drift warning (generatedAt preserved)", async () => {
await setupSystemWithPassport(workspaceRoot, "test-bundle");
const result = await runSternsystemValidate(makeInput({}), makeContext(workspaceRoot));
const passport03 = result.data!.warnings.filter((w) => w.field === "PASSPORT-03");
expect(passport03).toHaveLength(0);
});
test("clean state: no authorization file, no handover bordbuch event → no HANDOVER violations", async () => {
await setupSystemWithPassport(workspaceRoot, "test-bundle");
const result = await runSternsystemValidate(makeInput({}), makeContext(workspaceRoot));
const handoverViolations = result.data!.violations.filter((v) => v.rule.startsWith("HANDOVER"));
expect(handoverViolations).toHaveLength(0);
});