531 lines
22 KiB
Swift
531 lines
22 KiB
Swift
import CodexBarCore
|
|
import Foundation
|
|
import Testing
|
|
@testable import CodexBar
|
|
|
|
@Suite(.serialized)
|
|
@MainActor
|
|
struct CodexPresentationCharacterizationTests {
|
|
@Test
|
|
func `weekly only Codex menu rendering omits session row`() {
|
|
let settings = self.makeSettingsStore(suite: "CodexPresentationCharacterizationTests-weekly-only")
|
|
settings.statusChecksEnabled = false
|
|
|
|
let fetcher = UsageFetcher()
|
|
let store = UsageStore(
|
|
fetcher: fetcher,
|
|
browserDetection: BrowserDetection(cacheTTL: 0),
|
|
settings: settings,
|
|
startupBehavior: .testing)
|
|
store._setSnapshotForTesting(
|
|
UsageSnapshot(
|
|
primary: nil,
|
|
secondary: RateWindow(
|
|
usedPercent: 20,
|
|
windowMinutes: 10080,
|
|
resetsAt: nil,
|
|
resetDescription: "Apr 6, 2026"),
|
|
updatedAt: Date(),
|
|
identity: ProviderIdentitySnapshot(
|
|
providerID: .codex,
|
|
accountEmail: "codex@example.com",
|
|
accountOrganization: nil,
|
|
loginMethod: "free")),
|
|
provider: .codex)
|
|
|
|
let descriptor = MenuDescriptor.build(
|
|
provider: .codex,
|
|
store: store,
|
|
settings: settings,
|
|
account: fetcher.loadAccountInfo(),
|
|
updateReady: false,
|
|
includeContextualActions: false)
|
|
|
|
let lines = self.textLines(from: descriptor)
|
|
#expect(!lines.contains(where: { $0.hasPrefix("Session:") }))
|
|
#expect(lines.contains(where: { $0.hasPrefix("Weekly:") }))
|
|
}
|
|
|
|
@Test
|
|
func `Codex menu does not surface identity from another provider snapshot`() {
|
|
let settings = self.makeSettingsStore(suite: "CodexPresentationCharacterizationTests-provider-silo")
|
|
settings.statusChecksEnabled = false
|
|
|
|
let fetcher = UsageFetcher(environment: [:])
|
|
let store = UsageStore(
|
|
fetcher: fetcher,
|
|
browserDetection: BrowserDetection(cacheTTL: 0),
|
|
settings: settings,
|
|
startupBehavior: .testing)
|
|
store._setSnapshotForTesting(
|
|
UsageSnapshot(
|
|
primary: RateWindow(usedPercent: 12, windowMinutes: 300, resetsAt: nil, resetDescription: nil),
|
|
secondary: nil,
|
|
updatedAt: Date(),
|
|
identity: ProviderIdentitySnapshot(
|
|
providerID: .codex,
|
|
accountEmail: "codex@example.com",
|
|
accountOrganization: nil,
|
|
loginMethod: "free")),
|
|
provider: .codex)
|
|
store._setSnapshotForTesting(
|
|
UsageSnapshot(
|
|
primary: RateWindow(usedPercent: 40, windowMinutes: 300, resetsAt: nil, resetDescription: nil),
|
|
secondary: nil,
|
|
updatedAt: Date(),
|
|
identity: ProviderIdentitySnapshot(
|
|
providerID: .claude,
|
|
accountEmail: "claude@example.com",
|
|
accountOrganization: nil,
|
|
loginMethod: "max")),
|
|
provider: .claude)
|
|
|
|
let descriptor = MenuDescriptor.build(
|
|
provider: .codex,
|
|
store: store,
|
|
settings: settings,
|
|
account: fetcher.loadAccountInfo(),
|
|
updateReady: false,
|
|
includeContextualActions: false)
|
|
|
|
let lines = self.textLines(from: descriptor)
|
|
#expect(lines.contains("Account: codex@example.com"))
|
|
#expect(lines.contains("Plan: Free"))
|
|
#expect(!lines.contains("Account: claude@example.com"))
|
|
#expect(!lines.contains("Plan: Max"))
|
|
}
|
|
|
|
@Test
|
|
func `Codex menu omits account row when hiding personal info`() {
|
|
let settings = self.makeSettingsStore(suite: "CodexPresentationCharacterizationTests-hide-account")
|
|
settings.statusChecksEnabled = false
|
|
settings.hidePersonalInfo = true
|
|
|
|
let fetcher = UsageFetcher(environment: [:])
|
|
let store = UsageStore(
|
|
fetcher: fetcher,
|
|
browserDetection: BrowserDetection(cacheTTL: 0),
|
|
settings: settings,
|
|
startupBehavior: .testing)
|
|
store._setSnapshotForTesting(
|
|
UsageSnapshot(
|
|
primary: RateWindow(usedPercent: 12, windowMinutes: 300, resetsAt: nil, resetDescription: nil),
|
|
secondary: nil,
|
|
updatedAt: Date(),
|
|
identity: ProviderIdentitySnapshot(
|
|
providerID: .codex,
|
|
accountEmail: "codex@example.com",
|
|
accountOrganization: nil,
|
|
loginMethod: "free")),
|
|
provider: .codex)
|
|
|
|
let descriptor = MenuDescriptor.build(
|
|
provider: .codex,
|
|
store: store,
|
|
settings: settings,
|
|
account: fetcher.loadAccountInfo(),
|
|
updateReady: false,
|
|
includeContextualActions: false)
|
|
|
|
let lines = self.textLines(from: descriptor)
|
|
#expect(!lines.contains(where: { $0.hasPrefix("Account:") }))
|
|
#expect(!lines.contains(where: { $0.contains("codex@example.com") }))
|
|
#expect(!lines.contains(where: { $0.contains("Hidden") }))
|
|
#expect(lines.contains("Plan: Free"))
|
|
}
|
|
|
|
@Test
|
|
func `Codex menu maps prolite plan to multiplier display name`() {
|
|
let settings = self.makeSettingsStore(suite: "CodexPresentationCharacterizationTests-prolite")
|
|
settings.statusChecksEnabled = false
|
|
|
|
let fetcher = UsageFetcher(environment: [:])
|
|
let store = UsageStore(
|
|
fetcher: fetcher,
|
|
browserDetection: BrowserDetection(cacheTTL: 0),
|
|
settings: settings,
|
|
startupBehavior: .testing)
|
|
store._setSnapshotForTesting(
|
|
UsageSnapshot(
|
|
primary: RateWindow(usedPercent: 12, windowMinutes: 300, resetsAt: nil, resetDescription: nil),
|
|
secondary: nil,
|
|
updatedAt: Date(),
|
|
identity: ProviderIdentitySnapshot(
|
|
providerID: .codex,
|
|
accountEmail: "codex@example.com",
|
|
accountOrganization: nil,
|
|
loginMethod: "prolite")),
|
|
provider: .codex)
|
|
|
|
let descriptor = MenuDescriptor.build(
|
|
provider: .codex,
|
|
store: store,
|
|
settings: settings,
|
|
account: fetcher.loadAccountInfo(),
|
|
updateReady: false,
|
|
includeContextualActions: false)
|
|
|
|
let lines = self.textLines(from: descriptor)
|
|
#expect(lines.contains("Plan: Pro 5x"))
|
|
#expect(!lines.contains("Plan: Pro Lite"))
|
|
#expect(!lines.contains("Plan: Prolite"))
|
|
}
|
|
|
|
@Test
|
|
func `Codex menu prefers snapshot identity over conflicting fallback account info`() throws {
|
|
let settings = self.makeSettingsStore(suite: "CodexPresentationCharacterizationTests-snapshot-precedence")
|
|
settings.statusChecksEnabled = false
|
|
let managedHome = FileManager.default.temporaryDirectory
|
|
.appendingPathComponent("codex-presentation-fallback-\(UUID().uuidString)", isDirectory: true)
|
|
let managedAccount = ManagedCodexAccount(
|
|
id: UUID(),
|
|
email: "fallback@example.com",
|
|
managedHomePath: managedHome.path,
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
lastAuthenticatedAt: 1)
|
|
try Self.writeCodexAuthFile(homeURL: managedHome, email: "fallback@example.com", plan: "plus")
|
|
settings._test_activeManagedCodexAccount = managedAccount
|
|
settings._test_activeManagedCodexRemoteHomePath = managedHome.path
|
|
settings.codexActiveSource = .managedAccount(id: managedAccount.id)
|
|
defer {
|
|
settings._test_activeManagedCodexAccount = nil
|
|
settings._test_activeManagedCodexRemoteHomePath = nil
|
|
try? FileManager.default.removeItem(at: managedHome)
|
|
}
|
|
|
|
let fetcher = UsageFetcher(environment: [:])
|
|
let store = UsageStore(
|
|
fetcher: fetcher,
|
|
browserDetection: BrowserDetection(cacheTTL: 0),
|
|
settings: settings,
|
|
startupBehavior: .testing)
|
|
store._setSnapshotForTesting(
|
|
UsageSnapshot(
|
|
primary: RateWindow(usedPercent: 12, windowMinutes: 300, resetsAt: nil, resetDescription: nil),
|
|
secondary: nil,
|
|
updatedAt: Date(),
|
|
identity: ProviderIdentitySnapshot(
|
|
providerID: .codex,
|
|
accountEmail: "snapshot@example.com",
|
|
accountOrganization: nil,
|
|
loginMethod: "enterprise")),
|
|
provider: .codex)
|
|
|
|
let fallback = store.accountInfo(for: .codex)
|
|
#expect(fallback.email == "fallback@example.com")
|
|
#expect(fallback.plan == "plus")
|
|
|
|
let descriptor = MenuDescriptor.build(
|
|
provider: .codex,
|
|
store: store,
|
|
settings: settings,
|
|
account: AccountInfo(email: nil, plan: nil),
|
|
updateReady: false,
|
|
includeContextualActions: false)
|
|
|
|
let lines = self.textLines(from: descriptor)
|
|
#expect(lines.contains("Account: snapshot@example.com"))
|
|
#expect(lines.contains("Plan: Enterprise"))
|
|
#expect(!lines.contains("Account: fallback@example.com"))
|
|
#expect(!lines.contains("Plan: Plus"))
|
|
}
|
|
|
|
@Test
|
|
func `Codex menu falls back per field when snapshot identity is partial`() {
|
|
let settings = self.makeSettingsStore(suite: "CodexPresentationCharacterizationTests-partial-fallback")
|
|
settings.statusChecksEnabled = false
|
|
let managedHome = FileManager.default.temporaryDirectory
|
|
.appendingPathComponent("codex-presentation-partial-\(UUID().uuidString)", isDirectory: true)
|
|
let managedAccount = ManagedCodexAccount(
|
|
id: UUID(),
|
|
email: "fallback@example.com",
|
|
managedHomePath: managedHome.path,
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
lastAuthenticatedAt: 1)
|
|
try? Self.writeCodexAuthFile(homeURL: managedHome, email: "fallback@example.com", plan: "plus")
|
|
settings._test_activeManagedCodexAccount = managedAccount
|
|
settings._test_activeManagedCodexRemoteHomePath = managedHome.path
|
|
settings.codexActiveSource = .managedAccount(id: managedAccount.id)
|
|
defer {
|
|
settings._test_activeManagedCodexAccount = nil
|
|
settings._test_activeManagedCodexRemoteHomePath = nil
|
|
try? FileManager.default.removeItem(at: managedHome)
|
|
}
|
|
|
|
let fetcher = UsageFetcher(environment: [:])
|
|
let store = UsageStore(
|
|
fetcher: fetcher,
|
|
browserDetection: BrowserDetection(cacheTTL: 0),
|
|
settings: settings,
|
|
startupBehavior: .testing)
|
|
store._setSnapshotForTesting(
|
|
UsageSnapshot(
|
|
primary: RateWindow(usedPercent: 12, windowMinutes: 300, resetsAt: nil, resetDescription: nil),
|
|
secondary: nil,
|
|
updatedAt: Date(),
|
|
identity: ProviderIdentitySnapshot(
|
|
providerID: .codex,
|
|
accountEmail: "snapshot@example.com",
|
|
accountOrganization: nil,
|
|
loginMethod: nil)),
|
|
provider: .codex)
|
|
|
|
let descriptor = MenuDescriptor.build(
|
|
provider: .codex,
|
|
store: store,
|
|
settings: settings,
|
|
account: AccountInfo(email: nil, plan: nil),
|
|
updateReady: false,
|
|
includeContextualActions: false)
|
|
|
|
let lines = self.textLines(from: descriptor)
|
|
#expect(lines.contains("Account: snapshot@example.com"))
|
|
#expect(lines.contains("Plan: Plus"))
|
|
#expect(!lines.contains("Account: fallback@example.com"))
|
|
}
|
|
|
|
@Test
|
|
func `managed OpenAI web targeting uses active managed Codex identity and scope`() {
|
|
let settings = self.makeSettingsStore(suite: "CodexPresentationCharacterizationTests-managed-openai-web")
|
|
let managedAccount = ManagedCodexAccount(
|
|
id: UUID(),
|
|
email: "managed@example.com",
|
|
managedHomePath: "/tmp/managed-codex-home",
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
lastAuthenticatedAt: 1)
|
|
settings._test_activeManagedCodexAccount = managedAccount
|
|
settings.codexActiveSource = .managedAccount(id: managedAccount.id)
|
|
defer { settings._test_activeManagedCodexAccount = nil }
|
|
|
|
let store = UsageStore(
|
|
fetcher: UsageFetcher(environment: [:]),
|
|
browserDetection: BrowserDetection(cacheTTL: 0),
|
|
settings: settings,
|
|
startupBehavior: .testing)
|
|
|
|
#expect(store.codexAccountEmailForOpenAIDashboard() == managedAccount.email)
|
|
#expect(store.codexCookieCacheScopeForOpenAIWeb() == .managedAccount(managedAccount.id))
|
|
}
|
|
|
|
@Test
|
|
func `live OpenAI web targeting uses live Codex identity without managed scope`() {
|
|
let settings = self.makeSettingsStore(suite: "CodexPresentationCharacterizationTests-live-openai-web")
|
|
let managedAccount = ManagedCodexAccount(
|
|
id: UUID(),
|
|
email: "managed@example.com",
|
|
managedHomePath: "/tmp/managed-codex-home",
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
lastAuthenticatedAt: 1)
|
|
let liveAccount = ObservedSystemCodexAccount(
|
|
email: "system@example.com",
|
|
codexHomePath: "/tmp/live-codex-home",
|
|
observedAt: Date())
|
|
settings._test_activeManagedCodexAccount = managedAccount
|
|
settings._test_liveSystemCodexAccount = liveAccount
|
|
settings.codexActiveSource = .liveSystem
|
|
defer {
|
|
settings._test_activeManagedCodexAccount = nil
|
|
settings._test_liveSystemCodexAccount = nil
|
|
}
|
|
|
|
let store = UsageStore(
|
|
fetcher: UsageFetcher(environment: ["CODEX_HOME": liveAccount.codexHomePath]),
|
|
browserDetection: BrowserDetection(cacheTTL: 0),
|
|
settings: settings,
|
|
startupBehavior: .testing)
|
|
|
|
#expect(store.codexAccountEmailForOpenAIDashboard() == liveAccount.email)
|
|
#expect(store.codexAccountEmailForOpenAIDashboard() != managedAccount.email)
|
|
#expect(store.codexCookieCacheScopeForOpenAIWeb() == nil)
|
|
}
|
|
|
|
@Test
|
|
func `same email managed and live Codex resolves to live for OpenAI web targeting`() {
|
|
let settings = self.makeSettingsStore(suite: "CodexPresentationCharacterizationTests-same-email-prefers-live")
|
|
let managedAccount = ManagedCodexAccount(
|
|
id: UUID(),
|
|
email: "person@example.com",
|
|
managedHomePath: "/tmp/managed-codex-home",
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
lastAuthenticatedAt: 1)
|
|
let liveAccount = ObservedSystemCodexAccount(
|
|
email: "PERSON@example.com",
|
|
codexHomePath: "/tmp/live-codex-home",
|
|
observedAt: Date())
|
|
settings._test_activeManagedCodexAccount = managedAccount
|
|
settings._test_liveSystemCodexAccount = liveAccount
|
|
settings.codexActiveSource = .managedAccount(id: managedAccount.id)
|
|
defer {
|
|
settings._test_activeManagedCodexAccount = nil
|
|
settings._test_liveSystemCodexAccount = nil
|
|
}
|
|
|
|
let store = UsageStore(
|
|
fetcher: UsageFetcher(environment: ["CODEX_HOME": liveAccount.codexHomePath]),
|
|
browserDetection: BrowserDetection(cacheTTL: 0),
|
|
settings: settings,
|
|
startupBehavior: .testing)
|
|
|
|
#expect(settings.codexResolvedActiveSource == .liveSystem)
|
|
#expect(store.codexAccountEmailForOpenAIDashboard() == "person@example.com")
|
|
#expect(store.codexAccountEmailForOpenAIDashboard() != liveAccount.email)
|
|
#expect(store.codexCookieCacheScopeForOpenAIWeb() == nil)
|
|
}
|
|
|
|
@Test
|
|
func `live OpenAI web targeting does not reuse stale managed Codex snapshot identity`() {
|
|
let settings = self.makeSettingsStore(suite: "CodexPresentationCharacterizationTests-stale-managed-snapshot")
|
|
let isolatedHome = FileManager.default.temporaryDirectory
|
|
.appendingPathComponent("codex-presentation-openai-web-\(UUID().uuidString)", isDirectory: true)
|
|
try? FileManager.default.createDirectory(at: isolatedHome, withIntermediateDirectories: true)
|
|
let managedAccount = ManagedCodexAccount(
|
|
id: UUID(),
|
|
email: "managed@example.com",
|
|
managedHomePath: "/tmp/managed-codex-home",
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
lastAuthenticatedAt: 1)
|
|
|
|
settings._test_activeManagedCodexAccount = managedAccount
|
|
settings._test_codexReconciliationEnvironment = ["CODEX_HOME": isolatedHome.path]
|
|
settings.codexActiveSource = .liveSystem
|
|
defer {
|
|
settings._test_activeManagedCodexAccount = nil
|
|
settings._test_codexReconciliationEnvironment = nil
|
|
try? FileManager.default.removeItem(at: isolatedHome)
|
|
}
|
|
|
|
let store = UsageStore(
|
|
fetcher: UsageFetcher(environment: [:]),
|
|
browserDetection: BrowserDetection(cacheTTL: 0),
|
|
settings: settings,
|
|
startupBehavior: .testing)
|
|
store._setSnapshotForTesting(
|
|
UsageSnapshot(
|
|
primary: nil,
|
|
secondary: nil,
|
|
updatedAt: Date(),
|
|
identity: ProviderIdentitySnapshot(
|
|
providerID: .codex,
|
|
accountEmail: managedAccount.email,
|
|
accountOrganization: nil,
|
|
loginMethod: nil)),
|
|
provider: .codex)
|
|
|
|
#expect(store.codexAccountEmailForOpenAIDashboard() == nil)
|
|
#expect(store.codexAccountEmailForOpenAIDashboard() != managedAccount.email)
|
|
#expect(store.codexCookieCacheScopeForOpenAIWeb() == nil)
|
|
}
|
|
|
|
@Test
|
|
func `zai menu descriptor includes Tokens MCP and 5-hour rows`() {
|
|
let settings = self.makeSettingsStore(suite: "CodexPresentationCharacterizationTests-zai-three-quota")
|
|
settings.statusChecksEnabled = false
|
|
|
|
let fetcher = UsageFetcher(environment: [:])
|
|
let store = UsageStore(
|
|
fetcher: fetcher,
|
|
browserDetection: BrowserDetection(cacheTTL: 0),
|
|
settings: settings,
|
|
startupBehavior: .testing)
|
|
store._setSnapshotForTesting(
|
|
UsageSnapshot(
|
|
primary: RateWindow(
|
|
usedPercent: 9,
|
|
windowMinutes: 10080,
|
|
resetsAt: nil,
|
|
resetDescription: nil),
|
|
secondary: RateWindow(
|
|
usedPercent: 50,
|
|
windowMinutes: nil,
|
|
resetsAt: nil,
|
|
resetDescription: nil),
|
|
tertiary: RateWindow(
|
|
usedPercent: 25,
|
|
windowMinutes: 300,
|
|
resetsAt: nil,
|
|
resetDescription: nil),
|
|
updatedAt: Date(),
|
|
identity: ProviderIdentitySnapshot(
|
|
providerID: .zai,
|
|
accountEmail: nil,
|
|
accountOrganization: nil,
|
|
loginMethod: "pro")),
|
|
provider: .zai)
|
|
|
|
let descriptor = MenuDescriptor.build(
|
|
provider: .zai,
|
|
store: store,
|
|
settings: settings,
|
|
account: fetcher.loadAccountInfo(),
|
|
updateReady: false,
|
|
includeContextualActions: false)
|
|
|
|
let lines = self.textLines(from: descriptor)
|
|
#expect(lines.contains(where: { $0.hasPrefix("Tokens:") }))
|
|
#expect(lines.contains(where: { $0.hasPrefix("MCP:") }))
|
|
#expect(lines.contains(where: { $0.hasPrefix("5-hour:") }))
|
|
}
|
|
|
|
private func makeSettingsStore(suite: String) -> SettingsStore {
|
|
let defaults = UserDefaults(suiteName: suite)!
|
|
defaults.removePersistentDomain(forName: suite)
|
|
let settings = SettingsStore(
|
|
userDefaults: defaults,
|
|
configStore: testConfigStore(suiteName: suite),
|
|
zaiTokenStore: NoopZaiTokenStore(),
|
|
syntheticTokenStore: NoopSyntheticTokenStore())
|
|
settings._test_activeManagedCodexAccount = nil
|
|
settings._test_activeManagedCodexRemoteHomePath = nil
|
|
settings._test_unreadableManagedCodexAccountStore = false
|
|
settings._test_managedCodexAccountStoreURL = nil
|
|
settings._test_liveSystemCodexAccount = nil
|
|
settings._test_codexReconciliationEnvironment = nil
|
|
return settings
|
|
}
|
|
|
|
private func textLines(from descriptor: MenuDescriptor) -> [String] {
|
|
descriptor.sections
|
|
.flatMap(\.entries)
|
|
.compactMap { entry -> String? in
|
|
guard case let .text(text, _) = entry else { return nil }
|
|
return text
|
|
}
|
|
}
|
|
|
|
private static func writeCodexAuthFile(homeURL: URL, email: String, plan: String) throws {
|
|
try FileManager.default.createDirectory(at: homeURL, withIntermediateDirectories: true)
|
|
let auth = [
|
|
"tokens": [
|
|
"accessToken": "access-token",
|
|
"refreshToken": "refresh-token",
|
|
"idToken": Self.fakeJWT(email: email, plan: plan),
|
|
],
|
|
]
|
|
let data = try JSONSerialization.data(withJSONObject: auth)
|
|
try data.write(to: homeURL.appendingPathComponent("auth.json"))
|
|
}
|
|
|
|
private static func fakeJWT(email: String, plan: String) -> String {
|
|
let header = (try? JSONSerialization.data(withJSONObject: ["alg": "none"])) ?? Data()
|
|
let payload = (try? JSONSerialization.data(withJSONObject: [
|
|
"email": email,
|
|
"chatgpt_plan_type": plan,
|
|
])) ?? Data()
|
|
|
|
func base64URL(_ data: Data) -> String {
|
|
data.base64EncodedString()
|
|
.replacingOccurrences(of: "=", with: "")
|
|
.replacingOccurrences(of: "+", with: "-")
|
|
.replacingOccurrences(of: "/", with: "_")
|
|
}
|
|
|
|
return "\(base64URL(header)).\(base64URL(payload))."
|
|
}
|
|
}
|