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" ;
import { createCliRuntimeCapture } from "../test-runtime-capture.js" ;
const { runtimeLogs, defaultRuntime, resetRuntimeCapture } = createCliRuntimeCapture();
const serviceMock = vi.hoisted(() => ({
label: "Gateway" ,
loadedText: "loaded" ,
notLoadedText: "not loaded" ,
stage: vi.fn(async (_opts?: { environment?: Record<string, string | undefined> }) => {}),
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,
}));
const { runDaemonInstall } = await import ("./install.js" );
const { clearConfigCache, clearRuntimeConfigSnapshot } = 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" ,
"OPENCLAW_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 () => {
vi.clearAllMocks();
resetRuntimeCapture();
clearRuntimeConfigSnapshot();
// Keep these defined-but-empty so dotenv won't repopulate from local .env.
process.env.OPENCLAW_GATEWAY_TOKEN = "" ;
process.env.OPENCLAW_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 without embedding it into service 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).toBeUndefined();
});
});
Messung V0.5 in Prozent C=93 H=94 G=93
¤ Dauer der Verarbeitung: 0.15 Sekunden
(vorverarbeitet am 2026-06-05)
¤
*© Formatika GbR, Deutschland