install.test.ts

4.5 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	});49	db.run("UPDATE repos SET public = 1");50	kepr = Bun.serve({ port: 0, fetch: () => new Response("starting") });51	const app = createApp(52		{53			domain: `127.0.0.1:${kepr.port}`,54			dataDir,55			adminPasswordHash: "$argon2id$x",56			bucket: {57				kind: "s3",58				endpoint: `http://127.0.0.1:${s3.port}`,59				name: "b",60				region: "fr-par",61				accessKeyId: "read",62				secretAccessKey: "only",63			},64		},65		db,66	);67	kepr.reload({ fetch: app.fetch });68	const res = await fetch(`http://127.0.0.1:${kepr.port}/tool/install.sh`);69	expect([res.status, res.headers.get("Content-Type")]).toEqual([200, "text/plain; charset=UTF-8"]);70	script = await res.text();71});7273afterAll(() => {74	kepr.stop();75	s3.stop();76});7778async function install(): Promise<{ code: number; stderr: string; dir: string }> {79	const home = await mkdtemp(join(tmpdir(), "kepr-install-home-"));80	const path = join(home, "install.sh");81	await writeFile(path, script.replaceAll("https://", "http://"));82	const dir = join(home, "bin");83	const run = Bun.spawn(["sh", path], {84		cwd: home,85		env: { PATH: process.env.PATH, HOME: home, INSTALL_DIR: dir },86		stderr: "pipe",87		stdout: "pipe",88	});89	return { code: await run.exited, stderr: await new Response(run.stderr).text(), dir };90}9192test("install.sh is POSIX sh naming this repo and domain", async () => {93	const dir = await mkdtemp(join(tmpdir(), "kepr-install-check-"));94	await writeFile(join(dir, "install.sh"), script);95	expect(Bun.spawnSync(["sh", "-n", join(dir, "install.sh")]).exitCode).toBe(0);96	expect(script).toContain("repo=tool\n");97	expect(script).toContain(`base=https://127.0.0.1:${kepr.port}/tool/releases\n`);98});99100test("installs the newest build for this machine into INSTALL_DIR, executable", async () => {101	const { code, stderr, dir } = await install();102	expect([code, stderr]).toEqual([0, ""]);103	const installed = join(dir, "tool");104	expect(await readFile(installed)).toEqual(Buffer.from(binary("1.1.0", PLATFORM)));105	expect((await stat(installed)).mode & 0o111).toBe(0o111);106	expect(readdirSync(dir)).toEqual(["tool"]);107});108109test("refuses a file that doesn't match checksums.txt, installing nothing", async () => {110	await writeFile(join(bucketDir, "tool/releases/1.1.0", PLATFORM, "tool"), "tampered");111	const { code, stderr, dir } = await install();112	expect(code).not.toBe(0);113	expect(stderr).toContain("checksum mismatch; nothing installed");114	expect(existsSync(join(dir, "tool"))).toBe(false);115	expect(readdirSync(dir)).toEqual([]);116});117118test("a repo without releases has no installer", async () => {119	const res = await fetch(`http://127.0.0.1:${kepr.port}/nowhere/install.sh`);120	expect(res.status).toBe(404);121});