import { describe, it, expect } from 'vitest' import { RATE_WINDOW_MS, RATE_MAX_ATTEMPTS, checkAndRecordAttempt, } from '../../skills/data-portal/template/functions/api/_lib/ratelimit' import type { D1Database } from '../../skills/data-portal/template/functions/api/_lib/types' // Minimal D1 fake: one row per (scope, windowStart), matching the schema's // unique index. The INSERT ... ON CONFLICT DO UPDATE is modelled as an upsert. function fakeDb(seed: Array<{ scope: string; windowStart: number; count: number }> = []) { const rows = [...seed] const db: D1Database = { prepare(query: string) { let bound: unknown[] = [] const stmt = { bind(...v: unknown[]) { bound = v return stmt }, async run() { if (/INSERT/i.test(query)) { const [scope, windowStart] = bound as [string, number] const hit = rows.find((r) => r.scope === scope && r.windowStart === windowStart) if (hit) hit.count++ else rows.push({ scope, windowStart, count: 1 }) } return { meta: { changes: 1 } } }, async all() { return { results: [] } }, async first() { const [scope, windowStart] = bound as [string, number] const hit = rows.find((r) => r.scope === scope && r.windowStart === windowStart) return (hit ? { count: hit.count } : null) as T | null }, } return stmt }, } return { db, rows } } describe('checkAndRecordAttempt', () => { it('allows the first attempt in a window', async () => { const { db } = fakeDb() expect(await checkAndRecordAttempt(db, 'alice', 1_000_000)).toEqual({ allowed: true, count: 1 }) }) it('allows up to the cap', async () => { const w = Math.floor(1_000_000 / RATE_WINDOW_MS) * RATE_WINDOW_MS const { db } = fakeDb([{ scope: 'alice', windowStart: w, count: RATE_MAX_ATTEMPTS - 1 }]) expect((await checkAndRecordAttempt(db, 'alice', 1_000_000)).allowed).toBe(true) }) it('denies past the cap', async () => { const w = Math.floor(1_000_000 / RATE_WINDOW_MS) * RATE_WINDOW_MS const { db } = fakeDb([{ scope: 'alice', windowStart: w, count: RATE_MAX_ATTEMPTS }]) const res = await checkAndRecordAttempt(db, 'alice', 1_000_000) expect(res.allowed).toBe(false) expect(res.count).toBeGreaterThan(RATE_MAX_ATTEMPTS) }) it('starts a fresh count in the next window', async () => { const w = Math.floor(1_000_000 / RATE_WINDOW_MS) * RATE_WINDOW_MS const { db } = fakeDb([{ scope: 'alice', windowStart: w, count: RATE_MAX_ATTEMPTS }]) const later = 1_000_000 + RATE_WINDOW_MS expect((await checkAndRecordAttempt(db, 'alice', later)).allowed).toBe(true) }) it('scopes counts independently per person', async () => { const w = Math.floor(1_000_000 / RATE_WINDOW_MS) * RATE_WINDOW_MS const { db } = fakeDb([{ scope: 'alice', windowStart: w, count: RATE_MAX_ATTEMPTS }]) expect((await checkAndRecordAttempt(db, 'bob', 1_000_000)).allowed).toBe(true) }) })