Skip to content

Feat/1700 1701 1702 1703 test coverage - #1746

Merged
yusuftomilola merged 6 commits into
DistinctCodes:mainfrom
a-malik-gh:feat/1700-1701-1702-1703-test-coverage
Aug 31, 2026
Merged

Feat/1700 1701 1702 1703 test coverage#1746
yusuftomilola merged 6 commits into
DistinctCodes:mainfrom
a-malik-gh:feat/1700-1701-1702-1703-test-coverage

Conversation

@a-malik-gh

Copy link
Copy Markdown

Four test-coverage fixes across credits, payments, wallets, and auth:

#1700 — atomic revenue-split update validation
RevenueSplitRecipientDto validates each recipient's basis points at the
DTO layer, but nothing proved RevenueSplitService.replaceRecipients
(backing PUT .../recipients) rejects a set that individually passes
per-field validation but doesn't sum to 10000. Added a test with two such
sets (9999 and 10001 total) and confirmed neither rejected update deletes
the existing recipients first — the validation already ran before the
transaction starts; this just proves it.

#1701 — reconciliation overlap protection + full-batch test
ReconciliationService had no bound on how long a full
PAYMENT_RECONCILE_MAX_BATCH pass could take against a slow provider —
500 sequential awaits up to PAYMENT_VERIFY_TIMEOUT_MS each can exceed the
5-minute cron interval. Added a process-local in-progress flag so an
overlapping @Cron tick becomes a no-op instead of double-processing (and
a test proving it), plus a test running a full 500-payment batch to prove
the PAYMENT_RECONCILE_MAX_BATCH cap doesn't silently drop candidates.
True wall-clock timing against a real provider is a staging soak-test
concern, not a synchronous unit test — the overlap guard is the structural
fix for the case where a pass does run long.

#1702 — concurrent custodial wallet provisioning
WalletsService.provisionCustodialWallet's doc comment already claims the
same first-write-wins (user_id) constraint pattern the ledger's overdraft
race gets a dedicated test for, but nothing exercised real concurrency.
Added a test firing 5 genuinely concurrent calls through a small in-memory
WalletAccount table (serializing transactions and enforcing the unique
index, mirroring credits/testing/in-memory-ledger.ts's own approach) —
exactly one wallet is created, every caller converges on it, and
KeyCustodyService.provisionKeypair is only ever called once.

#1703 — RolesGuard none-of-several case
Only the single-role case and "holds one of several" were tested. Added
the missing "holds none of several accepted roles" case. Since UserRole
currently has only two values (USER, ADMIN), a requiredRoles list
naming both is satisfied by any real user — so this is only reachable via
a role value outside the enum (stale JWT, bad seed data), which is exactly
the case the guard must not silently let through. "Holds more than one
role at once" isn't representable today (RequestUser.role is a single
UserRole, not UserRole[]) — documented explicitly in a comment per the
issue's own fallback instruction, rather than left ambiguous.

One additional out-of-scope commit: credits.service.ts and
credits.service.spec.ts on main had a stray fragment of the spec's
build() helper spliced into the middle of CreditsService itself
(between getSystemAccount and getPayableAccount), with the spec file
left holding an orphaned, syntactically-invalid second copy of the same
helper. This broke TypeScript compilation for the whole package — nothing
in credits/, including the #1700 test above, could type-check without
it. Removed the stray fragments; no behavioral change.

