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

295 lines
14 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import CodexBarCore
import Foundation
import Testing
@testable import CodexBar
@MainActor
struct CodexUserFacingErrorTests {
@Test
func `missing codex CLI guidance is not collapsed to not running`() {
let store = self.makeUsageStore(suite: "CodexUserFacingErrorTests-missing-cli")
store.errors[.codex] = "Codex not running. Try running a Codex command first. "
+ "(Codex CLI not found. Install with `npm i -g @openai/codex`.)"
#expect(store.userFacingError(for: .codex) == CodexStatusProbeError.codexNotInstalled.localizedDescription)
}
@Test
func `logged out codex CLI guidance is not collapsed to temporary outage`() {
let store = self.makeUsageStore(suite: "CodexUserFacingErrorTests-cli-login-required")
store.errors[.codex] =
"Codex connection failed: codex account authentication required to read rate limits"
#expect(
store.userFacingError(for: .codex) ==
"Codex CLI is not signed in. Run `codex login --device-auth`, then refresh.")
}
@Test
func `cached logged out codex CLI failure preserves cached suffix`() {
let store = self.makeUsageStore(suite: "CodexUserFacingErrorTests-cached-cli-login-required")
store.lastCreditsError =
"Last Codex credits refresh failed: Codex connection failed: "
+ "codex account authentication required to read rate limits. Cached values from 2m ago."
#expect(
store.userFacingLastCreditsError ==
"Codex CLI is not signed in. Run `codex login --device-auth`, then refresh. "
+ "Cached values from 2m ago.")
}
@Test
func `expired codex auth is sanitized`() {
let store = self.makeUsageStore(suite: "CodexUserFacingErrorTests-expired-auth")
store.errors[.codex] = """
Codex connection failed: failed to fetch codex rate limits: GET https://chatgpt.com/backend-api/wham/usage \
failed: 401 Unauthorized; content-type=text/plain; body={\"error\":{\"message\":\"Provided authentication \
token is expired. Please try signing in again.\",\"code\":\"token_expired\"}}
"""
#expect(store.userFacingError(for: .codex) == "Codex session expired. Sign in again.")
}
@Test
func `transport codex error is sanitized`() {
let store = self.makeUsageStore(suite: "CodexUserFacingErrorTests-transport")
store.errors[.codex] =
"Codex connection failed: failed to fetch codex rate limits: "
+ "GET https://chatgpt.com/backend-api/wham/usage failed: 500"
#expect(store.userFacingError(for: .codex) == "Codex usage is temporarily unavailable. Try refreshing.")
}
@Test
func `decode mismatch codex error is sanitized`() {
let store = self.makeUsageStore(suite: "CodexUserFacingErrorTests-decode-mismatch")
store.errors[.codex] =
"Codex connection failed: failed to fetch codex rate limits: "
+ "Decode error for https://chatgpt.com/backend-api/wham/usage: "
+ "unknown variant `prolite`, expected one of `guest`, `free`, `go`, `plus`, `pro`"
#expect(store.userFacingError(for: .codex) == "Codex usage is temporarily unavailable. Try refreshing.")
}
@Test
func `cached credits failure preserves cached suffix while sanitizing body`() {
let store = self.makeUsageStore(suite: "CodexUserFacingErrorTests-cached-credits")
store.lastCreditsError =
"Last Codex credits refresh failed: Codex connection failed: failed to fetch codex rate limits: "
+ "GET https://chatgpt.com/backend-api/wham/usage failed: 500; body={\"error\":{}} "
+ "Cached values from 2m ago."
#expect(
store.userFacingLastCreditsError ==
"Codex usage is temporarily unavailable. Try refreshing. Cached values from 2m ago.")
}
@Test
func `localized cached credits failure preserves cached suffix while sanitizing body`() {
let result = CodexBarLocalizationOverride.$appLanguage.withValue("zh-Hant") {
let store = self.makeUsageStore(suite: "CodexUserFacingErrorTests-localized-cached-credits")
store.lastCreditsError =
"Last Codex credits refresh failed: Codex connection failed: failed to fetch codex rate limits: "
+ "GET https://chatgpt.com/backend-api/wham/usage failed: 500 Cached values from 2m ago."
return store.userFacingLastCreditsError
}
#expect(result == "Codex 使用量暫時無法取得。請嘗試重新整理。 使用 2m ago 的快取值。")
}
@Test
func `cached missing codex CLI failure preserves cached suffix`() {
let store = self.makeUsageStore(suite: "CodexUserFacingErrorTests-cached-missing-cli")
store.lastCreditsError =
"Last Codex credits refresh failed: Codex CLI not found. "
+ "Install with `npm i -g @openai/codex`. Cached values from 2m ago."
#expect(
store.userFacingLastCreditsError ==
CodexStatusProbeError.codexNotInstalled.localizedDescription + " Cached values from 2m ago.")
}
@Test
func `browser mismatch remains unchanged`() {
let store = self.makeUsageStore(suite: "CodexUserFacingErrorTests-browser-mismatch")
store.lastOpenAIDashboardError =
"OpenAI cookies are for ratulsarna@gmail.com, not rdsarna@gmail.com. "
+ "Switch chatgpt.com account, then refresh OpenAI cookies."
#expect(
store.userFacingLastOpenAIDashboardError ==
"OpenAI cookies are for ratulsarna@gmail.com, not rdsarna@gmail.com. "
+ "Switch chatgpt.com account, then refresh OpenAI cookies.")
}
@Test
func `frame load interrupted becomes retry guidance`() {
let store = self.makeUsageStore(suite: "CodexUserFacingErrorTests-frame-load")
store.lastOpenAIDashboardError = "Frame load interrupted"
#expect(
store.userFacingLastOpenAIDashboardError ==
"OpenAI web refresh was interrupted. Refresh OpenAI cookies and try again.")
}
@Test
func `open A I web timeout becomes retry guidance`() {
let store = self.makeUsageStore(suite: "CodexUserFacingErrorTests-openai-web-timeout")
store.lastOpenAIDashboardError = "The operation couldnt be completed. (NSURLErrorDomain error -1001.)"
#expect(
store.userFacingLastOpenAIDashboardError ==
"OpenAI web refresh timed out. Refresh OpenAI cookies and try again.")
}
@Test
func `localized cached open A I web timeout preserves cached suffix`() {
let result = CodexBarLocalizationOverride.$appLanguage.withValue("zh-Hant") {
let store = self.makeUsageStore(suite: "CodexUserFacingErrorTests-localized-openai-web-timeout")
store.lastOpenAIDashboardError =
"Last OpenAI dashboard refresh failed: "
+ "The operation couldnt be completed. (NSURLErrorDomain error -1001.). "
+ "Cached values from 2m ago."
return store.userFacingLastOpenAIDashboardError
}
#expect(
result ==
"OpenAI Web 重新整理逾時。請重新整理 OpenAI Cookie 後再試一次。 使用 2m ago 的快取值。")
}
@Test
func `open A I web network error becomes connection guidance`() {
let store = self.makeUsageStore(suite: "CodexUserFacingErrorTests-openai-web-network")
store.lastOpenAIDashboardError = "The operation couldnt be completed. (NSURLErrorDomain error -1004.)"
let expected = [
"OpenAI web refresh hit a network error.",
"Check your connection, then refresh OpenAI cookies and try again.",
].joined(separator: " ")
#expect(store.userFacingLastOpenAIDashboardError == expected)
}
@Test
func `non codex providers keep raw errors`() {
let store = self.makeUsageStore(suite: "CodexUserFacingErrorTests-non-codex")
store.errors[.claude] = "Claude probe failed with debug detail"
#expect(store.userFacingError(for: .claude) == "Claude probe failed with debug detail")
}
@Test
func `providers pane codex model uses sanitized values`() {
let settings = self.makeSettingsStore(suite: "CodexUserFacingErrorTests-pane-model")
let store = self.makeUsageStore(settings: settings)
store.errors[.codex] =
"Codex connection failed: failed to fetch codex rate limits: "
+ "GET https://chatgpt.com/backend-api/wham/usage failed: 500"
store.lastCreditsError =
"Last Codex credits refresh failed: Codex connection failed: failed to fetch codex rate limits: "
+ "GET https://chatgpt.com/backend-api/wham/usage failed: 500 "
+ "Cached values from 1m ago."
store.lastOpenAIDashboardError = "Frame load interrupted"
let pane = ProvidersPane(settings: settings, store: store)
let model = pane._test_menuCardModel(for: .codex)
#expect(model.subtitleText == "Codex usage is temporarily unavailable. Try refreshing.")
#expect(
model.creditsHintText ==
"OpenAI web refresh was interrupted. Refresh OpenAI cookies and try again.")
#expect(
model.creditsHintCopyText ==
"OpenAI web refresh was interrupted. Refresh OpenAI cookies and try again.")
#expect(
model.creditsText == "Codex usage is temporarily unavailable. Try refreshing. Cached values from 1m ago.")
}
@Test
func `menu card hides optional codex setup diagnostics kept by providers pane`() throws {
let settings = self.makeSettingsStore(suite: "CodexUserFacingErrorTests-menu-diagnostics")
let store = self.makeUsageStore(settings: settings)
store.lastCreditsError = UsageError.noRateLimitsFound.errorDescription
store.lastOpenAIDashboardError =
"No matching OpenAI web session found. Sign in to chatgpt.com, then refresh OpenAI cookies."
let fetcher = UsageFetcher(environment: [:])
let menuModel = try withStatusItemControllerForTesting(
store: store,
settings: settings,
fetcher: fetcher)
{ controller in
try #require(controller.menuCardModel(for: .codex))
}
let pane = ProvidersPane(settings: settings, store: store)
let settingsModel = pane._test_menuCardModel(for: .codex)
let settingsDiagnostic = pane._test_openAIWebDiagnostic(for: .codex)
let settingsInfoRows = ProviderMetricsInlineView.infoRows(
for: settingsModel,
openAIWebDiagnostic: settingsDiagnostic)
#expect(menuModel.creditsText == nil)
#expect(menuModel.creditsHintText == nil)
#expect(settingsModel.creditsText == UsageError.noRateLimitsFound.errorDescription)
#expect(settingsModel.creditsHintText?.contains("No matching OpenAI web session found") == true)
#expect(settingsInfoRows.contains { row in
row.id == .openAIWeb && row.value.contains("No matching OpenAI web session found")
})
}
@Test
func `providers pane codex error display keeps raw full text for copy`() {
let settings = self.makeSettingsStore(suite: "CodexUserFacingErrorTests-pane-error-display")
let store = self.makeUsageStore(settings: settings)
let raw =
"Codex connection failed: failed to fetch codex rate limits: "
+ "GET https://chatgpt.com/backend-api/wham/usage failed: 500; body={\"error\":{}}"
store.errors[.codex] = raw
let pane = ProvidersPane(settings: settings, store: store)
let display = pane._test_providerErrorDisplay(for: .codex)
#expect(display?.preview == "Codex usage is temporarily unavailable. Try refreshing.")
#expect(display?.full == raw)
}
private func makeUsageStore(suite: String) -> UsageStore {
let settings = self.makeSettingsStore(suite: suite)
return self.makeUsageStore(settings: settings)
}
private func makeUsageStore(settings: SettingsStore) -> UsageStore {
UsageStore(
fetcher: UsageFetcher(environment: [:]),
browserDetection: BrowserDetection(cacheTTL: 0),
settings: settings,
startupBehavior: .testing)
}
private func makeSettingsStore(suite: String) -> SettingsStore {
let defaults = UserDefaults(suiteName: suite)!
defaults.removePersistentDomain(forName: suite)
let configStore = testConfigStore(suiteName: suite)
return SettingsStore(
userDefaults: defaults,
configStore: configStore,
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())
}
}