mirror of
https://github.com/binwiederhier/ntfy.git
synced 2026-11-12 07:30:53 +08:00
Adds 56 Playwright tests in web/e2e/ that drive Chromium against a real ntfy server: subscribing, publishing, notifications, signup/login/logout, account settings, access tokens, reservations and languages. "make web-e2e" builds the binary and runs them against a fresh local server; setting NTFY_E2E_BASE_URL (plus an admin token and tier) runs them against an existing server instead. docs/develop.md describes both. The tests found two bugs, fixed here: - Signup showed "Username is already taken" without the name, since it read the username from the error instead of the form. - A poll that overlapped a WebSocket delivery could overwrite the stored message and drop its unread marker, or log a ConstraintError. Both write paths in SubscriptionManager now check and write in one transaction, and the poller skips messages that are already stored.
61 lines
2.5 KiB
JavaScript
61 lines
2.5 KiB
JavaScript
import { execFileSync, spawn } from "node:child_process";
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
|
|
// Starts a throwaway ntfy server for the e2e run; Playwright's webServer launches this
|
|
// script (with the paths from env.js) and kills it when the run is over. State is wiped
|
|
// on every start.
|
|
const [binary, configFile, port, tier] = process.argv.slice(2);
|
|
const runDir = path.dirname(configFile);
|
|
const baseURL = `http://127.0.0.1:${port}`;
|
|
|
|
if (!fs.existsSync(binary)) {
|
|
console.error(`${binary} not found; run "make web-e2e" to build it`);
|
|
process.exit(1);
|
|
}
|
|
fs.rmSync(runDir, { recursive: true, force: true });
|
|
fs.mkdirSync(runDir, { recursive: true });
|
|
|
|
// behind-proxy lets every test pose as its own visitor via X-Forwarded-For (see fixtures.js),
|
|
// so per-IP limits like the 6/day account creation limit don't leak between tests
|
|
const config = {
|
|
"base-url": baseURL,
|
|
"listen-http": `127.0.0.1:${port}`,
|
|
"cache-file": path.join(runDir, "cache.db"),
|
|
"attachment-cache-dir": path.join(runDir, "attachments"),
|
|
"auth-file": path.join(runDir, "user.db"),
|
|
"auth-default-access": "read-write",
|
|
"enable-signup": true,
|
|
"enable-login": true,
|
|
"enable-reservations": true,
|
|
"behind-proxy": true,
|
|
"log-file": path.join(runDir, "ntfy.log"),
|
|
};
|
|
fs.writeFileSync(
|
|
configFile,
|
|
Object.entries(config)
|
|
.map(([k, v]) => `${k}: ${v}\n`)
|
|
.join(""),
|
|
);
|
|
|
|
const server = spawn(binary, ["serve", "--config", configFile], { stdio: "inherit" });
|
|
server.on("exit", (code) => process.exit(code ?? 1));
|
|
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
process.on(signal, () => server.kill(signal));
|
|
}
|
|
|
|
// Wait for the server to create its database; the CLI refuses to run before that
|
|
while (!(await fetch(`${baseURL}/v1/health`).catch(() => null))?.ok) {
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
}
|
|
|
|
// Provision what a remote server is expected to already have: an admin token (specs create
|
|
// their users through the admin API) and a tier with reservations
|
|
const ntfy = (args, env = {}) => execFileSync(binary, args, { env: { ...process.env, ...env, NTFY_CONFIG_FILE: configFile } }).toString();
|
|
ntfy(["user", "add", "--role=admin", "e2eadmin"], { NTFY_PASSWORD: "e2eadmin-password" });
|
|
const token = ntfy(["token", "add", "e2eadmin"]).match(/token (tk_\w+) created/)[1];
|
|
ntfy(["tier", "add", "--name", "E2E", "--reservation-limit", "10", "--message-limit", "10000", tier]);
|
|
|
|
// Playwright waits for this line and exports the token as NTFY_E2E_ADMIN_TOKEN (see config)
|
|
console.log(`ntfy e2e server ready, admin token ${token}`);
|