Tests / checks performed

  • src/credits/revenue-split.service.spec.ts — all tests pass
  • src/payments/reconciliation.service.spec.ts — all tests pass (overlap-guard tests confirmed via log output: "Skipping reconciliation pass: the previous pass is still running")
  • src/auth/guards/roles.guard.spec.ts — all tests pass
  • npx tsc --noEmit — zero errors in any file this PR touches (confirmed after the credits.service.ts fix; a handful of pre-existing, unrelated errors remain elsewhere in the repo — missing modules for credits-admin.service/payments-admin.service, mismatched controller/DTO signatures in unrelated *.controller.spec.ts files, an Express cookie overload mismatch in auth.controller.ts — none of them touch anything this PR changes)
  • src/wallets/wallets.service.spec.tscould not get a clean local run. This sandbox's npm registry connectivity was severely degraded for this session (confirmed via ~12 install attempts across npm install, npm ci, varied retry/timeout/concurrency settings, and detached/nohup runs — each failed a different way: ECONNRESET, ENOTEMPTY/EPERM on a nested rxjs cleanup during dedup, npm's own "Exit handler never called" bug, or a genuine multi-minute stall with zero progress). The one clean install I did briefly get before a later reinstall attempt wiped it showed all 3 other suites above passing 45/45, and this file's only compile blocker was a corrupted @stellar/stellar-sdk/@stellar/stellar-base install (an unrelated third-party package, not this PR's code) — reinstalling just that package fixed the compile error and the file ran, before the environment degraded further. I'm confident in the test's correctness (same in-memory-transaction-table pattern as the ledger's own passing overdraft-race test) but would appreciate CI confirming this suite specifically.

Closes

Closes #1700
Closes #1701
Closes #1702
Closes #1703

…cipient set atomically

Every recipient in the failing cases individually satisfies
RevenueSplitRecipientDto's per-field @min(1) @max(10000) check — only the
set's total is wrong (9999 or 10001). Adds a test on the update path
(replaceRecipients, backing PUT .../recipients) proving that rejection,
and that neither rejected update partially deletes the existing recipients
before the sum check fails.

Closes DistinctCodes#1700
… a full batch

Nothing bounded how long a full PAYMENT_RECONCILE_MAX_BATCH pass could take
against a slow/degraded provider — 500 payments sequentially awaiting up to
PAYMENT_VERIFY_TIMEOUT_MS each can exceed the 5-minute cron interval. Adds a
process-local in-progress flag so a @Cron tick firing while the previous
pass is still running becomes a no-op instead of double-processing the same
payments (duplicate provider calls, doubled reconciliationAttempts).

Also adds a test processing a full 500-payment batch to prove the
PAYMENT_RECONCILE_MAX_BATCH cap doesn't silently drop candidates. True
wall-clock throughput against a real provider is a staging soak-test
concern, not something a synchronous unit test can honestly prove — the
overlap guard is the structural safeguard for the case where a pass does
run long.

Closes DistinctCodes#1701
…ouble-provision

KeyCustodyService.provisionKeypair() and WalletsService's wallet-creation
path rely on the same first-write-wins (user_id) unique constraint the
ledger's overdraft race already gets a dedicated test for, but had no
equivalent proof. Fires several genuinely concurrent
provisionCustodialWallet calls for the same user through a small in-memory
WalletAccount table (serializing transaction callbacks and enforcing the
unique index, matching credits/testing/in-memory-ledger.ts's own
documented approach) and asserts exactly one wallet is created, every
caller converges on it, and the losing calls recover via the existing
catch-and-refetch path rather than ever surfacing the raw 23505 to a
caller.

Closes DistinctCodes#1702
…-more-than-one gap

Only the single-role admin case and "holds one of several" were exercised
anywhere in the app. Adds the missing "holds none of several accepted
roles" case — since UserRole currently has only two values (USER, ADMIN),
a requiredRoles list naming both is satisfied by any real user, so this is
only reachable via a role value outside the enum (stale JWT, bad seed
data), which is exactly the case the guard must not silently allow.

"Holds more than one required role at once" isn't representable today:
RequestUser.role (authenticated-request.interface.ts) is a single
UserRole, not UserRole[], so per the issue's own fallback, that limitation
is documented explicitly in a comment rather than left ambiguous.

Closes DistinctCodes#1703
…edits.service.ts

Pre-existing bug on main, unrelated to DistinctCodes#1700-1703: a copy of
credits.service.spec.ts's build() helper was somehow duplicated into the
middle of CreditsService itself (between getSystemAccount and
getPayableAccount), and the spec file was left with an orphaned second
copy of the same helper missing its function signature. Both broke
TypeScript compilation outright, which meant nothing in this package —
including the four issues above — could even be type-checked, let alone
tested. Removing the stray fragments restores both files to valid,
working code; no behavioral change.
@vercel

vercel Bot commented Aug 30, 2026

Copy link
Copy Markdown

@A6dulmalik is attempting to deploy a commit to the naijabuz's projects Team on Vercel.

A member of the Team first needs to authorize it.

@drips-wave

drips-wave Bot commented Aug 30, 2026

Copy link
Copy Markdown

@a-malik-gh Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@yusuftomilola yusuftomilola left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No merge conflicts with main. Good test coverage additions across roles guard, revenue-split, reconciliation, and wallets services - approving.

@yusuftomilola
yusuftomilola merged commit d3fa7db into DistinctCodes:main Aug 31, 2026
2 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment