fix: local updates for PR #4873
Co-authored-by: Hisleren <Hisleren@users.noreply.github.com>
This commit is contained in:
parent
201d7fa956
commit
e5a95b5b66
6 changed files with 173 additions and 8 deletions
74
src/commands/configure.gateway.test.ts
Normal file
74
src/commands/configure.gateway.test.ts
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
import type { RuntimeEnv } from "../runtime.js";
|
||||||
|
|
||||||
|
const mocks = vi.hoisted(() => ({
|
||||||
|
text: vi.fn(),
|
||||||
|
select: vi.fn(),
|
||||||
|
confirm: vi.fn(),
|
||||||
|
resolveGatewayPort: vi.fn(),
|
||||||
|
buildGatewayAuthConfig: vi.fn(),
|
||||||
|
note: vi.fn(),
|
||||||
|
randomToken: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("../config/config.js", async (importActual) => {
|
||||||
|
const actual = await importActual<typeof import("../config/config.js")>();
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
resolveGatewayPort: mocks.resolveGatewayPort,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.mock("./configure.shared.js", () => ({
|
||||||
|
text: mocks.text,
|
||||||
|
select: mocks.select,
|
||||||
|
confirm: mocks.confirm,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("../terminal/note.js", () => ({
|
||||||
|
note: mocks.note,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("./configure.gateway-auth.js", () => ({
|
||||||
|
buildGatewayAuthConfig: mocks.buildGatewayAuthConfig,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("../infra/tailscale.js", () => ({
|
||||||
|
findTailscaleBinary: vi.fn(async () => undefined),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("./onboard-helpers.js", async (importActual) => {
|
||||||
|
const actual = await importActual<typeof import("./onboard-helpers.js")>();
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
randomToken: mocks.randomToken,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
import { promptGatewayConfig } from "./configure.gateway.js";
|
||||||
|
|
||||||
|
describe("promptGatewayConfig", () => {
|
||||||
|
it("generates a token when the prompt returns undefined", async () => {
|
||||||
|
mocks.resolveGatewayPort.mockReturnValue(18789);
|
||||||
|
const selectQueue = ["loopback", "token", "off"];
|
||||||
|
mocks.select.mockImplementation(async () => selectQueue.shift());
|
||||||
|
const textQueue = ["18789", undefined];
|
||||||
|
mocks.text.mockImplementation(async () => textQueue.shift());
|
||||||
|
mocks.randomToken.mockReturnValue("generated-token");
|
||||||
|
mocks.buildGatewayAuthConfig.mockImplementation(({ mode, token, password }) => ({
|
||||||
|
mode,
|
||||||
|
token,
|
||||||
|
password,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const runtime: RuntimeEnv = {
|
||||||
|
log: vi.fn(),
|
||||||
|
error: vi.fn(),
|
||||||
|
exit: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await promptGatewayConfig({}, runtime);
|
||||||
|
expect(result.token).toBe("generated-token");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -5,7 +5,7 @@ import type { RuntimeEnv } from "../runtime.js";
|
||||||
import { note } from "../terminal/note.js";
|
import { note } from "../terminal/note.js";
|
||||||
import { buildGatewayAuthConfig } from "./configure.gateway-auth.js";
|
import { buildGatewayAuthConfig } from "./configure.gateway-auth.js";
|
||||||
import { confirm, select, text } from "./configure.shared.js";
|
import { confirm, select, text } from "./configure.shared.js";
|
||||||
import { guardCancel, randomToken } from "./onboard-helpers.js";
|
import { guardCancel, normalizeGatewayTokenInput, randomToken } from "./onboard-helpers.js";
|
||||||
|
|
||||||
type GatewayAuthChoice = "token" | "password";
|
type GatewayAuthChoice = "token" | "password";
|
||||||
|
|
||||||
|
|
@ -177,8 +177,7 @@ export async function promptGatewayConfig(
|
||||||
}),
|
}),
|
||||||
runtime,
|
runtime,
|
||||||
);
|
);
|
||||||
const rawInput = tokenInput ? String(tokenInput).trim() : "";
|
gatewayToken = normalizeGatewayTokenInput(tokenInput) || randomToken();
|
||||||
gatewayToken = rawInput || randomToken();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authMode === "password") {
|
if (authMode === "password") {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
import { openUrl, resolveBrowserOpenCommand, resolveControlUiLinks } from "./onboard-helpers.js";
|
import {
|
||||||
|
normalizeGatewayTokenInput,
|
||||||
|
openUrl,
|
||||||
|
resolveBrowserOpenCommand,
|
||||||
|
resolveControlUiLinks,
|
||||||
|
} from "./onboard-helpers.js";
|
||||||
|
|
||||||
const mocks = vi.hoisted(() => ({
|
const mocks = vi.hoisted(() => ({
|
||||||
runCommandWithTimeout: vi.fn(async () => ({
|
runCommandWithTimeout: vi.fn(async () => ({
|
||||||
|
|
@ -103,3 +108,18 @@ describe("resolveControlUiLinks", () => {
|
||||||
expect(links.wsUrl).toBe("ws://127.0.0.1:18789");
|
expect(links.wsUrl).toBe("ws://127.0.0.1:18789");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("normalizeGatewayTokenInput", () => {
|
||||||
|
it("returns empty string for undefined or null", () => {
|
||||||
|
expect(normalizeGatewayTokenInput(undefined)).toBe("");
|
||||||
|
expect(normalizeGatewayTokenInput(null)).toBe("");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("trims string input", () => {
|
||||||
|
expect(normalizeGatewayTokenInput(" token ")).toBe("token");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("coerces non-string input to string", () => {
|
||||||
|
expect(normalizeGatewayTokenInput(123)).toBe("123");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,11 @@ export function randomToken(): string {
|
||||||
return crypto.randomBytes(24).toString("hex");
|
return crypto.randomBytes(24).toString("hex");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function normalizeGatewayTokenInput(value: unknown): string {
|
||||||
|
if (value == null) return "";
|
||||||
|
return String(value).trim();
|
||||||
|
}
|
||||||
|
|
||||||
export function printWizardHeader(runtime: RuntimeEnv) {
|
export function printWizardHeader(runtime: RuntimeEnv) {
|
||||||
const header = [
|
const header = [
|
||||||
"▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄",
|
"▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄",
|
||||||
|
|
|
||||||
69
src/wizard/onboarding.gateway-config.test.ts
Normal file
69
src/wizard/onboarding.gateway-config.test.ts
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
import type { RuntimeEnv } from "../runtime.js";
|
||||||
|
import type { WizardPrompter } from "./prompts.js";
|
||||||
|
|
||||||
|
const mocks = vi.hoisted(() => ({
|
||||||
|
randomToken: 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),
|
||||||
|
}));
|
||||||
|
|
||||||
|
import { configureGatewayForOnboarding } from "./onboarding.gateway-config.js";
|
||||||
|
|
||||||
|
describe("configureGatewayForOnboarding", () => {
|
||||||
|
it("generates a token when the prompt returns undefined", async () => {
|
||||||
|
mocks.randomToken.mockReturnValue("generated-token");
|
||||||
|
|
||||||
|
const selectQueue = ["loopback", "token", "off"];
|
||||||
|
const textQueue = ["18789", undefined];
|
||||||
|
const prompter: WizardPrompter = {
|
||||||
|
intro: vi.fn(async () => {}),
|
||||||
|
outro: vi.fn(async () => {}),
|
||||||
|
note: vi.fn(async () => {}),
|
||||||
|
select: vi.fn(async () => selectQueue.shift() as string),
|
||||||
|
multiselect: vi.fn(async () => []),
|
||||||
|
text: vi.fn(async () => textQueue.shift() as string),
|
||||||
|
confirm: vi.fn(async () => false),
|
||||||
|
progress: vi.fn(() => ({ update: vi.fn(), stop: vi.fn() })),
|
||||||
|
};
|
||||||
|
|
||||||
|
const runtime: RuntimeEnv = {
|
||||||
|
log: vi.fn(),
|
||||||
|
error: vi.fn(),
|
||||||
|
exit: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await configureGatewayForOnboarding({
|
||||||
|
flow: "advanced",
|
||||||
|
baseConfig: {},
|
||||||
|
nextConfig: {},
|
||||||
|
localPort: 18789,
|
||||||
|
quickstartGateway: {
|
||||||
|
hasExisting: false,
|
||||||
|
port: 18789,
|
||||||
|
bind: "loopback",
|
||||||
|
authMode: "token",
|
||||||
|
tailscaleMode: "off",
|
||||||
|
token: undefined,
|
||||||
|
password: undefined,
|
||||||
|
customBindHost: undefined,
|
||||||
|
tailscaleResetOnExit: false,
|
||||||
|
},
|
||||||
|
prompter,
|
||||||
|
runtime,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.settings.gatewayToken).toBe("generated-token");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { randomToken } from "../commands/onboard-helpers.js";
|
import { normalizeGatewayTokenInput, randomToken } from "../commands/onboard-helpers.js";
|
||||||
import type { GatewayAuthChoice } from "../commands/onboard-types.js";
|
import type { GatewayAuthChoice } from "../commands/onboard-types.js";
|
||||||
import type { OpenClawConfig } from "../config/config.js";
|
import type { OpenClawConfig } from "../config/config.js";
|
||||||
import { findTailscaleBinary } from "../infra/tailscale.js";
|
import { findTailscaleBinary } from "../infra/tailscale.js";
|
||||||
|
|
@ -182,9 +182,7 @@ export async function configureGatewayForOnboarding(
|
||||||
placeholder: "Needed for multi-machine or non-loopback access",
|
placeholder: "Needed for multi-machine or non-loopback access",
|
||||||
initialValue: quickstartGateway.token ?? "",
|
initialValue: quickstartGateway.token ?? "",
|
||||||
});
|
});
|
||||||
// FIX: Ensure undefined becomes an empty string, not "undefined" string
|
gatewayToken = normalizeGatewayTokenInput(tokenInput) || randomToken();
|
||||||
const rawInput = tokenInput ? String(tokenInput).trim() : "";
|
|
||||||
gatewayToken = rawInput || randomToken();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue