Files
Ekklesia/core/tests/module-runtime.test.ts
T
ekklesiaandClaude Sonnet 5 d3c6bd73d3 fix: enforce API request limits
Add a single platform-owned request-body policy (hono/body-limit) with
small, named limits for Better Auth and Core's grant-mutation endpoint, and
a new required maxBodyBytes field on the executable module route contract so
every module route makes a conscious no-body-or-bounded-body choice, capped
at a platform ceiling. Every oversized request gets one sanitized 413
contract and never reaches business logic, authorization, or a handler.
Also configure finite Node header/request/keep-alive timeouts, including the
non-obvious connectionsCheckingInterval needed for them to take effect
promptly, so a slow-header or slow-body connection cannot hold a socket
indefinitely.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-07-26 12:29:24 -05:00

1019 lines
40 KiB
TypeScript

import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import type { ModuleDatabase, ModuleRuntimeServices } from "@ekklesia/contracts";
import { afterEach, describe, expect, it, vi } from "vitest";
import { discoverModules } from "../src/modules/discovery.js";
import {
ModuleDiscoveryError,
ModuleRuntimeError,
type ModuleRuntimeIssueCode,
startDiscoveredModuleRuntime,
startModuleRuntime,
} from "../src/modules/index.js";
vi.mock("../src/modules/discovery.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("../src/modules/discovery.js")>();
return { ...actual, discoverModules: vi.fn(actual.discoverModules) };
});
interface RuntimeModuleFixture {
readonly directory?: string;
readonly manifest?: Record<string, unknown>;
readonly packageJson?: Record<string, unknown>;
readonly serverSource: string;
}
const temporaryDirectories: string[] = [];
const eventSymbols: symbol[] = [];
afterEach(async () => {
const globalValues = globalThis as unknown as Record<PropertyKey, unknown>;
for (const symbol of eventSymbols.splice(0)) {
delete globalValues[symbol];
}
await Promise.all(
temporaryDirectories.splice(0).map((directory) => rm(directory, { recursive: true })),
);
});
async function createModulesDirectory(): Promise<string> {
const directory = await mkdtemp(path.join(tmpdir(), "ekklesia-runtime-"));
temporaryDirectories.push(directory);
return directory;
}
function manifestFor(
id: string,
options: {
capabilities?: Array<{ id: string; version: string }>;
dependencies?: Record<string, unknown>;
permissions?: Array<{ id: string; baseline: string; description: string }>;
} = {},
): Record<string, unknown> {
return {
manifestVersion: 3,
id,
name: `${id} module`,
version: "0.1.0",
compatibility: {
platform: "^0.1.0",
moduleSdk: "^0.1.0",
},
capabilities: options.capabilities ?? [],
permissions: options.permissions ?? [],
...(options.dependencies ? { dependencies: options.dependencies } : {}),
entrypoints: {
server: "./dist/server/index.js",
},
};
}
async function createRuntimeModule(
modulesDirectory: string,
id: string,
fixture: RuntimeModuleFixture,
): Promise<void> {
const directoryName = fixture.directory ?? id;
const moduleDirectory = path.join(modulesDirectory, directoryName);
await mkdir(path.join(moduleDirectory, "dist/server"), { recursive: true });
await Promise.all([
writeFile(
path.join(moduleDirectory, "package.json"),
JSON.stringify(
fixture.packageJson ?? {
name: `@test/module-${id}`,
version: "0.1.0",
type: "module",
ekklesia: { layer: "module" },
},
),
),
writeFile(
path.join(moduleDirectory, "ekklesia.module.json"),
JSON.stringify(fixture.manifest ?? manifestFor(id)),
),
writeFile(path.join(moduleDirectory, "dist/server/index.js"), fixture.serverSource),
]);
}
function createEventRecorder(): { events: string[]; expression: string } {
const key = `ekklesia.runtime.test.${eventSymbols.length}.${Date.now()}.${Math.random()}`;
const symbol = Symbol.for(key);
const events: string[] = [];
(globalThis as unknown as Record<PropertyKey, unknown>)[symbol] = events;
eventSymbols.push(symbol);
return {
events,
expression: `globalThis[Symbol.for(${JSON.stringify(key)})]`,
};
}
/** Same globalThis-symbol technique as `createEventRecorder`, but for capturing an actual object
* reference (such as `context.services`) pushed from inside a dynamically imported fixture module,
* rather than a string label — dynamic `import()` of a file:// URL still shares this process's
* heap, so a reference pushed from the fixture and read back here is the exact same object. */
function createReferenceRecorder(): { values: unknown[]; expression: string } {
const key = `ekklesia.runtime.test.ref.${eventSymbols.length}.${Date.now()}.${Math.random()}`;
const symbol = Symbol.for(key);
const values: unknown[] = [];
(globalThis as unknown as Record<PropertyKey, unknown>)[symbol] = values;
eventSymbols.push(symbol);
return {
values,
expression: `globalThis[Symbol.for(${JSON.stringify(key)})]`,
};
}
/** A small typed fake `ModuleDatabase` — no PostgreSQL required for these unit tests, which only
* ever assert on context wiring and object identity, never on actual query behavior. */
function fakeModuleDatabase(): ModuleDatabase {
return Object.freeze({
delete: vi.fn(),
execute: vi.fn(),
insert: vi.fn(),
select: vi.fn(),
selectDistinct: vi.fn(),
selectDistinctOn: vi.fn(),
transaction: vi.fn(),
update: vi.fn(),
}) as unknown as ModuleDatabase;
}
function fakeModuleRuntimeServices(): ModuleRuntimeServices {
return Object.freeze({ database: fakeModuleDatabase() });
}
function runtimeOptions(
modulesDirectory: string,
services: ModuleRuntimeServices = fakeModuleRuntimeServices(),
) {
return {
moduleSdkVersion: "0.1.0",
modulesDirectory,
platformVersion: "0.1.0",
services,
};
}
function runtimeIssueCodes(error: ModuleRuntimeError): ModuleRuntimeIssueCode[] {
return error.issues.map((issue) => issue.code);
}
async function captureRuntimeError(modulesDirectory: string): Promise<ModuleRuntimeError> {
try {
await startModuleRuntime(runtimeOptions(modulesDirectory));
} catch (error) {
if (error instanceof ModuleRuntimeError) {
return error;
}
throw error;
}
throw new Error("Expected module runtime startup to fail.");
}
describe("module runtime", () => {
it("discovers, loads, registers, starts, and stops dependency-first modules end to end", async () => {
const modulesDirectory = await createModulesDirectory();
const recorder = createEventRecorder();
await createRuntimeModule(modulesDirectory, "people", {
manifest: manifestFor("people", {
capabilities: [{ id: "people.records", version: "2.1.0" }],
}),
serverSource: `
export default {
id: "people",
version: "0.1.0",
register({ capabilities }) {
${recorder.expression}.push("people:register");
capabilities.provide("people.records", { kind: "people-records" });
},
start() { ${recorder.expression}.push("people:start"); },
stop() { ${recorder.expression}.push("people:stop"); }
};
`,
});
await createRuntimeModule(modulesDirectory, "calendar", {
manifest: manifestFor("calendar", {
capabilities: [{ id: "calendar.events", version: "1.0.0" }],
dependencies: {
capabilities: { required: { "people.records": "^2.0.0" } },
},
}),
serverSource: `
export default {
id: "calendar",
version: "0.1.0",
register({ capabilities }) {
${recorder.expression}.push("calendar:register");
const people = capabilities.resolve("people.records");
if (!people || people.version !== "2.1.0") throw new Error("people unavailable");
capabilities.provide("calendar.events", { kind: "calendar-events" });
},
start({ capabilities }) {
if (!capabilities.resolve("people.records")) throw new Error("people unavailable");
${recorder.expression}.push("calendar:start");
},
stop() { ${recorder.expression}.push("calendar:stop"); }
};
`,
});
const runtime = await startModuleRuntime(runtimeOptions(modulesDirectory));
expect(recorder.events).toEqual([
"people:register",
"calendar:register",
"people:start",
"calendar:start",
]);
expect(runtime.modules.map((module) => module.id)).toEqual(["people", "calendar"]);
expect(runtime.capabilities.resolve("people.records")).toMatchObject({
moduleId: "people",
version: "2.1.0",
});
expect(runtime.capabilities.list().map((capability) => capability.id)).toEqual([
"calendar.events",
"people.records",
]);
await Promise.all([runtime.stop(), runtime.stop()]);
expect(recorder.events.slice(-2)).toEqual(["calendar:stop", "people:stop"]);
});
it("completes discovery before importing any server entrypoint", async () => {
const modulesDirectory = await createModulesDirectory();
const recorder = createEventRecorder();
await createRuntimeModule(modulesDirectory, "valid", {
serverSource: `${recorder.expression}.push("valid:import"); export default { id: "valid", version: "0.1.0" };`,
});
await createRuntimeModule(modulesDirectory, "invalid", {
manifest: { ...manifestFor("invalid"), manifestVersion: 1 },
serverSource: `${recorder.expression}.push("invalid:import"); export default { id: "invalid", version: "0.1.0" };`,
});
await expect(startModuleRuntime(runtimeOptions(modulesDirectory))).rejects.toBeInstanceOf(
ModuleDiscoveryError,
);
expect(recorder.events).toEqual([]);
});
it("validates every imported definition before running registration hooks", async () => {
const modulesDirectory = await createModulesDirectory();
const recorder = createEventRecorder();
await createRuntimeModule(modulesDirectory, "alpha", {
serverSource: `export default {
id: "alpha", version: "0.1.0",
register() { ${recorder.expression}.push("alpha:register"); }
};`,
});
await createRuntimeModule(modulesDirectory, "beta", {
serverSource: `export default { id: "wrong-id", version: "0.1.0" };`,
});
await createRuntimeModule(modulesDirectory, "gamma", {
serverSource: `export default { id: "gamma", version: "9.0.0" };`,
});
const error = await captureRuntimeError(modulesDirectory);
expect(runtimeIssueCodes(error)).toEqual([
"MODULE_RUNTIME_ID_MISMATCH",
"MODULE_RUNTIME_VERSION_MISMATCH",
]);
expect(recorder.events).toEqual([]);
});
it("rejects incomplete and undeclared capability registration before startup", async () => {
const missingDirectory = await createModulesDirectory();
await createRuntimeModule(missingDirectory, "missing", {
manifest: manifestFor("missing", {
capabilities: [{ id: "missing.records", version: "1.0.0" }],
}),
serverSource: `export default { id: "missing", version: "0.1.0", register() {} };`,
});
let error = await captureRuntimeError(missingDirectory);
expect(runtimeIssueCodes(error)).toEqual(["MODULE_CAPABILITY_UNREGISTERED"]);
const undeclaredDirectory = await createModulesDirectory();
await createRuntimeModule(undeclaredDirectory, "undeclared", {
serverSource: `export default {
id: "undeclared", version: "0.1.0",
register({ capabilities }) { capabilities.provide("undeclared.extra", {}); }
};`,
});
error = await captureRuntimeError(undeclaredDirectory);
expect(runtimeIssueCodes(error)).toEqual(["MODULE_CAPABILITY_UNDECLARED"]);
});
it("restricts module capability lookup to declared ownership and dependencies", async () => {
const modulesDirectory = await createModulesDirectory();
await createRuntimeModule(modulesDirectory, "snooper", {
serverSource: `export default {
id: "snooper", version: "0.1.0",
register({ capabilities }) { capabilities.resolve("people.records"); }
};`,
});
const error = await captureRuntimeError(modulesDirectory);
expect(runtimeIssueCodes(error)).toEqual(["MODULE_CAPABILITY_ACCESS_UNDECLARED"]);
});
it("rejects malformed runtime capability IDs without echoing their contents", async () => {
const modulesDirectory = await createModulesDirectory();
const unsafeCapabilityId = "secret-token at /machine/private/path";
await createRuntimeModule(modulesDirectory, "unsafe", {
serverSource: `export default {
id: "unsafe", version: "0.1.0",
register({ capabilities }) {
capabilities.resolve(${JSON.stringify(unsafeCapabilityId)});
}
};`,
});
const error = await captureRuntimeError(modulesDirectory);
expect(runtimeIssueCodes(error)).toEqual(["MODULE_CAPABILITY_ID_INVALID"]);
expect(`${error.message}\n${error.stack}\n${JSON.stringify(error.issues)}`).not.toContain(
unsafeCapabilityId,
);
expect(error.stack).not.toContain(modulesDirectory);
});
it("rolls back started modules in reverse order and aggregates cleanup failures", async () => {
const modulesDirectory = await createModulesDirectory();
const recorder = createEventRecorder();
for (const [id, dependency, stopFails] of [
["one", undefined, false],
["two", "one", true],
["three", "two", false],
] as const) {
await createRuntimeModule(modulesDirectory, id, {
manifest: manifestFor(id, {
...(dependency
? { dependencies: { modules: { required: { [dependency]: "^0.1.0" } } } }
: {}),
}),
serverSource: `export default {
id: ${JSON.stringify(id)}, version: "0.1.0",
start() {
${recorder.expression}.push(${JSON.stringify(`${id}:start`)});
${id === "three" ? 'throw new Error("secret-token at /private/path");' : ""}
},
stop() {
${recorder.expression}.push(${JSON.stringify(`${id}:stop`)});
${stopFails ? 'throw new Error("rollback secret");' : ""}
}
};`,
});
}
const error = await captureRuntimeError(modulesDirectory);
expect(runtimeIssueCodes(error)).toEqual(["MODULE_START_FAILED", "MODULE_ROLLBACK_FAILED"]);
expect(recorder.events).toEqual([
"one:start",
"two:start",
"three:start",
"two:stop",
"one:stop",
]);
expect(JSON.stringify(error.issues)).not.toContain("secret");
expect(JSON.stringify(error.issues)).not.toContain("/private/path");
});
it("continues reverse shutdown after a module stop failure", async () => {
const modulesDirectory = await createModulesDirectory();
const recorder = createEventRecorder();
await createRuntimeModule(modulesDirectory, "provider", {
serverSource: `export default {
id: "provider", version: "0.1.0",
stop() { ${recorder.expression}.push("provider:stop"); }
};`,
});
await createRuntimeModule(modulesDirectory, "consumer", {
manifest: manifestFor("consumer", {
dependencies: { modules: { required: { provider: "^0.1.0" } } },
}),
serverSource: `export default {
id: "consumer", version: "0.1.0",
stop() { ${recorder.expression}.push("consumer:stop"); throw new Error("hidden"); }
};`,
});
const runtime = await startModuleRuntime(runtimeOptions(modulesDirectory));
await expect(runtime.stop()).rejects.toMatchObject({
issues: [expect.objectContaining({ code: "MODULE_STOP_FAILED", moduleId: "consumer" })],
});
expect(recorder.events).toEqual(["consumer:stop", "provider:stop"]);
});
it("reports import failures without leaking exception text or machine paths", async () => {
const modulesDirectory = await createModulesDirectory();
await createRuntimeModule(modulesDirectory, "broken", {
serverSource: `throw new Error("database-password at /machine/private/path");`,
});
const error = await captureRuntimeError(modulesDirectory);
expect(runtimeIssueCodes(error)).toEqual(["MODULE_RUNTIME_IMPORT_FAILED"]);
expect(JSON.stringify(error.issues)).not.toContain("database-password");
expect(JSON.stringify(error.issues)).not.toContain(modulesDirectory);
});
});
function noopHandlerSource(): string {
return "() => new Response(null, { status: 204 })";
}
function permissionDeclaration(
id: string,
baseline: "authenticated" | "public" | "restricted" = "restricted",
): { id: string; baseline: string; description: string } {
return { id, baseline, description: `Test permission ${id}.` };
}
describe("module HTTP routes", () => {
it("registers one route and publishes it on the started runtime", async () => {
const modulesDirectory = await createModulesDirectory();
await createRuntimeModule(modulesDirectory, "single", {
manifest: manifestFor("single", {
permissions: [permissionDeclaration("single.items.read")],
}),
serverSource: `export default {
id: "single", version: "0.1.0",
register({ http }) {
http.route({
method: "GET",
path: "/items",
permission: "single.items.read",
maxBodyBytes: 1024,
handler: ${noopHandlerSource()},
});
}
};`,
});
const runtime = await startModuleRuntime(runtimeOptions(modulesDirectory));
expect(runtime.routes).toHaveLength(1);
expect(runtime.routes[0]).toMatchObject({
moduleId: "single",
method: "GET",
path: "/items",
permission: "single.items.read",
});
await runtime.stop();
});
it("retains registration order within a module and discovery order across modules", async () => {
const modulesDirectory = await createModulesDirectory();
await createRuntimeModule(modulesDirectory, "alpha", {
manifest: manifestFor("alpha", {
permissions: [
permissionDeclaration("alpha.first.read"),
permissionDeclaration("alpha.second.read"),
],
}),
serverSource: `export default {
id: "alpha", version: "0.1.0",
register({ http }) {
http.route({ method: "GET", path: "/first", permission: "alpha.first.read", maxBodyBytes: 1024, handler: ${noopHandlerSource()} });
http.route({ method: "GET", path: "/second", permission: "alpha.second.read", maxBodyBytes: 1024, handler: ${noopHandlerSource()} });
}
};`,
});
await createRuntimeModule(modulesDirectory, "beta", {
manifest: manifestFor("beta", {
permissions: [permissionDeclaration("beta.third.read")],
}),
serverSource: `export default {
id: "beta", version: "0.1.0",
register({ http }) {
http.route({ method: "GET", path: "/third", permission: "beta.third.read", maxBodyBytes: 1024, handler: ${noopHandlerSource()} });
}
};`,
});
const runtime = await startModuleRuntime(runtimeOptions(modulesDirectory));
expect(
runtime.routes.map((route) => `${route.moduleId}:${route.method}:${route.path}`),
).toEqual(["alpha:GET:/first", "alpha:GET:/second", "beta:GET:/third"]);
await runtime.stop();
});
it("allows the same path under different methods", async () => {
const modulesDirectory = await createModulesDirectory();
await createRuntimeModule(modulesDirectory, "same-path", {
manifest: manifestFor("same-path", {
permissions: [
permissionDeclaration("same-path.items.read"),
permissionDeclaration("same-path.items.write"),
],
}),
serverSource: `export default {
id: "same-path", version: "0.1.0",
register({ http }) {
http.route({ method: "GET", path: "/items", permission: "same-path.items.read", maxBodyBytes: 1024, handler: ${noopHandlerSource()} });
http.route({ method: "POST", path: "/items", permission: "same-path.items.write", maxBodyBytes: 1024, handler: ${noopHandlerSource()} });
}
};`,
});
const runtime = await startModuleRuntime(runtimeOptions(modulesDirectory));
expect(runtime.routes.map((route) => `${route.method} ${route.path}`)).toEqual([
"GET /items",
"POST /items",
]);
await runtime.stop();
});
it("rejects a duplicate method/path route registered by the same module", async () => {
const modulesDirectory = await createModulesDirectory();
await createRuntimeModule(modulesDirectory, "dup", {
manifest: manifestFor("dup", {
permissions: [permissionDeclaration("dup.items.read")],
}),
serverSource: `export default {
id: "dup", version: "0.1.0",
register({ http }) {
http.route({ method: "GET", path: "/items", permission: "dup.items.read", maxBodyBytes: 1024, handler: ${noopHandlerSource()} });
http.route({ method: "GET", path: "/items", permission: "dup.items.read", maxBodyBytes: 1024, handler: ${noopHandlerSource()} });
}
};`,
});
const error = await captureRuntimeError(modulesDirectory);
expect(runtimeIssueCodes(error)).toEqual(["MODULE_ROUTE_DUPLICATE"]);
});
it("collides parameter-name-only path variants under the same method", async () => {
const modulesDirectory = await createModulesDirectory();
await createRuntimeModule(modulesDirectory, "param-shape", {
manifest: manifestFor("param-shape", {
permissions: [permissionDeclaration("param-shape.events.read")],
}),
serverSource: `export default {
id: "param-shape", version: "0.1.0",
register({ http }) {
http.route({ method: "GET", path: "/events/:eventId", permission: "param-shape.events.read", maxBodyBytes: 1024, handler: ${noopHandlerSource()} });
http.route({ method: "GET", path: "/events/:id", permission: "param-shape.events.read", maxBodyBytes: 1024, handler: ${noopHandlerSource()} });
}
};`,
});
const error = await captureRuntimeError(modulesDirectory);
expect(runtimeIssueCodes(error)).toEqual(["MODULE_ROUTE_DUPLICATE"]);
});
it("fails a malformed route with its specific structured issue code", async () => {
const badMethodDirectory = await createModulesDirectory();
await createRuntimeModule(badMethodDirectory, "bad-method", {
manifest: manifestFor("bad-method", {
permissions: [permissionDeclaration("bad-method.items.read")],
}),
serverSource: `export default {
id: "bad-method", version: "0.1.0",
register({ http }) {
http.route({ method: "get", path: "/items", permission: "bad-method.items.read", maxBodyBytes: 1024, handler: ${noopHandlerSource()} });
}
};`,
});
expect(runtimeIssueCodes(await captureRuntimeError(badMethodDirectory))).toEqual([
"MODULE_ROUTE_METHOD_INVALID",
]);
const badPathDirectory = await createModulesDirectory();
await createRuntimeModule(badPathDirectory, "bad-path", {
manifest: manifestFor("bad-path", {
permissions: [permissionDeclaration("bad-path.items.read")],
}),
serverSource: `export default {
id: "bad-path", version: "0.1.0",
register({ http }) {
http.route({ method: "GET", path: "items", permission: "bad-path.items.read", maxBodyBytes: 1024, handler: ${noopHandlerSource()} });
}
};`,
});
expect(runtimeIssueCodes(await captureRuntimeError(badPathDirectory))).toEqual([
"MODULE_ROUTE_PATH_INVALID",
]);
const badMaxBodyBytesDirectory = await createModulesDirectory();
await createRuntimeModule(badMaxBodyBytesDirectory, "bad-max-body-bytes", {
manifest: manifestFor("bad-max-body-bytes", {
permissions: [permissionDeclaration("bad-max-body-bytes.items.read")],
}),
serverSource: `export default {
id: "bad-max-body-bytes", version: "0.1.0",
register({ http }) {
http.route({ method: "GET", path: "/items", permission: "bad-max-body-bytes.items.read", maxBodyBytes: -1, handler: ${noopHandlerSource()} });
}
};`,
});
expect(runtimeIssueCodes(await captureRuntimeError(badMaxBodyBytesDirectory))).toEqual([
"MODULE_ROUTE_MAX_BODY_BYTES_INVALID",
]);
const badDefinitionDirectory = await createModulesDirectory();
await createRuntimeModule(badDefinitionDirectory, "bad-shape", {
manifest: manifestFor("bad-shape", {
permissions: [permissionDeclaration("bad-shape.items.read")],
}),
serverSource: `export default {
id: "bad-shape", version: "0.1.0",
register({ http }) {
http.route({
method: "GET",
path: "/items",
permission: "bad-shape.items.read",
baseline: "public",
handler: ${noopHandlerSource()},
});
}
};`,
});
expect(runtimeIssueCodes(await captureRuntimeError(badDefinitionDirectory))).toEqual([
"MODULE_ROUTE_DEFINITION_INVALID",
]);
});
it("rejects a route permission the registering module does not itself declare", async () => {
const modulesDirectory = await createModulesDirectory();
await createRuntimeModule(modulesDirectory, "undeclared", {
manifest: manifestFor("undeclared"),
serverSource: `export default {
id: "undeclared", version: "0.1.0",
register({ http }) {
http.route({ method: "GET", path: "/items", permission: "undeclared.items.read", maxBodyBytes: 1024, handler: ${noopHandlerSource()} });
}
};`,
});
const error = await captureRuntimeError(modulesDirectory);
expect(runtimeIssueCodes(error)).toEqual(["MODULE_ROUTE_PERMISSION_UNDECLARED"]);
});
it("rejects a module gating a route with a Core-owned permission it cannot declare", async () => {
const modulesDirectory = await createModulesDirectory();
await createRuntimeModule(modulesDirectory, "coreattempt", {
manifest: manifestFor("coreattempt"),
serverSource: `export default {
id: "coreattempt", version: "0.1.0",
register({ http }) {
http.route({
method: "GET",
path: "/items",
permission: "core.authorization.grants.manage",
maxBodyBytes: 1024,
handler: ${noopHandlerSource()},
});
}
};`,
});
const error = await captureRuntimeError(modulesDirectory);
expect(runtimeIssueCodes(error)).toEqual(["MODULE_ROUTE_PERMISSION_UNDECLARED"]);
});
it("rejects a module gating a route with another module's permission", async () => {
const modulesDirectory = await createModulesDirectory();
await createRuntimeModule(modulesDirectory, "alpha", {
manifest: manifestFor("alpha", {
permissions: [permissionDeclaration("alpha.items.read")],
}),
serverSource: `export default { id: "alpha", version: "0.1.0" };`,
});
await createRuntimeModule(modulesDirectory, "beta", {
manifest: manifestFor("beta"),
serverSource: `export default {
id: "beta", version: "0.1.0",
register({ http }) {
http.route({ method: "GET", path: "/items", permission: "alpha.items.read", maxBodyBytes: 1024, handler: ${noopHandlerSource()} });
}
};`,
});
const error = await captureRuntimeError(modulesDirectory);
expect(runtimeIssueCodes(error)).toEqual(["MODULE_ROUTE_PERMISSION_UNDECLARED"]);
});
it("accepts a declared public, authenticated, or restricted permission without interpreting its baseline", async () => {
const modulesDirectory = await createModulesDirectory();
await createRuntimeModule(modulesDirectory, "baselines", {
manifest: manifestFor("baselines", {
permissions: [
permissionDeclaration("baselines.public.read", "public"),
permissionDeclaration("baselines.authenticated.read", "authenticated"),
permissionDeclaration("baselines.restricted.read", "restricted"),
],
}),
serverSource: `export default {
id: "baselines", version: "0.1.0",
register({ http }) {
http.route({ method: "GET", path: "/public", permission: "baselines.public.read", maxBodyBytes: 1024, handler: ${noopHandlerSource()} });
http.route({ method: "GET", path: "/authenticated", permission: "baselines.authenticated.read", maxBodyBytes: 1024, handler: ${noopHandlerSource()} });
http.route({ method: "GET", path: "/restricted", permission: "baselines.restricted.read", maxBodyBytes: 1024, handler: ${noopHandlerSource()} });
}
};`,
});
const runtime = await startModuleRuntime(runtimeOptions(modulesDirectory));
expect(runtime.routes).toHaveLength(3);
for (const route of runtime.routes) {
expect(Object.keys(route).sort()).toEqual([
"handler",
"maxBodyBytes",
"method",
"moduleId",
"path",
"permission",
]);
}
await runtime.stop();
});
it("never invokes a route handler while registering or starting", async () => {
const modulesDirectory = await createModulesDirectory();
const recorder = createEventRecorder();
await createRuntimeModule(modulesDirectory, "never-invoked", {
manifest: manifestFor("never-invoked", {
permissions: [permissionDeclaration("never-invoked.items.read")],
}),
serverSource: `export default {
id: "never-invoked", version: "0.1.0",
register({ http }) {
http.route({
method: "GET",
path: "/items",
permission: "never-invoked.items.read",
maxBodyBytes: 1024,
handler: () => { ${recorder.expression}.push("handler:called"); return new Response(null, { status: 204 }); },
});
}
};`,
});
const runtime = await startModuleRuntime(runtimeOptions(modulesDirectory));
expect(recorder.events).toEqual([]);
await runtime.stop();
expect(recorder.events).toEqual([]);
});
it("publishes frozen, immutable routes as a frozen route list", async () => {
const modulesDirectory = await createModulesDirectory();
await createRuntimeModule(modulesDirectory, "frozen", {
manifest: manifestFor("frozen", {
permissions: [permissionDeclaration("frozen.items.read")],
}),
serverSource: `export default {
id: "frozen", version: "0.1.0",
register({ http }) {
http.route({ method: "GET", path: "/items", permission: "frozen.items.read", maxBodyBytes: 1024, handler: ${noopHandlerSource()} });
}
};`,
});
const runtime = await startModuleRuntime(runtimeOptions(modulesDirectory));
expect(Object.isFrozen(runtime.routes)).toBe(true);
expect(Object.isFrozen(runtime.routes[0])).toBe(true);
await runtime.stop();
});
it("does not let a module mutate the published route by mutating its own definition afterward", async () => {
const modulesDirectory = await createModulesDirectory();
await createRuntimeModule(modulesDirectory, "mutate", {
manifest: manifestFor("mutate", {
permissions: [
permissionDeclaration("mutate.items.read"),
permissionDeclaration("mutate.other.read"),
],
}),
serverSource: `export default {
id: "mutate", version: "0.1.0",
register({ http }) {
const definition = {
method: "GET",
path: "/items",
permission: "mutate.items.read",
maxBodyBytes: 1024,
handler: ${noopHandlerSource()},
};
http.route(definition);
definition.method = "POST";
definition.path = "/mutated";
definition.permission = "mutate.other.read";
}
};`,
});
const runtime = await startModuleRuntime(runtimeOptions(modulesDirectory));
expect(runtime.routes[0]).toMatchObject({
method: "GET",
path: "/items",
permission: "mutate.items.read",
});
await runtime.stop();
});
it("publishes no runtime when a later module's route registration fails", async () => {
const modulesDirectory = await createModulesDirectory();
await createRuntimeModule(modulesDirectory, "first-ok", {
manifest: manifestFor("first-ok", {
permissions: [permissionDeclaration("first-ok.items.read")],
}),
serverSource: `export default {
id: "first-ok", version: "0.1.0",
register({ http }) {
http.route({ method: "GET", path: "/items", permission: "first-ok.items.read", maxBodyBytes: 1024, handler: ${noopHandlerSource()} });
}
};`,
});
await createRuntimeModule(modulesDirectory, "second-bad", {
manifest: manifestFor("second-bad"),
serverSource: `export default {
id: "second-bad", version: "0.1.0",
register({ http }) {
http.route({ method: "GET", path: "/items", permission: "second-bad.items.read", maxBodyBytes: 1024, handler: ${noopHandlerSource()} });
}
};`,
});
const error = await captureRuntimeError(modulesDirectory);
expect(runtimeIssueCodes(error)).toEqual(["MODULE_ROUTE_PERMISSION_UNDECLARED"]);
expect(error.issues[0]?.moduleId).toBe("second-bad");
});
it("preserves existing rollback behavior when a registered route's module later fails to start", async () => {
const modulesDirectory = await createModulesDirectory();
const recorder = createEventRecorder();
await createRuntimeModule(modulesDirectory, "routed", {
manifest: manifestFor("routed", {
permissions: [permissionDeclaration("routed.items.read")],
}),
serverSource: `export default {
id: "routed", version: "0.1.0",
register({ http }) {
http.route({ method: "GET", path: "/items", permission: "routed.items.read", maxBodyBytes: 1024, handler: ${noopHandlerSource()} });
},
start() { ${recorder.expression}.push("routed:start"); },
stop() { ${recorder.expression}.push("routed:stop"); }
};`,
});
await createRuntimeModule(modulesDirectory, "failing", {
manifest: manifestFor("failing", {
dependencies: { modules: { required: { routed: "^0.1.0" } } },
}),
serverSource: `export default {
id: "failing", version: "0.1.0",
start() { throw new Error("boom"); }
};`,
});
const error = await captureRuntimeError(modulesDirectory);
expect(runtimeIssueCodes(error)).toEqual(["MODULE_START_FAILED"]);
expect(recorder.events).toEqual(["routed:start", "routed:stop"]);
});
});
describe("already-discovered module runtime", () => {
it("starts from an already-discovered inventory without a second directory scan", async () => {
const modulesDirectory = await createModulesDirectory();
await createRuntimeModule(modulesDirectory, "already-discovered", {
serverSource: `export default { id: "already-discovered", version: "0.1.0" };`,
});
vi.mocked(discoverModules).mockClear();
const discovered = await discoverModules(runtimeOptions(modulesDirectory));
expect(discoverModules).toHaveBeenCalledTimes(1);
const runtime = await startDiscoveredModuleRuntime(discovered, fakeModuleRuntimeServices());
expect(discoverModules).toHaveBeenCalledTimes(1);
expect(runtime.modules.map((module) => module.id)).toEqual(["already-discovered"]);
await runtime.stop();
});
it("preserves the given discovered-module list order exactly, without re-sorting", async () => {
const modulesDirectory = await createModulesDirectory();
await createRuntimeModule(modulesDirectory, "first", {
serverSource: `export default { id: "first", version: "0.1.0" };`,
});
await createRuntimeModule(modulesDirectory, "second", {
serverSource: `export default { id: "second", version: "0.1.0" };`,
});
const discovered = await discoverModules(runtimeOptions(modulesDirectory));
expect(discovered.map((module) => module.manifest.id)).toEqual(["first", "second"]);
const runtime = await startDiscoveredModuleRuntime(
[...discovered].reverse(),
fakeModuleRuntimeServices(),
);
expect(runtime.modules.map((module) => module.id)).toEqual(["second", "first"]);
await runtime.stop();
});
it("startModuleRuntime remains backward-compatible and discovers exactly once", async () => {
const modulesDirectory = await createModulesDirectory();
await createRuntimeModule(modulesDirectory, "compat", {
serverSource: `export default { id: "compat", version: "0.1.0" };`,
});
vi.mocked(discoverModules).mockClear();
const runtime = await startModuleRuntime(runtimeOptions(modulesDirectory));
expect(discoverModules).toHaveBeenCalledTimes(1);
await runtime.stop();
});
});
describe("module runtime services", () => {
it("never gives register a services field, and gives start and stop the exact same services object", async () => {
const modulesDirectory = await createModulesDirectory();
const services = fakeModuleRuntimeServices();
const registerServices = createReferenceRecorder();
const lifecycleServices = createReferenceRecorder();
await createRuntimeModule(modulesDirectory, "wired", {
serverSource: `export default {
id: "wired", version: "0.1.0",
register(context) { ${registerServices.expression}.push(context.services); },
start(context) { ${lifecycleServices.expression}.push(context.services); },
stop(context) { ${lifecycleServices.expression}.push(context.services); }
};`,
});
const runtime = await startModuleRuntime(runtimeOptions(modulesDirectory, services));
await runtime.stop();
expect(registerServices.values).toEqual([undefined]);
expect(lifecycleServices.values).toHaveLength(2);
expect(lifecycleServices.values[0]).toBe(services);
expect(lifecycleServices.values[1]).toBe(services);
});
it("shares the exact same services object identity across every module's start hook", async () => {
const modulesDirectory = await createModulesDirectory();
const services = fakeModuleRuntimeServices();
const recorder = createReferenceRecorder();
await createRuntimeModule(modulesDirectory, "alpha", {
serverSource: `export default {
id: "alpha", version: "0.1.0",
start(context) { ${recorder.expression}.push(context.services); }
};`,
});
await createRuntimeModule(modulesDirectory, "beta", {
manifest: manifestFor("beta", {
dependencies: { modules: { required: { alpha: "^0.1.0" } } },
}),
serverSource: `export default {
id: "beta", version: "0.1.0",
start(context) { ${recorder.expression}.push(context.services); }
};`,
});
const runtime = await startModuleRuntime(runtimeOptions(modulesDirectory, services));
expect(recorder.values).toEqual([services, services]);
await runtime.stop();
});
it("gives a rollback stop hook the same services object a start failure left available", async () => {
const modulesDirectory = await createModulesDirectory();
const services = fakeModuleRuntimeServices();
const recorder = createReferenceRecorder();
await createRuntimeModule(modulesDirectory, "started", {
serverSource: `export default {
id: "started", version: "0.1.0",
start(context) { ${recorder.expression}.push(context.services); },
stop(context) { ${recorder.expression}.push(context.services); }
};`,
});
await createRuntimeModule(modulesDirectory, "failing", {
manifest: manifestFor("failing", {
dependencies: { modules: { required: { started: "^0.1.0" } } },
}),
serverSource: `export default {
id: "failing", version: "0.1.0",
start() { throw new Error("boom"); }
};`,
});
await expect(
startModuleRuntime(runtimeOptions(modulesDirectory, services)),
).rejects.toBeInstanceOf(ModuleRuntimeError);
expect(recorder.values).toEqual([services, services]);
});
it("starts and stops a zero-module runtime without ever touching the provided services", async () => {
const modulesDirectory = await createModulesDirectory();
const services = fakeModuleRuntimeServices();
const runtime = await startModuleRuntime(runtimeOptions(modulesDirectory, services));
await runtime.stop();
expect(runtime.modules).toEqual([]);
expect(vi.mocked(services.database.select)).not.toHaveBeenCalled();
expect(vi.mocked(services.database.insert)).not.toHaveBeenCalled();
expect(vi.mocked(services.database.update)).not.toHaveBeenCalled();
expect(vi.mocked(services.database.delete)).not.toHaveBeenCalled();
expect(vi.mocked(services.database.execute)).not.toHaveBeenCalled();
expect(vi.mocked(services.database.transaction)).not.toHaveBeenCalled();
});
});