218 lines
11 KiB
TypeScript
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,
|
|
});
|
|
});
|
|
});
|