fix: harden Pi lifecycle recovery
This commit is contained in:
@@ -2,7 +2,7 @@ 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, mkdtempSync, rmSync } from "node:fs";
|
||||
import { chmodSync, readFileSync, 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";
|
||||
@@ -97,7 +97,7 @@ test("maintenance rejects new and resumed session admission without interrupting
|
||||
expect(resume.json()).toEqual(create.json());
|
||||
});
|
||||
|
||||
test("a server-profile marker does not weaken the in-process maintenance gate", async () => {
|
||||
test("a durable maintenance marker initializes admission closed after backend recreation", async () => {
|
||||
const dir = mkdtempSync(path.join(tmpdir(), "tht-maintenance-"));
|
||||
try {
|
||||
const marker = path.join(dir, "maintenance.json");
|
||||
@@ -108,12 +108,71 @@ test("a server-profile marker does not weaken the in-process maintenance gate",
|
||||
const response = await app.inject({
|
||||
method: "POST", url: "/sessions", headers: aliceHeaders, payload: { question: "q" },
|
||||
});
|
||||
expect(response.statusCode).not.toBe(503);
|
||||
expect(response.statusCode).toBe(503);
|
||||
expect(response.json()).toMatchObject({ code: "maintenance" });
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test.each(["none", "upstream"] as const)(
|
||||
"maintenance control is loopback-only and independent of %s authentication",
|
||||
async (authMode) => {
|
||||
const dir = mkdtempSync(path.join(tmpdir(), "tht-maintenance-control-"));
|
||||
const marker = path.join(dir, "maintenance.json");
|
||||
try {
|
||||
const app = buildApp(loadConfig({
|
||||
AUTH_MODE: authMode,
|
||||
THT_HARNESS_DIR: "../harness",
|
||||
THT_MAINTENANCE_FILE: marker,
|
||||
}), { thtRunner: {} as any });
|
||||
|
||||
const activated = await app.inject({ method: "POST", url: "/internal/maintenance/activate" });
|
||||
expect(activated.statusCode).toBe(200);
|
||||
expect(activated.json()).toEqual({ active: true, admissions: 0 });
|
||||
|
||||
const spoofedProxy = await app.inject({
|
||||
method: "POST",
|
||||
url: "/internal/maintenance/deactivate",
|
||||
remoteAddress: "172.30.0.9",
|
||||
headers: {
|
||||
"x-thoth-principal-subject": "thothctl-maintenance",
|
||||
"x-thoth-is-admin": "1",
|
||||
},
|
||||
});
|
||||
expect(spoofedProxy.statusCode).toBe(403);
|
||||
|
||||
const status = await app.inject({ method: "GET", url: "/internal/maintenance/status" });
|
||||
expect(status.json()).toEqual({ active: true, admissions: 0 });
|
||||
const deactivated = await app.inject({ method: "POST", url: "/internal/maintenance/deactivate" });
|
||||
expect(deactivated.json()).toEqual({ active: false, admissions: 0 });
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
test("admin all-sessions response matches the authenticated lifecycle wire fixture", async () => {
|
||||
const fixture = JSON.parse(readFileSync(
|
||||
path.join(import.meta.dirname, "fixtures", "sessions-scope-all.json"),
|
||||
"utf8",
|
||||
));
|
||||
const app = buildApp(loadConfig({ AUTH_MODE: "upstream", THT_HARNESS_DIR: "../harness" }), {
|
||||
thtRunner: {
|
||||
withPrincipal: () => ({ sessionList: async () => fixture.map(({ active: _active, ...row }: any) => row) }),
|
||||
} as any,
|
||||
mgr: { get: () => undefined } as any,
|
||||
workspaceRegistry: defaultWorkspaceRegistry as any,
|
||||
});
|
||||
const response = await app.inject({
|
||||
method: "GET",
|
||||
url: "/sessions?scope=all",
|
||||
headers: { ...aliceHeaders, "x-thoth-is-admin": "1" },
|
||||
});
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.json()).toEqual(fixture);
|
||||
});
|
||||
|
||||
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" }), {
|
||||
|
||||
Reference in New Issue
Block a user