721 lines · 28.7 KB · Swift
RawDownload
import Foundationimport Testing@testable import CodexBarCore @Suite(.serialized)struct CopilotBudgetWebFetcherTests {    @Test    func `maps positive copilot budgets to extra rate windows`() {        let budgets: [CopilotBudgetWebFetcher.Budget] = [            .init(                id: "product-budget",                budgetProductSkus: ["copilot"],                budgetAmount: 100,                currentAmount: 15),            .init(                id: "agent-budget",                budgetProductSkus: ["copilot_agent_premium_request"],                budgetAmount: 20,                currentAmount: 5),            .init(                id: "zero-budget",                budgetProductSkus: ["spark_premium_request"],                budgetAmount: 0,                currentAmount: 0),        ]         let windows = CopilotBudgetWebFetcher.extraRateWindows(            from: budgets,            now: Date(timeIntervalSince1970: 1_780_358_400))         #expect(windows.map(\.id) == ["copilot-budget-product-budget", "copilot-budget-agent-budget"])        #expect(windows.map(\.title) == ["Budget - Copilot", "Budget - Copilot Agent Premium Requests"])        #expect(windows[0].window.usedPercent == 15)        #expect(windows[1].window.usedPercent == 25)        #expect(windows.allSatisfy { $0.window.resetsAt != nil })    }     @Test    func `decodes github web budget response shape`() throws {        let data = Data("""        {          "payload": {            "budgets": [              {                "uuid": "budget-1",                "targetName": "Example",                "pricingTargetType": "BundlePricing",                "pricingTargetId": "premium_requests",                "targetAmount": 30.0,                "currentAmount": 0.0              }            ],            "has_next_page": false          }        }        """.utf8)         let response = try JSONDecoder().decode(CopilotBudgetWebFetcher.BudgetResponse.self, from: data)        let budget = try #require(response.budgets.first)        #expect(response.hasNextPage == false)        #expect(budget.id == "budget-1")        #expect(budget.budgetEntityName == "Example")        #expect(budget.budgetAmount == 30)        #expect(budget.currentAmount == 0)         let windows = CopilotBudgetWebFetcher.extraRateWindows(            from: response.budgets,            now: Date(timeIntervalSince1970: 1_780_358_400))        #expect(windows.map(\.title) == ["Budget - All Premium Request SKUs"])        #expect(windows.first?.window.usedPercent == 0)    }     @Test    func `ignores malformed embedded minus amounts`() throws {        let data = Data("""        {          "budgets": [            {              "uuid": "budget-1",              "pricingTargetId": "premium_requests",              "targetAmount": "1-5",              "currentAmount": "$5.00"            },            {              "uuid": "budget-2",              "pricingTargetId": "premium_requests",              "targetAmount": "-$15.00",              "currentAmount": "$5.00"            }          ]        }        """.utf8)         let response = try JSONDecoder().decode(CopilotBudgetWebFetcher.BudgetResponse.self, from: data)         #expect(response.budgets.map(\.budgetAmount) == [0, -15])        #expect(CopilotBudgetWebFetcher.extraRateWindows(            from: response.budgets,            now: Date(timeIntervalSince1970: 1_780_358_400)).isEmpty)    }     @Test    func `normalizes documented copilot billing names`() {        #expect(CopilotBudgetWebFetcher.normalizedBillingIdentifier("Copilot") == "copilot")        #expect(            CopilotBudgetWebFetcher.normalizedBillingIdentifier("Copilot Premium Request") ==                "copilot_premium_request")        #expect(            CopilotBudgetWebFetcher.normalizedBillingIdentifier("Copilot Agent Premium Request") ==                "copilot_agent_premium_request")        #expect(            CopilotBudgetWebFetcher.normalizedBillingIdentifier("Spark Premium Request") ==                "spark_premium_request")        #expect(            CopilotBudgetWebFetcher.normalizedBillingIdentifier("Premium requests") ==                "copilot_premium_request")        #expect(            CopilotBudgetWebFetcher.normalizedBillingIdentifier("Bundled premium request budget") ==                "copilot_premium_request")        #expect(            CopilotBudgetWebFetcher.normalizedBillingIdentifier("Copilot cloud agent premium requests") ==                "copilot_agent_premium_request")        #expect(            CopilotBudgetWebFetcher.normalizedBillingIdentifier("coding_agent_premium_request") ==                "copilot_agent_premium_request")    }     @Test    func `extracts github fetch nonce from html`() {        let html = #"<meta name="x-fetch-nonce" content="v2:abc-123">"#        #expect(CopilotBudgetWebFetcher.extractFetchNonce(from: html) == "v2:abc-123")    }     @Test    func `extracts github web identity from html`() throws {        let html = """        <meta name="octolytics-actor-id" content="123">        <meta content = "octocat" name = "user-login">        """         let identity = try #require(CopilotBudgetWebFetcher.extractGitHubWebIdentity(from: html))         #expect(identity.id == "123")        #expect(identity.login == "octocat")        #expect(CopilotBudgetWebFetcher.webIdentity(identity, matches: "github:user:123"))        #expect(CopilotBudgetWebFetcher.webIdentity(identity, matches: "OctoCat"))        #expect(!CopilotBudgetWebFetcher.webIdentity(identity, matches: "github:user:456"))    }     @Test    func `missing github web identity with expected account maps to unknown account mismatch`() async throws {        let transport = ProviderHTTPTransportStub { request in            guard let url = request.url,                  let response = HTTPURLResponse(                      url: url,                      statusCode: 200,                      httpVersion: "HTTP/1.1",                      headerFields: nil)            else {                throw URLError(.badServerResponse)            }            if request.url?.query?.contains("page=") == true {                Issue.record("Missing identity should not reach budget JSON endpoint")                return (Data(#"{"budgets":[],"has_next_page":false}"#.utf8), response)            }            return (Data(#"<meta name="x-fetch-nonce" content="nonce">"#.utf8), response)        }        let fetcher = CopilotBudgetWebFetcher(            cookieHeaderOverride: "user_session=missing-identity",            expectedGitHubAccountIdentifier: "github:user:123",            transport: transport)         do {            _ = try await fetcher.fetchBudgetWindows()            Issue.record("Expected account mismatch")        } catch let error as CopilotBudgetWebFetcher.Error {            #expect(error == .accountMismatch(expected: "github:user:123", actual: nil))        }         #expect(await transport.requests().count == 1)    }     @Test    func `invalid github budget page html encoding maps to invalid response`() async throws {        let transport = ProviderHTTPTransportStub { request in            guard let url = request.url,                  let response = HTTPURLResponse(                      url: url,                      statusCode: 200,                      httpVersion: "HTTP/1.1",                      headerFields: nil)            else {                throw URLError(.badServerResponse)            }            if request.url?.query?.contains("page=") == true {                Issue.record("Invalid HTML encoding should not reach budget JSON endpoint")                return (Data(#"{"budgets":[],"has_next_page":false}"#.utf8), response)            }            return (Data([0xC3, 0x28]), response)        }        let fetcher = CopilotBudgetWebFetcher(            cookieHeaderOverride: "user_session=invalid-html",            expectedGitHubAccountIdentifier: "github:user:123",            transport: transport)         do {            _ = try await fetcher.fetchBudgetWindows()            Issue.record("Expected invalid response")        } catch let error as CopilotBudgetWebFetcher.Error {            #expect(error == .invalidResponse)        }         #expect(await transport.requests().count == 1)    }     @Test    func `manual budget cookie for different github account is ignored before budget request`() async throws {        let transport = ProviderHTTPTransportStub { request in            guard let url = request.url,                  let response = HTTPURLResponse(                      url: url,                      statusCode: 200,                      httpVersion: "HTTP/1.1",                      headerFields: nil)            else {                throw URLError(.badServerResponse)            }            if request.url?.query?.contains("page=") == true {                Issue.record("Mismatched cookie should not reach budget JSON endpoint")                return (Data(#"{"budgets":[],"has_next_page":false}"#.utf8), response)            }            return (                Data("""                <meta name="x-fetch-nonce" content="nonce">                <meta name="octolytics-actor-id" content="456">                <meta name="user-login" content="otheruser">                """.utf8),                response)        }        let fetcher = CopilotBudgetWebFetcher(            cookieHeaderOverride: "user_session=other",            expectedGitHubAccountIdentifier: "github:user:123",            transport: transport)         do {            _ = try await fetcher.fetchBudgetWindows()            Issue.record("Expected account mismatch")        } catch let error as CopilotBudgetWebFetcher.Error {            #expect(error == .accountMismatch(expected: "github:user:123", actual: "otheruser"))        }         #expect(await transport.requests().count == 1)    }     @Test    func `manual budget cookie with matching github account appends budget windows`() async throws {        let transport = ProviderHTTPTransportStub { request in            guard let url = request.url,                  let response = HTTPURLResponse(                      url: url,                      statusCode: 200,                      httpVersion: "HTTP/1.1",                      headerFields: nil)            else {                throw URLError(.badServerResponse)            }            if request.url?.query?.contains("page=") == true {                return (                    Data("""                    {                      "budgets": [                        {                          "uuid": "budget-1",                          "pricingTargetId": "premium_requests",                          "targetAmount": 100.0,                          "currentAmount": 40.0                        }                      ],                      "has_next_page": false                    }                    """.utf8),                    response)            }            return (                Data("""                <meta name="x-fetch-nonce" content="nonce">                <meta name="octolytics-actor-id" content="123">                <meta name="user-login" content="octocat">                """.utf8),                response)        }        let fetcher = CopilotBudgetWebFetcher(            cookieHeaderOverride: "user_session=matching",            expectedGitHubAccountIdentifier: "github:user:123",            transport: transport,            now: { Date(timeIntervalSince1970: 1_780_358_400) })         let windows = try await fetcher.fetchBudgetWindows()         #expect(windows.map(\.id) == ["copilot-budget-budget-1"])        #expect(windows.first?.window.usedPercent == 40)        #expect(await transport.requests().count == 2)    }     @Test    func `mismatched manual budget cookie leaves normal copilot usage unchanged`() async {        let registered = URLProtocol.registerClass(CopilotBudgetBindingStubURLProtocol.self)        defer {            if registered {                URLProtocol.unregisterClass(CopilotBudgetBindingStubURLProtocol.self)            }            CopilotBudgetBindingStubURLProtocol.reset()        }        CopilotBudgetBindingStubURLProtocol.reset()        CopilotBudgetBindingStubURLProtocol.handler = { request in            guard let url = request.url else {                throw URLError(.badURL)            }            if url.host == "api.github.com", url.path == "/copilot_internal/user" {                return Self.stubResponse(                    url: url,                    data: Data("""                    {                      "quota_snapshots": {                        "premium_interactions": {                          "entitlement": 300,                          "remaining": 240,                          "percent_remaining": 80,                          "quota_id": "premium"                        }                      },                      "copilot_plan": "pro"                    }                    """.utf8))            }            if url.host == "api.github.com", url.path == "/user" {                return Self.stubResponse(                    url: url,                    data: Data(#"{"id":123,"login":"expecteduser"}"#.utf8))            }            if url.host == "github.com", url.path == "/settings/billing/budgets", url.query == nil {                return Self.stubResponse(                    url: url,                    data: Data("""                    <meta name="x-fetch-nonce" content="nonce">                    <meta name="octolytics-actor-id" content="456">                    <meta name="user-login" content="otheruser">                    """.utf8))            }            Issue.record("Unexpected request: \(url.absoluteString)")            return Self.stubResponse(url: url, data: Data("{}".utf8), statusCode: 404)        }        let descriptor = ProviderDescriptorRegistry.descriptor(for: .copilot)        let settings = ProviderSettingsSnapshot.make(copilot: .init(            apiToken: "selected-token",            selectedAccountExternalIdentifier: "github:user:123",            budgetExtrasEnabled: true,            budgetCookieSource: .manual,            manualBudgetCookieHeader: "user_session=other"))        let context = Self.makeFetchContext(settings: settings)         let outcome = await descriptor.fetchPlan.fetchOutcome(context: context, provider: .copilot)         guard case let .success(result) = outcome.result else {            Issue.record("Expected Copilot usage fetch to succeed")            return        }        #expect(result.usage.primary?.usedPercent == 20)        #expect(result.usage.extraRateWindows == nil)        #expect(CopilotBudgetBindingStubURLProtocol.requests().contains {            $0.url?.query?.contains("page=") == true        } == false)    }     @Test    func `stale selected account identifier is ignored for budget cookie binding`() async {        let registered = URLProtocol.registerClass(CopilotBudgetBindingStubURLProtocol.self)        defer {            if registered {                URLProtocol.unregisterClass(CopilotBudgetBindingStubURLProtocol.self)            }            CopilotBudgetBindingStubURLProtocol.reset()        }        CopilotBudgetBindingStubURLProtocol.reset()        CopilotBudgetBindingStubURLProtocol.handler = { request in            guard let url = request.url else {                throw URLError(.badURL)            }            if url.host == "api.github.com", url.path == "/copilot_internal/user" {                return Self.stubResponse(                    url: url,                    data: Data("""                    {                      "quota_snapshots": {                        "premium_interactions": {                          "entitlement": 300,                          "remaining": 240,                          "percent_remaining": 80,                          "quota_id": "premium"                        }                      },                      "copilot_plan": "pro"                    }                    """.utf8))            }            if url.host == "api.github.com", url.path == "/user" {                return Self.stubResponse(                    url: url,                    data: Data(#"{"id":999,"login":"newuser"}"#.utf8))            }            if url.host == "github.com", url.path == "/settings/billing/budgets", url.query == nil {                return Self.stubResponse(                    url: url,                    data: Data("""                    <meta name="x-fetch-nonce" content="nonce">                    <meta name="octolytics-actor-id" content="123">                    <meta name="user-login" content="olduser">                    """.utf8))            }            Issue.record("Unexpected request: \(url.absoluteString)")            return Self.stubResponse(url: url, data: Data("{}".utf8), statusCode: 404)        }        let descriptor = ProviderDescriptorRegistry.descriptor(for: .copilot)        let settings = ProviderSettingsSnapshot.make(copilot: .init(            apiToken: "new-selected-token",            selectedAccountExternalIdentifier: "github:user:123",            budgetExtrasEnabled: true,            budgetCookieSource: .manual,            manualBudgetCookieHeader: "user_session=old-browser-account"))        let context = Self.makeFetchContext(settings: settings)         let outcome = await descriptor.fetchPlan.fetchOutcome(context: context, provider: .copilot)         guard case let .success(result) = outcome.result else {            Issue.record("Expected Copilot usage fetch to succeed")            return        }        #expect(result.usage.primary?.usedPercent == 20)        #expect(result.usage.extraRateWindows == nil)        #expect(CopilotBudgetBindingStubURLProtocol.requests().contains {            $0.url?.path == "/user"        })        #expect(CopilotBudgetBindingStubURLProtocol.requests().contains {            $0.url?.query?.contains("page=") == true        } == false)    }     @Test    func `invalid github budget JSON maps to invalid response`() async throws {        let transport = ProviderHTTPTransportStub { request in            guard let url = request.url,                  let response = HTTPURLResponse(                      url: url,                      statusCode: 200,                      httpVersion: "HTTP/1.1",                      headerFields: nil)            else {                throw URLError(.badServerResponse)            }            if request.url?.query?.contains("page=") == true {                return (Data("{".utf8), response)            }            return (Data(#"<meta name="x-fetch-nonce" content="nonce">"#.utf8), response)        }        let fetcher = CopilotBudgetWebFetcher(            transport: transport,            now: { Date(timeIntervalSince1970: 1_780_358_400) })         do {            _ = try await fetcher.fetchBudgetWindows(cookieHeader: "user_session=abc")            Issue.record("Expected invalidResponse")        } catch let error as CopilotBudgetWebFetcher.Error {            #expect(error == .invalidResponse)        }    }     @Test    func `cached cookie non auth errors do not fall back to browser import`() async throws {        KeychainCacheStore.setTestStoreForTesting(true)        defer { KeychainCacheStore.setTestStoreForTesting(false) }        CookieHeaderCache.store(provider: .copilot, cookieHeader: "user_session=cached", sourceLabel: "Chrome")        defer { CookieHeaderCache.clear(provider: .copilot) }         let transport = ProviderHTTPTransportStub { request in            guard let url = request.url,                  let response = HTTPURLResponse(                      url: url,                      statusCode: 500,                      httpVersion: "HTTP/1.1",                      headerFields: nil)            else {                throw URLError(.badServerResponse)            }            return (Data("{}".utf8), response)        }        let fetcher = CopilotBudgetWebFetcher(transport: transport)         do {            _ = try await fetcher.fetchBudgetWindows()            Issue.record("Expected badStatus")

Lines 1–500 of 721.