feat(service): P5.4 outbound sandbox sink + rate limit + inspector read endpoints

OutboundService: idempotent commands, per-(channelType,target) rate-limit bucket,
sandbox send (no network) + delivery attempts. POST /v1/adapters/:type/send +
GET inbound/outbound (session-auth). 55 tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-01 13:46:56 +05:30
parent 29b64c3c4e
commit 4f661f4c81
5 changed files with 152 additions and 4 deletions
@@ -6,6 +6,7 @@ import { IIOS_EVENTS, type CloudEvent } from '@insignia/iios-contracts';
import { signedFixture, emailFixture } from '@insignia/iios-adapter-sdk';
import { AdapterRegistry } from './adapter.registry';
import { InboundService } from './inbound.service';
import { OutboundService } from './outbound.service';
import { RawEventProjector } from './raw-event.projector';
import { IngestService } from '../interactions/ingest.service';
import { ActorResolver } from '../identity/actor.resolver';
@@ -73,3 +74,34 @@ describe('Adapter inbound (P5)', () => {
expect(await prisma.iiosInteraction.count()).toBe(1);
});
});
describe('Adapter outbound (P5)', () => {
const outbound = (limit?: number): OutboundService => {
const s = new OutboundService(asService, registry());
if (limit) s.limit = limit;
return s;
};
it('send → command SENT + one delivery attempt (sandbox, no network)', async () => {
const cmd = await outbound().send('WEBHOOK', 'user@x', { text: 'hi' }, 'ob-1');
expect(cmd.status).toBe('SENT');
expect(cmd.providerRef).toContain('sim-');
expect(await prisma.iiosDeliveryAttempt.count({ where: { commandId: cmd.id } })).toBe(1);
});
it('rate limit: exceeding the window → RATE_LIMITED', async () => {
const svc = outbound(2);
await svc.send('WEBHOOK', 't', {}, 'a');
await svc.send('WEBHOOK', 't', {}, 'b');
const third = await svc.send('WEBHOOK', 't', {}, 'c');
expect(third.status).toBe('RATE_LIMITED');
});
it('idempotent: same key twice → one command', async () => {
const svc = outbound();
const a = await svc.send('WEBHOOK', 't2', {}, 'k');
const b = await svc.send('WEBHOOK', 't2', {}, 'k');
expect(b.id).toBe(a.id);
expect(await prisma.iiosOutboundCommand.count()).toBe(1);
});
});