Files
ekklesia fc6c8045c4
CI / verify (pull_request) Successful in 37s
CI / infrastructure-smoke (pull_request) Successful in 13s
CI / migration-smoke (pull_request) Successful in 1m7s
feat: add native module realtime transport
2026-08-23 18:47:18 -05:00

84 lines
3.3 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
MODULE_REALTIME_CHANNEL_MAX_LENGTH,
MODULE_REALTIME_ERROR_CODES,
MODULE_REALTIME_MAX_CHANNELS_PER_MODULE,
MODULE_REALTIME_MAX_PAYLOAD_BYTES,
MODULE_REALTIME_MAX_SUBSCRIBERS_PER_CHANNEL,
ModuleRealtimeError,
type ModuleRealtimePublisher,
parseModuleRealtimeChannel,
parseModuleRealtimeChannelDefinition,
safeParseModuleRealtimeChannel,
safeParseModuleRealtimeChannelDefinition,
} from "../src/index.js";
describe("module realtime contracts", () => {
it("accepts an ordinary named state interface without an index signature or cast", () => {
interface SlideshowState {
readonly currentSlide: number;
readonly mode: "on" | "off";
readonly slides: readonly string[];
}
const state: SlideshowState = { currentSlide: 1, mode: "on", slides: ["/welcome.png"] };
const received: unknown[] = [];
const publisher: ModuleRealtimePublisher = {
publish: (_channel, payload) => received.push(payload),
};
publisher.publish("state", state);
expect(received).toEqual([state]);
});
it("accepts flat local kebab-case channels and rejects hierarchy and wildcards", () => {
expect(parseModuleRealtimeChannel("state")).toBe("state");
expect(parseModuleRealtimeChannel("slide-state")).toBe("slide-state");
for (const invalid of ["", "State", "slide_state", "slide.state", "slide/state", "*", "a.*"]) {
expect(safeParseModuleRealtimeChannel(invalid).success).toBe(false);
}
expect(
safeParseModuleRealtimeChannel("a".repeat(MODULE_REALTIME_CHANNEL_MAX_LENGTH)).success,
).toBe(true);
expect(
safeParseModuleRealtimeChannel("a".repeat(MODULE_REALTIME_CHANNEL_MAX_LENGTH + 1)).success,
).toBe(false);
});
it("parses only the exact channel and permission definition shape", () => {
expect(
parseModuleRealtimeChannelDefinition({
channel: "state",
permission: "announcements.slideshow.view",
}),
).toEqual({ channel: "state", permission: "announcements.slideshow.view" });
expect(
safeParseModuleRealtimeChannelDefinition({
channel: "state",
permission: "announcements.slideshow.view",
topic: "legacy",
}),
).toEqual({ success: false, code: "MODULE_REALTIME_CHANNEL_DEFINITION_INVALID" });
expect(safeParseModuleRealtimeChannelDefinition({ channel: "state", permission: "*" })).toEqual(
{ success: false, code: "MODULE_REALTIME_PERMISSION_INVALID" },
);
});
it("publishes finite platform limits as explicit contract constants", () => {
expect(MODULE_REALTIME_MAX_CHANNELS_PER_MODULE).toBe(16);
expect(MODULE_REALTIME_MAX_SUBSCRIBERS_PER_CHANNEL).toBe(100);
expect(MODULE_REALTIME_MAX_PAYLOAD_BYTES).toBe(65_536);
});
it("uses a closed error-code set with fixed sanitized messages and stacks", () => {
expect(MODULE_REALTIME_ERROR_CODES).toContain("REALTIME_PAYLOAD_INVALID");
const error = new ModuleRealtimeError("REALTIME_PAYLOAD_INVALID");
expect(error).toMatchObject({
code: "REALTIME_PAYLOAD_INVALID",
message: "The realtime payload is not JSON-compatible.",
name: "ModuleRealtimeError",
});
expect(error.stack).toBe("ModuleRealtimeError: The realtime payload is not JSON-compatible.");
expect(Object.isFrozen(error)).toBe(true);
});
});