mirror of
https://github.com/oblien/openship.git
synced 2026-10-15 05:19:34 +08:00
167 lines
6.8 KiB
JavaScript
167 lines
6.8 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { test } from "node:test";
|
|
import { moduleHttpSurface } from "./docs-http.mjs";
|
|
import { httpSurface } from "./docs-surface.mjs";
|
|
|
|
const router =
|
|
'const r = secureRouter(new Hono(), { module: "system", basePath: "/api/system", localOnly: true });';
|
|
const parse = (body) => moduleHttpSurface("routes.ts", router + "\n" + body);
|
|
|
|
test("documents root-mounted API-only landing pages without inventing an API prefix", () => {
|
|
const routes = moduleHttpSurface(
|
|
"access-page.ts",
|
|
[
|
|
'const r = secureRouter(new Hono(), { module: "instance-access", basePath: "", localOnly: true });',
|
|
'for (const path of ["/", "/login", "/accept-invite/:id"]) {',
|
|
' r.public("get", path, { reason: "Static landing page" }, handler);',
|
|
"}",
|
|
].join("\n"),
|
|
);
|
|
assert.deepEqual(
|
|
routes.map((route) => route.path),
|
|
["/", "/login", "/accept-invite/:id"],
|
|
);
|
|
assert.ok(routes.every((route) => route.localOnly));
|
|
});
|
|
|
|
test("resolves correlated route aliases and shared permission metadata without executing handlers", () => {
|
|
const routes = parse(
|
|
[
|
|
'throw new Error("Never execute route modules");',
|
|
'const read = { tag: "server:read" } as const;',
|
|
'const admin = { ...read, tag: "server:admin" } satisfies Spec;',
|
|
'const aliases = [["/networks", ""], ["/clusters", "network-"]] as const;',
|
|
"for (const [base, prefix] of aliases) {",
|
|
" const operations = `${base}/${prefix}operations`;",
|
|
' r.get(operations + "/:id", read, handler);',
|
|
" r.post(`${base}/${prefix}plans`, { ...admin, body: schema() }, handler);",
|
|
"}",
|
|
].join("\n"),
|
|
);
|
|
assert.deepEqual(
|
|
routes.map(({ method, path, access }) => ({ method, path, access })),
|
|
[
|
|
{ method: "GET", path: "/api/system/networks/operations/:id", access: "server:read" },
|
|
{ method: "POST", path: "/api/system/networks/plans", access: "server:admin" },
|
|
{ method: "GET", path: "/api/system/clusters/network-operations/:id", access: "server:read" },
|
|
{ method: "POST", path: "/api/system/clusters/network-plans", access: "server:admin" },
|
|
],
|
|
);
|
|
assert.ok(routes.every((route) => route.localOnly && route.module === "system"));
|
|
});
|
|
|
|
test("constant scopes and spread ordering preserve the route's actual permissions", () => {
|
|
const routes = parse(
|
|
[
|
|
'const tag = "server:read";',
|
|
"const spec = { tag };",
|
|
"{",
|
|
' const tag = "server:admin";',
|
|
' const path = "/inner";',
|
|
" r.post(path, { ...spec, tag }, handler);",
|
|
"}",
|
|
'r.get("/outer", { tag: "server:admin", ...spec }, handler);',
|
|
'function unrelated(r) { r.get("/not-a-route"); }',
|
|
'function destructured({ r }) { r.get("/not-a-route"); }',
|
|
].join("\n"),
|
|
);
|
|
assert.deepEqual(
|
|
routes.map(({ path, access }) => [path, access]),
|
|
[
|
|
["/api/system/inner", "server:admin"],
|
|
["/api/system/outer", "server:read"],
|
|
],
|
|
);
|
|
});
|
|
|
|
test("retains raw multi-method routes, internal access and public handler authentication", () => {
|
|
const routes = moduleHttpSurface(
|
|
"routes.ts",
|
|
[
|
|
"const authRoutes = new Hono();",
|
|
'authRoutes.on(["GET", "POST"], "/*", handler);',
|
|
'const r = secureRouter(new Hono(), { module: "system", basePath: "/api/system" });',
|
|
'r.public("get", "/callback", { reason: "Callback verifies its token" }, handler);',
|
|
'r.get("/internal", { tag: "server:read", localOnly: true }, internalAuth, handler);',
|
|
].join("\n"),
|
|
new Map([["authRoutes", "/api/auth"]]),
|
|
);
|
|
assert.deepEqual(
|
|
routes.map(({ method, path, access, localOnly }) => ({ method, path, access, localOnly })),
|
|
[
|
|
{ method: "GET", path: "/api/auth/*", access: "Handler authentication", localOnly: false },
|
|
{ method: "POST", path: "/api/auth/*", access: "Handler authentication", localOnly: false },
|
|
{
|
|
method: "GET",
|
|
path: "/api/system/callback",
|
|
access: "Handler authentication",
|
|
localOnly: false,
|
|
},
|
|
{ method: "GET", path: "/api/system/internal", access: "Internal operator", localOnly: true },
|
|
],
|
|
);
|
|
});
|
|
|
|
test("fails instead of silently omitting or guessing dynamic routes and permissions", () => {
|
|
for (const [body, error] of [
|
|
['r.get(runtimePath(), { tag: "server:read" }, handler);', /Unresolved route path/],
|
|
[
|
|
'let path = "/mutable"; r.get(path, { tag: "server:read" }, handler);',
|
|
/Unresolved route path/,
|
|
],
|
|
[
|
|
'for (const path of runtimePaths()) { r.get("/fixed", { tag: "server:read" }, handler); }',
|
|
/Unresolved route loop/,
|
|
],
|
|
['r.get("/", { ...runtimeSpec() }, handler);', /Unresolved route permission/],
|
|
[
|
|
'r.get("/", { tag: "server:read", ...runtimeSpec() }, handler);',
|
|
/Unresolved route permission/,
|
|
],
|
|
[
|
|
'const spec = { tag: "server:read" }; r.get("/", { ...spec, get tag() { return runtimeTag(); } }, handler);',
|
|
/Unresolved route permission/,
|
|
],
|
|
[
|
|
'r.get("/", { tag: "server:read", localOnly: runtimeFlag() }, handler);',
|
|
/Unresolved route availability/,
|
|
],
|
|
['r.public(runtimeMethod(), "/", {}, handler);', /Unresolved route method/],
|
|
])
|
|
assert.throws(() => parse(body), error);
|
|
});
|
|
|
|
test("the real network inventory includes both generations with their permission tags", () => {
|
|
const routes = httpSurface();
|
|
const find = (method, path) =>
|
|
routes.filter((route) => route.method === method && route.path === path);
|
|
for (const path of [
|
|
"/api/system/networks/operations/:operationId",
|
|
"/api/system/clusters/network-operations/:operationId",
|
|
"/api/system/networks/preparations/:preparationId/stream",
|
|
"/api/system/clusters/network-preparations/:preparationId/stream",
|
|
"/api/system/compute-clusters/:id",
|
|
]) {
|
|
const matches = find("GET", path);
|
|
assert.equal(matches.length, 1, path);
|
|
assert.equal(matches[0].access, "server:read");
|
|
assert.equal(matches[0].localOnly, true);
|
|
}
|
|
for (const path of ["/api/system/networks/plans", "/api/system/clusters/network-plans"]) {
|
|
const matches = find("POST", path);
|
|
assert.equal(matches.length, 1, path);
|
|
assert.equal(matches[0].access, "server:admin");
|
|
}
|
|
assert.equal(find("GET", "/.well-known/oauth-protected-resource/api/mcp").length, 1);
|
|
});
|
|
|
|
test("Cloud product telemetry does not shadow the authenticated traffic analytics relay", () => {
|
|
const routes = httpSurface();
|
|
const relay = routes.filter((route) => route.method === "POST" && route.path === "/api/cloud/analytics");
|
|
assert.equal(relay.length, 1, "the existing Cloud analytics relay must have exactly one handler");
|
|
assert.equal(relay[0].access, "cloud:write");
|
|
const telemetry = routes.filter((route) => route.method === "POST" && route.path === "/api/cloud/telemetry");
|
|
assert.equal(telemetry.length, 1);
|
|
assert.equal(telemetry[0].access, "Handler authentication");
|
|
});
|