import CodexBarCoreimport Foundationimport Testing@testable import CodexBar @Suite(.serialized)@MainActorstruct CodexAccountScopedRefreshTests { @Test func `account transition invalidates codex scoped state and preserves token usage`() async { let settings = self.makeSettingsStore(suite: "CodexAccountScopedRefreshTests-invalidate") settings.refreshFrequency = .manual settings.codexCookieSource = .auto settings._test_liveSystemCodexAccount = self.liveAccount(email: "alpha@example.com") let store = self.makeUsageStore(settings: settings) let staleSnapshot = self.codexSnapshot(email: "alpha@example.com", usedPercent: 10) let staleCredits = self.credits(remaining: 42) let staleDashboard = self.dashboard(email: "alpha@example.com", creditsRemaining: 42, usedPercent: 20) let tokenSnapshot = CostUsageTokenSnapshot( sessionTokens: 120, sessionCostUSD: 1.2, last30DaysTokens: 900, last30DaysCostUSD: 9.0, daily: [], updatedAt: Date()) var widgetSnapshots: [WidgetSnapshot] = [] store._setSnapshotForTesting(staleSnapshot, provider: .codex) store.credits = staleCredits store.lastCreditsSnapshot = staleCredits store.lastCreditsSnapshotAccountKey = "alpha@example.com" store.lastCreditsSource = .api store.openAIDashboard = staleDashboard store.lastOpenAIDashboardSnapshot = staleDashboard store.lastOpenAIDashboardTargetEmail = "alpha@example.com" store._setTokenSnapshotForTesting(tokenSnapshot, provider: .codex) store.lastCodexAccountScopedRefreshGuard = store .currentCodexAccountScopedRefreshGuard(preferCurrentSnapshot: false) store._test_widgetSnapshotSaveOverride = { widgetSnapshots.append($0) } defer { store._test_widgetSnapshotSaveOverride = nil } settings._test_liveSystemCodexAccount = self.liveAccount(email: "beta@example.com") let didInvalidate = store.prepareCodexAccountScopedRefreshIfNeeded() await store.widgetSnapshotPersistTask?.value #expect(didInvalidate) #expect(store.snapshots[.codex] == nil) #expect(store.credits == nil) #expect(store.lastCreditsSnapshot == nil) #expect(store.lastCreditsSnapshotAccountKey == nil) #expect(store.lastCreditsSource == .none) #expect(store.openAIDashboard == nil) #expect(store.lastOpenAIDashboardSnapshot == nil) #expect(store.tokenSnapshots[.codex] == tokenSnapshot) #expect(widgetSnapshots.count == 1) #expect(widgetSnapshots[0].entries.contains(where: { $0.provider == .codex }) == false) } @Test func `first switch invalidates after codex refresh seeds the previous account guard`() async { let settings = self.makeSettingsStore(suite: "CodexAccountScopedRefreshTests-first-switch") settings.refreshFrequency = .manual settings.codexCookieSource = .off settings._test_liveSystemCodexAccount = self.liveAccount(email: "alpha@example.com") let store = self.makeUsageStore(settings: settings) self.installImmediateCodexProvider( on: store, snapshot: self.codexSnapshot(email: "alpha@example.com", usedPercent: 10)) await store.refreshProvider(.codex, allowDisabled: true) #expect(store.lastCodexAccountScopedRefreshGuard?.accountKey == "alpha@example.com") settings._test_liveSystemCodexAccount = self.liveAccount(email: "beta@example.com") let didInvalidate = store.prepareCodexAccountScopedRefreshIfNeeded() #expect(didInvalidate) #expect(store.snapshots[.codex] == nil) } @Test func `stale codex usage success is discarded after account switch`() async { let settings = self.makeSettingsStore(suite: "CodexAccountScopedRefreshTests-stale-success") settings.refreshFrequency = .manual settings._test_liveSystemCodexAccount = self.liveAccount(email: "alpha@example.com") let store = self.makeUsageStore(settings: settings) let blocker = BlockingCodexFetchStrategy() self.installBlockingCodexProvider(on: store, blocker: blocker) let refreshTask = Task { await store.refreshProvider(.codex, allowDisabled: true) } await blocker.waitUntilStarted() settings._test_liveSystemCodexAccount = self.liveAccount(email: "beta@example.com") await blocker.resume(with: .success(self.codexSnapshot(email: "alpha@example.com", usedPercent: 25))) await refreshTask.value #expect(store.snapshots[.codex] == nil) #expect(store.errors[.codex] == nil) } @Test func `same email provider account switch discards stale codex usage success`() async { let settings = self.makeSettingsStore(suite: "CodexAccountScopedRefreshTests-stale-same-email-provider-account") settings.refreshFrequency = .manual settings._test_liveSystemCodexAccount = self.liveAccount( email: "alpha@example.com", identity: .providerAccount(id: "acct-alpha")) let store = self.makeUsageStore(settings: settings) let blocker = BlockingCodexFetchStrategy() self.installBlockingCodexProvider(on: store, blocker: blocker) let refreshTask = Task { await store.refreshProvider(.codex, allowDisabled: true) } await blocker.waitUntilStarted() settings._test_liveSystemCodexAccount = self.liveAccount( email: "alpha@example.com", identity: .providerAccount(id: "acct-beta")) await blocker.resume(with: .success(self.codexSnapshot(email: "alpha@example.com", usedPercent: 25))) await refreshTask.value #expect(store.snapshots[.codex] == nil) #expect(store.errors[.codex] == nil) } @Test func `stale codex usage failure does not clear newer account snapshot`() async { let settings = self.makeSettingsStore(suite: "CodexAccountScopedRefreshTests-stale-failure") settings.refreshFrequency = .manual settings._test_liveSystemCodexAccount = self.liveAccount(email: "alpha@example.com") let store = self.makeUsageStore(settings: settings) let blocker = BlockingCodexFetchStrategy() self.installBlockingCodexProvider(on: store, blocker: blocker) let refreshTask = Task { await store.refreshProvider(.codex, allowDisabled: true) } await blocker.waitUntilStarted() settings._test_liveSystemCodexAccount = self.liveAccount(email: "beta@example.com") let freshSnapshot = self.codexSnapshot(email: "beta@example.com", usedPercent: 5) store._setSnapshotForTesting(freshSnapshot, provider: .codex) await blocker.resume(with: .failure(TestRefreshError(message: "stale failure"))) await refreshTask.value #expect(store.snapshots[.codex]?.accountEmail(for: .codex) == "beta@example.com") #expect(store.errors[.codex] == nil) } @Test func `codex visible account refresh preserves prior snapshots when network fails`() async throws { try await self.withCodexVisibleAccountFailureStore( suite: "CodexAccountScopedRefreshTests-preserve-codex-snapshots", errorMessage: "Network error: offline") { store, snapshotStore, priorSnapshots in await store.refreshCodexVisibleAccountsForMenu() #expect(store.codexAccountSnapshots.count == priorSnapshots.count) #expect(store.codexAccountSnapshots.allSatisfy { $0.snapshot?.primary?.usedPercent == 17 }) #expect(store.codexAccountSnapshots.allSatisfy { $0.error == "Network error: offline" }) #expect(store.codexAccountSnapshots.allSatisfy { $0.sourceLabel == "cached" }) let persisted = snapshotStore.storedSnapshots #expect(persisted.count == priorSnapshots.count) #expect(persisted.allSatisfy { $0.snapshot?.primary?.usedPercent == 17 }) #expect(persisted.allSatisfy { $0.error == "Network error: offline" }) } } @Test func `codex visible account refresh drops prior snapshots when auth fails`() async throws { try await self.withCodexVisibleAccountFailureStore( suite: "CodexAccountScopedRefreshTests-drop-auth-failed-snapshots", errorMessage: "401 Unauthorized") { store, snapshotStore, priorSnapshots in await store.refreshCodexVisibleAccountsForMenu() #expect(store.codexAccountSnapshots.count == priorSnapshots.count) #expect(store.codexAccountSnapshots.allSatisfy { $0.snapshot == nil }) #expect(store.codexAccountSnapshots.allSatisfy { $0.error == "401 Unauthorized" }) let persisted = snapshotStore.storedSnapshots #expect(persisted.count == priorSnapshots.count) #expect(persisted.allSatisfy { $0.snapshot == nil }) #expect(persisted.allSatisfy { $0.error == "401 Unauthorized" }) } } @Test func `credits fallback only reuses cache for the same codex account`() async { let settings = self.makeSettingsStore(suite: "CodexAccountScopedRefreshTests-credits") settings.refreshFrequency = .manual settings._test_liveSystemCodexAccount = self.liveAccount(email: "alpha@example.com") let store = self.makeUsageStore(settings: settings) let cachedCredits = self.credits(remaining: 12) store._setSnapshotForTesting(self.codexSnapshot(email: "alpha@example.com", usedPercent: 10), provider: .codex) store.lastCreditsSnapshot = cachedCredits store.lastCreditsSnapshotAccountKey = "alpha@example.com" store._test_codexCreditsLoaderOverride = { throw TestRefreshError(message: "Codex credits data not available yet") } defer { store._test_codexCreditsLoaderOverride = nil } await store.refreshCreditsIfNeeded() #expect(store.credits == cachedCredits) #expect(store.lastCreditsError == nil) settings._test_liveSystemCodexAccount = self.liveAccount(email: "beta@example.com") store._setSnapshotForTesting(self.codexSnapshot(email: "beta@example.com", usedPercent: 10), provider: .codex) await store.refreshCreditsIfNeeded() #expect(store.credits == nil) #expect(store.lastCreditsSource == .none) #expect(store.lastCreditsError == "Codex credits are still loading; will retry shortly.") } @Test func `managed refresh invalidation keeps state when provider account is unchanged`() throws { let settings = self.makeSettingsStore( suite: "CodexAccountScopedRefreshTests-managed-renamed-email") let managedHome = FileManager.default.temporaryDirectory .appendingPathComponent(UUID().uuidString, isDirectory: true) defer { try? FileManager.default.removeItem(at: managedHome) } try Self.writeCodexAuthFile( homeURL: managedHome, email: "renamed@example.com", plan: "pro", accountId: "acct-managed") let managedAccountID = UUID() let managedAccount = ManagedCodexAccount( id: managedAccountID, email: "legacy@example.com", managedHomePath: managedHome.path, createdAt: 1, updatedAt: 1, lastAuthenticatedAt: 1) settings._test_activeManagedCodexAccount = managedAccount settings.codexActiveSource = .managedAccount(id: managedAccountID) defer { settings._test_activeManagedCodexAccount = nil } let store = self.makeUsageStore(settings: settings) let currentSnapshot = self.codexSnapshot(email: "renamed@example.com", usedPercent: 10) let currentCredits = self.credits(remaining: 42) let currentDashboard = self.dashboard(email: "renamed@example.com", creditsRemaining: 42, usedPercent: 20) store._setSnapshotForTesting(currentSnapshot, provider: .codex) store.credits = currentCredits store.lastCreditsSnapshot = currentCredits store.lastCreditsSnapshotAccountKey = "renamed@example.com" store.openAIDashboard = currentDashboard store.lastOpenAIDashboardSnapshot = currentDashboard store.lastOpenAIDashboardTargetEmail = "renamed@example.com" store.seedCodexAccountScopedRefreshGuard( source: .managedAccount(id: managedAccountID), accountEmail: "renamed@example.com") let currentGuard = store.currentCodexAccountScopedRefreshGuard( preferCurrentSnapshot: false, allowLastKnownLiveFallback: false) let didInvalidate = store.prepareCodexAccountScopedRefreshIfNeeded() #expect(currentGuard.identity == .providerAccount(id: "acct-managed")) #expect(currentGuard.accountKey == "renamed@example.com") #expect(didInvalidate == false) #expect(store.snapshots[.codex]?.accountEmail(for: .codex) == "renamed@example.com") #expect(store.credits?.remaining == 42) #expect(store.openAIDashboard?.signedInEmail == "renamed@example.com") } @Test func `credits refresh returns quickly when no live codex account is available`() async { let settings = self.makeSettingsStore(suite: "CodexAccountScopedRefreshTests-credits-no-live-account") let isolatedHome = FileManager.default.temporaryDirectory .appendingPathComponent("codex-credits-no-live-\(UUID().uuidString)", isDirectory: true) try? FileManager.default.createDirectory(at: isolatedHome, withIntermediateDirectories: true) settings.refreshFrequency = .manual settings._test_liveSystemCodexAccount = nil settings._test_codexReconciliationEnvironment = ["CODEX_HOME": isolatedHome.path] defer { settings._test_codexReconciliationEnvironment = nil try? FileManager.default.removeItem(at: isolatedHome) } let store = self.makeUsageStore(settings: settings) var loaderCalled = false store._test_codexCreditsLoaderOverride = { loaderCalled = true return self.credits(remaining: 1) } defer { store._test_codexCreditsLoaderOverride = nil } let startedAt = ContinuousClock.now await store.refreshCreditsIfNeeded(minimumSnapshotUpdatedAt: Date()) let elapsed = startedAt.duration(to: .now) #expect(loaderCalled == false) #expect(elapsed < .seconds(3)) } @Test func `stale dashboard apply is discarded after account switch`() async { let settings = self.makeSettingsStore(suite: "CodexAccountScopedRefreshTests-dashboard") settings.refreshFrequency = .manual settings._test_liveSystemCodexAccount = self.liveAccount(email: "alpha@example.com") let store = self.makeUsageStore(settings: settings) let expectedGuard = store.currentCodexAccountScopedRefreshGuard() settings._test_liveSystemCodexAccount = self.liveAccount(email: "beta@example.com") await store.applyOpenAIDashboard( self.dashboard(email: "alpha@example.com", creditsRemaining: 11, usedPercent: 35), targetEmail: "alpha@example.com", expectedGuard: expectedGuard, allowCodexUsageBackfill: true) #expect(store.openAIDashboard == nil) #expect(store.snapshots[.codex] == nil) #expect(store.credits == nil) } @Test func `dashboard refresh fail closes when live identity is unresolved without trusted continuity`() async { let settings = self.makeSettingsStore(suite: "CodexAccountScopedRefreshTests-dashboard-unresolved-fail-closed") let isolatedHome = FileManager.default.temporaryDirectory .appendingPathComponent("codex-openai-web-unresolved-fail-closed-\(UUID().uuidString)", isDirectory: true) try? FileManager.default.createDirectory(at: isolatedHome, withIntermediateDirectories: true) settings.refreshFrequency = .manual settings.codexCookieSource = .auto settings._test_liveSystemCodexAccount = nil settings._test_codexReconciliationEnvironment = ["CODEX_HOME": isolatedHome.path] defer { settings._test_codexReconciliationEnvironment = nil try? FileManager.default.removeItem(at: isolatedHome) } let store = self.makeUsageStore(settings: settings) store.lastKnownLiveSystemCodexEmail = nil store._test_openAIDashboardLoaderOverride = { _, _, _, _ in self.dashboard(email: "seeded@example.com", creditsRemaining: 33, usedPercent: 12) } defer { store._test_openAIDashboardLoaderOverride = nil } let expectedGuard = store.currentCodexAccountScopedRefreshGuard() #expect(expectedGuard.source == .liveSystem) #expect(expectedGuard.identity == .unresolved) #expect(expectedGuard.accountKey == nil) await store.refreshOpenAIDashboardIfNeeded(force: true, expectedGuard: expectedGuard) #expect(store.openAIDashboard == nil) #expect(store.lastOpenAIDashboardSnapshot == nil) #expect(store.snapshots[.codex] == nil) #expect(store.credits == nil) #expect(store.openAIDashboardRequiresLogin == true) #expect(store.lastOpenAIDashboardError?.contains("could not be verified") == true) } @Test func `dashboard refresh attaches for unresolved live identity with trusted non dashboard continuity`() async { let settings = self.makeSettingsStore( suite: "CodexAccountScopedRefreshTests-dashboard-unresolved-trusted-continuity") let isolatedHome = FileManager.default.temporaryDirectory .appendingPathComponent("codex-openai-web-unresolved-trusted-\(UUID().uuidString)", isDirectory: true) try? FileManager.default.createDirectory(at: isolatedHome, withIntermediateDirectories: true) settings.refreshFrequency = .manual settings.codexCookieSource = .auto settings._test_liveSystemCodexAccount = nil settings._test_codexReconciliationEnvironment = ["CODEX_HOME": isolatedHome.path] defer { settings._test_codexReconciliationEnvironment = nil try? FileManager.default.removeItem(at: isolatedHome) } let store = self.makeUsageStore(settings: settings) store._setSnapshotForTesting( self.codexSnapshot(email: "trusted@example.com", usedPercent: 12), provider: .codex) store.lastSourceLabels[.codex] = "codex-cli" store._test_openAIDashboardLoaderOverride = { _, _, _, _ in self.dashboard(email: "trusted@example.com", creditsRemaining: 33, usedPercent: 12) } defer { store._test_openAIDashboardLoaderOverride = nil } let expectedGuard = store.currentCodexOpenAIWebRefreshGuard() #expect(expectedGuard.identity == .unresolved) await store.refreshOpenAIDashboardIfNeeded(force: true, expectedGuard: expectedGuard) #expect(store.openAIDashboard?.signedInEmail == "trusted@example.com") #expect(store.snapshots[.codex]?.accountEmail(for: .codex) == "trusted@example.com") #expect(store.lastSourceLabels[.codex] == "codex-cli") #expect(store.credits?.remaining == 33) #expect(store.lastCreditsSource == .dashboardWeb) #expect(store.lastCreditsSnapshotAccountKey == "trusted@example.com") #expect( store.lastCodexAccountScopedRefreshGuard?.identity == .emailOnly(normalizedEmail: "trusted@example.com")) #expect(store.lastCodexAccountScopedRefreshGuard?.accountKey == "trusted@example.com") } @Test func `no usable codex usage does not block weekly only dashboard backfill`() async { let settings = self.makeSettingsStore( suite: "CodexAccountScopedRefreshTests-no-usable-usage-weekly-dashboard-backfill") settings.refreshFrequency = .manual settings.codexCookieSource = .auto settings._test_liveSystemCodexAccount = self.liveAccount( email: "weekly@example.com", identity: .providerAccount(id: "acct-weekly")) let store = self.makeUsageStore(settings: settings) self.installFailingCodexProvider(on: store, error: UsageError.noRateLimitsFound) await store.refreshProvider(.codex, allowDisabled: true) #expect(store.snapshots[.codex] == nil) await store.applyOpenAIDashboard( OpenAIDashboardSnapshot( signedInEmail: "weekly@example.com", codeReviewRemainingPercent: 88, creditEvents: [], dailyBreakdown: [], usageBreakdown: [], creditsPurchaseURL: nil, primaryLimit: nil, secondaryLimit: RateWindow( usedPercent: 27, windowMinutes: 10080, resetsAt: Date(timeIntervalSince1970: 1_775_000_000), resetDescription: "next week"), creditsRemaining: 14, accountPlan: "Pro", updatedAt: Date(timeIntervalSince1970: 1_774_900_000)), targetEmail: "weekly@example.com", allowCodexUsageBackfill: true) #expect(store.openAIDashboard?.signedInEmail == "weekly@example.com") #expect(store.snapshots[.codex]?.primary == nil) #expect(store.snapshots[.codex]?.secondary?.usedPercent == 27) #expect(store.snapshots[.codex]?.secondary?.windowMinutes == 10080) #expect(store.lastSourceLabels[.codex] == "openai-web") } @Test func `dashboard display only keeps dashboard visible and clears dashboard derived data`() async throws { let settings = self.makeSettingsStore(suite: "CodexAccountScopedRefreshTests-dashboard-display-only-cleanup") let managedHome = FileManager.default.temporaryDirectory .appendingPathComponent(UUID().uuidString, isDirectory: true) defer { try? FileManager.default.removeItem(at: managedHome) } try Self.writeCodexAuthFile( homeURL: managedHome, email: "shared@example.com", plan: "pro", accountId: "acct-managed") let managedAccount = ManagedCodexAccount( id: UUID(), email: "shared@example.com", managedHomePath: managedHome.path, createdAt: 1, updatedAt: 1, lastAuthenticatedAt: 1) let managedStoreURL = try self.makeManagedAccountStoreURL(accounts: [managedAccount]) defer { settings._test_managedCodexAccountStoreURL = nil try? FileManager.default.removeItem(at: managedStoreURL) OpenAIDashboardCacheStore.clear() } settings.refreshFrequency = .manual settings.codexCookieSource = .auto settings._test_managedCodexAccountStoreURL = managedStoreURL settings._test_liveSystemCodexAccount = self.liveAccount( email: "shared@example.com", identity: .emailOnly(normalizedEmail: "shared@example.com")) settings.codexActiveSource = .liveSystem let store = self.makeUsageStore(settings: settings) store._setSnapshotForTesting(self.codexSnapshot(email: "shared@example.com", usedPercent: 20), provider: .codex) store.lastSourceLabels[.codex] = "openai-web" let staleCredits = self.credits(remaining: 20) store.credits = staleCredits store.lastCreditsSnapshot = staleCredits store.lastCreditsSnapshotAccountKey = "shared@example.com" store.lastCreditsSource = .dashboardWeb OpenAIDashboardCacheStore.save(OpenAIDashboardCache( accountEmail: "shared@example.com", snapshot: self.dashboard(email: "shared@example.com", creditsRemaining: 20, usedPercent: 20))) await store.applyOpenAIDashboard( self.dashboard(email: "shared@example.com", creditsRemaining: 9, usedPercent: 35), targetEmail: "shared@example.com") #expect(store.openAIDashboard?.signedInEmail == "shared@example.com") #expect(store.lastOpenAIDashboardSnapshot?.signedInEmail == "shared@example.com") #expect(store.snapshots[.codex] == nil)Lines 1–500 of 925. Load more
import CodexBarCore import Foundation import Testing @testable import CodexBar @Suite(.serialized) @MainActor struct CodexAccountScopedRefreshTests { @Test func `account transition invalidates codex scoped state and preserves token usage`() async { let settings = self.makeSettingsStore(suite: "CodexAccountScopedRefreshTests-invalidate") settings.refreshFrequency = .manual settings.codexCookieSource = .auto settings._test_liveSystemCodexAccount = self.liveAccount(email: "alpha@example.com") let store = self.makeUsageStore(settings: settings) let staleSnapshot = self.codexSnapshot(email: "alpha@example.com", usedPercent: 10) let staleCredits = self.credits(remaining: 42) let staleDashboard = self.dashboard(email: "alpha@example.com", creditsRemaining: 42, usedPercent: 20) let tokenSnapshot = CostUsageTokenSnapshot( sessionTokens: 120, sessionCostUSD: 1.2, last30DaysTokens: 900, last30DaysCostUSD: 9.0, daily: [], updatedAt: Date()) var widgetSnapshots: [WidgetSnapshot] = [] store._setSnapshotForTesting(staleSnapshot, provider: .codex) store.credits = staleCredits store.lastCreditsSnapshot = staleCredits store.lastCreditsSnapshotAccountKey = "alpha@example.com" store.lastCreditsSource = .api store.openAIDashboard = staleDashboard store.lastOpenAIDashboardSnapshot = staleDashboard store.lastOpenAIDashboardTargetEmail = "alpha@example.com" store._setTokenSnapshotForTesting(tokenSnapshot, provider: .codex) store.lastCodexAccountScopedRefreshGuard = store .currentCodexAccountScopedRefreshGuard(preferCurrentSnapshot: false) store._test_widgetSnapshotSaveOverride = { widgetSnapshots.append($0) } defer { store._test_widgetSnapshotSaveOverride = nil } settings._test_liveSystemCodexAccount = self.liveAccount(email: "beta@example.com") let didInvalidate = store.prepareCodexAccountScopedRefreshIfNeeded() await store.widgetSnapshotPersistTask?.value #expect(didInvalidate) #expect(store.snapshots[.codex] == nil) #expect(store.credits == nil) #expect(store.lastCreditsSnapshot == nil) #expect(store.lastCreditsSnapshotAccountKey == nil) #expect(store.lastCreditsSource == .none) #expect(store.openAIDashboard == nil) #expect(store.lastOpenAIDashboardSnapshot == nil) #expect(store.tokenSnapshots[.codex] == tokenSnapshot) #expect(widgetSnapshots.count == 1) #expect(widgetSnapshots[0].entries.contains(where: { $0.provider == .codex }) == false) } @Test func `first switch invalidates after codex refresh seeds the previous account guard`() async { let settings = self.makeSettingsStore(suite: "CodexAccountScopedRefreshTests-first-switch") settings.refreshFrequency = .manual settings.codexCookieSource = .off settings._test_liveSystemCodexAccount = self.liveAccount(email: "alpha@example.com") let store = self.makeUsageStore(settings: settings) self.installImmediateCodexProvider( on: store, snapshot: self.codexSnapshot(email: "alpha@example.com", usedPercent: 10)) await store.refreshProvider(.codex, allowDisabled: true) #expect(store.lastCodexAccountScopedRefreshGuard?.accountKey == "alpha@example.com") settings._test_liveSystemCodexAccount = self.liveAccount(email: "beta@example.com") let didInvalidate = store.prepareCodexAccountScopedRefreshIfNeeded() #expect(didInvalidate) #expect(store.snapshots[.codex] == nil) } @Test func `stale codex usage success is discarded after account switch`() async { let settings = self.makeSettingsStore(suite: "CodexAccountScopedRefreshTests-stale-success") settings.refreshFrequency = .manual settings._test_liveSystemCodexAccount = self.liveAccount(email: "alpha@example.com") let store = self.makeUsageStore(settings: settings) let blocker = BlockingCodexFetchStrategy() self.installBlockingCodexProvider(on: store, blocker: blocker) let refreshTask = Task { await store.refreshProvider(.codex, allowDisabled: true) } await blocker.waitUntilStarted() settings._test_liveSystemCodexAccount = self.liveAccount(email: "beta@example.com") await blocker.resume(with: .success(self.codexSnapshot(email: "alpha@example.com", usedPercent: 25))) await refreshTask.value #expect(store.snapshots[.codex] == nil) #expect(store.errors[.codex] == nil) } @Test func `same email provider account switch discards stale codex usage success`() async { let settings = self.makeSettingsStore(suite: "CodexAccountScopedRefreshTests-stale-same-email-provider-account") settings.refreshFrequency = .manual settings._test_liveSystemCodexAccount = self.liveAccount( email: "alpha@example.com", identity: .providerAccount(id: "acct-alpha")) let store = self.makeUsageStore(settings: settings) let blocker = BlockingCodexFetchStrategy() self.installBlockingCodexProvider(on: store, blocker: blocker) let refreshTask = Task { await store.refreshProvider(.codex, allowDisabled: true) } await blocker.waitUntilStarted() settings._test_liveSystemCodexAccount = self.liveAccount( email: "alpha@example.com", identity: .providerAccount(id: "acct-beta")) await blocker.resume(with: .success(self.codexSnapshot(email: "alpha@example.com", usedPercent: 25))) await refreshTask.value #expect(store.snapshots[.codex] == nil) #expect(store.errors[.codex] == nil) } @Test func `stale codex usage failure does not clear newer account snapshot`() async { let settings = self.makeSettingsStore(suite: "CodexAccountScopedRefreshTests-stale-failure") settings.refreshFrequency = .manual settings._test_liveSystemCodexAccount = self.liveAccount(email: "alpha@example.com") let store = self.makeUsageStore(settings: settings) let blocker = BlockingCodexFetchStrategy() self.installBlockingCodexProvider(on: store, blocker: blocker) let refreshTask = Task { await store.refreshProvider(.codex, allowDisabled: true) } await blocker.waitUntilStarted() settings._test_liveSystemCodexAccount = self.liveAccount(email: "beta@example.com") let freshSnapshot = self.codexSnapshot(email: "beta@example.com", usedPercent: 5) store._setSnapshotForTesting(freshSnapshot, provider: .codex) await blocker.resume(with: .failure(TestRefreshError(message: "stale failure"))) await refreshTask.value #expect(store.snapshots[.codex]?.accountEmail(for: .codex) == "beta@example.com") #expect(store.errors[.codex] == nil) } @Test func `codex visible account refresh preserves prior snapshots when network fails`() async throws { try await self.withCodexVisibleAccountFailureStore( suite: "CodexAccountScopedRefreshTests-preserve-codex-snapshots", errorMessage: "Network error: offline") { store, snapshotStore, priorSnapshots in await store.refreshCodexVisibleAccountsForMenu() #expect(store.codexAccountSnapshots.count == priorSnapshots.count) #expect(store.codexAccountSnapshots.allSatisfy { $0.snapshot?.primary?.usedPercent == 17 }) #expect(store.codexAccountSnapshots.allSatisfy { $0.error == "Network error: offline" }) #expect(store.codexAccountSnapshots.allSatisfy { $0.sourceLabel == "cached" }) let persisted = snapshotStore.storedSnapshots #expect(persisted.count == priorSnapshots.count) #expect(persisted.allSatisfy { $0.snapshot?.primary?.usedPercent == 17 }) #expect(persisted.allSatisfy { $0.error == "Network error: offline" }) } } @Test func `codex visible account refresh drops prior snapshots when auth fails`() async throws { try await self.withCodexVisibleAccountFailureStore( suite: "CodexAccountScopedRefreshTests-drop-auth-failed-snapshots", errorMessage: "401 Unauthorized") { store, snapshotStore, priorSnapshots in await store.refreshCodexVisibleAccountsForMenu() #expect(store.codexAccountSnapshots.count == priorSnapshots.count) #expect(store.codexAccountSnapshots.allSatisfy { $0.snapshot == nil }) #expect(store.codexAccountSnapshots.allSatisfy { $0.error == "401 Unauthorized" }) let persisted = snapshotStore.storedSnapshots #expect(persisted.count == priorSnapshots.count) #expect(persisted.allSatisfy { $0.snapshot == nil }) #expect(persisted.allSatisfy { $0.error == "401 Unauthorized" }) } } @Test func `credits fallback only reuses cache for the same codex account`() async { let settings = self.makeSettingsStore(suite: "CodexAccountScopedRefreshTests-credits") settings.refreshFrequency = .manual settings._test_liveSystemCodexAccount = self.liveAccount(email: "alpha@example.com") let store = self.makeUsageStore(settings: settings) let cachedCredits = self.credits(remaining: 12) store._setSnapshotForTesting(self.codexSnapshot(email: "alpha@example.com", usedPercent: 10), provider: .codex) store.lastCreditsSnapshot = cachedCredits store.lastCreditsSnapshotAccountKey = "alpha@example.com" store._test_codexCreditsLoaderOverride = { throw TestRefreshError(message: "Codex credits data not available yet") } defer { store._test_codexCreditsLoaderOverride = nil } await store.refreshCreditsIfNeeded() #expect(store.credits == cachedCredits) #expect(store.lastCreditsError == nil) settings._test_liveSystemCodexAccount = self.liveAccount(email: "beta@example.com") store._setSnapshotForTesting(self.codexSnapshot(email: "beta@example.com", usedPercent: 10), provider: .codex) await store.refreshCreditsIfNeeded() #expect(store.credits == nil) #expect(store.lastCreditsSource == .none) #expect(store.lastCreditsError == "Codex credits are still loading; will retry shortly.") } @Test func `managed refresh invalidation keeps state when provider account is unchanged`() throws { let settings = self.makeSettingsStore( suite: "CodexAccountScopedRefreshTests-managed-renamed-email") let managedHome = FileManager.default.temporaryDirectory .appendingPathComponent(UUID().uuidString, isDirectory: true) defer { try? FileManager.default.removeItem(at: managedHome) } try Self.writeCodexAuthFile( homeURL: managedHome, email: "renamed@example.com", plan: "pro", accountId: "acct-managed") let managedAccountID = UUID() let managedAccount = ManagedCodexAccount( id: managedAccountID, email: "legacy@example.com", managedHomePath: managedHome.path, createdAt: 1, updatedAt: 1, lastAuthenticatedAt: 1) settings._test_activeManagedCodexAccount = managedAccount settings.codexActiveSource = .managedAccount(id: managedAccountID) defer { settings._test_activeManagedCodexAccount = nil } let store = self.makeUsageStore(settings: settings) let currentSnapshot = self.codexSnapshot(email: "renamed@example.com", usedPercent: 10) let currentCredits = self.credits(remaining: 42) let currentDashboard = self.dashboard(email: "renamed@example.com", creditsRemaining: 42, usedPercent: 20) store._setSnapshotForTesting(currentSnapshot, provider: .codex) store.credits = currentCredits store.lastCreditsSnapshot = currentCredits store.lastCreditsSnapshotAccountKey = "renamed@example.com" store.openAIDashboard = currentDashboard store.lastOpenAIDashboardSnapshot = currentDashboard store.lastOpenAIDashboardTargetEmail = "renamed@example.com" store.seedCodexAccountScopedRefreshGuard( source: .managedAccount(id: managedAccountID), accountEmail: "renamed@example.com") let currentGuard = store.currentCodexAccountScopedRefreshGuard( preferCurrentSnapshot: false, allowLastKnownLiveFallback: false) let didInvalidate = store.prepareCodexAccountScopedRefreshIfNeeded() #expect(currentGuard.identity == .providerAccount(id: "acct-managed")) #expect(currentGuard.accountKey == "renamed@example.com") #expect(didInvalidate == false) #expect(store.snapshots[.codex]?.accountEmail(for: .codex) == "renamed@example.com") #expect(store.credits?.remaining == 42) #expect(store.openAIDashboard?.signedInEmail == "renamed@example.com") } @Test func `credits refresh returns quickly when no live codex account is available`() async { let settings = self.makeSettingsStore(suite: "CodexAccountScopedRefreshTests-credits-no-live-account") let isolatedHome = FileManager.default.temporaryDirectory .appendingPathComponent("codex-credits-no-live-\(UUID().uuidString)", isDirectory: true) try? FileManager.default.createDirectory(at: isolatedHome, withIntermediateDirectories: true) settings.refreshFrequency = .manual settings._test_liveSystemCodexAccount = nil settings._test_codexReconciliationEnvironment = ["CODEX_HOME": isolatedHome.path] defer { settings._test_codexReconciliationEnvironment = nil try? FileManager.default.removeItem(at: isolatedHome) } let store = self.makeUsageStore(settings: settings) var loaderCalled = false store._test_codexCreditsLoaderOverride = { loaderCalled = true return self.credits(remaining: 1) } defer { store._test_codexCreditsLoaderOverride = nil } let startedAt = ContinuousClock.now await store.refreshCreditsIfNeeded(minimumSnapshotUpdatedAt: Date()) let elapsed = startedAt.duration(to: .now) #expect(loaderCalled == false) #expect(elapsed < .seconds(3)) } @Test func `stale dashboard apply is discarded after account switch`() async { let settings = self.makeSettingsStore(suite: "CodexAccountScopedRefreshTests-dashboard") settings.refreshFrequency = .manual settings._test_liveSystemCodexAccount = self.liveAccount(email: "alpha@example.com") let store = self.makeUsageStore(settings: settings) let expectedGuard = store.currentCodexAccountScopedRefreshGuard() settings._test_liveSystemCodexAccount = self.liveAccount(email: "beta@example.com") await store.applyOpenAIDashboard( self.dashboard(email: "alpha@example.com", creditsRemaining: 11, usedPercent: 35), targetEmail: "alpha@example.com", expectedGuard: expectedGuard, allowCodexUsageBackfill: true) #expect(store.openAIDashboard == nil) #expect(store.snapshots[.codex] == nil) #expect(store.credits == nil) } @Test func `dashboard refresh fail closes when live identity is unresolved without trusted continuity`() async { let settings = self.makeSettingsStore(suite: "CodexAccountScopedRefreshTests-dashboard-unresolved-fail-closed") let isolatedHome = FileManager.default.temporaryDirectory .appendingPathComponent("codex-openai-web-unresolved-fail-closed-\(UUID().uuidString)", isDirectory: true) try? FileManager.default.createDirectory(at: isolatedHome, withIntermediateDirectories: true) settings.refreshFrequency = .manual settings.codexCookieSource = .auto settings._test_liveSystemCodexAccount = nil settings._test_codexReconciliationEnvironment = ["CODEX_HOME": isolatedHome.path] defer { settings._test_codexReconciliationEnvironment = nil try? FileManager.default.removeItem(at: isolatedHome) } let store = self.makeUsageStore(settings: settings) store.lastKnownLiveSystemCodexEmail = nil store._test_openAIDashboardLoaderOverride = { _, _, _, _ in self.dashboard(email: "seeded@example.com", creditsRemaining: 33, usedPercent: 12) } defer { store._test_openAIDashboardLoaderOverride = nil } let expectedGuard = store.currentCodexAccountScopedRefreshGuard() #expect(expectedGuard.source == .liveSystem) #expect(expectedGuard.identity == .unresolved) #expect(expectedGuard.accountKey == nil) await store.refreshOpenAIDashboardIfNeeded(force: true, expectedGuard: expectedGuard) #expect(store.openAIDashboard == nil) #expect(store.lastOpenAIDashboardSnapshot == nil) #expect(store.snapshots[.codex] == nil) #expect(store.credits == nil) #expect(store.openAIDashboardRequiresLogin == true) #expect(store.lastOpenAIDashboardError?.contains("could not be verified") == true) } @Test func `dashboard refresh attaches for unresolved live identity with trusted non dashboard continuity`() async { let settings = self.makeSettingsStore( suite: "CodexAccountScopedRefreshTests-dashboard-unresolved-trusted-continuity") let isolatedHome = FileManager.default.temporaryDirectory .appendingPathComponent("codex-openai-web-unresolved-trusted-\(UUID().uuidString)", isDirectory: true) try? FileManager.default.createDirectory(at: isolatedHome, withIntermediateDirectories: true) settings.refreshFrequency = .manual settings.codexCookieSource = .auto settings._test_liveSystemCodexAccount = nil settings._test_codexReconciliationEnvironment = ["CODEX_HOME": isolatedHome.path] defer { settings._test_codexReconciliationEnvironment = nil try? FileManager.default.removeItem(at: isolatedHome) } let store = self.makeUsageStore(settings: settings) store._setSnapshotForTesting( self.codexSnapshot(email: "trusted@example.com", usedPercent: 12), provider: .codex) store.lastSourceLabels[.codex] = "codex-cli" store._test_openAIDashboardLoaderOverride = { _, _, _, _ in self.dashboard(email: "trusted@example.com", creditsRemaining: 33, usedPercent: 12) } defer { store._test_openAIDashboardLoaderOverride = nil } let expectedGuard = store.currentCodexOpenAIWebRefreshGuard() #expect(expectedGuard.identity == .unresolved) await store.refreshOpenAIDashboardIfNeeded(force: true, expectedGuard: expectedGuard) #expect(store.openAIDashboard?.signedInEmail == "trusted@example.com") #expect(store.snapshots[.codex]?.accountEmail(for: .codex) == "trusted@example.com") #expect(store.lastSourceLabels[.codex] == "codex-cli") #expect(store.credits?.remaining == 33) #expect(store.lastCreditsSource == .dashboardWeb) #expect(store.lastCreditsSnapshotAccountKey == "trusted@example.com") #expect( store.lastCodexAccountScopedRefreshGuard?.identity == .emailOnly(normalizedEmail: "trusted@example.com")) #expect(store.lastCodexAccountScopedRefreshGuard?.accountKey == "trusted@example.com") } @Test func `no usable codex usage does not block weekly only dashboard backfill`() async { let settings = self.makeSettingsStore( suite: "CodexAccountScopedRefreshTests-no-usable-usage-weekly-dashboard-backfill") settings.refreshFrequency = .manual settings.codexCookieSource = .auto settings._test_liveSystemCodexAccount = self.liveAccount( email: "weekly@example.com", identity: .providerAccount(id: "acct-weekly")) let store = self.makeUsageStore(settings: settings) self.installFailingCodexProvider(on: store, error: UsageError.noRateLimitsFound) await store.refreshProvider(.codex, allowDisabled: true) #expect(store.snapshots[.codex] == nil) await store.applyOpenAIDashboard( OpenAIDashboardSnapshot( signedInEmail: "weekly@example.com", codeReviewRemainingPercent: 88, creditEvents: [], dailyBreakdown: [], usageBreakdown: [], creditsPurchaseURL: nil, primaryLimit: nil, secondaryLimit: RateWindow( usedPercent: 27, windowMinutes: 10080, resetsAt: Date(timeIntervalSince1970: 1_775_000_000), resetDescription: "next week"), creditsRemaining: 14, accountPlan: "Pro", updatedAt: Date(timeIntervalSince1970: 1_774_900_000)), targetEmail: "weekly@example.com", allowCodexUsageBackfill: true) #expect(store.openAIDashboard?.signedInEmail == "weekly@example.com") #expect(store.snapshots[.codex]?.primary == nil) #expect(store.snapshots[.codex]?.secondary?.usedPercent == 27) #expect(store.snapshots[.codex]?.secondary?.windowMinutes == 10080) #expect(store.lastSourceLabels[.codex] == "openai-web") } @Test func `dashboard display only keeps dashboard visible and clears dashboard derived data`() async throws { let settings = self.makeSettingsStore(suite: "CodexAccountScopedRefreshTests-dashboard-display-only-cleanup") let managedHome = FileManager.default.temporaryDirectory .appendingPathComponent(UUID().uuidString, isDirectory: true) defer { try? FileManager.default.removeItem(at: managedHome) } try Self.writeCodexAuthFile( homeURL: managedHome, email: "shared@example.com", plan: "pro", accountId: "acct-managed") let managedAccount = ManagedCodexAccount( id: UUID(), email: "shared@example.com", managedHomePath: managedHome.path, createdAt: 1, updatedAt: 1, lastAuthenticatedAt: 1) let managedStoreURL = try self.makeManagedAccountStoreURL(accounts: [managedAccount]) defer { settings._test_managedCodexAccountStoreURL = nil try? FileManager.default.removeItem(at: managedStoreURL) OpenAIDashboardCacheStore.clear() } settings.refreshFrequency = .manual settings.codexCookieSource = .auto settings._test_managedCodexAccountStoreURL = managedStoreURL settings._test_liveSystemCodexAccount = self.liveAccount( email: "shared@example.com", identity: .emailOnly(normalizedEmail: "shared@example.com")) settings.codexActiveSource = .liveSystem let store = self.makeUsageStore(settings: settings) store._setSnapshotForTesting(self.codexSnapshot(email: "shared@example.com", usedPercent: 20), provider: .codex) store.lastSourceLabels[.codex] = "openai-web" let staleCredits = self.credits(remaining: 20) store.credits = staleCredits store.lastCreditsSnapshot = staleCredits store.lastCreditsSnapshotAccountKey = "shared@example.com" store.lastCreditsSource = .dashboardWeb OpenAIDashboardCacheStore.save(OpenAIDashboardCache( accountEmail: "shared@example.com", snapshot: self.dashboard(email: "shared@example.com", creditsRemaining: 20, usedPercent: 20))) await store.applyOpenAIDashboard( self.dashboard(email: "shared@example.com", creditsRemaining: 9, usedPercent: 35), targetEmail: "shared@example.com") #expect(store.openAIDashboard?.signedInEmail == "shared@example.com") #expect(store.lastOpenAIDashboardSnapshot?.signedInEmail == "shared@example.com") #expect(store.snapshots[.codex] == nil)