120 lines
4.0 KiB
TypeScript
120 lines
4.0 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
|
import {
|
|
listDockerfileTemplates,
|
|
renderDockerfileTemplate,
|
|
templateIds,
|
|
type DockerfileTemplateId,
|
|
} from "../../lib/scaffold-templates";
|
|
|
|
// Parse each Dockerfile's instructions and JSON commands to catch malformed
|
|
// templates without requiring a Docker daemon in the test environment.
|
|
function parseDockerfile(source: string): {
|
|
stages: string[];
|
|
commands: string[][];
|
|
} {
|
|
const stages: string[] = [];
|
|
const commands: string[][] = [];
|
|
let stageCount = 0;
|
|
for (const line of source.split("\n")) {
|
|
const text = line.trim();
|
|
if (!text || text.startsWith("#")) continue;
|
|
const match = /^([A-Z]+)\s+(.+)$/.exec(text);
|
|
expect(match, `Invalid Dockerfile instruction: ${text}`).not.toBeNull();
|
|
const [, instruction, argument] = match!;
|
|
expect([
|
|
"FROM",
|
|
"WORKDIR",
|
|
"COPY",
|
|
"RUN",
|
|
"ENV",
|
|
"USER",
|
|
"EXPOSE",
|
|
"CMD",
|
|
"ENTRYPOINT",
|
|
]).toContain(instruction!);
|
|
if (instruction === "FROM") {
|
|
stageCount++;
|
|
const alias = /\s+AS\s+(\w+)$/i.exec(argument!)?.[1];
|
|
if (alias) stages.push(alias);
|
|
} else {
|
|
expect(stageCount, `${instruction} must follow FROM`).toBeGreaterThan(0);
|
|
}
|
|
if (instruction === "COPY") {
|
|
const from = /^--from=(\w+)\s/.exec(argument!)?.[1];
|
|
if (from) expect(stages).toContain(from);
|
|
}
|
|
if (instruction === "CMD" || instruction === "ENTRYPOINT") {
|
|
const parsed: unknown = JSON.parse(argument!);
|
|
expect(Array.isArray(parsed)).toBe(true);
|
|
expect(
|
|
(parsed as unknown[]).every((value) => typeof value === "string"),
|
|
).toBe(true);
|
|
commands.push(parsed as string[]);
|
|
}
|
|
}
|
|
expect(stageCount).toBeGreaterThan(0);
|
|
expect(commands).toHaveLength(1);
|
|
return { stages, commands };
|
|
}
|
|
|
|
describe("embedded scaffold templates", () => {
|
|
test("lists eight selectable, distinct templates in stable order", () => {
|
|
const options = listDockerfileTemplates();
|
|
expect(options.map(({ id }) => id)).toEqual([...templateIds]);
|
|
expect(new Set(templateIds).size).toBe(8);
|
|
for (const { id, label, description } of options) {
|
|
expect(label.trim()).not.toBe("");
|
|
expect(description.trim()).not.toBe("");
|
|
expect(description).toMatch(
|
|
/src\/|server\.js|dist\/|app:app|binary|port/i,
|
|
);
|
|
expect(renderDockerfileTemplate(id).dockerfile).toContain("FROM ");
|
|
}
|
|
expect(listDockerfileTemplates()).toEqual(options);
|
|
});
|
|
|
|
test.each([...templateIds])(
|
|
"renders and parses %s with a safe build context",
|
|
(id) => {
|
|
const { dockerfile, dockerignore } = renderDockerfileTemplate(id);
|
|
expect(dockerfile.length).toBeGreaterThan(50);
|
|
expect(dockerignore.length).toBeGreaterThan(50);
|
|
expect(dockerfile.endsWith("\n")).toBe(true);
|
|
expect(dockerignore.endsWith("\n")).toBe(true);
|
|
parseDockerfile(dockerfile);
|
|
const patterns = dockerignore
|
|
.split("\n")
|
|
.filter((line) => line && !line.startsWith("#"));
|
|
expect(patterns).toContain(".git");
|
|
expect(patterns).toContain(".env");
|
|
expect(patterns).toContain(".env.*");
|
|
expect(patterns).toContain("**/.env");
|
|
expect(patterns).toContain("**/.env.*");
|
|
expect(dockerfile).not.toMatch(
|
|
/COPY\s+.*(?:\.env|credentials|\.pem|\.key)/i,
|
|
);
|
|
expect(dockerfile).not.toMatch(
|
|
/\/home\/dmgnr|\/home\/container|hamsterstore|kuber-server/,
|
|
);
|
|
},
|
|
);
|
|
|
|
test("static site includes built output while app builders omit it", () => {
|
|
expect(renderDockerfileTemplate("static-nginx").dockerignore).not.toMatch(
|
|
/^dist\/?$/m,
|
|
);
|
|
expect(renderDockerfileTemplate("static-nginx").dockerfile).toContain(
|
|
"COPY dist/",
|
|
);
|
|
for (const id of ["bun-compiled", "node-pnpm"] as const) {
|
|
expect(renderDockerfileTemplate(id).dockerignore).toMatch(/^dist$/m);
|
|
}
|
|
});
|
|
|
|
test("rejects unknown selections", () => {
|
|
expect(() =>
|
|
renderDockerfileTemplate("missing" as DockerfileTemplateId),
|
|
).toThrow("Unknown Dockerfile template: missing");
|
|
});
|
|
});
|