mirror of
https://github.com/devkitshq/notifkit.git
synced 2026-10-19 18:24:52 +02:00
128 lines
4.1 KiB
TypeScript
128 lines
4.1 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import { z } from "zod";
|
|
import { EventRegistry, registry, buildStreamEvent } from "@/contracts/index.js";
|
|
|
|
const Schema = z.object({ field: z.string() });
|
|
|
|
describe("EventRegistry", () => {
|
|
describe("define", () => {
|
|
it("registers a schema and exposes it", () => {
|
|
const r = new EventRegistry();
|
|
r.define("my.event", Schema);
|
|
|
|
expect(r.getSchema("my.event")).toBe(Schema);
|
|
expect(r.has("my.event")).toBe(true);
|
|
expect(r.types()).toEqual(["my.event"]);
|
|
});
|
|
|
|
it("refuses to redefine a registered type", () => {
|
|
const r = new EventRegistry();
|
|
r.define("my.event", Schema);
|
|
|
|
expect(() => r.define("my.event", Schema)).toThrow(
|
|
'Event type "my.event" is already registered',
|
|
);
|
|
});
|
|
|
|
it("reports unknown types as absent", () => {
|
|
const r = new EventRegistry();
|
|
|
|
expect(r.has("nope")).toBe(false);
|
|
expect(r.getSchema("nope")).toBeUndefined();
|
|
expect(r.types()).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe("parsePayload", () => {
|
|
it("returns the parsed payload", () => {
|
|
const r = new EventRegistry();
|
|
r.define("my.event", Schema);
|
|
|
|
expect(r.parsePayload("my.event" as any, { field: "value" })).toEqual({ field: "value" });
|
|
});
|
|
|
|
it("throws on an unregistered type", () => {
|
|
const r = new EventRegistry();
|
|
|
|
expect(() => r.parsePayload("nope" as any, {})).toThrow('Unknown event type: "nope"');
|
|
});
|
|
|
|
it("propagates the schema's own validation error", () => {
|
|
const r = new EventRegistry();
|
|
r.define("my.event", Schema);
|
|
|
|
expect(() => r.parsePayload("my.event" as any, { field: 42 })).toThrow(z.ZodError);
|
|
});
|
|
});
|
|
|
|
describe("safeParsePayload", () => {
|
|
it("succeeds with the parsed data", () => {
|
|
const r = new EventRegistry();
|
|
r.define("my.event", Schema);
|
|
const result = r.safeParsePayload("my.event" as any, { field: "value" });
|
|
|
|
expect(result.success).toBe(true);
|
|
if (result.success) expect(result.data).toEqual({ field: "value" });
|
|
});
|
|
|
|
it("fails with a synthesised error for an unregistered type", () => {
|
|
const r = new EventRegistry();
|
|
const result = r.safeParsePayload("nope" as any, {});
|
|
|
|
expect(result.success).toBe(false);
|
|
if (!result.success) {
|
|
expect(result.error.issues[0]!.message).toBe('Unknown event type: "nope"');
|
|
expect(result.error.issues[0]!.code).toBe("custom");
|
|
}
|
|
});
|
|
|
|
it("fails with the schema's issues for an invalid payload", () => {
|
|
const r = new EventRegistry();
|
|
r.define("my.event", Schema);
|
|
const result = r.safeParsePayload("my.event" as any, { field: 42 });
|
|
|
|
expect(result.success).toBe(false);
|
|
if (!result.success) expect(result.error.issues[0]!.path).toEqual(["field"]);
|
|
});
|
|
});
|
|
|
|
describe("the global registry", () => {
|
|
it("has the built-in notification events registered at import time", () => {
|
|
expect(registry.has("notification.requested")).toBe(true);
|
|
expect(registry.has("notification.delivered")).toBe(true);
|
|
expect(registry.types()).toContain("notification.ai_pending");
|
|
});
|
|
|
|
it("parses a built-in payload through the singleton", () => {
|
|
const parsed = registry.parsePayload("notification.requested", {
|
|
projectId: "11111111-1111-4111-8111-111111111111",
|
|
target: { type: "user", userId: "usr_1" },
|
|
templateId: "welcome",
|
|
});
|
|
|
|
expect(parsed.priority).toBe("normal");
|
|
expect(parsed.fallback).toBe(false);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("buildStreamEvent", () => {
|
|
it("carries through the traceId it is given", () => {
|
|
const event = buildStreamEvent("notification.failed", { a: 1 }, "enricher", "trace-1");
|
|
|
|
expect(event).toMatchObject({
|
|
type: "notification.failed",
|
|
payload: { a: 1 },
|
|
metadata: { traceId: "trace-1", source: "enricher", retryCount: 0 },
|
|
});
|
|
});
|
|
|
|
it("generates a traceId when none is supplied", () => {
|
|
const event = buildStreamEvent("notification.failed", {}, "enricher");
|
|
|
|
expect(event.metadata.traceId).toMatch(
|
|
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,
|
|
);
|
|
});
|
|
});
|