test(auth): add tests for per-model cooldown functions
- Add tests for markAuthProfileFailure with model param - Add tests for markAuthProfileCooldown with model param - Add tests for clearAuthProfileCooldown with model param - Add backward compatibility test (profile-level blocks all models) - Add test for disabledUntil handling in per-model cooldowns - Add edge case test for empty string model parameter
This commit is contained in:
parent
7f0c098665
commit
e9e6689c9c
@ -4,8 +4,11 @@ import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
calculateAuthProfileCooldownMs,
|
||||
clearAuthProfileCooldown,
|
||||
cooldownKey,
|
||||
isProfileInCooldown,
|
||||
markAuthProfileCooldown,
|
||||
markAuthProfileFailure,
|
||||
markAuthProfileUsed,
|
||||
saveAuthProfileStore,
|
||||
} from "./auth-profiles.js";
|
||||
@ -177,3 +180,230 @@ describe("markAuthProfileUsed with per-model support", () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("cooldownKey edge cases", () => {
|
||||
it("treats empty string model the same as undefined", () => {
|
||||
// Empty string should be treated as "no model" to avoid trailing colon
|
||||
expect(cooldownKey("openai:default", "")).toBe("openai:default");
|
||||
expect(cooldownKey("openai:default", " ")).toBe("openai:default");
|
||||
});
|
||||
});
|
||||
|
||||
describe("isProfileInCooldown backward compatibility", () => {
|
||||
it("returns true for any model when profile-level cooldown exists", () => {
|
||||
const store: AuthProfileStore = {
|
||||
version: AUTH_STORE_VERSION,
|
||||
profiles: {
|
||||
"openai:default": { type: "api_key", provider: "openai", key: "test" },
|
||||
},
|
||||
usageStats: {
|
||||
"openai:default": { cooldownUntil: Date.now() + 60_000 }, // profile-level only
|
||||
},
|
||||
};
|
||||
// Any model should be blocked when profile-level cooldown exists
|
||||
expect(isProfileInCooldown(store, "openai:default", "gpt-4")).toBe(true);
|
||||
expect(isProfileInCooldown(store, "openai:default", "gpt-3.5")).toBe(true);
|
||||
expect(isProfileInCooldown(store, "openai:default", "o1-preview")).toBe(true);
|
||||
// Profile-level check also works
|
||||
expect(isProfileInCooldown(store, "openai:default")).toBe(true);
|
||||
});
|
||||
|
||||
it("checks disabledUntil for per-model cooldowns (billing failures)", () => {
|
||||
const store: AuthProfileStore = {
|
||||
version: AUTH_STORE_VERSION,
|
||||
profiles: {
|
||||
"openai:default": { type: "api_key", provider: "openai", key: "test" },
|
||||
},
|
||||
usageStats: {
|
||||
"openai:default:gpt-4": { disabledUntil: Date.now() + 60_000 }, // billing failure
|
||||
},
|
||||
};
|
||||
expect(isProfileInCooldown(store, "openai:default", "gpt-4")).toBe(true);
|
||||
expect(isProfileInCooldown(store, "openai:default", "gpt-3.5")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("markAuthProfileFailure with per-model support", () => {
|
||||
it("tracks failure per model when model is provided", async () => {
|
||||
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-auth-"));
|
||||
const store: AuthProfileStore = {
|
||||
version: AUTH_STORE_VERSION,
|
||||
profiles: {
|
||||
"openai:default": { type: "api_key", provider: "openai", key: "test" },
|
||||
},
|
||||
};
|
||||
saveAuthProfileStore(store, tempDir);
|
||||
|
||||
try {
|
||||
await markAuthProfileFailure({
|
||||
store,
|
||||
profileId: "openai:default",
|
||||
model: "gpt-4",
|
||||
reason: "rate_limit",
|
||||
agentDir: tempDir,
|
||||
});
|
||||
|
||||
// Per-model key should have cooldown
|
||||
expect(store.usageStats?.["openai:default:gpt-4"]?.cooldownUntil).toBeGreaterThan(Date.now());
|
||||
expect(store.usageStats?.["openai:default:gpt-4"]?.errorCount).toBe(1);
|
||||
// Profile-level should NOT have cooldown (only model-specific)
|
||||
expect(store.usageStats?.["openai:default"]?.cooldownUntil).toBeUndefined();
|
||||
// Other models should not be affected
|
||||
expect(store.usageStats?.["openai:default:gpt-3.5"]).toBeUndefined();
|
||||
} finally {
|
||||
await fs.rm(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("tracks failure at profile level when model is not provided", async () => {
|
||||
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-auth-"));
|
||||
const store: AuthProfileStore = {
|
||||
version: AUTH_STORE_VERSION,
|
||||
profiles: {
|
||||
"openai:default": { type: "api_key", provider: "openai", key: "test" },
|
||||
},
|
||||
};
|
||||
saveAuthProfileStore(store, tempDir);
|
||||
|
||||
try {
|
||||
await markAuthProfileFailure({
|
||||
store,
|
||||
profileId: "openai:default",
|
||||
reason: "auth",
|
||||
agentDir: tempDir,
|
||||
});
|
||||
|
||||
// Profile-level key should have cooldown
|
||||
expect(store.usageStats?.["openai:default"]?.cooldownUntil).toBeGreaterThan(Date.now());
|
||||
expect(store.usageStats?.["openai:default"]?.errorCount).toBe(1);
|
||||
} finally {
|
||||
await fs.rm(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("tracks billing failures with disabledUntil per model", async () => {
|
||||
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-auth-"));
|
||||
const store: AuthProfileStore = {
|
||||
version: AUTH_STORE_VERSION,
|
||||
profiles: {
|
||||
"openai:default": { type: "api_key", provider: "openai", key: "test" },
|
||||
},
|
||||
};
|
||||
saveAuthProfileStore(store, tempDir);
|
||||
|
||||
try {
|
||||
await markAuthProfileFailure({
|
||||
store,
|
||||
profileId: "openai:default",
|
||||
model: "gpt-4",
|
||||
reason: "billing",
|
||||
agentDir: tempDir,
|
||||
});
|
||||
|
||||
// Billing failures use disabledUntil instead of cooldownUntil
|
||||
expect(store.usageStats?.["openai:default:gpt-4"]?.disabledUntil).toBeGreaterThan(Date.now());
|
||||
expect(store.usageStats?.["openai:default:gpt-4"]?.disabledReason).toBe("billing");
|
||||
} finally {
|
||||
await fs.rm(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("markAuthProfileCooldown with per-model support", () => {
|
||||
it("marks cooldown per model when model is provided", async () => {
|
||||
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-auth-"));
|
||||
const store: AuthProfileStore = {
|
||||
version: AUTH_STORE_VERSION,
|
||||
profiles: {
|
||||
"openai:default": { type: "api_key", provider: "openai", key: "test" },
|
||||
},
|
||||
};
|
||||
saveAuthProfileStore(store, tempDir);
|
||||
|
||||
try {
|
||||
await markAuthProfileCooldown({
|
||||
store,
|
||||
profileId: "openai:default",
|
||||
model: "gpt-4",
|
||||
agentDir: tempDir,
|
||||
});
|
||||
|
||||
// Per-model key should have cooldown
|
||||
expect(store.usageStats?.["openai:default:gpt-4"]?.cooldownUntil).toBeGreaterThan(Date.now());
|
||||
// Profile-level should NOT have cooldown
|
||||
expect(store.usageStats?.["openai:default"]?.cooldownUntil).toBeUndefined();
|
||||
} finally {
|
||||
await fs.rm(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("clearAuthProfileCooldown with per-model support", () => {
|
||||
it("clears per-model cooldown when model is provided", async () => {
|
||||
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-auth-"));
|
||||
const cooldownTime = Date.now() + 60_000;
|
||||
const store: AuthProfileStore = {
|
||||
version: AUTH_STORE_VERSION,
|
||||
profiles: {
|
||||
"openai:default": { type: "api_key", provider: "openai", key: "test" },
|
||||
},
|
||||
usageStats: {
|
||||
"openai:default": { cooldownUntil: cooldownTime },
|
||||
"openai:default:gpt-4": { cooldownUntil: cooldownTime, errorCount: 3 },
|
||||
"openai:default:gpt-3.5": { cooldownUntil: cooldownTime },
|
||||
},
|
||||
};
|
||||
saveAuthProfileStore(store, tempDir);
|
||||
|
||||
try {
|
||||
await clearAuthProfileCooldown({
|
||||
store,
|
||||
profileId: "openai:default",
|
||||
model: "gpt-4",
|
||||
agentDir: tempDir,
|
||||
});
|
||||
|
||||
// Per-model cooldown for gpt-4 should be cleared
|
||||
expect(store.usageStats?.["openai:default:gpt-4"]?.cooldownUntil).toBeUndefined();
|
||||
expect(store.usageStats?.["openai:default:gpt-4"]?.errorCount).toBe(0);
|
||||
// Profile-level cooldown should remain (different key)
|
||||
expect(store.usageStats?.["openai:default"]?.cooldownUntil).toBe(cooldownTime);
|
||||
// Other model cooldown should remain
|
||||
expect(store.usageStats?.["openai:default:gpt-3.5"]?.cooldownUntil).toBe(cooldownTime);
|
||||
} finally {
|
||||
await fs.rm(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("clears profile-level cooldown when model is not provided", async () => {
|
||||
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-auth-"));
|
||||
const cooldownTime = Date.now() + 60_000;
|
||||
const store: AuthProfileStore = {
|
||||
version: AUTH_STORE_VERSION,
|
||||
profiles: {
|
||||
"openai:default": { type: "api_key", provider: "openai", key: "test" },
|
||||
},
|
||||
usageStats: {
|
||||
"openai:default": { cooldownUntil: cooldownTime, errorCount: 2 },
|
||||
"openai:default:gpt-4": { cooldownUntil: cooldownTime },
|
||||
},
|
||||
};
|
||||
saveAuthProfileStore(store, tempDir);
|
||||
|
||||
try {
|
||||
await clearAuthProfileCooldown({
|
||||
store,
|
||||
profileId: "openai:default",
|
||||
agentDir: tempDir,
|
||||
});
|
||||
|
||||
// Profile-level cooldown should be cleared
|
||||
expect(store.usageStats?.["openai:default"]?.cooldownUntil).toBeUndefined();
|
||||
expect(store.usageStats?.["openai:default"]?.errorCount).toBe(0);
|
||||
// Per-model cooldown should remain (different key)
|
||||
expect(store.usageStats?.["openai:default:gpt-4"]?.cooldownUntil).toBe(cooldownTime);
|
||||
} finally {
|
||||
await fs.rm(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user