436 lines · 17.3 KB · Swift
RawDownload
import Foundationimport Testing@testable import CodexBarCore @Suite(.serialized)@MainActorstruct CodexWebDashboardStrategyAuthorityTests {    @Test    func `web dashboard attach converts snapshot with authority attachment email`() 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 result = try CodexWebDashboardStrategy.makeAuthorizedDashboardResultForTesting(            dashboard: self.makeDashboard(email: "owner@example.com"),            context: context,            routingTargetEmail: "route@example.com")         #expect(result.usage.accountEmail(for: .codex) == "owner@example.com")        #expect(result.credits?.remaining == 42)    }     @Test    func `web dashboard attach preserves credits when usage limits are absent`() 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.makeDashboardWithoutUsageLimits(email: "owner@example.com")         let result = try CodexWebDashboardStrategy.makeAuthorizedDashboardResultForTesting(            dashboard: dashboard,            context: context,            routingTargetEmail: "route@example.com")         #expect(result.usage.primary == nil)        #expect(result.usage.secondary == nil)        #expect(result.usage.updatedAt == dashboard.updatedAt)        #expect(result.usage.identity?.accountEmail == "owner@example.com")        #expect(result.usage.identity?.loginMethod == "pro")        #expect(result.credits?.remaining == 42)        #expect(result.dashboard == dashboard)    }     @Test    func `web dashboard display only throws typed policy error`() 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"),            ])        let dashboard = self.makeDashboard(email: "shared@example.com")        let expectedDecision = CodexDashboardAuthority.evaluate(            CodexCLIDashboardAuthorityContext.makeLiveWebInput(                dashboard: dashboard,                context: context,                routingTargetEmail: "route@example.com"))         do {            _ = try CodexWebDashboardStrategy.makeAuthorizedDashboardResultForTesting(                dashboard: dashboard,                context: context,                routingTargetEmail: "route@example.com")            Issue.record("Expected CodexDashboardPolicyError.displayOnly")        } catch let error as CodexDashboardPolicyError {            #expect(error == .displayOnly(expectedDecision))        } catch {            Issue.record("Expected CodexDashboardPolicyError.displayOnly, got \(error)")        }    }     @Test    func `web dashboard fail closed throws policy rejection`() 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"),            ])        let dashboard = self.makeDashboard(email: "owner@example.com")        let expectedDecision = CodexDashboardAuthority.evaluate(            CodexCLIDashboardAuthorityContext.makeLiveWebInput(                dashboard: dashboard,                context: context,                routingTargetEmail: "route@example.com"))         do {            _ = try CodexWebDashboardStrategy.makeAuthorizedDashboardResultForTesting(                dashboard: dashboard,                context: context,                routingTargetEmail: "route@example.com")            Issue.record("Expected OpenAIWebCodexError.policyRejected")        } catch let error as OpenAIWebCodexError {            #expect(error == .policyRejected(expectedDecision))        } catch {            Issue.record("Expected OpenAIWebCodexError.policyRejected, got \(error)")        }    }     @Test    func `web dashboard wrong email throws policy rejection`() 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: "other@example.com")        let expectedDecision = CodexDashboardAuthority.evaluate(            CodexCLIDashboardAuthorityContext.makeLiveWebInput(                dashboard: dashboard,                context: context,                routingTargetEmail: "owner@example.com"))         do {            _ = try CodexWebDashboardStrategy.makeAuthorizedDashboardResultForTesting(                dashboard: dashboard,                context: context,                routingTargetEmail: "owner@example.com")            Issue.record("Expected OpenAIWebCodexError.policyRejected")        } catch let error as OpenAIWebCodexError {            #expect(error == .policyRejected(expectedDecision))            if case let .policyRejected(decision) = error {                #expect(decision.reason == .wrongEmail(expected: "owner@example.com", actual: "other@example.com"))            }        } catch {            Issue.record("Expected OpenAIWebCodexError.policyRejected, got \(error)")        }    }     @Test    func `web dashboard provider account without scoped auth email fail closes on dashboard collision`() 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"),            ])        let dashboard = self.makeDashboard(email: "shared@example.com")        let expectedDecision = CodexDashboardAuthority.evaluate(            CodexCLIDashboardAuthorityContext.makeLiveWebInput(                dashboard: dashboard,                context: context,                routingTargetEmail: "shared@example.com"))         do {            _ = try CodexWebDashboardStrategy.makeAuthorizedDashboardResultForTesting(                dashboard: dashboard,                context: context,                routingTargetEmail: "shared@example.com")            Issue.record("Expected OpenAIWebCodexError.policyRejected")        } catch let error as OpenAIWebCodexError {            #expect(error == .policyRejected(expectedDecision))            if case let .policyRejected(decision) = error {                #expect(decision.reason == .providerAccountMissingScopedEmail)            }        } catch {            Issue.record("Expected OpenAIWebCodexError.policyRejected, got \(error)")        }    }     @Test    func `web dashboard attach saves cache with attached email not routing fallback`() 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"),            ])         _ = try CodexWebDashboardStrategy.makeAuthorizedDashboardResultForTesting(            dashboard: self.makeDashboard(email: "owner@example.com"),            context: context,            routingTargetEmail: "route@example.com")         let cache = try #require(OpenAIDashboardCacheStore.load())        #expect(cache.accountEmail == "owner@example.com")        #expect(cache.accountEmail != "route@example.com")    }     @Test    func `web dashboard fail closed clears stale cache`() throws {        OpenAIDashboardCacheStore.save(OpenAIDashboardCache(            accountEmail: "stale@example.com",            snapshot: self.makeDashboard(email: "stale@example.com")))        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"),            ])         do {            _ = try CodexWebDashboardStrategy.makeAuthorizedDashboardResultForTesting(                dashboard: self.makeDashboard(email: "owner@example.com"),                context: context,                routingTargetEmail: "route@example.com")            Issue.record("Expected OpenAIWebCodexError.policyRejected")        } catch is OpenAIWebCodexError {}         #expect(OpenAIDashboardCacheStore.load() == nil)    }     @Test    func `web dashboard display only clears stale cache`() throws {        OpenAIDashboardCacheStore.save(OpenAIDashboardCache(            accountEmail: "stale@example.com",            snapshot: self.makeDashboard(email: "stale@example.com")))        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"),            ])         do {            _ = try CodexWebDashboardStrategy.makeAuthorizedDashboardResultForTesting(                dashboard: self.makeDashboard(email: "shared@example.com"),                context: context,                routingTargetEmail: "route@example.com")            Issue.record("Expected CodexDashboardPolicyError.displayOnly")        } catch is CodexDashboardPolicyError {}         #expect(OpenAIDashboardCacheStore.load() == nil)    }     private func makeContext(        authHome: URL? = nil,        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 makeDashboard(email: String) -> OpenAIDashboardSnapshot {        OpenAIDashboardSnapshot(            signedInEmail: email,            codeReviewRemainingPercent: 75,            codeReviewLimit: RateWindow(                usedPercent: 25,                windowMinutes: 60,                resetsAt: Date(timeIntervalSince1970: 3600),                resetDescription: nil),            creditEvents: [                CreditEvent(                    date: Date(timeIntervalSince1970: 1000),                    service: "codex",                    creditsUsed: 3),            ],            dailyBreakdown: [],            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 makeDashboardWithoutUsageLimits(email: String) -> OpenAIDashboardSnapshot {        OpenAIDashboardSnapshot(            signedInEmail: email,            codeReviewRemainingPercent: nil,            creditEvents: [],            dailyBreakdown: [],            usageBreakdown: [],            creditsPurchaseURL: nil,            primaryLimit: 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 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))."    }}