Gateway: add SecretRef support for gateway.auth.token with auth-mode guardrails (#35094)
This commit is contained in:
147
src/cli/daemon-cli/install.integration.test.ts
Normal file
147
src/cli/daemon-cli/install.integration.test.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user