mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 02:21:25 +00:00
test(core): increase coverage for sessions, auth choice, and model listing
This commit is contained in:
@@ -13,6 +13,7 @@ function createHuggingfacePrompter(params: {
|
||||
text: WizardPrompter["text"];
|
||||
select: WizardPrompter["select"];
|
||||
confirm?: WizardPrompter["confirm"];
|
||||
note?: WizardPrompter["note"];
|
||||
}): WizardPrompter {
|
||||
const overrides: Partial<WizardPrompter> = {
|
||||
text: params.text,
|
||||
@@ -21,6 +22,9 @@ function createHuggingfacePrompter(params: {
|
||||
if (params.confirm) {
|
||||
overrides.confirm = params.confirm;
|
||||
}
|
||||
if (params.note) {
|
||||
overrides.note = params.note;
|
||||
}
|
||||
return createWizardPrompter(overrides, { defaultSelect: "" });
|
||||
}
|
||||
|
||||
@@ -95,9 +99,26 @@ describe("applyAuthChoiceHuggingface", () => {
|
||||
expect(parsed.profiles?.["huggingface:default"]?.key).toBe("hf-test-token");
|
||||
});
|
||||
|
||||
it("does not prompt to reuse env token when opts.token already provided", async () => {
|
||||
it.each([
|
||||
{
|
||||
caseName: "does not prompt to reuse env token when opts.token already provided",
|
||||
tokenProvider: "huggingface",
|
||||
token: "hf-opts-token",
|
||||
envToken: "hf-env-token",
|
||||
},
|
||||
{
|
||||
caseName: "accepts mixed-case tokenProvider from opts without prompting",
|
||||
tokenProvider: " HuGgInGfAcE ",
|
||||
token: "hf-opts-mixed",
|
||||
envToken: undefined,
|
||||
},
|
||||
])("$caseName", async ({ tokenProvider, token, envToken }) => {
|
||||
const agentDir = await setupTempState();
|
||||
process.env.HF_TOKEN = "hf-env-token";
|
||||
if (envToken) {
|
||||
process.env.HF_TOKEN = envToken;
|
||||
} else {
|
||||
delete process.env.HF_TOKEN;
|
||||
}
|
||||
delete process.env.HUGGINGFACE_HUB_TOKEN;
|
||||
|
||||
const text = vi.fn().mockResolvedValue("hf-text-token");
|
||||
@@ -115,8 +136,8 @@ describe("applyAuthChoiceHuggingface", () => {
|
||||
runtime,
|
||||
setDefaultModel: true,
|
||||
opts: {
|
||||
tokenProvider: "huggingface",
|
||||
token: "hf-opts-token",
|
||||
tokenProvider,
|
||||
token,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -125,20 +146,22 @@ describe("applyAuthChoiceHuggingface", () => {
|
||||
expect(text).not.toHaveBeenCalled();
|
||||
|
||||
const parsed = await readAuthProfiles(agentDir);
|
||||
expect(parsed.profiles?.["huggingface:default"]?.key).toBe("hf-opts-token");
|
||||
expect(parsed.profiles?.["huggingface:default"]?.key).toBe(token);
|
||||
});
|
||||
|
||||
it("accepts mixed-case tokenProvider from opts without prompting", async () => {
|
||||
const agentDir = await setupTempState();
|
||||
it("notes when selected Hugging Face model uses a locked router policy", async () => {
|
||||
await setupTempState();
|
||||
delete process.env.HF_TOKEN;
|
||||
delete process.env.HUGGINGFACE_HUB_TOKEN;
|
||||
|
||||
const text = vi.fn().mockResolvedValue("hf-text-token");
|
||||
const select: WizardPrompter["select"] = vi.fn(
|
||||
async (params) => params.options?.[0]?.value as never,
|
||||
);
|
||||
const confirm = vi.fn(async () => true);
|
||||
const prompter = createHuggingfacePrompter({ text, select, confirm });
|
||||
const text = vi.fn().mockResolvedValue("hf-test-token");
|
||||
const select: WizardPrompter["select"] = vi.fn(async (params) => {
|
||||
const options = (params.options ?? []) as Array<{ value: string }>;
|
||||
const cheapest = options.find((option) => option.value.endsWith(":cheapest"));
|
||||
return (cheapest?.value ?? options[0]?.value ?? "") as never;
|
||||
});
|
||||
const note: WizardPrompter["note"] = vi.fn(async () => {});
|
||||
const prompter = createHuggingfacePrompter({ text, select, note });
|
||||
const runtime = createExitThrowingRuntime();
|
||||
|
||||
const result = await applyAuthChoiceHuggingface({
|
||||
@@ -147,17 +170,13 @@ describe("applyAuthChoiceHuggingface", () => {
|
||||
prompter,
|
||||
runtime,
|
||||
setDefaultModel: true,
|
||||
opts: {
|
||||
tokenProvider: " HuGgInGfAcE ",
|
||||
token: "hf-opts-mixed",
|
||||
},
|
||||
});
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(confirm).not.toHaveBeenCalled();
|
||||
expect(text).not.toHaveBeenCalled();
|
||||
|
||||
const parsed = await readAuthProfiles(agentDir);
|
||||
expect(parsed.profiles?.["huggingface:default"]?.key).toBe("hf-opts-mixed");
|
||||
expect(String(result?.config.agents?.defaults?.model?.primary)).toContain(":cheapest");
|
||||
expect(note).toHaveBeenCalledWith(
|
||||
"Provider locked — router will choose backend by cost or speed.",
|
||||
"Hugging Face",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -47,6 +47,11 @@ describe("applyAuthChoiceMiniMax", () => {
|
||||
}>(agentDir);
|
||||
}
|
||||
|
||||
function resetMiniMaxEnv(): void {
|
||||
delete process.env.MINIMAX_API_KEY;
|
||||
delete process.env.MINIMAX_OAUTH_TOKEN;
|
||||
}
|
||||
|
||||
afterEach(async () => {
|
||||
await lifecycle.cleanup();
|
||||
});
|
||||
@@ -63,38 +68,60 @@ describe("applyAuthChoiceMiniMax", () => {
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("uses opts token for minimax-api without prompt", async () => {
|
||||
const agentDir = await setupTempState();
|
||||
delete process.env.MINIMAX_API_KEY;
|
||||
delete process.env.MINIMAX_OAUTH_TOKEN;
|
||||
|
||||
const text = vi.fn(async () => "should-not-be-used");
|
||||
const confirm = vi.fn(async () => true);
|
||||
|
||||
const result = await applyAuthChoiceMiniMax({
|
||||
authChoice: "minimax-api",
|
||||
config: {},
|
||||
prompter: createMinimaxPrompter({ text, confirm }),
|
||||
runtime: createExitThrowingRuntime(),
|
||||
setDefaultModel: true,
|
||||
opts: {
|
||||
tokenProvider: "minimax",
|
||||
token: "mm-opts-token",
|
||||
},
|
||||
});
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.config.auth?.profiles?.["minimax:default"]).toMatchObject({
|
||||
it.each([
|
||||
{
|
||||
caseName: "uses opts token for minimax-api without prompt",
|
||||
authChoice: "minimax-api" as const,
|
||||
tokenProvider: "minimax",
|
||||
token: "mm-opts-token",
|
||||
profileId: "minimax:default",
|
||||
provider: "minimax",
|
||||
mode: "api_key",
|
||||
});
|
||||
expect(result?.config.agents?.defaults?.model?.primary).toBe("minimax/MiniMax-M2.5");
|
||||
expect(text).not.toHaveBeenCalled();
|
||||
expect(confirm).not.toHaveBeenCalled();
|
||||
expectedModel: "minimax/MiniMax-M2.5",
|
||||
},
|
||||
{
|
||||
caseName:
|
||||
"uses opts token for minimax-api-key-cn with trimmed/case-insensitive tokenProvider",
|
||||
authChoice: "minimax-api-key-cn" as const,
|
||||
tokenProvider: " MINIMAX-CN ",
|
||||
token: "mm-cn-opts-token",
|
||||
profileId: "minimax-cn:default",
|
||||
provider: "minimax-cn",
|
||||
expectedModel: "minimax-cn/MiniMax-M2.5",
|
||||
},
|
||||
])(
|
||||
"$caseName",
|
||||
async ({ authChoice, tokenProvider, token, profileId, provider, expectedModel }) => {
|
||||
const agentDir = await setupTempState();
|
||||
resetMiniMaxEnv();
|
||||
|
||||
const parsed = await readAuthProfiles(agentDir);
|
||||
expect(parsed.profiles?.["minimax:default"]?.key).toBe("mm-opts-token");
|
||||
});
|
||||
const text = vi.fn(async () => "should-not-be-used");
|
||||
const confirm = vi.fn(async () => true);
|
||||
|
||||
const result = await applyAuthChoiceMiniMax({
|
||||
authChoice,
|
||||
config: {},
|
||||
prompter: createMinimaxPrompter({ text, confirm }),
|
||||
runtime: createExitThrowingRuntime(),
|
||||
setDefaultModel: true,
|
||||
opts: {
|
||||
tokenProvider,
|
||||
token,
|
||||
},
|
||||
});
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.config.auth?.profiles?.[profileId]).toMatchObject({
|
||||
provider,
|
||||
mode: "api_key",
|
||||
});
|
||||
expect(result?.config.agents?.defaults?.model?.primary).toBe(expectedModel);
|
||||
expect(text).not.toHaveBeenCalled();
|
||||
expect(confirm).not.toHaveBeenCalled();
|
||||
|
||||
const parsed = await readAuthProfiles(agentDir);
|
||||
expect(parsed.profiles?.[profileId]?.key).toBe(token);
|
||||
},
|
||||
);
|
||||
|
||||
it("uses env token for minimax-api-key-cn when confirmed", async () => {
|
||||
const agentDir = await setupTempState();
|
||||
@@ -125,36 +152,35 @@ describe("applyAuthChoiceMiniMax", () => {
|
||||
expect(parsed.profiles?.["minimax-cn:default"]?.key).toBe("mm-env-token");
|
||||
});
|
||||
|
||||
it("uses opts token for minimax-api-key-cn with trimmed/case-insensitive tokenProvider", async () => {
|
||||
it("uses minimax-api-lightning default model", async () => {
|
||||
const agentDir = await setupTempState();
|
||||
delete process.env.MINIMAX_API_KEY;
|
||||
delete process.env.MINIMAX_OAUTH_TOKEN;
|
||||
resetMiniMaxEnv();
|
||||
|
||||
const text = vi.fn(async () => "should-not-be-used");
|
||||
const confirm = vi.fn(async () => true);
|
||||
|
||||
const result = await applyAuthChoiceMiniMax({
|
||||
authChoice: "minimax-api-key-cn",
|
||||
authChoice: "minimax-api-lightning",
|
||||
config: {},
|
||||
prompter: createMinimaxPrompter({ text, confirm }),
|
||||
runtime: createExitThrowingRuntime(),
|
||||
setDefaultModel: true,
|
||||
opts: {
|
||||
tokenProvider: " MINIMAX-CN ",
|
||||
token: "mm-cn-opts-token",
|
||||
tokenProvider: "minimax",
|
||||
token: "mm-lightning-token",
|
||||
},
|
||||
});
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.config.auth?.profiles?.["minimax-cn:default"]).toMatchObject({
|
||||
provider: "minimax-cn",
|
||||
expect(result?.config.auth?.profiles?.["minimax:default"]).toMatchObject({
|
||||
provider: "minimax",
|
||||
mode: "api_key",
|
||||
});
|
||||
expect(result?.config.agents?.defaults?.model?.primary).toBe("minimax-cn/MiniMax-M2.5");
|
||||
expect(result?.config.agents?.defaults?.model?.primary).toBe("minimax/MiniMax-M2.5-Lightning");
|
||||
expect(text).not.toHaveBeenCalled();
|
||||
expect(confirm).not.toHaveBeenCalled();
|
||||
|
||||
const parsed = await readAuthProfiles(agentDir);
|
||||
expect(parsed.profiles?.["minimax-cn:default"]?.key).toBe("mm-cn-opts-token");
|
||||
expect(parsed.profiles?.["minimax:default"]?.key).toBe("mm-lightning-token");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -260,6 +260,23 @@ describe("models list/status", () => {
|
||||
return parseJsonLog(runtime);
|
||||
}
|
||||
|
||||
const GOOGLE_ANTIGRAVITY_OPUS_46_CASES = [
|
||||
{
|
||||
name: "thinking",
|
||||
configuredModelId: "claude-opus-4-6-thinking",
|
||||
templateId: "claude-opus-4-5-thinking",
|
||||
templateName: "Claude Opus 4.5 Thinking",
|
||||
expectedKey: "google-antigravity/claude-opus-4-6-thinking",
|
||||
},
|
||||
{
|
||||
name: "non-thinking",
|
||||
configuredModelId: "claude-opus-4-6",
|
||||
templateId: "claude-opus-4-5",
|
||||
templateName: "Claude Opus 4.5",
|
||||
expectedKey: "google-antigravity/claude-opus-4-6",
|
||||
},
|
||||
] as const;
|
||||
|
||||
function expectAntigravityModel(
|
||||
payload: Record<string, unknown>,
|
||||
params: { key: string; available: boolean; includesTags?: boolean },
|
||||
@@ -329,22 +346,7 @@ describe("models list/status", () => {
|
||||
expect(payload.models[0]?.available).toBe(false);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
name: "thinking",
|
||||
configuredModelId: "claude-opus-4-6-thinking",
|
||||
templateId: "claude-opus-4-5-thinking",
|
||||
templateName: "Claude Opus 4.5 Thinking",
|
||||
expectedKey: "google-antigravity/claude-opus-4-6-thinking",
|
||||
},
|
||||
{
|
||||
name: "non-thinking",
|
||||
configuredModelId: "claude-opus-4-6",
|
||||
templateId: "claude-opus-4-5",
|
||||
templateName: "Claude Opus 4.5",
|
||||
expectedKey: "google-antigravity/claude-opus-4-6",
|
||||
},
|
||||
] as const)(
|
||||
it.each(GOOGLE_ANTIGRAVITY_OPUS_46_CASES)(
|
||||
"models list resolves antigravity opus 4.6 $name from 4.5 template",
|
||||
async ({ configuredModelId, templateId, templateName, expectedKey }) => {
|
||||
const payload = await runGoogleAntigravityListCase({
|
||||
@@ -360,22 +362,7 @@ describe("models list/status", () => {
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
{
|
||||
name: "thinking",
|
||||
configuredModelId: "claude-opus-4-6-thinking",
|
||||
templateId: "claude-opus-4-5-thinking",
|
||||
templateName: "Claude Opus 4.5 Thinking",
|
||||
expectedKey: "google-antigravity/claude-opus-4-6-thinking",
|
||||
},
|
||||
{
|
||||
name: "non-thinking",
|
||||
configuredModelId: "claude-opus-4-6",
|
||||
templateId: "claude-opus-4-5",
|
||||
templateName: "Claude Opus 4.5",
|
||||
expectedKey: "google-antigravity/claude-opus-4-6",
|
||||
},
|
||||
] as const)(
|
||||
it.each(GOOGLE_ANTIGRAVITY_OPUS_46_CASES)(
|
||||
"models list marks synthesized antigravity opus 4.6 $name as available when template is available",
|
||||
async ({ configuredModelId, templateId, templateName, expectedKey }) => {
|
||||
const payload = await runGoogleAntigravityListCase({
|
||||
|
||||
Reference in New Issue
Block a user