Files
rea/tests/boundary/sqlite/sqliteDatabaseFailures.test.ts
T
morluto 6fdcac6cad fix(sqlite): preserve catalog byte identity and worker context
Match table metadata by stored name bytes and fold only ASCII code units for relationships, reusing parsed facts without per-trigger queries. Preserve caller paths on structured worker limits and V8 heap failures.

Validated invalid UTF-8 collisions, distinct UTF-16 names and real worker transport refusal through CLI and stdio MCP. Controlled stderr covers OOM classification; actual memory exhaustion was not induced.

Fixes #6795

Fixes #6607
2026-10-24 01:00:14 +08:00

158 lines
5.1 KiB
TypeScript

import { ArtifactReaderFailure } from "../../../src/artifacts/ArtifactReader.js";
import { expect, it } from "vitest";
import { OwnedCommandFailure } from "../../../src/process/OwnedCommand.js";
import { projectAnalysisError } from "../../../src/domain/analysisErrorProjection.js";
import { sqliteDatabaseFailure } from "../../../src/sqlite/SqliteDatabaseFailures.js";
import { SQLITE_DATABASE_LIMITS } from "../../../src/sqlite/SqliteDatabaseLimits.js";
it.each([null, "owned stop unconfirmed"])(
"preserves a selected worker deadline with cleanup=%s",
(cleanup) => {
const cause = new OwnedCommandFailure(
"timeout",
"Command deadline elapsed.",
{
stdout: { text: "partial-owned-output", bytes: 20, observedBytes: 20 },
stderr: { text: "", bytes: 0, observedBytes: 0 },
exitCode: null,
signal: null,
},
cleanup,
undefined,
["pid:owned-fixture"],
);
const failure = sqliteDatabaseFailure(
cause,
"/selected.db",
"worker",
undefined,
1234,
);
expect(failure.cause).toBe(cause);
const projected = projectAnalysisError(failure);
if (cleanup === null)
expect(projected).toMatchObject({
code: "provider_timeout",
details: { timeout_ms: 1234 },
remediation: { action: expect.stringContaining("--worker-timeout-ms") },
});
else
expect(projected).toMatchObject({
code: "cleanup_incomplete",
retryable: false,
details: {
resources: ["pid:owned-fixture"],
diagnostics: {
previous_error: {
code: "provider_timeout",
details: { timeout_ms: 1234 },
remediation: {
action: expect.stringContaining("worker_timeout_ms"),
},
},
},
},
});
expect(
JSON.stringify(projected).split("partial-owned-output"),
).toHaveLength(2);
if (cleanup !== null) {
expect(failure.cleanupPrimary).toMatchObject({
_tag: "AnalysisTimeoutError",
timeoutMs: 1234,
});
failure.resolveCleanup();
const resolved = projectAnalysisError(failure);
expect(resolved).toMatchObject({
code: "provider_timeout",
details: { timeout_ms: 1234 },
});
expect(
JSON.stringify(resolved).split("partial-owned-output"),
).toHaveLength(2);
}
},
);
it("does not recommend a timer above the supported maximum", () => {
const failure = projectAnalysisError(
sqliteDatabaseFailure(
new OwnedCommandFailure("timeout", "deadline"),
"/selected.db",
"worker",
undefined,
2_147_483_647,
),
);
expect(failure.details).toMatchObject({ timeout_ms: 2_147_483_647 });
expect(failure.remediation.action).toContain("no higher worker deadline");
expect(failure.remediation.action).not.toContain("Increase");
});
it("says diagnostic capture has no runtime override", () => {
const projected = projectAnalysisError(
sqliteDatabaseFailure(
new OwnedCommandFailure("output-limit", "diagnostics exceeded"),
"/selected.db",
"worker",
),
);
expect(projected.remediation.action).toContain(
String(SQLITE_DATABASE_LIMITS.diagnosticBytes),
);
expect(projected.remediation.action).toContain("no runtime override");
expect(projected.remediation.action).toContain(
"worker_timeout_ms does not raise it",
);
});
it.each(["/selected.db", "./Database files/another.db"])(
"retains the caller-selected path %s when the owned worker reports V8 heap exhaustion",
(path) => {
const stderr =
"FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory";
const cause = new OwnedCommandFailure("process", "Worker exited.", {
stdout: { text: "", bytes: 0, observedBytes: 0 },
stderr: {
text: stderr,
bytes: Buffer.byteLength(stderr),
observedBytes: Buffer.byteLength(stderr),
},
exitCode: null,
signal: "SIGABRT",
});
const failure = sqliteDatabaseFailure(cause, path, "worker");
expect(failure.cause).toBe(cause);
expect(projectAnalysisError(failure)).toMatchObject({
code: "resource_constraint",
details: {
operation: "inspect_sqlite_database",
resource: "memory",
reported_limits: { javascript_heap_bytes: 256 * 1024 * 1024 },
diagnostics: { selected_path: path },
captured_output: { stderr },
},
});
},
);
it("does not infer cancellation from a later signal when cleanup carries no cancellation fact", () => {
const controller = new AbortController();
const cause = new ArtifactReaderFailure("io", "cleanup remains owned", {
cleanup: { reason: "close failed", resources: ["/owned/reply.json"] },
});
controller.abort(new Error("later caller cancellation"));
const failure = sqliteDatabaseFailure(
cause,
"/selected.db",
"worker",
controller.signal,
);
expect(failure.cleanupIncomplete).toBe(true);
expect(failure.cleanupPrimary).toBeUndefined();
expect(projectAnalysisError(failure)).toMatchObject({
code: "cleanup_incomplete",
details: { resources: ["/owned/reply.json"] },
});
});