From 4d751707179a8e4030fd7f47b249c85c1a4f65a7 Mon Sep 17 00:00:00 2001 From: Jay Wedgeworth <12656028+jaywedgeworth22@users.noreply.github.com> Date: Mon, 5 Oct 2026 19:18:42 -0500 Subject: [PATCH] test(claude): cover the renewal-then-read-back path end to end MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The self-healing path had no coverage at all: an expired-but-renewable login was the one case the reader is supposed to recover from on its own, and nothing asserted that a renewal which actually lands is used. This drives the real sequence through LocalQuotaReader — expired record, one renewal run, Claude Code's rewritten login read back on the same refresh — and asserts the row reports the renewed token's quota with no issue and no consent prompt. Uses a locked one-way gate for the flag both closures touch, since they run on different tasks. Suite is now 332 tests, 3 skipped, 0 failures. --- .../LocalQuotaReaderTests.swift | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/Tests/QuotaCoreTests/LocalQuotaReaderTests.swift b/Tests/QuotaCoreTests/LocalQuotaReaderTests.swift index ef5790f..013abaa 100644 --- a/Tests/QuotaCoreTests/LocalQuotaReaderTests.swift +++ b/Tests/QuotaCoreTests/LocalQuotaReaderTests.swift @@ -185,6 +185,48 @@ final class LocalQuotaReaderTests: XCTestCase { XCTAssertNil(ClaudeCredentialSource.rememberedCredential()) } + func testARenewalThatLandsIsActuallyUsedOnTheSameRefresh() async throws { + // The self-healing path, end to end: the stored token is expired but + // renewable, so the reader asks Claude Code to renew its own login and + // then reads the result back. This failed silently for as long as the + // remembered-grant cache returned the pre-renewal bytes on that read-back + // (board dd5f6702), which made the row sit on "login idle" even while + // Claude Code was renewing happily in the background. + let home = try makeFixtureHome() + defer { try? FileManager.default.removeItem(at: home) } + try writeJSON(["mcpOAuth": [:]], to: home.appendingPathComponent(".claude/.credentials.json")) + + let expired = Data(#"{"claudeAiOauth":{"accessToken":"old","refreshToken":"r","expiresAt":1}}"#.utf8) + let renewed = Data(#"{"claudeAiOauth":{"accessToken":"renewed","expiresAt":4102444800000}}"#.utf8) + + // First read yields the expired record; after the renewal run, every + // later read yields the one Claude Code just wrote. + let renewedByClaudeCode = ClaudeRenewalGate() + defer { ClaudeCredentialSource.resetRememberedCredential() } + + var renewalRan = 0 + let reader = LocalQuotaReader( + homeDirectory: home, + fetchJSON: { request in + XCTAssertEqual(request.value(forHTTPHeaderField: "Authorization"), "Bearer renewed") + return Self.httpResponse(#"{"five_hour":{"utilization":42}}"#) + }, + runAntigravity: { Data("{}".utf8) }, + readClaudeCredential: { renewedByClaudeCode.hasRenewed ? .authorized(renewed) : .authorized(expired) }, + renewClaudeLogin: { + renewalRan += 1 + renewedByClaudeCode.hasRenewed = true + }, + renewalThrottle: ClaudeRenewalThrottle(minimumInterval: 0)) + + let result = await reader.read() + + XCTAssertEqual(renewalRan, 1, "an expired but renewable login must trigger one renewal run") + XCTAssertEqual(result.windows.first { $0.providerKey == "anthropic" }?.remainingPercent, 58) + XCTAssertNil(result.issues["anthropic"], "a renewal that landed must not report an issue") + XCTAssertTrue(result.consentNeeded.isEmpty) + } + func testClaudeKeychainReaderTimeoutDoesNotHoldRefresh() async throws { let home = try makeFixtureHome() defer { try? FileManager.default.removeItem(at: home) } @@ -283,3 +325,16 @@ final class LocalQuotaReaderTests: XCTestCase { return (Data(text.utf8), response) } } + +/// A one-way flag standing in for "Claude Code has rewritten its own login". +/// The reader's renewal closure and its credential closure both touch it, and +/// they run on different tasks, so it needs a lock rather than a bare `var`. +private final class ClaudeRenewalGate: @unchecked Sendable { + private let lock = NSLock() + private var renewed = false + + var hasRenewed: Bool { + get { lock.lock(); defer { lock.unlock() }; return renewed } + set { lock.lock(); renewed = newValue; lock.unlock() } + } +}