import AppKit import CodexBarCore import Testing @testable import CodexBar @MainActor @Suite struct StatusMenuTests { private func makeSettings() -> SettingsStore { let suite = "StatusMenuTests-\(UUID().uuidString)" let defaults = UserDefaults(suiteName: suite)! defaults.removePersistentDomain(forName: suite) return SettingsStore(userDefaults: defaults, zaiTokenStore: NoopZaiTokenStore()) } @Test func remembersProviderWhenMenuOpens() { let settings = self.makeSettings() settings.statusChecksEnabled = false settings.refreshFrequency = .manual settings.mergeIcons = true let registry = ProviderRegistry.shared if let codexMeta = registry.metadata[.codex] { settings.setProviderEnabled(provider: .codex, metadata: codexMeta, enabled: false) } if let claudeMeta = registry.metadata[.claude] { settings.setProviderEnabled(provider: .claude, metadata: claudeMeta, enabled: true) } if let geminiMeta = registry.metadata[.gemini] { settings.setProviderEnabled(provider: .gemini, metadata: geminiMeta, enabled: false) } let fetcher = UsageFetcher() let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings) let controller = StatusItemController( store: store, settings: settings, account: fetcher.loadAccountInfo(), updater: DisabledUpdaterController(), preferencesSelection: PreferencesSelection()) let claudeMenu = controller.makeMenu() controller.menuWillOpen(claudeMenu) #expect(controller.lastMenuProvider == .claude) // No providers enabled: fall back to Codex. for provider in UsageProvider.allCases { if let meta = registry.metadata[provider] { settings.setProviderEnabled(provider: provider, metadata: meta, enabled: false) } } let unmappedMenu = controller.makeMenu() controller.menuWillOpen(unmappedMenu) #expect(controller.lastMenuProvider == .codex) } @Test func hidesOpenAIWebSubmenusWhenNoHistory() { let settings = self.makeSettings() settings.statusChecksEnabled = false settings.refreshFrequency = .manual settings.mergeIcons = true settings.selectedMenuProvider = .codex let registry = ProviderRegistry.shared if let codexMeta = registry.metadata[.codex] { settings.setProviderEnabled(provider: .codex, metadata: codexMeta, enabled: true) } if let claudeMeta = registry.metadata[.claude] { settings.setProviderEnabled(provider: .claude, metadata: claudeMeta, enabled: false) } if let geminiMeta = registry.metadata[.gemini] { settings.setProviderEnabled(provider: .gemini, metadata: geminiMeta, enabled: false) } let fetcher = UsageFetcher() let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings) store.openAIDashboard = OpenAIDashboardSnapshot( signedInEmail: "user@example.com", codeReviewRemainingPercent: 100, creditEvents: [], dailyBreakdown: [], usageBreakdown: [], creditsPurchaseURL: nil, updatedAt: Date()) let controller = StatusItemController( store: store, settings: settings, account: fetcher.loadAccountInfo(), updater: DisabledUpdaterController(), preferencesSelection: PreferencesSelection()) let menu = controller.makeMenu() controller.menuWillOpen(menu) let titles = Set(menu.items.map(\.title)) #expect(!titles.contains("Credits history")) #expect(!titles.contains("Usage breakdown")) } @Test func showsOpenAIWebSubmenusWhenHistoryExists() { let settings = SettingsStore(zaiTokenStore: NoopZaiTokenStore()) settings.statusChecksEnabled = false settings.refreshFrequency = .manual settings.mergeIcons = true settings.selectedMenuProvider = .codex let registry = ProviderRegistry.shared if let codexMeta = registry.metadata[.codex] { settings.setProviderEnabled(provider: .codex, metadata: codexMeta, enabled: true) } if let claudeMeta = registry.metadata[.claude] { settings.setProviderEnabled(provider: .claude, metadata: claudeMeta, enabled: false) } if let geminiMeta = registry.metadata[.gemini] { settings.setProviderEnabled(provider: .gemini, metadata: geminiMeta, enabled: false) } let fetcher = UsageFetcher() let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings) let calendar = Calendar(identifier: .gregorian) var components = DateComponents() components.calendar = calendar components.timeZone = TimeZone(secondsFromGMT: 0) components.year = 2025 components.month = 12 components.day = 18 let date = components.date! let events = [CreditEvent(date: date, service: "CLI", creditsUsed: 1)] let breakdown = OpenAIDashboardSnapshot.makeDailyBreakdown(from: events, maxDays: 30) store.openAIDashboard = OpenAIDashboardSnapshot( signedInEmail: "user@example.com", codeReviewRemainingPercent: 100, creditEvents: events, dailyBreakdown: breakdown, usageBreakdown: breakdown, creditsPurchaseURL: nil, updatedAt: Date()) let controller = StatusItemController( store: store, settings: settings, account: fetcher.loadAccountInfo(), updater: DisabledUpdaterController(), preferencesSelection: PreferencesSelection()) let menu = controller.makeMenu() controller.menuWillOpen(menu) let usageItem = menu.items.first { ($0.representedObject as? String) == "menuCardUsage" } let creditsItem = menu.items.first { ($0.representedObject as? String) == "menuCardCredits" } #expect( usageItem?.submenu?.items .contains { ($0.representedObject as? String) == "usageBreakdownChart" } == true) #expect( creditsItem?.submenu?.items .contains { ($0.representedObject as? String) == "creditsHistoryChart" } == true) } @Test func showsCreditsBeforeCostInCodexMenuCardSections() { let settings = self.makeSettings() settings.statusChecksEnabled = false settings.refreshFrequency = .manual settings.mergeIcons = true settings.selectedMenuProvider = .codex settings.costUsageEnabled = true let registry = ProviderRegistry.shared if let codexMeta = registry.metadata[.codex] { settings.setProviderEnabled(provider: .codex, metadata: codexMeta, enabled: true) } if let claudeMeta = registry.metadata[.claude] { settings.setProviderEnabled(provider: .claude, metadata: claudeMeta, enabled: false) } if let geminiMeta = registry.metadata[.gemini] { settings.setProviderEnabled(provider: .gemini, metadata: geminiMeta, enabled: false) } let fetcher = UsageFetcher() let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings) store.credits = CreditsSnapshot(remaining: 100, events: [], updatedAt: Date()) store.openAIDashboard = OpenAIDashboardSnapshot( signedInEmail: "user@example.com", codeReviewRemainingPercent: 100, creditEvents: [], dailyBreakdown: [], usageBreakdown: [], creditsPurchaseURL: nil, updatedAt: Date()) store._setTokenSnapshotForTesting(CostUsageTokenSnapshot( sessionTokens: 123, sessionCostUSD: 0.12, last30DaysTokens: 123, last30DaysCostUSD: 1.23, daily: [ CostUsageDailyReport.Entry( date: "2025-12-23", inputTokens: nil, outputTokens: nil, totalTokens: 123, costUSD: 1.23, modelsUsed: nil, modelBreakdowns: nil), ], updatedAt: Date()), provider: .codex) let controller = StatusItemController( store: store, settings: settings, account: fetcher.loadAccountInfo(), updater: DisabledUpdaterController(), preferencesSelection: PreferencesSelection()) let menu = controller.makeMenu() controller.menuWillOpen(menu) let ids = menu.items.compactMap { $0.representedObject as? String } let creditsIndex = ids.firstIndex(of: "menuCardCredits") let costIndex = ids.firstIndex(of: "menuCardCost") #expect(creditsIndex != nil) #expect(costIndex != nil) #expect(creditsIndex! < costIndex!) } @Test func showsExtraUsageForClaudeWhenUsingMenuCardSections() { let settings = self.makeSettings() settings.statusChecksEnabled = false settings.refreshFrequency = .manual settings.mergeIcons = true settings.selectedMenuProvider = .claude settings.costUsageEnabled = true settings.claudeWebExtrasEnabled = true let registry = ProviderRegistry.shared if let codexMeta = registry.metadata[.codex] { settings.setProviderEnabled(provider: .codex, metadata: codexMeta, enabled: false) } if let claudeMeta = registry.metadata[.claude] { settings.setProviderEnabled(provider: .claude, metadata: claudeMeta, enabled: true) } if let geminiMeta = registry.metadata[.gemini] { settings.setProviderEnabled(provider: .gemini, metadata: geminiMeta, enabled: false) } let fetcher = UsageFetcher() let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings) let identity = ProviderIdentitySnapshot( providerID: .claude, accountEmail: "user@example.com", accountOrganization: nil, loginMethod: "web") let snapshot = UsageSnapshot( primary: RateWindow(usedPercent: 10, windowMinutes: nil, resetsAt: nil, resetDescription: "Resets soon"), secondary: nil, tertiary: nil, providerCost: ProviderCostSnapshot( used: 0, limit: 2000, currencyCode: "EUR", period: "Monthly", resetsAt: nil, updatedAt: Date()), updatedAt: Date(), identity: identity) store._setSnapshotForTesting(snapshot, provider: .claude) store._setTokenSnapshotForTesting(CostUsageTokenSnapshot( sessionTokens: 123, sessionCostUSD: 0.12, last30DaysTokens: 123, last30DaysCostUSD: 1.23, daily: [ CostUsageDailyReport.Entry( date: "2025-12-23", inputTokens: nil, outputTokens: nil, totalTokens: 123, costUSD: 1.23, modelsUsed: nil, modelBreakdowns: nil), ], updatedAt: Date()), provider: .claude) let controller = StatusItemController( store: store, settings: settings, account: fetcher.loadAccountInfo(), updater: DisabledUpdaterController(), preferencesSelection: PreferencesSelection()) let menu = controller.makeMenu() controller.menuWillOpen(menu) let ids = menu.items.compactMap { $0.representedObject as? String } #expect(ids.contains("menuCardExtraUsage")) } @Test func showsVertexCostWhenUsageErrorPresent() { let settings = self.makeSettings() settings.statusChecksEnabled = false settings.refreshFrequency = .manual settings.mergeIcons = true settings.selectedMenuProvider = .vertexai settings.costUsageEnabled = true let registry = ProviderRegistry.shared if let vertexMeta = registry.metadata[.vertexai] { settings.setProviderEnabled(provider: .vertexai, metadata: vertexMeta, enabled: true) } if let codexMeta = registry.metadata[.codex] { settings.setProviderEnabled(provider: .codex, metadata: codexMeta, enabled: false) } if let claudeMeta = registry.metadata[.claude] { settings.setProviderEnabled(provider: .claude, metadata: claudeMeta, enabled: false) } let fetcher = UsageFetcher() let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings) store._setErrorForTesting("No Vertex AI usage data found for the current project.", provider: .vertexai) store._setTokenSnapshotForTesting(CostUsageTokenSnapshot( sessionTokens: 10, sessionCostUSD: 0.01, last30DaysTokens: 100, last30DaysCostUSD: 1.0, daily: [ CostUsageDailyReport.Entry( date: "2025-12-23", inputTokens: nil, outputTokens: nil, totalTokens: 100, costUSD: 1.0, modelsUsed: nil, modelBreakdowns: nil), ], updatedAt: Date()), provider: .vertexai) let controller = StatusItemController( store: store, settings: settings, account: fetcher.loadAccountInfo(), updater: DisabledUpdaterController(), preferencesSelection: PreferencesSelection()) let menu = controller.makeMenu() controller.menuWillOpen(menu) let ids = menu.items.compactMap { $0.representedObject as? String } #expect(ids.contains("menuCardCost")) } }