import AppKitimport CodexBarCoreimport Testing@testable import CodexBar @MainActor@Suitestruct 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")) }}
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"))
}
}