177 lines
6.0 KiB
Swift
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" })
|
|
}
|
|
}
|