/** * KHAELOR * File: tests/session/events.test.ts * Description: Event catalog tests — serialization round-trip, durable/ephemeral classification, schema version. * * Author: Simon-Pierre Boucher * Contact: contact@spboucher.ai */ import { describe, expect, it } from "vitest"; import { DURABLE_EVENT_TYPES, EPHEMERAL_EVENT_TYPES, EVENT_SCHEMA_VERSION, isDurableEvent, isDurableEventType, isEphemeralEventType, } from "../../src/session/events.js"; import { envelope, ephemeralEnvelope, sampleDurableInputs, sampleEphemeralInputs } from "./fixtures.js"; describe("event catalog", () => { it("has exactly 40 durable and 6 ephemeral event types (EVENT_MODEL.md §3 + v2 additions)", () => { expect(DURABLE_EVENT_TYPES.size).toBe(40); expect(EPHEMERAL_EVENT_TYPES.size).toBe(6); }); it("durable and ephemeral sets are disjoint", () => { for (const t of DURABLE_EVENT_TYPES) { expect(EPHEMERAL_EVENT_TYPES.has(t as never)).toBe(false); } }); it("fixtures cover every catalog type exactly once", () => { const durableTypes = sampleDurableInputs().map((i) => i.type); expect(new Set(durableTypes).size).toBe(40); expect(new Set(durableTypes)).toEqual(new Set(DURABLE_EVENT_TYPES)); const ephemeralTypes = sampleEphemeralInputs().map((i) => i.type); expect(new Set(ephemeralTypes)).toEqual(new Set(EPHEMERAL_EVENT_TYPES)); }); it("classifies types correctly", () => { expect(isDurableEventType("session.started")).toBe(true); expect(isDurableEventType("model.text-delta")).toBe(false); expect(isDurableEventType("nonsense.event")).toBe(false); expect(isEphemeralEventType("model.text-delta")).toBe(true); expect(isEphemeralEventType("tool.completed")).toBe(false); }); it("schema version constant is 1", () => { expect(EVENT_SCHEMA_VERSION).toBe(1); }); }); describe("serialization round-trip", () => { it("every durable event type survives JSON round-trip byte-identically", () => { const inputs = sampleDurableInputs(); inputs.forEach((input, index) => { const event = envelope(index + 1, input); const line = JSON.stringify(event); const parsed = JSON.parse(line); expect(parsed).toEqual(event); // Byte-stability: re-serializing the parsed object yields identical bytes. expect(JSON.stringify(parsed)).toBe(line); expect(isDurableEvent(parsed)).toBe(true); expect(parsed.v).toBe(EVENT_SCHEMA_VERSION); expect(parsed.seq).toBe(index + 1); }); }); it("every ephemeral event type survives JSON round-trip (no v, no seq)", () => { for (const input of sampleEphemeralInputs()) { const event = ephemeralEnvelope(input); const parsed = JSON.parse(JSON.stringify(event)); expect(parsed).toEqual(event); expect("v" in parsed).toBe(false); expect("seq" in parsed).toBe(false); expect(isDurableEvent(parsed)).toBe(false); } }); it("durable envelope keys serialize in the fixed order v,id,sessionId,seq,ts,type,payload", () => { const event = envelope(1, sampleDurableInputs()[0]!); const keys = Object.keys(JSON.parse(JSON.stringify(event))); expect(keys).toEqual(["v", "id", "sessionId", "seq", "ts", "type", "payload"]); }); });