fix: harden pi management verification

This commit is contained in:
2026-08-05 01:00:13 +02:00
parent d6b4a08a02
commit 55926c75f8
12 changed files with 502 additions and 25 deletions
+87 -1
View File
@@ -1,7 +1,7 @@
import { mkdtempSync, readFileSync, readdirSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { expect, test } from "vitest";
import { expect, test, vi } from "vitest";
import { loadConfig } from "../src/config.js";
import {
PiManagementError,
@@ -140,12 +140,95 @@ test("smoke uses the configured timeout and reports a sanitized timeout", async
expect(calls).toEqual([{ command: "/usr/local/bin/pi", args: ["--version"], timeout: 750 }]);
});
// Catches a smoke endpoint that validates only the Pi binary/model catalogue and never makes a
// request through the configured provider and model.
test("smoke exercises the configured provider and model", async () => {
const providerChecks: unknown[] = [];
const service = createPiManagement(configFor(), {
execute: successfulExec([]),
listModels: async () => supportedModels,
readSettings: () => ({ provider: "zai", model: "glm-5.2", thinking: "medium" }),
smokeProvider: async (request) => { providerChecks.push(request); },
now: () => new Date("2026-08-05T10:00:00.000Z"),
});
await expect(service.test()).resolves.toEqual({
ready: true,
checkedAt: "2026-08-05T10:00:00.000Z",
});
expect(providerChecks).toEqual([{
provider: "zai", model: "glm-5.2", reasoning: "medium", timeoutMs: expect.any(Number),
}]);
});
// Catches expired provider credentials being treated as ready or raw provider diagnostics being
// reflected through the management API.
test("smoke fails closed and sanitizes configured-provider authentication errors", async () => {
const service = createPiManagement(configFor(), {
execute: successfulExec([]),
listModels: async () => supportedModels,
readSettings: () => ({ provider: "zai", model: "glm-5.2", thinking: "medium" }),
smokeProvider: async () => {
throw new Error('401 {"token":"raw-expired-token","output":"raw-provider-output"}');
},
now: () => new Date("2026-08-05T10:00:00.000Z"),
});
const result = await service.test();
expect(result).toEqual({
ready: false,
message: "Pi provider smoke check failed",
checkedAt: "2026-08-05T10:00:00.000Z",
});
expect(JSON.stringify(result)).not.toMatch(/raw-expired-token|raw-provider-output/);
});
// Catches separate per-phase timeouts that allow a later provider turn to exceed the one
// end-to-end Pi Management smoke budget.
test("smoke applies one deadline across version and a hung provider turn", async () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-08-05T10:00:00.000Z"));
try {
const providerTimeouts: number[] = [];
const service = createPiManagement(configFor(), {
execute: async () => await new Promise((resolve) => setTimeout(
() => resolve({ stdout: "pi 0.80.3\n", stderr: "" }),
500,
)),
listModels: async () => supportedModels,
readSettings: () => ({ provider: "zai", model: "glm-5.2", thinking: "medium" }),
smokeProvider: async ({ timeoutMs }) => {
providerTimeouts.push(timeoutMs);
await new Promise(() => {});
},
now: () => new Date("2026-08-05T10:00:00.000Z"),
});
let settled = false;
const pending = service.test().finally(() => { settled = true; });
await vi.advanceTimersByTimeAsync(500);
expect(providerTimeouts).toEqual([250]);
await vi.advanceTimersByTimeAsync(249);
expect(settled).toBe(false);
await vi.advanceTimersByTimeAsync(1);
await expect(pending).resolves.toEqual({
ready: false,
message: "Pi smoke check timed out",
checkedAt: "2026-08-05T10:00:00.000Z",
});
} finally {
vi.useRealTimers();
}
});
// Catches an unbounded diagnostics endpoint or one that returns bearer tokens and connection
// passwords captured in Pi output.
test("logs keep only the latest 200 redacted lines", async () => {
const source = Array.from({ length: 205 }, (_, index) => `line-${index + 1}`);
source[203] = "Authorization: Bearer raw-bearer-token";
source[204] = "database_url=postgres://thoth:raw-db-password@example.invalid/db";
source[202] = '{"token":"raw-json-secret","password":"raw-json-password"}';
source[201] = "THT_MODEL_API_KEY=raw-env-secret";
const service = createPiManagement(configFor(), {
execute: successfulExec([]),
listModels: async () => supportedModels,
@@ -159,5 +242,8 @@ test("logs keep only the latest 200 redacted lines", async () => {
expect(logs.lines[0]).toBe("line-6");
expect(logs.lines.join("\n")).not.toContain("raw-bearer-token");
expect(logs.lines.join("\n")).not.toContain("raw-db-password");
expect(logs.lines.join("\n")).not.toContain("raw-json-secret");
expect(logs.lines.join("\n")).not.toContain("raw-json-password");
expect(logs.lines.join("\n")).not.toContain("raw-env-secret");
expect(logs.lines.join("\n")).toContain("[REDACTED]");
});