294 lines · 13.5 KB · Swift
RawDownload
import CodexBarCoreimport Foundationimport Testing@testable import CodexBar @MainActorstruct 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 couldn’t 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 couldn’t 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 couldn’t 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())    }}