Files
steipete--codexbar/Tests/CodexBarTests/ClaudeWebRefreshResilienceTests.swift
2026-07-13 12:22:33 +08:00

176 lines
7.3 KiB
Swift

import Foundation
import Testing
@testable import CodexBar
@testable import CodexBarCore
struct ClaudeWebRefreshResilienceTests {
@Test
func `web unauthorized respects failure gate while keeping prior Claude snapshot`() async throws {
try await ClaudeOAuthCredentialsStore.withIsolatedCredentialsFileTrackingForTesting {
let tempDir = FileManager.default.temporaryDirectory
.appendingPathComponent(UUID().uuidString, isDirectory: true)
try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)
let fileURL = tempDir.appendingPathComponent("missing-credentials.json")
try await ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) {
let prior = Self.makePriorSnapshot()
let store = try await MainActor.run {
try Self.makeStore(
suite: "ClaudeWebRefreshResilienceTests-web-unauthorized",
prior: prior)
}
await store.refreshProvider(.claude)
let firstResult = await MainActor.run {
(
updatedAt: store.snapshot(for: .claude)?.updatedAt,
hasError: store.error(for: .claude) != nil)
}
#expect(firstResult.updatedAt == prior.updatedAt)
#expect(!firstResult.hasError)
await store.refreshProvider(.claude)
let secondResult = await MainActor.run {
(
updatedAt: store.snapshot(for: .claude)?.updatedAt,
error: store.error(for: .claude))
}
#expect(secondResult.updatedAt == prior.updatedAt)
#expect(secondResult.error == ClaudeWebAPIFetcher.FetchError.unauthorized.localizedDescription)
}
}
}
@Test
func `web unauthorized without prior Claude snapshot still surfaces failure`() async throws {
try await ClaudeOAuthCredentialsStore.withIsolatedCredentialsFileTrackingForTesting {
let tempDir = FileManager.default.temporaryDirectory
.appendingPathComponent(UUID().uuidString, isDirectory: true)
try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)
let fileURL = tempDir.appendingPathComponent("missing-credentials.json")
try await ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) {
let store = try await MainActor.run {
try Self.makeStore(
suite: "ClaudeWebRefreshResilienceTests-web-unauthorized-no-prior",
prior: nil)
}
await store.refreshProvider(.claude)
let result = await MainActor.run {
(
hasSnapshot: store.snapshot(for: .claude) != nil,
error: store.error(for: .claude))
}
#expect(!result.hasSnapshot)
#expect(result.error == ClaudeWebAPIFetcher.FetchError.unauthorized.localizedDescription)
}
}
}
@MainActor
private static func makeStore(suite: String, prior: UsageSnapshot?) throws -> UsageStore {
let settings = self.makeSettingsStore(suite: suite)
settings.refreshFrequency = .manual
settings.statusChecksEnabled = false
settings.claudeUsageDataSource = .web
let metadata = ProviderRegistry.shared.metadata
for provider in UsageProvider.allCases {
try settings.setProviderEnabled(
provider: provider,
metadata: #require(metadata[provider]),
enabled: provider == .claude)
}
let store = UsageStore(
fetcher: UsageFetcher(environment: [:]),
browserDetection: BrowserDetection(cacheTTL: 0),
settings: settings,
startupBehavior: .testing,
environmentBase: [:])
if let prior {
store._setSnapshotForTesting(prior, provider: .claude)
}
let baseSpec = try #require(store.providerSpecs[.claude])
let descriptor = ProviderDescriptor(
id: .claude,
metadata: baseSpec.descriptor.metadata,
branding: baseSpec.descriptor.branding,
tokenCost: baseSpec.descriptor.tokenCost,
fetchPlan: ProviderFetchPlan(
sourceModes: [.web],
pipeline: ProviderFetchPipeline { _ in [ClaudeWebUnauthorizedFetchStrategy()] }),
cli: baseSpec.descriptor.cli)
store.providerSpecs[.claude] = ProviderSpec(
style: baseSpec.style,
isEnabled: baseSpec.isEnabled,
descriptor: descriptor,
makeFetchContext: baseSpec.makeFetchContext)
return store
}
private static func makePriorSnapshot() -> UsageSnapshot {
UsageSnapshot(
primary: RateWindow(usedPercent: 12, windowMinutes: 300, resetsAt: nil, resetDescription: nil),
secondary: RateWindow(
usedPercent: 34,
windowMinutes: 10080,
resetsAt: nil,
resetDescription: nil),
updatedAt: Date(timeIntervalSince1970: 1_800_000_000),
identity: ProviderIdentitySnapshot(
providerID: .claude,
accountEmail: "claude@example.com",
accountOrganization: nil,
loginMethod: "Pro"))
}
@MainActor
private static func makeSettingsStore(suite: String) -> SettingsStore {
let defaults = UserDefaults(suiteName: suite)!
defaults.removePersistentDomain(forName: suite)
let settings = SettingsStore(
userDefaults: defaults,
configStore: testConfigStore(suiteName: suite),
zaiTokenStore: NoopZaiTokenStore(),
syntheticTokenStore: NoopSyntheticTokenStore(),
codexCookieStore: InMemoryCookieHeaderStore(),
claudeCookieStore: InMemoryCookieHeaderStore(),
cursorCookieStore: InMemoryCookieHeaderStore(),
opencodeCookieStore: InMemoryCookieHeaderStore(),
factoryCookieStore: InMemoryCookieHeaderStore(),
minimaxCookieStore: InMemoryMiniMaxCookieStore(),
minimaxAPITokenStore: InMemoryMiniMaxAPITokenStore(),
kimiTokenStore: InMemoryKimiTokenStore(),
kimiK2TokenStore: InMemoryKimiK2TokenStore(),
augmentCookieStore: InMemoryCookieHeaderStore(),
ampCookieStore: InMemoryCookieHeaderStore(),
copilotTokenStore: InMemoryCopilotTokenStore(),
tokenAccountStore: InMemoryTokenAccountStore())
settings.providerDetectionCompleted = true
return settings
}
}
private struct ClaudeWebUnauthorizedFetchStrategy: ProviderFetchStrategy {
let id = "test.claude-web-unauthorized"
let kind: ProviderFetchKind = .web
func isAvailable(_: ProviderFetchContext) async -> Bool {
true
}
func fetch(_: ProviderFetchContext) async throws -> ProviderFetchResult {
throw ClaudeWebAPIFetcher.FetchError.unauthorized
}
func shouldFallback(on _: Error, context _: ProviderFetchContext) -> Bool {
false
}
}