// Covers the sync on its own thread (2026-10-02): a real worker syncs a koh-made bucket, holds // syncs off while the app works, and applies hidden lists. import { expect, test } from "bun:test"; import { existsSync, renameSync } from "node:fs"; import { mkdtemp } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { startSyncWorker } from "../src/bucket/sync.ts"; import { fileAt, headId } from "../src/mirror/queries.ts"; import { closeMirror, openMirror } from "../src/mirror/store.ts"; import { openKeprDb } from "../src/server/db.ts"; import { makeKohBucket } from "./fixture.ts"; test("the sync worker syncs, holds and hides", async () => { const fx = await makeKohBucket("worked"); const dataDir = await mkdtemp(join(tmpdir(), "kepr-worker-")); const kepr = openKeprDb(dataDir); const syncer = startSyncWorker(dataDir, { kind: "folder", path: fx.bucketDir }); await syncer.syncAll(); const mirror = () => openMirror(join(dataDir, "mirror"), fx.repo); const head = fx.saveIds[0] as string; expect(headId(mirror())).toBe(head); const order: string[] = []; let release = () => {}; const held = syncer.hold(async () => { order.push("held"); await new Promise((resolve) => { release = resolve; }); order.push("released"); }); for (let i = 0; i < 100 && order.length === 0; i++) await Bun.sleep(10); const synced2 = syncer.syncAll().then(() => order.push("synced")); await Bun.sleep(50); expect(order).toEqual(["held"]); release(); await Promise.all([held, synced2]); expect(order).toEqual(["held", "released", "synced"]); kepr.query("UPDATE repos SET hidden_paths = 'secret.txt' WHERE name = ?").run(fx.repo); await syncer.applyHidden([fx.repo]); expect(fileAt(mirror(), head, "secret.txt")).toBeNull(); expect(fileAt(mirror(), head, "src/main.ts")).not.toBeNull(); }); test("a hidden list edited during a rename reaches the repo under its new name", async () => { const fx = await makeKohBucket("before"); const dataDir = await mkdtemp(join(tmpdir(), "kepr-worker-")); const mirrorDir = join(dataDir, "mirror"); const kepr = openKeprDb(dataDir); const syncer = startSyncWorker(dataDir, { kind: "folder", path: fx.bucketDir }); await syncer.syncAll(); const head = fx.saveIds[0] as string; let edited: Promise = Promise.resolve(); await syncer.hold(async () => { kepr.query("UPDATE repos SET hidden_paths = 'secret.txt' WHERE name = 'before'").run(); edited = syncer.applyHidden(["before"]); // What a rename does under its hold: rows and mirror move to the new name. kepr.query("UPDATE repos SET name = 'after' WHERE name = 'before'").run(); kepr.query("INSERT INTO renamed (old, new) VALUES ('before', 'after')").run(); closeMirror(mirrorDir, "before"); for (const suffix of ["", "-wal", "-shm"]) { const path = join(mirrorDir, `before.db${suffix}`); if (existsSync(path)) renameSync(path, join(mirrorDir, `after.db${suffix}`)); } }); await edited; expect(fileAt(openMirror(mirrorDir, "after"), head, "secret.txt")).toBeNull(); expect(existsSync(join(mirrorDir, "before.db"))).toBe(false); });