import * as TestApp from '../../../test/App.js' import * as TestFunding from '../../../test/Funding.js' import * as FundingTransferEvents from '../../db/tables/fundingTransferEvents.js' import * as Webhooks from '../Webhooks.js' import * as Transfer from './Transfer.js' import * as TransferWebhook from './TransferWebhook.js' const db = TestApp.database() describe('stage', () => { test('stages owner-scoped public versions with stable event ids', async () => { const id = Transfer.generateId() const orgId = 'org_funding_transfer_webhook' const projectId = 'prj_funding_transfer_webhook' const org = await subscription({ filters: { id }, orgId }) const project = await subscription({ filters: { id, status: 'completed' }, orgId, projectId }) await subscription({ filters: { id }, orgId, projectId: 'prj_funding_transfer_sibling' }) await subscription({ environment: 'sandbox', filters: { id }, orgId }) await subscription({ filters: { id: Transfer.generateId() }, orgId }) await subscription({ filters: { id }, now: new Date('2099-01-01T00:00:00.000Z'), orgId }) const created = await db.transaction((tx) => Transfer.createIn(tx, { apiKeyId: 'key_funding_transfer_webhook', environment: 'production', id, orgId, projectId, providerState: { privateRequestId: 'provider-private' }, snapshot: TestFunding.transferSnapshot(), }), ) const processing = await Transfer.transition(db, { expectedVersion: created.record.version, id, status: 'processing', }) if (processing.type !== 'applied') throw new Error('Expected processing transition.') const completed = await Transfer.transition(db, { expectedVersion: processing.record.version, id, status: 'completed', }) if (completed.type !== 'applied') throw new Error('Expected completion transition.') expect({ completed: completed.references.length, created: created.references.length, processing: processing.references.length, }).toEqual({ completed: 2, created: 1, processing: 1 }) const rows = await db.kysely .selectFrom('webhook_queue_events') .select(['envelope', 'subscriptionId']) .where('subscriptionId', 'in', [org.id, project.id]) .orderBy('subscriptionId') .execute() expect( rows.map(({ envelope, subscriptionId }) => ({ data: envelope.data, eventId: envelope.id, subscriptionId, type: envelope.type, })), ).toEqual( expect.arrayContaining([ expect.objectContaining({ data: expect.objectContaining({ id, status: 'awaiting-source', version: 1 }), subscriptionId: org.id, type: 'funding:transfer.updated', }), expect.objectContaining({ data: expect.objectContaining({ id, status: 'processing', version: 2 }), subscriptionId: org.id, type: 'funding:transfer.updated', }), expect.objectContaining({ data: expect.objectContaining({ id, status: 'completed', version: 3 }), subscriptionId: org.id, type: 'funding:transfer.updated', }), expect.objectContaining({ data: expect.objectContaining({ id, status: 'completed', version: 3 }), subscriptionId: project.id, type: 'funding:transfer.updated', }), ]), ) expect(rows).toHaveLength(4) expect(rows[0]?.envelope.data).not.toHaveProperty('action') expect(rows[0]?.envelope.data).not.toHaveProperty('providerState') const events = await FundingTransferEvents.listByTransfer(db, id) const repeated = await TransferWebhook.stage(db, { changes: [{ event: events.at(-1)!, record: completed.record }], }) expect(repeated.created).toBe(0) expect(new Set(repeated.references.map((reference) => reference.eventId))).toEqual( new Set([rows.find(({ subscriptionId }) => subscriptionId === project.id)?.envelope.id]), ) }) }) function subscription(options: subscription.Options) { return Webhooks.createSubscription( db, { chainId: 4217, destination: { type: 'url', url: 'https://example.com/webhooks/tempo' }, environment: options.environment ?? 'production', eventType: 'funding:transfer.updated', filters: options.filters, owner: { orgId: options.orgId, type: 'api_key' }, ...(options.projectId === undefined ? {} : { projectId: options.projectId }), }, options.now === undefined ? {} : { now: () => options.now! }, ) } declare namespace subscription { type Options = { environment?: 'production' | 'sandbox' | undefined filters: Record now?: Date | undefined orgId: string projectId?: string | undefined } }