import test from "node:test"; import assert from "node:assert/strict"; import fs from "node:fs"; import path from "node:path"; test("Docker Configuration: Dockerfile enforces Node runtime, healthcheck, and entrypoint", () => { const dockerfilePath = path.resolve(process.cwd(), "Dockerfile"); assert.ok(fs.existsSync(dockerfilePath), "Dockerfile must exist"); const content = fs.readFileSync(dockerfilePath, "utf-8"); // Must use Node 22 alpine assert.match(content, /FROM\s+node:22-alpine/i, "Dockerfile must use node:22-alpine base image"); // Must NOT contain nginx base images or references assert.doesNotMatch(content, /FROM\s+nginx/i, "Dockerfile must not use nginx base image"); assert.doesNotMatch(content, /nginx/i, "Dockerfile must not contain nginx directives"); // Must build static site and configure dynamic port assert.match(content, /RUN\s+npm\s+run\s+build/, "Dockerfile must compile baseline static assets"); assert.match(content, /ARG\s+PORT=8080/, "Dockerfile must define ARG PORT with default fallback"); assert.match(content, /ENV\s+PORT=\$\{?PORT\}?/, "Dockerfile must define ENV PORT using variable"); assert.match(content, /EXPOSE\s+\$\{?PORT\}?/, "Dockerfile must expose dynamic PORT variable"); // Must define built-in HEALTHCHECK probing 127.0.0.1 loopback assert.match(content, /HEALTHCHECK[\s\S]*?127\.0\.0\.1/, "Dockerfile must define baked-in HEALTHCHECK on 127.0.0.1"); // Must configure dynamic entrypoint script assert.match(content, /ENTRYPOINT\s*\[\s*["']\/app\/entrypoint\.sh["']\s*\]/, "Dockerfile must use /app/entrypoint.sh entrypoint"); }); test("Docker Configuration: entrypoint.sh script exists and has valid structure", () => { const entrypointPath = path.resolve(process.cwd(), "entrypoint.sh"); assert.ok(fs.existsSync(entrypointPath), "entrypoint.sh must exist"); const stats = fs.statSync(entrypointPath); // Check executable permissions (at least owner executable: 0o100) assert.ok(Boolean(stats.mode & 0o111), "entrypoint.sh must have executable permission bits"); const content = fs.readFileSync(entrypointPath, "utf-8"); assert.match(content, /^#!\/bin\/sh/, "entrypoint.sh must use POSIX /bin/sh shebang"); assert.match(content, /exec\s+"\$@"/, "entrypoint.sh must pass through custom container arguments"); assert.match(content, /npm\s+run\s+build/, "entrypoint.sh must build the static site"); assert.match(content, /exec\s+node\s+server\.js/, "entrypoint.sh must start static server with exec"); }); test("Docker Configuration: docker-compose configures Node service correctly", () => { const composePath = fs.existsSync(path.resolve(process.cwd(), "sample-docker-compose.yml")) ? path.resolve(process.cwd(), "sample-docker-compose.yml") : path.resolve(process.cwd(), "docker-compose.yml"); assert.ok(fs.existsSync(composePath), "Compose template file must exist"); const content = fs.readFileSync(composePath, "utf-8"); // Verifies dynamic port mapping and environment variables assert.match(content, /PORT.*8080/, "docker-compose.yml must configure PORT fallback"); assert.match(content, /:\$\{PORT:-8080\}/, "docker-compose.yml must map dynamic container PORT"); // Verifies domain argument enforcement assert.match(content, /SITE_DOMAIN/, "docker-compose.yml must require SITE_DOMAIN"); // Verifies markdown volume mount assert.match(content, /\/app\/src\/posts/, "docker-compose.yml must mount external markdown posts"); });