63 lines
2.6 KiB
TypeScript
63 lines
2.6 KiB
TypeScript
import { spawnSync } from "node:child_process";
|
|
import { chmodSync, mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join, resolve } from "node:path";
|
|
import { describe, expect, it } from "vitest";
|
|
import { formatStartupFailure } from "../src/startup-error.js";
|
|
|
|
describe("formatStartupFailure", () => {
|
|
it.each([
|
|
[new Error("auth_session_store_invalid"), "backend startup failed: auth_session_store_invalid"],
|
|
[new Error("auth_config_invalid"), "backend startup failed: auth_config_invalid"],
|
|
[new Error("workspace_registry_invalid"), "backend startup failed: workspace_registry_invalid"],
|
|
])("emits only an allowlisted startup cause", (error, expected) => {
|
|
expect(formatStartupFailure(error)).toBe(expected);
|
|
});
|
|
|
|
it("collapses unknown errors without exposing their message, stack, token, or path", () => {
|
|
const error = new Error(
|
|
"EACCES password=plain-secret token=token-secret at /run/secrets/private-token",
|
|
);
|
|
error.stack = "Error: raw failure\n at /app/backend/dist/server.js:42:1";
|
|
|
|
const formatted = formatStartupFailure(error);
|
|
|
|
expect(formatted).toBe("backend startup failed: startup_unknown");
|
|
for (const leaked of [
|
|
"EACCES", "plain-secret", "token-secret", "/run/secrets", "raw failure", "server.js",
|
|
]) {
|
|
expect(formatted).not.toContain(leaked);
|
|
}
|
|
});
|
|
|
|
it("sanitizes synchronous configuration failures from the real server subprocess", () => {
|
|
const root = mkdtempSync(join(tmpdir(), "thothii-startup-secret-"));
|
|
const secret = "startup-password-do-not-log";
|
|
const authDirectory = join(root, `auth-${secret}`);
|
|
mkdirSync(authDirectory, { mode: 0o700 });
|
|
const authFile = join(authDirectory, "auth.yaml");
|
|
writeFileSync(authFile, `version: 1\nmode: local\npassword: ${secret}\n`, { mode: 0o600 });
|
|
chmodSync(authDirectory, 0o700);
|
|
const entrypoint = resolve(process.cwd(), "src/server.ts");
|
|
|
|
const result = spawnSync(process.execPath, ["--import", "tsx", entrypoint], {
|
|
cwd: process.cwd(),
|
|
encoding: "utf8",
|
|
timeout: 15_000,
|
|
env: {
|
|
...process.env,
|
|
NODE_ENV: "test",
|
|
THT_AUTH_CONFIG_FILE: authFile,
|
|
THT_AUTH_STATE_ROOT: join(root, "auth-state"),
|
|
},
|
|
});
|
|
|
|
expect(result.status).toBe(1);
|
|
expect(result.stdout).toBe("");
|
|
expect(result.stderr.trim()).toBe("backend startup failed: auth_config_invalid");
|
|
expect(`${result.stdout}${result.stderr}`).not.toContain(secret);
|
|
expect(`${result.stdout}${result.stderr}`).not.toContain(authDirectory);
|
|
expect(`${result.stdout}${result.stderr}`).not.toContain("server.ts");
|
|
});
|
|
});
|