import { ANONYMOUS_PRINCIPAL, type CapabilityId, EMPTY_MODULE_CONFIGURATION, EMPTY_MODULE_STORAGE, type ModuleConfiguration, type ModuleRouteDefinitionInput, type ModuleRouteHandlerContext, type ModuleRuntimeServices, type RegisteredCapability, } from "@ekklesia/module-sdk"; import moduleRuntime from "../../src/server/index.js"; /** * The module-layer test harness: everything a Signage unit test needs to exercise the real runtime * definition without a platform. * * Every object here is a `@ekklesia/contracts` interface constructed by hand, which is exactly what * a module-layer test may legitimately do — a module may never import Core. It also demonstrates the * portability claim directly: the host producing these objects could be Ekklesia, or a future * standalone host reading YAML from disk, and Signage cannot tell the difference. */ export interface CapturedRegistration { readonly channels: { channel: string; permission: string }[]; readonly provided: Map; readonly routes: ModuleRouteDefinitionInput[]; } export async function registerModule( config: ModuleConfiguration = EMPTY_MODULE_CONFIGURATION, ): Promise { const channels: { channel: string; permission: string }[] = []; const provided = new Map(); const routes: ModuleRouteDefinitionInput[] = []; await moduleRuntime.register?.({ capabilities: { provide(capabilityId, implementation) { provided.set(capabilityId, implementation); }, resolve(_capabilityId: CapabilityId): RegisteredCapability | undefined { return undefined; }, }, config, http: { route(definition) { routes.push(definition); }, }, moduleId: "signage", realtime: { channel(definition) { channels.push({ channel: definition.channel, permission: definition.permission }); }, }, }); return { channels, provided, routes }; } export function findRoute( registration: CapturedRegistration, method: string, path: string, ): ModuleRouteDefinitionInput { const route = registration.routes.find( (candidate) => candidate.method === method && candidate.path === path, ); if (!route) { throw new Error(`Signage did not register ${method} ${path}.`); } return route; } export function fakeConfiguration(values: Readonly>): ModuleConfiguration { const read = (key: string, kind: string): Value | undefined => { const value = values[key]; return typeof value === kind ? (value as Value) : undefined; }; return Object.freeze({ get: (key: string) => values[key] as never, getBoolean: (key: string) => read(key, "boolean"), getEnum: (key: string) => read(key, "string"), getInteger: (key: string) => read(key, "number"), getSecret: (key: string) => read(key, "string"), getString: (key: string) => read(key, "string"), getUrl: (key: string) => read(key, "string"), has: (key: string) => Object.hasOwn(values, key), keys: () => Object.freeze(Object.keys(values)), }); } export function fakeHandlerContext( overrides: Partial = {}, ): ModuleRouteHandlerContext { const services = { database: undefined } as unknown as ModuleRuntimeServices; return Object.freeze({ config: EMPTY_MODULE_CONFIGURATION, moduleId: "signage", params: Object.freeze({}), principal: ANONYMOUS_PRINCIPAL, realtime: { publish: () => undefined }, services, storage: EMPTY_MODULE_STORAGE, ...overrides, }); }