Lazy-load soundManager, requestId concurrency tests, pauseGuard tests + 503 fix, webhooks.md cross-check - #1569
Open
Themancalledpg wants to merge 4 commits into
Conversation
…bsCrypt#1523) soundManager.ts was statically imported by GamificationSettings, LevelUpModal, and GlobalXPGain — the last mounted globally in app/layout.tsx — pulling the SoundManager class (and its ~8 placeholder Audio-element setup) into every page load regardless of whether sound is enabled or ever triggered. Splits the SoundManager class into a new soundManagerCore.ts, loaded via a dynamic import() the first time useSoundEffect()'s returned play/setEnabled/setVolume/preload is actually called. Calls made before the import resolves are queued and replayed in order once it's ready. useSoundEffect()'s public API is unchanged, so no consumer call site needed to change — only the module doing the actual work is now deferred.
…pt#1522) Existing tests only covered shape, presence, and propagation for a single request — none proved IDs stay unique when many requests are actually in flight concurrently, the scenario createRequestId is used for in practice. Adds two tests: a 200-request concurrent burst asserting every x-request-id is distinct, and a second test using a route that awaits inside the handler so multiple requests' async contexts genuinely interleave, asserting no request ever sees another request's ID through AsyncLocalStorage. Also documents the ID generation strategy in requestId.ts per the issue's acceptance criteria: createRequestId() delegates to Node's crypto.randomUUID(), which needs no shared/mutable state, so uniqueness under concurrency holds by construction rather than by locking. Note: could not execute this test file in this environment — backend/src/services/defaultChecker.ts has a pre-existing syntax error (`private` modifier used outside any class body, confirmed via `git diff upstream/main` showing zero changes to that file) that breaks the whole TS compile graph transitively through app.js. Verified the core uniqueness assertion logic standalone with a small script using the same crypto.randomUUID() call directly; disclosed in the PR.
…ry (LabsCrypt#1521) Adds pauseGuard.test.ts covering: paused state blocks POST/PUT/PATCH/ DELETE with a 503 AppError, GET/HEAD/OPTIONS bypass the guard even while paused, the rejection is logged with method/path/contracts/ reason, and setPauseState both updates in-memory state and persists it via query(). 10 tests total. While writing these tests, found pauseGuard.ts:77 calls AppError.serviceUnavailable(...) — a method that didn't exist anywhere on the AppError class, and there was no SERVICE_UNAVAILABLE entry in the ErrorCode enum either. This meant the guard crashed with a TypeError instead of returning the intended 503 every time it actually blocked a write during a real pause — the guard's core purpose was non-functional. Added ErrorCode.SERVICE_UNAVAILABLE (httpStatus: 503) and AppError.serviceUnavailable(), matching the existing factory-method pattern exactly. This is a crash fix, not a change to pause-state semantics (which the issue marks out of scope) — the guard's documented 503 behavior now actually happens.
…LabsCrypt#1520) docs/webhooks.md documented 6 event names (loan_approved, repayment_due, repayment_confirmed, loan_defaulted, loan_liquidated, score_changed) that don't exist in webhookService.ts's SUPPORTED_WEBHOOK_EVENT_TYPES at all — they're actually NotificationType values from the unrelated in-app notification system (notificationService.ts / the /api/notifications REST API). The real webhook subscription system dispatches raw Soroban contract event names (LoanApproved, LoanRepaid, CollateralLiquidated, ProposalCreated, etc.) — 64 entries total, including ~20 explicitly-commented legacy aliases. Replaced the "Supported Event Types" table with the real 64 entries (cross-checked programmatically against SUPPORTED_WEBHOOK_EVENT_TYPES — zero missing, zero invented), split into current events and a "Legacy Aliases" section matching eventIndexer.ts's EVENT_TYPE_ALIASES map exactly (including which legacy names do vs. don't currently resolve to a canonical name). Updated the "Creating a Subscription" and "Payload Examples" sections' event names to match, since they'd otherwise reference event names no longer present in the corrected table — kept the same envelope/example format, only the event identifiers changed. Added a comment in webhookService.ts pointing back to this doc so future additions/renames to SUPPORTED_WEBHOOK_EVENT_TYPES are a reminder to update both together.
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.
Summary
Four independent frontend/backend/docs fixes.
closes #1523
closes #1522
closes #1521
closes #1520
Changes
[Frontend] frontend/src/app/utils/soundManager.ts is imported eagerly even when sound is disabled #1523 —
soundManager.tsimported eagerly even when sound is disabled:soundManager.tswas statically imported byGamificationSettings,LevelUpModal, andGlobalXPGain(the last mounted globally inapp/layout.tsx), pulling theSoundManagerclass — including its ~8 placeholderAudioelements — into every page load regardless of whether sound is enabled or ever triggered. Split the class into a newsoundManagerCore.ts, loaded via a dynamicimport()the first timeuseSoundEffect()'s returnedplay/setEnabled/setVolume/preloadis actually called; calls made before the import resolves are queued and replayed in order.useSoundEffect()'s public API is unchanged, so no consumer call site needed to change.[Backend] backend/src/middleware/requestId.ts uniqueness under concurrency is untested #1522 —
requestId.tsuniqueness untested under concurrency: the existing tests only covered shape, presence, and propagation for a single request. Added two tests: a 200-request concurrent burst asserting everyx-request-idis distinct, and a second test through a route thatawaits inside the handler (so requests' async contexts genuinely interleave on the event loop) asserting no request ever observes another request's ID viaAsyncLocalStorage. Also documented the ID generation strategy inrequestId.tsper the acceptance criteria:createRequestId()delegates to Node'scrypto.randomUUID(), which needs no shared mutable state, so concurrent uniqueness holds by construction rather than by locking/sequencing.[Testing] backend/src/middleware/pauseGuard.ts has no dedicated unit test #1521 —
pauseGuard.tshas no dedicated unit test: addedpauseGuard.test.ts(10 tests) covering paused-blocks-writes (POST/PUT/PATCH/DELETE, all with a 503), unpaused-allows-writes, and that GET/HEAD/OPTIONS bypass the guard even while paused (confirmed this is the intended behavior by reading the middleware's own read-only check). Also covers the rejection log shape andsetPauseState's persistence + in-memory update. While writing these, foundpauseGuard.ts:77callsAppError.serviceUnavailable(...)— a method that didn't exist anywhere onAppError, and there was noSERVICE_UNAVAILABLEErrorCodeeither. This meant the guard crashed with aTypeErrorinstead of returning its documented 503 every time it actually blocked a write during a real pause. Added the missingErrorCode.SERVICE_UNAVAILABLE(httpStatus 503) andAppError.serviceUnavailable()factory, matching the existing pattern exactly — this is a crash fix that makes the guard's already-documented behavior work, not a change to pause-state semantics (which the issue marks out of scope).[Docs] docs/webhooks.md event type list is not cross checked against webhookService.ts #1520 —
webhooks.mdnot cross-checked againstwebhookService.ts: the doc's "Supported Event Types" table listed 6 names (loan_approved,repayment_due,repayment_confirmed,loan_defaulted,loan_liquidated,score_changed) that turned out to belong to a completely different, unrelated system — the in-appNotificationTypeused bynotificationService.ts's/api/notificationsREST API. The real webhook subscription system dispatches raw Soroban contract event names fromSUPPORTED_WEBHOOK_EVENT_TYPES(64 entries, including ~20 explicitly-commented legacy aliases). Replaced the table with the real list — cross-checked programmatically against the source array (zero missing, zero invented) — split into current events and a "Legacy Aliases" section that matcheseventIndexer.ts'sEVENT_TYPE_ALIASESmap exactly, including which legacy names do vs. don't currently resolve to a canonical name. Updated the "Creating a Subscription" and "Payload Examples" sections' event names to match (same envelope/format, only the identifiers changed), since they'd otherwise reference names no longer in the corrected table. Added a comment inwebhookService.tspointing back to the doc.Test plan
frontend/src/app/utils/soundManager.test.ts(new, 5 tests): confirmsuseSoundEffect()alone triggers no import; confirmsplay()/setEnabled()each trigger exactly one core-module construction; confirms repeated trigger calls only construct the manager once; confirms calls made before the import resolves are queued and replayed correctly. All 5 passing.backend/src/__tests__/requestId.test.ts(extended, +2 tests): 200-request concurrent burst uniqueness, and an interleaved-async-context uniqueness/no-leakage check.backend/src/middleware/__tests__/pauseGuard.test.ts(new, 10 tests): all passing.npx tsc --noEmiton bothfrontendandbackend— clean (backend has pre-existing errors insrc/services/defaultChecker.ts, confirmed viagit diff upstream/mainto be untouched by this PR — see note below).npx eslinton every touched file — 0 errors; pre-existing warnings confirmed unrelated (unused_frequencies/_durationparams carried over verbatim from the originalsoundManager.ts, and an unrelated pre-existinganycast inrequestId.test.ts).Note on
requestId.test.tsCould not execute this specific test file in this environment:
backend/src/services/defaultChecker.tshas a pre-existing syntax error (aprivatemodifier used outside any class body — it's declared at module scope after the class it presumably belongs to has already closed) that breaks the whole TypeScript compile graph, andrequestId.test.tstransitively importsapp.jswhich pulls that file in. Confirmed viagit diff upstream/main -- backend/src/services/defaultChecker.ts(zero lines changed) that this is fully pre-existing and unrelated to this PR —pauseGuard.test.ts, which doesn't importapp.js, ran and passed fine in the same environment. Verified the concurrent-uniqueness assertion's core logic standalone with a small script callingcrypto.randomUUID()directly (200/200 unique), so the test logic itself is sound; recommend confirming in CI wheredefaultChecker.tspresumably isn't broken (or flagging that file separately).