import { afterEach, describe, expect, test } from "bun:test"; import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"; import { join } from "node:path"; import { tmpdir } from "node:os"; import { readCompose, resolveComposeFile } from "../../lib/yaml"; const temporaryDirectories: string[] = []; async function temporaryDirectory(): Promise { const path = await mkdtemp(join(tmpdir(), "kuber-test-")); temporaryDirectories.push(path); return path; } afterEach(async () => { await Promise.all( temporaryDirectories .splice(0) .map((path) => rm(path, { recursive: true, force: true })), ); }); describe("Compose file discovery", () => { test("finds supported names and chooses lexical precedence", async () => { const directory = await temporaryDirectory(); await Promise.all([ writeFile(join(directory, "docker-compose.yml"), "services: {}\n"), writeFile(join(directory, "compose.yaml"), "services: {}\n"), writeFile(join(directory, "ignored.yml"), "services: {}\n"), ]); expect(await resolveComposeFile(directory)).toBe( join(directory, "compose.yaml"), ); }); test("ignores directories with Compose-looking names", async () => { const directory = await temporaryDirectory(); await mkdir(join(directory, "compose.yml")); expect(await resolveComposeFile(directory)).toBeUndefined(); }); test("returns undefined when no supported file exists", async () => { const directory = await temporaryDirectory(); await writeFile(join(directory, "stack.yml"), "services: {}\n"); expect(await resolveComposeFile(directory)).toBeUndefined(); }); }); describe("Compose parsing", () => { test("accepts the generated marker and preserves x- extensions", async () => { const directory = await temporaryDirectory(); const path = join(directory, "compose.yml"); await writeFile( path, "managedBy: kuber # See https://npmx.dev/@dmgnr/kuber for documentation.\nname: compose-project\nx-note: retained\nservices:\n app:\n image: nginx:latest\n", ); expect(await readCompose(path)).toMatchObject({ managedBy: "kuber", name: "compose-project", "x-note": "retained", services: { app: { image: "nginx:latest" } }, }); }); test("rejects incorrect managedBy values", async () => { const directory = await temporaryDirectory(); const path = join(directory, "compose.yml"); for (const marker of ["other", "42"]) { await writeFile(path, `managedBy: ${marker}\nservices: {}\n`); await expect(readCompose(path)).rejects.toThrow(); } }); test("rejects other unknown top-level properties", async () => { const directory = await temporaryDirectory(); const path = join(directory, "compose.yml"); await writeFile(path, "managedBy: kuber\nunknown: value\nservices: {}\n"); await expect(readCompose(path)).rejects.toThrow(); }); test("parses and validates a minimal Compose project", async () => { const directory = await temporaryDirectory(); const path = join(directory, "compose.yml"); await writeFile( path, "name: compose-project\nservices:\n app:\n image: nginx:latest\n", ); expect(await readCompose(path)).toMatchObject({ name: "compose-project", services: { app: { image: "nginx:latest" } }, }); }); test("rejects malformed YAML", async () => { const directory = await temporaryDirectory(); const path = join(directory, "compose.yml"); await writeFile(path, "services: [\n"); await expect(readCompose(path)).rejects.toThrow(); }); test("rejects schema-invalid service values", async () => { const directory = await temporaryDirectory(); const path = join(directory, "compose.yml"); await writeFile(path, "services:\n app: 42\n"); await expect(readCompose(path)).rejects.toThrow(); }); });