trustgraph/ts/packages/base/src/__tests__/producer.test.ts
elpresidank 0746d7ffd5 feat(ts): add real quality gates — Biome lint + effect-law ratchet + class inventory
- biome.json (2.4.16, linter-only) wired as "lint" in all six packages
- scripts/check-effect-laws.ts: Effect-native law enforcement encoding the
  adapted beep-effect effect-first/schema-first laws (no native JSON/switch/
  sort/fetch/timers, no process.env, no throw new, no Effect.run* outside
  boundaries, no Schema-suffixed constants, no node:fs/path, AST-based
  pure-data interface detection per law 38/39)
- ratcheting baseline allowlist (95 entries / 290 findings) that must shrink
  to documented exemptions only; stale counts fail the gate
- root lint chains turbo lint + law check + native-class inventory
- fix all 163 initial Biome findings: import-type style, templates, two `any`s,
  ten non-null assertions (librarian getService gate, A.matchRight in atoms,
  ensureNode returning nodes, main.tsx mount guard)

Gates: lint, check:tsgo, build, test (force, 11 tasks) all green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 06:40:01 -05:00

95 lines
3 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { Effect } from "effect";
import type {
BackendConsumer,
BackendProducer,
CreateConsumerOptions,
CreateProducerOptions,
PubSubBackend,
} from "../index.js";
import {
makeProducer,
pubSubError,
} from "../index.js";
class ProducerBackend implements PubSubBackend {
readonly sent: Array<{ readonly message: unknown; readonly properties?: Record<string, string> }> = [];
readonly producerTopics: Array<string> = [];
closeCount = 0;
flushCount = 0;
failFlush = false;
createProducer<T>(options: CreateProducerOptions<T>): Effect.Effect<BackendProducer<T>> {
return Effect.sync(() => {
this.producerTopics.push(options.topic);
return {
send: (message, properties) => Effect.sync(() => {
this.sent.push(properties === undefined ? { message } : { message, properties });
}),
flush: Effect.suspend(() => {
this.flushCount += 1;
if (this.failFlush) {
return Effect.fail(pubSubError("flush", "flush failed"));
}
return Effect.void;
}),
close: Effect.sync(() => {
this.closeCount += 1;
}),
};
});
}
createConsumer<T>(_options: CreateConsumerOptions<T>): Effect.Effect<BackendConsumer<T>> {
return Effect.fail(pubSubError("create-consumer", "consumer not supported"));
}
readonly close: Effect.Effect<void> = Effect.void;
}
describe("Producer", () => {
it("routes the compatibility facade through the scoped Effect producer", async () => {
const backend = new ProducerBackend();
const producer = makeProducer<string>(backend, "tg.test.producer");
await Effect.runPromise(producer.start);
await Effect.runPromise(producer.send("message-1", "hello"));
await Effect.runPromise(producer.stop);
expect(backend.producerTopics).toEqual(["tg.test.producer"]);
expect(backend.sent).toEqual([
{ message: "hello", properties: { id: "message-1" } },
]);
expect(backend.flushCount).toBe(1);
expect(backend.closeCount).toBe(1);
await expect(Effect.runPromise(producer.stop)).resolves.toBeUndefined();
const error = await Effect.runPromise(
producer.send("message-2", "late"),
).catch((caught: unknown) => caught);
expect(error).toMatchObject({
_tag: "MessagingLifecycleError",
operation: "send",
resource: "tg.test.producer",
});
});
it("closes the scoped producer when flush fails during stop", async () => {
const backend = new ProducerBackend();
const producer = makeProducer<string>(backend, "tg.test.producer");
await Effect.runPromise(producer.start);
backend.failFlush = true;
const error = await Effect.runPromise(producer.stop).catch((caught: unknown) => caught);
expect(error).toMatchObject({
_tag: "MessagingDeliveryError",
operation: "flush",
topic: "tg.test.producer",
});
expect(backend.flushCount).toBe(1);
expect(backend.closeCount).toBe(1);
});
});