test(oauth): cover stored-credential validation guards - #331
test(oauth): cover stored-credential validation guards#331KrasimirKralev wants to merge 1 commit into
Conversation
|
🦞👀 Pull request received. I will update this pull request when review starts. |
|
Codex review: needs real behavior proof before merge. Reviewed August 24, 2026, 7:02 AM ET / 11:02 UTC. ClawSweeper reviewWhat this changesThe PR adds direct Vitest coverage for the guards that accept or reject stored OAuth token and client-information records. Merge readiness⛔ Blocked until real behavior proof from a real setup is added - 3 items remain The patch is a focused, correct coverage addition, but it still lacks real persistence-path proof; keep it open for that contributor evidence. Priority: P3 Review scores
Verification
Live VerificationCommand: Result: PASS (completed) Assertions:
How this fits togetherMCPorter’s OAuth persistence layer reads token and client JSON from disk, validates stored shapes, and either recovers a session or drops invalid data so authentication can restart. This PR tests that validation boundary directly. flowchart LR
A[Credential JSON on disk] --> B[OAuth persistence reader]
B --> C[Stored credential validation]
C -->|valid| D[Recovered OAuth session]
C -->|invalid| E[Drop credential]
E --> F[Re-authentication]
G[New direct tests] --> C
Before merge
Agent review detailsSecurityNone. Review metrics
Merge-risk optionsMaintainer options:
Technical reviewBest possible solution: Add a redacted terminal trace that writes parseable malformed token and client JSON through the directory persistence reader and shows both are dropped, then land the focused test-only patch. Do we have a high-confidence way to reproduce the issue? Not applicable: this is a coverage-only PR, and its acceptance target is the existing stored-credential validation contract rather than a reported runtime failure. Is this the best way to solve the issue? Yes: a direct regression suite is the narrowest way to pin the shared validation rules, provided the claimed persistence effect is also shown with a real reader trace. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against ae3d9000c320. LabelsLabel justifications:
EvidenceWhat I checked:
Likely related people:
Rank-up movesOptional improvements that raise the rating; they are not merge blockers.
Rating scale
Overall follows the weaker of proof and patch quality. Workflow
History |
What Problem This Solves
src/oauth-credential-validation.tsexports the two type-guards that decide whether a stored OAuth credential read back from disk is trusted or dropped —isStoredOAuthTokensandisStoredOAuthClientInformation— and the module shipped with no direct test coverage (notests/oauth-credential-validation.test.tsonmain). Both gate real persistence decisions:oauth-persistence-stores.ts:146/:198—readTokens()/readClientInfo()return the stored value only if the guard passes, otherwiseundefined(drop-and-re-auth).oauth-vault.ts:247/:248— a snapshot persiststokens/clientInfowith a hidden generation marker only if the guard passes.The guards carry a subtle contract a plausible refactor could silently break:
isStoredOAuthTokensrequires a non-emptyaccess_tokenstring and a non-emptytoken_typestring;refresh_token/scope/issuermust each be absent-or-string;expires_in/expires_at/expiresAtmust each be absent-or-a-finite-number; a non-record (null, array, primitive) is rejected.isStoredOAuthClientInformationrejects a non-record and rejects a present-but-non-stringissuer.Dropping the empty-string check, the
Number.isFiniteguard, the optional-type checks, or the array/record discrimination would let a malformed credential through (or reject a valid one) with nothing to catch it.Why This Change Was Made
Coverage-only. Adds one new file,
tests/oauth-credential-validation.test.ts(+79, no production code touched), pinning both guards' currentmainbehavior. The tests import the real exported functions and drive them directly — no stubs. No new config, defaults, dependencies, or behavior.Not a duplicate of the existing boundary suite (scope note).
tests/oauth-persistence-stores.test.tsreaches these guards, but only on the accept arm — it stores valid tokens and asserts they round-trip (:37,:59,:99), and its "corrupt credential JSON" case (:71) writes'{bad', which failsJSON.parsein the reader before the guard runs, so it never exercises the guards' rejection logic. Every rejection branch (empty required string, wrong optional type, non-finite number, non-record, non-string issuer) is unreached by any test at any level. This suite adds exactly those provably-skipped arms — see the mutation evidence below, where the existing boundary suite stays green under guard mutations that this suite catches.User Impact
No user-visible or runtime change. For maintainers, the stored-credential validation contract now regresses loudly instead of silently: a future edit that breaks the empty-token gate, an optional-field type check, the finite-number check, or the record/array discrimination will fail this suite.
Evidence
Linux, Node 22.22,
pnpm install --frozen-lockfilefrom source, branched off currentmain(ae3d900).10/10 pass on clean
main:Non-vacuous — the suite bites when the target is mutated (each mutation applied to
oauth-credential-validation.ts, suite re-run, then reverted byte-identical):oauth-credential-validation.tsaccess_token.length > 0→>= 0(accept empty)token_type.length > 0→>= 0(accept empty)isRecorddrops!Array.isArray(value)isOptionalStringdrops the=== undefinedarm (require string)isOptionalFiniteNumberdropsNumber.isFiniteisStoredOAuthClientInformationreturnstrueafter the record checkThese regressions are invisible to the existing suite. Running the current caller/boundary tests (
oauth-persistence-stores,oauth-persistence,vault-command,oauth-session) under the first, fifth, and third mutations above leaves their pass/fail count unchanged — the boundary suite never feeds a parseable-but-invalid credential to the guard, so it cannot catch these. This suite is the only coverage of the rejection contract.Format / lint / types clean on the new file:
Scope note: one new
*.test.tsundertests/,+79 / -0, no production code touched. Same module family as the merged OAuth-helper coverage #246 (token generation) and #281 (client info) — this is the stored-credential validation sibling.AI-assisted contribution.
Opened from an org-owned fork via the API; if GitHub's Allow edits by maintainers toggle isn't honored on this PR, a maintainer can still push to the branch or supersede-and-land.
Generated by Claude Code