diff --git a/scripts/src/workdir-normalize.test.ts b/scripts/src/workdir-normalize.test.ts new file mode 100644 index 00000000..17c5e3ce --- /dev/null +++ b/scripts/src/workdir-normalize.test.ts @@ -0,0 +1,76 @@ +import { describe, expect, test } from "bun:test"; + +const WORKDIR_MAX_LENGTH = 512; + +function normalizeWorkdirPath(value: unknown, providerId: string): string { + const raw = typeof value === "string" ? value.trim() : ""; + if (raw.length === 0) throw new Error("workdir path is required"); + if (raw.length > WORKDIR_MAX_LENGTH) throw new Error(`workdir path must be ${WORKDIR_MAX_LENGTH} characters or fewer`); + if (raw.includes("\u0000")) throw new Error("workdir path contains an invalid character"); + if (raw.startsWith("/")) return raw.replace(/\/+$/u, "") || "/"; + const base = providerId === "main-server" ? "/workspace" : "/home/ubuntu"; + return `${base.replace(/\/+$/u, "")}/${raw}`.replace(/\/+$/u, "") || "/"; +} + +function normalizeWorkdirProviderId(value: string): string { + const trimmed = value.trim(); + if (trimmed.length === 0) return "main-server"; + return trimmed; +} + +describe("workdir path normalization contract", () => { + test("absolute paths are preserved as-is", () => { + expect(normalizeWorkdirPath("/workspace/met_nonlinear", "D601")).toBe("/workspace/met_nonlinear"); + expect(normalizeWorkdirPath("/home/ubuntu/big-paper3", "D518")).toBe("/home/ubuntu/big-paper3"); + }); + +test("relative paths are prefixed with provider default workdir", () => { + expect(normalizeWorkdirPath("met_nonlinear", "D601")).toBe("/home/ubuntu/met_nonlinear"); + expect(normalizeWorkdirPath("met_nonlinear", "D518")).toBe("/home/ubuntu/met_nonlinear"); + expect(normalizeWorkdirPath("met_nonlinear", "main-server")).toBe("/workspace/met_nonlinear"); + }); + + test("D518 and D601 both use /home/ubuntu as base", () => { + expect(normalizeWorkdirPath("big_paper", "D518")).toBe("/home/ubuntu/big_paper"); + expect(normalizeWorkdirPath("big_paper", "D601")).toBe("/home/ubuntu/big_paper"); + }); + + test("trailing slashes are stripped", () => { + expect(normalizeWorkdirPath("/workspace/met_nonlinear///", "D601")).toBe("/workspace/met_nonlinear"); + }); + + test("empty string throws", () => { + expect(() => normalizeWorkdirPath("", "D601")).toThrow("workdir path is required"); + }); + + test("null character throws", () => { + expect(() => normalizeWorkdirPath("/workspace/test\u0000bad", "D601")).toThrow("workdir path contains an invalid character"); + }); + + test("paths exceeding max length throw", () => { + const longPath = "/" + "a".repeat(WORKDIR_MAX_LENGTH); + expect(() => normalizeWorkdirPath(longPath, "D601")).toThrow(`workdir path must be ${WORKDIR_MAX_LENGTH} characters or fewer`); + }); + + test("root path normalizes to /", () => { + expect(normalizeWorkdirPath("/", "D601")).toBe("/"); + }); + + test("D518 provider maps to /home/ubuntu", () => { + expect(normalizeWorkdirPath("big_paper", "D518")).toBe("/home/ubuntu/big_paper"); + expect(normalizeWorkdirPath("/mnt/d/work/big_paper", "D518")).toBe("/mnt/d/work/big_paper"); + }); +}); + +describe("workdir provider normalization contract", () => { + test("empty providerId defaults to main-server", () => { + expect(normalizeWorkdirProviderId("")).toBe("main-server"); + expect(normalizeWorkdirProviderId(" ")).toBe("main-server"); + }); + + test("valid providerIds are preserved", () => { + expect(normalizeWorkdirProviderId("D518")).toBe("D518"); + expect(normalizeWorkdirProviderId("D601")).toBe("D601"); + expect(normalizeWorkdirProviderId("main-server")).toBe("main-server"); + }); +}); \ No newline at end of file