import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest'; import { PrismaClient } from '@prisma/client'; import { DlqService } from '../outbox/dlq.service'; import { resetDb } from '../test-utils/reset-db'; import { makeFakePorts, portalMessageBasic, replayTwice, isIdempotent } from '@insignia/iios-testkit'; import { IngestService } from '../interactions/ingest.service'; import { OutboxRelay } from './outbox.relay'; import { OutboxBus } from './outbox.bus'; import type { PrismaService } from '../prisma/prisma.service'; const url = process.env.DATABASE_URL ?? 'postgresql://iios:iios@localhost:5434/iios?schema=public'; const prisma = new PrismaClient({ datasources: { db: { url } } }); const asService = prisma as unknown as PrismaService; async function clean(): Promise { await resetDb(prisma); } beforeAll(async () => { await prisma.$connect(); }); afterAll(async () => { await prisma.$disconnect(); }); beforeEach(async () => { await clean(); }); describe('outbox relay + replay', () => { it('replaying a fixture twice produces no duplicate state', async () => { const ingest = new IngestService(asService, makeFakePorts()); const result = await replayTwice( portalMessageBasic, (f) => ingest.ingest(f, f.providerEventId!), () => prisma.iiosInteraction.count(), ); expect(isIdempotent(result)).toBe(true); expect(result.countAfterSecond).toBe(1); }); it('relay publishes exactly one event per interaction, then nothing new', async () => { const ingest = new IngestService(asService, makeFakePorts()); const bus = new OutboxBus(); const received: string[] = []; bus.onAny((eventType) => received.push(eventType)); const relay = new OutboxRelay(asService, bus, new DlqService(asService)); await ingest.ingest(portalMessageBasic, portalMessageBasic.providerEventId!); const published = await relay.relayOnce(); expect(published).toBe(1); expect(received).toEqual(['com.insignia.iios.interaction.normalized.v1']); expect(await prisma.iiosOutboxEvent.count({ where: { status: 'PUBLISHED' } })).toBe(1); expect(await prisma.iiosProcessedEvent.count()).toBe(1); // A second relay pass finds nothing PENDING. expect(await relay.relayOnce()).toBe(0); }); });