Files
olp/lib/providers
2b07a3bd1b test: D83 — Suite 38 (quota probe) + Suite 39 (dashboard smoke) (Phase 5) (#55)
* test: D83 — Suite 38 (quota probe) + Suite 39 (dashboard smoke) (Phase 5)

ADR 0012 D83 row: comprehensive test coverage for the Phase 5 quota probe
machinery (D80) and dashboard rendering (D82).

## Authorities cited

- ADR 0012 D83 (D-day specification)
- ADR 0013 Rules 2–6 (quota probe constraints being tested)
- D80 PR #52 (anthropic.quotaStatus() + _probeOnce + _parseRateLimitHeaders)
- D81 PR #53 (aggregateProviderQuota quota_v2 shape)
- D82 PR #54 (dashboard.html Claude.ai-style restructure)
- Schema pin: ~/.cc-rules/memory/learnings/anthropic_plan_usage_probe_schema_2026_05_26.md

## Test seams added to lib/providers/anthropic.mjs

Minimal test seams added — 0 production-logic changes:
- `_setQuotaUrlsForTest(apiUrl, oauthUrl)` — redirects probe HTTP to local
  mock server (auto-detects http: vs https: to switch transport module)
- `_resetQuotaProbeStateForTest()` — resets cache + backoff + URLs + auth seam
- `_resetQuotaStateOnlyForTest()` — resets cache + backoff only (URLs stay)
- `_getQuotaProbeStateForTest()` — returns direct reference to quotaProbeState
  for test assertions and controlled state mutation
- `_setQuotaAuthReadFnForTest(fn)` — injects a mock auth reader into quotaStatus()
  so tests are not affected by real ~/.claude/.credentials.json on the machine
  (fixes 38f: no-auth test was finding real keychain credentials)

## Suite 38 — 20 quota-probe unit tests (38a–38t)

38a: all 13 ratelimit-unified-* headers parsed correctly (numeric types, null defaults)
38b: missing overage-reset → overage_reset: null
38c: missing all three overage fields → all three null
38d: new 5h-status + 7d-status fields (NEW vs OCP 2026-04) parsed correctly
38e: quota_probe_enabled: false → null without HTTP call
38f: auth returns null → quotaStatus returns null without HTTP call
38g: 200 + all 13 headers → full shape with stale:false + backoff reset
38h: cache hit within 5min TTL → no second HTTP call (request count stays 1)
38i: expired cache (6min > 5min TTL) → fires fresh HTTP probe
38j: 401 with no refreshToken → null (idempotent-failure per ADR 0002 Amendment 8 §3)
38k: 429 + stale cache → returns stale cache with stale:true + last_fresh_at
38l: 429 + no cache → null + backoff scheduled (backoffUntil in the future)
38m: exponential backoff growth: 60s→120s→240s→cap at 3600s
38n: successful probe resets backoffMs to 60s + backoffUntil to 0
38o: schemaVersion from models-registry.json (falls back to constant)
38p: doctor quota_probe_reachable disabled → ok with opt-in advisory
38q: doctor probe enabled + probe succeeds → ok with utilization in message
38r: doctor probe enabled + fails + stale cache → warn
38s: doctor probe enabled + fails + no cache → fail with fix_commands
38t: doctor probe enabled + no creds → fail with human_steps only

## Suite 39 — 8 dashboard rendering smoke tests (39a–39h)

39a: /dashboard with owner token → 200 + text/html
39b: /dashboard without token → 401
39c: /dashboard with guest key → 401 (owner-only_block enforcement per ADR 0008 §8)
39d: HTML contains "Plan Usage" header (D82 panel)
39e: HTML contains ↻ Refresh button (D82 manual refresh)
39f: HTML contains QUOTA_POLL_INTERVAL_MS = 60000 (D82 1-min refresh)
39g: HTML contains visibilitychange / visibilityState guard (D82 ADR 0012)
39h: HTML contains quota_v2 consumer + legacy renderQuota fallback code

## Test count: 727 → 755 (+28)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: D83 fold-in — fix 38j misleading name + add 38j2 positive-path + fix 38o ESM require

Fresh-context reviewer (PR #55) flagged one blocking issue + 1 nit:

BLOCKING (Q5) — 38j misleading name + uncovered 401→refresh→retry positive path.
The original 38j test description said "401 → refresh succeeds → retry probe
succeeds" but the test actually asserted the OPPOSITE behavior (401 with no
refreshToken → null, no retry). The most important non-trivial control-flow
branch in _probeOnce (anthropic.mjs:451-457 the refresh-and-retry on 401) was
uncovered, and the misleading name actively masked the gap.

Fix:
- Renamed 38j to accurately describe what it tests: "401 with no refreshToken
  → null (idempotent-failure, no refresh attempt)". The test pins valuable
  behavior — idempotent-failure when refreshToken is absent — but now its
  name matches.
- Added 38j2 (NEW) to exercise the actual positive-path: inject creds with
  refreshToken via _setQuotaAuthReadFnForTest, mock returns 401 on first API
  call + 200 with 13 headers on retry. Asserts: 2 API calls, 1 OAuth call,
  retry parsed full shape, OAuth body contains the injected refreshToken
  (Rule 1 credential reuse verification).

NIT (Q5 dead assertion) — 38o tried to read models-registry.json via
require('fs') which is undefined in ESM. The assert was silently never
exercised. Replaced with the already-imported readFileSync from node:fs
(added _readFileSync38 import). Also tightened: schemaVersion MUST equal
the registry's quota_probe.schema_version (no longer conditional on
"if expected" which was always falsy).

Other reviewer nits deferred (non-blocking per reviewer):
- N2: seam naming convention (_ vs __) — defer
- N3: commit message "zero production changes" — note for v0.5.0 close
- N4: _getQuotaProbeStateForTest returns mutable ref — defer
- N5: test seam runtime guard — defer
- N6: coverage gaps (concurrent probes, registry-missing fallback,
       network error path) — v1.x roadmap follow-ups
- N7: Suite 39 stray error listener — defer (benign)

Test count: 755 → 756 (+1 net, +2 new minus existing 38j rename).
All 756 pass; 0 fail. Local re-run: stable.

Authority: PR #55 review thread (D83 fresh-context opus reviewer)
+ ADR 0013 Rule 1 (credential reuse via refresh).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: dtzp555 <dtzp555@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 17:47:15 +10:00
..