Files
linuxcncWebWASM/web/workspace/test_recovery.mjs

47 lines
2.7 KiB
JavaScript

import assert from "node:assert/strict";
import { writeFile } from "node:fs/promises";
import { MemoryStorage, WorkspaceJournal, canonicalJson } from "./journal.mjs";
import { recoverWorkspace } from "./recovery.mjs";
const storage = new MemoryStorage();
const journal = new WorkspaceJournal(storage, { projectId: "demo" });
const prepared = journal.prepare("op-1", [{ path: "config/main.ini", data: "[EMC]\n" }, { path: "programs/main.ngc", data: "G0 X1\n" }]);
journal.commit("op-1");
let recovered = recoverWorkspace(storage, { projectId: "demo" });
assert.equal(recovered.status, "recovered");
assert.deepEqual(recovered.files, { "config/main.ini": "[EMC]\n", "programs/main.ngc": "G0 X1\n" });
assert.equal(recovered.applied[0].commit_seq, prepared.commit_seq);
const halfStorage = new MemoryStorage();
const halfJournal = new WorkspaceJournal(halfStorage, { projectId: "half" });
halfJournal.prepare("op-half", [{ path: "config/half.ini", data: "half" }]);
recovered = recoverWorkspace(halfStorage, { projectId: "half" });
assert.equal(recovered.status, "recovered");
assert.deepEqual(recovered.files, {});
assert.deepEqual(recovered.half_commits, [1]);
assert.throws(() => journal.prepare("op-path", [{ path: "../escape", data: "x" }]), /workspace_path_invalid/);
assert.throws(() => journal.prepare("op-data", [{ path: "config/bad", data: 1 }]), /workspace_data_invalid/);
assert.throws(() => journal.commit("missing"), /prepare_missing/);
const corrupted = new MemoryStorage(Object.fromEntries(storage.entries()));
const commitKey = [...corrupted.records.keys()].find((key) => key.endsWith(".commit"));
const commit = JSON.parse(corrupted.get(commitKey));
commit.prepare_hash = "fnv1a64:0000000000000000";
corrupted.set(commitKey, canonicalJson(commit));
recovered = recoverWorkspace(corrupted, { projectId: "demo" });
assert.equal(recovered.status, "blocked");
assert.equal(recovered.blocked[0].reason, "hash_mismatch");
const firstBytes = canonicalJson([...storage.entries()]);
const secondBytes = canonicalJson([...storage.entries()]);
assert.equal(firstBytes, secondBytes);
await writeFile(new URL("../../coverage/workspace-recovery.report.json", import.meta.url), `${JSON.stringify({ schema: "workspace-recovery-report.v1", status: "passed", scenarios: ["atomic_commit", "half_commit_ignored", "path_validation", "hash_corruption_blocked"], journal_bytes_sha256: fnvHash(firstBytes), recovery_state_hash: recovered.state_hash }, null, 2)}\n`);
console.log("workspace_recovery=passed commit=" + prepared.commit_seq);
function fnvHash(value) {
let hash = 1469598103934665603n;
for (const byte of new TextEncoder().encode(value)) hash = BigInt.asUintN(64, (hash ^ BigInt(byte)) * 1099511628211n);
return `fnv1a64:${hash.toString(16).padStart(16, "0")}`;
}