A ui-test entry had survived into the active trip's dailies. Three independent failures had to line up for that, and all three were real: 1. cleanupEntry() used host-side fs.rmSync. Grav's Apache workers run as root, so every entry the form creates is root-owned and recursive removal needs write permission on that directory — which the host user lacks. Cleanup had never worked for form-created entries; it just threw inside a path nothing checked. It now falls back to `docker exec … rm -rf` in the container that actually serves USER_DIR. 2. globalTeardown's dailies sweep keyed off a `parent:` in post-form.md — a key deliberately removed (the write target comes from site.yaml active_trip, and CLAUDE.md forbids re-adding a static parent). The regex could never match, so dailiesDir was always null and the sweep silently did nothing. It now reuses helpers' own resolution instead of keeping a divergent copy. 3. Nothing pinned the suite to this checkout's server. playwright.config.js defaults to :8081, so a worktree run hit the MAIN checkout — entries created in one content tree while the specs asserted and cleaned up in another. test-ui now passes GRAV_BASE_URL from GRAV_PORT, and globalSetup hard-fails when the server's bind mount disagrees with the tree the specs read. Also fixed, found on the way to a green run: - test-account interpolated the password into an `sh -c` string, so a password containing a shell metacharacter was re-parsed by the container's shell (`sh: 2: <fragment>: not found`, no account, every UI run dead). It now travels via `docker exec -e`, making the recipe indifferent to its contents. - `make start` in a worktree always failed: travel-memories declares `env_file: .env` and worktree-new creates none. It degrades to start-grav there — a worktree with no server is what sent runs to :8081 in the first place. - test-form-config asserted a hero_image field that 8cf1145 deliberately removed; it had been failing ever since. Verified: config 22/22, post 6/6, location-override 20/20, and a full UI run now leaves zero ui-test entries behind. The remaining UI failures are pre-existing on main — site.yaml pins owner_username to a real account while the suite logs in as testrunner, so owner-only controls never render for it. Only trip-publish.spec.js patches that; delete-flow, edit-mode and anon-view do not. Left for a separate branch.
84 lines
3.9 KiB
JavaScript
84 lines
3.9 KiB
JavaScript
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:<port>.`
|
|
);
|
|
}
|
|
}
|
|
|
|
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);
|
|
};
|