import { CommandId, CorrelationId, EventId, type OrchestrationEvent, ThreadId, } from "@t3tools/contracts"; import { it as effectIt } from "@effect/vitest"; import * as Cause from "effect/Cause"; import * as Deferred from "effect/Deferred"; import * as Effect from "effect/Effect"; import * as Exit from "effect/Exit"; import * as Fiber from "effect/Fiber"; import * as Layer from "effect/Layer"; import * as Ref from "effect/Ref"; import * as Stream from "effect/Stream"; import { describe, expect, it } from "vite-plus/test"; import { ProviderService, type ProviderServiceShape, } from "../../provider/Services/ProviderService.ts"; import * as TerminalManager from "../../terminal/Manager.ts"; import { OrchestrationEngineService, type OrchestrationEngineShape, } from "../Services/OrchestrationEngine.ts"; import { ThreadDeletionReactor } from "../Services/ThreadDeletionReactor.ts"; import { logCleanupCauseUnlessInterrupted, ThreadDeletionReactorLive, } from "./ThreadDeletionReactor.ts"; describe("logCleanupCauseUnlessInterrupted", () => { const threadId = ThreadId.make("thread-deletion-reactor-test"); it("swallows ordinary cleanup failures", async () => { const exit = await Effect.runPromiseExit( logCleanupCauseUnlessInterrupted({ effect: Effect.fail("cleanup failed"), message: "thread deletion cleanup skipped provider session stop", threadId, }), ); expect(Exit.isSuccess(exit)).toBe(true); }); it("preserves interrupt causes", async () => { const exit = await Effect.runPromiseExit( logCleanupCauseUnlessInterrupted({ effect: Effect.interrupt, message: "thread deletion cleanup skipped provider session stop", threadId, }), ); expect(Exit.isFailure(exit)).toBe(true); if (Exit.isFailure(exit)) { expect(Cause.hasInterruptsOnly(exit.cause)).toBe(true); } }); }); describe("ThreadDeletionReactor drain", () => { const now = "2026-01-01T00:00:00.000Z"; const threadId = ThreadId.make("thread-deletion-reactor-drain"); const deletedEvent = (sequence: number): OrchestrationEvent => ({ sequence, eventId: EventId.make(`evt-deleted-${sequence}`), aggregateKind: "thread", aggregateId: threadId, type: "thread.deleted", occurredAt: now, commandId: CommandId.make(`cmd-deleted-${sequence}`), causationEventId: null, correlationId: CorrelationId.make(`cmd-deleted-${sequence}`), metadata: {}, payload: { threadId, deletedAt: now }, }); effectIt.effect("waits for a published deletion the subscriber has not consumed yet", () => Effect.gen(function* () { const stops: Array = []; const firstCleanupDone = yield* Deferred.make(); // The engine has already committed and published sequence 2, but the // subscriber has not received it yet: the stream releases it on demand. const releaseSecondEvent = yield* Deferred.make(); const latestSequence = yield* Ref.make(0); const engine = { latestSequence: Ref.get(latestSequence), streamDomainEvents: Stream.concat( Stream.make(deletedEvent(1)), Stream.fromEffect(Deferred.await(releaseSecondEvent)).pipe( Stream.map(() => deletedEvent(2)), ), ), } as unknown as OrchestrationEngineShape; const providerService = { stopSession: () => Effect.gen(function* () { stops.push(stops.length + 1); if (stops.length === 1) { yield* Deferred.succeed(firstCleanupDone, undefined); } }), } as unknown as ProviderServiceShape; const terminalManager = { close: () => Effect.void, } as unknown as TerminalManager.TerminalManager["Service"]; const layer = ThreadDeletionReactorLive.pipe( Layer.provide(Layer.succeed(ProviderService, providerService)), Layer.provide(Layer.succeed(TerminalManager.TerminalManager, terminalManager)), Layer.provide(Layer.succeed(OrchestrationEngineService, engine)), ); yield* Effect.scoped( Effect.gen(function* () { const reactor = yield* ThreadDeletionReactor; yield* reactor.start(); yield* Deferred.await(firstCleanupDone); // Sequence 1 is fully cleaned and the worker queue is idle. Sequence // 2 is committed and published but still in flight to the subscriber. yield* Ref.set(latestSequence, 2); const drained = yield* Effect.forkChild(reactor.drainThrough(2)); yield* Effect.yieldNow; yield* Effect.yieldNow; expect(stops).toEqual([1]); expect(drained.pollUnsafe()).toBeUndefined(); yield* Deferred.succeed(releaseSecondEvent, undefined); yield* Fiber.join(drained); expect(stops).toEqual([1, 2]); }), ).pipe(Effect.provide(layer)); }), ); });