test: improve keys.ts branch coverage — cache-hit paths for createProKey, downgradeByCustomer, findKeyByCustomerId
Some checks failed
Build & Deploy to Staging / Build & Deploy to Staging (push) Failing after 2m56s

This commit is contained in:
OpenClaw Subagent 2026-03-14 14:18:50 +01:00
parent f5ec837e20
commit 3aae96fd8a

View file

@ -0,0 +1,120 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
vi.unmock("../services/keys.js");
vi.mock("../services/db.js", () => ({
default: { query: vi.fn(), connect: vi.fn(), on: vi.fn(), end: vi.fn() },
pool: { query: vi.fn(), connect: vi.fn(), on: vi.fn(), end: vi.fn() },
queryWithRetry: vi.fn().mockResolvedValue({ rows: [], rowCount: 0 }),
connectWithRetry: vi.fn().mockResolvedValue(undefined),
initDatabase: vi.fn().mockResolvedValue(undefined),
cleanupStaleData: vi.fn(),
isTransientError: vi.fn(),
}));
vi.mock("../services/logger.js", () => ({
default: { info: vi.fn(), error: vi.fn(), warn: vi.fn(), debug: vi.fn() },
}));
import { queryWithRetry } from "../services/db.js";
import { loadKeys, createProKey, downgradeByCustomer, findKeyByCustomerId, getAllKeys } from "../services/keys.js";
const mockQuery = vi.mocked(queryWithRetry);
describe("keys cache-hit paths", () => {
beforeEach(async () => {
vi.clearAllMocks();
// Reset cache via loadKeys with empty result
mockQuery.mockResolvedValueOnce({ rows: [], rowCount: 0 } as any);
await loadKeys();
vi.clearAllMocks();
});
describe("createProKey - cache UPSERT update (line 142)", () => {
it("updates existing cache entry on second call with same stripeCustomerId", async () => {
// First call: creates entry and pushes to cache (else branch)
const firstResult = {
key: "df_pro_first",
tier: "pro",
email: "first@test.com",
created_at: "2026-01-01T00:00:00.000Z",
stripe_customer_id: "cus_repeat",
};
mockQuery.mockResolvedValueOnce({ rows: [firstResult], rowCount: 1 } as any);
await createProKey("first@test.com", "cus_repeat");
// Second call: same stripeCustomerId → cacheIdx >= 0 → updates in place (line 142)
const secondResult = {
key: "df_pro_first",
tier: "pro",
email: "first@test.com",
created_at: "2026-01-01T00:00:00.000Z",
stripe_customer_id: "cus_repeat",
};
mockQuery.mockResolvedValueOnce({ rows: [secondResult], rowCount: 1 } as any);
const result = await createProKey("second@test.com", "cus_repeat");
expect(result.key).toBe("df_pro_first");
// Cache should have exactly 1 entry for this customer (updated, not duplicated)
const allKeys = getAllKeys();
const matching = allKeys.filter((k) => k.stripeCustomerId === "cus_repeat");
expect(matching).toHaveLength(1);
});
});
describe("downgradeByCustomer - cache HIT (lines 153-155)", () => {
it("downgrades cached entry to free tier", async () => {
// First, populate cache via createProKey
const entry = {
key: "df_pro_downgrade",
tier: "pro",
email: "downgrade@test.com",
created_at: "2026-01-01T00:00:00.000Z",
stripe_customer_id: "cus_downgrade",
};
mockQuery.mockResolvedValueOnce({ rows: [entry], rowCount: 1 } as any);
await createProKey("downgrade@test.com", "cus_downgrade");
vi.clearAllMocks();
// Now downgrade — entry is in cache
mockQuery.mockResolvedValueOnce({ rows: [], rowCount: 1 } as any); // UPDATE
const result = await downgradeByCustomer("cus_downgrade");
expect(result).toBe(true);
expect(mockQuery).toHaveBeenCalledWith(
expect.stringContaining("UPDATE"),
expect.arrayContaining(["cus_downgrade"])
);
const allKeys = getAllKeys();
const found = allKeys.find((k) => k.stripeCustomerId === "cus_downgrade");
expect(found?.tier).toBe("free");
});
});
describe("findKeyByCustomerId (line 175)", () => {
it("finds key by stripe customer ID via DB lookup", async () => {
mockQuery.mockResolvedValueOnce({
rows: [{
key: "df_pro_found",
tier: "pro",
email: "found@test.com",
created_at: "2026-01-01T00:00:00.000Z",
stripe_customer_id: "cus_find_me",
}],
rowCount: 1,
} as any);
const result = await findKeyByCustomerId("cus_find_me");
expect(result).not.toBeNull();
expect(result!.key).toBe("df_pro_found");
});
it("returns null for unknown customer ID", async () => {
mockQuery.mockResolvedValueOnce({ rows: [], rowCount: 0 } as any);
const result = await findKeyByCustomerId("cus_unknown");
expect(result).toBeNull();
});
});
});