const fs = require('fs'); const path = require('path'); const { execSync } = require('child_process'); /** * Fail fast if the server under test does not serve the `user/` tree the specs * read from disk. * * This mismatch is silent and destructive. Every post spec submits through the * live form (the write target is derived server-side from site.yaml * `active_trip`, so there is no per-request override), then asserts and cleans up * on disk via helpers' USER_DIR. Run the specs from a worktree whose own * container is down and baseURL falls back to localhost:8081 — the MAIN * checkout — so entries get created in one content tree while cleanup deletes * from another. The entries are then left behind in real trip content, which is * exactly what happened on 2026-07-24. * * Docker is the only thing that knows the mapping, so this is best-effort: if we * cannot determine it we warn and continue rather than blocking non-Docker runs. * But when we CAN determine it and it disagrees, that is always a bug. */ function assertServerServesUserDir(baseURL, userDir) { const port = new URL(baseURL).port || '80'; let mountedUserDir; try { const container = execSync("docker ps --format '{{.Names}}\t{{.Ports}}'", { encoding: 'utf-8' }) .split('\n').filter(Boolean) .find(l => l.includes(`:${port}->`)); if (!container) { console.warn(`[setup] no running container publishes port ${port} — is the dev server up? (make start)`); return; } const name = container.split('\t')[0]; mountedUserDir = execSync( `docker inspect ${name} --format '{{range .Mounts}}{{if eq .Destination "/var/www/html/user"}}{{.Source}}{{end}}{{end}}'`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] } ).trim(); if (!mountedUserDir) return; // no bind mount to compare against } catch (_) { return; // docker unavailable — nothing to check } const served = fs.realpathSync(mountedUserDir); const asserted = fs.realpathSync(userDir); if (served !== asserted) { throw new Error( `Test target mismatch — refusing to run.\n` + ` baseURL ${baseURL} is served from: ${served}\n` + ` but the specs read/clean up: ${asserted}\n` + `Entries would be created in one tree and cleanup would miss them, leaving\n` + `test entries behind in real content. Start this checkout's own server\n` + `(make start) and point the run at it, e.g. GRAV_BASE_URL=http://localhost:.` ); } } module.exports = async function globalSetup() { const envFile = path.join(__dirname, '../.env'); if (fs.existsSync(envFile)) { fs.readFileSync(envFile, 'utf-8').split(/\r?\n/).forEach(line => { const m = line.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/); if (m && !process.env[m[1]]) { process.env[m[1]] = m[2].trim().replace(/^(['"])(.*)\1$/, '$2'); } }); } // Local test-account defaults (mirror the Makefile) so direct `npx playwright // test` runs are self-contained without needing GRAV_TEST_* in .env. if (!process.env.GRAV_TEST_USER) process.env.GRAV_TEST_USER = 'testrunner'; if (!process.env.GRAV_TEST_PASS) process.env.GRAV_TEST_PASS = 'Testpass1234'; // Ensure the local test account exists (idempotent; never committed). execSync('make test-account', { cwd: path.join(__dirname, '..'), stdio: 'inherit' }); // Ensure demo content is loaded (italy-2026-demo trip + stories + GPX files) execSync('make demo-load', { cwd: path.join(__dirname, '..'), stdio: 'inherit' }); // Required last: helpers.js resolves USER_DIR at require time, and the .env // load above can supply GRAV_USER_DIR. const { USER_DIR } = require('./ui/helpers'); assertServerServesUserDir(process.env.GRAV_BASE_URL || 'http://localhost:8081', USER_DIR); };