import CodexBarCoreimport Foundationimport Testing@testable import CodexBar @MainActorstruct CodexConsumerProjectionTests { @Test func `live card projection compacts weekly lanes and attaches dashboard extras`() { let store = self.makeStore(suite: "CodexConsumerProjectionTests-live-card") let now = Date(timeIntervalSince1970: 1_700_000_000) store._setSnapshotForTesting( UsageSnapshot( primary: nil, secondary: RateWindow( usedPercent: 25, windowMinutes: 10080, resetsAt: now.addingTimeInterval(3600), resetDescription: nil), updatedAt: now), provider: .codex) store.credits = CreditsSnapshot(remaining: 42, events: [], updatedAt: now) store.openAIDashboard = OpenAIDashboardSnapshot( signedInEmail: "codex@example.com", codeReviewRemainingPercent: 88, codeReviewLimit: RateWindow( usedPercent: 12, windowMinutes: nil, resetsAt: now.addingTimeInterval(7200), resetDescription: nil), creditEvents: [], dailyBreakdown: [OpenAIDashboardDailyBreakdown(day: "2024-01-01", services: [], totalCreditsUsed: 3)], usageBreakdown: [OpenAIDashboardDailyBreakdown(day: "2024-01-01", services: [], totalCreditsUsed: 4)], creditsPurchaseURL: "https://chatgpt.com/settings/billing", updatedAt: now) store.openAIDashboardAttachmentAuthorized = true store.openAIDashboardRequiresLogin = false let projection = store.codexConsumerProjection(surface: .liveCard, now: now) #expect(projection.visibleRateLanes == [.weekly]) #expect(projection.planUtilizationLanes.map(\.role.rawValue) == ["weekly"]) #expect(projection.dashboardVisibility == .attached) #expect(projection.supplementalMetrics == [.codeReview]) #expect(projection.remainingPercent(for: .codeReview) == 88) #expect(projection.credits?.remaining == 42) #expect(projection.canShowBuyCredits) #expect(projection.hasUsageBreakdown) #expect(projection.hasCreditsHistory) } @Test func `display only dashboard stays visible without attached extras`() { let store = self.makeStore(suite: "CodexConsumerProjectionTests-display-only") let now = Date(timeIntervalSince1970: 1_700_000_000) store._setSnapshotForTesting( UsageSnapshot( primary: RateWindow( usedPercent: 15, windowMinutes: 300, resetsAt: now.addingTimeInterval(1800), resetDescription: nil), secondary: RateWindow( usedPercent: 30, windowMinutes: 10080, resetsAt: now.addingTimeInterval(3600), resetDescription: nil), updatedAt: now), provider: .codex) store.openAIDashboard = OpenAIDashboardSnapshot( signedInEmail: "codex@example.com", codeReviewRemainingPercent: 66, creditEvents: [], dailyBreakdown: [OpenAIDashboardDailyBreakdown(day: "2024-01-01", services: [], totalCreditsUsed: 3)], usageBreakdown: [OpenAIDashboardDailyBreakdown(day: "2024-01-01", services: [], totalCreditsUsed: 4)], creditsPurchaseURL: "https://chatgpt.com/settings/billing", updatedAt: now) store.openAIDashboardAttachmentAuthorized = false store.openAIDashboardRequiresLogin = false let projection = store.codexConsumerProjection(surface: .liveCard, now: now) #expect(projection.dashboardVisibility == .displayOnly) #expect(projection.supplementalMetrics.isEmpty) #expect(projection.canShowBuyCredits) #expect(!projection.hasUsageBreakdown) #expect(!projection.hasCreditsHistory) } @Test func `override card projection does not pull live codex adjuncts`() { let store = self.makeStore(suite: "CodexConsumerProjectionTests-override") let now = Date(timeIntervalSince1970: 1_700_000_000) store._setSnapshotForTesting( UsageSnapshot( primary: RateWindow( usedPercent: 18, windowMinutes: 300, resetsAt: now.addingTimeInterval(1800), resetDescription: nil), secondary: nil, updatedAt: now), provider: .codex) store.credits = CreditsSnapshot(remaining: 42, events: [], updatedAt: now) store.lastCreditsError = "Frame load interrupted" store.openAIDashboard = OpenAIDashboardSnapshot( signedInEmail: "codex@example.com", codeReviewRemainingPercent: 88, creditEvents: [], dailyBreakdown: [OpenAIDashboardDailyBreakdown(day: "2024-01-01", services: [], totalCreditsUsed: 3)], usageBreakdown: [OpenAIDashboardDailyBreakdown(day: "2024-01-01", services: [], totalCreditsUsed: 4)], creditsPurchaseURL: "https://chatgpt.com/settings/billing", updatedAt: now) store.openAIDashboardAttachmentAuthorized = true store.openAIDashboardRequiresLogin = false store._setErrorForTesting("Live codex error", provider: .codex) let overrideSnapshot = UsageSnapshot( primary: RateWindow( usedPercent: 55, windowMinutes: 300, resetsAt: now.addingTimeInterval(1200), resetDescription: nil), secondary: nil, updatedAt: now) let projection = store.codexConsumerProjection( surface: .overrideCard, snapshotOverride: overrideSnapshot, errorOverride: "Override error", now: now) #expect(projection.visibleRateLanes == [.session]) #expect(projection.dashboardVisibility == .hidden) #expect(projection.credits == nil) #expect(projection.supplementalMetrics.isEmpty) #expect(!projection.canShowBuyCredits) #expect(!projection.hasUsageBreakdown) #expect(!projection.hasCreditsHistory) #expect(projection.userFacingErrors.usage == "Override error") #expect(projection.userFacingErrors.credits == nil) #expect(projection.userFacingErrors.dashboard == nil) } @Test func `menu bar projection flags credits fallback on exhaustion`() { let store = self.makeStore(suite: "CodexConsumerProjectionTests-menu-bar") let now = Date(timeIntervalSince1970: 1_700_000_000) store._setSnapshotForTesting( UsageSnapshot( primary: RateWindow( usedPercent: 100, windowMinutes: 300, resetsAt: now.addingTimeInterval(1800), resetDescription: nil), secondary: RateWindow( usedPercent: 40, windowMinutes: 10080, resetsAt: now.addingTimeInterval(3600), resetDescription: nil), updatedAt: now), provider: .codex) store.credits = CreditsSnapshot(remaining: 80, events: [], updatedAt: now) let projection = store.codexConsumerProjection(surface: .menuBar, now: now) #expect(projection.menuBarFallback == .creditsBalance) } @Test func `live card projection keeps buy credits available without dashboard purchase URL`() { let store = self.makeStore(suite: "CodexConsumerProjectionTests-buy-credits") let now = Date(timeIntervalSince1970: 1_700_000_000) store._setSnapshotForTesting( UsageSnapshot( primary: RateWindow( usedPercent: 20, windowMinutes: 300, resetsAt: now.addingTimeInterval(1800), resetDescription: nil), secondary: nil, updatedAt: now), provider: .codex) store.credits = CreditsSnapshot(remaining: 42, events: [], updatedAt: now) store.openAIDashboardAttachmentAuthorized = false store.openAIDashboardRequiresLogin = false let projection = store.codexConsumerProjection(surface: .liveCard, now: now) #expect(projection.canShowBuyCredits) } @Test func `menu bar projection keeps credits fallback when credits load before usage`() { let store = self.makeStore(suite: "CodexConsumerProjectionTests-menu-bar-credits-only") let now = Date(timeIntervalSince1970: 1_700_000_000) store._setSnapshotForTesting(nil, provider: .codex) store.credits = CreditsSnapshot(remaining: 80, events: [], updatedAt: now) let projection = store.codexConsumerProjection(surface: .menuBar, now: now) #expect(projection.menuBarFallback == .creditsBalance) #expect(!projection.hasExhaustedRateLane) } private func makeStore(suite: String) -> UsageStore { let defaults = UserDefaults(suiteName: suite)! defaults.removePersistentDomain(forName: suite) let configStore = testConfigStore(suiteName: suite) let settings = 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()) return UsageStore( fetcher: UsageFetcher(environment: [:]), browserDetection: BrowserDetection(cacheTTL: 0), settings: settings, startupBehavior: .testing) }}
import CodexBarCore
import Foundation
import Testing
@testable import CodexBar
@MainActor
struct CodexConsumerProjectionTests {
@Test
func `live card projection compacts weekly lanes and attaches dashboard extras`() {
let store = self.makeStore(suite: "CodexConsumerProjectionTests-live-card")
let now = Date(timeIntervalSince1970: 1_700_000_000)
store._setSnapshotForTesting(
UsageSnapshot(
primary: nil,
secondary: RateWindow(
usedPercent: 25,
windowMinutes: 10080,
resetsAt: now.addingTimeInterval(3600),
resetDescription: nil),
updatedAt: now),
provider: .codex)
store.credits = CreditsSnapshot(remaining: 42, events: [], updatedAt: now)
store.openAIDashboard = OpenAIDashboardSnapshot(
signedInEmail: "codex@example.com",
codeReviewRemainingPercent: 88,
codeReviewLimit: RateWindow(
usedPercent: 12,
windowMinutes: nil,
resetsAt: now.addingTimeInterval(7200),
resetDescription: nil),
creditEvents: [],
dailyBreakdown: [OpenAIDashboardDailyBreakdown(day: "2024-01-01", services: [], totalCreditsUsed: 3)],
usageBreakdown: [OpenAIDashboardDailyBreakdown(day: "2024-01-01", services: [], totalCreditsUsed: 4)],
creditsPurchaseURL: "https://chatgpt.com/settings/billing",
updatedAt: now)
store.openAIDashboardAttachmentAuthorized = true
store.openAIDashboardRequiresLogin = false
let projection = store.codexConsumerProjection(surface: .liveCard, now: now)
#expect(projection.visibleRateLanes == [.weekly])
#expect(projection.planUtilizationLanes.map(\.role.rawValue) == ["weekly"])
#expect(projection.dashboardVisibility == .attached)
#expect(projection.supplementalMetrics == [.codeReview])
#expect(projection.remainingPercent(for: .codeReview) == 88)
#expect(projection.credits?.remaining == 42)
#expect(projection.canShowBuyCredits)
#expect(projection.hasUsageBreakdown)
#expect(projection.hasCreditsHistory)
}
@Test
func `display only dashboard stays visible without attached extras`() {
let store = self.makeStore(suite: "CodexConsumerProjectionTests-display-only")
let now = Date(timeIntervalSince1970: 1_700_000_000)
store._setSnapshotForTesting(
UsageSnapshot(
primary: RateWindow(
usedPercent: 15,
windowMinutes: 300,
resetsAt: now.addingTimeInterval(1800),
resetDescription: nil),
secondary: RateWindow(
usedPercent: 30,
windowMinutes: 10080,
resetsAt: now.addingTimeInterval(3600),
resetDescription: nil),
updatedAt: now),
provider: .codex)
store.openAIDashboard = OpenAIDashboardSnapshot(
signedInEmail: "codex@example.com",
codeReviewRemainingPercent: 66,
creditEvents: [],
dailyBreakdown: [OpenAIDashboardDailyBreakdown(day: "2024-01-01", services: [], totalCreditsUsed: 3)],
usageBreakdown: [OpenAIDashboardDailyBreakdown(day: "2024-01-01", services: [], totalCreditsUsed: 4)],
creditsPurchaseURL: "https://chatgpt.com/settings/billing",
updatedAt: now)
store.openAIDashboardAttachmentAuthorized = false
store.openAIDashboardRequiresLogin = false
let projection = store.codexConsumerProjection(surface: .liveCard, now: now)
#expect(projection.dashboardVisibility == .displayOnly)
#expect(projection.supplementalMetrics.isEmpty)
#expect(projection.canShowBuyCredits)
#expect(!projection.hasUsageBreakdown)
#expect(!projection.hasCreditsHistory)
}
@Test
func `override card projection does not pull live codex adjuncts`() {
let store = self.makeStore(suite: "CodexConsumerProjectionTests-override")
let now = Date(timeIntervalSince1970: 1_700_000_000)
store._setSnapshotForTesting(
UsageSnapshot(
primary: RateWindow(
usedPercent: 18,
windowMinutes: 300,
resetsAt: now.addingTimeInterval(1800),
resetDescription: nil),
secondary: nil,
updatedAt: now),
provider: .codex)
store.credits = CreditsSnapshot(remaining: 42, events: [], updatedAt: now)
store.lastCreditsError = "Frame load interrupted"
store.openAIDashboard = OpenAIDashboardSnapshot(
signedInEmail: "codex@example.com",
codeReviewRemainingPercent: 88,
creditEvents: [],
dailyBreakdown: [OpenAIDashboardDailyBreakdown(day: "2024-01-01", services: [], totalCreditsUsed: 3)],
usageBreakdown: [OpenAIDashboardDailyBreakdown(day: "2024-01-01", services: [], totalCreditsUsed: 4)],
creditsPurchaseURL: "https://chatgpt.com/settings/billing",
updatedAt: now)
store.openAIDashboardAttachmentAuthorized = true
store.openAIDashboardRequiresLogin = false
store._setErrorForTesting("Live codex error", provider: .codex)
let overrideSnapshot = UsageSnapshot(
primary: RateWindow(
usedPercent: 55,
windowMinutes: 300,
resetsAt: now.addingTimeInterval(1200),
resetDescription: nil),
secondary: nil,
updatedAt: now)
let projection = store.codexConsumerProjection(
surface: .overrideCard,
snapshotOverride: overrideSnapshot,
errorOverride: "Override error",
now: now)
#expect(projection.visibleRateLanes == [.session])
#expect(projection.dashboardVisibility == .hidden)
#expect(projection.credits == nil)
#expect(projection.supplementalMetrics.isEmpty)
#expect(!projection.canShowBuyCredits)
#expect(!projection.hasUsageBreakdown)
#expect(!projection.hasCreditsHistory)
#expect(projection.userFacingErrors.usage == "Override error")
#expect(projection.userFacingErrors.credits == nil)
#expect(projection.userFacingErrors.dashboard == nil)
}
@Test
func `menu bar projection flags credits fallback on exhaustion`() {
let store = self.makeStore(suite: "CodexConsumerProjectionTests-menu-bar")
let now = Date(timeIntervalSince1970: 1_700_000_000)
store._setSnapshotForTesting(
UsageSnapshot(
primary: RateWindow(
usedPercent: 100,
windowMinutes: 300,
resetsAt: now.addingTimeInterval(1800),
resetDescription: nil),
secondary: RateWindow(
usedPercent: 40,
windowMinutes: 10080,
resetsAt: now.addingTimeInterval(3600),
resetDescription: nil),
updatedAt: now),
provider: .codex)
store.credits = CreditsSnapshot(remaining: 80, events: [], updatedAt: now)
let projection = store.codexConsumerProjection(surface: .menuBar, now: now)
#expect(projection.menuBarFallback == .creditsBalance)
}
@Test
func `live card projection keeps buy credits available without dashboard purchase URL`() {
let store = self.makeStore(suite: "CodexConsumerProjectionTests-buy-credits")
let now = Date(timeIntervalSince1970: 1_700_000_000)
store._setSnapshotForTesting(
UsageSnapshot(
primary: RateWindow(
usedPercent: 20,
windowMinutes: 300,
resetsAt: now.addingTimeInterval(1800),
resetDescription: nil),
secondary: nil,
updatedAt: now),
provider: .codex)
store.credits = CreditsSnapshot(remaining: 42, events: [], updatedAt: now)
store.openAIDashboardAttachmentAuthorized = false
store.openAIDashboardRequiresLogin = false
let projection = store.codexConsumerProjection(surface: .liveCard, now: now)
#expect(projection.canShowBuyCredits)
}
@Test
func `menu bar projection keeps credits fallback when credits load before usage`() {
let store = self.makeStore(suite: "CodexConsumerProjectionTests-menu-bar-credits-only")
let now = Date(timeIntervalSince1970: 1_700_000_000)
store._setSnapshotForTesting(nil, provider: .codex)
store.credits = CreditsSnapshot(remaining: 80, events: [], updatedAt: now)
let projection = store.codexConsumerProjection(surface: .menuBar, now: now)
#expect(projection.menuBarFallback == .creditsBalance)
#expect(!projection.hasExhaustedRateLane)
}
private func makeStore(suite: String) -> UsageStore {
let defaults = UserDefaults(suiteName: suite)!
defaults.removePersistentDomain(forName: suite)
let configStore = testConfigStore(suiteName: suite)
let settings = 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())
return UsageStore(
fetcher: UsageFetcher(environment: [:]),
browserDetection: BrowserDetection(cacheTTL: 0),
settings: settings,
startupBehavior: .testing)
}
}