import { Database } from "bun:sqlite"; import { afterEach, beforeEach, describe, expect, test } from "bun:test"; import { mkdtempSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { drizzle } from "drizzle-orm/bun-sqlite"; import { migrate } from "drizzle-orm/bun-sqlite/migrator"; import { MIGRATIONS_FOLDER } from "../db/fixtures"; import { timelineEvents } from "../db/schema"; import { getActivityLevel } from "./activity-level"; import { getSyncCoverage } from "./coverage"; let tmpDir: string; let sqlite: Database; // biome-ignore lint/suspicious/noExplicitAny: drizzle bun-sqlite handle let db: any; beforeEach(() => { tmpDir = mkdtempSync(join(tmpdir(), "scribe-coverage-")); sqlite = new Database(join(tmpDir, "scribe.db"), { create: true }); db = drizzle({ client: sqlite }); migrate(db, { migrationsFolder: MIGRATIONS_FOLDER }); }); afterEach(() => { sqlite.close(); rmSync(tmpDir, { recursive: true, force: true }); }); async function insertEvent(timestamp: Date) { await db.insert(timelineEvents).values({ timestamp, source: "manual" }); } describe("getSyncCoverage", () => { test("returns empty weeks and null range for an empty db", async () => { const coverage = await getSyncCoverage(db); expect(coverage.weeks).toEqual([]); expect(coverage.range).toEqual({ minDate: null, maxDate: null, total: 0 }); }); test("groups events by ISO week and reports the overall range", async () => { // Two events in one week, one event in a later week. const wk1a = new Date("2026-01-05T10:00:00Z"); // Monday const wk1b = new Date("2026-01-07T12:00:00Z"); // same week const wk2 = new Date("2026-02-16T09:00:00Z"); // several weeks later await insertEvent(wk1a); await insertEvent(wk1b); await insertEvent(wk2); const coverage = await getSyncCoverage(db); // Two distinct week buckets, ordered ascending by week key. expect(coverage.weeks.length).toBe(2); const totalCounted = coverage.weeks.reduce((s, w) => s + w.count, 0); expect(totalCounted).toBe(3); // The first bucket holds the two same-week events. const firstWeek = coverage.weeks[0]; expect(firstWeek.count).toBe(2); // Range spans the earliest and latest events (whole-db, aggregated in SQL). expect(coverage.range.total).toBe(3); expect(coverage.range.minDate).toBe(wk1a.toISOString()); expect(coverage.range.maxDate).toBe(wk2.toISOString()); }); test("per-week min/max timestamps are unix seconds bounding the bucket", async () => { const early = new Date("2026-03-02T08:00:00Z"); const late = new Date("2026-03-06T20:00:00Z"); // same ISO week await insertEvent(early); await insertEvent(late); const coverage = await getSyncCoverage(db); expect(coverage.weeks.length).toBe(1); const [week] = coverage.weeks; expect(week.minDate).toBe(Math.floor(early.getTime() / 1000)); expect(week.maxDate).toBe(Math.floor(late.getTime() / 1000)); }); }); describe("getActivityLevel", () => { test("zero activity is always level 0", () => { expect(getActivityLevel(0, 0)).toBe(0); expect(getActivityLevel(0, 100)).toBe(0); }); test("buckets counts into quartiles relative to the busiest week", () => { // maxCount = 100 → thresholds at 25 / 50 / 75. expect(getActivityLevel(1, 100)).toBe(1); // < 0.25 expect(getActivityLevel(24, 100)).toBe(1); expect(getActivityLevel(25, 100)).toBe(2); // [0.25, 0.5) expect(getActivityLevel(49, 100)).toBe(2); expect(getActivityLevel(50, 100)).toBe(3); // [0.5, 0.75) expect(getActivityLevel(74, 100)).toBe(3); expect(getActivityLevel(75, 100)).toBe(4); // >= 0.75 expect(getActivityLevel(100, 100)).toBe(4); }); test("the busiest week is the top level", () => { expect(getActivityLevel(7, 7)).toBe(4); }); });