Add webhooks tests 1574 - #1700
Open
Stanley-Owoh wants to merge 3 commits into
Open
Stanley-Owoh wants to merge 3 commits into
Stanley-Owoh wants to merge 3 commits into
Conversation
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
fix(indexer): persist
DrawTriggeredraffle state asDRAWINGSummary
DrawTriggeredwas decoded, classified, and routed through the ingestion pipeline — butCursorAdvancelogged it and returnednull, so nothing was ever written to the database.A raffle that had entered the draw process stayed
OPENin the indexer indefinitely, whichmade raffles that were mid-draw indistinguishable from raffles still selling tickets (API
responses, active-raffle listings, and the UI all keyed off
status).This PR wires the event through to a real state transition, so the persisted lifecycle now
matches the contract:
RaffleStatus.DRAWINGalready existed in@tikka/typesand in theraffle_statusdatabaseenum, so this required no schema, enum, or generated-file changes — only the missing write path.
What changed
indexer/src/processors/raffle.processor.tsNew
handleDrawTriggered(raffleId, ledger, txHash, schemaVersion), mirroring the existingraffle handlers:
QueryRunnerand transaction, and returns the runner —CursorAdvanceowns commit/release, consistent with
handleRaffleCreated/handleRaffleFinalizedUPDATE raffles SET status = DRAWING WHERE id = :raffleId AND status = :openDrawTriggeredrow toraffle_eventswithorIgnore()(uniquetx_hash)indexer/src/ingestor/cursor-advance.tsDrawTriggerednow routes tohandleDrawTriggeredinstead of logging and returningnull,so the event is committed through the normal cursor-advance path.
indexer/src/ingestor/duplicate-detector.tseventNeedsDatabase("DrawTriggered")→true(it mutates durable state, so the dispatcherreports
succeededrather thanskipped)RaffleProcessor.handleDrawTriggered, so DLQ entries and tracing spans areattributed to a real processor instead of
DrawTriggeredHandlerRandomnessRequested/RandomnessReceivedintentionally remain non-persistedindexer/src/ingestor/ingestion-dispatcher.service.tsThe same three changes mirrored into the service's legacy private copies of
eventNeedsDatabase/applyEvent/getHandlerName. These are currently unreachable(dispatch goes through
CursorAdvance+DuplicateDetector), but leaving them stale wouldmislead the next reader.
Idempotency and terminal-state safety
The transition is guarded on
status = :openrather thanstatus != :drawing, which gives threeproperties at once:
tx_hash)DrawTriggeredtx for an already-DRAWINGraffleDrawTriggeredon aFINALIZED/CANCELLEDraffleTests
raffle.processor.spec.ts— 5 new cases: status set toDRAWING, thestatus = :openguardclause, cache invalidation, runner returned without committing, and rollback/error propagation
cursor-advance.spec.ts— routing asserts the exact processor arguments plus commit/releaseduplicate-detector.spec.ts—DrawTriggeredassertsneedsDatabase: trueand the new handlername; randomness cases still assert
falsehandlers/duplicate-delivery.spec.ts— replaced the previous "leaves empty durable state"expectation with two real behavioural tests: duplicate delivery leaves an identical
DRAWINGraffle with exactly one audit row, and a parameterized case proving
FINALIZEDandCANCELLEDraffles are not reopened. The in-memory
QueryRunnerharness was extended to interpret thestatus = :openguard (and returnaffected: 0when blocked).Verification
Because a green suite proves little on its own, each new behaviour was mutation-tested to
confirm the tests actually fail when the fix is removed:
AND status = :openguarddoes not reopen a finalized raffle+does not reopen a cancelled rafflereturn nullduplicate delivery leaves an identical DRAWING raffleneedsDatabaseback tofalsemarks DrawTriggered as requiring durable database mutationAll mutations were reverted; the final diff is byte-identical to the pre-mutation state.
Notes / follow-ups (not in this PR)
handleDrawTriggereddoes not emit awebhookService.dispatch("DrawTriggered", ...)call.A drawing transition is arguably subscriber-relevant, but introducing a new externally visible
webhook event felt out of scope for a persistence fix — happy to add it as a follow-up.
indexerintegration suite (pnpm test:integration) requires Docker/TestcontainersPostgres and could not run in this environment, so the
WHEREclause is verified via thein-memory
QueryRunnerharness and mock assertions rather than against a real database.Worth confirming in CI.
IngestionDispatcherService'seventNeedsDatabase/applyEvent/getHandlerNameare deadcode. They were updated for consistency, but no test can cover them; deleting them would be the
real fix.
feat(backend): add webhook testsAlso on this branch — hardening and test coverage for inbound webhook signature verification,
kept separate from the indexer fix above.
webhook-signature-verification.interceptor.tssignatures no longer break on key ordering or whitespace
timingSafeEqualwith equal-length buffers instead of===MAX_TIMESTAMP_AGE_MSwindowx-tikka-webhook-sourceheader format and resolves per-source secrets(
<SOURCE>_WEBHOOK_SECRET, defaulting toINDEXER_WEBHOOK_SECRET)x-webhook-signature/x-tikka-signatureheader variantsUnauthorizedExceptionwhen the raw body or signature is missingmain.tsEnables Nest/Fastify
rawBody: true— without it the interceptor cannot verify signatures at all.Tests
webhook-signature-verification.interceptor.spec.ts— 302 lines covering valid signatures,tampered bodies, bad timestamps, prefixed/alternate signature headers, missing raw body,
invalid sources, and constant-time comparison
webhooks.controller.spec.ts— 188 lines of controller coveragetest/webhooks-signature.e2e-spec.ts— rewritten to POST real raw bodies end-to-endwebhook-delivery.worker.spec.ts— fixed BullMQ injection to usegetQueueToken(WEBHOOK_DELIVERY_QUEUE)and added shutdown-path coveragebackend/package.json— Jest discovery/transform fixes and Zod 4 import compatibility;zod-validation.pipe.tsandsupabase.provider.tsadjusted accordinglyCloses #1574