import { beforeEach, describe, expect, mock, test } from "bun:test"; import { Database } from "bun:sqlite"; import { readFileSync } from "node:fs"; import { fakeD1 } from "../metering/fake-d1.test-helper"; let d1 = fakeD1(); const kv = new Map(); mock.module("cloudflare:workers", () => ({ env: { get DB() { return d1.binding; }, KV: { get: async (key: string) => kv.get(key) ?? null, put: async (key: string, value: string) => void kv.set(key, value), }, }, waitUntil: () => undefined, })); const { REPO_SCHEMA } = await import("../scoped-db/schema.gen"); const { forgetSiteDirectory, publishSiteDirectory, siteDirectoryStats } = await import("./directory"); const NOW = 1_000_000_000_000; const WEEK = NOW - 7 * 24 * 3600_000; function scope() { const db = new Database(":memory:"); for (const statement of REPO_SCHEMA) db.run(statement); const sql = { exec: (query: string, ...params: Array) => { const rows = db.query(query).all(...params) as never[]; return { toArray: () => rows }; }, }; const site = (id: string, fields: Record) => { const row = { id, label: id, source: "branch", created_at: 0, updated_at: 0, ...fields }; db.query( `INSERT INTO sites (${Object.keys(row).join(", ")}) VALUES (${Object.keys(row) .map(() => "?") .join(", ")})`, ).run(...Object.values(row)); }; return { db, sql, site }; } beforeEach(() => { d1 = fakeD1(); d1.db.exec(readFileSync(new URL("../../../migrations/0062_site_directory.sql", import.meta.url), "utf8")); d1.db.exec("CREATE TABLE abuse_reports (id TEXT, status TEXT, site_id TEXT)"); kv.clear(); }); describe("site directory", () => { test("each scope replaces its rows, and the admin counts read them in one query", async () => { const repo = scope(); repo.site("s1", { owner_id: "o1", repo_id: "r1", active_deployment_id: "d1" }); await publishSiteDirectory("r1", repo.sql, NOW); const upload = scope(); upload.site("s2", { owner_id: "o1", paused_at: NOW - 1000, disabled_reason: "paused" }); await publishSiteDirectory("site:s2", upload.sql, NOW); const scratch = scope(); scratch.site("s3", { scratch: 1, expires_at: NOW + 1000, disabled_reason: "abuse" }); await publishSiteDirectory("site:s3", scratch.sql, NOW); d1.db.run("INSERT INTO abuse_reports VALUES ('a1', 'open', 's2'), ('a2', 'closed', 's1')"); const stats = await siteDirectoryStats(NOW, WEEK); expect(stats.totals).toEqual({ total: 3, active: 1, paused: 1, disabled: 1, scratch: 1, autoPausedWeek: 1 }); expect(stats.sitesByOwner).toEqual(new Map([["o1", 2]])); expect(stats.reportsByOwner).toEqual(new Map([["o1", 1]])); repo.db.run("UPDATE sites SET owner_id = 'o2' WHERE id = 's1'"); await publishSiteDirectory("r1", repo.sql, NOW); await forgetSiteDirectory("s3"); const after = await siteDirectoryStats(NOW, WEEK); expect(after.totals.total).toBe(2); expect(after.sitesByOwner).toEqual( new Map([ ["o1", 1], ["o2", 1], ]), ); }); });