import CodexBarCore import Foundation import Testing @testable import CodexBar extension HistoricalUsagePaceTests { @MainActor @Test func `backfill skips when dashboard authority is display only`() async throws { let store = try Self.makeUsageStoreForBackfillTests( suite: "HistoricalUsagePaceTests-backfill-display-only", historyFileURL: Self.makeTempURL()) store._setCodexHistoricalDatasetForTesting(nil) let snapshotNow = Date(timeIntervalSince1970: 1_770_000_000) let dashboard = OpenAIDashboardSnapshot( signedInEmail: "shared@example.com", codeReviewRemainingPercent: nil, creditEvents: [], dailyBreakdown: [], usageBreakdown: Self.syntheticBreakdown(endingAt: snapshotNow, days: 35, dailyCredits: 10), creditsPurchaseURL: nil, primaryLimit: nil, secondaryLimit: RateWindow( usedPercent: 50, windowMinutes: 10080, resetsAt: snapshotNow.addingTimeInterval(2 * 24 * 60 * 60), resetDescription: nil), creditsRemaining: nil, accountPlan: nil, updatedAt: snapshotNow) store.backfillCodexHistoricalFromDashboardIfNeeded( dashboard, authorityDecision: CodexDashboardAuthorityDecision( disposition: .displayOnly, reason: .sameEmailAmbiguity(email: "shared@example.com"), allowedEffects: [], cleanup: Set(CodexDashboardCleanup.allCases)), attachedAccountEmail: "shared@example.com") try await Task.sleep(for: .milliseconds(250)) #expect(store.codexHistoricalDataset == nil) } @MainActor @Test func `backfill skips when dashboard authority fail closes`() async throws { let store = try Self.makeUsageStoreForBackfillTests( suite: "HistoricalUsagePaceTests-backfill-fail-closed", historyFileURL: Self.makeTempURL()) store._setCodexHistoricalDatasetForTesting(nil) let snapshotNow = Date(timeIntervalSince1970: 1_770_000_000) let dashboard = OpenAIDashboardSnapshot( signedInEmail: "wrong@example.com", codeReviewRemainingPercent: nil, creditEvents: [], dailyBreakdown: [], usageBreakdown: Self.syntheticBreakdown(endingAt: snapshotNow, days: 35, dailyCredits: 10), creditsPurchaseURL: nil, primaryLimit: nil, secondaryLimit: RateWindow( usedPercent: 50, windowMinutes: 10080, resetsAt: snapshotNow.addingTimeInterval(2 * 24 * 60 * 60), resetDescription: nil), creditsRemaining: nil, accountPlan: nil, updatedAt: snapshotNow) store.backfillCodexHistoricalFromDashboardIfNeeded( dashboard, authorityDecision: CodexDashboardAuthorityDecision( disposition: .failClosed, reason: .wrongEmail(expected: "expected@example.com", actual: "wrong@example.com"), allowedEffects: [], cleanup: Set(CodexDashboardCleanup.allCases)), attachedAccountEmail: "expected@example.com") try await Task.sleep(for: .milliseconds(250)) #expect(store.codexHistoricalDataset == nil) } @MainActor @Test func `backfill uses dashboard secondary when available`() async throws { let store = try Self.makeUsageStoreForBackfillTests( suite: "HistoricalUsagePaceTests-backfill-dashboard-secondary", historyFileURL: Self.makeTempURL()) store._setCodexHistoricalDatasetForTesting(nil) let snapshotNow = Date(timeIntervalSince1970: 1_770_000_000) let staleSnapshot = UsageSnapshot( primary: nil, secondary: RateWindow( usedPercent: 5, windowMinutes: 10080, resetsAt: snapshotNow.addingTimeInterval(2 * 24 * 60 * 60), resetDescription: nil), tertiary: nil, providerCost: nil, updatedAt: snapshotNow.addingTimeInterval(-30 * 60), identity: nil) store._setSnapshotForTesting(staleSnapshot, provider: .codex) let dashboard = OpenAIDashboardSnapshot( signedInEmail: nil, codeReviewRemainingPercent: nil, creditEvents: [], dailyBreakdown: [], usageBreakdown: Self.syntheticBreakdown(endingAt: snapshotNow, days: 35, dailyCredits: 10), creditsPurchaseURL: nil, primaryLimit: nil, secondaryLimit: RateWindow( usedPercent: 50, windowMinutes: 10080, resetsAt: snapshotNow.addingTimeInterval(2 * 24 * 60 * 60), resetDescription: nil), creditsRemaining: nil, accountPlan: nil, updatedAt: snapshotNow) store.backfillCodexHistoricalFromDashboardIfNeeded( dashboard, authorityDecision: CodexDashboardAuthorityDecision( disposition: .attach, reason: .trustedEmailMatchNoCompetingOwner, allowedEffects: [.historicalBackfill], cleanup: []), attachedAccountEmail: "attached@example.com") for _ in 0..<40 { if (store.codexHistoricalDataset?.weeks.count ?? 0) >= 3 { break } try await Task.sleep(for: .milliseconds(50)) } #expect((store.codexHistoricalDataset?.weeks.count ?? 0) >= 3) } @MainActor @Test func `backfill uses normalized dashboard weekly when only primary window is weekly`() async throws { let store = try Self.makeUsageStoreForBackfillTests( suite: "HistoricalUsagePaceTests-backfill-normalized-dashboard-weekly", historyFileURL: Self.makeTempURL()) store._setCodexHistoricalDatasetForTesting(nil) let snapshotNow = Date(timeIntervalSince1970: 1_770_000_000) let seededSnapshot = UsageSnapshot( primary: RateWindow( usedPercent: 22, windowMinutes: 300, resetsAt: snapshotNow.addingTimeInterval(2 * 60 * 60), resetDescription: nil), secondary: nil, tertiary: nil, updatedAt: snapshotNow, identity: nil) store._setSnapshotForTesting(seededSnapshot, provider: .codex) let dashboard = OpenAIDashboardSnapshot( signedInEmail: nil, codeReviewRemainingPercent: nil, creditEvents: [], dailyBreakdown: [], usageBreakdown: Self.syntheticBreakdown(endingAt: snapshotNow, days: 35, dailyCredits: 10), creditsPurchaseURL: nil, primaryLimit: RateWindow( usedPercent: 50, windowMinutes: 10080, resetsAt: snapshotNow.addingTimeInterval(2 * 24 * 60 * 60), resetDescription: nil), secondaryLimit: nil, creditsRemaining: nil, accountPlan: nil, updatedAt: snapshotNow) store.backfillCodexHistoricalFromDashboardIfNeeded( dashboard, authorityDecision: CodexDashboardAuthorityDecision( disposition: .attach, reason: .trustedEmailMatchNoCompetingOwner, allowedEffects: [.historicalBackfill], cleanup: []), attachedAccountEmail: "attached@example.com") for _ in 0..<40 { if (store.codexHistoricalDataset?.weeks.count ?? 0) >= 3 { break } try await Task.sleep(for: .milliseconds(50)) } #expect((store.codexHistoricalDataset?.weeks.count ?? 0) >= 3) } @MainActor @Test func `backfill uses attached account email from authority instead of dashboard email`() async throws { let store = try Self.makeUsageStoreForBackfillTests( suite: "HistoricalUsagePaceTests-backfill-attached-email", historyFileURL: Self.makeTempURL()) let isolatedHome = FileManager.default.temporaryDirectory .appendingPathComponent("codex-historical-attached-email-\(UUID().uuidString)", isDirectory: true) try FileManager.default.createDirectory(at: isolatedHome, withIntermediateDirectories: true) store.settings._test_liveSystemCodexAccount = nil store.settings._test_codexReconciliationEnvironment = ["CODEX_HOME": isolatedHome.path] defer { store.settings._test_codexReconciliationEnvironment = nil try? FileManager.default.removeItem(at: isolatedHome) } store._setCodexHistoricalDatasetForTesting(nil) let snapshotNow = Date(timeIntervalSince1970: 1_770_000_000) let dashboard = OpenAIDashboardSnapshot( signedInEmail: "dashboard@example.com", codeReviewRemainingPercent: nil, creditEvents: [], dailyBreakdown: [], usageBreakdown: Self.syntheticBreakdown(endingAt: snapshotNow, days: 35, dailyCredits: 10), creditsPurchaseURL: nil, primaryLimit: nil, secondaryLimit: RateWindow( usedPercent: 50, windowMinutes: 10080, resetsAt: snapshotNow.addingTimeInterval(2 * 24 * 60 * 60), resetDescription: nil), creditsRemaining: nil, accountPlan: nil, updatedAt: snapshotNow) store.backfillCodexHistoricalFromDashboardIfNeeded( dashboard, authorityDecision: CodexDashboardAuthorityDecision( disposition: .attach, reason: .trustedEmailMatchNoCompetingOwner, allowedEffects: [.historicalBackfill], cleanup: []), attachedAccountEmail: "attached@example.com") for _ in 0..<40 { if store.codexHistoricalDatasetAccountKey != nil { break } try await Task.sleep(for: .milliseconds(50)) } #expect( store.codexHistoricalDatasetAccountKey == CodexHistoryOwnership.canonicalEmailHashKey(for: "attached@example.com")) #expect( store.codexHistoricalDatasetAccountKey != CodexHistoryOwnership.canonicalEmailHashKey(for: "dashboard@example.com")) } @Test func `will last decision uses smoothed probability when risk hidden`() throws { let now = Date(timeIntervalSince1970: 0) let windowMinutes = 10080 let duration = TimeInterval(windowMinutes) * 60 let currentResetsAt = now.addingTimeInterval(duration / 2) let window = RateWindow( usedPercent: 50, windowMinutes: windowMinutes, resetsAt: currentResetsAt, resetDescription: nil) let weeks = (0..<4).map { index in HistoricalWeekProfile( resetsAt: currentResetsAt.addingTimeInterval(-duration * Double(index + 1)), windowMinutes: windowMinutes, curve: Self.linearCurve(end: 100)) } let pace = try #require(CodexHistoricalPaceEvaluator.evaluate( window: window, now: now, dataset: CodexHistoricalDataset(weeks: weeks))) #expect(pace.runOutProbability == nil) let totalWeight = weeks.enumerated().reduce(0.0) { partial, element in let ageWeeks = currentResetsAt.timeIntervalSince(element.element.resetsAt) / duration return partial + exp(-ageWeeks / 3.0) } let smoothedProbability = (totalWeight + 0.5) / (totalWeight + 1.0) #expect(pace.willLastToReset == (smoothedProbability < 0.5)) } @MainActor @Test func `usage store falls back to linear when history disabled or insufficient`() throws { let suite = "HistoricalUsagePaceTests-usage-store" let defaults = try #require(UserDefaults(suiteName: suite)) defaults.removePersistentDomain(forName: suite) let settings = SettingsStore( userDefaults: defaults, configStore: testConfigStore(suiteName: suite), 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()) settings.historicalTrackingEnabled = true settings.weeklyProgressWorkDays = 5 let planHistoryStore = testPlanUtilizationHistoryStore( suiteName: "HistoricalUsagePaceTests-\(UUID().uuidString)") let store = UsageStore( fetcher: UsageFetcher(environment: [:]), browserDetection: BrowserDetection(cacheTTL: 0), settings: settings, historicalUsageHistoryStore: HistoricalUsageHistoryStore(fileURL: Self.makeTempURL()), planUtilizationHistoryStore: planHistoryStore) store._cancelPlanUtilizationHistoryLoadForTesting() let now = Date(timeIntervalSince1970: 0) let window = RateWindow( usedPercent: 50, windowMinutes: 10080, resetsAt: now.addingTimeInterval(4 * 24 * 60 * 60), resetDescription: nil) let twoWeeksDataset = CodexHistoricalDataset(weeks: [ HistoricalWeekProfile( resetsAt: now.addingTimeInterval(-7 * 24 * 60 * 60), windowMinutes: 10080, curve: Self.linearCurve(end: 100)), HistoricalWeekProfile( resetsAt: now.addingTimeInterval(-14 * 24 * 60 * 60), windowMinutes: 10080, curve: Self.linearCurve(end: 100)), ]) store._setCodexHistoricalDatasetForTesting(twoWeeksDataset) let computed = store.weeklyPace(provider: .codex, window: window, now: now) let workdayAware = UsagePace.weekly( window: window, now: now, defaultWindowMinutes: 10080, workDays: 5) #expect(computed != nil) #expect(abs((computed?.deltaPercent ?? 0) - (workdayAware?.deltaPercent ?? 0)) < 0.001) } @MainActor @Test func `usage store preserves historical Codex pace when work days are off`() throws { let suite = "HistoricalUsagePaceTests-workdays-off-preserve-history-\(UUID().uuidString)" let store = try Self.makeUsageStoreForBackfillTests( suite: suite, historyFileURL: Self.makeTempURL()) store.settings.weeklyProgressWorkDays = nil let now = Date(timeIntervalSince1970: 0) let duration = TimeInterval(10080 * 60) let resetsAt = now.addingTimeInterval(duration / 2) let window = RateWindow( usedPercent: 60, windowMinutes: 10080, resetsAt: resetsAt, resetDescription: nil) let dataset = CodexHistoricalDataset(weeks: (0..<4).map { index in HistoricalWeekProfile( resetsAt: resetsAt.addingTimeInterval(-duration * Double(index + 1)), windowMinutes: 10080, curve: Self.linearCurve(end: 80)) }) let expected = try #require(CodexHistoricalPaceEvaluator.evaluate( window: window, now: now, dataset: dataset)) let linear = try #require(UsagePace.weekly(window: window, now: now, workDays: nil)) store._setCodexHistoricalDatasetForTesting( dataset, accountKey: store.codexOwnershipContext().canonicalKey) let computed = try #require(store.weeklyPace(provider: .codex, window: window, now: now)) #expect(abs(expected.expectedUsedPercent - linear.expectedUsedPercent) > 0.001) #expect(abs(computed.expectedUsedPercent - expected.expectedUsedPercent) < 0.001) #expect(abs(computed.deltaPercent - expected.deltaPercent) < 0.001) #expect(computed.etaSeconds == expected.etaSeconds) #expect(computed.willLastToReset == expected.willLastToReset) #expect(computed.runOutProbability == expected.runOutProbability) } @MainActor @Test func `usage store preserves historical Codex pace when work days are configured`() throws { let suite = "HistoricalUsagePaceTests-workdays-preserve-history-\(UUID().uuidString)" let store = try Self.makeUsageStoreForBackfillTests( suite: suite, historyFileURL: Self.makeTempURL()) store.settings.weeklyProgressWorkDays = 5 let now = Date(timeIntervalSince1970: 0) let duration = TimeInterval(10080 * 60) let resetsAt = now.addingTimeInterval(duration / 2) let window = RateWindow( usedPercent: 60, windowMinutes: 10080, resetsAt: resetsAt, resetDescription: nil) let weeks = (0..<4).map { index in HistoricalWeekProfile( resetsAt: resetsAt.addingTimeInterval(-duration * Double(index + 1)), windowMinutes: 10080, curve: Self.linearCurve(end: 80)) } let dataset = CodexHistoricalDataset(weeks: weeks) let expected = try #require(CodexHistoricalPaceEvaluator.evaluate( window: window, now: now, dataset: dataset)) store._setCodexHistoricalDatasetForTesting( dataset, accountKey: store.codexOwnershipContext().canonicalKey) let computed = try #require(store.weeklyPace(provider: .codex, window: window, now: now)) #expect(abs(computed.expectedUsedPercent - expected.expectedUsedPercent) < 0.001) #expect(abs(computed.deltaPercent - expected.deltaPercent) < 0.001) #expect(computed.etaSeconds == expected.etaSeconds) #expect(computed.willLastToReset == expected.willLastToReset) #expect(computed.runOutProbability == expected.runOutProbability) } @MainActor @Test func `usage store computes linear pace for providers with quota windows`() throws { let suite = "HistoricalUsagePaceTests-generic-provider-\(UUID().uuidString)" let store = try Self.makeUsageStoreForBackfillTests( suite: suite, historyFileURL: Self.makeTempURL()) let now = Date(timeIntervalSince1970: 0) let window = RateWindow( usedPercent: 40, windowMinutes: 10080, resetsAt: now.addingTimeInterval(4 * 24 * 60 * 60), resetDescription: nil) let pace = store.weeklyPace(provider: .zai, window: window, now: now) #expect(pace != nil) #expect(abs((pace?.deltaPercent ?? 0) - (40 - (3.0 / 7.0 * 100.0))) < 0.001) } @MainActor @Test func `usage store applies configured work days to generic weekly pace`() throws { let suite = "HistoricalUsagePaceTests-generic-workdays-\(UUID().uuidString)" let store = try Self.makeUsageStoreForBackfillTests( suite: suite, historyFileURL: Self.makeTempURL()) store.settings.weeklyProgressWorkDays = 5 var calendar = Calendar(identifier: .gregorian) calendar.timeZone = .current let resetsAt = try #require(calendar.date(from: DateComponents( calendar: calendar, timeZone: calendar.timeZone, year: 2026, month: 6, day: 14))) let now = try #require(calendar.date(from: DateComponents( calendar: calendar, timeZone: calendar.timeZone, year: 2026, month: 6, day: 11))) let window = RateWindow( usedPercent: 60, windowMinutes: 10080, resetsAt: resetsAt, resetDescription: nil) let pace = try #require(store.weeklyPace(provider: .zai, window: window, now: now)) #expect(abs(pace.expectedUsedPercent - 60) < 0.001) #expect(abs(pace.deltaPercent) < 0.001) } @MainActor @Test func `usage store returns nil pace when generic window lacks explicit duration`() throws { let suite = "HistoricalUsagePaceTests-no-window-minutes-\(UUID().uuidString)" let store = try Self.makeUsageStoreForBackfillTests( suite: suite, historyFileURL: Self.makeTempURL()) let now = Date(timeIntervalSince1970: 0) let window = RateWindow( usedPercent: 40, windowMinutes: nil, resetsAt: now.addingTimeInterval(4 * 24 * 60 * 60), resetDescription: nil) let pace = store.weeklyPace(provider: .factory, window: window, now: now) #expect(pace == nil) } }