fix: harden pi maintenance lifecycle

This commit is contained in:
2026-08-04 19:09:04 +02:00
parent 8fde1f81c7
commit 0b9ad7f53f
21 changed files with 432 additions and 171 deletions
+39 -1
View File
@@ -2,7 +2,8 @@ import { test, expect, vi } from "vitest";
import { spawn as nodeSpawn } from "node:child_process";
import path from "node:path";
import os from "node:os";
import { chmodSync, unlinkSync, writeFileSync } from "node:fs";
import { chmodSync, unlinkSync, writeFileSync, mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { buildApp as buildRealApp } from "../src/app.js";
import { loadConfig } from "../src/config.js";
import { SseHub } from "../src/sse/sse-hub.js";
@@ -74,6 +75,43 @@ test("upstream requests without a principal fail before a Pi runtime can be crea
expect(created).toBe(false);
});
test("maintenance rejects new and resumed session admission without interrupting running sessions", async () => {
const app = buildApp(loadConfig({ AUTH_MODE: "upstream", THT_HARNESS_DIR: "../harness" }), {
maintenanceGate: () => true,
thtRunner: { withPrincipal: () => ({ sessionShow: async () => ({ id: "open", status: "open" }) }) } as any,
});
const create = await app.inject({
method: "POST", url: "/sessions", headers: aliceHeaders, payload: { question: "q" },
});
const resume = await app.inject({ method: "POST", url: "/sessions/open/resume", headers: aliceHeaders });
expect(create.statusCode).toBe(503);
expect(resume.statusCode).toBe(503);
expect(create.json()).toEqual({
code: "maintenance", error: "Session admission is temporarily paused for maintenance. Try again shortly.",
});
expect(resume.json()).toEqual(create.json());
});
test("the on-disk maintenance marker gates admission in an upstream server profile", async () => {
const dir = mkdtempSync(path.join(tmpdir(), "tht-maintenance-"));
try {
const marker = path.join(dir, "maintenance.json");
writeFileSync(marker, '{"transaction":"test"}\n');
const app = buildApp(loadConfig({
AUTH_MODE: "upstream", THT_HARNESS_DIR: "../harness", THT_MAINTENANCE_FILE: marker,
}), { thtRunner: {} as any });
const response = await app.inject({
method: "POST", url: "/sessions", headers: aliceHeaders, payload: { question: "q" },
});
expect(response.statusCode).toBe(503);
expect(response.json().code).toBe("maintenance");
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test("session routes conceal foreign or missing sessions and deny SSE before it subscribes", async () => {
let subscribed = false;
const app = buildApp(loadConfig({ AUTH_MODE: "upstream", THT_HARNESS_DIR: "../harness" }), {