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:
parent
84a65f28d8
commit
c736f1d185
1 changed files with 30 additions and 0 deletions
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue