mirror of
https://github.com/morluto/rea
synced 2026-10-26 06:16:17 -04:00
262 lines
8.8 KiB
TypeScript
262 lines
8.8 KiB
TypeScript
import { readdir, writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
|
|
import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
|
|
import { expect, it, onTestFinished, vi } from "vitest";
|
|
import { z } from "zod";
|
|
|
|
import { createCli } from "../../src/cli.js";
|
|
import { compareEvidenceBundlesCommand } from "../../src/application/EvidenceBundleCommands.js";
|
|
import {
|
|
readEvidenceBundle,
|
|
readImmutableEvidenceBundle,
|
|
writeEvidenceBundle,
|
|
} from "../../src/application/EvidenceBundleFiles.js";
|
|
import { ArtifactResourceScope } from "../../src/artifacts/ArtifactResourceScope.js";
|
|
import { bundleComparisonResultSchema } from "../../src/domain/bundleComparison.js";
|
|
import { processEvidence } from "../../src/application/ReconstructionObligationLedger.fixture.js";
|
|
import { processCaptureSchema } from "../../src/domain/process/processCapture.js";
|
|
import * as evidenceBundleDomain from "../../src/domain/evidenceBundle.js";
|
|
import * as evidenceDomain from "../../src/domain/evidence.js";
|
|
import { createServer } from "../../src/server/createServer.js";
|
|
import { createTestBinarySession } from "../fixtures/binarySession.js";
|
|
import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js";
|
|
|
|
const unexpectedBundleFailures = [
|
|
new TypeError("bundle authentication TypeError"),
|
|
new Error("bundle authentication Error"),
|
|
] as const;
|
|
|
|
const spyOnBundleParse = (failure: Error): void => {
|
|
const parser = vi
|
|
.spyOn(evidenceBundleDomain.evidenceBundleSchema, "parse")
|
|
.mockImplementation(() => {
|
|
throw failure;
|
|
});
|
|
onTestFinished(() => parser.mockRestore());
|
|
};
|
|
|
|
const validBundleFile = async (directory: string, name = "bundle.json") => {
|
|
const path = join(directory, name);
|
|
const bundle = evidenceBundleDomain.createEvidenceBundle([processEvidence()]);
|
|
await writeFile(path, evidenceBundleDomain.serializeEvidenceBundle(bundle));
|
|
return { bundle, path };
|
|
};
|
|
|
|
it.each([
|
|
new TypeError("capture validation implementation failed"),
|
|
new Error("capture validator failed"),
|
|
new z.ZodError([
|
|
{ code: "custom", path: [], message: "capture refinement failed" },
|
|
]),
|
|
])(
|
|
"preserves unexpected process validation failures while reading a bundle: $name",
|
|
async (failure) => {
|
|
const directory = await createTestTempDirectory(
|
|
"rea-bundle-capture-failure-",
|
|
);
|
|
const path = join(directory, "bundle.json");
|
|
await writeFile(
|
|
path,
|
|
evidenceBundleDomain.serializeEvidenceBundle(
|
|
evidenceBundleDomain.createEvidenceBundle([processEvidence()]),
|
|
),
|
|
);
|
|
const validator = vi
|
|
.spyOn(processCaptureSchema, "safeParse")
|
|
.mockImplementation(() => {
|
|
throw failure;
|
|
});
|
|
onTestFinished(() => validator.mockRestore());
|
|
|
|
await expect(
|
|
compareEvidenceBundlesCommand({ leftPath: path, rightPath: path }),
|
|
).rejects.toBe(failure);
|
|
},
|
|
);
|
|
|
|
it.each(unexpectedBundleFailures)(
|
|
"preserves unexpected bundle authentication failures in mutable and immutable readers: $name",
|
|
async (failure) => {
|
|
const directory = await createTestTempDirectory(
|
|
"rea-bundle-reader-failure-",
|
|
);
|
|
const { path } = await validBundleFile(directory);
|
|
spyOnBundleParse(failure);
|
|
|
|
await expect(readEvidenceBundle(path)).rejects.toBe(failure);
|
|
await expect(readImmutableEvidenceBundle(path)).rejects.toBe(failure);
|
|
await expect(
|
|
compareEvidenceBundlesCommand({ leftPath: path, rightPath: path }),
|
|
).rejects.toBe(failure);
|
|
},
|
|
);
|
|
|
|
it.each(unexpectedBundleFailures)(
|
|
"does not publish output or retain resources when bundle authentication throws: $name",
|
|
async (failure) => {
|
|
const directory = await createTestTempDirectory(
|
|
"rea-bundle-write-failure-",
|
|
);
|
|
const { bundle } = await validBundleFile(directory, "input.json");
|
|
const outputPath = join(directory, "output.json");
|
|
const resources = new ArtifactResourceScope();
|
|
onTestFinished(async () => {
|
|
await resources.close();
|
|
});
|
|
spyOnBundleParse(failure);
|
|
|
|
await expect(
|
|
writeEvidenceBundle(bundle, outputPath, false, resources),
|
|
).rejects.toBe(failure);
|
|
expect(await readdir(directory)).toEqual(["input.json"]);
|
|
expect(resources.hasPending).toBe(false);
|
|
await resources.close();
|
|
},
|
|
);
|
|
|
|
it.each(unexpectedBundleFailures)(
|
|
"preserves unexpected bundle authentication failures through SDK import: $name",
|
|
async (failure) => {
|
|
const directory = await createTestTempDirectory(
|
|
"rea-bundle-import-failure-",
|
|
);
|
|
const { path } = await validBundleFile(directory);
|
|
const session = createTestBinarySession(() => {
|
|
throw new Error("Evidence import must not launch a provider");
|
|
});
|
|
const server = createServer({ kind: "session", session });
|
|
const client = new Client({ name: "bundle-import-failure", version: "1" });
|
|
const [clientTransport, serverTransport] =
|
|
InMemoryTransport.createLinkedPair();
|
|
spyOnBundleParse(failure);
|
|
try {
|
|
await server.connect(serverTransport);
|
|
await client.connect(clientTransport);
|
|
const response = await client.callTool({
|
|
name: "import_evidence_bundle",
|
|
arguments: { path },
|
|
});
|
|
expect(response).toMatchObject({
|
|
isError: true,
|
|
content: [{ type: "text", text: failure.message }],
|
|
});
|
|
expect(response.structuredContent).toBeUndefined();
|
|
expect(session.exportEvidenceBundle().records).toEqual([]);
|
|
expect(session.exportEvidenceBundle().unknowns).toEqual([]);
|
|
} finally {
|
|
await Promise.allSettled([
|
|
client.close(),
|
|
server.close(),
|
|
session.close(),
|
|
]);
|
|
}
|
|
},
|
|
);
|
|
|
|
it.each(unexpectedBundleFailures)(
|
|
"preserves the session ledger when bundle authentication throws: $name",
|
|
async (failure) => {
|
|
const directory = await createTestTempDirectory(
|
|
"rea-ledger-import-failure-",
|
|
);
|
|
const { bundle } = await validBundleFile(directory);
|
|
const existing = evidenceDomain.createEvidence(
|
|
undefined,
|
|
{ id: "fixture", name: "Fixture", version: "1" },
|
|
{
|
|
operation: "observe",
|
|
parameters: {},
|
|
result: { retained: true },
|
|
},
|
|
);
|
|
const session = createTestBinarySession(() => {
|
|
throw new Error("Evidence import must not launch a provider");
|
|
});
|
|
onTestFinished(async () => {
|
|
await session.close();
|
|
});
|
|
expect(session.recordEvidence(existing).ok).toBe(true);
|
|
const before = session.exportEvidenceBundle();
|
|
spyOnBundleParse(failure);
|
|
|
|
expect(() => session.importEvidenceBundle(bundle)).toThrow(failure);
|
|
expect(session.exportEvidenceBundle()).toEqual(before);
|
|
},
|
|
);
|
|
|
|
it.each(unexpectedBundleFailures)(
|
|
"propagates unexpected standalone-record classification failures: $name",
|
|
async (failure) => {
|
|
const directory = await createTestTempDirectory(
|
|
"rea-bundle-classifier-failure-",
|
|
);
|
|
const path = join(directory, "record.json");
|
|
const record = processEvidence();
|
|
await writeFile(path, JSON.stringify(record));
|
|
const parser = vi
|
|
.spyOn(evidenceDomain, "parseEvidence")
|
|
.mockImplementation(() => {
|
|
throw failure;
|
|
});
|
|
onTestFinished(() => parser.mockRestore());
|
|
|
|
await expect(readImmutableEvidenceBundle(path)).rejects.toBe(failure);
|
|
},
|
|
);
|
|
|
|
it.each([
|
|
new TypeError("comparison output invariant failed"),
|
|
new RangeError("comparison computation failed"),
|
|
new z.ZodError([
|
|
{ code: "custom", path: ["status"], message: "invalid generated result" },
|
|
]),
|
|
])(
|
|
"keeps an internal comparison failure distinct from damaged Evidence: $name",
|
|
async (failure) => {
|
|
const directory = await createTestTempDirectory(
|
|
"rea-bundle-compute-failure-",
|
|
);
|
|
const path = join(directory, "bundle.json");
|
|
await writeFile(
|
|
path,
|
|
evidenceBundleDomain.serializeEvidenceBundle(
|
|
evidenceBundleDomain.createEvidenceBundle([]),
|
|
),
|
|
);
|
|
// Reach output validation through the actual command, reader, authentication
|
|
// and comparison. This fault is generated output, not the caller's bundle.
|
|
const output = vi
|
|
.spyOn(bundleComparisonResultSchema, "parse")
|
|
.mockImplementation(() => {
|
|
throw failure;
|
|
});
|
|
onTestFinished(() => output.mockRestore());
|
|
|
|
const originalExitCode = process.exitCode;
|
|
onTestFinished(() => {
|
|
process.exitCode = originalExitCode;
|
|
});
|
|
process.exitCode = 0;
|
|
let stdout = "";
|
|
let exitCode = 0;
|
|
await createCli({}).serve(["compare", path, path, "--json"], {
|
|
env: {},
|
|
stdout: (chunk) => {
|
|
stdout += chunk;
|
|
},
|
|
exit: (code) => {
|
|
exitCode = code;
|
|
},
|
|
});
|
|
expect(exitCode || Number(process.exitCode ?? 0)).toBe(1);
|
|
expect(JSON.parse(stdout)).toEqual({
|
|
code: "UNKNOWN",
|
|
message: failure.message,
|
|
});
|
|
await expect(
|
|
compareEvidenceBundlesCommand({ leftPath: path, rightPath: path }),
|
|
).rejects.toBe(failure);
|
|
},
|
|
);
|