Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,313 @@
import { OpenAPIHono } from "@hono/zod-openapi";
import { createOpenAPIRegistry } from "@suaveplan/api-openapi";
import { createPluginContext } from "@suaveplan/api-plugin";
import { EventBus } from "@suaveplan/events";
import { describe, expect, it } from "@suaveplan/testing/runner";
import { EVENT_BUS_CODES, EventBusError } from "../error-codes/error-codes.js";
import { resetEventBusInstruments } from "../otel/instruments.js";
import type { EventBusService } from "../services/event-bus-service.types.js";
import type { EventBusPluginConfig } from "../types/types.js";
import { createEventBusPlugin } from "./plugin.js";
const noopLogger = {
debug: () => undefined,
info: () => undefined,
warn: () => undefined,
error: () => undefined,
};
function makeContext(
services: Map<string, unknown>,
defaults: EventBusPluginConfig
) {
return createPluginContext<EventBusPluginConfig>({
logger: noopLogger,
eventBus: new EventBus(),
signal: new AbortController().signal,
config: defaults,
services,
});
}
describe("createEventBusPlugin", () => {
it("createEventBusPlugin resolves defaults and returns a typed PluginDescriptor", {
timeout: 5000,
}, () => {
const plugin = createEventBusPlugin({});
expect(plugin.name).toBe("event-bus");
expect(plugin.version).toBe("0.1.0");
const defaults = plugin.defaults as EventBusPluginConfig;
expect(defaults.routePrefix).toBe("/event-bus");
expect(defaults.mountRoutes).toBe(true);
expect(defaults.requireAuthForRoutes).toBe(true);
expect(defaults.tenantIsolation).toBe(true);
expect(defaults.backpressure.policy).toBe("drop-oldest");
expect(defaults.backpressure.capacity).toBe(1024);
expect(defaults.idempotency.enabled).toBe(false);
expect(defaults.maxTopicLength).toBe(256);
});
it("createEventBusPlugin throws EVENT_BUS.INVALID_INPUT for invalid options", {
timeout: 5000,
}, () => {
try {
createEventBusPlugin({
routePrefix: "no-leading-slash",
} as never);
throw new Error("expected throw");
} catch (err) {
expect(err).toBeInstanceOf(EventBusError);
expect((err as EventBusError).eventBusCode).toBe(
EVENT_BUS_CODES.INVALID_INPUT
);
}
});
});
describe("createEventBusPlugin.onInit", () => {
it("onInit throws SERVICE_NOT_AVAILABLE when the app slot is missing", {
timeout: 30_000,
}, async () => {
resetEventBusInstruments();
const plugin = createEventBusPlugin({ mountRoutes: false });
const ctx = makeContext(
new Map<string, unknown>(),
plugin.defaults as EventBusPluginConfig
);
try {
await plugin.onInit?.(ctx);
throw new Error("expected throw");
} catch (err) {
expect(err).toBeInstanceOf(EventBusError);
expect((err as EventBusError).eventBusCode).toBe(
EVENT_BUS_CODES.SERVICE_NOT_AVAILABLE
);
}
});
it("onInit registers eventBusService in the ServiceRegistry", {
timeout: 30_000,
}, async () => {
resetEventBusInstruments();
const services = new Map<string, unknown>();
services.set("app", new OpenAPIHono());
const plugin = createEventBusPlugin({ mountRoutes: false });
const ctx = makeContext(
services,
plugin.defaults as EventBusPluginConfig
);
await plugin.onInit?.(ctx);
const svc = services.get("eventBusService") as EventBusService;
expect(svc).toBeDefined();
expect(typeof svc.publish).toBe("function");
expect(typeof svc.subscribe).toBe("function");
});
it("onInit installs request-bus middleware that exposes ScopedEventBusHandle", {
timeout: 30_000,
}, async () => {
resetEventBusInstruments();
const app = new OpenAPIHono();
const services = new Map<string, unknown>();
services.set("app", app);
const plugin = createEventBusPlugin({
mountRoutes: false,
requireAuthForRoutes: false,
});
await plugin.onInit?.(
makeContext(services, plugin.defaults as EventBusPluginConfig)
);
let observedTenant: string | null | undefined;
app.get("/probe", (c) => {
const handle = c.get("eventBus");
observedTenant = handle?.tenantId ?? null;
return c.text("ok");
});
const res = await app.request("/probe");
expect(res.status).toBe(200);
expect(observedTenant).toBeNull();
});
it("onInit mounts routes onto OpenAPIRegistry when present", {
timeout: 30_000,
}, async () => {
resetEventBusInstruments();
const app = new OpenAPIHono();
const registry = createOpenAPIRegistry({
info: { title: "test", version: "0.1.0" },
});
const services = new Map<string, unknown>();
services.set("app", app);
services.set("openAPIRegistry", registry);
const plugin = createEventBusPlugin({
requireAuthForRoutes: false,
});
await plugin.onInit?.(
makeContext(services, plugin.defaults as EventBusPluginConfig)
);
expect(registry.getRoutesByPlugin("event-bus")).toHaveLength(3);
});
it("mountRoutes=false skips route registration", {
timeout: 30_000,
}, async () => {
resetEventBusInstruments();
const app = new OpenAPIHono();
const registry = createOpenAPIRegistry({
info: { title: "test", version: "0.1.0" },
});
const services = new Map<string, unknown>();
services.set("app", app);
services.set("openAPIRegistry", registry);
const plugin = createEventBusPlugin({
mountRoutes: false,
requireAuthForRoutes: false,
});
await plugin.onInit?.(
makeContext(services, plugin.defaults as EventBusPluginConfig)
);
expect(registry.getRoutesByPlugin("event-bus")).toHaveLength(0);
});
it("onInit throws ROUTE_PREFIX_COLLISION when routes already occupy the prefix", {
timeout: 30_000,
}, async () => {
resetEventBusInstruments();
const app = new OpenAPIHono();
const registry = createOpenAPIRegistry({
info: { title: "test", version: "0.1.0" },
});
registry.registerRoute({
method: "get",
path: "/event-bus/x",
tags: [],
pluginName: "other",
});
const services = new Map<string, unknown>();
services.set("app", app);
services.set("openAPIRegistry", registry);
const plugin = createEventBusPlugin({
requireAuthForRoutes: false,
});
try {
await plugin.onInit?.(
makeContext(services, plugin.defaults as EventBusPluginConfig)
);
throw new Error("expected throw");
} catch (err) {
expect(err).toBeInstanceOf(EventBusError);
expect((err as EventBusError).eventBusCode).toBe(
EVENT_BUS_CODES.ROUTE_PREFIX_COLLISION
);
}
});
it("mounts onto Hono only when openAPIRegistry is absent", {
timeout: 30_000,
}, async () => {
resetEventBusInstruments();
const app = new OpenAPIHono();
const services = new Map<string, unknown>();
services.set("app", app);
const plugin = createEventBusPlugin({
requireAuthForRoutes: false,
});
await plugin.onInit?.(
makeContext(services, plugin.defaults as EventBusPluginConfig)
);
// The publish route should now answer on /event-bus/publish.
const res = await app.request("/event-bus/publish", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ topic: "order.created", payload: {} }),
});
// Should be 2xx (handler ran).
expect(res.status).toBeGreaterThanOrEqual(200);
expect(res.status).toBeLessThan(400);
});
it("onClose clears the cached service", { timeout: 30_000 }, async () => {
resetEventBusInstruments();
const services = new Map<string, unknown>();
services.set("app", new OpenAPIHono());
const plugin = createEventBusPlugin({ mountRoutes: false });
await plugin.onInit?.(
makeContext(services, plugin.defaults as EventBusPluginConfig)
);
await plugin.onClose?.(
makeContext(services, plugin.defaults as EventBusPluginConfig)
);
// The state-internal cleared field is private; we observe via
// the lack of throw and the descriptor's metadata.
expect(plugin.name).toBe("event-bus");
});
it("mounts under custom routePrefix '/'", { timeout: 30_000 }, async () => {
resetEventBusInstruments();
const app = new OpenAPIHono();
const services = new Map<string, unknown>();
services.set("app", app);
const plugin = createEventBusPlugin({
mountRoutes: true,
requireAuthForRoutes: false,
routePrefix: "/",
});
await plugin.onInit?.(
makeContext(services, plugin.defaults as EventBusPluginConfig)
);
// Publish reachable at top-level /publish.
const res = await app.request("/publish", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ topic: "x", payload: {} }),
});
expect(res.status).toBeGreaterThanOrEqual(200);
expect(res.status).toBeLessThan(400);
});
it("requireAuthForRoutes=true installs requireAuth() under the prefix", {
timeout: 30_000,
}, async () => {
resetEventBusInstruments();
const app = new OpenAPIHono();
const registry = createOpenAPIRegistry({
info: { title: "test", version: "0.1.0" },
});
const services = new Map<string, unknown>();
services.set("app", app);
services.set("openAPIRegistry", registry);
const plugin = createEventBusPlugin({
mountRoutes: true,
requireAuthForRoutes: true,
});
await plugin.onInit?.(
makeContext(services, plugin.defaults as EventBusPluginConfig)
);
// Anonymous request — requireAuth() raises AuthError because
// `c.get("user")` is null. The default Hono error handler maps
// that to 500 (the api error-handler middleware would map to
// 401, but the bare app does not have it wired). Either way the
// request did not succeed — the gate fired.
const res = await app.request("/event-bus/subscriptions");
expect(res.status).not.toBe(200);
});
it("requireAuthForRoutes=true with root prefix mounts /* gate", {
timeout: 30_000,
}, async () => {
resetEventBusInstruments();
const app = new OpenAPIHono();
const services = new Map<string, unknown>();
services.set("app", app);
const plugin = createEventBusPlugin({
mountRoutes: true,
requireAuthForRoutes: true,
routePrefix: "/",
});
await plugin.onInit?.(
makeContext(services, plugin.defaults as EventBusPluginConfig)
);
const res = await app.request("/subscriptions");
expect(res.status).not.toBe(200);
});
});