test(cli): pin the CLI's own minimum-budget floor

Copilot round 1 on #2256: the `budgetMs: 0` case passes under the old
`remainingMs <= 0` bound too, so nothing in the CLI suite detected the
floor added to `sendPlans`.

The new case gives it a budget that is positive and below the floor, and
asserts on *this loop's* exhaustion message — the one naming the server
URL. That distinction is the test: with the floor removed the plan is
handed a 4ms budget and core's identical floor declines it one level
down, so a bare "budget was exhausted" match passes either way. Verified
as a detector — 1 failed / 14 passed with the CLI floor reverted, 15
passed with it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017wEXtbs8UEHUMxDEAxbs99
Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-09-05 01:43:01 -04:00
commit c736f1d185

View file

@ -6,6 +6,7 @@ import {
getStateFilePath,
resetNodeOAuthStorageCache,
} from "@inspector/core/auth/node/storage-node.js";
import { MIN_REVOCATION_REQUEST_BUDGET_MS } from "@inspector/core/auth/revocation.js";
import { clearStoredAuthForRelogin } from "../src/clear-stored-auth-for-relogin.js";
const AS_ISSUER = "https://as.example.com";
@ -379,6 +380,35 @@ describe("clearStoredAuthForRelogin", () => {
}
});
// The zero-budget case above passes under the old `remainingMs <= 0` bound
// too, so it says nothing about the floor. This one is the floor's own
// detector: a budget that is genuinely positive, and genuinely too small to
// complete a request, must be spent on no request at all (#2252). The
// budget only shrinks as the loop runs, so a slow machine cannot flip it.
it("issues no request for a positive budget below the minimum", async () => {
seedBothSpellings("live-r", "stale-r");
const fetchSpy = vi
.spyOn(globalThis, "fetch")
.mockResolvedValue(new Response(null, { status: 200 }));
try {
const outcome = await clearStoredAuthForRelogin("https://example.com", {
budgetMs: MIN_REVOCATION_REQUEST_BUDGET_MS - 1,
});
expect(fetchSpy).not.toHaveBeenCalled();
expect(outcome).toMatchObject({ status: "failed" });
// The *plan* was never attempted, so the report has to be this loop's
// own — naming the server URL — and not the per-grant one from inside
// `executeOAuthRevocation`. Without that distinction the assertion
// passes on the old `<= 0` bound too: the plan would be handed a 4ms
// budget, and core's identical floor would decline it one level down.
expect(outcome?.status === "failed" ? outcome.detail : "").toContain(
'budget was exhausted before "',
);
} finally {
fetchSpy.mockRestore();
}
});
it("reports a later failure over an earlier success", async () => {
seedBothSpellings("live-r", "stale-r");
const fetchSpy = vi