import { afterEach, describe, expect, test } from "bun:test";
import { mkdirSync, mkdtempSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { readFactoryArtifact } from "./server";

const dirs: string[] = [];
afterEach(() => { while (dirs.length) rmSync(dirs.pop()!, { recursive: true, force: true }); });

describe("factory artifact reader", () => {
  test("serves contained text and tails oversized files", async () => {
    const root = mkdtempSync(join(tmpdir(), "factory-data-"));
    dirs.push(root);
    mkdirSync(join(root, "logs"));
    writeFileSync(join(root, "logs", "small.txt"), "hello");
    writeFileSync(join(root, "logs", "large.txt"), `head${"x".repeat(1024 * 1024)}tail`);
    const small = await readFactoryArtifact(root, "logs/small.txt");
    expect(small.status).toBe(200);
    expect(small.headers.get("content-type")).toContain("text/plain");
    expect(await small.text()).toBe("hello");
    expect(small.headers.get("x-overdeck-artifact-size")).toBe("5");
    const tail = await readFactoryArtifact(root, "logs/large.txt");
    expect(tail.status).toBe(200);
    expect(tail.headers.get("content-length")).toBe(String(1024 * 1024));
    expect(tail.headers.get("x-overdeck-artifact-size")).toBe(String(1024 * 1024 + 8));
    expect(tail.headers.get("x-overdeck-artifact-tail")).toBe("true");
    expect(tail.headers.get("x-overdeck-artifact-truncated")).toBe("true");
    expect((await tail.text()).endsWith("tail")).toBe(true);

    const range = await readFactoryArtifact(root, "logs/large.txt", "bytes=2-7");
    expect(range.status).toBe(206);
    expect(range.headers.get("content-length")).toBe("6");
    expect(range.headers.get("content-range")).toBe(`bytes 2-7/${1024 * 1024 + 8}`);
    expect(range.headers.get("x-overdeck-artifact-size")).toBe(String(1024 * 1024 + 8));
    expect(await range.text()).toBe("adxxxx");
  });

  test("rejects traversal and symlinks escaping the data directory", async () => {
    const parent = mkdtempSync(join(tmpdir(), "factory-boundary-"));
    dirs.push(parent);
    const root = join(parent, "data");
    mkdirSync(root);
    const outside = join(parent, "secret.txt");
    writeFileSync(outside, "secret");
    symlinkSync(outside, join(root, "escape.txt"));
    expect((await readFactoryArtifact(root, "../secret.txt")).status).toBe(403);
    expect((await readFactoryArtifact(root, "escape.txt")).status).toBe(403);
  });
});
