17 lines
3.4 KiB
TypeScript
17 lines
3.4 KiB
TypeScript
import { describe, expect, it, afterEach } from "vitest";
|
|
import { mkdtempSync, renameSync, mkdirSync, rmSync, readFileSync, chmodSync, writeFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { WorkspaceFsAtV1 } from "../src/workspaces/workspace-fs-at.js";
|
|
import { PreprocessingStateStore } from "../src/workspaces/preprocessing-state.js";
|
|
import { VerifiedWorkspaceLockRootLeaseFactory } from "../src/workspaces/workspace-lock-root-lease.js";
|
|
const roots: string[] = [];
|
|
afterEach(async () => { for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true }); });
|
|
async function makeStore() { const parent = mkdtempSync(join(process.cwd(), "thoth-state-")); roots.push(parent); const factory = new VerifiedWorkspaceLockRootLeaseFactory({ workspaceFsAt: new WorkspaceFsAtV1(), installationId: "test", sessionsRootFromValidatedInstallationConfig: parent, serviceUid: process.getuid!(), provisionedWorkspaceMode: 0o700 }); const lease = await factory.acquireOrProvision(factory.canonicalInput("abc-workspace")); return { root: join(parent, "abc-workspace"), store: new PreprocessingStateStore(lease), lease }; }
|
|
const input = { workspaceId: "abc-workspace" as never, revision: "a".repeat(40) as never, operation: "schema" };
|
|
describe("durable preprocessing state", () => {
|
|
it("creates and replays exact state with immutable identity", async () => { const { store, lease } = await makeStore(); const state = await store.create(input); expect(await store.create({ ...input, runId: state.runId })).toEqual(state); expect(await store.loadForResume({ ...input, runId: state.runId })).toEqual(state); await expect(store.transition(state.runId, { phase: "introspected", workspaceId: "evil" } as never)).rejects.toThrow("preprocessing_conflict"); await lease.close(); });
|
|
it("rejects tampered, traversal, mode and version state before returning it", async () => { const { root, store, lease } = await makeStore(); const state = await store.create(input); const path = join(root, "preprocessing", "jobs", `${state.runId}.json`); const original = JSON.parse(readFileSync(path, "utf8")); writeFileSync(path, JSON.stringify({ ...original, schemaVersion: 9 })); await expect(store.load({ ...input, runId: state.runId })).rejects.toThrow(); writeFileSync(path, JSON.stringify(original)); chmodSync(path, 0o644); await expect(store.load({ ...input, runId: state.runId })).rejects.toThrow("preprocessing_conflict"); await expect(store.load({ ...input, runId: "../" + state.runId })).rejects.toThrow(); await lease.close(); });
|
|
it("writes candidate artifacts atomically and validates review identity", async () => { const { store, lease } = await makeStore(); const state = await store.create(input); const bytes = new TextEncoder().encode("tables: []\n"); const artifact = await store.writeFkCandidate(state.runId, bytes); expect(artifact.bytes).toBe(bytes.byteLength); await expect(store.recordFkReview(state.runId, { candidate: { ...artifact, digest: "not-a-digest" }, reviewSha256: "a".repeat(64) })).rejects.toThrow(); await lease.close(); });
|
|
it("retained root replacement fails closed without writing replacement state", async () => { const { root, store, lease } = await makeStore(); renameSync(root, `${root}.old`); mkdirSync(root, { mode: 0o700 }); await expect(store.create(input)).rejects.toThrow("preprocessing_conflict"); expect(() => readFileSync(join(root, "preprocessing", "jobs"))).toThrow(); await lease.close().catch(() => undefined); });
|
|
});
|