Files
HiveGrid-Wireframes/03 Quellen und Referenzen/hivegrid-cloud/scripts/verify-web-build-surfaces.test.mjs
T

163 lines
8.7 KiB
JavaScript

import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { describe, it } from "vitest";
import { verifyWebBuildSurfaces } from "./verify-web-build-surfaces.mjs";
const portalRoutes = {
root: { id: "root", path: "" },
"routes/healthz": { id: "routes/healthz", path: "healthz" },
"routes/readyz": { id: "routes/readyz", path: "readyz" },
"routes/healthcheck": { id: "routes/healthcheck", path: "healthcheck" },
"routes/api.metrics.portal": { id: "routes/api.metrics.portal", path: "api/metrics" },
"routes/customer-portal/layout": { id: "routes/customer-portal/layout", path: ":tenant" },
"routes/customer-portal/dashboard": { id: "routes/customer-portal/dashboard", index: true },
"routes/customer-portal/projects": { id: "routes/customer-portal/projects", path: "projects" },
"routes/customer-portal/project-detail": { id: "routes/customer-portal/project-detail", path: "projects/:projectId" },
"routes/customer-portal/document-download": { id: "routes/customer-portal/document-download", path: "documents/:releaseId" },
"routes/customer-portal/upload": { id: "routes/customer-portal/upload", path: "upload" },
"routes/customer-portal/settings": { id: "routes/customer-portal/settings", path: "settings" },
"routes/customer-portal/branding-logo": { id: "routes/customer-portal/branding-logo", path: "branding/logo" },
"routes/customer-portal/export-download": { id: "routes/customer-portal/export-download", path: "exports/:exportId" },
"routes/customer-portal/project-upload": { id: "routes/customer-portal/project-upload", path: "projects/:projectId/upload" },
"routes/customer-portal/project-complaint": {
id: "routes/customer-portal/project-complaint",
path: "projects/:projectId/complaints/:formId",
},
"routes/customer-portal/logout": { id: "routes/customer-portal/logout", path: "logout" },
"routes/customer-portal/auth": { id: "routes/customer-portal/auth", path: "*" },
"routes/customer-portal/layout.__custom-host": {
id: "routes/customer-portal/layout.__custom-host",
parentId: "root",
},
...Object.fromEntries(
[
["dashboard", undefined],
["projects", "projects"],
["upload", "upload"],
["settings", "settings"],
["branding-logo", "branding/logo"],
["export-download", "exports/:exportId"],
["project-upload", "projects/:projectId/upload"],
["project-complaint", "projects/:projectId/complaints/:formId"],
["project-detail", "projects/:projectId"],
["document-download", "documents/:releaseId"],
["logout", "logout"],
["auth.login", "login/*"],
["auth.forgot-password", "forgot-password"],
["auth.setup", "setup/*"],
["auth.verify-email", "verify-email/*"],
["auth.reset-password", "reset-password/*"],
].map(([name, path]) => {
const id = `routes/customer-portal/${name}.__custom-host`;
return [id, { id, parentId: "routes/customer-portal/layout.__custom-host", ...(path ? { path } : { index: true }) }];
})
),
};
describe("verify-web-build-surfaces", () => {
it("accepts isolated Core and Customer Portal manifests", () => {
const root = fixture({ root: { id: "root" }, "routes/app": { id: "routes/app", path: ":tenant" } }, portalRoutes);
assert.deepEqual(verifyWebBuildSurfaces(root), { coreRouteCount: 2, portalRouteCount: 35 });
});
it("rejects Customer Portal routes in Core and Core routes in Portal", () => {
const crossedCore = fixture({ root: { id: "root" }, "routes/customer-portal/layout": { id: "routes/customer-portal/layout" } }, portalRoutes);
assert.throws(() => verifyWebBuildSurfaces(crossedCore), /Core manifest contains Customer Portal routes/);
const crossedPortal = fixture({ root: { id: "root" } }, { ...portalRoutes, "routes/app": { id: "routes/app" } });
assert.throws(() => verifyWebBuildSurfaces(crossedPortal), /Portal manifest route isolation failed/);
});
it("rejects opposite-surface server chunks even when route manifests are isolated", () => {
const portalLeak = fixture({ root: { id: "root" } }, portalRoutes);
writeServerAsset(portalLeak, "build-portal", "root-core.server-fixture.js", "export function loadCoreRootData() {}\n");
assert.throws(() => verifyWebBuildSurfaces(portalLeak), /Portal server build contains opposite-surface code/);
const coreLeak = fixture({ root: { id: "root" } }, portalRoutes);
writeServerAsset(coreLeak, "build", "portal-root.server-fixture.js", "export function loadPortalRootData() {}\n");
assert.throws(() => verifyWebBuildSurfaces(coreLeak), /Core server build contains opposite-surface code/);
});
it("rejects the Core cache bootstrap in the Portal server artifact", () => {
const portalLeak = fixture({ root: { id: "root" } }, portalRoutes);
writeServerAsset(portalLeak, "build-portal", "cache-bootstrap-fixture.js", "export function startCacheSubscribers() {}\n");
assert.throws(() => verifyWebBuildSurfaces(portalLeak), /Portal server build contains opposite-surface code \(cache-bootstrap\)/);
});
it("rejects client assets compiled for the opposite surface", () => {
const portalLeak = fixture({ root: { id: "root" } }, portalRoutes);
writeClientAsset(portalLeak, "build-portal", "core-root-fixture.js", 'throw new Error("Core build received Portal root data");\n');
assert.throws(() => verifyWebBuildSurfaces(portalLeak), /Portal client build contains opposite-surface code/);
const coreLeak = fixture({ root: { id: "root" } }, portalRoutes);
writeClientAsset(coreLeak, "build", "portal-root-fixture.js", 'throw new Error("Portal build received Core root data");\n');
assert.throws(() => verifyWebBuildSurfaces(coreLeak), /Core client build contains opposite-surface code/);
});
it("rejects Core browser code and public files in the Portal artifact", () => {
const clientLeak = fixture({ root: { id: "root" } }, portalRoutes);
writeClientAsset(clientLeak, "build-portal", "core-cookie-fixture.js", 'const cookie = "__Host-hivegrid_app_session";\n');
assert.throws(() => verifyWebBuildSurfaces(clientLeak), /Portal client build contains opposite-surface code/);
const publicLeak = fixture({ root: { id: "root" } }, portalRoutes);
writePublicAsset(publicLeak, "build-portal", "sw.js", "core service worker");
assert.throws(() => verifyWebBuildSurfaces(publicLeak), /Portal public artifact contains Core\/unapproved files/);
});
it("rejects a full Core translation catalog staged under the Portal locale path", () => {
const localeLeak = fixture({ root: { id: "root" } }, portalRoutes);
writePublicAsset(
localeLeak,
"build-portal",
"locales/de/translations.json",
JSON.stringify({ customerPortal: { login: "Anmelden" }, cookies: { title: "Cookies" } })
);
assert.throws(() => verifyWebBuildSurfaces(localeLeak), /must contain only the customerPortal catalog/);
});
});
function fixture(coreRoutes, customerPortalRoutes) {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "hivegrid-web-surfaces-"));
writeBuild(root, "build", coreRoutes);
writeBuild(root, "build-portal", customerPortalRoutes);
return root;
}
function writeBuild(root, directory, routes) {
const clientAssets = path.join(root, "apps", "web", directory, "client", "assets");
const server = path.join(root, "apps", "web", directory, "server");
fs.mkdirSync(clientAssets, { recursive: true });
fs.mkdirSync(server, { recursive: true });
fs.writeFileSync(path.join(server, "index.js"), "export {};\n");
fs.writeFileSync(path.join(clientAssets, "manifest-fixture.js"), `window.__reactRouterManifest=${JSON.stringify({ routes })};`);
if (directory === "build-portal") {
writePublicAsset(root, directory, "favicon-32x32.png", "favicon");
writePublicAsset(root, directory, "fonts/instrument-sans-latin.woff2", "font");
writePublicAsset(root, directory, "fonts/instrument-sans-latin-ext.woff2", "font-ext");
for (const locale of ["de", "en", "pl", "ru", "uk", "hu"]) {
writePublicAsset(root, directory, `locales/${locale}/translations.json`, JSON.stringify({ customerPortal: { login: locale } }));
}
}
}
function writeServerAsset(root, directory, fileName, source) {
const assets = path.join(root, "apps", "web", directory, "server", "assets");
fs.mkdirSync(assets, { recursive: true });
fs.writeFileSync(path.join(assets, fileName), source);
}
function writeClientAsset(root, directory, fileName, source) {
const assets = path.join(root, "apps", "web", directory, "client", "assets");
fs.writeFileSync(path.join(assets, fileName), source);
}
function writePublicAsset(root, directory, relativePath, source) {
const filePath = path.join(root, "apps", "web", directory, "client", relativePath);
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(filePath, source);
}