test: merge repeated onboarding auth cases

This commit is contained in:
Peter Steinberger
2026-04-17 08:35:58 +01:00
parent 7995d43625
commit e477125608
3 changed files with 90 additions and 154 deletions

View File

@@ -1092,21 +1092,50 @@ describe("applyAuthChoice", () => {
}
});
it("maps apiKey tokenProvider aliases to provider flow", async () => {
it("uses provided tokens without prompting across alias and direct provider choices", async () => {
const scenarios: Array<{
authChoice: "apiKey" | "opencode-zen" | "gemini-api-key";
config?: OpenClawConfig;
setDefaultModel: boolean;
tokenProvider: string;
token: string;
profileId: string;
provider: string;
expectedModel: string;
expectedModel?: string;
expectedModelPrefix?: string;
expectedAgentModelOverride?: string;
extraProfiles?: string[];
}> = [
{
authChoice: "apiKey",
setDefaultModel: true,
tokenProvider: " GOOGLE ",
token: "sk-gemini-token-provider-test",
profileId: "google:default",
provider: "google",
expectedModel: GOOGLE_GEMINI_DEFAULT_MODEL,
},
{
authChoice: "opencode-zen",
setDefaultModel: true,
tokenProvider: "opencode",
token: "sk-opencode-test",
profileId: "opencode:default",
provider: "opencode",
expectedModelPrefix: "opencode/",
extraProfiles: ["opencode-go:default"],
},
{
authChoice: "gemini-api-key",
config: { agents: { defaults: { model: { primary: "openai/gpt-4o-mini" } } } },
setDefaultModel: false,
tokenProvider: "google",
token: "sk-gemini-test",
profileId: "google:default",
provider: "google",
expectedModel: "openai/gpt-4o-mini",
expectedAgentModelOverride: GOOGLE_GEMINI_DEFAULT_MODEL,
},
];
await setupTempState();
for (const scenario of scenarios) {
@@ -1118,124 +1147,40 @@ describe("applyAuthChoice", () => {
const { prompter, runtime } = createApiKeyPromptHarness({ text, confirm });
const result = await applyAuthChoice({
authChoice: "apiKey",
config: {},
authChoice: scenario.authChoice,
config: scenario.config ?? {},
prompter,
runtime,
setDefaultModel: true,
setDefaultModel: scenario.setDefaultModel,
opts: {
tokenProvider: scenario.tokenProvider,
token: scenario.token,
},
});
expect(text).not.toHaveBeenCalled();
expect(confirm).not.toHaveBeenCalled();
expect(result.config.auth?.profiles?.[scenario.profileId]).toMatchObject({
provider: scenario.provider,
mode: "api_key",
});
expect(resolveAgentModelPrimaryValue(result.config.agents?.defaults?.model)).toBe(
scenario.expectedModel,
);
expect(text).not.toHaveBeenCalled();
expect(confirm).not.toHaveBeenCalled();
const selectedModel = resolveAgentModelPrimaryValue(result.config.agents?.defaults?.model);
if (scenario.expectedModel) {
expect(selectedModel).toBe(scenario.expectedModel);
}
if (scenario.expectedModelPrefix) {
expect(selectedModel?.startsWith(scenario.expectedModelPrefix)).toBe(true);
}
if (scenario.expectedAgentModelOverride) {
expect(result.agentModelOverride).toBe(scenario.expectedAgentModelOverride);
}
expect((await readAuthProfile(scenario.profileId))?.key).toBe(scenario.token);
}
});
it("uses opts token for direct provider choices without prompting", async () => {
await setupTempState();
const scenarios: Array<{
authChoice: "opencode-zen";
tokenProvider: "opencode";
profileId: "opencode:default";
provider: "opencode";
modelPrefix: "opencode/";
extraProfiles: string[];
}> = [
{
authChoice: "opencode-zen",
tokenProvider: "opencode",
profileId: "opencode:default",
provider: "opencode",
modelPrefix: "opencode/",
extraProfiles: ["opencode-go:default"],
},
];
for (const {
authChoice,
tokenProvider,
profileId,
provider,
modelPrefix,
extraProfiles,
} of scenarios) {
const text = vi.fn();
const confirm = vi.fn(async () => false);
const { prompter, runtime } = createApiKeyPromptHarness({ text, confirm });
const token = `sk-${tokenProvider}-test`;
const result = await applyAuthChoice({
authChoice,
config: {},
prompter,
runtime,
setDefaultModel: true,
opts: {
tokenProvider,
token,
},
});
expect(text).not.toHaveBeenCalled();
expect(confirm).not.toHaveBeenCalled();
expect(result.config.auth?.profiles?.[profileId]).toMatchObject({
provider,
mode: "api_key",
});
expect(
resolveAgentModelPrimaryValue(result.config.agents?.defaults?.model)?.startsWith(
modelPrefix,
),
).toBe(true);
expect((await readAuthProfile(profileId))?.key).toBe(token);
for (const extraProfile of extraProfiles ?? []) {
expect((await readAuthProfile(extraProfile))?.key).toBe(token);
for (const extraProfile of scenario.extraProfiles ?? []) {
expect((await readAuthProfile(extraProfile))?.key).toBe(scenario.token);
}
}
});
it("uses opts token for Gemini and keeps global default model when setDefaultModel=false", async () => {
await setupTempState();
const text = vi.fn();
const confirm = vi.fn(async () => false);
const { prompter, runtime } = createApiKeyPromptHarness({ text, confirm });
const result = await applyAuthChoice({
authChoice: "gemini-api-key",
config: { agents: { defaults: { model: { primary: "openai/gpt-4o-mini" } } } },
prompter,
runtime,
setDefaultModel: false,
opts: {
tokenProvider: "google",
token: "sk-gemini-test",
},
});
expect(text).not.toHaveBeenCalled();
expect(confirm).not.toHaveBeenCalled();
expect(result.config.auth?.profiles?.["google:default"]).toMatchObject({
provider: "google",
mode: "api_key",
});
expect(resolveAgentModelPrimaryValue(result.config.agents?.defaults?.model)).toBe(
"openai/gpt-4o-mini",
);
expect(result.agentModelOverride).toBe(GOOGLE_GEMINI_DEFAULT_MODEL);
expect((await readAuthProfile("google:default"))?.key).toBe("sk-gemini-test");
});
it("prompts for Venice API key and shows the Venice note when no token is provided", async () => {
await setupTempState();
process.env.VENICE_API_KEY = "";

View File

@@ -635,7 +635,7 @@ describe("onboard (non-interactive): gateway and remote auth", () => {
});
}, 60_000);
it("uses a longer Windows health deadline when daemon install was requested", async () => {
it("uses longer Windows health timeouts when daemon install was requested", async () => {
await withStateDir("state-local-daemon-health-win-", async (stateDir) => {
const captured = mockGatewayReachableWithCapturedTimeouts();
@@ -646,17 +646,6 @@ describe("onboard (non-interactive): gateway and remote auth", () => {
expect(installGatewayDaemonNonInteractiveMock).toHaveBeenCalledTimes(1);
expect(captured.deadlineMs).toBe(90_000);
expect(captured.probeTimeoutMs).toBe(15_000);
});
}, 60_000);
it("uses a longer Windows health command timeout when daemon install was requested", async () => {
await withStateDir("state-local-daemon-health-command-win-", async (stateDir) => {
waitForGatewayReachableMock = vi.fn(async () => ({ ok: true }));
await withMockedPlatform("win32", async () => {
await runLocalDaemonSetup(stateDir);
});
expect(healthCommandMock).toHaveBeenCalledTimes(1);
expect(healthCommandMock).toHaveBeenCalledWith(
expect.objectContaining({

View File

@@ -1178,49 +1178,51 @@ describe("onboard (non-interactive): provider auth", () => {
});
});
it("configures a custom provider from non-interactive flags", async () => {
await withOnboardEnv("openclaw-onboard-custom-provider-", async ({ configPath, runtime }) => {
await runNonInteractiveSetupWithDefaults(runtime, {
authChoice: "custom-api-key",
customBaseUrl: "https://llm.example.com/v1",
customApiKey: "custom-test-key", // pragma: allowlist secret
customModelId: "foo-large",
customCompatibility: "anthropic",
skipSkills: true,
});
const cfg = await readJsonFile<ProviderAuthConfigSnapshot>(configPath);
const provider = cfg.models?.providers?.["custom-llm-example-com"];
expect(provider?.baseUrl).toBe("https://llm.example.com/v1");
expect(provider?.api).toBe("anthropic-messages");
expect(provider?.apiKey).toBe("custom-test-key");
expect(provider?.models?.some((model) => model.id === "foo-large")).toBe(true);
expect(cfg.agents?.defaults?.model?.primary).toBe("custom-llm-example-com/foo-large");
});
});
it("infers custom provider auth choice from custom flags", async () => {
await withOnboardEnv(
"openclaw-onboard-custom-provider-infer-",
async ({ configPath, runtime }) => {
await runNonInteractiveSetupWithDefaults(runtime, {
it("configures custom providers from explicit or inferred non-interactive flags", async () => {
const scenarios = [
{
options: {
authChoice: "custom-api-key",
customBaseUrl: "https://llm.example.com/v1",
customApiKey: "custom-test-key", // pragma: allowlist secret
customModelId: "foo-large",
customCompatibility: "anthropic",
skipSkills: true,
},
providerId: "custom-llm-example-com",
expectedBaseUrl: "https://llm.example.com/v1",
expectedApi: "anthropic-messages",
expectedModel: "custom-llm-example-com/foo-large",
modelId: "foo-large",
},
{
options: {
customBaseUrl: "https://models.custom.local/v1",
customModelId: "local-large",
customApiKey: "custom-test-key", // pragma: allowlist secret
skipSkills: true,
});
const cfg = await readJsonFile<ProviderAuthConfigSnapshot>(configPath);
expect(cfg.models?.providers?.["custom-models-custom-local"]?.baseUrl).toBe(
"https://models.custom.local/v1",
);
expect(cfg.models?.providers?.["custom-models-custom-local"]?.api).toBe(
"openai-completions",
);
expect(cfg.agents?.defaults?.model?.primary).toBe("custom-models-custom-local/local-large");
},
providerId: "custom-models-custom-local",
expectedBaseUrl: "https://models.custom.local/v1",
expectedApi: "openai-completions",
expectedModel: "custom-models-custom-local/local-large",
modelId: "local-large",
},
);
] as const;
await withOnboardEnv("openclaw-onboard-custom-provider-", async ({ configPath, runtime }) => {
for (const scenario of scenarios) {
await fs.rm(configPath, { force: true });
resetProviderAuthTestState();
await runNonInteractiveSetupWithDefaults(runtime, scenario.options);
const cfg = await readJsonFile<ProviderAuthConfigSnapshot>(configPath);
const provider = cfg.models?.providers?.[scenario.providerId];
expect(provider?.baseUrl).toBe(scenario.expectedBaseUrl);
expect(provider?.api).toBe(scenario.expectedApi);
expect(provider?.apiKey).toBe("custom-test-key");
expect(provider?.models?.some((model) => model.id === scenario.modelId)).toBe(true);
expect(cfg.agents?.defaults?.model?.primary).toBe(scenario.expectedModel);
}
});
});
});