760 lines
30 KiB
Swift
760 lines
30 KiB
Swift
import AppKit
|
|
import CodexBarCore
|
|
import Testing
|
|
@testable import CodexBar
|
|
|
|
@Suite(.serialized)
|
|
@MainActor
|
|
struct StatusItemBalanceDisplayTests {
|
|
@Test
|
|
func `menu bar display text uses open router balance`() {
|
|
let settings = self.makeSettings(
|
|
suiteName: "StatusItemBalanceDisplayTests-openrouter-balance",
|
|
provider: .openrouter)
|
|
settings.setMenuBarMetricPreference(.automatic, for: .openrouter)
|
|
let (store, controller) = self.makeStoreAndController(settings: settings)
|
|
defer { controller.releaseStatusItemsForTesting() }
|
|
let snapshot = Self.openRouterSnapshot()
|
|
|
|
store._setSnapshotForTesting(snapshot, provider: .openrouter)
|
|
store._setErrorForTesting(nil, provider: .openrouter)
|
|
|
|
let displayText = controller.menuBarDisplayText(for: .openrouter, snapshot: snapshot)
|
|
|
|
#expect(displayText == "$12.34")
|
|
}
|
|
|
|
@Test
|
|
func `reset time mode preserves automatic open router balance`() {
|
|
let settings = self.makeSettings(
|
|
suiteName: "StatusItemBalanceDisplayTests-openrouter-reset-time",
|
|
provider: .openrouter)
|
|
settings.menuBarDisplayMode = .resetTime
|
|
settings.setMenuBarMetricPreference(.automatic, for: .openrouter)
|
|
let (store, controller) = self.makeStoreAndController(settings: settings)
|
|
defer { controller.releaseStatusItemsForTesting() }
|
|
let snapshot = Self.openRouterSnapshot()
|
|
|
|
store._setSnapshotForTesting(snapshot, provider: .openrouter)
|
|
store._setErrorForTesting(nil, provider: .openrouter)
|
|
|
|
let displayText = controller.menuBarDisplayText(for: .openrouter, snapshot: snapshot)
|
|
|
|
#expect(displayText == "$12.34")
|
|
}
|
|
|
|
@Test
|
|
func `menu bar display text uses crossmodel balance currency`() {
|
|
let settings = self.makeSettings(
|
|
suiteName: "StatusItemBalanceDisplayTests-crossmodel-eur-balance",
|
|
provider: .crossmodel)
|
|
settings.setMenuBarMetricPreference(.automatic, for: .crossmodel)
|
|
let (store, controller) = self.makeStoreAndController(settings: settings)
|
|
defer { controller.releaseStatusItemsForTesting() }
|
|
let snapshot = CrossModelUsageSnapshot(
|
|
currency: "EUR",
|
|
balance: 8.059489,
|
|
uncollected: 0,
|
|
daily: nil,
|
|
weekly: nil,
|
|
monthly: nil,
|
|
updatedAt: Date())
|
|
.toUsageSnapshot()
|
|
|
|
store._setSnapshotForTesting(snapshot, provider: .crossmodel)
|
|
store._setErrorForTesting(nil, provider: .crossmodel)
|
|
|
|
let displayText = controller.menuBarDisplayText(for: .crossmodel, snapshot: snapshot)
|
|
|
|
#expect(displayText == "€8.06")
|
|
}
|
|
|
|
@Test
|
|
func `menu bar display text uses zen balance when open code has no subscription`() {
|
|
let settings = self.makeSettings(
|
|
suiteName: "StatusItemBalanceDisplayTests-opencodego-zen-only",
|
|
provider: .opencodego)
|
|
let (store, controller) = self.makeStoreAndController(settings: settings)
|
|
defer { controller.releaseStatusItemsForTesting() }
|
|
let snapshot = UsageSnapshot(
|
|
primary: nil,
|
|
secondary: nil,
|
|
providerCost: ProviderCostSnapshot(
|
|
used: 23.75,
|
|
limit: 0,
|
|
currencyCode: "USD",
|
|
period: "Zen balance",
|
|
updatedAt: Date()),
|
|
updatedAt: Date())
|
|
|
|
store._setSnapshotForTesting(snapshot, provider: .opencodego)
|
|
store._setErrorForTesting(nil, provider: .opencodego)
|
|
|
|
let displayText = controller.menuBarDisplayText(for: .opencodego, snapshot: snapshot)
|
|
|
|
#expect(displayText == "$23.75")
|
|
}
|
|
|
|
@Test
|
|
func `menu bar display text uses negative zen balance when open code is in deficit`() {
|
|
let settings = self.makeSettings(
|
|
suiteName: "StatusItemBalanceDisplayTests-opencodego-zen-deficit",
|
|
provider: .opencodego)
|
|
let (store, controller) = self.makeStoreAndController(settings: settings)
|
|
defer { controller.releaseStatusItemsForTesting() }
|
|
let snapshot = UsageSnapshot(
|
|
primary: nil,
|
|
secondary: nil,
|
|
providerCost: ProviderCostSnapshot(
|
|
used: -4.25,
|
|
limit: 0,
|
|
currencyCode: "USD",
|
|
period: "Zen balance",
|
|
updatedAt: Date()),
|
|
updatedAt: Date())
|
|
|
|
store._setSnapshotForTesting(snapshot, provider: .opencodego)
|
|
store._setErrorForTesting(nil, provider: .opencodego)
|
|
|
|
let displayText = controller.menuBarDisplayText(for: .opencodego, snapshot: snapshot)
|
|
|
|
#expect(displayText == "-$4.25")
|
|
}
|
|
|
|
@Test
|
|
func `menu bar display text keeps open code subscription percentage`() {
|
|
let settings = self.makeSettings(
|
|
suiteName: "StatusItemBalanceDisplayTests-opencodego-subscription",
|
|
provider: .opencodego)
|
|
let (store, controller) = self.makeStoreAndController(settings: settings)
|
|
defer { controller.releaseStatusItemsForTesting() }
|
|
let snapshot = UsageSnapshot(
|
|
primary: RateWindow(usedPercent: 12, windowMinutes: 300, resetsAt: nil, resetDescription: nil),
|
|
secondary: RateWindow(usedPercent: 34, windowMinutes: 10080, resetsAt: nil, resetDescription: nil),
|
|
providerCost: ProviderCostSnapshot(
|
|
used: 23.75,
|
|
limit: 0,
|
|
currencyCode: "USD",
|
|
period: "Zen balance",
|
|
updatedAt: Date()),
|
|
updatedAt: Date())
|
|
|
|
store._setSnapshotForTesting(snapshot, provider: .opencodego)
|
|
store._setErrorForTesting(nil, provider: .opencodego)
|
|
|
|
let displayText = controller.menuBarDisplayText(for: .opencodego, snapshot: snapshot)
|
|
|
|
#expect(displayText == "12%")
|
|
}
|
|
|
|
@Test
|
|
func `reset time mode preserves balance when provider has no quota window`() {
|
|
let settings = self.makeSettings(
|
|
suiteName: "StatusItemBalanceDisplayTests-moonshot-reset-time",
|
|
provider: .moonshot)
|
|
settings.menuBarDisplayMode = .resetTime
|
|
let (store, controller) = self.makeStoreAndController(settings: settings)
|
|
defer { controller.releaseStatusItemsForTesting() }
|
|
let snapshot = UsageSnapshot(
|
|
primary: nil,
|
|
secondary: nil,
|
|
updatedAt: Date(),
|
|
identity: ProviderIdentitySnapshot(
|
|
providerID: .moonshot,
|
|
accountEmail: nil,
|
|
accountOrganization: nil,
|
|
loginMethod: "Balance: $49.58 · $0.42 in deficit"))
|
|
|
|
store._setSnapshotForTesting(snapshot, provider: .moonshot)
|
|
store._setErrorForTesting(nil, provider: .moonshot)
|
|
|
|
let displayText = controller.menuBarDisplayText(for: .moonshot, snapshot: snapshot)
|
|
|
|
#expect(displayText == "$49.58")
|
|
}
|
|
|
|
@Test
|
|
func `menu bar display text respects open router primary metric preference`() {
|
|
let settings = self.makeSettings(
|
|
suiteName: "StatusItemBalanceDisplayTests-openrouter-primary-metric",
|
|
provider: .openrouter)
|
|
settings.setMenuBarMetricPreference(.primary, for: .openrouter)
|
|
let (store, controller) = self.makeStoreAndController(settings: settings)
|
|
defer { controller.releaseStatusItemsForTesting() }
|
|
let snapshot = Self.openRouterSnapshot()
|
|
|
|
store._setSnapshotForTesting(snapshot, provider: .openrouter)
|
|
store._setErrorForTesting(nil, provider: .openrouter)
|
|
|
|
let displayText = controller.menuBarDisplayText(for: .openrouter, snapshot: snapshot)
|
|
|
|
#expect(displayText == "25%")
|
|
}
|
|
|
|
@Test
|
|
func `menu bar display text skips exhausted cursor api subquota when total remains usable`() {
|
|
let settings = self.makeSettings(
|
|
suiteName: "StatusItemBalanceDisplayTests-cursor-exhausted-api",
|
|
provider: .cursor)
|
|
settings.usageBarsShowUsed = false
|
|
settings.setMenuBarMetricPreference(.automatic, for: .cursor)
|
|
let (store, controller) = self.makeStoreAndController(settings: settings)
|
|
defer { controller.releaseStatusItemsForTesting() }
|
|
let snapshot = UsageSnapshot(
|
|
primary: RateWindow(usedPercent: 67, windowMinutes: 30 * 24 * 60, resetsAt: nil, resetDescription: "Total"),
|
|
secondary: RateWindow(
|
|
usedPercent: 34,
|
|
windowMinutes: 30 * 24 * 60,
|
|
resetsAt: nil,
|
|
resetDescription: "Auto"),
|
|
tertiary: RateWindow(usedPercent: 100, windowMinutes: 30 * 24 * 60, resetsAt: nil, resetDescription: "API"),
|
|
updatedAt: Date())
|
|
|
|
store._setSnapshotForTesting(snapshot, provider: .cursor)
|
|
store._setErrorForTesting(nil, provider: .cursor)
|
|
|
|
let displayText = controller.menuBarDisplayText(for: .cursor, snapshot: snapshot)
|
|
|
|
#expect(displayText == "33%")
|
|
}
|
|
|
|
@Test
|
|
func `menu bar display text uses deepseek balance`() {
|
|
let settings = self.makeSettings(
|
|
suiteName: "StatusItemBalanceDisplayTests-deepseek-balance",
|
|
provider: .deepseek)
|
|
let (store, controller) = self.makeStoreAndController(settings: settings)
|
|
defer { controller.releaseStatusItemsForTesting() }
|
|
let snapshot = UsageSnapshot(
|
|
primary: RateWindow(
|
|
usedPercent: 0,
|
|
windowMinutes: nil,
|
|
resetsAt: nil,
|
|
resetDescription: "$9.32 (Paid: $9.32 / Granted: $0.00)"),
|
|
secondary: nil,
|
|
updatedAt: Date())
|
|
|
|
store._setSnapshotForTesting(snapshot, provider: .deepseek)
|
|
store._setErrorForTesting(nil, provider: .deepseek)
|
|
|
|
let displayText = controller.menuBarDisplayText(for: .deepseek, snapshot: snapshot)
|
|
|
|
#expect(displayText == "$9.32")
|
|
}
|
|
|
|
@Test
|
|
func `menu bar display text uses mimo balance without token plan`() {
|
|
let settings = self.makeSettings(
|
|
suiteName: "StatusItemBalanceDisplayTests-mimo-balance",
|
|
provider: .mimo)
|
|
let (store, controller) = self.makeStoreAndController(settings: settings)
|
|
defer { controller.releaseStatusItemsForTesting() }
|
|
let snapshot = MiMoUsageSnapshot(
|
|
balance: 25.51,
|
|
currency: "USD",
|
|
cashBalance: 20,
|
|
giftBalance: 5.51,
|
|
updatedAt: Date())
|
|
.toUsageSnapshot()
|
|
|
|
store._setSnapshotForTesting(snapshot, provider: .mimo)
|
|
store._setErrorForTesting(nil, provider: .mimo)
|
|
|
|
let displayText = controller.menuBarDisplayText(for: .mimo, snapshot: snapshot)
|
|
|
|
#expect(displayText == "$25.51")
|
|
}
|
|
|
|
@Test
|
|
func `menu bar display text uses selected mimo balance with token plan`() {
|
|
let settings = self.makeSettings(
|
|
suiteName: "StatusItemBalanceDisplayTests-mimo-token-plan",
|
|
provider: .mimo)
|
|
settings.setMenuBarMetricPreference(.secondary, for: .mimo)
|
|
let (store, controller) = self.makeStoreAndController(settings: settings)
|
|
defer { controller.releaseStatusItemsForTesting() }
|
|
let snapshot = MiMoUsageSnapshot(
|
|
balance: 25.51,
|
|
currency: "USD",
|
|
planCode: "standard",
|
|
tokenUsed: 10,
|
|
tokenLimit: 100,
|
|
tokenPercent: 0.1,
|
|
updatedAt: Date())
|
|
.toUsageSnapshot()
|
|
|
|
store._setSnapshotForTesting(snapshot, provider: .mimo)
|
|
store._setErrorForTesting(nil, provider: .mimo)
|
|
|
|
let displayText = controller.menuBarDisplayText(for: .mimo, snapshot: snapshot)
|
|
|
|
#expect(displayText == "$25.51")
|
|
}
|
|
|
|
@Test
|
|
func `menu bar display text uses moonshot balance`() {
|
|
let settings = self.makeSettings(
|
|
suiteName: "StatusItemBalanceDisplayTests-moonshot-balance",
|
|
provider: .moonshot)
|
|
let (store, controller) = self.makeStoreAndController(settings: settings)
|
|
defer { controller.releaseStatusItemsForTesting() }
|
|
let snapshot = UsageSnapshot(
|
|
primary: nil,
|
|
secondary: nil,
|
|
updatedAt: Date(),
|
|
identity: ProviderIdentitySnapshot(
|
|
providerID: .moonshot,
|
|
accountEmail: nil,
|
|
accountOrganization: nil,
|
|
loginMethod: "Balance: $49.58 · $0.42 in deficit"))
|
|
|
|
store._setSnapshotForTesting(snapshot, provider: .moonshot)
|
|
store._setErrorForTesting(nil, provider: .moonshot)
|
|
|
|
let displayText = controller.menuBarDisplayText(for: .moonshot, snapshot: snapshot)
|
|
|
|
#expect(snapshot.primary == nil)
|
|
#expect(displayText == "$49.58")
|
|
}
|
|
|
|
@Test
|
|
func `menu bar display text uses mistral current month api spend`() {
|
|
let settings = self.makeSettings(
|
|
suiteName: "StatusItemBalanceDisplayTests-mistral-spend",
|
|
provider: .mistral)
|
|
let (store, controller) = self.makeStoreAndController(settings: settings)
|
|
defer { controller.releaseStatusItemsForTesting() }
|
|
let snapshot = MistralUsageSnapshot(
|
|
totalCost: 1.2345,
|
|
currency: "EUR",
|
|
currencySymbol: "€",
|
|
totalInputTokens: 10000,
|
|
totalOutputTokens: 5000,
|
|
totalCachedTokens: 0,
|
|
modelCount: 2,
|
|
startDate: nil,
|
|
endDate: nil,
|
|
updatedAt: Date()).toUsageSnapshot()
|
|
|
|
store._setSnapshotForTesting(snapshot, provider: .mistral)
|
|
store._setErrorForTesting(nil, provider: .mistral)
|
|
|
|
let displayText = controller.menuBarDisplayText(for: .mistral, snapshot: snapshot)
|
|
|
|
#expect(snapshot.primary == nil)
|
|
#expect(snapshot.identity?.loginMethod == "API spend: €1.2345 this month")
|
|
#expect(displayText == "€1.2345")
|
|
}
|
|
|
|
@Test
|
|
func `menu bar display text uses mistral monthly plan when selected`() {
|
|
let settings = self.makeSettings(
|
|
suiteName: "StatusItemBalanceDisplayTests-mistral-monthly-plan",
|
|
provider: .mistral)
|
|
settings.setMenuBarMetricPreference(.monthlyPlan, for: .mistral)
|
|
let (store, controller) = self.makeStoreAndController(settings: settings)
|
|
defer { controller.releaseStatusItemsForTesting() }
|
|
let snapshot = MistralUsageSnapshot(
|
|
totalCost: 1.2345,
|
|
currency: "EUR",
|
|
currencySymbol: "€",
|
|
totalInputTokens: 10000,
|
|
totalOutputTokens: 5000,
|
|
totalCachedTokens: 0,
|
|
modelCount: 2,
|
|
startDate: nil,
|
|
endDate: nil,
|
|
updatedAt: Date())
|
|
.toUsageSnapshot()
|
|
.with(extraRateWindows: [
|
|
NamedRateWindow(
|
|
id: "mistral-monthly-plan",
|
|
title: "Monthly Plan",
|
|
window: RateWindow(
|
|
usedPercent: 42,
|
|
windowMinutes: nil,
|
|
resetsAt: nil,
|
|
resetDescription: nil)),
|
|
])
|
|
|
|
store._setSnapshotForTesting(snapshot, provider: .mistral)
|
|
store._setErrorForTesting(nil, provider: .mistral)
|
|
|
|
let displayText = controller.menuBarDisplayText(for: .mistral, snapshot: snapshot)
|
|
|
|
#expect(snapshot.identity?.loginMethod == "API spend: €1.2345 this month")
|
|
#expect(displayText == "42%")
|
|
}
|
|
|
|
@Test
|
|
func `menu bar display text falls back to mistral spend when monthly plan is missing`() {
|
|
let settings = self.makeSettings(
|
|
suiteName: "StatusItemBalanceDisplayTests-mistral-monthly-plan-missing",
|
|
provider: .mistral)
|
|
settings.setMenuBarMetricPreference(.monthlyPlan, for: .mistral)
|
|
let (store, controller) = self.makeStoreAndController(settings: settings)
|
|
defer { controller.releaseStatusItemsForTesting() }
|
|
let snapshot = MistralUsageSnapshot(
|
|
totalCost: 1.2345,
|
|
currency: "EUR",
|
|
currencySymbol: "€",
|
|
totalInputTokens: 10000,
|
|
totalOutputTokens: 5000,
|
|
totalCachedTokens: 0,
|
|
modelCount: 2,
|
|
startDate: nil,
|
|
endDate: nil,
|
|
updatedAt: Date())
|
|
.toUsageSnapshot()
|
|
|
|
store._setSnapshotForTesting(snapshot, provider: .mistral)
|
|
store._setErrorForTesting(nil, provider: .mistral)
|
|
|
|
let displayText = controller.menuBarDisplayText(for: .mistral, snapshot: snapshot)
|
|
|
|
#expect(snapshot.identity?.loginMethod == "API spend: €1.2345 this month")
|
|
#expect(displayText == "€1.2345")
|
|
}
|
|
|
|
@Test
|
|
func `menu bar display text uses kimi k2 api key credits`() {
|
|
let settings = self.makeSettings(
|
|
suiteName: "StatusItemBalanceDisplayTests-kimik2-credits",
|
|
provider: .kimik2)
|
|
let (store, controller) = self.makeStoreAndController(settings: settings)
|
|
defer { controller.releaseStatusItemsForTesting() }
|
|
let snapshot = KimiK2UsageSummary(
|
|
consumed: 75,
|
|
remaining: 1234.5,
|
|
averageTokens: nil,
|
|
updatedAt: Date()).toUsageSnapshot()
|
|
|
|
store._setSnapshotForTesting(snapshot, provider: .kimik2)
|
|
store._setErrorForTesting(nil, provider: .kimik2)
|
|
|
|
let displayText = controller.menuBarDisplayText(for: .kimik2, snapshot: snapshot)
|
|
|
|
#expect(snapshot.primary == nil)
|
|
#expect(snapshot.identity?.loginMethod == "Credits: 1234.5 left")
|
|
#expect(displayText == "1234.5")
|
|
}
|
|
|
|
@Test
|
|
func `kiro menu bar automatic uses credits left`() {
|
|
let settings = self.makeSettings(
|
|
suiteName: "StatusItemBalanceDisplayTests-kiro-automatic",
|
|
provider: .kiro)
|
|
settings.kiroMenuBarDisplayMode = .automatic
|
|
let (store, controller) = self.makeStoreAndController(settings: settings)
|
|
defer { controller.releaseStatusItemsForTesting() }
|
|
let snapshot = Self.kiroSnapshot()
|
|
|
|
store._setSnapshotForTesting(snapshot, provider: .kiro)
|
|
store._setErrorForTesting(nil, provider: .kiro)
|
|
|
|
let displayText = controller.menuBarDisplayText(for: .kiro, snapshot: snapshot)
|
|
|
|
#expect(displayText == "49.83")
|
|
}
|
|
|
|
@Test
|
|
func `kiro menu bar credits and percent combines values`() {
|
|
let settings = self.makeSettings(
|
|
suiteName: "StatusItemBalanceDisplayTests-kiro-both",
|
|
provider: .kiro)
|
|
settings.kiroMenuBarDisplayMode = .creditsAndPercent
|
|
let (store, controller) = self.makeStoreAndController(settings: settings)
|
|
defer { controller.releaseStatusItemsForTesting() }
|
|
let snapshot = Self.kiroSnapshot()
|
|
|
|
store._setSnapshotForTesting(snapshot, provider: .kiro)
|
|
store._setErrorForTesting(nil, provider: .kiro)
|
|
|
|
let displayText = controller.menuBarDisplayText(for: .kiro, snapshot: snapshot)
|
|
|
|
#expect(displayText == "49.83 · 0%")
|
|
}
|
|
|
|
@Test
|
|
func `kiro menu bar hidden suppresses text value`() {
|
|
let settings = self.makeSettings(
|
|
suiteName: "StatusItemBalanceDisplayTests-kiro-hidden",
|
|
provider: .kiro)
|
|
settings.kiroMenuBarDisplayMode = .hidden
|
|
let (store, controller) = self.makeStoreAndController(settings: settings)
|
|
defer { controller.releaseStatusItemsForTesting() }
|
|
let snapshot = Self.kiroSnapshot()
|
|
|
|
store._setSnapshotForTesting(snapshot, provider: .kiro)
|
|
store._setErrorForTesting(nil, provider: .kiro)
|
|
|
|
let displayText = controller.menuBarDisplayText(for: .kiro, snapshot: snapshot)
|
|
|
|
#expect(displayText == nil)
|
|
}
|
|
|
|
@Test
|
|
func `kiro menu bar used and total formats credits`() {
|
|
let settings = self.makeSettings(
|
|
suiteName: "StatusItemBalanceDisplayTests-kiro-used-total",
|
|
provider: .kiro)
|
|
settings.kiroMenuBarDisplayMode = .usedAndTotal
|
|
let (store, controller) = self.makeStoreAndController(settings: settings)
|
|
defer { controller.releaseStatusItemsForTesting() }
|
|
let snapshot = Self.kiroSnapshot()
|
|
|
|
store._setSnapshotForTesting(snapshot, provider: .kiro)
|
|
store._setErrorForTesting(nil, provider: .kiro)
|
|
|
|
let displayText = controller.menuBarDisplayText(for: .kiro, snapshot: snapshot)
|
|
|
|
#expect(displayText == "0.17 / 50")
|
|
}
|
|
|
|
@Test
|
|
func `kiro menu bar overage credits mode shows overage credits when exhausted`() {
|
|
let settings = self.makeSettings(
|
|
suiteName: "StatusItemBalanceDisplayTests-kiro-overage-credits",
|
|
provider: .kiro)
|
|
settings.kiroMenuBarDisplayMode = .overageCreditsWhenExhausted
|
|
let (store, controller) = self.makeStoreAndController(settings: settings)
|
|
defer { controller.releaseStatusItemsForTesting() }
|
|
let snapshot = Self.exhaustedKiroSnapshot()
|
|
|
|
store._setSnapshotForTesting(snapshot, provider: .kiro)
|
|
store._setErrorForTesting(nil, provider: .kiro)
|
|
|
|
let displayText = controller.menuBarDisplayText(for: .kiro, snapshot: snapshot)
|
|
|
|
#expect(displayText == "40.29 over")
|
|
}
|
|
|
|
@Test
|
|
func `kiro menu bar overage cost mode shows cost when exhausted`() {
|
|
let settings = self.makeSettings(
|
|
suiteName: "StatusItemBalanceDisplayTests-kiro-overage-cost",
|
|
provider: .kiro)
|
|
settings.kiroMenuBarDisplayMode = .overageCostWhenExhausted
|
|
let (store, controller) = self.makeStoreAndController(settings: settings)
|
|
defer { controller.releaseStatusItemsForTesting() }
|
|
let snapshot = Self.exhaustedKiroSnapshot()
|
|
|
|
store._setSnapshotForTesting(snapshot, provider: .kiro)
|
|
store._setErrorForTesting(nil, provider: .kiro)
|
|
|
|
let displayText = controller.menuBarDisplayText(for: .kiro, snapshot: snapshot)
|
|
|
|
#expect(displayText == "$1.61 over")
|
|
}
|
|
|
|
@Test
|
|
func `kiro menu bar overage credits and cost mode shows both when exhausted`() {
|
|
let settings = self.makeSettings(
|
|
suiteName: "StatusItemBalanceDisplayTests-kiro-overage-both",
|
|
provider: .kiro)
|
|
settings.kiroMenuBarDisplayMode = .overageCreditsAndCostWhenExhausted
|
|
let (store, controller) = self.makeStoreAndController(settings: settings)
|
|
defer { controller.releaseStatusItemsForTesting() }
|
|
let snapshot = Self.exhaustedKiroSnapshot()
|
|
|
|
store._setSnapshotForTesting(snapshot, provider: .kiro)
|
|
store._setErrorForTesting(nil, provider: .kiro)
|
|
|
|
let displayText = controller.menuBarDisplayText(for: .kiro, snapshot: snapshot)
|
|
|
|
#expect(displayText == "40.29 · $1.61")
|
|
}
|
|
|
|
@Test
|
|
func `kiro menu bar overage mode keeps credits left before exhaustion`() {
|
|
let settings = self.makeSettings(
|
|
suiteName: "StatusItemBalanceDisplayTests-kiro-overage-not-exhausted",
|
|
provider: .kiro)
|
|
settings.kiroMenuBarDisplayMode = .overageCreditsAndCostWhenExhausted
|
|
let (store, controller) = self.makeStoreAndController(settings: settings)
|
|
defer { controller.releaseStatusItemsForTesting() }
|
|
let snapshot = Self.kiroSnapshot()
|
|
|
|
store._setSnapshotForTesting(snapshot, provider: .kiro)
|
|
store._setErrorForTesting(nil, provider: .kiro)
|
|
|
|
let displayText = controller.menuBarDisplayText(for: .kiro, snapshot: snapshot)
|
|
|
|
#expect(displayText == "49.83")
|
|
}
|
|
|
|
@Test
|
|
func `kiro menu bar overage mode ignores disabled overage values`() {
|
|
let settings = self.makeSettings(
|
|
suiteName: "StatusItemBalanceDisplayTests-kiro-overage-disabled",
|
|
provider: .kiro)
|
|
settings.kiroMenuBarDisplayMode = .overageCreditsAndCostWhenExhausted
|
|
let (store, controller) = self.makeStoreAndController(settings: settings)
|
|
defer { controller.releaseStatusItemsForTesting() }
|
|
let snapshot = Self.exhaustedKiroSnapshot(overagesStatus: "Disabled")
|
|
|
|
store._setSnapshotForTesting(snapshot, provider: .kiro)
|
|
store._setErrorForTesting(nil, provider: .kiro)
|
|
|
|
let displayText = controller.menuBarDisplayText(for: .kiro, snapshot: snapshot)
|
|
|
|
#expect(displayText == "0")
|
|
}
|
|
|
|
@Test
|
|
func `kiro managed plan display falls back to percent`() {
|
|
let settings = self.makeSettings(
|
|
suiteName: "StatusItemBalanceDisplayTests-kiro-managed",
|
|
provider: .kiro)
|
|
settings.kiroMenuBarDisplayMode = .automatic
|
|
settings.usageBarsShowUsed = false
|
|
let (store, controller) = self.makeStoreAndController(settings: settings)
|
|
defer { controller.releaseStatusItemsForTesting() }
|
|
let snapshot = KiroUsageSnapshot(
|
|
planName: "Q Developer Pro",
|
|
creditsUsed: 0,
|
|
creditsTotal: 0,
|
|
creditsPercent: 0,
|
|
bonusCreditsUsed: nil,
|
|
bonusCreditsTotal: nil,
|
|
bonusExpiryDays: nil,
|
|
resetsAt: nil,
|
|
updatedAt: Date()).toUsageSnapshot()
|
|
|
|
store._setSnapshotForTesting(snapshot, provider: .kiro)
|
|
store._setErrorForTesting(nil, provider: .kiro)
|
|
|
|
let displayText = controller.menuBarDisplayText(for: .kiro, snapshot: snapshot)
|
|
|
|
#expect(displayText == "100%")
|
|
}
|
|
|
|
@Test
|
|
func `mistral primary window is nil without credits even when billing end date is set`() {
|
|
let endDate = Date(timeIntervalSinceNow: 3600)
|
|
let snapshot = MistralUsageSnapshot(
|
|
totalCost: 0.5,
|
|
currency: "USD",
|
|
currencySymbol: "$",
|
|
totalInputTokens: 1000,
|
|
totalOutputTokens: 500,
|
|
totalCachedTokens: 0,
|
|
modelCount: 1,
|
|
startDate: nil,
|
|
endDate: endDate,
|
|
updatedAt: Date()).toUsageSnapshot()
|
|
|
|
// Billing end date alone is not a quota window; credits are what populate primary.
|
|
#expect(snapshot.primary == nil)
|
|
}
|
|
|
|
@Test
|
|
func `button title spacing only applies when image is present`() {
|
|
#expect(StatusItemController.buttonTitle("42%", hasImage: true) == " 42%")
|
|
#expect(StatusItemController.buttonTitle("42%", hasImage: false) == "42%")
|
|
#expect(StatusItemController.buttonTitle(nil, hasImage: true).isEmpty)
|
|
#expect(StatusItemController.buttonTitle("", hasImage: true).isEmpty)
|
|
}
|
|
|
|
@Test
|
|
func `debug button title stays visible with or without a usage value`() {
|
|
#expect(StatusItemController.buttonTitle(nil, hasImage: true, isDebugApp: true) == " D")
|
|
#expect(StatusItemController.buttonTitle("42%", hasImage: true, isDebugApp: true) == " 42% D")
|
|
#expect(StatusItemController.buttonTitle("42%", hasImage: false, isDebugApp: true) == "42% D")
|
|
}
|
|
|
|
@Test
|
|
func `debug bundle identity updates status item accessibility`() {
|
|
#expect(StatusItemController.isDebugApp(bundleIdentifier: "com.steipete.codexbar.debug"))
|
|
#expect(!StatusItemController.isDebugApp(bundleIdentifier: "com.steipete.codexbar"))
|
|
#expect(!StatusItemController.isDebugApp(bundleIdentifier: nil))
|
|
#expect(StatusItemController.statusItemAccessibilityTitle(isDebugApp: true) == "CodexBar Debug")
|
|
#expect(StatusItemController.statusItemAccessibilityTitle(isDebugApp: false) == "CodexBar")
|
|
}
|
|
|
|
private func makeSettings(suiteName: String, provider: UsageProvider) -> SettingsStore {
|
|
let settings = testSettingsStore(suiteName: suiteName)
|
|
settings.statusChecksEnabled = false
|
|
settings.refreshFrequency = .manual
|
|
settings.mergeIcons = true
|
|
settings.selectedMenuProvider = provider
|
|
settings.menuBarDisplayMode = .both
|
|
settings.usageBarsShowUsed = true
|
|
|
|
let registry = ProviderRegistry.shared
|
|
if let metadata = registry.metadata[provider] {
|
|
settings.setProviderEnabled(provider: provider, metadata: metadata, enabled: true)
|
|
}
|
|
return settings
|
|
}
|
|
|
|
private func makeStoreAndController(settings: SettingsStore) -> (UsageStore, StatusItemController) {
|
|
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(),
|
|
statusBar: testStatusBar())
|
|
return (store, controller)
|
|
}
|
|
|
|
private static func openRouterSnapshot() -> UsageSnapshot {
|
|
OpenRouterUsageSnapshot(
|
|
totalCredits: 50,
|
|
totalUsage: 37.66,
|
|
balance: 12.34,
|
|
usedPercent: 75.32,
|
|
keyLimit: 20,
|
|
keyUsage: 5,
|
|
rateLimit: nil,
|
|
updatedAt: Date()).toUsageSnapshot()
|
|
}
|
|
|
|
private static func kiroSnapshot() -> UsageSnapshot {
|
|
KiroUsageSnapshot(
|
|
planName: "KIRO FREE",
|
|
accountEmail: "person@example.com",
|
|
authMethod: "Google",
|
|
creditsUsed: 0.17,
|
|
creditsTotal: 50,
|
|
creditsPercent: 0,
|
|
bonusCreditsUsed: 45.53,
|
|
bonusCreditsTotal: 2000,
|
|
bonusExpiryDays: 19,
|
|
overagesStatus: "Disabled",
|
|
manageURL: "https://app.kiro.dev/account/usage",
|
|
contextUsage: KiroContextUsageSnapshot(
|
|
totalPercentUsed: 1.3,
|
|
contextFilesPercent: 0.5,
|
|
toolsPercent: 0.8,
|
|
kiroResponsesPercent: 0,
|
|
promptsPercent: 0),
|
|
resetsAt: Date(),
|
|
updatedAt: Date()).toUsageSnapshot()
|
|
}
|
|
|
|
private static func exhaustedKiroSnapshot(overagesStatus: String = "Enabled billed at $0.04 per request")
|
|
-> UsageSnapshot
|
|
{
|
|
KiroUsageSnapshot(
|
|
planName: "KIRO FREE",
|
|
accountEmail: "person@example.com",
|
|
authMethod: "Google",
|
|
creditsUsed: 50,
|
|
creditsTotal: 50,
|
|
creditsPercent: 100,
|
|
bonusCreditsUsed: nil,
|
|
bonusCreditsTotal: nil,
|
|
bonusExpiryDays: nil,
|
|
overagesStatus: overagesStatus,
|
|
overageCreditsUsed: 40.29,
|
|
estimatedOverageCostUSD: 1.61,
|
|
manageURL: "https://app.kiro.dev/account/usage",
|
|
resetsAt: Date(),
|
|
updatedAt: Date()).toUsageSnapshot()
|
|
}
|
|
}
|