207 lines
9.0 KiB
TypeScript
207 lines
9.0 KiB
TypeScript
// @vitest-environment node
|
|
import { describe, expect, test } from "vitest";
|
|
import { getHostRoutingRuntimeDecision } from "./host-routing-runtime";
|
|
|
|
function expectDispatchDecision(request: Request) {
|
|
const decision = getHostRoutingRuntimeDecision(request);
|
|
expect(decision.type).toBe("dispatch");
|
|
if (decision.type !== "dispatch") {
|
|
throw new Error(`Expected dispatch decision, received ${decision.type}`);
|
|
}
|
|
return decision;
|
|
}
|
|
|
|
describe("getHostRoutingRuntimeDecision", () => {
|
|
test("dispatches the canonical app host root to the tenant picker route", () => {
|
|
const decision = expectDispatchDecision(new Request("http://app.localhost/?from=login#tenants"));
|
|
|
|
expect(decision.target).toBe("web");
|
|
expect(new URL(decision.request.url).pathname).toBe("/");
|
|
expect(new URL(decision.request.url).search).toBe("?from=login");
|
|
expect(new URL(decision.request.url).hash).toBe("#tenants");
|
|
});
|
|
|
|
test("keeps app host requests on promoted tenant paths for web dispatch", () => {
|
|
const decision = expectDispatchDecision(new Request("http://app.localhost/acme/dashboard?tab=activity#events"));
|
|
|
|
expect(decision.target).toBe("web");
|
|
expect(new URL(decision.request.url).pathname).toBe("/acme/dashboard");
|
|
expect(new URL(decision.request.url).search).toBe("?tab=activity");
|
|
expect(new URL(decision.request.url).hash).toBe("#events");
|
|
});
|
|
|
|
test("dispatches public auth routes on the app host without tenant rewriting", () => {
|
|
const decision = expectDispatchDecision(new Request("http://app.localhost/login?redirectTo=%2Facme%2Fdashboard"));
|
|
|
|
expect(decision.target).toBe("web");
|
|
expect(new URL(decision.request.url).pathname).toBe("/login");
|
|
expect(new URL(decision.request.url).search).toBe("?redirectTo=%2Facme%2Fdashboard");
|
|
});
|
|
|
|
test("rejects removed public API paths instead of redirecting compatibility routes", () => {
|
|
const decision = getHostRoutingRuntimeDecision(new Request("http://app.localhost/api/workflows/run/workflow_1?tenantId=tenant_1"));
|
|
|
|
expect(decision).toEqual({
|
|
type: "reject",
|
|
status: 404,
|
|
});
|
|
});
|
|
|
|
test("redirects legacy api v1 paths before removed API path rejection", () => {
|
|
expect(getHostRoutingRuntimeDecision(new Request("http://app.localhost/api/v1/health?check=deep#status"))).toEqual({
|
|
type: "redirect",
|
|
status: 308,
|
|
location: "http://api.localhost/v1/health?check=deep#status",
|
|
});
|
|
});
|
|
|
|
test("lets internal API proxies reach private web API handlers without public redirects", () => {
|
|
const decision = expectDispatchDecision(
|
|
new Request("http://app.localhost/internal/api/workflows/run/workflow_1?tenantId=tenant_1", {
|
|
method: "POST",
|
|
headers: {
|
|
"x-toolctrl-internal-web-api-proxy": "1",
|
|
"x-toolctrl-internal-web-api-proxy-signature": "signed",
|
|
"x-api-key": "tenant-api-key",
|
|
},
|
|
body: JSON.stringify({ name: "Ada" }),
|
|
})
|
|
);
|
|
|
|
expect(decision.target).toBe("web");
|
|
expect(new URL(decision.request.url).pathname).toBe("/internal/api/workflows/run/workflow_1");
|
|
expect(new URL(decision.request.url).search).toBe("?tenantId=tenant_1");
|
|
expect(decision.request.headers.get("x-toolctrl-internal-web-api-proxy")).toBe("1");
|
|
expect(decision.request.headers.get("x-toolctrl-internal-web-api-proxy-signature")).toBe("signed");
|
|
expect(decision.request.headers.get("x-api-key")).toBe("tenant-api-key");
|
|
});
|
|
|
|
test("dispatches canonical admin host requests for web dispatch", () => {
|
|
const decision = expectDispatchDecision(new Request("http://admin.localhost/accounts/users?role=owner#active"));
|
|
|
|
expect(decision.target).toBe("web");
|
|
expect(new URL(decision.request.url).pathname).toBe("/accounts/users");
|
|
expect(new URL(decision.request.url).search).toBe("?role=owner");
|
|
expect(new URL(decision.request.url).hash).toBe("#active");
|
|
});
|
|
|
|
test("keeps canonical admin host routes stable for web dispatch", () => {
|
|
const settingsDecision = expectDispatchDecision(new Request("http://admin.localhost/settings/profile?tab=mfa#codes"));
|
|
const metricsDataDecision = expectDispatchDecision(new Request("http://admin.localhost/metrics/summary.data?_routes=routes%2Fadmin%2Fmetrics%2Fsummary.__host"));
|
|
|
|
expect(settingsDecision.target).toBe("web");
|
|
expect(new URL(settingsDecision.request.url).pathname).toBe("/settings/profile");
|
|
expect(new URL(settingsDecision.request.url).search).toBe("?tab=mfa");
|
|
expect(new URL(settingsDecision.request.url).hash).toBe("#codes");
|
|
expect(new URL(metricsDataDecision.request.url).pathname).toBe("/metrics/summary.data");
|
|
});
|
|
|
|
test.each([
|
|
["/api?tab=overview", "/api"],
|
|
["/api/keys?filter=active#keys", "/api/keys"],
|
|
["/api/keys/new", "/api/keys/new"],
|
|
["/api/keys/api_key_1", "/api/keys/api_key_1"],
|
|
["/api/logs", "/api/logs"],
|
|
["/api/docs", "/api/docs"],
|
|
["/api/credits", "/api/credits"],
|
|
["/api/keys.data?_routes=routes%2Fadmin%2Fapi%2Fkeys.__host", "/api/keys.data"],
|
|
])("dispatches canonical admin API section route %s to the web runtime", (route, expectedPathname) => {
|
|
const decision = expectDispatchDecision(new Request(`http://admin.localhost${route}`));
|
|
|
|
expect(decision.target).toBe("web");
|
|
expect(new URL(decision.request.url).pathname).toBe(expectedPathname);
|
|
});
|
|
|
|
test("redirects API host unversioned aliases to v1 paths before dispatch", () => {
|
|
expect(getHostRoutingRuntimeDecision(new Request("http://api.localhost/health?check=deep#status"))).toEqual({
|
|
type: "redirect",
|
|
status: 308,
|
|
location: "http://api.localhost/v1/health?check=deep#status",
|
|
});
|
|
expect(getHostRoutingRuntimeDecision(new Request("http://api.localhost/accounts?cursor=next"))).toEqual({
|
|
type: "redirect",
|
|
status: 308,
|
|
location: "http://api.localhost/v1/accounts?cursor=next",
|
|
});
|
|
});
|
|
|
|
test("redirects public web paths away from the API host", () => {
|
|
const decision = getHostRoutingRuntimeDecision(new Request("http://api.localhost/login?redirectTo=%2Facme%2Fdashboard"));
|
|
|
|
expect(decision).toEqual({
|
|
type: "redirect",
|
|
status: 308,
|
|
location: "http://app.localhost/login?redirectTo=%2Facme%2Fdashboard",
|
|
});
|
|
});
|
|
|
|
test("does not double-prefix API internal paths", () => {
|
|
const apiDecision = expectDispatchDecision(new Request("http://api.localhost/v1/health?check=deep#status"));
|
|
|
|
expect(new URL(apiDecision.request.url).pathname).toBe("/v1/health");
|
|
});
|
|
|
|
test("rejects removed root app paths instead of redirecting compatibility routes", () => {
|
|
const decision = getHostRoutingRuntimeDecision(new Request("http://localhost/app/acme/settings?tab=profile#billing"));
|
|
|
|
expect(decision).toEqual({
|
|
type: "reject",
|
|
status: 404,
|
|
});
|
|
});
|
|
|
|
test("rejects removed admin paths on root and app hosts", () => {
|
|
expect(getHostRoutingRuntimeDecision(new Request("http://localhost/admin/settings?tab=users#active"))).toEqual({
|
|
type: "reject",
|
|
status: 404,
|
|
});
|
|
expect(getHostRoutingRuntimeDecision(new Request("http://app.localhost/admin/settings?tab=users#active"))).toEqual({
|
|
type: "reject",
|
|
status: 404,
|
|
});
|
|
});
|
|
|
|
test("redirects canonical paths away from the wrong host before dispatch", () => {
|
|
expect(getHostRoutingRuntimeDecision(new Request("http://app.localhost/dashboard?tab=activity#events"))).toEqual({
|
|
type: "redirect",
|
|
status: 308,
|
|
location: "http://admin.localhost/dashboard?tab=activity#events",
|
|
});
|
|
expect(getHostRoutingRuntimeDecision(new Request("http://app.localhost/accounts/users?filter=active"))).toEqual({
|
|
type: "redirect",
|
|
status: 308,
|
|
location: "http://admin.localhost/accounts/users?filter=active",
|
|
});
|
|
expect(getHostRoutingRuntimeDecision(new Request("http://admin.localhost/acme/dashboard?tab=activity#events"))).toEqual({
|
|
type: "redirect",
|
|
status: 308,
|
|
location: "http://app.localhost/acme/dashboard?tab=activity#events",
|
|
});
|
|
expect(getHostRoutingRuntimeDecision(new Request("http://admin.localhost/acme"))).toEqual({
|
|
type: "redirect",
|
|
status: 308,
|
|
location: "http://app.localhost/acme",
|
|
});
|
|
expect(getHostRoutingRuntimeDecision(new Request("http://api.localhost/acme"))).toEqual({
|
|
type: "redirect",
|
|
status: 308,
|
|
location: "http://app.localhost/acme",
|
|
});
|
|
expect(getHostRoutingRuntimeDecision(new Request("http://app.localhost/v1/health"))).toEqual({
|
|
type: "redirect",
|
|
status: 308,
|
|
location: "http://api.localhost/v1/health",
|
|
});
|
|
});
|
|
|
|
test("dispatches API operational routes to the API runtime", () => {
|
|
const healthDecision = expectDispatchDecision(new Request("http://api.localhost/healthz"));
|
|
const readyDecision = expectDispatchDecision(new Request("http://api.localhost/readyz"));
|
|
|
|
expect(healthDecision.target).toBe("api");
|
|
expect(new URL(healthDecision.request.url).pathname).toBe("/healthz");
|
|
expect(readyDecision.target).toBe("api");
|
|
expect(new URL(readyDecision.request.url).pathname).toBe("/readyz");
|
|
});
|
|
});
|