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
Some checks failed
Build & Deploy to Staging / Build & Deploy to Staging (push) Failing after 2m56s
This commit is contained in:
parent
f5ec837e20
commit
3aae96fd8a
1 changed files with 120 additions and 0 deletions
120
src/__tests__/keys-cache-hit.test.ts
Normal file
120
src/__tests__/keys-cache-hit.test.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue