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

434 lines
16 KiB
Swift

import Foundation
import Testing
@testable import CodexBarCLI
@testable import CodexBarCore
@Suite(.serialized)
struct CLIOpenAIDashboardCacheTests {
@Test
func `cached dashboard restores when authority allows cached reuse`() throws {
OpenAIDashboardCacheStore.clear()
defer { OpenAIDashboardCacheStore.clear() }
let authHome = try self.makeAuthHome(
email: "owner@example.com",
accountId: "acct-owner")
defer { try? FileManager.default.removeItem(at: authHome) }
let context = self.makeContext(
authHome: authHome,
knownOwners: [
CodexDashboardKnownOwnerCandidate(
identity: .providerAccount(id: "acct-owner"),
normalizedEmail: "owner@example.com"),
])
let dashboard = self.makeDashboard(email: "owner@example.com")
OpenAIDashboardCacheStore.save(OpenAIDashboardCache(
accountEmail: "stale-route@example.com",
snapshot: dashboard))
let restored = CodexBarCLI.loadOpenAIDashboardIfAvailable(
usage: self.makeUsage(email: nil),
sourceLabel: "openai-web",
context: context)
#expect(restored == dashboard)
}
@Test
func `cached dashboard returns nil on display only and clears cache`() throws {
OpenAIDashboardCacheStore.clear()
defer { OpenAIDashboardCacheStore.clear() }
let authHome = try self.makeAuthHome(email: "shared@example.com")
defer { try? FileManager.default.removeItem(at: authHome) }
let context = self.makeContext(
authHome: authHome,
knownOwners: [
CodexDashboardKnownOwnerCandidate(
identity: .providerAccount(id: "acct-alpha"),
normalizedEmail: "shared@example.com"),
CodexDashboardKnownOwnerCandidate(
identity: .providerAccount(id: "acct-beta"),
normalizedEmail: "shared@example.com"),
])
OpenAIDashboardCacheStore.save(OpenAIDashboardCache(
accountEmail: "shared@example.com",
snapshot: self.makeDashboard(email: "shared@example.com")))
let restored = CodexBarCLI.loadOpenAIDashboardIfAvailable(
usage: self.makeUsage(email: "shared@example.com"),
sourceLabel: "codex-cli",
context: context)
#expect(restored == nil)
#expect(OpenAIDashboardCacheStore.load() == nil)
}
@Test
func `cached dashboard returns nil on fail closed and clears cache`() throws {
OpenAIDashboardCacheStore.clear()
defer { OpenAIDashboardCacheStore.clear() }
let authHome = try self.makeAuthHome(
email: "owner@example.com",
accountId: "acct-owner")
defer { try? FileManager.default.removeItem(at: authHome) }
let context = self.makeContext(
authHome: authHome,
knownOwners: [
CodexDashboardKnownOwnerCandidate(
identity: .providerAccount(id: "acct-other"),
normalizedEmail: "owner@example.com"),
])
OpenAIDashboardCacheStore.save(OpenAIDashboardCache(
accountEmail: "owner@example.com",
snapshot: self.makeDashboard(email: "owner@example.com")))
let restored = CodexBarCLI.loadOpenAIDashboardIfAvailable(
usage: self.makeUsage(email: "owner@example.com"),
sourceLabel: "codex-cli",
context: context)
#expect(restored == nil)
#expect(OpenAIDashboardCacheStore.load() == nil)
}
@Test
func `cached dashboard wrong email returns nil and clears cache`() throws {
OpenAIDashboardCacheStore.clear()
defer { OpenAIDashboardCacheStore.clear() }
let authHome = try self.makeAuthHome(
email: "owner@example.com",
accountId: "acct-owner")
defer { try? FileManager.default.removeItem(at: authHome) }
let context = self.makeContext(
authHome: authHome,
knownOwners: [
CodexDashboardKnownOwnerCandidate(
identity: .providerAccount(id: "acct-owner"),
normalizedEmail: "owner@example.com"),
])
OpenAIDashboardCacheStore.save(OpenAIDashboardCache(
accountEmail: "other@example.com",
snapshot: self.makeDashboard(email: "other@example.com")))
let restored = CodexBarCLI.loadOpenAIDashboardIfAvailable(
usage: self.makeUsage(email: "owner@example.com"),
sourceLabel: "codex-cli",
context: context)
#expect(restored == nil)
#expect(OpenAIDashboardCacheStore.load() == nil)
}
@Test
func `cached dashboard provider account without scoped auth email fails closed`() throws {
OpenAIDashboardCacheStore.clear()
defer { OpenAIDashboardCacheStore.clear() }
let authHome = try self.makeAuthHome(email: nil, accountId: "acct-owner")
defer { try? FileManager.default.removeItem(at: authHome) }
let context = self.makeContext(
authHome: authHome,
knownOwners: [
CodexDashboardKnownOwnerCandidate(
identity: .providerAccount(id: "acct-alpha"),
normalizedEmail: "shared@example.com"),
CodexDashboardKnownOwnerCandidate(
identity: .providerAccount(id: "acct-beta"),
normalizedEmail: "shared@example.com"),
])
OpenAIDashboardCacheStore.save(OpenAIDashboardCache(
accountEmail: "shared@example.com",
snapshot: self.makeDashboard(email: "shared@example.com")))
let restored = CodexBarCLI.loadOpenAIDashboardIfAvailable(
usage: self.makeUsage(email: "shared@example.com"),
sourceLabel: "codex-cli",
context: context)
#expect(restored == nil)
#expect(OpenAIDashboardCacheStore.load() == nil)
}
@Test
func `cached dashboard unresolved trusted continuity with competing owner returns nil`() {
OpenAIDashboardCacheStore.clear()
defer { OpenAIDashboardCacheStore.clear() }
let emptyHome = self.makeEmptyHome()
defer { try? FileManager.default.removeItem(at: emptyHome) }
let context = self.makeContext(
authHome: emptyHome,
knownOwners: [
CodexDashboardKnownOwnerCandidate(
identity: .providerAccount(id: "acct-alpha"),
normalizedEmail: "shared@example.com"),
CodexDashboardKnownOwnerCandidate(
identity: .providerAccount(id: "acct-beta"),
normalizedEmail: "shared@example.com"),
])
OpenAIDashboardCacheStore.save(OpenAIDashboardCache(
accountEmail: "shared@example.com",
snapshot: self.makeDashboard(email: "shared@example.com")))
let restored = CodexBarCLI.loadOpenAIDashboardIfAvailable(
usage: self.makeUsage(email: "shared@example.com"),
sourceLabel: "codex-cli",
context: context)
#expect(restored == nil)
#expect(OpenAIDashboardCacheStore.load() == nil)
}
@Test
func `cached dashboard trusts codex cli usage continuity`() {
OpenAIDashboardCacheStore.clear()
defer { OpenAIDashboardCacheStore.clear() }
let emptyHome = self.makeEmptyHome()
defer { try? FileManager.default.removeItem(at: emptyHome) }
let context = self.makeContext(
authHome: emptyHome,
knownOwners: [
CodexDashboardKnownOwnerCandidate(
identity: .providerAccount(id: "acct-owner"),
normalizedEmail: "owner@example.com"),
])
let dashboard = self.makeDashboard(email: "owner@example.com")
OpenAIDashboardCacheStore.save(OpenAIDashboardCache(
accountEmail: "stale-route@example.com",
snapshot: dashboard))
let restored = CodexBarCLI.loadOpenAIDashboardIfAvailable(
usage: self.makeUsage(email: "owner@example.com"),
sourceLabel: "codex-cli",
context: context)
#expect(restored == dashboard)
}
@Test
func `cached dashboard trusts oauth usage continuity`() {
OpenAIDashboardCacheStore.clear()
defer { OpenAIDashboardCacheStore.clear() }
let emptyHome = self.makeEmptyHome()
defer { try? FileManager.default.removeItem(at: emptyHome) }
let context = self.makeContext(
authHome: emptyHome,
knownOwners: [
CodexDashboardKnownOwnerCandidate(
identity: .providerAccount(id: "acct-owner"),
normalizedEmail: "owner@example.com"),
])
let dashboard = self.makeDashboard(email: "owner@example.com")
OpenAIDashboardCacheStore.save(OpenAIDashboardCache(
accountEmail: "stale-route@example.com",
snapshot: dashboard))
let restored = CodexBarCLI.loadOpenAIDashboardIfAvailable(
usage: self.makeUsage(email: "owner@example.com"),
sourceLabel: "oauth",
context: context)
#expect(restored == dashboard)
}
@Test
func `cached dashboard does not trust open A I web usage continuity`() {
OpenAIDashboardCacheStore.clear()
defer { OpenAIDashboardCacheStore.clear() }
let emptyHome = self.makeEmptyHome()
defer { try? FileManager.default.removeItem(at: emptyHome) }
let context = self.makeContext(
authHome: emptyHome,
knownOwners: [
CodexDashboardKnownOwnerCandidate(
identity: .providerAccount(id: "acct-owner"),
normalizedEmail: "owner@example.com"),
])
OpenAIDashboardCacheStore.save(OpenAIDashboardCache(
accountEmail: "owner@example.com",
snapshot: self.makeDashboard(email: "owner@example.com")))
let restored = CodexBarCLI.loadOpenAIDashboardIfAvailable(
usage: self.makeUsage(email: "owner@example.com"),
sourceLabel: "openai-web",
context: context)
#expect(restored == nil)
#expect(OpenAIDashboardCacheStore.load() == nil)
}
@Test
func `cached dashboard ignores cached account email equality when authority rejects`() throws {
OpenAIDashboardCacheStore.clear()
defer { OpenAIDashboardCacheStore.clear() }
let authHome = try self.makeAuthHome(
email: "owner@example.com",
accountId: "acct-owner")
defer { try? FileManager.default.removeItem(at: authHome) }
let context = self.makeContext(
authHome: authHome,
knownOwners: [
CodexDashboardKnownOwnerCandidate(
identity: .providerAccount(id: "acct-other"),
normalizedEmail: "owner@example.com"),
])
OpenAIDashboardCacheStore.save(OpenAIDashboardCache(
accountEmail: "owner@example.com",
snapshot: self.makeDashboard(email: "owner@example.com")))
let restored = CodexBarCLI.loadOpenAIDashboardIfAvailable(
usage: self.makeUsage(email: "owner@example.com"),
sourceLabel: "codex-cli",
context: context)
#expect(restored == nil)
#expect(OpenAIDashboardCacheStore.load() == nil)
}
private func makeContext(
authHome: URL?,
knownOwners: [CodexDashboardKnownOwnerCandidate]) -> ProviderFetchContext
{
let env = authHome.map { ["CODEX_HOME": $0.path] } ?? [:]
let browserDetection = BrowserDetection(cacheTTL: 0)
return ProviderFetchContext(
runtime: .cli,
sourceMode: .auto,
includeCredits: true,
webTimeout: 60,
webDebugDumpHTML: false,
verbose: false,
env: env,
settings: ProviderSettingsSnapshot.make(
codex: .init(
usageDataSource: .auto,
cookieSource: .auto,
manualCookieHeader: nil,
dashboardAuthorityKnownOwners: knownOwners)),
fetcher: UsageFetcher(environment: env),
claudeFetcher: ClaudeUsageFetcher(browserDetection: browserDetection),
browserDetection: browserDetection)
}
private func makeUsage(email: String?) -> UsageSnapshot {
UsageSnapshot(
primary: RateWindow(
usedPercent: 10,
windowMinutes: 300,
resetsAt: Date(timeIntervalSince1970: 7200),
resetDescription: nil),
secondary: nil,
updatedAt: Date(timeIntervalSince1970: 2000),
identity: ProviderIdentitySnapshot(
providerID: .codex,
accountEmail: email,
accountOrganization: nil,
loginMethod: nil))
}
private func makeDashboard(email: String) -> OpenAIDashboardSnapshot {
let creditEvents = [
CreditEvent(
date: Date(timeIntervalSince1970: 1000),
service: "codex",
creditsUsed: 3),
]
return OpenAIDashboardSnapshot(
signedInEmail: email,
codeReviewRemainingPercent: 75,
codeReviewLimit: RateWindow(
usedPercent: 25,
windowMinutes: 60,
resetsAt: Date(timeIntervalSince1970: 3600),
resetDescription: nil),
creditEvents: creditEvents,
dailyBreakdown: OpenAIDashboardSnapshot.makeDailyBreakdown(from: creditEvents, maxDays: 30),
usageBreakdown: [],
creditsPurchaseURL: nil,
primaryLimit: RateWindow(
usedPercent: 10,
windowMinutes: 300,
resetsAt: Date(timeIntervalSince1970: 7200),
resetDescription: nil),
secondaryLimit: nil,
creditsRemaining: 42,
accountPlan: "pro",
updatedAt: Date(timeIntervalSince1970: 2000))
}
private func makeAuthHome(email: String?, accountId: String? = nil) throws -> URL {
let homeURL = FileManager.default.temporaryDirectory.appendingPathComponent(
UUID().uuidString,
isDirectory: true)
try self.writeCodexAuthFile(homeURL: homeURL, email: email, accountId: accountId)
return homeURL
}
private func makeEmptyHome() -> URL {
let homeURL = FileManager.default.temporaryDirectory.appendingPathComponent(
UUID().uuidString,
isDirectory: true)
try? FileManager.default.createDirectory(at: homeURL, withIntermediateDirectories: true)
return homeURL
}
private func writeCodexAuthFile(
homeURL: URL,
email: String?,
accountId: String?) throws
{
try FileManager.default.createDirectory(at: homeURL, withIntermediateDirectories: true)
var tokens: [String: Any] = [
"accessToken": "access-token",
"refreshToken": "refresh-token",
"idToken": Self.fakeJWT(email: email, accountId: accountId),
]
if let accountId {
tokens["accountId"] = accountId
}
let auth = ["tokens": tokens]
let data = try JSONSerialization.data(withJSONObject: auth)
try data.write(to: homeURL.appendingPathComponent("auth.json"))
}
private static func fakeJWT(email: String?, accountId: String?) -> String {
let header = (try? JSONSerialization.data(withJSONObject: ["alg": "none"])) ?? Data()
var authClaims: [String: Any] = [
"chatgpt_plan_type": "pro",
]
if let accountId {
authClaims["chatgpt_account_id"] = accountId
}
var claims: [String: Any] = [
"chatgpt_plan_type": "pro",
"https://api.openai.com/auth": authClaims,
]
if let email {
claims["email"] = email
}
let payload = (try? JSONSerialization.data(withJSONObject: claims)) ?? Data()
func base64URL(_ data: Data) -> String {
data.base64EncodedString()
.replacingOccurrences(of: "=", with: "")
.replacingOccurrences(of: "+", with: "-")
.replacingOccurrences(of: "/", with: "_")
}
return "\(base64URL(header)).\(base64URL(payload))."
}
}