Files
HiveGrid-Wireframes/03 Quellen und Referenzen/hivegrid-cloud/server/react-router-manifest-dispatch.test.ts
T

218 lines
11 KiB
TypeScript

import { matchRoutes } from "react-router";
import { describe, expect, it } from "vitest";
import { createPortalRouteConfig } from "../apps/web/app/routes";
import { finalizePortalHostRoutingDecision, finalizePortalTenantRoutingDecision, getHostRoutingRuntimeDecision } from "./host-routing-runtime";
import { prepareReactRouterManifestRequest } from "./react-router-manifest-dispatch";
const routing = {
appHost: "app.hivegrid.test",
adminHost: "admin.hivegrid.test",
apiHost: "api.hivegrid.test",
portalAppHost: "portal.hivegrid.test",
};
describe("React Router manifest dispatch", () => {
it("validates and removes the trusted portal namespace before route matching", () => {
const outerDecision = getHostRoutingRuntimeDecision(
new Request("https://portal.hivegrid.test/acme/projects/project-1?tab=files", {
headers: { host: "portal.hivegrid.test" },
}),
{ runtimeSurface: "portal", routing }
);
expect(outerDecision.type).toBe("resolve-portal-tenant");
if (outerDecision.type !== "resolve-portal-tenant") throw new Error("expected portal tenant resolution");
const resolvedDecision = finalizePortalTenantRoutingDecision(outerDecision, "acme");
expect(resolvedDecision.type).toBe("dispatch");
if (resolvedDecision.type !== "dispatch") throw new Error("expected portal dispatch");
expect(new URL(resolvedDecision.request.url).pathname).toBe("/__portal/acme/projects/project-1");
const manifestDecision = prepareReactRouterManifestRequest(resolvedDecision.request, "portal");
expect(manifestDecision.type).toBe("dispatch");
if (manifestDecision.type !== "dispatch") throw new Error("expected manifest dispatch");
expect(manifestDecision.request.url).toBe("https://portal.hivegrid.test/acme/projects/project-1?tab=files");
expect(manifestDecision.request.headers.get("x-hivegrid-portal-tenant")).toBe("acme");
});
it("normalizes portal data requests to their public path", () => {
const request = new Request("https://portal.hivegrid.test/__portal/acme.data?_routes=routes%2Fcustomer-portal%2Flayout", {
headers: { "x-hivegrid-portal-tenant": "acme" },
});
const decision = prepareReactRouterManifestRequest(request, "portal");
expect(decision.type).toBe("dispatch");
if (decision.type !== "dispatch") throw new Error("expected manifest dispatch");
expect(new URL(decision.request.url).pathname).toBe("/acme.data");
});
it.each([
["public portal path", "https://portal.hivegrid.test/acme/projects", "acme"],
["missing trusted tenant", "https://portal.hivegrid.test/__portal/acme/projects", null],
["mismatched trusted tenant", "https://portal.hivegrid.test/__portal/acme/projects", "other"],
])("rejects %s before the portal manifest", (_label, url, tenant) => {
const headers = tenant ? { "x-hivegrid-portal-tenant": tenant } : undefined;
expect(prepareReactRouterManifestRequest(new Request(url, { headers }), "portal")).toEqual({
type: "reject",
status: 404,
});
});
it.each(["/healthz", "/readyz", "/healthcheck", "/api/metrics"])(
"passes the %s operational route without portal context",
(pathname) => {
const request = new Request(`https://portal.hivegrid.test${pathname}`);
expect(prepareReactRouterManifestRequest(request, "portal")).toEqual({ type: "dispatch", request });
}
);
it("passes an authorized custom-host health probe through both routing boundaries", () => {
const customRouting = {
appBaseDomain: "hivegrid.de",
webAppHost: "app.hivegrid.de",
adminAppHost: "admin.hivegrid.de",
apiHost: "api.hivegrid.de",
portalAppHost: "portal.hivegrid.de",
protocol: "https" as const,
};
const outerDecision = getHostRoutingRuntimeDecision(
new Request("https://kunden.example.org/healthz", {
headers: {
host: "kunden.example.org",
"x-hivegrid-portal-tenant": "spoofed",
"x-hivegrid-portal-origin": "https://attacker.example.org",
},
}),
{ runtimeSurface: "portal", routing: customRouting }
);
expect(outerDecision.type).toBe("resolve-portal-host");
if (outerDecision.type !== "resolve-portal-host") throw new Error("expected custom-host resolution");
const resolvedDecision = finalizePortalHostRoutingDecision(outerDecision, {
hostname: "kunden.example.org",
tenantSlug: "acme",
activeOrigin: "https://kunden.example.org",
});
expect(resolvedDecision.type).toBe("dispatch");
if (resolvedDecision.type !== "dispatch") throw new Error("expected custom-host health dispatch");
expect(resolvedDecision.request.headers.has("x-hivegrid-portal-tenant")).toBe(false);
expect(resolvedDecision.request.headers.has("x-hivegrid-portal-origin")).toBe(false);
const manifestDecision = prepareReactRouterManifestRequest(resolvedDecision.request, "portal");
expect(manifestDecision.type).toBe("dispatch");
if (manifestDecision.type !== "dispatch") throw new Error("expected operational manifest dispatch");
expect(new URL(manifestDecision.request.url).pathname).toBe("/healthz");
});
it("keeps custom-host SSR and browser route ids byte-for-byte aligned", () => {
const customRouting = {
appBaseDomain: "hivegrid.de",
webAppHost: "app.hivegrid.de",
adminAppHost: "admin.hivegrid.de",
apiHost: "api.hivegrid.de",
portalAppHost: "portal.hivegrid.de",
protocol: "https" as const,
};
const outerDecision = getHostRoutingRuntimeDecision(new Request("https://kunden.example.org/login"), {
runtimeSurface: "portal",
routing: customRouting,
});
expect(outerDecision.type).toBe("resolve-portal-host");
if (outerDecision.type !== "resolve-portal-host") throw new Error("expected custom-host resolution");
const resolvedDecision = finalizePortalHostRoutingDecision(outerDecision, {
hostname: "kunden.example.org",
tenantSlug: "acme",
activeOrigin: "https://kunden.example.org",
});
expect(resolvedDecision.type).toBe("dispatch");
if (resolvedDecision.type !== "dispatch") throw new Error("expected custom-host dispatch");
const manifestDecision = prepareReactRouterManifestRequest(resolvedDecision.request, "portal");
expect(manifestDecision.type).toBe("dispatch");
if (manifestDecision.type !== "dispatch") throw new Error("expected manifest dispatch");
const ssrPathname = new URL(manifestDecision.request.url).pathname;
const browserPathname = "/login";
const routes = createPortalRouteConfig();
const ssrRouteIds = matchRoutes(routes, ssrPathname)?.map((match) => match.route.id);
const browserRouteIds = matchRoutes(routes, browserPathname)?.map((match) => match.route.id);
expect(ssrPathname).toBe(browserPathname);
expect(ssrRouteIds).toEqual(["routes/customer-portal/layout.__custom-host", "routes/customer-portal/auth.login.__custom-host"]);
expect(browserRouteIds).toEqual(ssrRouteIds);
});
it("keeps custom-host data and mutation dispatch slugless without dropping trusted context", async () => {
const customRouting = {
appBaseDomain: "hivegrid.de",
webAppHost: "app.hivegrid.de",
adminAppHost: "admin.hivegrid.de",
apiHost: "api.hivegrid.de",
portalAppHost: "portal.hivegrid.de",
protocol: "https" as const,
};
const resolution = { hostname: "kunden.example.org", tenantSlug: "acme", activeOrigin: "https://kunden.example.org" };
const dataOuter = getHostRoutingRuntimeDecision(new Request("https://kunden.example.org/login.data?_routes=custom-auth"), {
runtimeSurface: "portal",
routing: customRouting,
});
if (dataOuter.type !== "resolve-portal-host") throw new Error("expected custom-host data resolution");
const dataResolved = finalizePortalHostRoutingDecision(dataOuter, resolution);
if (dataResolved.type !== "dispatch") throw new Error("expected custom-host data dispatch");
const dataManifest = prepareReactRouterManifestRequest(dataResolved.request, "portal");
expect(dataManifest.type).toBe("dispatch");
if (dataManifest.type !== "dispatch") throw new Error("expected data manifest dispatch");
expect(new URL(dataManifest.request.url).pathname).toBe("/login.data");
const actionOuter = getHostRoutingRuntimeDecision(
new Request("https://kunden.example.org/settings", {
method: "POST",
headers: { "content-type": "application/x-www-form-urlencoded" },
body: "intent=request_export",
}),
{ runtimeSurface: "portal", routing: customRouting }
);
if (actionOuter.type !== "resolve-portal-host") throw new Error("expected custom-host action resolution");
const actionResolved = finalizePortalHostRoutingDecision(actionOuter, resolution);
if (actionResolved.type !== "dispatch") throw new Error("expected custom-host action dispatch");
const actionManifest = prepareReactRouterManifestRequest(actionResolved.request, "portal");
expect(actionManifest.type).toBe("dispatch");
if (actionManifest.type !== "dispatch") throw new Error("expected action manifest dispatch");
expect(new URL(actionManifest.request.url).pathname).toBe("/settings");
expect(actionManifest.request.method).toBe("POST");
expect(actionManifest.request.headers.get("x-hivegrid-portal-tenant")).toBe("acme");
expect(actionManifest.request.headers.get("x-hivegrid-portal-custom-host")).toBe("kunden.example.org");
await expect(actionManifest.request.text()).resolves.toBe("intent=request_export");
});
it("passes the exact React Router manifest endpoint to the selected portal build", () => {
const request = new Request("https://portal.hivegrid.test/__manifest?paths=%2Facme%2Flogin");
expect(prepareReactRouterManifestRequest(request, "portal")).toEqual({ type: "dispatch", request });
expect(prepareReactRouterManifestRequest(new Request("https://portal.hivegrid.test/__manifest/extra"), "portal")).toEqual({
type: "reject",
status: 404,
});
});
it("never dispatches an internal portal request into the Core manifest", () => {
const request = new Request("https://app.hivegrid.test/__portal/acme", {
headers: { "x-hivegrid-portal-tenant": "acme" },
});
expect(prepareReactRouterManifestRequest(request, "core")).toEqual({ type: "reject", status: 404 });
});
it("dispatches Admin routes through the shared Core manifest without weakening the portal boundary", () => {
const adminRequest = new Request("https://admin.hivegrid.test/settings");
expect(prepareReactRouterManifestRequest(adminRequest, "admin")).toEqual({
type: "dispatch",
request: adminRequest,
});
const internalPortalRequest = new Request("https://admin.hivegrid.test/__portal/acme", {
headers: { "x-hivegrid-portal-tenant": "acme" },
});
expect(prepareReactRouterManifestRequest(internalPortalRequest, "admin")).toEqual({
type: "reject",
status: 404,
});
});
});