test: harden restart sentinel and host env coverage

This commit is contained in:
Peter Steinberger
2026-03-13 19:36:49 +00:00
parent a9b5fe4099
commit eb32f42b53
2 changed files with 109 additions and 0 deletions

View File

@@ -120,6 +120,39 @@ describe("sanitizeHostExecEnv", () => {
expect(env[" BAD KEY"]).toBeUndefined();
expect(env["NOT-PORTABLE"]).toBeUndefined();
});
it("can allow PATH overrides when explicitly opted out of blocking", () => {
const env = sanitizeHostExecEnv({
baseEnv: {
PATH: "/usr/bin:/bin",
},
overrides: {
PATH: "/custom/bin",
},
blockPathOverrides: false,
});
expect(env.PATH).toBe("/custom/bin");
expect(env.OPENCLAW_CLI).toBe(OPENCLAW_CLI_ENV_VALUE);
});
it("drops non-string inherited values and non-portable inherited keys", () => {
const env = sanitizeHostExecEnv({
baseEnv: {
PATH: "/usr/bin:/bin",
GOOD: "1",
// oxlint-disable-next-line typescript/no-explicit-any
BAD_NUMBER: 1 as any,
"NOT-PORTABLE": "x",
},
});
expect(env).toEqual({
OPENCLAW_CLI: OPENCLAW_CLI_ENV_VALUE,
PATH: "/usr/bin:/bin",
GOOD: "1",
});
});
});
describe("isDangerousHostEnvOverrideVarName", () => {
@@ -174,6 +207,33 @@ describe("sanitizeSystemRunEnvOverrides", () => {
LC_ALL: "C",
});
});
it("returns undefined when no shell-wrapper overrides survive", () => {
expect(
sanitizeSystemRunEnvOverrides({
shellWrapper: true,
overrides: {
TOKEN: "abc",
},
}),
).toBeUndefined();
expect(sanitizeSystemRunEnvOverrides({ shellWrapper: true })).toBeUndefined();
});
it("keeps allowlisted shell-wrapper overrides case-insensitively", () => {
expect(
sanitizeSystemRunEnvOverrides({
shellWrapper: true,
overrides: {
lang: "C",
ColorTerm: "truecolor",
},
}),
).toEqual({
lang: "C",
ColorTerm: "truecolor",
});
});
});
describe("shell wrapper exploit regression", () => {

View File

@@ -5,9 +5,11 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { captureEnv } from "../test-utils/env.js";
import {
consumeRestartSentinel,
formatDoctorNonInteractiveHint,
formatRestartSentinelMessage,
readRestartSentinel,
resolveRestartSentinelPath,
summarizeRestartSentinel,
trimLogTail,
writeRestartSentinel,
} from "./restart-sentinel.js";
@@ -59,6 +61,15 @@ describe("restart sentinel", () => {
await expect(fs.stat(filePath)).rejects.toThrow();
});
it("drops structurally invalid sentinel payloads", async () => {
const filePath = resolveRestartSentinelPath();
await fs.mkdir(path.dirname(filePath), { recursive: true });
await fs.writeFile(filePath, JSON.stringify({ version: 2, payload: null }), "utf-8");
await expect(readRestartSentinel()).resolves.toBeNull();
await expect(fs.stat(filePath)).rejects.toThrow();
});
it("formatRestartSentinelMessage uses custom message when present", () => {
const payload = {
kind: "config-apply" as const,
@@ -93,6 +104,26 @@ describe("restart sentinel", () => {
expect(result).toContain("Gateway restart");
});
it("formats summary, distinct reason, and doctor hint together", () => {
const payload = {
kind: "config-patch" as const,
status: "error" as const,
ts: Date.now(),
message: "Patch failed",
doctorHint: "Run openclaw doctor",
stats: { mode: "patch", reason: "validation failed" },
};
expect(formatRestartSentinelMessage(payload)).toBe(
[
"Gateway restart config-patch error (patch)",
"Patch failed",
"Reason: validation failed",
"Run openclaw doctor",
].join("\n"),
);
});
it("trims log tails", () => {
const text = "a".repeat(9000);
const trimmed = trimLogTail(text, 8000);
@@ -115,6 +146,18 @@ describe("restart sentinel", () => {
expect(textA).toContain("Gateway restart restart ok");
expect(textA).not.toContain('"ts"');
});
it("summarizes restart payloads and trims log tails without trailing whitespace", () => {
expect(
summarizeRestartSentinel({
kind: "update",
status: "skipped",
ts: 1,
}),
).toBe("Gateway restart update skipped");
expect(trimLogTail("hello\n")).toBe("hello");
expect(trimLogTail(undefined)).toBeNull();
});
});
describe("restart sentinel message dedup", () => {
@@ -145,4 +188,10 @@ describe("restart sentinel message dedup", () => {
expect(result).toContain("Restart requested by /restart");
expect(result).toContain("Reason: /restart");
});
it("formats the non-interactive doctor command", () => {
expect(formatDoctorNonInteractiveHint({ PATH: "/usr/bin:/bin" })).toContain(
"openclaw doctor --non-interactive",
);
});
});