import type { Database } from "bun:sqlite"; import { beforeAll, describe, expect, test } from "bun:test"; import { mkdtemp, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import type { Hono } from "hono"; import { openBucket } from "../src/bucket/reader.ts"; import { type SyncTarget, syncAll } from "../src/bucket/sync.ts"; import { openMirror } from "../src/mirror/store.ts"; import { compareVersions, visibleReleases } from "../src/releases/releases.ts"; import { createApp } from "../src/server/app.ts"; import { openKeprDb } from "../src/server/db.ts"; import { makeRepos, writeRelease } from "./fixture.ts"; const bytes = (text: string) => new TextEncoder().encode(text); describe("release reading in sync", () => { let bucketDir: string; let save: string; let target: SyncTarget; const releases = () => visibleReleases(target.kepr, openMirror(target.mirrorDir, "app"), "app").map((r) => r.version); const lastError = () => (target.kepr.query("SELECT last_sync_error FROM repos").get() as { last_sync_error: string }) .last_sync_error; beforeAll(async () => { const repos = await makeRepos(["app"]); bucketDir = repos.bucketDir; save = repos.saves.app as string; const dataDir = await mkdtemp(join(tmpdir(), "kepr-releases-")); target = { bucket: openBucket({ kind: "folder", path: bucketDir }), kepr: openKeprDb(dataDir), mirrorDir: join(dataDir, "mirror"), tarballDir: join(dataDir, "tarballs"), }; for (const version of ["1.9.0", "1.10.0", "1.2.3"]) { const files = [{ platform: "linux-x86_64", name: "app", bytes: bytes(version) }]; await writeRelease(bucketDir, "app", { version, save, files }, version === "1.10.0"); } await syncAll(target); }); test("reads every release.json, newest first by number", () => { expect(lastError()).toBeNull(); expect(releases()).toEqual(["1.10.0", "1.9.0", "1.2.3"]); }); test("a version folder without release.json does not exist, and nothing is re-read until latest moves", async () => { await writeFile(join(bucketDir, "app/releases/2.0.0.tmp"), "half-written"); const orphan = join(bucketDir, "app/releases/3.0.0/linux-x86_64"); await Bun.write(join(orphan, "app"), "no record yet"); await writeRelease( bucketDir, "app", { version: "1.11.0", save, files: [{ platform: "linux-x86_64", name: "app", bytes: bytes("x") }], }, false, ); await syncAll(target); expect(releases()).toEqual(["1.10.0", "1.9.0", "1.2.3"]); await writeFile(join(bucketDir, "app/releases/latest"), "1.11.0"); await syncAll(target); expect(releases()).toEqual(["1.11.0", "1.10.0", "1.9.0", "1.2.3"]); }); test("hidden versions drop out and a notes override replaces the record's notes", () => { target.kepr.run( "INSERT INTO release_settings (repo, version, hidden, notes) VALUES ('app', '1.11.0', 1, NULL), ('app', '1.9.0', 0, 'Operator notes.')", ); const shown = visibleReleases(target.kepr, openMirror(target.mirrorDir, "app"), "app"); expect(shown.map((r) => [r.version, r.notes])).toEqual([ ["1.10.0", ""], ["1.9.0", "Operator notes."], ["1.2.3", ""], ]); target.kepr.run("DELETE FROM release_settings"); }); test("a release record that breaks §11.2 is the repo's last sync error", async () => { await writeRelease(bucketDir, "app", { version: "1.12.0", save, files: [{ platform: "plan9-mips", name: "app", bytes: bytes("x") }], }); await syncAll(target); expect(lastError()).toContain("release record does not match"); await writeRelease(bucketDir, "app", { version: "1.12.0", save, files: [{ platform: "linux-x86_64", name: "../app", bytes: bytes("x") }], }); await syncAll(target); expect(lastError()).toContain("release record does not match"); }); }); test("versions compare as numbers, segment by segment", () => { const sorted = ["1.10.0", "1.9.9", "2", "1.9", "0.0.0", "1.10", "10.0.0"].sort(compareVersions); expect(sorted).toEqual(["0.0.0", "1.9", "1.9.9", "1.10", "1.10.0", "2", "10.0.0"]); }); describe("the builds panel", () => { let app: Hono; let saves: Record; const get = (path: string) => app.request(path); beforeAll(async () => { const repos = await makeRepos(["tool", "secret"]); saves = repos.saves; const tool = saves.tool as string; const b = repos.bucketDir; await writeRelease(b, "tool", { version: "0.9.0", save: tool, notes: "## Fixes\n\n- the *first* fix