Skip to content

Commit 89c48cd

Browse files
committed
test(webhooks): pin the pre-lookup handshake order
The inversion fixed in the previous commit passed every existing test, because nothing asserted where the challenge offer sits relative to the load-shed gate or to the setup-verification probe. Both are answered from the request alone, so the ordering is the whole behavior and no other assertion touches it. Covers the two symptoms directly: a challenge is answered without taking an admission ticket, and it is offered before the verification probe rather than after. Reintroducing the inverted order fails both.
1 parent 012d3e7 commit 89c48cd

1 file changed

Lines changed: 58 additions & 0 deletions

File tree

apps/sim/app/api/webhooks/trigger/[path]/route.test.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,10 @@ vi.mock('postgres', () => vi.fn().mockReturnValue({}))
462462

463463
process.env.DATABASE_URL = 'postgresql://test:test@localhost:5432/test'
464464

465+
import {
466+
handlePreLookupWebhookVerification,
467+
handleProviderChallenges,
468+
} from '@/lib/webhooks/processor'
465469
import { DELETE, GET, PATCH, POST, PUT } from '@/app/api/webhooks/trigger/[path]/route'
466470

467471
describe('Webhook Trigger API Route', () => {
@@ -683,6 +687,60 @@ describe('Webhook Trigger API Route', () => {
683687
})
684688
})
685689

690+
/**
691+
* Both handshakes are answered from the request alone, before any webhook lookup, so their
692+
* order relative to each other and to the load-shed gate is the behavior — and it is invisible
693+
* to every other test here, which is how an earlier refactor inverted it unnoticed.
694+
*/
695+
describe('pre-lookup handshake ordering', () => {
696+
/**
697+
* Meta verifies a WhatsApp URL with a GET challenge. Answering it behind the load-shed gate
698+
* means a busy instance returns 429 and the webhook silently fails to verify, at setup time
699+
* only — so the challenge must be answered without taking a ticket at all.
700+
*/
701+
it('answers a provider challenge without taking an admission ticket', async () => {
702+
vi.mocked(handleProviderChallenges).mockResolvedValueOnce(
703+
new NextResponse('hub-challenge-123', { status: 200 })
704+
)
705+
706+
const req = createMockRequest(
707+
'GET',
708+
undefined,
709+
{},
710+
'http://localhost:3000/api/webhooks/trigger/verify-path?hub.challenge=hub-challenge-123'
711+
)
712+
713+
const response = await GET(req, { params: Promise.resolve({ path: 'verify-path' }) })
714+
715+
expect(response.status).toBe(200)
716+
await expect(response.text()).resolves.toBe('hub-challenge-123')
717+
expect(tryAdmitMock).not.toHaveBeenCalled()
718+
})
719+
720+
/**
721+
* A challenge is the more specific answer: the provider is echoing a token it chose, where a
722+
* pending verification only claims the URL is reachable. Answering the generic 200 first
723+
* fails the handshake that actually had a token to return.
724+
*/
725+
it('prefers a provider challenge over a pending setup verification', async () => {
726+
vi.mocked(handleProviderChallenges).mockResolvedValueOnce(
727+
new NextResponse('hub-challenge-123', { status: 200 })
728+
)
729+
730+
const req = createMockRequest(
731+
'GET',
732+
undefined,
733+
{},
734+
'http://localhost:3000/api/webhooks/trigger/verify-path?hub.challenge=hub-challenge-123'
735+
)
736+
737+
const response = await GET(req, { params: Promise.resolve({ path: 'verify-path' }) })
738+
739+
await expect(response.text()).resolves.toBe('hub-challenge-123')
740+
expect(handlePreLookupWebhookVerification).not.toHaveBeenCalled()
741+
})
742+
})
743+
686744
describe('GET deliveries', () => {
687745
it('dispatches a GET delivery to a generic webhook', async () => {
688746
testData.webhooks.push({

0 commit comments

Comments
 (0)