test(e2e): add playwright e2e harness
Process and API-only Playwright harness: binary and image setup, server/CLI runners, fake Docker Engine socket, read-only database inspection, secret redaction in reports, seeded fuzzing, leak detection and harness self-checks.
This commit is contained in:
1 parent
a02eef13a7
commit
fe0cd4bf6d
33 files changed
+6370
No files matched your search
@@ -0,0 +1,44 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright (C) 2026 Bryan Joshua Pedini
|
||||
|
||||
/**
|
||||
* `host-setup` project: build (when using the default ../dpsapi) and validate the production
|
||||
* binary, and check every host prerequisite. All host tests depend on it.
|
||||
*/
|
||||
import fs from 'node:fs';
|
||||
import { test as setup, expect } from '@playwright/test';
|
||||
import { binaryIsDefault, binaryPath, REPO_ROOT } from '../harness/env';
|
||||
import { HarnessError } from '../harness/errors';
|
||||
import { requireCurl, requireDocker, requireUnshare, sh } from './prereq';
|
||||
|
||||
setup('host prerequisites: tools, namespaces, Docker, production binary', async () => {
|
||||
setup.setTimeout(300_000);
|
||||
requireCurl();
|
||||
requireUnshare();
|
||||
requireDocker();
|
||||
|
||||
const bin = binaryPath();
|
||||
if (binaryIsDefault() && process.env.DPSAPI_E2E_SKIP_BUILD !== '1') {
|
||||
// PLAN §12 item 1: build the production binary used by the suite (same recipe as `make build`).
|
||||
const r = sh('make', ['-C', REPO_ROOT, 'build'], 280_000);
|
||||
if (r.code !== 0) throw new HarnessError('prerequisite', `make build failed (${r.code}):\n${r.out}\n${r.err}`);
|
||||
}
|
||||
if (!fs.existsSync(bin)) throw new HarnessError('prerequisite', `binary ${bin} does not exist (run make build or set DPSAPI_BIN)`);
|
||||
try {
|
||||
fs.accessSync(bin, fs.constants.X_OK);
|
||||
} catch {
|
||||
throw new HarnessError('prerequisite', `binary ${bin} is not executable`);
|
||||
}
|
||||
const head = Buffer.alloc(4);
|
||||
const fd = fs.openSync(bin, 'r');
|
||||
fs.readSync(fd, head, 0, 4, 0);
|
||||
fs.closeSync(fd);
|
||||
if (!head.equals(Buffer.from([0x7f, 0x45, 0x4c, 0x46]))) throw new HarnessError('prerequisite', `binary ${bin} is not an ELF executable`);
|
||||
|
||||
// Smoke check: with an empty environment the binary must reject the configuration (exit 2).
|
||||
const r = sh('env', ['-i', bin], 10_000);
|
||||
if (r.code !== 2 || r.err !== 'dpsapi: configuration error: DB_PATH is required\n') {
|
||||
throw new HarnessError('prerequisite', `binary ${bin} does not behave like dpsapi (env -i → exit ${r.code}, stderr ${JSON.stringify(r.err)})`);
|
||||
}
|
||||
expect(r.out).toBe('');
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright (C) 2026 Bryan Joshua Pedini
|
||||
|
||||
/**
|
||||
* `image-setup` project: check the image under test ($DPSAPI_IMAGE) and the tools the image
|
||||
* suite needs. All image tests depend on it.
|
||||
*/
|
||||
import { test as setup } from '@playwright/test';
|
||||
import { imageRef } from '../harness/env';
|
||||
import { HarnessError } from '../harness/errors';
|
||||
import { requireCurl, requireDocker, sh } from './prereq';
|
||||
|
||||
setup('image prerequisites: Docker, curl, image under test', async () => {
|
||||
requireCurl();
|
||||
requireDocker();
|
||||
const image = imageRef();
|
||||
if (!image) throw new HarnessError('prerequisite', 'DPSAPI_IMAGE is not set (run `make docker-e2e`, or export DPSAPI_IMAGE=<image ref>)');
|
||||
const r = sh('docker', ['image', 'inspect', '--format', '{{.Id}}', image]);
|
||||
if (r.code !== 0) throw new HarnessError('prerequisite', `image ${image} is not present locally (build it with \`make docker\`): ${r.err.trim()}`);
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright (C) 2026 Bryan Joshua Pedini
|
||||
|
||||
/**
|
||||
* Prerequisite checks shared by the setup projects. Every failure is a HarnessError('prerequisite')
|
||||
* with an actionable message, so a broken environment fails fast and is never mistaken for an
|
||||
* application failure.
|
||||
*/
|
||||
import fs from 'node:fs';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { ALPINE_IMAGE } from '../harness/env';
|
||||
import { HarnessError } from '../harness/errors';
|
||||
|
||||
export function sh(cmd: string, args: string[], timeoutMs = 60_000): { code: number | null; out: string; err: string } {
|
||||
const r = spawnSync(cmd, args, { encoding: 'utf8', timeout: timeoutMs });
|
||||
if (r.error) return { code: null, out: '', err: r.error.message };
|
||||
return { code: r.status, out: r.stdout ?? '', err: r.stderr ?? '' };
|
||||
}
|
||||
|
||||
export function requireTool(name: string, args: string[], hint: string): void {
|
||||
const r = sh(name, args, 20_000);
|
||||
if (r.code !== 0) throw new HarnessError('prerequisite', `${name} is required (${hint}): \`${name} ${args.join(' ')}\` failed: ${r.err.trim() || r.out.trim()}`);
|
||||
}
|
||||
|
||||
export function requireDocker(): void {
|
||||
requireTool('docker', ['version', '--format', '{{.Server.Version}}'], 'Docker Engine reachable by the current user');
|
||||
const r = sh('docker', ['image', 'inspect', '--format', '{{.Id}}', ALPINE_IMAGE]);
|
||||
if (r.code !== 0) throw new HarnessError('prerequisite', `test image ${ALPINE_IMAGE} is not present locally (docker pull ${ALPINE_IMAGE}): ${r.err.trim()}`);
|
||||
}
|
||||
|
||||
export function requireCurl(): void {
|
||||
requireTool('curl', ['--version'], 'real curl client for the HTTP tests');
|
||||
}
|
||||
|
||||
/** Unprivileged user+mount namespaces with a bind mount over /var/run/docker.sock (fake/inaccessible Docker sockets). */
|
||||
export function requireUnshare(): void {
|
||||
if (!fs.existsSync('/var/run/docker.sock')) throw new HarnessError('prerequisite', '/var/run/docker.sock must exist (bind-mount target for the fake Docker socket)');
|
||||
const r = sh('unshare', ['-Urm', 'sh', '-c', 'mount --bind /etc/hostname /var/run/docker.sock && test -f /var/run/docker.sock && mount -t tmpfs none /run && test ! -e /var/run/docker.sock']);
|
||||
if (r.code !== 0) throw new HarnessError('prerequisite', `unprivileged user/mount namespaces are required (unshare -Urm + bind mount + tmpfs): ${r.err.trim()}`);
|
||||
}
|
||||
Reference in new issue
Block a user