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

177 lines
6.0 KiB
Swift

import CodexBarCore
import Foundation
import Testing
@testable import CodexBar
@MainActor
@Suite(.serialized)
struct ClaudeWebRecoveryMenuTests {
@Test
func `unauthorized error explains how to restore web usage`() {
#expect(
ClaudeWebAPIFetcher.FetchError.unauthorized.localizedDescription ==
"Sign in to claude.ai (or refresh Claude cookies) to load usage data.")
}
private func makeSettings() -> SettingsStore {
let suite = "ClaudeWebRecoveryMenuTests-\(UUID().uuidString)"
let defaults = UserDefaults(suiteName: suite)!
defaults.removePersistentDomain(forName: suite)
return SettingsStore(
userDefaults: defaults,
configStore: testConfigStore(suiteName: suite),
zaiTokenStore: NoopZaiTokenStore(),
syntheticTokenStore: NoopSyntheticTokenStore())
}
private func actions(
error: String? = nil,
source: ClaudeUsageDataSource,
cookieSource: ProviderCookieSource = .auto,
selectedSessionKey: Bool = false,
attempts: [ProviderFetchAttempt] = []) -> [(String, MenuDescriptor.MenuAction)]
{
let settings = self.makeSettings()
settings.claudeUsageDataSource = source
if selectedSessionKey {
settings.addTokenAccount(provider: .claude, label: "Session", token: "sk-ant-session-token")
}
settings.claudeCookieSource = cookieSource
let fetcher = UsageFetcher()
let store = UsageStore(
fetcher: fetcher,
browserDetection: BrowserDetection(cacheTTL: 0),
settings: settings)
store.errors[.claude] = error
store.lastFetchAttempts[.claude] = attempts
return MenuDescriptor.build(
provider: .claude,
store: store,
settings: settings,
account: fetcher.loadAccountInfo(),
updateReady: false)
.sections
.flatMap(\.entries)
.compactMap { entry in
guard case let .action(label, action) = entry else { return nil }
return (label, action)
}
}
@Test
func `default account action localizes ambient Claude Code sign in`() {
let actions = CodexBarLocalizationOverride.$appLanguage.withValue("zh-Hant") {
self.actions(source: .auto)
}
#expect(actions.contains {
$0.0 == "使用 Claude Code 登入…" && $0.1 == .switchAccount(.claude)
})
#expect(!actions.contains { $0.0 == "Add Account..." })
}
@Test
func `web session errors show claude relogin action`() {
let errors = [
ClaudeWebAPIFetcher.FetchError.unauthorized.localizedDescription,
ClaudeWebAPIFetcher.FetchError.noSessionKeyFound.localizedDescription,
ClaudeWebAPIFetcher.FetchError.invalidSessionKey.localizedDescription,
]
for error in errors {
let actions = self.actions(error: error, source: .web)
#expect(actions.contains {
$0.0 == "Re-login at claude.ai" &&
$0.1 == .loginToProvider(url: "https://claude.ai/")
})
}
}
@Test
func `auto source shows relogin action for terminal web session error`() {
let actions = self.actions(
error: ClaudeWebAPIFetcher.FetchError.unauthorized.localizedDescription,
source: .auto)
#expect(actions.contains {
$0.0 == "Re-login at claude.ai" &&
$0.1 == .loginToProvider(url: "https://claude.ai/")
})
}
@Test
func `non-web source does not replace account action`() {
let actions = self.actions(
error: ClaudeWebAPIFetcher.FetchError.unauthorized.localizedDescription,
source: .oauth)
#expect(!actions.contains { $0.0 == "Re-login at claude.ai" })
}
@Test
func `manual cookies do not show browser relogin action`() {
let actions = self.actions(
error: ClaudeWebAPIFetcher.FetchError.unauthorized.localizedDescription,
source: .web,
cookieSource: .manual)
#expect(!actions.contains { $0.0 == "Re-login at claude.ai" })
}
@Test
func `selected session account does not show browser relogin action`() {
let actions = self.actions(
error: ClaudeWebAPIFetcher.FetchError.unauthorized.localizedDescription,
source: .web,
cookieSource: .auto,
selectedSessionKey: true)
#expect(!actions.contains { $0.0 == "Re-login at claude.ai" })
}
@Test
func `unavailable web strategy shows relogin action`() {
let actions = self.actions(
error: ProviderFetchError.noAvailableStrategy(.claude).localizedDescription,
source: .web,
attempts: [
ProviderFetchAttempt(
strategyID: "claude.web",
kind: .web,
wasAvailable: false,
errorDescription: nil),
])
#expect(actions.contains {
$0.0 == "Re-login at claude.ai" &&
$0.1 == .loginToProvider(url: "https://claude.ai/")
})
}
@Test
func `generic unavailable error without web attempt keeps account action`() {
let actions = self.actions(
error: ProviderFetchError.noAvailableStrategy(.claude).localizedDescription,
source: .auto,
attempts: [
ProviderFetchAttempt(
strategyID: "claude.cli",
kind: .cli,
wasAvailable: false,
errorDescription: nil),
])
#expect(!actions.contains { $0.0 == "Re-login at claude.ai" })
}
@Test
func `unrelated web error does not replace account action`() {
let actions = self.actions(
error: ClaudeWebAPIFetcher.FetchError.serverError(statusCode: 500).localizedDescription,
source: .web)
#expect(!actions.contains { $0.0 == "Re-login at claude.ai" })
}
}