Files
ThothII/backend/test/maintenance-gate.test.ts

130 lines
5.3 KiB
TypeScript

import { test, expect } from "vitest";
import { existsSync, mkdtempSync, readdirSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { MaintenanceBarrier } from "../src/runtime/maintenance-gate.js";
test("activation waits for an in-flight admission lease and rejects later admissions", async () => {
const gate = new MaintenanceBarrier();
const release = gate.acquire();
expect(release).toBeTypeOf("function");
let acknowledged = false;
const activation = gate.activate().then(() => { acknowledged = true; });
await Promise.resolve();
expect(acknowledged).toBe(false);
expect(gate.acquire()).toBeUndefined();
release?.();
await activation;
expect(acknowledged).toBe(true);
expect(gate.status()).toEqual({ active: true, admissions: 0 });
gate.deactivate();
expect(gate.acquire()).toBeTypeOf("function");
});
test("durable activation survives recreation and deactivation removes the marker first", async () => {
const directory = mkdtempSync(join(tmpdir(), "tht-maintenance-gate-"));
const marker = join(directory, "maintenance.json");
try {
const first = new MaintenanceBarrier(marker);
const release = first.acquire();
expect(release).toBeTypeOf("function");
const activation = first.activate();
expect(existsSync(marker)).toBe(true);
expect(first.acquire()).toBeUndefined();
expect(first.status()).toEqual({ active: true, admissions: 1 });
release?.();
await activation;
expect(first.status()).toEqual({ active: true, admissions: 0 });
const recreated = new MaintenanceBarrier(marker);
expect(recreated.status()).toEqual({ active: true, admissions: 0 });
recreated.deactivate();
expect(existsSync(marker)).toBe(false);
expect(recreated.status()).toEqual({ active: false, admissions: 0 });
} finally {
rmSync(directory, { recursive: true, force: true });
}
});
test("activation reconciles active state when directory fsync fails after marker rename", async () => {
const directory = mkdtempSync(join(tmpdir(), "tht-maintenance-activate-fsync-"));
const marker = join(directory, "maintenance.json");
try {
const gate = new MaintenanceBarrier(marker, {
syncDirectory() { throw new Error("injected post-rename fsync failure"); },
});
await expect(gate.activate()).rejects.toThrow(/post-rename fsync failure/);
expect(existsSync(marker)).toBe(true);
expect(gate.status()).toEqual({ active: true, admissions: 0, recoveryRequired: true });
expect(gate.acquire()).toBeUndefined();
const recovered = new MaintenanceBarrier(marker);
expect(recovered.status()).toEqual({ active: true, admissions: 0 });
expect(recovered.acquire()).toBeUndefined();
} finally {
rmSync(directory, { recursive: true, force: true });
}
});
test.each(["write", "fsync"] as const)(
"activation cleans its marker temp file after a pre-rename %s failure",
async (failure) => {
const directory = mkdtempSync(join(tmpdir(), "tht-maintenance-temp-cleanup-"));
const marker = join(directory, "maintenance.json");
try {
const gate = new MaintenanceBarrier(marker, {
writeFile(descriptor, contents) {
if (failure === "write") throw new Error("injected marker write failure");
writeFileSync(descriptor, contents, "utf8");
},
syncFile() {
if (failure === "fsync") throw new Error("injected marker fsync failure");
},
syncDirectory() {},
});
await expect(gate.activate()).rejects.toThrow(`injected marker ${failure} failure`);
expect(existsSync(marker)).toBe(false);
expect(readdirSync(directory).filter((entry) => entry.includes(".tmp-"))).toEqual([]);
expect(gate.status()).toEqual({ active: false, admissions: 0, recoveryRequired: true });
} finally {
rmSync(directory, { recursive: true, force: true });
}
},
);
test("deactivation reconciles inactive state when directory fsync fails after marker removal", async () => {
const directory = mkdtempSync(join(tmpdir(), "tht-maintenance-deactivate-fsync-"));
const marker = join(directory, "maintenance.json");
let failSync = false;
try {
const gate = new MaintenanceBarrier(marker, {
syncDirectory() {
if (failSync) throw new Error("injected post-remove fsync failure");
},
});
await gate.activate();
failSync = true;
expect(() => gate.deactivate()).toThrow(/post-remove fsync failure/);
expect(existsSync(marker)).toBe(true);
expect(gate.status()).toEqual({ active: true, admissions: 0, recoveryRequired: true });
expect(gate.acquire()).toBeUndefined();
} finally {
rmSync(directory, { recursive: true, force: true });
}
});
test("status and admission recover from a persistent marker even when memory started inactive", () => {
const directory = mkdtempSync(join(tmpdir(), "tht-maintenance-reconcile-"));
const marker = join(directory, "maintenance.json");
try {
const gate = new MaintenanceBarrier(marker);
expect(gate.status().active).toBe(false);
writeFileSync(marker, '{"version":1,"active":true}\n', { mode: 0o600 });
expect(gate.status()).toEqual({ active: true, admissions: 0 });
expect(gate.acquire()).toBeUndefined();
} finally {
rmSync(directory, { recursive: true, force: true });
}
});