feat+test: F4 CLI/plugin quota_v2 migration + v1.x #7 AUTH_MISSING test pin (#59)

## F4 — bin/olp.mjs + olp-plugin/index.js migrate to quota_v2

Codex post-v0.5.0 review Q4: both `olp usage` CLI and `/olp usage`
plugin handler read legacy `body.quota` shape, which never has
`percent_used` or meaningful `available` data → display always fell
through to "no quota api" even when anthropic live quota was visible
on the dashboard (D81/D82).

Authority: codex review Q4 (PR #58); ADR 0008 Amendment 2 (quota_v2
shape: { provider, status, utilization, reset, representative_claim,
fallback_percentage, overage, failure?, failure_kind?, backoff_until? }).

### bin/olp.mjs cmdUsage

- When `body.quota_v2` present (non-empty array, server v0.5.0+): render
  per-provider rows with status badge (live/stale/unreachable/unavailable),
  5h + 7d utilization % (color-coded: green <50% / yellow 50-80% / red ≥80%),
  reset countdowns, binding claim, ⚠ stale /  unreachable annotations.
- When `body.quota_v2` absent: fall through to existing legacy `body.quota`
  rendering (preserves backwards compat with pre-v0.5.0 servers).
- Added `formatResetCountdown(epochSeconds)` — 5-range formatter (past /
  <1h / <24h / <7d / ≥7d), ported from dashboard.html D82. Exported for
  tests. Kept in bin/olp.mjs (not lib/) per spec guidance.
- Added `formatAgo(diffMs)` — small helper for stale-row age display.

### olp-plugin/index.js fmtUsage()

- Same quota_v2 migration. Plain-text output (no ANSI), one line per
  provider. Legacy body.quota fallback preserved.
- Added `pluginFormatResetCountdown(epochSeconds)` — intentionally
  duplicated from bin/olp.mjs (olp-plugin ships as a separate package
  and must not import from bin/). Exported for tests.
- Also fixed fmtUsage to read `w.request_count` (dashboard-data shape)
  in addition to legacy `w.requests` (OCP-era shape) for completeness.

### Backwards compat

- Pre-v0.5.0 server returns body.quota only → both surfaces use legacy
  display (unchanged behaviour).
- v0.5.0+ server returns both quota and quota_v2 → both surfaces prefer
  quota_v2.
- Legacy code paths kept (5-10 lines each, not deleted).

## v1.x roadmap #7 — AUTH_MISSING tuple path test coverage — CLOSED

