test(user-repository): add failure-handling regression coverage #1027 - #1179
Open
ejiromowarin wants to merge 2 commits into
Open
ejiromowarin wants to merge 2 commits into
ejiromowarin wants to merge 2 commits into
Conversation
Locks the three explicit failure / empty-result exits of UserRepository so a
softened guard fails CI instead of silently succeeding in production:
- createUser, empty RETURNING set -> plain Error('Failed to create user')
- updateUser({ id }) with no field -> plain Error('User not found')
- updateUser, empty UPDATE result -> plain Error('Failed to update user')
New suite: src/db/repositories/userRepository.regression.test.ts (43 cases).
It pins exact messages, the Error class (never UniqueConstraintError for an
empty result), rows.length as the decision key (not rowCount), that no failure
path ever resolves with a partial/fabricated user, that failure messages leak
no SQL/email/password hash, that hostile email content stays a bound
parameter, and that the updateKycRiskTier delegation keeps the same contract.
Evidence (see docs/user-failure-handling-regression.md):
- focused suite 43/43, coverage run 72/72 with userRepository.ts at
100% statements/branches/functions/lines against the 95% gate
- repository suite + direct consumers 157/157
- 4/4 mutants killed (throw->return null, throw->return undefined,
rows.length->rowCount, message drift)
- eslint clean on the new file; tsc 250 error lines, same as baseline
- pre-existing repo failures (whole-suite stall, npm audit gate, 18 failures
in four unrelated suites) reproduce identically with this diff stashed
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.
Overview
This PR adds a focused, test-only regression suite that pins the three explicit
failure / empty-result exits of
UserRepository— the exact branches named in theissue (
src/db/repositories/userRepository.ts:129,:182,:201). No productioncode is touched, so the existing public contract is preserved exactly and the suite
documents current behaviour instead of redefining it.
Related Issue
Closes #1027
Changes
🧪 User Repository Failure Handling
src/db/repositories/userRepository.regression.test.ts— 43 focused casesline 129—createUserwith an emptyRETURNINGset: exactError('Failed to create user')message andErrorclass (neverUniqueConstraintError), never resolves with a partial or fabricated user,still throws when the driver reports
rowCount: 1for zero rows (decision ispinned to
rows.length), still rejects on an unusable row (rows: [null]),deterministic across repeated calls, and the message leaks no SQL text, email
or password hash.
line 182—updateUser({ id })with no updatable field: exactError('User not found'), exactly oneSELECTand never anUPDATE,explicitly-
undefinedoptionals count as "not provided", an empty-stringidis an ordinary lookup miss, deterministic, and never resolves with a user.
line 201—updateUserwhoseUPDATE … RETURNING *matched no row: exactError('Failed to update user'), the statement is issued exactly once, keyedon
rows.length(a stalerowCountstill throws), never resolves with apartial user,
23505staysUniqueConstraintError, non-23505errors arere-thrown by identity, and nothing sensitive leaks into the message.
five bound parameters in positional order for
createUser; every supportedfield bound in declaration order with the id last for
updateUser;startup/standarddefaults and unknown stored tier →standard; storedNULLname →undefined;last_oidc_groups: nulltreated as a providedvalue (real
UPDATEclearing groups) while[]serialises to"[]";empty-string and quote-laden emails bound as parameters; hostile email
payloads never interpolated into SQL;
updateUser({ id })no-op read returnsthe existing user without an
UPDATE.updateKycRiskTierdelegation: success plus propagation of the exact'Failed to update user'/'User not found'contract to its callers.📄 Documentation
docs/user-failure-handling-regression.md— pinned contract rules,security assumptions and abuse/failure-path table, deliberately pinned follow-up
items, run instructions, evidence and mutation results.
⚙️ Tooling
package.json— addstest:coverage:user-failure, a focused runthat applies the 95% threshold to the touched file.
Verification Results
npx jest --ci --coverage=falseis not green on the base commit: 44 suites reportFAILand the run then stalls on a suite whose HTTP server never tears down, so nosummary is printed. The new spec runs in ~2 s and exits cleanly.
18 failed / 263 passedboth times.npm run audit:cifails identically with and without the diff(
@stellar/stellar-sdk,express,qs,stellar-sdk,toml, plus azodoutdated-gate false positive); the gate reads
package-lock.json, which this diffdoes not touch.
.github/workflows/ci.ymlonly triggers for PRs targetingmain, and upstream hasno
mainbranch (every merged PR here targetsmaster), so theaudit/alert-mappingsjobs never run on this repo's PRs. The local runs above are thesubstitute evidence; the workflow fix is deliberately out of scope for this PR.
Security Notes
RETURNINGset — the suite proves the call fails loudly instead of reporting success.
password hash: safe for logs/alerts, and not a probe surface for statement or
credential detail.
emailcontent (including'; DROP TABLE …) stays a bound parameter onboth
createUserandupdateUser; the generated SQL never contains the payload.23505→UniqueConstraintError(field: 'email'), empty results → plainError, so asoftened guard cannot be mistaken for (or masked by) a conflict.
no validation/normalisation (
Callers: RegisterService,scim.ts,oidcRoute.ts),rows: [null]throws aTypeError(only "never returns a user" is asserted), andupdatePasswordHashreports success for an unknown id.userRepository.tsis unchanged, messages/classes pinned as-isrows.lengthvsrowCount, empty-string id/email,NULLname,last_oidc_groups: null, hostile SQL payloadstscat baseline, alert-mapping contract OK,audit:ciunchanged (pre-existing)docs/user-failure-handling-regression.md