Files
EpicNext-Cms/e2e/ui/fixture-server.mjs
Simo c5c9941768
CI / check (push) Failing after 1m34s
CI / deploy (push) Skipped
CI / publish-container (push) Skipped
feat(hk): explain background updates with protected content links and retry details
2026-09-13 19:41:25 +02:00

231 lines
7.0 KiB
JavaScript

import { readFile } from "node:fs/promises";
import { createServer } from "node:http";
import path from "node:path";
import { fileURLToPath } from "node:url";
import tailwind from "@tailwindcss/postcss";
import { build } from "esbuild";
import postcss from "postcss";
const root = path.resolve(
path.dirname(fileURLToPath(import.meta.url)),
"../..",
);
const fixtures = path.join(root, "e2e/ui/fixtures");
const port = Number(process.env.UI_TEST_PORT || 3107);
if (!Number.isInteger(port) || port < 1024 || port > 65535)
throw Error("Invalid UI_TEST_PORT");
const actions = new Set([
"@/actions/catalog",
"@/actions/import-furni",
"@/actions/events",
"@/actions/admin-settings",
"@/actions/admin-table-views",
"@/actions/article-recovery",
"@/actions/admin-media",
]);
const bundle = await build({
entryPoints: [path.join(fixtures, "entry.tsx")],
bundle: true,
write: false,
outdir: path.join(root, "test-results/ui-fixture"),
format: "iife",
jsx: "automatic",
alias: { "@": path.join(root, "src") },
define: { "process.env.NODE_ENV": '"production"' },
plugins: [
{
name: "server-contracts",
setup(build) {
build.onResolve(
{ filter: /^next\/navigation$|^next\/link$|^@\/actions\// },
(args) => {
if (args.path === "next/navigation")
return { path: path.join(fixtures, "navigation.ts") };
if (args.path === "next/link")
return { path: path.join(fixtures, "next-link.tsx") };
if (actions.has(args.path))
return { path: path.join(fixtures, "server-actions.ts") };
throw Error(`Unexpected server action in UI fixture: ${args.path}`);
},
);
build.onResolve(
{ filter: /^@\/lib\/db$|^mysql2|^next\/headers$/ },
(args) => {
throw Error(
`Database or server dependency forbidden in UI fixture: ${args.path}`,
);
},
);
},
},
],
});
const globalCss = (
await postcss([tailwind({ base: root })]).process(
await readFile(path.join(root, "src/app/globals.css"), "utf8"),
{ from: path.join(root, "src/app/globals.css") },
)
).css;
const css =
globalCss +
"\n" +
bundle.outputFiles
.filter((file) => file.path.endsWith(".css"))
.map((file) => file.text)
.join("\n");
const js = bundle.outputFiles.find((file) => file.path.endsWith(".js")).text;
const html =
'<!doctype html><html lang="en" class="dark"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="csrf-token" content="' +
"a".repeat(64) +
'"><title>UI verification fixture</title><link rel="stylesheet" href="/fixture/styles.css"></head><body><div id="root"></div><script src="/fixture/app.js"></script></body></html>';
const cover =
'<svg xmlns="http://www.w3.org/2000/svg" width="720" height="160" viewBox="0 0 720 160"><rect width="720" height="160" fill="#334155"/><circle cx="620" cy="80" r="48" fill="#f59e0b"/></svg>';
const jobs = new Map();
const views = [];
const json = (res, value, status = 200) => {
res.writeHead(status, {
"content-type": "application/json",
"cache-control": "no-store",
});
res.end(JSON.stringify(value));
};
const server = createServer(async (req, res) => {
try {
const url = new URL(req.url, "http://127.0.0.1");
if (url.pathname === "/fixture/reset") {
jobs.clear();
views.length = 0;
return json(res, { ok: true });
}
if (url.pathname === "/health") return json(res, { ready: true });
if (url.pathname === "/fixture/styles.css") {
res.setHeader("content-type", "text/css");
return res.end(css);
}
if (url.pathname === "/fixture/app.js") {
res.setHeader("content-type", "application/javascript");
return res.end(js);
}
if (url.pathname === "/fixture/cover.svg") {
res.setHeader("content-type", "image/svg+xml");
return res.end(cover);
}
if (url.pathname.startsWith("/vendor/tinymce/")) {
const assetRoot = path.join(root, "node_modules/tinymce");
const asset = path.resolve(
assetRoot,
url.pathname.slice("/vendor/tinymce/".length),
);
if (!asset.startsWith(assetRoot + path.sep)) {
res.writeHead(400);
return res.end();
}
res.setHeader(
"content-type",
asset.endsWith(".css")
? "text/css"
: asset.endsWith(".js")
? "application/javascript"
: asset.endsWith(".woff")
? "font/woff"
: "application/octet-stream",
);
return res.end(await readFile(asset));
}
let body = {};
if (req.method === "POST" || req.method === "PATCH") {
const chunks = [];
let size = 0;
for await (const chunk of req) {
size += chunk.length;
if (size > 1000000) throw Error("Fixture payload too large");
chunks.push(chunk);
}
body = JSON.parse(Buffer.concat(chunks).toString() || "{}");
}
if (url.pathname === "/api/admin/studio/import-jobs") {
if (req.method === "PATCH") {
const job = jobs.get(body.id);
if (job) job.state = "cancelled";
return json(res, { ok: true, job });
}
return json(res, {
ok: true,
jobs: [...jobs.values()],
nextCursor: null,
});
}
if (url.pathname === "/api/admin/import/official/sync-all") {
if (!jobs.has(body.id))
jobs.set(body.id, {
id: body.id,
userId: 7,
createdAt: "2030-01-01T12:00:00.000Z",
updatedAt: "2030-01-01T12:00:00.000Z",
state: "queued",
translate: false,
syncKind: "official",
items: [
{
id: 0,
classname: "fixture_chair",
name: "Fixture chair",
description: "",
type: "flooritem",
category: "other",
revision: 1,
state: "pending",
},
],
});
await new Promise((resolve) => setTimeout(resolve, 150));
return json(res, { ok: true, job: jobs.get(body.id) });
}
if (url.pathname === "/api/media") return json(res, { files: [] });
if (url.pathname.startsWith("/fixture/actions/")) {
const name = url.pathname.split("/").at(-1);
if (name === "load-recovery")
return json(res, { version: 0, draft: null, revisions: [] });
if (name === "autosave" || name === "clear-recovery")
return json(res, { ok: true, version: 1 });
if (name === "list-views")
return json(res, { ok: true, data: { views } });
if (name === "save-view") {
views.push(body.view ?? body);
return json(res, { ok: true });
}
return json(res, { ok: true, data: {} });
}
if (
[
"/notifications",
"/admin/unsaved-harness",
"/admin/deliveries-harness",
"/admin/attachment-harness",
"/admin/studio-harness",
"/admin/jobs-harness",
"/admin/settings",
"/admin/articles/new",
"/admin/articles/recovery",
"/admin/events/recovery",
"/admin/tickets/desk",
"/admin/import/official",
].includes(url.pathname)
) {
res.setHeader("content-type", "text/html");
return res.end(html);
}
res.writeHead(404);
res.end("Not found");
} catch (error) {
console.error(error);
res.writeHead(500);
res.end("Fixture error");
}
});
server.listen(port, "127.0.0.1", () =>
console.log(`Isolated UI fixture ready at http://127.0.0.1:${port}`),
);
for (const signal of ["SIGINT", "SIGTERM"])
process.on(signal, () => server.close(() => process.exit(0)));