1import { beforeAll, describe, expect, test } from "bun:test";2import { mkdtemp, writeFile } from "node:fs/promises";3import { tmpdir } from "node:os";4import { join } from "node:path";5import { openBucket } from "../src/bucket/reader.ts";6import { type SyncTarget, syncAll } from "../src/bucket/sync.ts";7import { openMirror } from "../src/mirror/store.ts";8import { compareVersions, visibleReleases } from "../src/releases/releases.ts";9import { openKeprDb } from "../src/server/db.ts";10import { makeRepos, writeRelease } from "./fixture.ts";1112const bytes = (text: string) => new TextEncoder().encode(text);1314describe("release reading in sync", () => {15 let bucketDir: string;16 let save: string;17 let target: SyncTarget;18 const releases = () =>19 visibleReleases(target.kepr, openMirror(target.mirrorDir, "app"), "app").map((r) => r.version);20 const lastError = () =>21 (target.kepr.query("SELECT last_sync_error FROM repos").get() as { last_sync_error: string })22 .last_sync_error;2324 beforeAll(async () => {25 const repos = await makeRepos(["app"]);26 bucketDir = repos.bucketDir;27 save = repos.saves.app as string;28 const dataDir = await mkdtemp(join(tmpdir(), "kepr-releases-"));29 target = {30 bucket: openBucket({ kind: "folder", path: bucketDir }),31 kepr: openKeprDb(dataDir),32 mirrorDir: join(dataDir, "mirror"),33 tarballDir: join(dataDir, "tarballs"),34 };35 for (const version of ["1.9.0", "1.10.0", "1.2.3"]) {36 const files = [{ platform: "linux-x86_64", name: "app", bytes: bytes(version) }];37 await writeRelease(bucketDir, "app", { version, save, files }, version === "1.10.0");38 }39 await syncAll(target);40 });4142 test("reads every release.json, newest first by number", () => {43 expect(lastError()).toBeNull();44 expect(releases()).toEqual(["1.10.0", "1.9.0", "1.2.3"]);45 });4647 test("a version folder without release.json does not exist, and nothing is re-read until latest moves", async () => {48 await writeFile(join(bucketDir, "app/releases/2.0.0.tmp"), "half-written");49 const orphan = join(bucketDir, "app/releases/3.0.0/linux-x86_64");50 await Bun.write(join(orphan, "app"), "no record yet");51 await writeRelease(52 bucketDir,53 "app",54 {55 version: "1.11.0",56 save,57 files: [{ platform: "linux-x86_64", name: "app", bytes: bytes("x") }],58 },59 false,60 );61 await syncAll(target);62 expect(releases()).toEqual(["1.10.0", "1.9.0", "1.2.3"]);63 await writeFile(join(bucketDir, "app/releases/latest"), "1.11.0");64 await syncAll(target);65 expect(releases()).toEqual(["1.11.0", "1.10.0", "1.9.0", "1.2.3"]);66 });6768 test("hidden versions drop out and a notes override replaces the record's notes", () => {69 target.kepr.run(70 "INSERT INTO release_settings (repo, version, hidden, notes) VALUES ('app', '1.11.0', 1, NULL), ('app', '1.9.0', 0, 'Operator notes.')",71 );72 const shown = visibleReleases(target.kepr, openMirror(target.mirrorDir, "app"), "app");73 expect(shown.map((r) => [r.version, r.notes])).toEqual([74 ["1.10.0", ""],75 ["1.9.0", "Operator notes."],76 ["1.2.3", ""],77 ]);78 target.kepr.run("DELETE FROM release_settings");79 });8081 test("a release record that breaks §11.2 is the repo's last sync error", async () => {82 await writeRelease(bucketDir, "app", {83 version: "1.12.0",84 save,85 files: [{ platform: "plan9-mips", name: "app", bytes: bytes("x") }],86 });87 await syncAll(target);88 expect(lastError()).toContain("release record does not match");89 await writeRelease(bucketDir, "app", {90 version: "1.12.0",91 save,92 files: [{ platform: "linux-x86_64", name: "../app", bytes: bytes("x") }],93 });94 await syncAll(target);95 expect(lastError()).toContain("release record does not match");96 });97});9899test("versions compare as numbers, segment by segment", () => {100 const sorted = ["1.10.0", "1.9.9", "2", "1.9", "0.0.0", "1.10", "10.0.0"].sort(compareVersions);101 expect(sorted).toEqual(["0.0.0", "1.9", "1.9.9", "1.10", "1.10.0", "2", "10.0.0"]);102});