mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 07:22:44 +00:00
refactor(tests): consolidate repeated setup helpers
This commit is contained in:
@@ -44,12 +44,40 @@ function createPromptSpies(params?: { confirmResult?: boolean; textResult?: stri
|
||||
return { confirm, note, text };
|
||||
}
|
||||
|
||||
function createPromptAndCredentialSpies(params?: { confirmResult?: boolean; textResult?: string }) {
|
||||
return {
|
||||
...createPromptSpies(params),
|
||||
setCredential: vi.fn(async () => undefined),
|
||||
};
|
||||
}
|
||||
|
||||
async function ensureMinimaxApiKey(params: {
|
||||
config?: Parameters<typeof ensureApiKeyFromEnvOrPrompt>[0]["config"];
|
||||
confirm: WizardPrompter["confirm"];
|
||||
note?: WizardPrompter["note"];
|
||||
select?: WizardPrompter["select"];
|
||||
text: WizardPrompter["text"];
|
||||
setCredential: Parameters<typeof ensureApiKeyFromEnvOrPrompt>[0]["setCredential"];
|
||||
config?: Parameters<typeof ensureApiKeyFromEnvOrPrompt>[0]["config"];
|
||||
secretInputMode?: Parameters<typeof ensureApiKeyFromEnvOrPrompt>[0]["secretInputMode"];
|
||||
}) {
|
||||
return await ensureMinimaxApiKeyInternal({
|
||||
config: params.config,
|
||||
prompter: createPrompter({
|
||||
confirm: params.confirm,
|
||||
note: params.note,
|
||||
select: params.select,
|
||||
text: params.text,
|
||||
}),
|
||||
secretInputMode: params.secretInputMode,
|
||||
setCredential: params.setCredential,
|
||||
});
|
||||
}
|
||||
|
||||
async function ensureMinimaxApiKeyInternal(params: {
|
||||
config?: Parameters<typeof ensureApiKeyFromEnvOrPrompt>[0]["config"];
|
||||
prompter: WizardPrompter;
|
||||
secretInputMode?: Parameters<typeof ensureApiKeyFromEnvOrPrompt>[0]["secretInputMode"];
|
||||
setCredential: Parameters<typeof ensureApiKeyFromEnvOrPrompt>[0]["setCredential"];
|
||||
}) {
|
||||
return await ensureApiKeyFromEnvOrPrompt({
|
||||
config: params.config ?? {},
|
||||
@@ -58,7 +86,7 @@ async function ensureMinimaxApiKey(params: {
|
||||
promptMessage: "Enter key",
|
||||
normalize: (value) => value.trim(),
|
||||
validate: () => undefined,
|
||||
prompter: createPrompter({ confirm: params.confirm, text: params.text }),
|
||||
prompter: params.prompter,
|
||||
secretInputMode: params.secretInputMode,
|
||||
setCredential: params.setCredential,
|
||||
});
|
||||
@@ -71,13 +99,8 @@ async function ensureMinimaxApiKeyWithEnvRefPrompter(params: {
|
||||
setCredential: Parameters<typeof ensureApiKeyFromEnvOrPrompt>[0]["setCredential"];
|
||||
text: WizardPrompter["text"];
|
||||
}) {
|
||||
return await ensureApiKeyFromEnvOrPrompt({
|
||||
config: params.config ?? {},
|
||||
provider: "minimax",
|
||||
envLabel: "MINIMAX_API_KEY",
|
||||
promptMessage: "Enter key",
|
||||
normalize: (value) => value.trim(),
|
||||
validate: () => undefined,
|
||||
return await ensureMinimaxApiKeyInternal({
|
||||
config: params.config,
|
||||
prompter: createPrompter({ select: params.select, text: params.text, note: params.note }),
|
||||
secretInputMode: "ref",
|
||||
setCredential: params.setCredential,
|
||||
@@ -102,6 +125,55 @@ async function runEnsureMinimaxApiKeyFlow(params: { confirmResult: boolean; text
|
||||
return { result, setCredential, confirm, text };
|
||||
}
|
||||
|
||||
async function runMaybeApplyHuggingFaceToken(tokenProvider: string) {
|
||||
const setCredential = vi.fn(async () => undefined);
|
||||
const result = await maybeApplyApiKeyFromOption({
|
||||
token: " opt-key ",
|
||||
tokenProvider,
|
||||
expectedProviders: ["huggingface"],
|
||||
normalize: (value) => value.trim(),
|
||||
setCredential,
|
||||
});
|
||||
return { result, setCredential };
|
||||
}
|
||||
|
||||
function expectMinimaxEnvRefCredentialStored(setCredential: ReturnType<typeof vi.fn>) {
|
||||
expect(setCredential).toHaveBeenCalledWith(
|
||||
{ source: "env", provider: "default", id: "MINIMAX_API_KEY" },
|
||||
"ref",
|
||||
);
|
||||
}
|
||||
|
||||
async function ensureWithOptionEnvOrPrompt(params: {
|
||||
token: string;
|
||||
tokenProvider: string;
|
||||
expectedProviders: string[];
|
||||
provider: string;
|
||||
envLabel: string;
|
||||
confirm: WizardPrompter["confirm"];
|
||||
note: WizardPrompter["note"];
|
||||
noteMessage: string;
|
||||
noteTitle: string;
|
||||
setCredential: Parameters<typeof ensureApiKeyFromOptionEnvOrPrompt>[0]["setCredential"];
|
||||
text: WizardPrompter["text"];
|
||||
}) {
|
||||
return await ensureApiKeyFromOptionEnvOrPrompt({
|
||||
token: params.token,
|
||||
tokenProvider: params.tokenProvider,
|
||||
config: {},
|
||||
expectedProviders: params.expectedProviders,
|
||||
provider: params.provider,
|
||||
envLabel: params.envLabel,
|
||||
promptMessage: "Enter key",
|
||||
normalize: (value) => value.trim(),
|
||||
validate: () => undefined,
|
||||
prompter: createPrompter({ confirm: params.confirm, note: params.note, text: params.text }),
|
||||
setCredential: params.setCredential,
|
||||
noteMessage: params.noteMessage,
|
||||
noteTitle: params.noteTitle,
|
||||
});
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
restoreMinimaxEnv();
|
||||
vi.restoreAllMocks();
|
||||
@@ -116,30 +188,14 @@ describe("normalizeTokenProviderInput", () => {
|
||||
|
||||
describe("maybeApplyApiKeyFromOption", () => {
|
||||
it("stores normalized token when provider matches", async () => {
|
||||
const setCredential = vi.fn(async () => undefined);
|
||||
|
||||
const result = await maybeApplyApiKeyFromOption({
|
||||
token: " opt-key ",
|
||||
tokenProvider: "huggingface",
|
||||
expectedProviders: ["huggingface"],
|
||||
normalize: (value) => value.trim(),
|
||||
setCredential,
|
||||
});
|
||||
const { result, setCredential } = await runMaybeApplyHuggingFaceToken("huggingface");
|
||||
|
||||
expect(result).toBe("opt-key");
|
||||
expect(setCredential).toHaveBeenCalledWith("opt-key", undefined);
|
||||
});
|
||||
|
||||
it("matches provider with whitespace/case normalization", async () => {
|
||||
const setCredential = vi.fn(async () => undefined);
|
||||
|
||||
const result = await maybeApplyApiKeyFromOption({
|
||||
token: " opt-key ",
|
||||
tokenProvider: " HuGgInGfAcE ",
|
||||
expectedProviders: ["huggingface"],
|
||||
normalize: (value) => value.trim(),
|
||||
setCredential,
|
||||
});
|
||||
const { result, setCredential } = await runMaybeApplyHuggingFaceToken(" HuGgInGfAcE ");
|
||||
|
||||
expect(result).toBe("opt-key");
|
||||
expect(setCredential).toHaveBeenCalledWith("opt-key", undefined);
|
||||
@@ -192,11 +248,10 @@ describe("ensureApiKeyFromEnvOrPrompt", () => {
|
||||
process.env.MINIMAX_API_KEY = "env-key";
|
||||
delete process.env.MINIMAX_OAUTH_TOKEN;
|
||||
|
||||
const { confirm, text } = createPromptSpies({
|
||||
const { confirm, text, setCredential } = createPromptAndCredentialSpies({
|
||||
confirmResult: true,
|
||||
textResult: "prompt-key",
|
||||
});
|
||||
const setCredential = vi.fn(async () => undefined);
|
||||
|
||||
const result = await ensureMinimaxApiKey({
|
||||
confirm,
|
||||
@@ -206,10 +261,7 @@ describe("ensureApiKeyFromEnvOrPrompt", () => {
|
||||
});
|
||||
|
||||
expect(result).toBe("env-key");
|
||||
expect(setCredential).toHaveBeenCalledWith(
|
||||
{ source: "env", provider: "default", id: "MINIMAX_API_KEY" },
|
||||
"ref",
|
||||
);
|
||||
expectMinimaxEnvRefCredentialStored(setCredential);
|
||||
expect(text).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -217,11 +269,10 @@ describe("ensureApiKeyFromEnvOrPrompt", () => {
|
||||
delete process.env.MINIMAX_API_KEY;
|
||||
delete process.env.MINIMAX_OAUTH_TOKEN;
|
||||
|
||||
const { confirm, text } = createPromptSpies({
|
||||
const { confirm, text, setCredential } = createPromptAndCredentialSpies({
|
||||
confirmResult: true,
|
||||
textResult: "prompt-key",
|
||||
});
|
||||
const setCredential = vi.fn(async () => undefined);
|
||||
|
||||
await expect(
|
||||
ensureMinimaxApiKey({
|
||||
@@ -268,10 +319,7 @@ describe("ensureApiKeyFromEnvOrPrompt", () => {
|
||||
});
|
||||
|
||||
expect(result).toBe("env-key");
|
||||
expect(setCredential).toHaveBeenCalledWith(
|
||||
{ source: "env", provider: "default", id: "MINIMAX_API_KEY" },
|
||||
"ref",
|
||||
);
|
||||
expectMinimaxEnvRefCredentialStored(setCredential);
|
||||
expect(note).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Could not validate provider reference"),
|
||||
"Reference check failed",
|
||||
@@ -304,26 +352,23 @@ describe("ensureApiKeyFromEnvOrPrompt", () => {
|
||||
|
||||
describe("ensureApiKeyFromOptionEnvOrPrompt", () => {
|
||||
it("uses opts token and skips note/env/prompt", async () => {
|
||||
const { confirm, note, text } = createPromptSpies({
|
||||
const { confirm, note, text, setCredential } = createPromptAndCredentialSpies({
|
||||
confirmResult: true,
|
||||
textResult: "prompt-key",
|
||||
});
|
||||
const setCredential = vi.fn(async () => undefined);
|
||||
|
||||
const result = await ensureApiKeyFromOptionEnvOrPrompt({
|
||||
const result = await ensureWithOptionEnvOrPrompt({
|
||||
token: " opts-key ",
|
||||
tokenProvider: " HUGGINGFACE ",
|
||||
config: {},
|
||||
expectedProviders: ["huggingface"],
|
||||
provider: "huggingface",
|
||||
envLabel: "HF_TOKEN",
|
||||
promptMessage: "Enter key",
|
||||
normalize: (value) => value.trim(),
|
||||
validate: () => undefined,
|
||||
prompter: createPrompter({ confirm, note, text }),
|
||||
setCredential,
|
||||
confirm,
|
||||
note,
|
||||
noteMessage: "Hugging Face note",
|
||||
noteTitle: "Hugging Face",
|
||||
setCredential,
|
||||
text,
|
||||
});
|
||||
|
||||
expect(result).toBe("opts-key");
|
||||
@@ -337,26 +382,23 @@ describe("ensureApiKeyFromOptionEnvOrPrompt", () => {
|
||||
delete process.env.MINIMAX_OAUTH_TOKEN;
|
||||
process.env.MINIMAX_API_KEY = "env-key";
|
||||
|
||||
const { confirm, note, text } = createPromptSpies({
|
||||
const { confirm, note, text, setCredential } = createPromptAndCredentialSpies({
|
||||
confirmResult: true,
|
||||
textResult: "prompt-key",
|
||||
});
|
||||
const setCredential = vi.fn(async () => undefined);
|
||||
|
||||
const result = await ensureApiKeyFromOptionEnvOrPrompt({
|
||||
const result = await ensureWithOptionEnvOrPrompt({
|
||||
token: "opts-key",
|
||||
tokenProvider: "openai",
|
||||
config: {},
|
||||
expectedProviders: ["minimax"],
|
||||
provider: "minimax",
|
||||
envLabel: "MINIMAX_API_KEY",
|
||||
promptMessage: "Enter key",
|
||||
normalize: (value) => value.trim(),
|
||||
validate: () => undefined,
|
||||
prompter: createPrompter({ confirm, note, text }),
|
||||
setCredential,
|
||||
confirm,
|
||||
note,
|
||||
noteMessage: "MiniMax note",
|
||||
noteTitle: "MiniMax",
|
||||
setCredential,
|
||||
text,
|
||||
});
|
||||
|
||||
expect(result).toBe("env-key");
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { withEnvAsync } from "../test-utils/env.js";
|
||||
|
||||
const loadConfig = vi.fn(() => ({
|
||||
@@ -134,15 +135,33 @@ function createRuntimeCapture() {
|
||||
return { runtime, runtimeLogs, runtimeErrors };
|
||||
}
|
||||
|
||||
function asRuntimeEnv(runtime: ReturnType<typeof createRuntimeCapture>["runtime"]): RuntimeEnv {
|
||||
return runtime as unknown as RuntimeEnv;
|
||||
}
|
||||
|
||||
function makeRemoteGatewayConfig(url: string, token = "rtok", localToken = "ltok") {
|
||||
return {
|
||||
gateway: {
|
||||
mode: "remote",
|
||||
remote: { url, token },
|
||||
auth: { token: localToken },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function runGatewayStatus(
|
||||
runtime: ReturnType<typeof createRuntimeCapture>["runtime"],
|
||||
opts: { timeout: string; json?: boolean; ssh?: string; sshAuto?: boolean; sshIdentity?: string },
|
||||
) {
|
||||
const { gatewayStatusCommand } = await import("./gateway-status.js");
|
||||
await gatewayStatusCommand(opts, asRuntimeEnv(runtime));
|
||||
}
|
||||
|
||||
describe("gateway-status command", () => {
|
||||
it("prints human output by default", async () => {
|
||||
const { runtime, runtimeLogs, runtimeErrors } = createRuntimeCapture();
|
||||
|
||||
const { gatewayStatusCommand } = await import("./gateway-status.js");
|
||||
await gatewayStatusCommand(
|
||||
{ timeout: "1000" },
|
||||
runtime as unknown as import("../runtime.js").RuntimeEnv,
|
||||
);
|
||||
await runGatewayStatus(runtime, { timeout: "1000" });
|
||||
|
||||
expect(runtimeErrors).toHaveLength(0);
|
||||
expect(runtimeLogs.join("\n")).toContain("Gateway Status");
|
||||
@@ -153,11 +172,7 @@ describe("gateway-status command", () => {
|
||||
it("prints a structured JSON envelope when --json is set", async () => {
|
||||
const { runtime, runtimeLogs, runtimeErrors } = createRuntimeCapture();
|
||||
|
||||
const { gatewayStatusCommand } = await import("./gateway-status.js");
|
||||
await gatewayStatusCommand(
|
||||
{ timeout: "1000", json: true },
|
||||
runtime as unknown as import("../runtime.js").RuntimeEnv,
|
||||
);
|
||||
await runGatewayStatus(runtime, { timeout: "1000", json: true });
|
||||
|
||||
expect(runtimeErrors).toHaveLength(0);
|
||||
const parsed = JSON.parse(runtimeLogs.join("\n")) as Record<string, unknown>;
|
||||
@@ -176,11 +191,7 @@ describe("gateway-status command", () => {
|
||||
sshStop.mockClear();
|
||||
probeGateway.mockClear();
|
||||
|
||||
const { gatewayStatusCommand } = await import("./gateway-status.js");
|
||||
await gatewayStatusCommand(
|
||||
{ timeout: "1000", json: true, ssh: "me@studio" },
|
||||
runtime as unknown as import("../runtime.js").RuntimeEnv,
|
||||
);
|
||||
await runGatewayStatus(runtime, { timeout: "1000", json: true, ssh: "me@studio" });
|
||||
|
||||
expect(startSshPortForward).toHaveBeenCalledTimes(1);
|
||||
expect(probeGateway).toHaveBeenCalled();
|
||||
@@ -198,24 +209,14 @@ describe("gateway-status command", () => {
|
||||
it("skips invalid ssh-auto discovery targets", async () => {
|
||||
const { runtime } = createRuntimeCapture();
|
||||
await withEnvAsync({ USER: "steipete" }, async () => {
|
||||
loadConfig.mockReturnValueOnce({
|
||||
gateway: {
|
||||
mode: "remote",
|
||||
remote: { url: "", token: "" },
|
||||
auth: { token: "ltok" },
|
||||
},
|
||||
});
|
||||
loadConfig.mockReturnValueOnce(makeRemoteGatewayConfig("", "", "ltok"));
|
||||
discoverGatewayBeacons.mockResolvedValueOnce([
|
||||
{ tailnetDns: "-V" },
|
||||
{ tailnetDns: "goodhost" },
|
||||
]);
|
||||
|
||||
startSshPortForward.mockClear();
|
||||
const { gatewayStatusCommand } = await import("./gateway-status.js");
|
||||
await gatewayStatusCommand(
|
||||
{ timeout: "1000", json: true, sshAuto: true },
|
||||
runtime as unknown as import("../runtime.js").RuntimeEnv,
|
||||
);
|
||||
await runGatewayStatus(runtime, { timeout: "1000", json: true, sshAuto: true });
|
||||
|
||||
expect(startSshPortForward).toHaveBeenCalledTimes(1);
|
||||
const call = startSshPortForward.mock.calls[0]?.[0] as { target: string };
|
||||
@@ -226,13 +227,9 @@ describe("gateway-status command", () => {
|
||||
it("infers SSH target from gateway.remote.url and ssh config", async () => {
|
||||
const { runtime } = createRuntimeCapture();
|
||||
await withEnvAsync({ USER: "steipete" }, async () => {
|
||||
loadConfig.mockReturnValueOnce({
|
||||
gateway: {
|
||||
mode: "remote",
|
||||
remote: { url: "ws://peters-mac-studio-1.sheep-coho.ts.net:18789", token: "rtok" },
|
||||
auth: { token: "ltok" },
|
||||
},
|
||||
});
|
||||
loadConfig.mockReturnValueOnce(
|
||||
makeRemoteGatewayConfig("ws://peters-mac-studio-1.sheep-coho.ts.net:18789"),
|
||||
);
|
||||
resolveSshConfig.mockResolvedValueOnce({
|
||||
user: "steipete",
|
||||
host: "peters-mac-studio-1.sheep-coho.ts.net",
|
||||
@@ -241,11 +238,7 @@ describe("gateway-status command", () => {
|
||||
});
|
||||
|
||||
startSshPortForward.mockClear();
|
||||
const { gatewayStatusCommand } = await import("./gateway-status.js");
|
||||
await gatewayStatusCommand(
|
||||
{ timeout: "1000", json: true },
|
||||
runtime as unknown as import("../runtime.js").RuntimeEnv,
|
||||
);
|
||||
await runGatewayStatus(runtime, { timeout: "1000", json: true });
|
||||
|
||||
expect(startSshPortForward).toHaveBeenCalledTimes(1);
|
||||
const call = startSshPortForward.mock.calls[0]?.[0] as {
|
||||
@@ -260,21 +253,11 @@ describe("gateway-status command", () => {
|
||||
it("falls back to host-only when USER is missing and ssh config is unavailable", async () => {
|
||||
const { runtime } = createRuntimeCapture();
|
||||
await withEnvAsync({ USER: "" }, async () => {
|
||||
loadConfig.mockReturnValueOnce({
|
||||
gateway: {
|
||||
mode: "remote",
|
||||
remote: { url: "wss://studio.example:18789", token: "rtok" },
|
||||
auth: { token: "ltok" },
|
||||
},
|
||||
});
|
||||
loadConfig.mockReturnValueOnce(makeRemoteGatewayConfig("wss://studio.example:18789"));
|
||||
resolveSshConfig.mockResolvedValueOnce(null);
|
||||
|
||||
startSshPortForward.mockClear();
|
||||
const { gatewayStatusCommand } = await import("./gateway-status.js");
|
||||
await gatewayStatusCommand(
|
||||
{ timeout: "1000", json: true },
|
||||
runtime as unknown as import("../runtime.js").RuntimeEnv,
|
||||
);
|
||||
await runGatewayStatus(runtime, { timeout: "1000", json: true });
|
||||
|
||||
const call = startSshPortForward.mock.calls[0]?.[0] as {
|
||||
target: string;
|
||||
@@ -286,13 +269,7 @@ describe("gateway-status command", () => {
|
||||
it("keeps explicit SSH identity even when ssh config provides one", async () => {
|
||||
const { runtime } = createRuntimeCapture();
|
||||
|
||||
loadConfig.mockReturnValueOnce({
|
||||
gateway: {
|
||||
mode: "remote",
|
||||
remote: { url: "wss://studio.example:18789", token: "rtok" },
|
||||
auth: { token: "ltok" },
|
||||
},
|
||||
});
|
||||
loadConfig.mockReturnValueOnce(makeRemoteGatewayConfig("wss://studio.example:18789"));
|
||||
resolveSshConfig.mockResolvedValueOnce({
|
||||
user: "me",
|
||||
host: "studio.example",
|
||||
@@ -301,11 +278,11 @@ describe("gateway-status command", () => {
|
||||
});
|
||||
|
||||
startSshPortForward.mockClear();
|
||||
const { gatewayStatusCommand } = await import("./gateway-status.js");
|
||||
await gatewayStatusCommand(
|
||||
{ timeout: "1000", json: true, sshIdentity: "/tmp/explicit_id" },
|
||||
runtime as unknown as import("../runtime.js").RuntimeEnv,
|
||||
);
|
||||
await runGatewayStatus(runtime, {
|
||||
timeout: "1000",
|
||||
json: true,
|
||||
sshIdentity: "/tmp/explicit_id",
|
||||
});
|
||||
|
||||
const call = startSshPortForward.mock.calls[0]?.[0] as {
|
||||
identity?: string;
|
||||
|
||||
Reference in New Issue
Block a user