mirror of https://github.com/openclaw/openclaw.git
263 lines
8.4 KiB
TypeScript
263 lines
8.4 KiB
TypeScript
import { describe, expect, it, vi } from "vitest";
|
|
import { createWizardPrompter as buildWizardPrompter } from "../../test/helpers/wizard-prompter.js";
|
|
import { DEFAULT_DANGEROUS_NODE_COMMANDS } from "../gateway/node-command-policy.js";
|
|
import type { RuntimeEnv } from "../runtime.js";
|
|
import type { WizardPrompter, WizardSelectParams } from "./prompts.js";
|
|
|
|
const mocks = vi.hoisted(() => ({
|
|
randomToken: vi.fn(),
|
|
getTailnetHostname: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("../commands/onboard-helpers.js", async (importActual) => {
|
|
const actual = await importActual<typeof import("../commands/onboard-helpers.js")>();
|
|
return {
|
|
...actual,
|
|
randomToken: mocks.randomToken,
|
|
};
|
|
});
|
|
|
|
vi.mock("../infra/tailscale.js", () => ({
|
|
findTailscaleBinary: vi.fn(async () => undefined),
|
|
getTailnetHostname: mocks.getTailnetHostname,
|
|
}));
|
|
|
|
import { configureGatewayForSetup } from "./setup.gateway-config.js";
|
|
|
|
describe("configureGatewayForSetup", () => {
|
|
function createPrompter(params: { selectQueue: string[]; textQueue: Array<string | undefined> }) {
|
|
const selectQueue = [...params.selectQueue];
|
|
const textQueue = [...params.textQueue];
|
|
const select = vi.fn(async (params: WizardSelectParams<unknown>) => {
|
|
const next = selectQueue.shift();
|
|
if (next !== undefined) {
|
|
return next;
|
|
}
|
|
return params.initialValue ?? params.options[0]?.value;
|
|
}) as unknown as WizardPrompter["select"];
|
|
|
|
return buildWizardPrompter({
|
|
select,
|
|
text: vi.fn(async () => textQueue.shift() as string),
|
|
});
|
|
}
|
|
|
|
function createRuntime(): RuntimeEnv {
|
|
return {
|
|
log: vi.fn(),
|
|
error: vi.fn(),
|
|
exit: vi.fn(),
|
|
};
|
|
}
|
|
|
|
function createQuickstartGateway(authMode: "token" | "password") {
|
|
return {
|
|
hasExisting: false,
|
|
port: 18789,
|
|
bind: "loopback" as const,
|
|
authMode,
|
|
tailscaleMode: "off" as const,
|
|
token: undefined,
|
|
password: undefined,
|
|
customBindHost: undefined,
|
|
tailscaleResetOnExit: false,
|
|
};
|
|
}
|
|
|
|
async function runGatewayConfig(params?: {
|
|
flow?: "advanced" | "quickstart";
|
|
bindChoice?: string;
|
|
authChoice?: "token" | "password";
|
|
tailscaleChoice?: "off" | "serve";
|
|
textQueue?: Array<string | undefined>;
|
|
nextConfig?: Record<string, unknown>;
|
|
}) {
|
|
const authChoice = params?.authChoice ?? "token";
|
|
const prompter = createPrompter({
|
|
selectQueue: [params?.bindChoice ?? "loopback", authChoice, params?.tailscaleChoice ?? "off"],
|
|
textQueue: params?.textQueue ?? ["18789", undefined],
|
|
});
|
|
const runtime = createRuntime();
|
|
return configureGatewayForSetup({
|
|
flow: params?.flow ?? "advanced",
|
|
baseConfig: {},
|
|
nextConfig: params?.nextConfig ?? {},
|
|
localPort: 18789,
|
|
quickstartGateway: createQuickstartGateway(authChoice),
|
|
prompter,
|
|
runtime,
|
|
});
|
|
}
|
|
|
|
it("generates a token when the prompt returns undefined", async () => {
|
|
mocks.randomToken.mockReturnValue("generated-token");
|
|
const result = await runGatewayConfig();
|
|
|
|
expect(result.settings.gatewayToken).toBe("generated-token");
|
|
expect(result.nextConfig.gateway?.nodes?.denyCommands).toEqual(DEFAULT_DANGEROUS_NODE_COMMANDS);
|
|
});
|
|
|
|
it("prefers OPENCLAW_GATEWAY_TOKEN during quickstart token setup", async () => {
|
|
const prevToken = process.env.OPENCLAW_GATEWAY_TOKEN;
|
|
process.env.OPENCLAW_GATEWAY_TOKEN = "token-from-env";
|
|
mocks.randomToken.mockReturnValue("generated-token");
|
|
mocks.randomToken.mockClear();
|
|
|
|
try {
|
|
const result = await runGatewayConfig({
|
|
flow: "quickstart",
|
|
textQueue: [],
|
|
});
|
|
|
|
expect(result.settings.gatewayToken).toBe("token-from-env");
|
|
} finally {
|
|
if (prevToken === undefined) {
|
|
delete process.env.OPENCLAW_GATEWAY_TOKEN;
|
|
} else {
|
|
process.env.OPENCLAW_GATEWAY_TOKEN = prevToken;
|
|
}
|
|
}
|
|
});
|
|
|
|
it("does not set password to literal 'undefined' when prompt returns undefined", async () => {
|
|
mocks.randomToken.mockReturnValue("unused");
|
|
const result = await runGatewayConfig({
|
|
authChoice: "password",
|
|
});
|
|
|
|
const authConfig = result.nextConfig.gateway?.auth as { mode?: string; password?: string };
|
|
expect(authConfig?.mode).toBe("password");
|
|
expect(authConfig?.password).toBe("");
|
|
expect(authConfig?.password).not.toBe("undefined");
|
|
});
|
|
|
|
it("seeds control UI allowed origins for non-loopback binds", async () => {
|
|
mocks.randomToken.mockReturnValue("generated-token");
|
|
const result = await runGatewayConfig({
|
|
bindChoice: "lan",
|
|
});
|
|
|
|
expect(result.nextConfig.gateway?.controlUi?.allowedOrigins).toEqual([
|
|
"http://localhost:18789",
|
|
"http://127.0.0.1:18789",
|
|
]);
|
|
});
|
|
|
|
it("honors secretInputMode=ref for gateway password prompts", async () => {
|
|
const previous = process.env.OPENCLAW_GATEWAY_PASSWORD;
|
|
process.env.OPENCLAW_GATEWAY_PASSWORD = "gateway-secret"; // pragma: allowlist secret
|
|
try {
|
|
const prompter = createPrompter({
|
|
selectQueue: ["loopback", "password", "off", "env"],
|
|
textQueue: ["18789", "OPENCLAW_GATEWAY_PASSWORD"],
|
|
});
|
|
const runtime = createRuntime();
|
|
|
|
const result = await configureGatewayForSetup({
|
|
flow: "advanced",
|
|
baseConfig: {},
|
|
nextConfig: {},
|
|
localPort: 18789,
|
|
quickstartGateway: createQuickstartGateway("password"),
|
|
secretInputMode: "ref", // pragma: allowlist secret
|
|
prompter,
|
|
runtime,
|
|
});
|
|
|
|
expect(result.nextConfig.gateway?.auth?.mode).toBe("password");
|
|
expect(result.nextConfig.gateway?.auth?.password).toEqual({
|
|
source: "env",
|
|
provider: "default",
|
|
id: "OPENCLAW_GATEWAY_PASSWORD",
|
|
});
|
|
} finally {
|
|
if (previous === undefined) {
|
|
delete process.env.OPENCLAW_GATEWAY_PASSWORD;
|
|
} else {
|
|
process.env.OPENCLAW_GATEWAY_PASSWORD = previous;
|
|
}
|
|
}
|
|
});
|
|
|
|
it("stores gateway token as SecretRef when secretInputMode=ref", async () => {
|
|
const previous = process.env.OPENCLAW_GATEWAY_TOKEN;
|
|
process.env.OPENCLAW_GATEWAY_TOKEN = "token-from-env";
|
|
try {
|
|
const prompter = createPrompter({
|
|
selectQueue: ["loopback", "token", "off", "env"],
|
|
textQueue: ["18789", "OPENCLAW_GATEWAY_TOKEN"],
|
|
});
|
|
const runtime = createRuntime();
|
|
|
|
const result = await configureGatewayForSetup({
|
|
flow: "advanced",
|
|
baseConfig: {},
|
|
nextConfig: {},
|
|
localPort: 18789,
|
|
quickstartGateway: createQuickstartGateway("token"),
|
|
secretInputMode: "ref", // pragma: allowlist secret
|
|
prompter,
|
|
runtime,
|
|
});
|
|
|
|
expect(result.nextConfig.gateway?.auth?.mode).toBe("token");
|
|
expect(result.nextConfig.gateway?.auth?.token).toEqual({
|
|
source: "env",
|
|
provider: "default",
|
|
id: "OPENCLAW_GATEWAY_TOKEN",
|
|
});
|
|
expect(result.settings.gatewayToken).toBe("token-from-env");
|
|
} finally {
|
|
if (previous === undefined) {
|
|
delete process.env.OPENCLAW_GATEWAY_TOKEN;
|
|
} else {
|
|
process.env.OPENCLAW_GATEWAY_TOKEN = previous;
|
|
}
|
|
}
|
|
});
|
|
|
|
it("resolves quickstart exec SecretRefs for gateway token bootstrap", async () => {
|
|
const quickstartGateway = {
|
|
...createQuickstartGateway("token"),
|
|
token: {
|
|
source: "exec" as const,
|
|
provider: "gatewayTokens",
|
|
id: "gateway/auth/token",
|
|
},
|
|
};
|
|
const runtime = createRuntime();
|
|
const prompter = createPrompter({
|
|
selectQueue: [],
|
|
textQueue: [],
|
|
});
|
|
|
|
const result = await configureGatewayForSetup({
|
|
flow: "quickstart",
|
|
baseConfig: {},
|
|
nextConfig: {
|
|
secrets: {
|
|
providers: {
|
|
gatewayTokens: {
|
|
source: "exec",
|
|
command: process.execPath,
|
|
allowInsecurePath: true,
|
|
allowSymlinkCommand: true,
|
|
args: [
|
|
"-e",
|
|
"let input='';process.stdin.setEncoding('utf8');process.stdin.on('data',d=>input+=d);process.stdin.on('end',()=>{const req=JSON.parse(input||'{}');const values={};for(const id of req.ids||[]){values[id]='token-from-exec';}process.stdout.write(JSON.stringify({protocolVersion:1,values}));});",
|
|
],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
localPort: 18789,
|
|
quickstartGateway,
|
|
prompter,
|
|
runtime,
|
|
});
|
|
|
|
expect(result.nextConfig.gateway?.auth?.token).toEqual(quickstartGateway.token);
|
|
expect(result.settings.gatewayToken).toBe("token-from-exec");
|
|
});
|
|
});
|