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 { blake3 } from "@noble/hashes/blake3.js"; import { bytesToHex } from "@noble/hashes/utils.js"; import type { Hono } from "hono"; import { openBucket } from "../src/bucket/reader.ts"; import { syncAll } from "../src/bucket/sync.ts"; import { createApp } from "../src/server/app.ts"; import { openKeprDb } from "../src/server/db.ts"; import { kohWorkspace, platform } from "./fixture.ts"; /** * The joint contract (ยง3 item 4): `koh offer --builds` publishes a release into a folder remote * with the real koh binary, Kepr syncs it, and every route that reads a release agrees with the * bytes the build produced. */ const REPO = "shipped"; const VERSION = "2.4.6"; const NOTES = "a release note long enough to clear forty characters and the twelve word rule easily"; // No newline: a real newline inside koh.build's command would end the line there. const ARTIFACT = "the artifact"; type Latest = { version: string; notes?: string; platforms: Record< string, { url: string; blake3: string; sha256: string; provenance: Record; signature: string; } >; }; describe("a release published by koh offer --builds", () => { let app: Hono; let bucketDir: string; let expected = { blake3: "", sha256: "" }; const platformName = platform(); const fileName = `demo-${VERSION}-${platformName}`; beforeAll(async () => { const space = await kohWorkspace(); bucketDir = space.bucketDir; await writeFile(join(space.repoDir, "VERSION"), `${VERSION}\n`); await writeFile( join(space.repoDir, "koh.build"), [ "# what koh publishes in this test", `command = mkdir -p out && printf '${ARTIFACT}' > out/${fileName}`, "output = out/demo-{version}-{platform}", `platforms = ${platformName}`, "", ].join("\n"), ); space.run("init"); space.run("remote", "folder", bucketDir); space.run("save", "first"); space.run("offer", "--builds", "--notes", NOTES); const bytes = new TextEncoder().encode(ARTIFACT); expected = { blake3: bytesToHex(blake3(bytes)), sha256: new Bun.CryptoHasher("sha256").update(bytes).digest("hex"), }; const dataDir = await mkdtemp(join(tmpdir(), "kepr-kohrelease-")); const kepr = openKeprDb(dataDir); await syncAll({ bucket: openBucket({ kind: "folder", path: bucketDir }), kepr, mirrorDir: join(dataDir, "mirror"), }); kepr.run(`UPDATE repos SET public = 1 WHERE name = '${REPO}'`); app = createApp( { domain: "kepr.test", dataDir, adminPasswordHash: "$argon2id$x", bucket: { kind: "folder", path: bucketDir }, }, kepr, ); }); test("the newest release is the one koh built, with the hashes of its bytes", async () => { const res = await app.request(`/api/${REPO}/releases/latest`); expect(res.status).toBe(200); const latest = (await res.json()) as Latest; expect(latest.version).toBe(VERSION); expect(latest.notes).toBe(NOTES); const file = latest.platforms[platformName] as Latest["platforms"][string]; expect(file).toBeDefined(); expect(file.blake3).toBe(expected.blake3); expect(file.sha256).toBe(expected.sha256); expect(file.signature).toBe(""); expect(file.provenance.version).toBe(VERSION); expect(file.provenance.platform).toBe(platformName); }); test("latest.json, history, version.txt and checksums.txt agree with the record", async () => { const record = JSON.parse( await Bun.file(join(bucketDir, REPO, "releases", VERSION, "release.json")).text(), ) as { version: string; notes: string; files: { name: string; size: number }[] }; expect(record.version).toBe(VERSION); expect(record.notes).toBe(NOTES); expect(record.files.map((file) => file.name)).toEqual([fileName]); expect(record.files[0]?.size).toBe(ARTIFACT.length); const json = (await (await app.request(`/${REPO}/releases/latest.json`)).json()) as Latest; expect(json.version).toBe(VERSION); expect(json.platforms[platformName]?.blake3).toBe(expected.blake3); const history = (await (await app.request(`/api/${REPO}/releases/history`)).json()) as { version: string; notes: string; }[]; expect(history).toEqual([{ version: VERSION, notes: NOTES }]); const versionText = await app.request(`/${REPO}/releases/latest/version.txt`); expect(await versionText.text()).toBe(VERSION); const checksums = await app.request(`/${REPO}/releases/${VERSION}/checksums.txt`); const text = await checksums.text(); expect(text).toContain(`sha256:${expected.sha256} ${REPO}-${platformName}`); expect(text).toContain(`blake3:${expected.blake3} ${REPO}-${platformName}`); }); test("the download redirect serves the bytes koh built", async () => { const res = await app.request(`/${REPO}/releases/${VERSION}/${platformName}`, { redirect: "manual", }); expect(res.status).toBe(302); const target = res.headers.get("location") ?? ""; expect(target).toContain(fileName); const path = target.replace(/^file:\/\//, ""); expect(await Bun.file(path).text()).toBe(ARTIFACT); }); });