refactor(test): simplify auth-profile-cooldowns tests with helpers
- Add makeStore() helper to eliminate repeated store object literals - Add withTempDir() helper to eliminate try/finally boilerplate - Merge cooldownKey edge cases into main describe block - Remove redundant markAuthProfileCooldown test (covered by markAuthProfileFailure) - Net reduction: 245 lines removed (57% smaller), 19→17 tests
This commit is contained in:
parent
e3caf006bb
commit
e954d29291
@ -20,7 +20,6 @@ import {
|
||||
clearAuthProfileCooldown,
|
||||
cooldownKey,
|
||||
isProfileInCooldown,
|
||||
markAuthProfileCooldown,
|
||||
markAuthProfileFailure,
|
||||
markAuthProfileUsed,
|
||||
saveAuthProfileStore,
|
||||
@ -28,6 +27,24 @@ import {
|
||||
import type { AuthProfileStore } from "./auth-profiles.js";
|
||||
import { AUTH_STORE_VERSION } from "./auth-profiles/constants.js";
|
||||
|
||||
// Test helpers
|
||||
const makeStore = (usageStats?: AuthProfileStore["usageStats"]): AuthProfileStore => ({
|
||||
version: AUTH_STORE_VERSION,
|
||||
profiles: {
|
||||
"openai:default": { type: "api_key", provider: "openai", key: "test" },
|
||||
},
|
||||
...(usageStats && { usageStats }),
|
||||
});
|
||||
|
||||
async function withTempDir<T>(fn: (tempDir: string) => Promise<T>): Promise<T> {
|
||||
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-auth-"));
|
||||
try {
|
||||
return await fn(tempDir);
|
||||
} finally {
|
||||
await fs.rm(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
describe("auth profile cooldowns", () => {
|
||||
it("applies exponential backoff with a 1h cap", () => {
|
||||
expect(calculateAuthProfileCooldownMs(1)).toBe(60_000);
|
||||
@ -39,9 +56,11 @@ describe("auth profile cooldowns", () => {
|
||||
});
|
||||
|
||||
describe("cooldownKey", () => {
|
||||
it("returns profileId when model is not provided", () => {
|
||||
it("returns profileId when model is not provided or empty", () => {
|
||||
expect(cooldownKey("openai:default")).toBe("openai:default");
|
||||
expect(cooldownKey("openai:default", undefined)).toBe("openai:default");
|
||||
expect(cooldownKey("openai:default", "")).toBe("openai:default");
|
||||
expect(cooldownKey("openai:default", " ")).toBe("openai:default");
|
||||
});
|
||||
|
||||
it("returns composite key when model is provided", () => {
|
||||
@ -52,44 +71,20 @@ describe("cooldownKey", () => {
|
||||
|
||||
describe("isProfileInCooldown with per-model support", () => {
|
||||
it("returns false when no cooldown exists", () => {
|
||||
const store: AuthProfileStore = {
|
||||
version: AUTH_STORE_VERSION,
|
||||
profiles: {
|
||||
"openai:default": { type: "api_key", provider: "openai", key: "test" },
|
||||
},
|
||||
};
|
||||
const store = makeStore();
|
||||
expect(isProfileInCooldown(store, "openai:default")).toBe(false);
|
||||
expect(isProfileInCooldown(store, "openai:default", "gpt-4")).toBe(false);
|
||||
});
|
||||
|
||||
it("checks profile-level cooldown when model not provided", () => {
|
||||
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 },
|
||||
},
|
||||
};
|
||||
const store = makeStore({ "openai:default": { cooldownUntil: Date.now() + 60_000 } });
|
||||
expect(isProfileInCooldown(store, "openai:default")).toBe(true);
|
||||
});
|
||||
|
||||
it("checks per-model cooldown when model is provided", () => {
|
||||
const store: AuthProfileStore = {
|
||||
version: AUTH_STORE_VERSION,
|
||||
profiles: {
|
||||
"openai:default": { type: "api_key", provider: "openai", key: "test" },
|
||||
},
|
||||
usageStats: {
|
||||
"openai:default:gpt-4": { cooldownUntil: Date.now() + 60_000 },
|
||||
},
|
||||
};
|
||||
// model-specific cooldown exists
|
||||
const store = makeStore({ "openai:default:gpt-4": { cooldownUntil: Date.now() + 60_000 } });
|
||||
expect(isProfileInCooldown(store, "openai:default", "gpt-4")).toBe(true);
|
||||
// different model is not in cooldown
|
||||
expect(isProfileInCooldown(store, "openai:default", "gpt-3.5")).toBe(false);
|
||||
// profile-level is not in cooldown
|
||||
expect(isProfileInCooldown(store, "openai:default")).toBe(false);
|
||||
});
|
||||
|
||||
@ -97,55 +92,31 @@ describe("isProfileInCooldown with per-model support", () => {
|
||||
const store: AuthProfileStore = {
|
||||
version: AUTH_STORE_VERSION,
|
||||
profiles: {
|
||||
"github-copilot:default": {
|
||||
type: "api_key",
|
||||
provider: "github-copilot",
|
||||
key: "test",
|
||||
},
|
||||
},
|
||||
usageStats: {
|
||||
// gpt-5.2 is in cooldown (rate limited)
|
||||
"github-copilot:default:gpt-5.2": { cooldownUntil: Date.now() + 60_000 },
|
||||
// gpt-5-mini has no cooldown (unlimited quota)
|
||||
"github-copilot:default": { type: "api_key", provider: "github-copilot", key: "test" },
|
||||
},
|
||||
usageStats: { "github-copilot:default:gpt-5.2": { cooldownUntil: Date.now() + 60_000 } },
|
||||
};
|
||||
expect(isProfileInCooldown(store, "github-copilot:default", "gpt-5.2")).toBe(true);
|
||||
expect(isProfileInCooldown(store, "github-copilot:default", "gpt-5-mini")).toBe(false);
|
||||
});
|
||||
|
||||
it("returns false when cooldown has expired", () => {
|
||||
const store: AuthProfileStore = {
|
||||
version: AUTH_STORE_VERSION,
|
||||
profiles: {
|
||||
"openai:default": { type: "api_key", provider: "openai", key: "test" },
|
||||
},
|
||||
usageStats: {
|
||||
"openai:default:gpt-4": { cooldownUntil: Date.now() - 1000 }, // expired
|
||||
},
|
||||
};
|
||||
const store = makeStore({ "openai:default:gpt-4": { cooldownUntil: Date.now() - 1000 } });
|
||||
expect(isProfileInCooldown(store, "openai:default", "gpt-4")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("markAuthProfileUsed 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: {
|
||||
await withTempDir(async (tempDir) => {
|
||||
const cooldownTime = Date.now() + 60_000;
|
||||
const store = makeStore({
|
||||
"openai:default": { cooldownUntil: cooldownTime },
|
||||
"openai:default:gpt-4": { cooldownUntil: cooldownTime, errorCount: 3 },
|
||||
"openai:default:gpt-3.5": { cooldownUntil: cooldownTime },
|
||||
},
|
||||
};
|
||||
saveAuthProfileStore(store, tempDir);
|
||||
});
|
||||
saveAuthProfileStore(store, tempDir);
|
||||
|
||||
try {
|
||||
// Mark gpt-4 as used (successful)
|
||||
await markAuthProfileUsed({
|
||||
store,
|
||||
profileId: "openai:default",
|
||||
@ -153,84 +124,41 @@ describe("markAuthProfileUsed with per-model support", () => {
|
||||
agentDir: tempDir,
|
||||
});
|
||||
|
||||
// Profile-level cooldown should be cleared
|
||||
expect(store.usageStats?.["openai:default"]?.cooldownUntil).toBeUndefined();
|
||||
// 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);
|
||||
// Per-model cooldown for gpt-3.5 should remain (different model)
|
||||
expect(store.usageStats?.["openai:default:gpt-3.5"]?.cooldownUntil).toBe(cooldownTime);
|
||||
} finally {
|
||||
await fs.rm(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("only 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: {
|
||||
await withTempDir(async (tempDir) => {
|
||||
const cooldownTime = Date.now() + 60_000;
|
||||
const store = makeStore({
|
||||
"openai:default": { cooldownUntil: cooldownTime },
|
||||
"openai:default:gpt-4": { cooldownUntil: cooldownTime },
|
||||
},
|
||||
};
|
||||
saveAuthProfileStore(store, tempDir);
|
||||
});
|
||||
saveAuthProfileStore(store, tempDir);
|
||||
|
||||
try {
|
||||
// Mark profile as used without specifying model
|
||||
await markAuthProfileUsed({ store, profileId: "openai:default", agentDir: tempDir });
|
||||
|
||||
// Profile-level cooldown should be cleared
|
||||
expect(store.usageStats?.["openai:default"]?.cooldownUntil).toBeUndefined();
|
||||
// Per-model cooldown should remain (no model specified)
|
||||
expect(store.usageStats?.["openai:default:gpt-4"]?.cooldownUntil).toBe(cooldownTime);
|
||||
} finally {
|
||||
await fs.rm(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
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
|
||||
const store = makeStore({ "openai:default": { cooldownUntil: Date.now() + 60_000 } });
|
||||
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
|
||||
},
|
||||
};
|
||||
const store = makeStore({ "openai:default:gpt-4": { disabledUntil: Date.now() + 60_000 } });
|
||||
expect(isProfileInCooldown(store, "openai:default", "gpt-4")).toBe(true);
|
||||
expect(isProfileInCooldown(store, "openai:default", "gpt-3.5")).toBe(false);
|
||||
});
|
||||
@ -238,16 +166,10 @@ describe("isProfileInCooldown backward compatibility", () => {
|
||||
|
||||
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);
|
||||
await withTempDir(async (tempDir) => {
|
||||
const store = makeStore();
|
||||
saveAuthProfileStore(store, tempDir);
|
||||
|
||||
try {
|
||||
await markAuthProfileFailure({
|
||||
store,
|
||||
profileId: "openai:default",
|
||||
@ -256,29 +178,18 @@ describe("markAuthProfileFailure with per-model support", () => {
|
||||
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);
|
||||
await withTempDir(async (tempDir) => {
|
||||
const store = makeStore();
|
||||
saveAuthProfileStore(store, tempDir);
|
||||
|
||||
try {
|
||||
await markAuthProfileFailure({
|
||||
store,
|
||||
profileId: "openai:default",
|
||||
@ -286,25 +197,16 @@ describe("markAuthProfileFailure with per-model support", () => {
|
||||
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);
|
||||
await withTempDir(async (tempDir) => {
|
||||
const store = makeStore();
|
||||
saveAuthProfileStore(store, tempDir);
|
||||
|
||||
try {
|
||||
await markAuthProfileFailure({
|
||||
store,
|
||||
profileId: "openai:default",
|
||||
@ -313,62 +215,23 @@ describe("markAuthProfileFailure with per-model support", () => {
|
||||
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: {
|
||||
await withTempDir(async (tempDir) => {
|
||||
const cooldownTime = Date.now() + 60_000;
|
||||
const store = makeStore({
|
||||
"openai:default": { cooldownUntil: cooldownTime },
|
||||
"openai:default:gpt-4": { cooldownUntil: cooldownTime, errorCount: 3 },
|
||||
"openai:default:gpt-3.5": { cooldownUntil: cooldownTime },
|
||||
},
|
||||
};
|
||||
saveAuthProfileStore(store, tempDir);
|
||||
});
|
||||
saveAuthProfileStore(store, tempDir);
|
||||
|
||||
try {
|
||||
await clearAuthProfileCooldown({
|
||||
store,
|
||||
profileId: "openai:default",
|
||||
@ -376,47 +239,27 @@ describe("clearAuthProfileCooldown with per-model support", () => {
|
||||
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: {
|
||||
await withTempDir(async (tempDir) => {
|
||||
const cooldownTime = Date.now() + 60_000;
|
||||
const store = makeStore({
|
||||
"openai:default": { cooldownUntil: cooldownTime, errorCount: 2 },
|
||||
"openai:default:gpt-4": { cooldownUntil: cooldownTime },
|
||||
},
|
||||
};
|
||||
saveAuthProfileStore(store, tempDir);
|
||||
|
||||
try {
|
||||
await clearAuthProfileCooldown({
|
||||
store,
|
||||
profileId: "openai:default",
|
||||
agentDir: tempDir,
|
||||
});
|
||||
saveAuthProfileStore(store, tempDir);
|
||||
|
||||
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