mirror of
https://github.com/morluto/rea.git
synced 2026-11-15 11:13:21 +00:00
Each snapshot entry is validated alone, so its schema issue path started at the entry. Prefix it with entries or workflow_entries and the entry index so the reason names the entry to repair, and match the header case's issue count without freezing the whole sentence. Co-Authored-By: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
161 lines
6.0 KiB
TypeScript
161 lines
6.0 KiB
TypeScript
import { readFile, stat, writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
import {
|
|
readAnalysisSnapshot,
|
|
writeAnalysisSnapshot,
|
|
} from "../../../src/application/binary/AnalysisSnapshotFiles.js";
|
|
import { projectAnalysisError } from "../../../src/domain/analysisErrorProjection.js";
|
|
import {
|
|
snapshotBinding,
|
|
snapshotTarget,
|
|
} from "../../../src/domain/analysisSnapshot.js";
|
|
import {
|
|
ANALYSIS_SNAPSHOT_PROFILE,
|
|
ANALYSIS_SNAPSHOT_TARGET,
|
|
} from "../../../src/domain/analysisSnapshot.fixture.js";
|
|
import { createEvidenceBundle } from "../../../src/domain/evidenceBundle.js";
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
import { withArtifactResourceScope } from "../../fixtures/artifactInventory.js";
|
|
|
|
describe.each([
|
|
{ mode: "native", paddingBytes: 0 },
|
|
{ mode: "streamed", paddingBytes: 9 * 1024 * 1024 },
|
|
])("analysis snapshot input ($mode)", ({ paddingBytes }) => {
|
|
it.each([
|
|
{
|
|
label: "header",
|
|
document: { entries: [], workflow_entries: [] },
|
|
field: "target",
|
|
},
|
|
{
|
|
label: "provider entry",
|
|
document: { entries: [{}], workflow_entries: [] },
|
|
field: "entries.0.query_id",
|
|
},
|
|
{
|
|
label: "workflow entry",
|
|
document: { entries: [], workflow_entries: [{}] },
|
|
field: "workflow_entries.0.query_id",
|
|
},
|
|
])(
|
|
"retains the first $label schema diagnostic through the file wrapper",
|
|
async ({ document, field }) => {
|
|
const root = await createTestTempDirectory(
|
|
"rea-snapshot-schema-diagnostic-",
|
|
);
|
|
const path = join(root, "snapshot.json");
|
|
const encoded = `${" ".repeat(paddingBytes)}${JSON.stringify(document)}`;
|
|
await writeFile(path, encoded);
|
|
const loaded = await readAnalysisSnapshot(path);
|
|
if (loaded.ok) throw new Error("Expected invalid snapshot refusal");
|
|
expect(loaded.error.cause).toMatchObject({ name: "ZodError" });
|
|
const projected = projectAnalysisError(loaded.error);
|
|
expect(projected).toMatchObject({
|
|
code: "evidence_integrity_mismatch",
|
|
category: "integrity_mismatch",
|
|
retryable: false,
|
|
details: { reason: expect.stringContaining(`at ${field}:`) },
|
|
});
|
|
if (field === "target")
|
|
expect(projected.details?.reason).toMatch(
|
|
/ at target: .+ \(2 more issues\)$/,
|
|
);
|
|
expect(projected).not.toHaveProperty("cause");
|
|
expect(projected).not.toHaveProperty("stack");
|
|
expect(await readFile(path, "utf8")).toBe(encoded);
|
|
},
|
|
);
|
|
|
|
it.each(["[]", "null", "5", '"x"', "true"])(
|
|
"classifies a readable %s root as invalid content, not an I/O failure",
|
|
async (document) => {
|
|
const root = await createTestTempDirectory("rea-snapshot-root-");
|
|
const path = join(root, "snapshot.json");
|
|
const encoded = `${" ".repeat(paddingBytes)}${document}`;
|
|
await writeFile(path, encoded);
|
|
|
|
const loaded = await readAnalysisSnapshot(path);
|
|
if (loaded.ok) throw new Error("Expected a non-object root rejection");
|
|
const projected = projectAnalysisError(loaded.error);
|
|
expect(projected).toMatchObject({
|
|
code: "evidence_integrity_mismatch",
|
|
details: { reason: "invalid-json" },
|
|
});
|
|
expect(projected.message).toContain("Repair or recreate");
|
|
expect(loaded.error.cause).toMatchObject({
|
|
message: "JSON document root must be an object",
|
|
});
|
|
expect(await readFile(path, "utf8")).toBe(encoded);
|
|
},
|
|
);
|
|
|
|
it("retains syntax and snapshot-member validation and loads valid objects", async () => {
|
|
const root = await createTestTempDirectory("rea-snapshot-validation-");
|
|
const path = join(root, "snapshot.json");
|
|
const prefix = " ".repeat(paddingBytes);
|
|
await writeFile(path, `${prefix}[`);
|
|
expect(await readAnalysisSnapshot(path)).toMatchObject({
|
|
ok: false,
|
|
error: { _tag: "EvidenceFileError", reason: "invalid-json" },
|
|
});
|
|
await writeFile(path, `${prefix}{}`);
|
|
expect(await readAnalysisSnapshot(path)).toMatchObject({
|
|
ok: false,
|
|
error: { _tag: "EvidenceIntegrityError" },
|
|
});
|
|
const snapshot = {
|
|
target: snapshotTarget(ANALYSIS_SNAPSHOT_TARGET),
|
|
binding: snapshotBinding(ANALYSIS_SNAPSHOT_PROFILE),
|
|
entries: [],
|
|
workflow_entries: [],
|
|
evidence_bundle: createEvidenceBundle([]),
|
|
};
|
|
await writeFile(path, `${prefix}${JSON.stringify(snapshot)}`);
|
|
expect(await readAnalysisSnapshot(path)).toEqual({
|
|
ok: true,
|
|
value: snapshot,
|
|
});
|
|
});
|
|
});
|
|
|
|
it("retains serialization schema diagnostics without creating a snapshot file", async () => {
|
|
const root = await createTestTempDirectory("rea-snapshot-write-diagnostic-");
|
|
const path = join(root, "snapshot.json");
|
|
const snapshot = {
|
|
target: snapshotTarget(ANALYSIS_SNAPSHOT_TARGET),
|
|
binding: snapshotBinding(ANALYSIS_SNAPSHOT_PROFILE),
|
|
entries: [],
|
|
workflow_entries: [],
|
|
evidence_bundle: createEvidenceBundle([]),
|
|
};
|
|
Reflect.deleteProperty(snapshot, "binding");
|
|
const written = await withArtifactResourceScope((resources) =>
|
|
writeAnalysisSnapshot(snapshot, path, false, resources),
|
|
);
|
|
if (written.ok) throw new Error("Expected invalid snapshot refusal");
|
|
expect(written.error.cause).toMatchObject({ name: "ZodError" });
|
|
expect(projectAnalysisError(written.error)).toMatchObject({
|
|
code: "evidence_integrity_mismatch",
|
|
details: { reason: expect.stringContaining("at binding:") },
|
|
});
|
|
await expect(stat(path)).rejects.toMatchObject({ code: "ENOENT" });
|
|
});
|
|
|
|
it("keeps file-selection failures distinct from invalid snapshot contents", async () => {
|
|
const root = await createTestTempDirectory("rea-snapshot-selection-");
|
|
for (const [path, reason] of [
|
|
[join(root, "missing.json"), "missing"],
|
|
[root, "not-file"],
|
|
] as const) {
|
|
const loaded = await readAnalysisSnapshot(path);
|
|
if (loaded.ok) throw new Error("Expected a file-selection failure");
|
|
expect(projectAnalysisError(loaded.error)).toMatchObject({
|
|
code: "invalid_request",
|
|
details: { reason },
|
|
});
|
|
}
|
|
});
|