mirror of
https://github.com/morluto/rea
synced 2026-10-25 22:08:11 -04:00
949966712e
Tag physical read outcomes at the owning boundary and preserve opaque caller cancellation reasons through JSON pipelines and reference imports. Share named bounded-read options across consumers and retain failed cleanup ownership for retry. Closes #5686
197 lines
7.1 KiB
TypeScript
197 lines
7.1 KiB
TypeScript
import { writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
|
|
import { describe, expect, it, vi } from "vitest";
|
|
|
|
import { analysisViewCaptureEvidence } from "../../../tests/fixtures/analysisView.js";
|
|
import { createTestTempDirectory } from "../../../tests/fixtures/temporaryDirectory.js";
|
|
import { ArtifactResourceScope } from "../../artifacts/ArtifactResourceScope.js";
|
|
import { AnalysisCancelledError } from "../../domain/analysisErrorCore.js";
|
|
import { JsonInputPhysicalReadFailure } from "../JsonInputFile.js";
|
|
import { failFileReadAfterAbort } from "../../../tests/support/failFileReadAfterAbort.js";
|
|
import { holdFirstFileRead } from "../../../tests/support/holdFirstFileRead.js";
|
|
import {
|
|
estimateJsonValueBytes,
|
|
readEvidenceFileSource,
|
|
requireAuthenticationHeadroom,
|
|
} from "./evidenceFileSource.js";
|
|
|
|
describe("evidence-file source", () => {
|
|
it("estimates parsed JSON at no less than its UTF-16 text", () => {
|
|
const text = "x".repeat(1000);
|
|
expect(estimateJsonValueBytes(text)).toBeGreaterThanOrEqual(2000);
|
|
expect(
|
|
estimateJsonValueBytes({ outer: [text, { inner: text }] }),
|
|
).toBeGreaterThan(2 * estimateJsonValueBytes(text));
|
|
});
|
|
|
|
it("refuses authentication when live heap headroom cannot hold its copies", () => {
|
|
const value = { normalized_result: "y".repeat(10_000) };
|
|
expect(
|
|
requireAuthenticationHeadroom(value, "/tmp/capture.json", 1024),
|
|
).toMatchObject({
|
|
ok: false,
|
|
error: {
|
|
_tag: "AnalysisResourceConstraintError",
|
|
resource: "memory",
|
|
reportedLimits: {
|
|
input_path: "/tmp/capture.json",
|
|
available_heap_bytes: 1024,
|
|
},
|
|
},
|
|
});
|
|
expect(
|
|
requireAuthenticationHeadroom(value, "/tmp/capture.json", 1 << 30),
|
|
).toEqual({ ok: true, value: undefined });
|
|
});
|
|
|
|
it("refuses a cancelled request before reading and reads within the caller's resource scope", async () => {
|
|
const controller = new AbortController();
|
|
const reason = new JsonInputPhysicalReadFailure(
|
|
new Error("opaque caller reason with physical-failure shape"),
|
|
);
|
|
controller.abort(reason);
|
|
const cancelled = await readEvidenceFileSource(
|
|
"/nonexistent/capture.json",
|
|
{
|
|
signal: controller.signal,
|
|
},
|
|
);
|
|
expect(cancelled).toMatchObject({
|
|
ok: false,
|
|
error: { _tag: "AnalysisCancelledError" },
|
|
});
|
|
if (cancelled.ok || !(cancelled.error instanceof AnalysisCancelledError))
|
|
throw new Error("Expected cancelled analysis-view read");
|
|
expect(cancelled.error.cause).toBe(reason);
|
|
const path = join(
|
|
await createTestTempDirectory("rea-evidence-file-source-"),
|
|
"capture.json",
|
|
);
|
|
const parent = analysisViewCaptureEvidence();
|
|
await writeFile(path, JSON.stringify(parent));
|
|
const resources = new ArtifactResourceScope();
|
|
const read = await readEvidenceFileSource(path, { resources });
|
|
await resources.close();
|
|
if (!read.ok) throw read.error;
|
|
expect(read.value).toEqual(JSON.parse(JSON.stringify(parent)));
|
|
});
|
|
});
|
|
|
|
describe("evidence-file read failure provenance", () => {
|
|
it("keeps a physical null read cause after the analysis-view request aborts", async () => {
|
|
const root = await createTestTempDirectory("rea-view-late-io-");
|
|
const path = join(root, "capture.json");
|
|
await writeFile(path, JSON.stringify(analysisViewCaptureEvidence()));
|
|
const controller = new AbortController();
|
|
await failFileReadAfterAbort(
|
|
path,
|
|
controller,
|
|
new Error("later abort"),
|
|
null,
|
|
);
|
|
try {
|
|
const result = await readEvidenceFileSource(path, {
|
|
signal: controller.signal,
|
|
});
|
|
expect(result).toMatchObject({
|
|
ok: false,
|
|
error: { _tag: "EvidenceFileError", reason: "io", path },
|
|
});
|
|
if (result.ok) throw new Error("Expected physical JSON read failure");
|
|
expect(result.error.cause).toBeNull();
|
|
} finally {
|
|
vi.restoreAllMocks();
|
|
}
|
|
});
|
|
|
|
it("preserves identical physical and abort causes for analysis-view reads", async () => {
|
|
const root = await createTestTempDirectory("rea-view-identical-abort-");
|
|
const path = join(root, "capture.json");
|
|
await writeFile(path, JSON.stringify(analysisViewCaptureEvidence()));
|
|
const controller = new AbortController();
|
|
const physical = Object.assign(new Error("descriptor EIO"), {
|
|
code: "EIO",
|
|
});
|
|
await failFileReadAfterAbort(path, controller, physical, physical);
|
|
try {
|
|
const result = await readEvidenceFileSource(path, {
|
|
signal: controller.signal,
|
|
});
|
|
expect(result).toMatchObject({
|
|
ok: false,
|
|
error: { _tag: "EvidenceFileError", reason: "io" },
|
|
});
|
|
if (result.ok) throw new Error("Expected physical JSON read failure");
|
|
expect(result.error.cause).toBe(physical);
|
|
} finally {
|
|
vi.restoreAllMocks();
|
|
}
|
|
});
|
|
|
|
it("preserves the caller cancellation reason during an analysis-view read", async () => {
|
|
const root = await createTestTempDirectory("rea-view-read-cancel-");
|
|
const path = join(root, "capture.json");
|
|
await writeFile(path, JSON.stringify(analysisViewCaptureEvidence()));
|
|
const reader = await holdFirstFileRead(path);
|
|
const controller = new AbortController();
|
|
const reason = new Error("stop analysis-view read");
|
|
try {
|
|
const pending = readEvidenceFileSource(path, {
|
|
signal: controller.signal,
|
|
});
|
|
await reader.firstRead;
|
|
controller.abort(reason);
|
|
reader.release();
|
|
const result = await pending;
|
|
expect(result).toMatchObject({
|
|
ok: false,
|
|
error: {
|
|
_tag: "AnalysisCancelledError",
|
|
operation: "inspect_analysis_view",
|
|
},
|
|
});
|
|
if (result.ok || !(result.error instanceof AnalysisCancelledError))
|
|
throw new Error("Expected cancelled analysis-view read");
|
|
expect(result.error.cause).toBe(reason);
|
|
} finally {
|
|
controller.abort(reason);
|
|
reader.release();
|
|
vi.restoreAllMocks();
|
|
}
|
|
});
|
|
|
|
it("does not treat a caller reason with physical-read shape as a physical read", async () => {
|
|
const root = await createTestTempDirectory("rea-view-opaque-cancel-");
|
|
const path = join(root, "capture.json");
|
|
await writeFile(path, JSON.stringify(analysisViewCaptureEvidence()));
|
|
const reader = await holdFirstFileRead(path);
|
|
const controller = new AbortController();
|
|
const reason = new JsonInputPhysicalReadFailure(
|
|
new Error("opaque caller reason with physical-failure shape"),
|
|
);
|
|
try {
|
|
const pending = readEvidenceFileSource(path, {
|
|
signal: controller.signal,
|
|
});
|
|
await reader.firstRead;
|
|
controller.abort(reason);
|
|
reader.release();
|
|
const result = await pending;
|
|
expect(result).toMatchObject({
|
|
ok: false,
|
|
error: { _tag: "AnalysisCancelledError", cause: reason },
|
|
});
|
|
if (result.ok || !(result.error instanceof AnalysisCancelledError))
|
|
throw new Error(
|
|
"Expected caller cancellation, not physical I/O failure",
|
|
);
|
|
expect(result.error.cause).toBe(reason);
|
|
} finally {
|
|
controller.abort(reason);
|
|
reader.release();
|
|
vi.restoreAllMocks();
|
|
}
|
|
});
|
|
});
|