test: dedupe auth fallback tests and add auth util unit coverage

This commit is contained in:
Peter Steinberger
2026-02-18 05:04:51 +00:00
parent 112f8250fc
commit 23f2150190
3 changed files with 279 additions and 250 deletions

View File

@@ -23,6 +23,61 @@ function makeCfg(overrides: Partial<OpenClawConfig> = {}): OpenClawConfig {
} as OpenClawConfig;
}
function makeFallbacksOnlyCfg(): OpenClawConfig {
return {
agents: {
defaults: {
model: {
fallbacks: ["openai/gpt-5.2"],
},
},
},
} as OpenClawConfig;
}
function makeProviderFallbackCfg(provider: string): OpenClawConfig {
return makeCfg({
agents: {
defaults: {
model: {
primary: `${provider}/m1`,
fallbacks: ["fallback/ok-model"],
},
},
},
});
}
async function withTempAuthStore<T>(
store: AuthProfileStore,
run: (tempDir: string) => Promise<T>,
): Promise<T> {
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-auth-"));
saveAuthProfileStore(store, tempDir);
try {
return await run(tempDir);
} finally {
await fs.rm(tempDir, { recursive: true, force: true });
}
}
async function runWithStoredAuth(params: {
cfg: OpenClawConfig;
store: AuthProfileStore;
provider: string;
run: (provider: string, model: string) => Promise<string>;
}) {
return withTempAuthStore(params.store, async (tempDir) =>
runWithModelFallback({
cfg: params.cfg,
provider: params.provider,
model: "m1",
agentDir: tempDir,
run: params.run,
}),
);
}
async function expectFallsBackToHaiku(params: {
provider: string;
model: string;
@@ -121,7 +176,6 @@ describe("runWithModelFallback", () => {
});
it("skips providers when all profiles are in cooldown", async () => {
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-auth-"));
const provider = `cooldown-test-${crypto.randomUUID()}`;
const profileId = `${provider}:default`;
@@ -136,23 +190,12 @@ describe("runWithModelFallback", () => {
},
usageStats: {
[profileId]: {
cooldownUntil: Date.now() + 60_000,
cooldownUntil: Date.now() + 5 * 60_000,
},
},
};
saveAuthProfileStore(store, tempDir);
const cfg = makeCfg({
agents: {
defaults: {
model: {
primary: `${provider}/m1`,
fallbacks: ["fallback/ok-model"],
},
},
},
});
const cfg = makeProviderFallbackCfg(provider);
const run = vi.fn().mockImplementation(async (providerId, modelId) => {
if (providerId === "fallback") {
return "ok";
@@ -160,25 +203,19 @@ describe("runWithModelFallback", () => {
throw new Error(`unexpected provider: ${providerId}/${modelId}`);
});
try {
const result = await runWithModelFallback({
cfg,
provider,
model: "m1",
agentDir: tempDir,
run,
});
const result = await runWithStoredAuth({
cfg,
store,
provider,
run,
});
expect(result.result).toBe("ok");
expect(run.mock.calls).toEqual([["fallback", "ok-model"]]);
expect(result.attempts[0]?.reason).toBe("rate_limit");
} finally {
await fs.rm(tempDir, { recursive: true, force: true });
}
expect(result.result).toBe("ok");
expect(run.mock.calls).toEqual([["fallback", "ok-model"]]);
expect(result.attempts[0]?.reason).toBe("rate_limit");
});
it("does not skip when any profile is available", async () => {
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-auth-"));
const provider = `cooldown-mixed-${crypto.randomUUID()}`;
const profileA = `${provider}:a`;
const profileB = `${provider}:b`;
@@ -204,18 +241,7 @@ describe("runWithModelFallback", () => {
},
};
saveAuthProfileStore(store, tempDir);
const cfg = makeCfg({
agents: {
defaults: {
model: {
primary: `${provider}/m1`,
fallbacks: ["fallback/ok-model"],
},
},
},
});
const cfg = makeProviderFallbackCfg(provider);
const run = vi.fn().mockImplementation(async (providerId) => {
if (providerId === provider) {
return "ok";
@@ -223,21 +249,16 @@ describe("runWithModelFallback", () => {
return "unexpected";
});
try {
const result = await runWithModelFallback({
cfg,
provider,
model: "m1",
agentDir: tempDir,
run,
});
const result = await runWithStoredAuth({
cfg,
store,
provider,
run,
});
expect(result.result).toBe("ok");
expect(run.mock.calls).toEqual([[provider, "m1"]]);
expect(result.attempts).toEqual([]);
} finally {
await fs.rm(tempDir, { recursive: true, force: true });
}
expect(result.result).toBe("ok");
expect(run.mock.calls).toEqual([[provider, "m1"]]);
expect(result.attempts).toEqual([]);
});
it("does not append configured primary when fallbacksOverride is set", async () => {
@@ -271,15 +292,7 @@ describe("runWithModelFallback", () => {
});
it("uses fallbacksOverride instead of agents.defaults.model.fallbacks", async () => {
const cfg = {
agents: {
defaults: {
model: {
fallbacks: ["openai/gpt-5.2"],
},
},
},
} as OpenClawConfig;
const cfg = makeFallbacksOnlyCfg();
const calls: Array<{ provider: string; model: string }> = [];
@@ -308,15 +321,7 @@ describe("runWithModelFallback", () => {
});
it("treats an empty fallbacksOverride as disabling global fallbacks", async () => {
const cfg = {
agents: {
defaults: {
model: {
fallbacks: ["openai/gpt-5.2"],
},
},
},
} as OpenClawConfig;
const cfg = makeFallbacksOnlyCfg();
const calls: Array<{ provider: string; model: string }> = [];