import CodexBarCore import Foundation import Testing @testable import CodexBar @MainActor extension CodexAccountScopedRefreshTests { @Test func `same account token refresh fingerprint change keeps codex usage success`() async { SettingsStore.codexAccountReconciliationSnapshotCacheIntervalOverrideForTesting = 60 let settings = self.makeSettingsStore( suite: "CodexAccountScopedRefreshTests-token-refresh-fingerprint-change") defer { SettingsStore.codexAccountReconciliationSnapshotCacheIntervalOverrideForTesting = nil settings._test_liveSystemCodexAccount = nil } settings.refreshFrequency = .manual settings._test_liveSystemCodexAccount = ObservedSystemCodexAccount( email: "alpha@example.com", authFingerprint: "old-token-material", codexHomePath: "/Users/test/.codex", observedAt: Date(), identity: .providerAccount(id: "acct-alpha")) let staleReconciliationSnapshot = settings.codexAccountReconciliationSnapshot 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 = ObservedSystemCodexAccount( email: "alpha@example.com", authFingerprint: "new-token-material", codexHomePath: "/Users/test/.codex", observedAt: Date(), identity: .providerAccount(id: "acct-alpha")) settings.cachedCodexAccountReconciliationSnapshot = CachedCodexAccountReconciliationSnapshot( activeSource: .liveSystem, loadedAt: Date(), snapshot: staleReconciliationSnapshot) await blocker.resume(with: .success(self.codexSnapshot(email: "alpha@example.com", usedPercent: 25))) await refreshTask.value #expect(store.snapshots[.codex]?.primary?.usedPercent == 25) #expect(store.lastCodexAccountScopedRefreshGuard?.authFingerprint == "new-token-material") #expect(store.errors[.codex] == nil) } @Test func `same account token refresh fingerprint change keeps reset backfill`() async { let settings = self.makeSettingsStore( suite: "CodexAccountScopedRefreshTests-token-refresh-reset-backfill") defer { settings._test_liveSystemCodexAccount = nil } settings.refreshFrequency = .manual settings._test_liveSystemCodexAccount = ObservedSystemCodexAccount( email: "alpha@example.com", authFingerprint: "old-token-material", codexHomePath: "/Users/test/.codex", observedAt: Date(), identity: .providerAccount(id: "acct-alpha")) let store = self.makeUsageStore(settings: settings) let resetsAt = Date().addingTimeInterval(45 * 60) let publicationGuard = store.freshCodexAccountScopedRefreshGuard() store.lastCodexUsagePublicationGuard = publicationGuard store.lastCodexAccountScopedRefreshGuard = publicationGuard store.lastKnownResetSnapshots[.codex] = UsageSnapshot( primary: RateWindow( usedPercent: 10, windowMinutes: 300, resetsAt: resetsAt, resetDescription: "resets soon"), secondary: nil, updatedAt: Date(), identity: ProviderIdentitySnapshot( providerID: .codex, accountEmail: "alpha@example.com", accountOrganization: nil, loginMethod: "Pro")) settings._test_liveSystemCodexAccount = ObservedSystemCodexAccount( email: "alpha@example.com", authFingerprint: "new-token-material", codexHomePath: "/Users/test/.codex", observedAt: Date(), identity: .providerAccount(id: "acct-alpha")) self.installImmediateCodexProvider( on: store, snapshot: self.codexSnapshot(email: "alpha@example.com", usedPercent: 25)) await store.refreshProvider(.codex, allowDisabled: true) #expect(store.snapshots[.codex]?.primary?.usedPercent == 25) #expect(store.snapshots[.codex]?.primary?.resetsAt == resetsAt) #expect(store.lastKnownResetSnapshots[.codex]?.primary?.resetsAt == resetsAt) #expect(store.lastCodexAccountScopedRefreshGuard?.authFingerprint == "new-token-material") } @Test func `same account token refresh fingerprint change keeps scoped state during prepare`() { let settings = self.makeSettingsStore( suite: "CodexAccountScopedRefreshTests-token-refresh-prepare") defer { settings._test_liveSystemCodexAccount = nil } settings.refreshFrequency = .manual settings._test_liveSystemCodexAccount = ObservedSystemCodexAccount( email: "alpha@example.com", authFingerprint: "old-token-material", codexHomePath: "/Users/test/.codex", observedAt: Date(), identity: .providerAccount(id: "acct-alpha")) let store = self.makeUsageStore(settings: settings) let resetsAt = Date().addingTimeInterval(45 * 60) let cached = UsageSnapshot( primary: RateWindow( usedPercent: 10, windowMinutes: 300, resetsAt: resetsAt, resetDescription: "resets soon"), secondary: nil, updatedAt: Date(), identity: ProviderIdentitySnapshot( providerID: .codex, accountEmail: "alpha@example.com", accountOrganization: nil, loginMethod: "Pro")) store.snapshots[.codex] = cached store.lastKnownResetSnapshots[.codex] = cached store.credits = self.credits(remaining: 42) store.lastCodexAccountScopedRefreshGuard = store.freshCodexAccountScopedRefreshGuard() settings._test_liveSystemCodexAccount = ObservedSystemCodexAccount( email: "alpha@example.com", authFingerprint: "new-token-material", codexHomePath: "/Users/test/.codex", observedAt: Date(), identity: .providerAccount(id: "acct-alpha")) let invalidated = store.prepareCodexAccountScopedRefreshIfNeeded() #expect(!invalidated) #expect(store.snapshots[.codex]?.primary?.resetsAt == resetsAt) #expect(store.lastKnownResetSnapshots[.codex]?.primary?.resetsAt == resetsAt) #expect(store.credits?.remaining == 42) #expect(store.lastCodexAccountScopedRefreshGuard?.authFingerprint == "new-token-material") } @Test func `usage success applies when auth fingerprint appears after refresh starts`() { let settings = self.makeSettingsStore( suite: "CodexAccountScopedRefreshTests-auth-fingerprint-appears") defer { settings._test_liveSystemCodexAccount = nil } settings.refreshFrequency = .manual settings._test_liveSystemCodexAccount = ObservedSystemCodexAccount( email: "alpha@example.com", authFingerprint: nil, codexHomePath: "/Users/test/.codex", observedAt: Date(), identity: .emailOnly(normalizedEmail: "alpha@example.com")) let store = self.makeUsageStore(settings: settings) let expectedGuard = store.freshCodexAccountScopedRefreshGuard() settings._test_liveSystemCodexAccount = ObservedSystemCodexAccount( email: "alpha@example.com", authFingerprint: "new-token-material", codexHomePath: "/Users/test/.codex", observedAt: Date(), identity: .emailOnly(normalizedEmail: "alpha@example.com")) #expect(store.shouldApplyCodexUsageResult( expectedGuard: expectedGuard, usage: self.codexSnapshot(email: "alpha@example.com", usedPercent: 25))) } @Test func `same account token refresh fingerprint change discards codex usage failure`() async { SettingsStore.codexAccountReconciliationSnapshotCacheIntervalOverrideForTesting = 60 let settings = self.makeSettingsStore( suite: "CodexAccountScopedRefreshTests-token-refresh-fingerprint-failure") defer { SettingsStore.codexAccountReconciliationSnapshotCacheIntervalOverrideForTesting = nil settings._test_liveSystemCodexAccount = nil } settings.refreshFrequency = .manual settings._test_liveSystemCodexAccount = ObservedSystemCodexAccount( email: "alpha@example.com", authFingerprint: "old-token-material", codexHomePath: "/Users/test/.codex", observedAt: Date(), identity: .providerAccount(id: "acct-alpha")) let staleReconciliationSnapshot = settings.codexAccountReconciliationSnapshot 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 = ObservedSystemCodexAccount( email: "alpha@example.com", authFingerprint: "new-token-material", codexHomePath: "/Users/test/.codex", observedAt: Date(), identity: .providerAccount(id: "acct-alpha")) settings.cachedCodexAccountReconciliationSnapshot = CachedCodexAccountReconciliationSnapshot( activeSource: .liveSystem, loadedAt: Date(), snapshot: staleReconciliationSnapshot) await blocker.resume(with: .failure(TestRefreshError(message: "old token failure"))) await refreshTask.value #expect(store.snapshots[.codex] == nil) #expect(store.errors[.codex] == nil) } @Test func `same account token refresh fingerprint change keeps codex credits success`() async { SettingsStore.codexAccountReconciliationSnapshotCacheIntervalOverrideForTesting = 60 let settings = self.makeSettingsStore( suite: "CodexAccountScopedRefreshTests-token-refresh-credits-success") defer { SettingsStore.codexAccountReconciliationSnapshotCacheIntervalOverrideForTesting = nil settings._test_liveSystemCodexAccount = nil } settings.refreshFrequency = .manual settings._test_liveSystemCodexAccount = ObservedSystemCodexAccount( email: "alpha@example.com", authFingerprint: "old-token-material", codexHomePath: "/Users/test/.codex", observedAt: Date(), identity: .providerAccount(id: "acct-alpha")) let store = self.makeUsageStore(settings: settings) store._test_codexCreditsLoaderOverride = { settings._test_liveSystemCodexAccount = ObservedSystemCodexAccount( email: "alpha@example.com", authFingerprint: "new-token-material", codexHomePath: "/Users/test/.codex", observedAt: Date(), identity: .providerAccount(id: "acct-alpha")) return CreditsSnapshot(remaining: 42, events: [], updatedAt: Date()) } defer { store._test_codexCreditsLoaderOverride = nil } await store.refreshCreditsIfNeeded() #expect(store.credits?.remaining == 42) #expect(store.lastCreditsSnapshotAccountKey == "alpha@example.com") #expect(store.lastCodexAccountScopedRefreshGuard?.authFingerprint == "new-token-material") #expect(store.lastCreditsError == nil) } @Test func `credits refresh key separates same account auth fingerprints`() { let settings = self.makeSettingsStore( suite: "CodexAccountScopedRefreshTests-credits-key-auth-fingerprint") let store = self.makeUsageStore(settings: settings) let oldGuard = CodexAccountScopedRefreshGuard( source: .liveSystem, identity: .providerAccount(id: "acct-alpha"), accountKey: "alpha@example.com", authFingerprint: "old-token-material") let newGuard = CodexAccountScopedRefreshGuard( source: .liveSystem, identity: .providerAccount(id: "acct-alpha"), accountKey: "alpha@example.com", authFingerprint: "new-token-material") #expect(store.codexCreditsRefreshKey(expectedGuard: oldGuard) != store.codexCreditsRefreshKey(expectedGuard: newGuard)) } @Test func `same account token refresh fingerprint change keeps dashboard success`() async throws { let settings = self.makeSettingsStore( suite: "CodexAccountScopedRefreshTests-token-refresh-dashboard-success") let codexMetadata = try #require(ProviderDescriptorRegistry.metadata[.codex]) settings.setProviderEnabled(provider: .codex, metadata: codexMetadata, enabled: true) settings.refreshFrequency = .manual settings.openAIWebAccessEnabled = true settings.codexCookieSource = .auto settings._test_liveSystemCodexAccount = ObservedSystemCodexAccount( email: "alpha@example.com", authFingerprint: "old-token-material", codexHomePath: "/Users/test/.codex", observedAt: Date(), identity: .providerAccount(id: "acct-alpha")) defer { settings._test_liveSystemCodexAccount = nil } let store = self.makeUsageStore(settings: settings) let expectedGuard = store.freshCodexOpenAIWebRefreshGuard() store._test_openAIDashboardLoaderOverride = { _, _, _, _ in settings._test_liveSystemCodexAccount = ObservedSystemCodexAccount( email: "alpha@example.com", authFingerprint: "new-token-material", codexHomePath: "/Users/test/.codex", observedAt: Date(), identity: .providerAccount(id: "acct-alpha")) return self.dashboard(email: "alpha@example.com", creditsRemaining: 64, usedPercent: 27) } defer { store._test_openAIDashboardLoaderOverride = nil } await store.refreshOpenAIDashboardIfNeeded(force: true, expectedGuard: expectedGuard) #expect(store.openAIDashboard?.creditsRemaining == 64) #expect(store.openAIDashboard?.signedInEmail == "alpha@example.com") #expect(store.lastOpenAIDashboardError == nil) #expect(store.openAIDashboardRequiresLogin == false) } @Test func `dashboard refresh key separates same account auth fingerprints`() async throws { let settings = self.makeSettingsStore( suite: "CodexAccountScopedRefreshTests-dashboard-key-auth-fingerprint") let codexMetadata = try #require(ProviderDescriptorRegistry.metadata[.codex]) settings.setProviderEnabled(provider: .codex, metadata: codexMetadata, enabled: true) settings.refreshFrequency = .manual settings.openAIWebAccessEnabled = true settings.codexCookieSource = .auto settings._test_liveSystemCodexAccount = ObservedSystemCodexAccount( email: "alpha@example.com", authFingerprint: "old-token-material", codexHomePath: "/Users/test/.codex", observedAt: Date(), identity: .providerAccount(id: "acct-alpha")) defer { settings._test_liveSystemCodexAccount = nil } let store = self.makeUsageStore(settings: settings) let blocker = BlockingManagedOpenAIDashboardLoader() store._test_openAIDashboardLoaderOverride = { _, _, _, _ in try await blocker.awaitResult() } defer { store._test_openAIDashboardLoaderOverride = nil } let oldGuard = store.freshCodexOpenAIWebRefreshGuard() let oldRefreshTask = Task { await store.refreshOpenAIDashboardIfNeeded(force: true, expectedGuard: oldGuard) } await blocker.waitUntilStarted(count: 1) settings._test_liveSystemCodexAccount = ObservedSystemCodexAccount( email: "alpha@example.com", authFingerprint: "new-token-material", codexHomePath: "/Users/test/.codex", observedAt: Date(), identity: .providerAccount(id: "acct-alpha")) let newGuard = store.freshCodexOpenAIWebRefreshGuard() let newRefreshTask = Task { await store.refreshOpenAIDashboardIfNeeded(force: true, expectedGuard: newGuard) } let didStartFreshRefresh = await blocker.waitUntilStartedWithin(count: 2) #expect(didStartFreshRefresh) guard didStartFreshRefresh else { await blocker.resumeNext(with: .failure(TestRefreshError(message: "stale dashboard failure"))) await oldRefreshTask.value await newRefreshTask.value return } await blocker.resumeNext(with: .failure(TestRefreshError(message: "old dashboard failure"))) await blocker.resumeNext(with: .success(self.dashboard( email: "alpha@example.com", creditsRemaining: 64, usedPercent: 27))) await oldRefreshTask.value await newRefreshTask.value #expect(store.openAIDashboard?.creditsRemaining == 64) #expect(store.lastOpenAIDashboardError == nil) } @Test func `same account token refresh fingerprint change discards dashboard failure`() async throws { let settings = self.makeSettingsStore( suite: "CodexAccountScopedRefreshTests-token-refresh-dashboard-failure") let codexMetadata = try #require(ProviderDescriptorRegistry.metadata[.codex]) settings.setProviderEnabled(provider: .codex, metadata: codexMetadata, enabled: true) settings.refreshFrequency = .manual settings.openAIWebAccessEnabled = true settings.codexCookieSource = .auto settings._test_liveSystemCodexAccount = ObservedSystemCodexAccount( email: "alpha@example.com", authFingerprint: "old-token-material", codexHomePath: "/Users/test/.codex", observedAt: Date(), identity: .providerAccount(id: "acct-alpha")) defer { settings._test_liveSystemCodexAccount = nil } let store = self.makeUsageStore(settings: settings) let expectedGuard = store.freshCodexOpenAIWebRefreshGuard() store._test_openAIDashboardLoaderOverride = { _, _, _, _ in settings._test_liveSystemCodexAccount = ObservedSystemCodexAccount( email: "alpha@example.com", authFingerprint: "new-token-material", codexHomePath: "/Users/test/.codex", observedAt: Date(), identity: .providerAccount(id: "acct-alpha")) throw TestRefreshError(message: "old dashboard failure") } defer { store._test_openAIDashboardLoaderOverride = nil } await store.refreshOpenAIDashboardIfNeeded(force: true, expectedGuard: expectedGuard) #expect(store.openAIDashboard == nil) #expect(store.lastOpenAIDashboardError == nil) #expect(store.openAIDashboardRequiresLogin == false) } @Test func `same account token refresh fingerprint change applies dashboard policy failure`() async throws { let settings = self.makeSettingsStore( suite: "CodexAccountScopedRefreshTests-token-refresh-dashboard-policy-failure") let codexMetadata = try #require(ProviderDescriptorRegistry.metadata[.codex]) settings.setProviderEnabled(provider: .codex, metadata: codexMetadata, enabled: true) settings.refreshFrequency = .manual settings.openAIWebAccessEnabled = true settings.codexCookieSource = .auto settings._test_liveSystemCodexAccount = ObservedSystemCodexAccount( email: "alpha@example.com", authFingerprint: "old-token-material", codexHomePath: "/Users/test/.codex", observedAt: Date(), identity: .providerAccount(id: "acct-alpha")) defer { settings._test_liveSystemCodexAccount = nil } let store = self.makeUsageStore(settings: settings) let expectedGuard = store.freshCodexOpenAIWebRefreshGuard() settings._test_liveSystemCodexAccount = ObservedSystemCodexAccount( email: "alpha@example.com", authFingerprint: "new-token-material", codexHomePath: "/Users/test/.codex", observedAt: Date(), identity: .providerAccount(id: "acct-alpha")) await store.applyOpenAIDashboard( self.dashboard(email: "other@example.com", creditsRemaining: 64, usedPercent: 27), targetEmail: "alpha@example.com", expectedGuard: expectedGuard) #expect(store.openAIDashboard == nil) #expect(store.lastOpenAIDashboardError?.contains("OpenAI dashboard signed in as other@example.com") == true) #expect(store.openAIDashboardRequiresLogin == true) } @Test func `stacked visible refresh discards selected failure after managed token fingerprint rotates`() async throws { let settings = self.makeSettingsStore( suite: "CodexAccountScopedRefreshTests-selected-managed-token-failure") settings.refreshFrequency = .manual settings.multiAccountMenuLayout = .stacked let targetID = try #require(UUID(uuidString: "DDDDDDDD-EEEE-FFFF-AAAA-444444444444")) let siblingID = try #require(UUID(uuidString: "DDDDDDDD-EEEE-FFFF-AAAA-333333333333")) let targetHome = FileManager.default.temporaryDirectory .appendingPathComponent("codex-visible-managed-token-\(UUID().uuidString)", isDirectory: true) let siblingHome = FileManager.default.temporaryDirectory .appendingPathComponent("codex-visible-managed-token-sibling-\(UUID().uuidString)", isDirectory: true) try FileManager.default.createDirectory(at: targetHome, withIntermediateDirectories: true) try FileManager.default.createDirectory(at: siblingHome, withIntermediateDirectories: true) let targetAccount = ManagedCodexAccount( id: targetID, email: "managed-token@example.com", providerAccountID: "acct-managed-token", workspaceLabel: "Managed Team", workspaceAccountID: "acct-managed-token", authFingerprint: "old-managed-token", managedHomePath: targetHome.path, createdAt: 1, updatedAt: 2, lastAuthenticatedAt: 2) let updatedTarget = ManagedCodexAccount( id: targetID, email: "managed-token@example.com", providerAccountID: "acct-managed-token", workspaceLabel: "Managed Team", workspaceAccountID: "acct-managed-token", authFingerprint: "new-managed-token", managedHomePath: targetHome.path, createdAt: 1, updatedAt: 3, lastAuthenticatedAt: 3) let siblingAccount = ManagedCodexAccount( id: siblingID, email: "managed-token-sibling@example.com", providerAccountID: "acct-managed-token-sibling", workspaceLabel: "Sibling Team", workspaceAccountID: "acct-managed-token-sibling", authFingerprint: "sibling-managed-token", managedHomePath: siblingHome.path, createdAt: 1, updatedAt: 2, lastAuthenticatedAt: 2) let storeURL = try self.makeManagedAccountStoreURL(accounts: [targetAccount, siblingAccount]) defer { settings._test_managedCodexAccountStoreURL = nil try? FileManager.default.removeItem(at: storeURL) try? FileManager.default.removeItem(at: targetHome) try? FileManager.default.removeItem(at: siblingHome) } settings._test_managedCodexAccountStoreURL = storeURL settings.codexActiveSource = .managedAccount(id: targetID) let snapshotStore = RecordingCodexAccountUsageSnapshotStore(initialSnapshots: []) let store = UsageStore( fetcher: UsageFetcher(environment: [:]), browserDetection: BrowserDetection(cacheTTL: 0), settings: settings, codexAccountUsageSnapshotStore: snapshotStore, startupBehavior: .testing) let blocker = BlockingCodexFetchStrategy() let targetHomePath = targetHome.path let now = Date() self.installContextualCodexProvider(on: store) { context in if context.env["CODEX_HOME"] == targetHomePath { return try await blocker.awaitResult() } return UsageSnapshot( primary: RateWindow( usedPercent: 9, windowMinutes: 300, resetsAt: nil, resetDescription: nil), secondary: nil, updatedAt: now, identity: ProviderIdentitySnapshot( providerID: .codex, accountEmail: "managed-token-sibling@example.com", accountOrganization: nil, loginMethod: "Sibling Team")) } let refreshTask = Task { await store.refreshCodexVisibleAccountsForMenu() } await blocker.waitUntilStarted() try FileManagedCodexAccountStore(fileURL: storeURL).storeAccounts(ManagedCodexAccountSet( version: FileManagedCodexAccountStore.currentVersion, accounts: [updatedTarget, siblingAccount])) await blocker.resume(with: .failure(TestRefreshError(message: "old managed token failure"))) await refreshTask.value #expect(store.snapshots[.codex] == nil) #expect(store.errors[.codex] == nil) #expect(!store.codexAccountSnapshots.contains { $0.account.workspaceAccountID == "acct-managed-token" }) #expect(!snapshotStore.storedSnapshots.contains { $0.account.workspaceAccountID == "acct-managed-token" }) } @Test func `stacked visible refresh discards selected failure after managed auth file rotates`() async throws { let settings = self.makeSettingsStore( suite: "CodexAccountScopedRefreshTests-selected-managed-auth-file-failure") settings.refreshFrequency = .manual settings.multiAccountMenuLayout = .stacked let targetID = try #require(UUID(uuidString: "DDDDDDDD-EEEE-FFFF-AAAA-121212121212")) let siblingID = try #require(UUID(uuidString: "DDDDDDDD-EEEE-FFFF-AAAA-131313131313")) let targetHome = FileManager.default.temporaryDirectory .appendingPathComponent("codex-visible-managed-auth-file-\(UUID().uuidString)", isDirectory: true) let siblingHome = FileManager.default.temporaryDirectory .appendingPathComponent("codex-visible-managed-auth-file-sibling-\(UUID().uuidString)", isDirectory: true) try Self.writeCodexAuthFile( homeURL: targetHome, email: "managed-file-token@example.com", plan: "Pro", accountId: "acct-managed-file-token") let oldFingerprint = try #require(CodexAuthFingerprint.fingerprint(homePath: targetHome.path)) let targetAccount = ManagedCodexAccount( id: targetID, email: "managed-file-token@example.com", providerAccountID: "acct-managed-file-token", workspaceLabel: "Managed Team", workspaceAccountID: "acct-managed-file-token", authFingerprint: oldFingerprint, managedHomePath: targetHome.path, createdAt: 1, updatedAt: 2, lastAuthenticatedAt: 2) let siblingAccount = ManagedCodexAccount( id: siblingID, email: "managed-file-token-sibling@example.com", providerAccountID: "acct-managed-file-token-sibling", workspaceLabel: "Sibling Team", workspaceAccountID: "acct-managed-file-token-sibling", authFingerprint: "sibling-managed-file-token", managedHomePath: siblingHome.path, createdAt: 1, updatedAt: 2, lastAuthenticatedAt: 2) let storeURL = try self.makeManagedAccountStoreURL(accounts: [targetAccount, siblingAccount]) defer { settings._test_managedCodexAccountStoreURL = nil try? FileManager.default.removeItem(at: storeURL) try? FileManager.default.removeItem(at: targetHome) try? FileManager.default.removeItem(at: siblingHome) } settings._test_managedCodexAccountStoreURL = storeURL settings.codexActiveSource = .managedAccount(id: targetID) let snapshotStore = RecordingCodexAccountUsageSnapshotStore(initialSnapshots: []) let store = UsageStore( fetcher: UsageFetcher(environment: [:]), browserDetection: BrowserDetection(cacheTTL: 0), settings: settings, codexAccountUsageSnapshotStore: snapshotStore, startupBehavior: .testing) let blocker = BlockingCodexFetchStrategy() let targetHomePath = targetHome.path let now = Date() self.installContextualCodexProvider(on: store) { context in if context.env["CODEX_HOME"] == targetHomePath { return try await blocker.awaitResult() } return UsageSnapshot( primary: RateWindow( usedPercent: 9, windowMinutes: 300, resetsAt: nil, resetDescription: nil), secondary: nil, updatedAt: now, identity: ProviderIdentitySnapshot( providerID: .codex, accountEmail: "managed-file-token-sibling@example.com", accountOrganization: nil, loginMethod: "Sibling Team")) } let refreshTask = Task { await store.refreshCodexVisibleAccountsForMenu() } await blocker.waitUntilStarted() try Self.writeCodexAuthFile( homeURL: targetHome, email: "managed-file-token@example.com", plan: "Team", accountId: "acct-managed-file-token") let newFingerprint = try #require(CodexAuthFingerprint.fingerprint(homePath: targetHome.path)) #expect(newFingerprint != oldFingerprint) await blocker.resume(with: .failure(TestRefreshError(message: "old managed auth file failure"))) await refreshTask.value #expect(store.snapshots[.codex] == nil) #expect(store.errors[.codex] == nil) #expect(!store.codexAccountSnapshots.contains { $0.account.workspaceAccountID == "acct-managed-file-token" }) #expect(!snapshotStore.storedSnapshots.contains { $0.account.workspaceAccountID == "acct-managed-file-token" }) } @Test func `stacked visible refresh keeps selected failure when managed auth file rotated before start`() async throws { let settings = self.makeSettingsStore( suite: "CodexAccountScopedRefreshTests-selected-managed-auth-file-current-failure") settings.refreshFrequency = .manual settings.multiAccountMenuLayout = .stacked let targetID = try #require(UUID(uuidString: "DDDDDDDD-EEEE-FFFF-AAAA-161616161616")) let siblingID = try #require(UUID(uuidString: "DDDDDDDD-EEEE-FFFF-AAAA-171717171717")) let targetHome = FileManager.default.temporaryDirectory .appendingPathComponent("codex-visible-managed-current-failure-\(UUID().uuidString)", isDirectory: true) let siblingHome = FileManager.default.temporaryDirectory .appendingPathComponent("codex-visible-managed-current-sibling-\(UUID().uuidString)", isDirectory: true) try Self.writeCodexAuthFile( homeURL: targetHome, email: "managed-current-failure@example.com", plan: "Pro", accountId: "acct-managed-current-failure") let oldFingerprint = try #require(CodexAuthFingerprint.fingerprint(homePath: targetHome.path)) let targetAccount = ManagedCodexAccount( id: targetID, email: "managed-current-failure@example.com", providerAccountID: "acct-managed-current-failure", workspaceLabel: "Managed Team", workspaceAccountID: "acct-managed-current-failure", authFingerprint: oldFingerprint, managedHomePath: targetHome.path, createdAt: 1, updatedAt: 2, lastAuthenticatedAt: 2) let siblingAccount = ManagedCodexAccount( id: siblingID, email: "managed-current-sibling@example.com", providerAccountID: "acct-managed-current-sibling", workspaceLabel: "Sibling Team", workspaceAccountID: "acct-managed-current-sibling", authFingerprint: "sibling-managed-current", managedHomePath: siblingHome.path, createdAt: 1, updatedAt: 2, lastAuthenticatedAt: 2) let storeURL = try self.makeManagedAccountStoreURL(accounts: [targetAccount, siblingAccount]) defer { settings._test_managedCodexAccountStoreURL = nil try? FileManager.default.removeItem(at: storeURL) try? FileManager.default.removeItem(at: targetHome) try? FileManager.default.removeItem(at: siblingHome) } settings._test_managedCodexAccountStoreURL = storeURL settings.codexActiveSource = .managedAccount(id: targetID) try Self.writeCodexAuthFile( homeURL: targetHome, email: "managed-current-failure@example.com", plan: "Team", accountId: "acct-managed-current-failure") let newFingerprint = try #require(CodexAuthFingerprint.fingerprint(homePath: targetHome.path)) #expect(newFingerprint != oldFingerprint) let snapshotStore = RecordingCodexAccountUsageSnapshotStore(initialSnapshots: []) let store = UsageStore( fetcher: UsageFetcher(environment: [:]), browserDetection: BrowserDetection(cacheTTL: 0), settings: settings, codexAccountUsageSnapshotStore: snapshotStore, startupBehavior: .testing) let targetHomePath = targetHome.path let now = Date() self.installContextualCodexProvider(on: store) { context in if context.env["CODEX_HOME"] == targetHomePath { throw TestRefreshError(message: "current managed auth file failure") } return UsageSnapshot( primary: RateWindow( usedPercent: 9, windowMinutes: 300, resetsAt: nil, resetDescription: nil), secondary: nil, updatedAt: now, identity: ProviderIdentitySnapshot( providerID: .codex, accountEmail: "managed-current-sibling@example.com", accountOrganization: nil, loginMethod: "Sibling Team")) } await store.refreshCodexVisibleAccountsForMenu() #expect(store.errors[.codex] == "current managed auth file failure") let targetSnapshot = try #require(store.codexAccountSnapshots.first { $0.account.workspaceAccountID == "acct-managed-current-failure" }) #expect(targetSnapshot.error == "current managed auth file failure") #expect(targetSnapshot.account.authFingerprint == newFingerprint) let persistedTargetSnapshot = try #require(snapshotStore.storedSnapshots.first { $0.account.workspaceAccountID == "acct-managed-current-failure" }) #expect(persistedTargetSnapshot.error == "current managed auth file failure") #expect(persistedTargetSnapshot.account.authFingerprint == newFingerprint) } @Test func `stacked visible refresh discards selected success after managed auth file switches accounts`() async throws { let settings = self.makeSettingsStore( suite: "CodexAccountScopedRefreshTests-selected-managed-auth-file-success") settings.refreshFrequency = .manual settings.multiAccountMenuLayout = .stacked let targetID = try #require(UUID(uuidString: "DDDDDDDD-EEEE-FFFF-AAAA-141414141414")) let siblingID = try #require(UUID(uuidString: "DDDDDDDD-EEEE-FFFF-AAAA-151515151515")) let targetHome = FileManager.default.temporaryDirectory .appendingPathComponent("codex-visible-managed-auth-success-\(UUID().uuidString)", isDirectory: true) let siblingHome = FileManager.default.temporaryDirectory .appendingPathComponent( "codex-visible-managed-auth-success-sibling-\(UUID().uuidString)", isDirectory: true) try Self.writeCodexAuthFile( homeURL: targetHome, email: "managed-old-success@example.com", plan: "Pro", accountId: "acct-managed-old-success") let oldFingerprint = try #require(CodexAuthFingerprint.fingerprint(homePath: targetHome.path)) let targetAccount = ManagedCodexAccount( id: targetID, email: "managed-old-success@example.com", providerAccountID: "acct-managed-old-success", workspaceLabel: "Managed Team", workspaceAccountID: "acct-managed-old-success", authFingerprint: oldFingerprint, managedHomePath: targetHome.path, createdAt: 1, updatedAt: 2, lastAuthenticatedAt: 2) let siblingAccount = ManagedCodexAccount( id: siblingID, email: "managed-success-sibling@example.com", providerAccountID: "acct-managed-success-sibling", workspaceLabel: "Sibling Team", workspaceAccountID: "acct-managed-success-sibling", authFingerprint: "sibling-managed-success", managedHomePath: siblingHome.path, createdAt: 1, updatedAt: 2, lastAuthenticatedAt: 2) let storeURL = try self.makeManagedAccountStoreURL(accounts: [targetAccount, siblingAccount]) defer { settings._test_managedCodexAccountStoreURL = nil try? FileManager.default.removeItem(at: storeURL) try? FileManager.default.removeItem(at: targetHome) try? FileManager.default.removeItem(at: siblingHome) } settings._test_managedCodexAccountStoreURL = storeURL settings.codexActiveSource = .managedAccount(id: targetID) let snapshotStore = RecordingCodexAccountUsageSnapshotStore(initialSnapshots: []) let store = UsageStore( fetcher: UsageFetcher(environment: [:]), browserDetection: BrowserDetection(cacheTTL: 0), settings: settings, codexAccountUsageSnapshotStore: snapshotStore, startupBehavior: .testing) let blocker = BlockingCodexFetchStrategy() let targetHomePath = targetHome.path let now = Date() self.installContextualCodexProvider(on: store) { context in if context.env["CODEX_HOME"] == targetHomePath { return try await blocker.awaitResult() } return UsageSnapshot( primary: RateWindow( usedPercent: 9, windowMinutes: 300, resetsAt: nil, resetDescription: nil), secondary: nil, updatedAt: now, identity: ProviderIdentitySnapshot( providerID: .codex, accountEmail: "managed-success-sibling@example.com", accountOrganization: nil, loginMethod: "Sibling Team")) } let refreshTask = Task { await store.refreshCodexVisibleAccountsForMenu() } await blocker.waitUntilStarted() try Self.writeCodexAuthFile( homeURL: targetHome, email: "managed-new-success@example.com", plan: "Pro", accountId: "acct-managed-new-success") let newFingerprint = try #require(CodexAuthFingerprint.fingerprint(homePath: targetHome.path)) #expect(newFingerprint != oldFingerprint) await blocker.resume(with: .success(UsageSnapshot( primary: RateWindow( usedPercent: 64, windowMinutes: 300, resetsAt: nil, resetDescription: nil), secondary: nil, updatedAt: now, identity: ProviderIdentitySnapshot( providerID: .codex, accountEmail: "managed-old-success@example.com", accountOrganization: nil, loginMethod: "Managed Team")))) await refreshTask.value #expect(store.snapshots[.codex] == nil) #expect(store.errors[.codex] == nil) #expect(!store.codexAccountSnapshots.contains { $0.account.workspaceAccountID == "acct-managed-old-success" }) #expect(!snapshotStore.storedSnapshots.contains { $0.account.workspaceAccountID == "acct-managed-old-success" }) } @Test func `stacked visible refresh keeps migrated managed account after token rotation`() async throws { let settings = self.makeSettingsStore( suite: "CodexAccountScopedRefreshTests-migrated-managed-token-rotation") settings.refreshFrequency = .manual settings.multiAccountMenuLayout = .stacked let targetID = try #require(UUID(uuidString: "DDDDDDDD-EEEE-FFFF-AAAA-171717171717")) let siblingID = try #require(UUID(uuidString: "DDDDDDDD-EEEE-FFFF-AAAA-181818181818")) let targetHome = FileManager.default.temporaryDirectory .appendingPathComponent("codex-visible-migrated-managed-\(UUID().uuidString)", isDirectory: true) let siblingHome = FileManager.default.temporaryDirectory .appendingPathComponent( "codex-visible-migrated-managed-sibling-\(UUID().uuidString)", isDirectory: true) try Self.writeCodexAuthFile( homeURL: targetHome, email: "migrated-managed@example.com", plan: "Pro", accountId: "acct-migrated-managed") try Self.writeCodexAuthFile( homeURL: siblingHome, email: "migrated-sibling@example.com", plan: "Pro", accountId: "acct-migrated-sibling") let oldFingerprint = try #require(CodexAuthFingerprint.fingerprint(homePath: targetHome.path)) let siblingFingerprint = try #require(CodexAuthFingerprint.fingerprint(homePath: siblingHome.path)) let targetAccount = ManagedCodexAccount( id: targetID, email: "migrated-managed@example.com", providerAccountID: "acct-migrated-managed", workspaceLabel: "Managed Team", authFingerprint: oldFingerprint, managedHomePath: targetHome.path, createdAt: 1, updatedAt: 2, lastAuthenticatedAt: 2) let siblingAccount = ManagedCodexAccount( id: siblingID, email: "migrated-sibling@example.com", providerAccountID: "acct-migrated-sibling", workspaceLabel: "Sibling Team", authFingerprint: siblingFingerprint, managedHomePath: siblingHome.path, createdAt: 1, updatedAt: 2, lastAuthenticatedAt: 2) let storeURL = try self.makeManagedAccountStoreURL(accounts: [targetAccount, siblingAccount]) defer { settings._test_managedCodexAccountStoreURL = nil try? FileManager.default.removeItem(at: storeURL) try? FileManager.default.removeItem(at: targetHome) try? FileManager.default.removeItem(at: siblingHome) } settings._test_managedCodexAccountStoreURL = storeURL settings.codexActiveSource = .managedAccount(id: targetID) let snapshotStore = RecordingCodexAccountUsageSnapshotStore(initialSnapshots: []) let store = UsageStore( fetcher: UsageFetcher(environment: [:]), browserDetection: BrowserDetection(cacheTTL: 0), settings: settings, codexAccountUsageSnapshotStore: snapshotStore, startupBehavior: .testing) let blocker = BlockingCodexFetchStrategy() let targetHomePath = targetHome.path let now = Date() self.installContextualCodexProvider(on: store) { context in if context.env["CODEX_HOME"] == targetHomePath { return try await blocker.awaitResult() } return UsageSnapshot( primary: RateWindow( usedPercent: 9, windowMinutes: 300, resetsAt: nil, resetDescription: nil), secondary: nil, updatedAt: now, identity: ProviderIdentitySnapshot( providerID: .codex, accountEmail: "migrated-sibling@example.com", accountOrganization: nil, loginMethod: "Sibling Team")) } let refreshTask = Task { await store.refreshCodexVisibleAccountsForMenu() } await blocker.waitUntilStarted() try Self.writeCodexAuthFile( homeURL: targetHome, email: "migrated-managed@example.com", plan: "Team", accountId: "acct-migrated-managed") let newFingerprint = try #require(CodexAuthFingerprint.fingerprint(homePath: targetHome.path)) #expect(newFingerprint != oldFingerprint) await blocker.resume(with: .success(UsageSnapshot( primary: RateWindow( usedPercent: 64, windowMinutes: 300, resetsAt: nil, resetDescription: nil), secondary: nil, updatedAt: now, identity: ProviderIdentitySnapshot( providerID: .codex, accountEmail: "migrated-managed@example.com", accountOrganization: nil, loginMethod: "Managed Team")))) await refreshTask.value let selectedSnapshot = try #require(store.snapshots[.codex]) #expect(selectedSnapshot.primary?.usedPercent == 64) let targetRow = try #require(store.codexAccountSnapshots.first { $0.account.workspaceAccountID == "acct-migrated-managed" }) #expect(targetRow.account.authFingerprint == newFingerprint) #expect(targetRow.snapshot?.primary?.usedPercent == 64) let persistedTarget = try #require(snapshotStore.storedSnapshots.first { $0.account.workspaceAccountID == "acct-migrated-managed" }) #expect(persistedTarget.account.authFingerprint == newFingerprint) #expect(persistedTarget.snapshot?.primary?.usedPercent == 64) } @Test func `stacked visible refresh discards selected success after managed auth file email changes`() async throws { let settings = self.makeSettingsStore( suite: "CodexAccountScopedRefreshTests-selected-managed-auth-email-success") settings.refreshFrequency = .manual settings.multiAccountMenuLayout = .stacked let targetID = try #require(UUID(uuidString: "DDDDDDDD-EEEE-FFFF-AAAA-191919191919")) let siblingID = try #require(UUID(uuidString: "DDDDDDDD-EEEE-FFFF-AAAA-202020202020")) let targetHome = FileManager.default.temporaryDirectory .appendingPathComponent("codex-visible-managed-auth-email-\(UUID().uuidString)", isDirectory: true) let siblingHome = FileManager.default.temporaryDirectory .appendingPathComponent( "codex-visible-managed-auth-email-sibling-\(UUID().uuidString)", isDirectory: true) try Self.writeCodexAuthFile( homeURL: targetHome, email: "managed-old-email@example.com", plan: "Pro", accountId: "acct-managed-email-same") try Self.writeCodexAuthFile( homeURL: siblingHome, email: "managed-email-sibling@example.com", plan: "Pro", accountId: "acct-managed-email-sibling") let oldFingerprint = try #require(CodexAuthFingerprint.fingerprint(homePath: targetHome.path)) let siblingFingerprint = try #require(CodexAuthFingerprint.fingerprint(homePath: siblingHome.path)) let targetAccount = ManagedCodexAccount( id: targetID, email: "managed-old-email@example.com", providerAccountID: "acct-managed-email-same", workspaceLabel: "Managed Team", workspaceAccountID: "acct-managed-email-same", authFingerprint: oldFingerprint, managedHomePath: targetHome.path, createdAt: 1, updatedAt: 2, lastAuthenticatedAt: 2) let siblingAccount = ManagedCodexAccount( id: siblingID, email: "managed-email-sibling@example.com", providerAccountID: "acct-managed-email-sibling", workspaceLabel: "Sibling Team", workspaceAccountID: "acct-managed-email-sibling", authFingerprint: siblingFingerprint, managedHomePath: siblingHome.path, createdAt: 1, updatedAt: 2, lastAuthenticatedAt: 2) let storeURL = try self.makeManagedAccountStoreURL(accounts: [targetAccount, siblingAccount]) defer { settings._test_managedCodexAccountStoreURL = nil try? FileManager.default.removeItem(at: storeURL) try? FileManager.default.removeItem(at: targetHome) try? FileManager.default.removeItem(at: siblingHome) } settings._test_managedCodexAccountStoreURL = storeURL settings.codexActiveSource = .managedAccount(id: targetID) let snapshotStore = RecordingCodexAccountUsageSnapshotStore(initialSnapshots: []) let store = UsageStore( fetcher: UsageFetcher(environment: [:]), browserDetection: BrowserDetection(cacheTTL: 0), settings: settings, codexAccountUsageSnapshotStore: snapshotStore, startupBehavior: .testing) let blocker = BlockingCodexFetchStrategy() let targetHomePath = targetHome.path let now = Date() self.installContextualCodexProvider(on: store) { context in if context.env["CODEX_HOME"] == targetHomePath { return try await blocker.awaitResult() } return UsageSnapshot( primary: RateWindow( usedPercent: 9, windowMinutes: 300, resetsAt: nil, resetDescription: nil), secondary: nil, updatedAt: now, identity: ProviderIdentitySnapshot( providerID: .codex, accountEmail: "managed-email-sibling@example.com", accountOrganization: nil, loginMethod: "Sibling Team")) } let refreshTask = Task { await store.refreshCodexVisibleAccountsForMenu() } await blocker.waitUntilStarted() try Self.writeCodexAuthFile( homeURL: targetHome, email: "managed-new-email@example.com", plan: "Pro", accountId: "acct-managed-email-same") let newFingerprint = try #require(CodexAuthFingerprint.fingerprint(homePath: targetHome.path)) #expect(newFingerprint != oldFingerprint) await blocker.resume(with: .success(UsageSnapshot( primary: RateWindow( usedPercent: 64, windowMinutes: 300, resetsAt: nil, resetDescription: nil), secondary: nil, updatedAt: now, identity: ProviderIdentitySnapshot( providerID: .codex, accountEmail: "managed-old-email@example.com", accountOrganization: nil, loginMethod: "Managed Team")))) await refreshTask.value #expect(store.snapshots[.codex] == nil) #expect(store.errors[.codex] == nil) #expect(!store.codexAccountSnapshots.contains { $0.account.workspaceAccountID == "acct-managed-email-same" }) #expect(!snapshotStore.storedSnapshots.contains { $0.account.workspaceAccountID == "acct-managed-email-same" }) } @Test func `managed failure guard reads current auth file fingerprint`() throws { let settings = self.makeSettingsStore( suite: "CodexAccountScopedRefreshTests-managed-auth-file-fingerprint") settings.refreshFrequency = .manual let accountID = try #require(UUID(uuidString: "DDDDDDDD-EEEE-FFFF-AAAA-555555555555")) let managedHome = FileManager.default.temporaryDirectory .appendingPathComponent("codex-managed-auth-file-\(UUID().uuidString)", isDirectory: true) try Self.writeCodexAuthFile( homeURL: managedHome, email: "managed-auth@example.com", plan: "Pro", accountId: "acct-managed-auth") let oldFingerprint = try #require(CodexAuthFingerprint.fingerprint(homePath: managedHome.path)) let managedAccount = ManagedCodexAccount( id: accountID, email: "managed-auth@example.com", providerAccountID: "acct-managed-auth", workspaceLabel: "Managed Auth", workspaceAccountID: "acct-managed-auth", authFingerprint: oldFingerprint, managedHomePath: managedHome.path, createdAt: 1, updatedAt: 2, lastAuthenticatedAt: 2) let storeURL = try self.makeManagedAccountStoreURL(accounts: [managedAccount]) defer { settings._test_managedCodexAccountStoreURL = nil try? FileManager.default.removeItem(at: storeURL) try? FileManager.default.removeItem(at: managedHome) } settings._test_managedCodexAccountStoreURL = storeURL settings.codexActiveSource = .managedAccount(id: accountID) let store = self.makeUsageStore(settings: settings) let expectedGuard = store.freshCodexAccountScopedRefreshGuard() #expect(expectedGuard.authFingerprint == oldFingerprint) try Self.writeCodexAuthFile( homeURL: managedHome, email: "managed-auth@example.com", plan: "Team", accountId: "acct-managed-auth") let newFingerprint = try #require(CodexAuthFingerprint.fingerprint(homePath: managedHome.path)) #expect(newFingerprint != oldFingerprint) #expect(store.freshCodexAccountScopedRefreshGuard().authFingerprint == newFingerprint) #expect(!store.shouldApplyCodexScopedFailure(expectedGuard: expectedGuard)) #expect(!store.shouldApplyCodexScopedNonUsageFailure(expectedGuard: expectedGuard)) try FileManager.default.removeItem(at: managedHome) #expect(store.freshCodexAccountScopedRefreshGuard().authFingerprint == nil) let staleUsage = UsageSnapshot( primary: RateWindow( usedPercent: 41, windowMinutes: 300, resetsAt: nil, resetDescription: nil), secondary: nil, updatedAt: Date(), identity: ProviderIdentitySnapshot( providerID: .codex, accountEmail: "managed-auth@example.com", accountOrganization: nil, loginMethod: "Managed Auth")) #expect(!store.shouldApplyCodexUsageResult(expectedGuard: expectedGuard, usage: staleUsage)) #expect(!store.shouldApplyCodexScopedFailure(expectedGuard: expectedGuard)) #expect(!store.shouldApplyCodexScopedNonUsageFailure(expectedGuard: expectedGuard)) } @Test func `stale auth fingerprint cache at refresh start keeps current codex usage success`() async { SettingsStore.codexAccountReconciliationSnapshotCacheIntervalOverrideForTesting = 60 let settings = self.makeSettingsStore( suite: "CodexAccountScopedRefreshTests-stale-start-cache-current-auth") defer { SettingsStore.codexAccountReconciliationSnapshotCacheIntervalOverrideForTesting = nil settings._test_liveSystemCodexAccount = nil } settings.refreshFrequency = .manual settings._test_liveSystemCodexAccount = ObservedSystemCodexAccount( email: "alpha@example.com", authFingerprint: "old-email-only-auth", codexHomePath: "/Users/test/.codex", observedAt: Date(), identity: .emailOnly(normalizedEmail: "alpha@example.com")) let staleReconciliationSnapshot = settings.codexAccountReconciliationSnapshot settings._test_liveSystemCodexAccount = ObservedSystemCodexAccount( email: "alpha@example.com", authFingerprint: "new-email-only-auth", codexHomePath: "/Users/test/.codex", observedAt: Date(), identity: .emailOnly(normalizedEmail: "alpha@example.com")) settings.cachedCodexAccountReconciliationSnapshot = CachedCodexAccountReconciliationSnapshot( activeSource: .liveSystem, loadedAt: Date(), snapshot: staleReconciliationSnapshot) 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() await blocker.resume(with: .success(self.codexSnapshot(email: "alpha@example.com", usedPercent: 33))) await refreshTask.value #expect(store.snapshots[.codex]?.primary?.usedPercent == 33) #expect(store.lastCodexAccountScopedRefreshGuard?.authFingerprint == "new-email-only-auth") #expect(store.errors[.codex] == nil) } @Test func `same provider account live email change discards stale codex usage success`() async { SettingsStore.codexAccountReconciliationSnapshotCacheIntervalOverrideForTesting = 60 let settings = self.makeSettingsStore( suite: "CodexAccountScopedRefreshTests-provider-email-change") defer { SettingsStore.codexAccountReconciliationSnapshotCacheIntervalOverrideForTesting = nil settings._test_liveSystemCodexAccount = nil } settings.refreshFrequency = .manual settings._test_liveSystemCodexAccount = ObservedSystemCodexAccount( email: "old@example.com", authFingerprint: "old-provider-auth", codexHomePath: "/Users/test/.codex", observedAt: Date(), identity: .providerAccount(id: "acct-shared")) let staleReconciliationSnapshot = settings.codexAccountReconciliationSnapshot 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 = ObservedSystemCodexAccount( email: "new@example.com", authFingerprint: "new-provider-auth", codexHomePath: "/Users/test/.codex", observedAt: Date(), identity: .providerAccount(id: "acct-shared")) settings.cachedCodexAccountReconciliationSnapshot = CachedCodexAccountReconciliationSnapshot( activeSource: .liveSystem, loadedAt: Date(), snapshot: staleReconciliationSnapshot) await blocker.resume(with: .success(self.codexSnapshot(email: "old@example.com", usedPercent: 25))) await refreshTask.value #expect(store.snapshots[.codex] == nil) #expect(store.errors[.codex] == nil) } @Test func `same provider account managed email change discards stale codex usage success`() async throws { let settings = self.makeSettingsStore( suite: "CodexAccountScopedRefreshTests-managed-provider-email-change") settings.refreshFrequency = .manual let accountID = try #require(UUID(uuidString: "DDDDDDDD-EEEE-FFFF-AAAA-161616161616")) let managedHome = FileManager.default.temporaryDirectory .appendingPathComponent("codex-managed-provider-email-\(UUID().uuidString)", isDirectory: true) try Self.writeCodexAuthFile( homeURL: managedHome, email: "old-managed@example.com", plan: "Pro", accountId: "acct-managed-shared") let oldFingerprint = try #require(CodexAuthFingerprint.fingerprint(homePath: managedHome.path)) let managedAccount = ManagedCodexAccount( id: accountID, email: "old-managed@example.com", providerAccountID: "acct-managed-shared", workspaceLabel: "Managed Team", workspaceAccountID: "acct-managed-shared", authFingerprint: oldFingerprint, managedHomePath: managedHome.path, createdAt: 1, updatedAt: 2, lastAuthenticatedAt: 2) let storeURL = try self.makeManagedAccountStoreURL(accounts: [managedAccount]) defer { settings._test_managedCodexAccountStoreURL = nil try? FileManager.default.removeItem(at: storeURL) try? FileManager.default.removeItem(at: managedHome) } settings._test_managedCodexAccountStoreURL = storeURL settings.codexActiveSource = .managedAccount(id: accountID) 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() try Self.writeCodexAuthFile( homeURL: managedHome, email: "new-managed@example.com", plan: "Pro", accountId: "acct-managed-shared") let newFingerprint = try #require(CodexAuthFingerprint.fingerprint(homePath: managedHome.path)) #expect(newFingerprint != oldFingerprint) await blocker.resume(with: .success(self.codexSnapshot(email: "old-managed@example.com", usedPercent: 25))) await refreshTask.value #expect(store.snapshots[.codex] == nil) #expect(store.errors[.codex] == nil) } @Test func `managed codex usage success without email applies when auth guard matches`() async throws { let settings = self.makeSettingsStore( suite: "CodexAccountScopedRefreshTests-managed-usage-without-email") settings.refreshFrequency = .manual let accountID = try #require(UUID(uuidString: "DDDDDDDD-EEEE-FFFF-AAAA-171717171717")) let managedHome = FileManager.default.temporaryDirectory .appendingPathComponent("codex-managed-usage-without-email-\(UUID().uuidString)", isDirectory: true) try Self.writeCodexAuthFile( homeURL: managedHome, email: "email-less-managed@example.com", plan: "Pro", accountId: "acct-managed-email-less") let authFingerprint = try #require(CodexAuthFingerprint.fingerprint(homePath: managedHome.path)) let managedAccount = ManagedCodexAccount( id: accountID, email: "email-less-managed@example.com", providerAccountID: "acct-managed-email-less", workspaceLabel: "Managed Team", workspaceAccountID: "acct-managed-email-less", authFingerprint: authFingerprint, managedHomePath: managedHome.path, createdAt: 1, updatedAt: 2, lastAuthenticatedAt: 2) let storeURL = try self.makeManagedAccountStoreURL(accounts: [managedAccount]) defer { settings._test_managedCodexAccountStoreURL = nil try? FileManager.default.removeItem(at: storeURL) try? FileManager.default.removeItem(at: managedHome) } settings._test_managedCodexAccountStoreURL = storeURL settings.codexActiveSource = .managedAccount(id: accountID) 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() await blocker.resume(with: .success(UsageSnapshot( primary: RateWindow( usedPercent: 25, windowMinutes: 300, resetsAt: nil, resetDescription: nil), secondary: nil, updatedAt: Date()))) await refreshTask.value #expect(store.snapshots[.codex]?.primary?.usedPercent == 25) #expect(store.snapshots[.codex]?.accountEmail(for: .codex) == "email-less-managed@example.com") #expect(store.errors[.codex] == nil) #expect(store.lastCodexAccountScopedRefreshGuard?.accountKey == "email-less-managed@example.com") #expect(store.codexAccountSnapshots.first?.account.email == "email-less-managed@example.com") #expect(store.codexAccountSnapshots.first?.snapshot?.accountEmail(for: .codex) == "email-less-managed@example.com") } @Test func `same provider account managed email change discards stale codex usage success without email`() async throws { let settings = self.makeSettingsStore( suite: "CodexAccountScopedRefreshTests-managed-provider-email-change-without-email") settings.refreshFrequency = .manual let accountID = try #require(UUID(uuidString: "DDDDDDDD-EEEE-FFFF-AAAA-181818181818")) let managedHome = FileManager.default.temporaryDirectory .appendingPathComponent( "codex-managed-provider-email-without-email-\(UUID().uuidString)", isDirectory: true) try Self.writeCodexAuthFile( homeURL: managedHome, email: "old-managed-empty@example.com", plan: "Pro", accountId: "acct-managed-shared-empty") let oldFingerprint = try #require(CodexAuthFingerprint.fingerprint(homePath: managedHome.path)) let managedAccount = ManagedCodexAccount( id: accountID, email: "old-managed-empty@example.com", providerAccountID: "acct-managed-shared-empty", workspaceLabel: "Managed Team", workspaceAccountID: "acct-managed-shared-empty", authFingerprint: oldFingerprint, managedHomePath: managedHome.path, createdAt: 1, updatedAt: 2, lastAuthenticatedAt: 2) let storeURL = try self.makeManagedAccountStoreURL(accounts: [managedAccount]) defer { settings._test_managedCodexAccountStoreURL = nil try? FileManager.default.removeItem(at: storeURL) try? FileManager.default.removeItem(at: managedHome) } settings._test_managedCodexAccountStoreURL = storeURL settings.codexActiveSource = .managedAccount(id: accountID) 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() try Self.writeCodexAuthFile( homeURL: managedHome, email: "new-managed-empty@example.com", plan: "Pro", accountId: "acct-managed-shared-empty") let newFingerprint = try #require(CodexAuthFingerprint.fingerprint(homePath: managedHome.path)) #expect(newFingerprint != oldFingerprint) await blocker.resume(with: .success(UsageSnapshot( primary: RateWindow( usedPercent: 25, windowMinutes: 300, resetsAt: nil, resetDescription: nil), secondary: nil, updatedAt: Date()))) await refreshTask.value #expect(store.snapshots[.codex] == nil) #expect(store.errors[.codex] == nil) } @Test func `same email email-only auth fingerprint switch discards stale codex usage success`() async { SettingsStore.codexAccountReconciliationSnapshotCacheIntervalOverrideForTesting = 60 let settings = self.makeSettingsStore( suite: "CodexAccountScopedRefreshTests-email-only-fingerprint-switch") defer { SettingsStore.codexAccountReconciliationSnapshotCacheIntervalOverrideForTesting = nil settings._test_liveSystemCodexAccount = nil } settings.refreshFrequency = .manual settings._test_liveSystemCodexAccount = ObservedSystemCodexAccount( email: "alpha@example.com", authFingerprint: "old-email-only-auth", codexHomePath: "/Users/test/.codex", observedAt: Date(), identity: .emailOnly(normalizedEmail: "alpha@example.com")) let staleReconciliationSnapshot = settings.codexAccountReconciliationSnapshot 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 = ObservedSystemCodexAccount( email: "alpha@example.com", authFingerprint: "new-email-only-auth", codexHomePath: "/Users/test/.codex", observedAt: Date(), identity: .emailOnly(normalizedEmail: "alpha@example.com")) settings.cachedCodexAccountReconciliationSnapshot = CachedCodexAccountReconciliationSnapshot( activeSource: .liveSystem, loadedAt: Date(), snapshot: staleReconciliationSnapshot) 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) } }