docfast/src/__tests__/keys.test.ts
Hoid 1aea9c872c
All checks were successful
Build & Deploy to Staging / Build & Deploy to Staging (push) Successful in 12m13s
test: add auth, rate-limit, and keys service tests
2026-02-26 10:03:31 +00:00

108 lines
3.6 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from "vitest";
// Unmock keys service — we want to test the real implementation
vi.unmock("../services/keys.js");
// DB is still mocked by setup.ts
import { queryWithRetry } from "../services/db.js";
describe("keys service", () => {
let keys: typeof import("../services/keys.js");
beforeEach(async () => {
vi.clearAllMocks();
vi.resetModules();
// Re-import to get fresh cache
keys = await import("../services/keys.js");
});
describe("after loadKeys", () => {
const mockRows = [
{ key: "df_free_abc", tier: "free", email: "a@b.com", created_at: "2025-01-01T00:00:00Z", stripe_customer_id: null },
{ key: "df_pro_xyz", tier: "pro", email: "pro@b.com", created_at: "2025-01-01T00:00:00Z", stripe_customer_id: "cus_123" },
];
beforeEach(async () => {
vi.mocked(queryWithRetry).mockResolvedValueOnce({ rows: mockRows, rowCount: 2 } as any);
await keys.loadKeys();
});
it("isValidKey returns true for cached keys", () => {
expect(keys.isValidKey("df_free_abc")).toBe(true);
expect(keys.isValidKey("df_pro_xyz")).toBe(true);
});
it("isValidKey returns false for unknown keys", () => {
expect(keys.isValidKey("unknown")).toBe(false);
});
it("isProKey returns true for pro tier, false for free", () => {
expect(keys.isProKey("df_pro_xyz")).toBe(true);
expect(keys.isProKey("df_free_abc")).toBe(false);
});
it("getKeyInfo returns correct ApiKey object", () => {
const info = keys.getKeyInfo("df_pro_xyz");
expect(info).toEqual({
key: "df_pro_xyz",
tier: "pro",
email: "pro@b.com",
createdAt: "2025-01-01T00:00:00Z",
stripeCustomerId: "cus_123",
});
});
it("getKeyInfo returns undefined for unknown key", () => {
expect(keys.getKeyInfo("nope")).toBeUndefined();
});
});
describe("createFreeKey", () => {
beforeEach(async () => {
vi.mocked(queryWithRetry).mockResolvedValueOnce({ rows: [], rowCount: 0 } as any);
await keys.loadKeys();
});
it("creates key with df_free prefix", async () => {
vi.mocked(queryWithRetry).mockResolvedValueOnce({ rows: [], rowCount: 1 } as any);
const result = await keys.createFreeKey("new@test.com");
expect(result.key).toMatch(/^df_free_/);
expect(result.tier).toBe("free");
expect(result.email).toBe("new@test.com");
});
it("returns existing key for same email", async () => {
vi.mocked(queryWithRetry).mockResolvedValueOnce({ rows: [], rowCount: 1 } as any);
const first = await keys.createFreeKey("dup@test.com");
const second = await keys.createFreeKey("dup@test.com");
expect(second.key).toBe(first.key);
});
});
describe("createProKey", () => {
beforeEach(async () => {
vi.mocked(queryWithRetry).mockResolvedValueOnce({ rows: [], rowCount: 0 } as any);
await keys.loadKeys();
});
it("uses UPSERT and returns key", async () => {
const returnedRow = {
key: "df_pro_newkey",
tier: "pro",
email: "pro@test.com",
created_at: "2025-06-01T00:00:00Z",
stripe_customer_id: "cus_new",
};
vi.mocked(queryWithRetry).mockResolvedValueOnce({ rows: [returnedRow], rowCount: 1 } as any);
const result = await keys.createProKey("pro@test.com", "cus_new");
expect(result.tier).toBe("pro");
expect(result.stripeCustomerId).toBe("cus_new");
const call = vi.mocked(queryWithRetry).mock.calls.find(
(c) => typeof c[0] === "string" && c[0].includes("ON CONFLICT")
);
expect(call).toBeTruthy();
});
});
});