fix(mp-client): honor expires_in instead of capping every token at 5 minutes - #77
Merged
Merged
Conversation
…minutes `ensureValidToken()` set `expiresAt = now + 5min` for every token, discarding the `expires_in` the OAuth endpoint returned — while the comment claimed it was subtracting a safety buffer from the real expiration. Since the provider is a singleton and `ensureValidToken()` runs before every service call, a 1-hour MP token was thrown away after 5 minutes, roughly 12x more token requests than necessary. The lifetime now comes from `expires_in`, minus a 5-minute `TOKEN_SAFETY_MARGIN`, floored at 30 seconds so a pathologically short or negative value cannot drive a refresh storm. Missing or non-numeric values fall back to 3600s. `getClientCredentialsToken()` declares a `ClientCredentialsToken` return type instead of leaking `any` out of `response.json()`. Two existing tests pinned the wrong behavior by advancing timers past the 5-minute mark and asserting a refresh; both are rewritten against the real boundary, and the Token Lifecycle block now covers 3600s -> 55min, absent `expires_in`, `expires_in: 60` -> 30s floor, and a non-numeric value. Verified by mutation: restoring the flat cap fails all four, and dropping just the `Math.max` floor fails the clamp test. Closes the TODO; TestCoverage.md 5.7 marked fixed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
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.
Problem
MinistryPlatformClient.ensureValidToken()setexpiresAt = now + 5minfor every token, discarding theexpires_invalue the MP OAuth endpoint returns — while the surrounding comments claimed it was subtracting a safety buffer from the real expiration. Intent and behavior disagreed.MinistryPlatformProvideris a singleton andensureValidToken()runs before every service call, so a 1-hour MP token was being thrown away after 5 minutes: roughly 12× more token requests than necessary.Fix
expires_in, minus a 5-minuteTOKEN_SAFETY_MARGIN, floored atMIN_TOKEN_LIFETIME(30s) so a pathologically short or negative value can't drive a refresh storm.expires_infalls back toDEFAULT_TOKEN_LIFETIME_SECONDS(3600).TOKEN_LIFErenamed toTOKEN_SAFETY_MARGINso the constant says what it is.getClientCredentialsToken()now declares aClientCredentialsTokenreturn type instead of leakinganyout ofresponse.json()— previouslycreds.expires_inwas completely unchecked at compile time.Tests
Two existing tests pinned the wrong behavior — they advanced timers past the 5-minute mark and asserted a refresh, so the flat cap looked deliberate. (The TODO predicted the fix wouldn't break existing tests; it does, which is what made the cap look intentional in the first place.) Both are rewritten against the real boundary, and the
Token Lifecycleblock now covers:expires_in: 3600→ valid at 54:59, refreshes past 55:00expires_inabsent → same 55-minute boundary via the 1-hour defaultexpires_in: 60→ valid at 29s, refreshes past 30s (floor applied, not negative)expires_in: 'not-a-number'→ falls back to the default instead ofNaNAssertions are behavioral (fake timers + call counts), matching the file's existing style — no new public surface on the client.
Verified by mutation: restoring the flat 5-minute cap fails all four new tests; dropping just the
Math.maxfloor fails only the clamp test.Full suite: 582 passed (32 files).
npm run lintandtsc --noEmitclean.Housekeeping
.claude/TODO/mp-client-token-lifetime-ignores-expires-in.mddeleted.claude/docs/TestCoverage.md§5.7 marked ✅ FIXEDOut of scope, noted for later:
ensureValidToken()logs threeconsole.loglines on every MP call, and there's still no in-flight dedup for concurrent first calls (documented as accepted inclient.test.ts).🤖 Generated with Claude Code