import { Prisma } from '../../../generated/prisma'; import { deferClaim, settleSkipped, settleSuccess } from './outbox-claims'; function fakeDb(count = 1) { return { $executeRaw: jest.fn().mockResolvedValue(count) }; } function sqlOf(db: ReturnType): Prisma.Sql { return (db.$executeRaw.mock.calls as [Prisma.Sql][])[0][0]; } describe('NotificationDelivery outbox helpers', () => { const claimedAt = new Date('2026-10-15T10:00:00.000Z'); it('settleSuccess settles NotificationDelivery as DELIVERED into deliveredAt', async () => { const db = fakeDb(); await expect( settleSuccess( db as never, 'NotificationDelivery', 'd1', claimedAt, 'DELIVERED', ), ).resolves.toBe(true); const query = sqlOf(db); expect(query.sql).toContain('UPDATE "NotificationDelivery"'); expect(query.sql).toContain('"deliveredAt" = now()'); expect(query.sql).toContain('"NotificationDeliveryStatus"'); }); it('settleSuccess refuses DONE for NotificationDelivery', async () => { await expect( settleSuccess( fakeDb() as never, 'NotificationDelivery', 'd1', claimedAt, 'DONE', ), ).rejects.toThrow('NotificationDelivery settles as DELIVERED, not DONE'); }); it('settleSkipped is compare-and-set on id and claimedAt and keeps attempts', async () => { const db = fakeDb(); await expect( settleSkipped( db as never, 'NotificationDelivery', 'd1', claimedAt, 'chat gone', ), ).resolves.toBe(true); const query = sqlOf(db); expect(query.sql).toContain('UPDATE "NotificationDelivery"'); expect(query.sql).toContain(`CAST(? AS "NotificationDeliveryStatus")`); expect(query.sql).toContain( `WHERE "id" = ? AND "status" = 'PROCESSING' AND "claimedAt" = ?`, ); expect(query.sql).not.toContain('"attempts"'); expect(query.values).toEqual(['SKIPPED', 'chat gone', 'd1', claimedAt]); }); it('settleSkipped reports a lost claim', async () => { await expect( settleSkipped( fakeDb(0) as never, 'NotificationDelivery', 'd1', claimedAt, 'x', ), ).resolves.toBe(false); }); it('deferClaim puts the entry back to PENDING after the delay by the database clock, attempts unchanged, compare-and-set', async () => { const db = fakeDb(); await expect( deferClaim( db as never, 'NotificationDelivery', 'd1', claimedAt, 15 * 60_000, ), ).resolves.toBe(true); const query = sqlOf(db); expect(query.sql).toContain('UPDATE "NotificationDelivery"'); expect(query.sql).toContain(`"status" = 'PENDING'`); expect(query.sql).toContain( `"availableAt" = now() + (? * interval '1 millisecond')`, ); expect(query.sql).toContain('"claimedAt" = NULL'); expect(query.sql).not.toContain('"attempts"'); expect(query.sql).toContain( `WHERE "id" = ? AND "status" = 'PROCESSING' AND "claimedAt" = ?`, ); expect(query.values).toEqual([900_000, 'd1', claimedAt]); }); it('deferClaim reports a lost claim and rejects a negative or non-integer delay', async () => { await expect( deferClaim( fakeDb(0) as never, 'NotificationDelivery', 'd1', claimedAt, 1, ), ).resolves.toBe(false); await expect( deferClaim( fakeDb() as never, 'NotificationDelivery', 'd1', claimedAt, -1, ), ).rejects.toThrow(RangeError); await expect( deferClaim( fakeDb() as never, 'NotificationDelivery', 'd1', claimedAt, 1.5, ), ).rejects.toThrow(RangeError); }); it('deferClaim caps the delay at 24 hours', async () => { const db = fakeDb(); await deferClaim( db as never, 'NotificationDelivery', 'd1', claimedAt, 30 * 24 * 3_600_000, ); expect(sqlOf(db).values[0]).toBe(24 * 3_600_000); }); it('settleSkipped truncates the reason to 200 characters', async () => { const db = fakeDb(); await settleSkipped( db as never, 'NotificationDelivery', 'd1', claimedAt, 'r'.repeat(500), ); expect(sqlOf(db).values[1]).toBe('r'.repeat(200)); }); });