install.test.ts

4.6 KB · raw

1import { afterAll, beforeAll, expect, test } from "bun:test";2import { existsSync, readdirSync } from "node:fs";3import { mkdtemp, readFile, stat, writeFile } from "node:fs/promises";4import { tmpdir } from "node:os";5import { join } from "node:path";6import { openBucket } from "../src/bucket/reader.ts";7import { syncAll } from "../src/bucket/sync.ts";8import { createApp } from "../src/server/app.ts";9import { openKeprDb } from "../src/server/db.ts";10import { makeRepos, writeRelease } from "./fixture.ts";1112// install.sh runs for real: Kepr serves on localhost, and its downloads redirect to a stand-in S313// server that serves the bucket folder (a folder source's file:// URLs are not followed by curl).14// The script's https:// addresses are switched to http:// for the run; nothing else changes.1516const PLATFORM = `${process.platform === "darwin" ? "macos" : "linux"}-${process.arch === "arm64" ? "aarch64" : "x86_64"}`;17const PLATFORMS = ["linux-x86_64", "linux-aarch64", "macos-aarch64", "macos-x86_64"];18const binary = (version: string, platform: string) =>19	new TextEncoder().encode(`#!/bin/sh\necho tool ${version} ${platform}\n`);2021let kepr: ReturnType<typeof Bun.serve>;22let s3: ReturnType<typeof Bun.serve>;23let bucketDir: string;24let script: string;2526beforeAll(async () => {27	const repos = await makeRepos(["tool"]);28	bucketDir = repos.bucketDir;29	for (const version of ["1.0.0", "1.1.0"]) {30		const files = PLATFORMS.map((platform) => ({31			platform,32			name: "tool",33			bytes: binary(version, platform),34		}));35		await writeRelease(bucketDir, "tool", { version, save: repos.saves.tool as string, files });36	}37	s3 = Bun.serve({38		port: 0,39		fetch: (req) =>40			new Response(Bun.file(join(bucketDir, new URL(req.url).pathname.slice("/b/".length)))),41	});42	const dataDir = await mkdtemp(join(tmpdir(), "kepr-install-"));43	const db = openKeprDb(dataDir);44	await syncAll({45		bucket: openBucket({ kind: "folder", path: bucketDir }),46		kepr: db,47		mirrorDir: join(dataDir, "mirror"),48		tarballDir: join(dataDir, "tarballs"),49	});50	db.run("UPDATE repos SET public = 1");51	kepr = Bun.serve({ port: 0, fetch: () => new Response("starting") });52	const app = createApp(53		{54			domain: `127.0.0.1:${kepr.port}`,55			dataDir,56			adminPasswordHash: "$argon2id$x",57			bucket: {58				kind: "s3",59				endpoint: `http://127.0.0.1:${s3.port}`,60				name: "b",61				region: "fr-par",62				accessKeyId: "read",63				secretAccessKey: "only",64			},65		},66		db,67	);68	kepr.reload({ fetch: app.fetch });69	const res = await fetch(`http://127.0.0.1:${kepr.port}/tool/install.sh`);70	expect([res.status, res.headers.get("Content-Type")]).toEqual([200, "text/plain; charset=UTF-8"]);71	script = await res.text();72});7374afterAll(() => {75	kepr.stop();76	s3.stop();77});7879async function install(): Promise<{ code: number; stderr: string; dir: string }> {80	const home = await mkdtemp(join(tmpdir(), "kepr-install-home-"));81	const path = join(home, "install.sh");82	await writeFile(path, script.replaceAll("https://", "http://"));83	const dir = join(home, "bin");84	const run = Bun.spawn(["sh", path], {85		cwd: home,86		env: { PATH: process.env.PATH, HOME: home, INSTALL_DIR: dir },87		stderr: "pipe",88		stdout: "pipe",89	});90	return { code: await run.exited, stderr: await new Response(run.stderr).text(), dir };91}9293test("install.sh is POSIX sh naming this repo and domain", async () => {94	const dir = await mkdtemp(join(tmpdir(), "kepr-install-check-"));95	await writeFile(join(dir, "install.sh"), script);96	expect(Bun.spawnSync(["sh", "-n", join(dir, "install.sh")]).exitCode).toBe(0);97	expect(script).toContain("repo=tool\n");98	expect(script).toContain(`base=https://127.0.0.1:${kepr.port}/tool/releases\n`);99});100101test("installs the newest build for this machine into INSTALL_DIR, executable", async () => {102	const { code, stderr, dir } = await install();103	expect([code, stderr]).toEqual([0, ""]);104	const installed = join(dir, "tool");105	expect(await readFile(installed)).toEqual(Buffer.from(binary("1.1.0", PLATFORM)));106	expect((await stat(installed)).mode & 0o111).toBe(0o111);107	expect(readdirSync(dir)).toEqual(["tool"]);108});109110test("refuses a file that doesn't match checksums.txt, installing nothing", async () => {111	await writeFile(join(bucketDir, "tool/releases/1.1.0", PLATFORM, "tool"), "tampered");112	const { code, stderr, dir } = await install();113	expect(code).not.toBe(0);114	expect(stderr).toContain("checksum mismatch; nothing installed");115	expect(existsSync(join(dir, "tool"))).toBe(false);116	expect(readdirSync(dir)).toEqual([]);117});118119test("a repo without releases has no installer", async () => {120	const res = await fetch(`http://127.0.0.1:${kepr.port}/nowhere/install.sh`);121	expect(res.status).toBe(404);122});