import { describe, expect, test } from 'bun:test' import { isWebhookOwnerAtCapacity, MAX_WEBHOOK_LIST_LIMIT, normalizeWebhookListLimit, validateWebhookCreateInput, type WebhookCreateInput, } from './webhook-policy' const did = 'did:plc:aaaaaaaaaaaaaaaaaaaaaaaa' const valid = { scopeAturi: `at://${did}`, url: 'https://receiver.example/hook', events: ['create', 'update'] as const, } describe('main webhook API policy', () => { test('accepts a canonical DID-only scope and HTTPS endpoint', () => { const result = validateWebhookCreateInput(valid, { allowLoopbackDev: false }) expect(result.ok).toBe(true) if (result.ok) expect(result.record.scope.aturi).toBe(valid.scopeAturi) }) test('rejects HTTP, handle scopes, duplicate/oversized events, and conflicting secret forms before PDS writes', () => { const invalidInputs: WebhookCreateInput[] = [ { ...valid, url: 'http://receiver.example/hook' }, { ...valid, scopeAturi: 'at://alice.example' }, { ...valid, events: ['create', 'create'] }, { ...valid, events: ['create', 'update', 'delete', 'create'] }, { ...valid, secret: 'inline', secretId: 'managed' }, { ...valid, secret: 'x'.repeat(257) }, { ...valid, secretId: 'x'.repeat(65) }, { ...valid, secretId: 'secret-☃' }, { ...valid, secretId: 'secret/name' }, { ...valid, secretId: 'secret%2Fname' }, { ...valid, url: `https://receiver.example/${'x'.repeat(2_049)}` }, { ...valid, scopeAturi: `at://${did}/${'x'.repeat(2_049)}` }, ] for (const input of invalidInputs) { expect(validateWebhookCreateInput(input, { allowLoopbackDev: false }).ok).toBe(false) } }) test('uses the same exact secret-ID boundary as secret management', () => { expect(validateWebhookCreateInput({ ...valid, secretId: 'a'.repeat(64) }, { allowLoopbackDev: false }).ok).toBe( true, ) }) test('permits HTTP only for explicitly enabled loopback development', () => { expect( validateWebhookCreateInput({ ...valid, url: 'http://127.0.0.1:43123/hook' }, { allowLoopbackDev: false }).ok, ).toBe(false) expect( validateWebhookCreateInput({ ...valid, url: 'http://127.0.0.1:43123/hook' }, { allowLoopbackDev: true }).ok, ).toBe(true) expect( validateWebhookCreateInput({ ...valid, url: 'http://not-loopback.example/hook' }, { allowLoopbackDev: true }).ok, ).toBe(false) }) test('passes backlinksOnly through and rejects it alongside backlinks: false', () => { const result = validateWebhookCreateInput({ ...valid, backlinksOnly: true }, { allowLoopbackDev: false }) expect(result.ok).toBe(true) if (result.ok) expect(result.record.scope.backlinksOnly).toBe(true) expect( validateWebhookCreateInput({ ...valid, backlinks: false, backlinksOnly: true }, { allowLoopbackDev: false }).ok, ).toBe(false) }) test('enforces the owner quota at exactly 50 records', () => { expect(isWebhookOwnerAtCapacity(49)).toBe(false) expect(isWebhookOwnerAtCapacity(50)).toBe(true) expect(isWebhookOwnerAtCapacity(51)).toBe(true) }) test('bounds public webhook list pages', () => { expect(normalizeWebhookListLimit(undefined)).toBe(MAX_WEBHOOK_LIST_LIMIT) expect(normalizeWebhookListLimit(0)).toBe(MAX_WEBHOOK_LIST_LIMIT) expect(normalizeWebhookListLimit(1)).toBe(1) expect(normalizeWebhookListLimit(10_000)).toBe(MAX_WEBHOOK_LIST_LIMIT) }) })