Files
openclaw/src/cli/daemon-cli/install.integration.test.ts

148 lines
4.4 KiB
TypeScript

import fs from "node:fs/promises";
import path from "node:path";
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { makeTempWorkspace } from "../../test-helpers/workspace.js";
import { captureEnv } from "../../test-utils/env.js";
const runtimeLogs: string[] = [];
const runtimeErrors: string[] = [];
const serviceMock = vi.hoisted(() => ({
label: "Gateway",
loadedText: "loaded",
notLoadedText: "not loaded",
install: vi.fn(async (_opts?: { environment?: Record<string, string | undefined> }) => {}),
uninstall: vi.fn(async () => {}),
stop: vi.fn(async () => {}),
restart: vi.fn(async () => {}),
isLoaded: vi.fn(async () => false),
readCommand: vi.fn(async () => null),
readRuntime: vi.fn(async () => ({ status: "stopped" as const })),
}));
vi.mock("../../daemon/service.js", () => ({
resolveGatewayService: () => serviceMock,
}));
vi.mock("../../runtime.js", () => ({
defaultRuntime: {
log: (message: string) => runtimeLogs.push(message),
error: (message: string) => runtimeErrors.push(message),
exit: (code: number) => {
throw new Error(`__exit__:${code}`);
},
},
}));
const { runDaemonInstall } = await import("./install.js");
const { clearConfigCache } = await import("../../config/config.js");
async function readJson(filePath: string): Promise<Record<string, unknown>> {
return JSON.parse(await fs.readFile(filePath, "utf8")) as Record<string, unknown>;
}
describe("runDaemonInstall integration", () => {
let envSnapshot: ReturnType<typeof captureEnv>;
let tempHome: string;
let configPath: string;
beforeAll(async () => {
envSnapshot = captureEnv([
"HOME",
"OPENCLAW_STATE_DIR",
"OPENCLAW_CONFIG_PATH",
"OPENCLAW_GATEWAY_TOKEN",
"CLAWDBOT_GATEWAY_TOKEN",
"OPENCLAW_GATEWAY_PASSWORD",
"CLAWDBOT_GATEWAY_PASSWORD",
]);
tempHome = await makeTempWorkspace("openclaw-daemon-install-int-");
configPath = path.join(tempHome, "openclaw.json");
process.env.HOME = tempHome;
process.env.OPENCLAW_STATE_DIR = tempHome;
process.env.OPENCLAW_CONFIG_PATH = configPath;
});
afterAll(async () => {
envSnapshot.restore();
await fs.rm(tempHome, { recursive: true, force: true });
});
beforeEach(async () => {
runtimeLogs.length = 0;
runtimeErrors.length = 0;
vi.clearAllMocks();
delete process.env.OPENCLAW_GATEWAY_TOKEN;
delete process.env.CLAWDBOT_GATEWAY_TOKEN;
delete process.env.OPENCLAW_GATEWAY_PASSWORD;
delete process.env.CLAWDBOT_GATEWAY_PASSWORD;
serviceMock.isLoaded.mockResolvedValue(false);
await fs.writeFile(configPath, JSON.stringify({}, null, 2));
clearConfigCache();
});
it("fails closed when token SecretRef is required but unresolved", async () => {
await fs.writeFile(
configPath,
JSON.stringify(
{
secrets: {
providers: {
default: { source: "env" },
},
},
gateway: {
auth: {
mode: "token",
token: {
source: "env",
provider: "default",
id: "MISSING_GATEWAY_TOKEN",
},
},
},
},
null,
2,
),
);
clearConfigCache();
await expect(runDaemonInstall({ json: true })).rejects.toThrow("__exit__:1");
expect(serviceMock.install).not.toHaveBeenCalled();
const joined = runtimeLogs.join("\n");
expect(joined).toContain("SecretRef is configured but unresolved");
expect(joined).toContain("MISSING_GATEWAY_TOKEN");
});
it("auto-mints token when no source exists and persists the same token used for install env", async () => {
await fs.writeFile(
configPath,
JSON.stringify(
{
gateway: {
auth: {
mode: "token",
},
},
},
null,
2,
),
);
clearConfigCache();
await runDaemonInstall({ json: true });
expect(serviceMock.install).toHaveBeenCalledTimes(1);
const updated = await readJson(configPath);
const gateway = (updated.gateway ?? {}) as { auth?: { token?: string } };
const persistedToken = gateway.auth?.token;
expect(typeof persistedToken).toBe("string");
expect((persistedToken ?? "").length).toBeGreaterThan(0);
const installEnv = serviceMock.install.mock.calls[0]?.[0]?.environment;
expect(installEnv?.OPENCLAW_GATEWAY_TOKEN).toBe(persistedToken);
});
});