Files
steipete--codexbar/Tests/CodexBarTests/CodexConsumerProjectionTests.swift
2026-07-13 12:22:33 +08:00

503 lines
21 KiB
Swift

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)
}
@Test
func `projection prefers monthly credit limit remaining over zero balance`() {
let store = self.makeStore(suite: "CodexConsumerProjectionTests-monthly-credit-limit")
let now = Date(timeIntervalSince1970: 1_700_000_000)
store._setSnapshotForTesting(nil, provider: .codex)
store.credits = CreditsSnapshot(
remaining: 0,
events: [],
updatedAt: now,
codexCreditLimit: CodexCreditLimitSnapshot(
used: 7761,
limit: 100_000,
remainingPercent: 92.239,
resetsAt: nil,
updatedAt: now))
let projection = store.codexConsumerProjection(surface: .widget, now: now)
#expect(projection.credits?.remaining == 92239)
}
@Test
func `exhausted weekly lane caps session display until weekly reset`() throws {
let store = self.makeStore(suite: "CodexConsumerProjectionTests-weekly-caps-session")
let now = Date(timeIntervalSince1970: 1_800_000_000)
let sessionReset = now.addingTimeInterval(3 * 3600)
let weeklyReset = now.addingTimeInterval(4 * 24 * 3600)
store._setSnapshotForTesting(
UsageSnapshot(
primary: RateWindow(
usedPercent: 1,
windowMinutes: 300,
resetsAt: sessionReset,
resetDescription: nil),
secondary: RateWindow(
usedPercent: 157,
windowMinutes: 10080,
resetsAt: weeklyReset,
resetDescription: nil),
updatedAt: now),
provider: .codex)
let projection = store.codexConsumerProjection(surface: .liveCard, now: now)
let session = try #require(projection.rateWindow(for: .session))
let weekly = try #require(projection.rateWindow(for: .weekly))
#expect(session.remainingPercent == 0)
#expect(session.resetsAt == weeklyReset)
#expect(weekly.remainingPercent == 0)
#expect(weekly.resetsAt == weeklyReset)
#expect(projection.planUtilizationLanes.first?.window.usedPercent == 1)
}
@Test
func `exhausted weekly lane retargets session reset when session is also exhausted`() throws {
let store = self.makeStore(suite: "CodexConsumerProjectionTests-weekly-caps-both-exhausted")
let now = Date(timeIntervalSince1970: 1_800_000_000)
let sessionReset = now.addingTimeInterval(42 * 60)
let weeklyReset = now.addingTimeInterval(4 * 24 * 3600)
store._setSnapshotForTesting(
UsageSnapshot(
primary: RateWindow(
usedPercent: 100,
windowMinutes: 300,
resetsAt: sessionReset,
resetDescription: nil),
secondary: RateWindow(
usedPercent: 157,
windowMinutes: 10080,
resetsAt: weeklyReset,
resetDescription: nil),
updatedAt: now),
provider: .codex)
let projection = store.codexConsumerProjection(surface: .liveCard, now: now)
let session = try #require(projection.rateWindow(for: .session))
#expect(session.remainingPercent == 0)
#expect(session.resetsAt == weeklyReset)
#expect(session.resetsAt != sessionReset)
}
@Test
func `both exhausted lanes use the later session reset`() throws {
let store = self.makeStore(suite: "CodexConsumerProjectionTests-session-reset-binds-later")
let now = Date(timeIntervalSince1970: 1_800_000_000)
let weeklyReset = now.addingTimeInterval(60 * 60)
let sessionReset = now.addingTimeInterval(4 * 60 * 60)
store._setSnapshotForTesting(
UsageSnapshot(
primary: RateWindow(
usedPercent: 100,
windowMinutes: 300,
resetsAt: sessionReset,
resetDescription: "session reset"),
secondary: RateWindow(
usedPercent: 100,
windowMinutes: 10080,
resetsAt: weeklyReset,
resetDescription: "weekly reset"),
updatedAt: now),
provider: .codex)
let projection = store.codexConsumerProjection(surface: .liveCard, now: now)
let session = try #require(projection.rateWindow(for: .session))
#expect(session.remainingPercent == 0)
#expect(session.resetsAt == sessionReset)
#expect(session.resetDescription == "session reset")
}
@Test
func `both exhausted lanes keep effective reset unknown when session reset is unknown`() throws {
let store = self.makeStore(suite: "CodexConsumerProjectionTests-session-reset-unknown")
let now = Date(timeIntervalSince1970: 1_800_000_000)
store._setSnapshotForTesting(
UsageSnapshot(
primary: RateWindow(
usedPercent: 100,
windowMinutes: 300,
resetsAt: nil,
resetDescription: nil),
secondary: RateWindow(
usedPercent: 100,
windowMinutes: 10080,
resetsAt: now.addingTimeInterval(60 * 60),
resetDescription: "weekly reset"),
updatedAt: now),
provider: .codex)
let projection = store.codexConsumerProjection(surface: .liveCard, now: now)
let session = try #require(projection.rateWindow(for: .session))
#expect(session.remainingPercent == 0)
#expect(session.resetsAt == nil)
#expect(session.resetDescription == nil)
}
@Test
func `exhausted weekly lane leaves session reset unknown when weekly reset is unknown`() throws {
let store = self.makeStore(suite: "CodexConsumerProjectionTests-weekly-caps-unknown-reset")
let now = Date(timeIntervalSince1970: 1_800_000_000)
let sessionReset = now.addingTimeInterval(42 * 60)
store._setSnapshotForTesting(
UsageSnapshot(
primary: RateWindow(
usedPercent: 1,
windowMinutes: 300,
resetsAt: sessionReset,
resetDescription: "in 42m"),
secondary: RateWindow(
usedPercent: 100,
windowMinutes: 10080,
resetsAt: nil,
resetDescription: nil),
updatedAt: now),
provider: .codex)
let projection = store.codexConsumerProjection(surface: .liveCard, now: now)
let session = try #require(projection.rateWindow(for: .session))
#expect(session.remainingPercent == 0)
#expect(session.resetsAt == nil)
#expect(session.resetDescription == nil)
}
@Test
func `weekly cap lifts after weekly reset even with stale snapshot timestamp`() throws {
let store = self.makeStore(suite: "CodexConsumerProjectionTests-weekly-cap-stale-snapshot")
let now = Date(timeIntervalSince1970: 1_800_000_000)
let snapshotCapturedAt = now.addingTimeInterval(-2 * 3600)
let sessionReset = now.addingTimeInterval(3 * 3600)
let weeklyReset = now.addingTimeInterval(-3600)
store._setSnapshotForTesting(
UsageSnapshot(
primary: RateWindow(
usedPercent: 1,
windowMinutes: 300,
resetsAt: sessionReset,
resetDescription: nil),
secondary: RateWindow(
usedPercent: 100,
windowMinutes: 10080,
resetsAt: weeklyReset,
resetDescription: nil),
updatedAt: snapshotCapturedAt),
provider: .codex)
store.credits = CreditsSnapshot(remaining: 80, events: [], updatedAt: now)
let projection = store.codexConsumerProjection(surface: .menuBar, now: now)
let session = try #require(projection.rateWindow(for: .session))
#expect(session.remainingPercent == 99)
#expect(session.resetsAt == sessionReset)
#expect(projection.menuBarFallback == .none)
}
@Test
func `weekly cap does not alter session display when weekly has reset`() throws {
let store = self.makeStore(suite: "CodexConsumerProjectionTests-weekly-reset-session-uncapped")
let now = Date(timeIntervalSince1970: 1_800_000_000)
let sessionReset = now.addingTimeInterval(3 * 3600)
let weeklyReset = now.addingTimeInterval(-3600)
store._setSnapshotForTesting(
UsageSnapshot(
primary: RateWindow(
usedPercent: 1,
windowMinutes: 300,
resetsAt: sessionReset,
resetDescription: nil),
secondary: RateWindow(
usedPercent: 100,
windowMinutes: 10080,
resetsAt: weeklyReset,
resetDescription: nil),
updatedAt: now),
provider: .codex)
let projection = store.codexConsumerProjection(surface: .liveCard, now: now)
let session = try #require(projection.rateWindow(for: .session))
#expect(session.remainingPercent == 99)
#expect(session.resetsAt == sessionReset)
}
@Test
func `weekly cap lifts at the weekly reset boundary`() throws {
let store = self.makeStore(suite: "CodexConsumerProjectionTests-weekly-reset-boundary")
let now = Date(timeIntervalSince1970: 1_800_000_000)
let sessionReset = now.addingTimeInterval(3 * 3600)
store._setSnapshotForTesting(
UsageSnapshot(
primary: RateWindow(
usedPercent: 1,
windowMinutes: 300,
resetsAt: sessionReset,
resetDescription: nil),
secondary: RateWindow(
usedPercent: 100,
windowMinutes: 10080,
resetsAt: now,
resetDescription: nil),
updatedAt: now.addingTimeInterval(-3600)),
provider: .codex)
let projection = store.codexConsumerProjection(surface: .liveCard, now: now)
let session = try #require(projection.rateWindow(for: .session))
#expect(session.remainingPercent == 99)
#expect(session.resetsAt == sessionReset)
}
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)
}
}