Initial Commit
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
import type { PipelineStageEvent } from "@suaveplan/api-types/pipeline-events";
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
import { InMemoryPipelineEventsBus } from "./in-memory-bus.js";
|
||||
|
||||
function ev(
|
||||
requestId: string,
|
||||
stage: PipelineStageEvent["stage"],
|
||||
outcome: PipelineStageEvent["outcome"],
|
||||
at: number
|
||||
): PipelineStageEvent {
|
||||
return { requestId, stage, outcome, at };
|
||||
}
|
||||
|
||||
describe("InMemoryPipelineEventsBus — FIFO + fan-out semantics", () => {
|
||||
it("emit appends to the internal FIFO in order", { timeout: 5000 }, () => {
|
||||
const bus = new InMemoryPipelineEventsBus();
|
||||
bus.emit(ev("r1", "tenant", "entered", 1));
|
||||
bus.emit(ev("r1", "tenant", "completed", 2));
|
||||
expect(bus.events.map((e) => e.outcome)).toEqual([
|
||||
"entered",
|
||||
"completed",
|
||||
]);
|
||||
expect(bus.length).toBe(2);
|
||||
});
|
||||
|
||||
it("subscribe receives events in emission order", { timeout: 5000 }, () => {
|
||||
const bus = new InMemoryPipelineEventsBus();
|
||||
const seen: string[] = [];
|
||||
bus.subscribe((e) => seen.push(`${e.stage}/${e.outcome}`));
|
||||
bus.emit(ev("r1", "auth", "entered", 1));
|
||||
bus.emit(ev("r1", "auth", "rejected", 2));
|
||||
expect(seen).toEqual(["auth/entered", "auth/rejected"]);
|
||||
});
|
||||
|
||||
it("subscriber throws are isolated — FIFO + remaining subscribers unaffected", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const bus = new InMemoryPipelineEventsBus();
|
||||
const survivor: PipelineStageEvent[] = [];
|
||||
bus.subscribe(() => {
|
||||
throw new Error("boom");
|
||||
});
|
||||
bus.subscribe((e) => survivor.push(e));
|
||||
const event = ev("r1", "csrf", "completed", 1);
|
||||
expect(() => bus.emit(event)).not.toThrow();
|
||||
expect(survivor).toEqual([event]);
|
||||
expect(bus.events).toEqual([event]);
|
||||
});
|
||||
|
||||
it("unsubscribe is idempotent — calling twice does not double-detach", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const bus = new InMemoryPipelineEventsBus();
|
||||
const seen: PipelineStageEvent[] = [];
|
||||
const off = bus.subscribe((e) => seen.push(e));
|
||||
expect(bus.subscriberCount).toBe(1);
|
||||
off();
|
||||
expect(bus.subscriberCount).toBe(0);
|
||||
// Second call is a no-op.
|
||||
expect(() => off()).not.toThrow();
|
||||
expect(bus.subscriberCount).toBe(0);
|
||||
// Re-subscribe to confirm the set still works.
|
||||
const off2 = bus.subscribe((e) => seen.push(e));
|
||||
bus.emit(ev("r1", "audit", "completed", 1));
|
||||
expect(seen).toHaveLength(1);
|
||||
off2();
|
||||
});
|
||||
|
||||
it("drain returns + clears the FIFO atomically", { timeout: 5000 }, () => {
|
||||
const bus = new InMemoryPipelineEventsBus();
|
||||
bus.emit(ev("r1", "tenant", "entered", 1));
|
||||
bus.emit(ev("r2", "tenant", "entered", 2));
|
||||
const out = bus.drain();
|
||||
expect(out).toHaveLength(2);
|
||||
expect(bus.length).toBe(0);
|
||||
expect(bus.events).toEqual([]);
|
||||
});
|
||||
|
||||
it("byRequest + byStage slice the FIFO correctly", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const bus = new InMemoryPipelineEventsBus();
|
||||
bus.emit(ev("r1", "tenant", "entered", 1));
|
||||
bus.emit(ev("r2", "tenant", "entered", 2));
|
||||
bus.emit(ev("r1", "auth", "entered", 3));
|
||||
expect(bus.byRequest("r1")).toHaveLength(2);
|
||||
expect(bus.byRequest("r2")).toHaveLength(1);
|
||||
expect(bus.byStage("tenant")).toHaveLength(2);
|
||||
expect(bus.byStage("auth")).toHaveLength(1);
|
||||
expect(bus.byStage("response-validate")).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("events getter returns a fresh array snapshot each call", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const bus = new InMemoryPipelineEventsBus();
|
||||
bus.emit(ev("r1", "audit", "completed", 1));
|
||||
const snap1 = bus.events;
|
||||
// Mutating the snapshot MUST NOT poison the internal FIFO.
|
||||
(snap1 as PipelineStageEvent[]).pop();
|
||||
expect(bus.length).toBe(1);
|
||||
const snap2 = bus.events;
|
||||
expect(snap2).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user