The dedicated test was already shipped at D56 (test-features.mjs line
6255: 'engine: AUTH_MISSING terminates chain, fallbackDetail tuple
records trigger_type:"auth_missing" (D56, v1.x roadmap #7)'). This
commit closes the roadmap entry with a date stamp and PR reference.

Authority: docs/v1x-roadmap.md § "#7 — AUTH_MISSING tuple path test
coverage (D40 follow-up)".

## Tests

Suite 40 (9 new tests — 40a through 40i):
  40a — cmdUsage parses quota_v2 live rows (mock server)
  40b — cmdUsage falls back to legacy body.quota when quota_v2 absent
  40c — olp-plugin fmtUsage parses quota_v2 live row
  40d — olp-plugin fmtUsage falls back to legacy body.quota
  40e — formatResetCountdown covers all 5 time ranges
  40f — pluginFormatResetCountdown covers past/<1h/<24h/<7d/≥7d
  40g — cmdUsage renders quota_v2 stale row with ⚠ stale note
  40h — cmdUsage renders quota_v2 unreachable row with  indicator
  40i — cmdUsage renders quota_v2 unavailable rows correctly

759 → 768 tests, 0 fail.

Co-authored-by: dtzp555 <dtzp555@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
dtzp555-max
2026-05-27 11:24:46 +10:00
committed by GitHub
co-authored by taodeng Claude Sonnet 4.6
parent bddf2cba1e
commit fa2d1af130
5 changed files with 551 additions and 8 deletions
+20 -1
View File
@@ -4,7 +4,26 @@ All notable changes to OLP land here. Per `CLAUDE.md` release_kit overlay, this
## Unreleased ## Unreleased
(empty — Phase 6 entries land here once Phase 6 opens) ### F4 — `bin/olp.mjs` + `olp-plugin/index.js` migration to `quota_v2` shape
**Codex post-v0.5.0 review Q4.** Both CLI surfaces (`olp usage` and `/olp usage`) previously fell through to "no quota api" for every provider because they read the legacy `body.quota` shape, which never carries `percent_used` or meaningful `available` data. Now that the server (v0.5.0+) emits `body.quota_v2` per ADR 0008 Amendment 2, both surfaces prefer `quota_v2` and fall back to legacy `quota` on older servers.
- **`bin/olp.mjs cmdUsage`**: when `body.quota_v2` is present (non-empty array), renders per-provider rows with status (`live` / `stale` / `unreachable` / `unavailable`), 5h and 7d utilization percentages with color-coding (green < 50% / yellow 5080% / red ≥ 80%), reset countdowns, binding claim, and ⚠ stale / ❌ unreachable annotations. Legacy `body.quota` path preserved as fallback for pre-v0.5.0 servers. `formatResetCountdown(epochSeconds)` added — 5-range formatter (past / <1h / <24h / <7d / ≥7d), ported from `dashboard.html` D82, kept in-file (no shared lib).
- **`olp-plugin/index.js fmtUsage()`**: same migration — `quota_v2` rows render as one-line plain text per provider (no ANSI; Telegram/Discord safe). `pluginFormatResetCountdown(epochSeconds)` added; intentionally duplicated (plugin ships as a separate package). Legacy `body.quota` fallback preserved.
### v1.x roadmap #7 — AUTH_MISSING tuple path test coverage — ✅ CLOSED
The dedicated AUTH_MISSING engine test (asserting `fallbackDetail[0].trigger_type === 'auth_missing'`) was already shipped at D56 (`test-features.mjs` line 6255). This item closes the roadmap entry with a date stamp and PR reference per the tracker convention. No code changes — documentation only.
### Tests
- Suite 40 (9 new tests): `40a``40i` covering `cmdUsage` quota_v2 live/stale/unreachable/unavailable parse, legacy fallback, `pluginFormatResetCountdown` and `formatResetCountdown` 5-range coverage, olp-plugin `fmtUsage` quota_v2 + legacy paths. 759 → 768 tests, 0 fail.
### Authority
- F4: codex post-v0.5.0 review Q4 (PR #58 review); ADR 0008 Amendment 2 (quota_v2 shape).
- #7: `docs/v1x-roadmap.md` § "#7 — AUTH_MISSING tuple path test coverage (D40 follow-up)".
## v0.5.1 — 2026-05-27 ## v0.5.1 — 2026-05-27
+86 -1
View File
@@ -226,6 +226,53 @@ function formatMs(ms) {
return `${Math.floor(ms / 3600000)}h${Math.floor((ms % 3600000) / 60000)}m`; return `${Math.floor(ms / 3600000)}h${Math.floor((ms % 3600000) / 60000)}m`;
} }
/** formatAgo(diffMs) — "N min ago" / "Nh ago" from a millisecond diff. */
function formatAgo(diffMs) {
if (typeof diffMs !== 'number' || diffMs < 0) return 'just now';
const sec = Math.floor(diffMs / 1000);
if (sec < 60) return `${sec}s ago`;
const min = Math.floor(sec / 60);
if (min < 60) return `${min}m ago`;
return `${Math.floor(min / 60)}h ago`;
}
/**
* formatResetCountdown(epochSeconds) → human-readable reset countdown string.
*
* Mirrors dashboard.html formatResetCountdown(). Five ranges:
* past / < 1h / < 24h / < 7d / ≥ 7d
*
* Authority: ADR 0008 Amendment 2 (quota_v2 shape); ported from
* dashboard.html (D82). No external deps. Pure formatter.
*
* @param {number|null} epochSeconds — Unix epoch seconds for reset time
* @returns {string}
*/
export function formatResetCountdown(epochSeconds) {
if (epochSeconds == null) return '—';
const nowMs = Date.now();
const targetMs = epochSeconds * 1000;
const diffMs = targetMs - nowMs;
if (diffMs <= 0) return 'resetting now';
const diffMin = Math.floor(diffMs / 60000);
const diffHr = Math.floor(diffMin / 60);
const diffDay = Math.floor(diffHr / 24);
if (diffMin < 60) return `resets in ${diffMin}m`;
if (diffHr < 24) {
const remMin = diffMin - diffHr * 60;
if (remMin === 0) return `resets in ${diffHr}h`;
return `resets in ${diffHr}h ${remMin}m`;
}
const target = new Date(targetMs);
const timeStr = target.toLocaleString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true });
if (diffDay < 7) {
const dayStr = target.toLocaleString('en-US', { weekday: 'short' });
return `resets ${dayStr} ${timeStr}`;
}
const dateStr = target.toLocaleString('en-US', { month: 'short', day: 'numeric' });
return `resets ${dateStr} ${timeStr}`;
}
// ── Subcommand: status ──────────────────────────────────────────────────── // ── Subcommand: status ────────────────────────────────────────────────────
async function cmdStatus(flags, io) { async function cmdStatus(flags, io) {
@@ -329,7 +376,45 @@ async function cmdUsage(flags, io) {
} else { } else {
io.log(' (no 24h usage data — server may not have processed any requests yet)'); io.log(' (no 24h usage data — server may not have processed any requests yet)');
} }
if (Array.isArray(body.quota) && body.quota.length > 0) { // F4 (v0.5.1 codex post-release review Q4): prefer quota_v2 when present
// (server v0.5.0+), fall back to legacy quota array on older servers.
// Authority: ADR 0008 Amendment 2 (quota_v2 shape).
if (Array.isArray(body.quota_v2) && body.quota_v2.length > 0) {
io.log('');
io.log(colorize('Per-provider quota (live)', ANSI.bold, io.useColor));
io.log('─'.repeat(60));
for (const p of body.quota_v2) {
const label = String(p.provider ?? '?').toUpperCase().padEnd(12);
const status = p.status ?? 'unavailable';
if (status === 'unavailable') {
io.log(` ${colorize(label, ANSI.gray, io.useColor)} unavailable ${p.reason ?? 'no public quota api'}`);
} else if (status === 'unreachable') {
const fk = p.failure?.kind ?? 'unknown';
const fm = p.failure?.message ?? 'probe failed';
io.log(` ${colorize(label, ANSI.red, io.useColor)} ❌ no cached data — failure: ${fk} (${fm})`);
} else {
// live or stale
const staleWarn = status === 'stale'
? colorize(` ⚠ stale${p.last_fresh_at ? ` (${formatAgo(Date.now() - p.last_fresh_at)})` : ''} failure: ${p.failure?.kind ?? 'unknown'}`, ANSI.yellow, io.useColor)
: '';
const util = p.utilization ?? {};
const reset = p.reset ?? {};
const parts = [];
for (const window of ['5h', '7d']) {
const frac = util[window];
const resetEpoch = reset[window];
if (frac != null) {
const pct = `${Math.round(frac * 100)}%`;
const rst = formatResetCountdown(resetEpoch);
parts.push(`${window}: ${colorize(pct, frac >= 0.8 ? ANSI.red : frac >= 0.5 ? ANSI.yellow : ANSI.green, io.useColor)} (${rst})`);
}
}
const binding = p.representative_claim ? ` binding: ${p.representative_claim.replace('_', '-')}` : '';
io.log(` ${colorize(label, ANSI.bold, io.useColor)} ${colorize(status, status === 'live' ? ANSI.green : ANSI.yellow, io.useColor).padEnd(6)} ${parts.join(' ')}${binding}${staleWarn}`);
}
}
} else if (Array.isArray(body.quota) && body.quota.length > 0) {
// Legacy fallback for pre-v0.5.0 servers
io.log(''); io.log('');
io.log(colorize('Per-provider quota', ANSI.bold, io.useColor)); io.log(colorize('Per-provider quota', ANSI.bold, io.useColor));
io.log('─'.repeat(60)); io.log('─'.repeat(60));
+3 -2
View File
@@ -8,7 +8,7 @@
3. **Where** does the work live in the tree today (file + anchor). 3. **Where** does the work live in the tree today (file + anchor).
4. **When** does it need to land (trigger: load profile, security event, governance amendment). 4. **When** does it need to land (trigger: load profile, security event, governance amendment).
**Reading order for a v1.x sprint kickoff.** As of 2026-05-26, #1 (streaming SF, D57+D58), #2 (multi-key auth, Phase 2), #4 and #7 (closed in D56), and #8 (dashboard enrichment, D82 Phase 5) are CLOSED. Remaining v1.x scope: #3 (soft trigger reactivation), #5 (provider cacheKeyFields mask), #6 (streaming SPAWN_FAILED salvage — unbundled from #1 at #1 close). All three remaining items have explicit "trigger to start" gates that have not fired. **Reading order for a v1.x sprint kickoff.** As of 2026-05-27, #1 (streaming SF, D57+D58), #2 (multi-key auth, Phase 2), #4, #7, and #8 are CLOSED. Remaining v1.x scope: #3 (soft trigger reactivation), #5 (provider cacheKeyFields mask), #6 (streaming SPAWN_FAILED salvage — unbundled from #1 at #1 close). All three remaining items have explicit "trigger to start" gates that have not fired.
--- ---
@@ -107,8 +107,9 @@
- `lib/audit-query.mjs` — current `aggregateRequests` is wall-clock-window; needs session-window variant - `lib/audit-query.mjs` — current `aggregateRequests` is wall-clock-window; needs session-window variant
- **Trigger to start.** ANY of: (a) Anthropic publishes a documented `claude usage` CLI or `api.anthropic.com/v1/usage` endpoint, (b) maintainer hits real "I want to see quota right now" pain often enough to design without per-provider truth (audit-derived only), (c) Phase 5 multi-tenant adds per-key spend limits and the dashboard needs to surface those. - **Trigger to start.** ANY of: (a) Anthropic publishes a documented `claude usage` CLI or `api.anthropic.com/v1/usage` endpoint, (b) maintainer hits real "I want to see quota right now" pain often enough to design without per-provider truth (audit-derived only), (c) Phase 5 multi-tenant adds per-key spend limits and the dashboard needs to surface those.
## #7 — AUTH_MISSING tuple path test coverage (D40 follow-up) ## #7 — AUTH_MISSING tuple path test coverage (D40 follow-up) — ✅ **CLOSED (D56, 2026-05-27)**
- **Status.** Closed. Test shipped at D56 (PR `f4-cli-plugin-quota-v2-plus-auth-missing-test`, 2026-05-27). Test: `test-features.mjs` line 6255 — `'engine: AUTH_MISSING terminates chain, fallbackDetail tuple records trigger_type:"auth_missing" (D56, v1.x roadmap #7)'`. Asserts: `result.fallbackDetail[0].code === 'AUTH_MISSING'`, `result.fallbackDetail[0].trigger_type === 'auth_missing'`, `result.fallbackHops === 0` (no advance). The test was already present in the file before this PR closed the roadmap entry.
- **What.** Dedicated test in `test-features.mjs` Suite D40 that asserts the `fallbackDetail` tuple records the AUTH_MISSING path with `trigger_type: 'auth_missing'`. D40 reviewer flagged this as the last gap in the engine-path matrix; code is structurally correct, just lacks an explicit pin. - **What.** Dedicated test in `test-features.mjs` Suite D40 that asserts the `fallbackDetail` tuple records the AUTH_MISSING path with `trigger_type: 'auth_missing'`. D40 reviewer flagged this as the last gap in the engine-path matrix; code is structurally correct, just lacks an explicit pin.
- **Why deferred.** Low priority — the AUTH_MISSING early-return branch has the tuple push BEFORE it (verified in D40 reviewer pass), so coverage is implicit via the other engine-path tests. A 3-line dedicated test would make the pin explicit. - **Why deferred.** Low priority — the AUTH_MISSING early-return branch has the tuple push BEFORE it (verified in D40 reviewer pass), so coverage is implicit via the other engine-path tests. A 3-line dedicated test would make the pin explicit.
- **Design.** No ADR needed. ~5-line test addition. - **Design.** No ADR needed. ~5-line test addition.
+79 -4
View File
@@ -169,26 +169,101 @@ export function fmtHealth(body) {
return out; return out;
} }
/**
* formatResetCountdown(epochSeconds) → human-readable reset countdown.
*
* Mirrors bin/olp.mjs + dashboard.html versions. Five ranges:
* past / < 1h / < 24h / < 7d / ≥ 7d
*
* Authority: ADR 0008 Amendment 2 (quota_v2 shape), ported from dashboard.html (D82).
* No external deps. Duplicated here intentionally (olp-plugin ships separately).
*/
export function pluginFormatResetCountdown(epochSeconds) {
if (epochSeconds == null) return "—";
const nowMs = Date.now();
const targetMs = epochSeconds * 1000;
const diffMs = targetMs - nowMs;
if (diffMs <= 0) return "resetting now";
const diffMin = Math.floor(diffMs / 60000);
const diffHr = Math.floor(diffMin / 60);
const diffDay = Math.floor(diffHr / 24);
if (diffMin < 60) return `resets in ${diffMin}m`;
if (diffHr < 24) {
const remMin = diffMin - diffHr * 60;
if (remMin === 0) return `resets in ${diffHr}h`;
return `resets in ${diffHr}h ${remMin}m`;
}
const target = new Date(targetMs);
const timeStr = target.toLocaleString("en-US", { hour: "numeric", minute: "2-digit", hour12: true });
if (diffDay < 7) {
const dayStr = target.toLocaleString("en-US", { weekday: "short" });
return `resets ${dayStr} ${timeStr}`;
}
const dateStr = target.toLocaleString("en-US", { month: "short", day: "numeric" });
return `resets ${dateStr} ${timeStr}`;
}
export function fmtUsage(body) { export function fmtUsage(body) {
let out = "OLP usage (24h)\n"; let out = "OLP usage (24h)\n";
out += "─────────────────────────────\n"; out += "─────────────────────────────\n";
const w = body.window_24h ?? body.usage_24h ?? {}; const w = body.window_24h ?? body.usage_24h ?? {};
if (w.requests !== undefined) { if (w.request_count !== undefined) {
out += `Requests: ${w.request_count}\n`;
const c = body.cache_hit_24h ?? {};
if (typeof c.hit_rate === "number") {
out += `Cache hit: ${(c.hit_rate * 100).toFixed(1)}%\n`;
}
} else if (w.requests !== undefined) {
out += `Requests: ${w.requests}\n`; out += `Requests: ${w.requests}\n`;
out += `Cache hit: ${w.cache_hit_rate != null ? `${(w.cache_hit_rate * 100).toFixed(1)}%` : "?"}\n`; out += `Cache hit: ${w.cache_hit_rate != null ? `${(w.cache_hit_rate * 100).toFixed(1)}%` : "?"}\n`;
out += `Fallbacks: ${w.fallbacks ?? "?"}\n`; out += `Fallbacks: ${w.fallbacks ?? "?"}\n`;
} else if (typeof body.cache_hit_24h === "number") { } else if (typeof body.cache_hit_24h === "number") {
// Dashboard-data shape: cache_hit_24h is a rate ∈ [0,1] // Legacy: cache_hit_24h as a bare number
out += `Cache hit (24h): ${(body.cache_hit_24h * 100).toFixed(1)}%\n`; out += `Cache hit (24h): ${(body.cache_hit_24h * 100).toFixed(1)}%\n`;
} }
if (Array.isArray(body.quota) && body.quota.length > 0) {
// F4: prefer quota_v2 when present (server v0.5.0+), fall back to legacy quota.
// Authority: ADR 0008 Amendment 2 (quota_v2 shape).
if (Array.isArray(body.quota_v2) && body.quota_v2.length > 0) {
out += `\nPer-provider quota (live):\n`;
for (const p of body.quota_v2) {
const name = String(p.provider ?? "?").toUpperCase().padEnd(10);
const status = p.status ?? "unavailable";
if (status === "unavailable") {
out += ` ${name} unavailable ${p.reason ?? "no public quota api"}\n`;
} else if (status === "unreachable") {
const fk = p.failure?.kind ?? "unknown";
out += ` ${name} no cached data — failure: ${fk}\n`;
} else {
// live or stale
const util = p.utilization ?? {};
const reset = p.reset ?? {};
const parts = [];
for (const window of ["5h", "7d"]) {
const frac = util[window];
const resetEpoch = reset[window];
if (frac != null) {
const pct = `${Math.round(frac * 100)}%`;
const rst = pluginFormatResetCountdown(resetEpoch);
parts.push(`${window}: ${pct} (${rst})`);
}
}
const staleNote = status === "stale"
? ` ⚠ stale (${p.failure?.kind ?? "unknown"})`
: "";
out += ` ${name} ${status.padEnd(6)} ${parts.join(" ")}${staleNote}\n`;
}
}
} else if (Array.isArray(body.quota) && body.quota.length > 0) {
// Legacy fallback for pre-v0.5.0 servers
out += `\nPer-provider quota:\n`; out += `\nPer-provider quota:\n`;
for (const q of body.quota) { for (const q of body.quota) {
const pct = typeof q.percent_used === "number" ? q.percent_used : null; const pct = typeof q.percent_used === "number" ? q.percent_used : null;
const bar0 = pct != null ? ` ${bar(pct / 100, 12)} ${pct.toFixed(0)}%` : " no quota api"; const bar0 = pct != null ? ` ${bar(pct / 100, 12)} ${pct.toFixed(0)}%` : " no quota api";
out += ` ${String(q.name ?? "?").padEnd(10)}${bar0}\n`; out += ` ${String(q.provider ?? q.name ?? "?").padEnd(10)}${bar0}\n`;
} }
} }
if (Array.isArray(body.top_fallback_chains_24h) && body.top_fallback_chains_24h.length > 0) { if (Array.isArray(body.top_fallback_chains_24h) && body.top_fallback_chains_24h.length > 0) {
out += `\nTop fallback chains (24h):\n`; out += `\nTop fallback chains (24h):\n`;
for (const f of body.top_fallback_chains_24h.slice(0, 5)) { for (const f of body.top_fallback_chains_24h.slice(0, 5)) {
+363
View File
@@ -16900,3 +16900,366 @@ describe('Suite 39 — D83 dashboard rendering smoke tests (Phase 5, ADR 0012 D8
'39h: dashboard HTML must contain legacy quota fallback path (graceful-degradation proof)'); '39h: dashboard HTML must contain legacy quota fallback path (graceful-degradation proof)');
}); });
}); });
// ── Suite 40: F4 CLI/plugin quota_v2 migration + v1.x #7 AUTH_MISSING test pin ─
//
// Codex post-v0.5.0 review Q4: bin/olp.mjs cmdUsage and olp-plugin/index.js
// fmtUsage both read legacy body.quota (never has percent_used or available data),
// so the display always fell through to "no quota api" even when anthropic's live
// quota was visible on the dashboard. F4 adds consumption of body.quota_v2 (server
// v0.5.0+ shape per ADR 0008 Amendment 2) with graceful fallback to body.quota on
// older servers.
//
// v1.x roadmap #7 was already closed at D56 (test at line 6255); this suite
// references that test by description. The roadmap entry closure (marking ✅ CLOSED)
// happens in docs/v1x-roadmap.md (committed alongside these tests).
//
// Tests:
// 40a — cmdUsage renders quota_v2 live rows (mock server)
// 40b — cmdUsage falls back to legacy body.quota when quota_v2 absent
// 40c — olp-plugin fmtUsage parses quota_v2 live row
// 40d — olp-plugin fmtUsage falls back to legacy body.quota when quota_v2 absent
// 40e — formatResetCountdown past / <1h / <24h / <7d / ≥7d ranges
// 40f — olp-plugin pluginFormatResetCountdown parallel coverage
// 40g — cmdUsage renders quota_v2 stale row with ⚠ stale note
// 40h — cmdUsage renders quota_v2 unreachable row with ❌ indicator
// 40i — cmdUsage renders quota_v2 unavailable row with reason
import { runCli as runOlpCli40, formatResetCountdown as olpFormatResetCountdown } from './bin/olp.mjs';
import { fmtUsage as plugFmtUsage40, pluginFormatResetCountdown } from './olp-plugin/index.js';
// ── Helper: tiny HTTP server that responds to one request with a static JSON body ──
import { createServer as createHttpServerS40 } from 'node:http';
function makeJsonServer40(responseBody) {
return new Promise((resolve, reject) => {
const srv = createHttpServerS40((req, res) => {
const body = typeof responseBody === 'function' ? responseBody(req) : responseBody;
const raw = JSON.stringify(body);
res.writeHead(200, { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(raw) });
res.end(raw);
});
srv.listen(0, '127.0.0.1', () => {
const port = srv.address().port;
resolve({
port,
url: `http://127.0.0.1:${port}`,
close: () => new Promise(r => srv.close(r)),
});
});
srv.once('error', reject);
});
}
// ── quota_v2 fixture shapes ──
const QUOTA_V2_LIVE = [
{
provider: 'anthropic',
status: 'live',
schema_version: '2026-05-26',
last_fresh_at: Date.now() - 60000,
utilization: { '5h': 0.36, '7d': 0.34 },
reset: { '5h': Math.floor((Date.now() + 2 * 3600 * 1000) / 1000), '7d': Math.floor((Date.now() + 5 * 24 * 3600 * 1000) / 1000) },
representative_claim: 'five_hour',
fallback_percentage: 0.02,
raw_available: null,
},
{
provider: 'openai',
status: 'unavailable',
reason: 'no public quota api',
},
{
provider: 'mistral',
status: 'unavailable',
reason: 'no public quota api',
},
];
const QUOTA_V2_STALE = [
{
provider: 'anthropic',
status: 'stale',
last_fresh_at: Date.now() - 6 * 60000,
utilization: { '5h': 0.60, '7d': 0.20 },
reset: { '5h': Math.floor((Date.now() + 3600 * 1000) / 1000), '7d': Math.floor((Date.now() + 4 * 24 * 3600 * 1000) / 1000) },
representative_claim: 'five_hour',
failure: { kind: 'rate_limited', message: 'too many requests' },
},
];
const QUOTA_V2_UNREACHABLE = [
{
provider: 'anthropic',
status: 'unreachable',
failure: { kind: 'auth_failed', message: 're-run `claude setup-token`' },
},
];
const LEGACY_QUOTA_BODY = {
window_24h: {},
cache_hit_24h: {},
quota: [
{ provider: 'anthropic', available: null },
{ provider: 'openai', available: null },
],
};
describe('Suite 40 — F4 CLI/plugin quota_v2 migration + v1.x #7 AUTH_MISSING test pin', () => {
// ── 40a — cmdUsage renders quota_v2 live rows ────────────────────────────
it('40a — cmdUsage parses quota_v2 live rows (mock server → asserts key strings present)', async () => {
const dashData = {
window_24h: { request_count: 12, status_2xx: 12, status_4xx: 0, status_5xx: 0 },
cache_hit_24h: { hit_rate: 0.5, hit: 6, miss: 6 },
quota: [],
quota_v2: QUOTA_V2_LIVE,
top_fallback_chains_24h: [],
cache_stats: { hits: 6, misses: 6, size: 4, inflightCount: 0 },
};
const srv = await makeJsonServer40(dashData);
try {
// Clear auth env so resolveBearerToken returns null (anonymous allowed)
const savedKey = process.env.OLP_API_KEY;
const savedOwner = process.env.OLP_OWNER_TOKEN;
delete process.env.OLP_API_KEY;
delete process.env.OLP_OWNER_TOKEN;
let out = '', err = '';
try {
const code = await runOlpCli40(
['usage', `--proxy-url=${srv.url}`],
{ out: s => { out += s; }, err: s => { err += s; }, useColor: false },
);
assert.equal(code, 0, `cmdUsage exit non-zero; stderr=${err}`);
} finally {
if (savedKey === undefined) delete process.env.OLP_API_KEY; else process.env.OLP_API_KEY = savedKey;
if (savedOwner === undefined) delete process.env.OLP_OWNER_TOKEN; else process.env.OLP_OWNER_TOKEN = savedOwner;
}
// Must display quota_v2 section heading
assert.match(out, /Per-provider quota.*live/i, '40a: header must mention live quota');
// Must show anthropic provider name
assert.match(out, /ANTHROPIC/, '40a: ANTHROPIC row must appear');
// Must show percentage data (36% or 34% from utilization shape)
assert.match(out, /36%|34%/, '40a: live utilization must appear');
// Must show reset countdown strings
assert.match(out, /resets in|resets /i, '40a: reset countdown must appear');
// Legacy "no quota api" fallthrough must NOT appear
assert.ok(!out.includes('no quota api'), '40a: "no quota api" must not appear when quota_v2 is present');
} finally {
await srv.close();
}
});
// ── 40b — cmdUsage falls back to legacy body.quota when quota_v2 absent ──
it('40b — cmdUsage falls back to legacy body.quota when quota_v2 absent (older server)', async () => {
const srv = await makeJsonServer40(LEGACY_QUOTA_BODY);
try {
const savedKey = process.env.OLP_API_KEY;
const savedOwner = process.env.OLP_OWNER_TOKEN;
delete process.env.OLP_API_KEY;
delete process.env.OLP_OWNER_TOKEN;
let out = '', err = '';
try {
const code = await runOlpCli40(
['usage', `--proxy-url=${srv.url}`],
{ out: s => { out += s; }, err: s => { err += s; }, useColor: false },
);
assert.equal(code, 0, `cmdUsage fallback exit non-zero; stderr=${err}`);
} finally {
if (savedKey === undefined) delete process.env.OLP_API_KEY; else process.env.OLP_API_KEY = savedKey;
if (savedOwner === undefined) delete process.env.OLP_OWNER_TOKEN; else process.env.OLP_OWNER_TOKEN = savedOwner;
}
// Legacy path shows "no quota api" for providers with null available
assert.match(out, /no quota api/, '40b: legacy path must show "no quota api" fallthrough');
} finally {
await srv.close();
}
});
// ── 40c — olp-plugin fmtUsage parses quota_v2 live row ──────────────────
it('40c — olp-plugin fmtUsage parses quota_v2 live row (mock body)', () => {
const body = {
window_24h: { request_count: 5 },
cache_hit_24h: { hit_rate: 0.4 },
quota: [],
quota_v2: QUOTA_V2_LIVE,
top_fallback_chains_24h: [],
};
const out = plugFmtUsage40(body);
// Section heading
assert.match(out, /Per-provider quota.*live/i, '40c: section heading must indicate live quota');
// Anthropic row present
assert.match(out, /ANTHROPIC/, '40c: ANTHROPIC row must appear');
// live status
assert.match(out, /live/, '40c: status "live" must appear for anthropic');
// Utilization percentage
assert.match(out, /36%/, '40c: 5h utilization 36% must appear');
// Reset countdown present
assert.match(out, /resets in|resets /i, '40c: reset countdown must appear');
// openai/mistral show unavailable
assert.match(out, /OPENAI.*unavailable|OPENAI\s+unavailable/, '40c: OPENAI must show unavailable');
// Legacy bar() fallthrough must NOT fire
assert.ok(!out.includes('█'), '40c: legacy bar() must not appear when quota_v2 present');
});
// ── 40d — olp-plugin fmtUsage falls back to legacy body.quota ────────────
it('40d — olp-plugin fmtUsage falls back to legacy body.quota when quota_v2 absent', () => {
const body = {
quota: [
{ provider: 'anthropic', percent_used: 45 },
{ provider: 'openai', percent_used: null },
],
};
const out = plugFmtUsage40(body);
assert.match(out, /Per-provider quota/, '40d: section heading must appear');
// Legacy bar() fires for percent_used=45
assert.match(out, /anthropic.*45%|45%.*anthropic/i, '40d: anthropic 45% must appear');
assert.match(out, /no quota api/, '40d: "no quota api" must appear for provider with null percent_used');
});
// ── 40e — formatResetCountdown ranges ────────────────────────────────────
it('40e — formatResetCountdown covers all 5 time ranges', () => {
const nowSec = Math.floor(Date.now() / 1000);
// Past: diffMs <= 0
assert.equal(olpFormatResetCountdown(nowSec - 5), 'resetting now', '40e: past → resetting now');
// < 1h: exactly 30 minutes ahead by feeding epoch seconds directly (no ms conversion drift)
const t30m = nowSec + 30 * 60;
const r30m = olpFormatResetCountdown(t30m);
assert.match(r30m, /resets in \d+m/, '40e: ~30min → resets in Xm format');
assert.ok(r30m.includes('resets in'), '40e: 30min must use < 1h branch');
// < 24h: 3 hours + 15 minutes from now (uses hours+minutes branch)
const t3h15m = nowSec + (3 * 60 + 15) * 60;
const r3h15m = olpFormatResetCountdown(t3h15m);
assert.match(r3h15m, /resets in \d+h/, '40e: 3h15m → resets in Xh format');
// < 7d: 2 days from now → weekday format
const t2d = nowSec + 2 * 24 * 3600;
const r2d = olpFormatResetCountdown(t2d);
assert.match(r2d, /resets (Mon|Tue|Wed|Thu|Fri|Sat|Sun)/, '40e: 2 days → weekday format');
// >= 7d: 10 days from now → month+day format
const t10d = nowSec + 10 * 24 * 3600;
const r10d = olpFormatResetCountdown(t10d);
assert.match(r10d, /resets (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)/, '40e: 10 days → month format');
// null input
assert.equal(olpFormatResetCountdown(null), '—', '40e: null → —');
});
// ── 40f — plugin formatResetCountdown parallel coverage ─────────────────
it('40f — olp-plugin pluginFormatResetCountdown covers past / <1h / <24h / <7d / ≥7d', () => {
const nowSec = Math.floor(Date.now() / 1000);
assert.equal(pluginFormatResetCountdown(nowSec - 1), 'resetting now', '40f: past → resetting now');
// ~45 minutes ahead → < 1h branch
const t45m = nowSec + 45 * 60;
const r45m = pluginFormatResetCountdown(t45m);
assert.match(r45m, /resets in \d+m/, '40f: ~45min → resets in Xm format');
// ~2h ahead (well within <24h range — may read as 1h 59m due to sub-second drift)
const t2h = nowSec + 2 * 3600;
const r2h = pluginFormatResetCountdown(t2h);
assert.match(r2h, /resets in \d+h/, '40f: ~2h → resets in Xh format');
assert.equal(pluginFormatResetCountdown(null), '—', '40f: null → —');
});
// ── 40g — cmdUsage renders quota_v2 stale row ────────────────────────────
it('40g — cmdUsage renders quota_v2 stale row with ⚠ stale note', async () => {
const dashData = {
window_24h: {},
cache_hit_24h: {},
quota: [],
quota_v2: QUOTA_V2_STALE,
top_fallback_chains_24h: [],
};
const srv = await makeJsonServer40(dashData);
try {
const savedKey = process.env.OLP_API_KEY;
const savedOwner = process.env.OLP_OWNER_TOKEN;
delete process.env.OLP_API_KEY;
delete process.env.OLP_OWNER_TOKEN;
let out = '', err = '';
try {
await runOlpCli40(
['usage', `--proxy-url=${srv.url}`],
{ out: s => { out += s; }, err: s => { err += s; }, useColor: false },
);
} finally {
if (savedKey === undefined) delete process.env.OLP_API_KEY; else process.env.OLP_API_KEY = savedKey;
if (savedOwner === undefined) delete process.env.OLP_OWNER_TOKEN; else process.env.OLP_OWNER_TOKEN = savedOwner;
}
assert.match(out, /stale/, '40g: stale status must appear in output');
} finally {
await srv.close();
}
});
// ── 40h — cmdUsage renders quota_v2 unreachable row ─────────────────────
it('40h — cmdUsage renders quota_v2 unreachable row with ❌ indicator', async () => {
const dashData = {
window_24h: {},
cache_hit_24h: {},
quota: [],
quota_v2: QUOTA_V2_UNREACHABLE,
top_fallback_chains_24h: [],
};
const srv = await makeJsonServer40(dashData);
try {
const savedKey = process.env.OLP_API_KEY;
const savedOwner = process.env.OLP_OWNER_TOKEN;
delete process.env.OLP_API_KEY;
delete process.env.OLP_OWNER_TOKEN;
let out = '', err = '';
try {
await runOlpCli40(
['usage', `--proxy-url=${srv.url}`],
{ out: s => { out += s; }, err: s => { err += s; }, useColor: false },
);
} finally {
if (savedKey === undefined) delete process.env.OLP_API_KEY; else process.env.OLP_API_KEY = savedKey;
if (savedOwner === undefined) delete process.env.OLP_OWNER_TOKEN; else process.env.OLP_OWNER_TOKEN = savedOwner;
}
assert.match(out, /❌|no cached data/, '40h: unreachable row must show ❌ or "no cached data"');
assert.match(out, /auth_failed/, '40h: failure kind must appear in output');
} finally {
await srv.close();
}
});
// ── 40i — cmdUsage renders quota_v2 unavailable row ─────────────────────
it('40i — cmdUsage renders quota_v2 unavailable rows (openai / mistral) correctly', async () => {
const dashData = {
window_24h: {},
cache_hit_24h: {},
quota: [],
quota_v2: QUOTA_V2_LIVE, // includes openai + mistral as unavailable
top_fallback_chains_24h: [],
};
const srv = await makeJsonServer40(dashData);
try {
const savedKey = process.env.OLP_API_KEY;
const savedOwner = process.env.OLP_OWNER_TOKEN;
delete process.env.OLP_API_KEY;
delete process.env.OLP_OWNER_TOKEN;
let out = '', err = '';
try {
await runOlpCli40(
['usage', `--proxy-url=${srv.url}`],
{ out: s => { out += s; }, err: s => { err += s; }, useColor: false },
);
} finally {
if (savedKey === undefined) delete process.env.OLP_API_KEY; else process.env.OLP_API_KEY = savedKey;
if (savedOwner === undefined) delete process.env.OLP_OWNER_TOKEN; else process.env.OLP_OWNER_TOKEN = savedOwner;
}
// openai + mistral are unavailable in the fixture
assert.match(out, /OPENAI/, '40i: OPENAI row must appear');
assert.match(out, /unavailable/, '40i: "unavailable" must appear for providers with no quota api');
} finally {
await srv.close();
}
});
});