From 7b7a61e64349f4224a467373ec7348850b92af60 Mon Sep 17 00:00:00 2001 From: slamhan Date: Wed, 23 Sep 2026 11:37:48 +0800 Subject: [PATCH] fix(pricing): return every territory from per-territory price lists The IAP price schedule and the offer-code, promotional-offer and win-back price lists called Apple without a limit and read a single page, so any territory past Apple's default 50 was silently dropped. They now request limit=200 and follow every page through a shared APIClient.requestAllPages(_:nextCursor:) helper, which the subscription price schedule now uses too. Verified live: a promotional offer priced in 175 territories lists all 175 over both the CLI and REST. Co-Authored-By: Claude Opus 5.5 (1M context) --- CHANGELOG.md | 1 + .../SDKInAppPurchaseOfferCodeRepository.swift | 9 ++-- .../SDKInAppPurchasePriceRepository.swift | 14 ++++--- .../SDKSubscriptionOfferCodeRepository.swift | 9 ++-- ...bscriptionPromotionalOfferRepository.swift | 9 ++-- .../SDKSubscriptionPriceRepository.swift | 19 ++++----- .../SDKWinBackOfferRepository.swift | 9 ++-- Sources/Infrastructure/Client/APIClient.swift | 20 +++++++++ ...nAppPurchaseOfferCodeRepositoryTests.swift | 27 ++++++++++++ ...SDKInAppPurchasePriceRepositoryTests.swift | 42 +++++++++++++++++++ ...SubscriptionOfferCodeRepositoryTests.swift | 27 ++++++++++++ ...ptionPromotionalOfferRepositoryTests.swift | 27 ++++++++++++ .../SDKWinBackOfferRepositoryTests.swift | 27 ++++++++++++ 13 files changed, 211 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f58aed16..8cb86f35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - **`asc subscriptions prices set-batch` sends one request instead of one per territory** — it now sends a single `PATCH /v1/subscriptions/{id}` with every price inlined (`SubscriptionPriceInlineCreate`) instead of one `POST /v1/subscriptionPrices` per territory. Pricing all 175 territories took 175 sequential POSTs (over a minute per subscription), and a failure partway through left some territories priced and others not; Apple now applies the batch all-or-nothing. The REST `POST /api/v1/subscriptions/:id/prices` route benefits too, since it uses the same repository method. - **Subscription price schedules no longer drop manual prices after the first 50** — `subscription-price-schedule get` (and the read-back after `set-batch`) fetched `/v1/subscriptions/{id}/prices` without a `limit`, so only Apple's default first page came back and the other territories were filled with the first price's equalizations, showing wrong prices where custom per-territory prices were set. It now requests `limit=200` and follows the pagination cursor. +- **The other per-territory price lists no longer stop at 50** — the same truncation affected `iap-price-schedule get` (manual prices), `iap-offer-codes prices list`, `subscription-offer-codes prices list`, `subscription-promotional-offers prices list` and `win-back-offers prices list`. They called Apple without a `limit` and read one page, so any territory past Apple's default 50 was missing. They now request `limit=200` and follow every page through a shared `APIClient.requestAllPages(_:nextCursor:)` helper, which `subscription-price-schedule get` uses too. --- diff --git a/Sources/Infrastructure/Apps/InAppPurchases/OfferCodes/SDKInAppPurchaseOfferCodeRepository.swift b/Sources/Infrastructure/Apps/InAppPurchases/OfferCodes/SDKInAppPurchaseOfferCodeRepository.swift index dfc6bc3c..ed6c0fb4 100644 --- a/Sources/Infrastructure/Apps/InAppPurchases/OfferCodes/SDKInAppPurchaseOfferCodeRepository.swift +++ b/Sources/Infrastructure/Apps/InAppPurchases/OfferCodes/SDKInAppPurchaseOfferCodeRepository.swift @@ -84,9 +84,12 @@ public struct SDKInAppPurchaseOfferCodeRepository: InAppPurchaseOfferCodeReposit // MARK: - Prices public func listPrices(offerCodeId: String) async throws -> [Domain.InAppPurchaseOfferCodePrice] { - let request = APIEndpoint.v1.inAppPurchaseOfferCodes.id(offerCodeId).prices.get() - let response = try await client.request(request) - return response.data.map { mapPrice($0, offerCodeId: offerCodeId) } + // One price per territory — follow every page to get all 175. + let pages = try await client.requestAllPages( + APIEndpoint.v1.inAppPurchaseOfferCodes.id(offerCodeId).prices.get(parameters: .init(limit: 200)), + nextCursor: { $0.meta?.paging.nextCursor } + ) + return pages.flatMap(\.data).map { mapPrice($0, offerCodeId: offerCodeId) } } // MARK: - Custom Codes diff --git a/Sources/Infrastructure/Apps/InAppPurchases/SDKInAppPurchasePriceRepository.swift b/Sources/Infrastructure/Apps/InAppPurchases/SDKInAppPurchasePriceRepository.swift index d3662b68..432d2e00 100644 --- a/Sources/Infrastructure/Apps/InAppPurchases/SDKInAppPurchasePriceRepository.swift +++ b/Sources/Infrastructure/Apps/InAppPurchases/SDKInAppPurchasePriceRepository.swift @@ -30,19 +30,23 @@ public struct SDKInAppPurchasePriceRepository: InAppPurchasePriceRepository, @un } // Step 2: manual prices with territory + price point includes. - let manualResponse = try await client.request( + // Follow every page — manual prices can cover all 175 territories. + let manualPages = try await client.requestAllPages( APIEndpoint.v1.inAppPurchasePriceSchedules.id(scheduleId).manualPrices.get(parameters: .init( fieldsInAppPurchasePrices: [.inAppPurchasePricePoint, .territory], fieldsInAppPurchasePricePoints: [.customerPrice, .proceeds, .territory], fieldsTerritories: [.currency], + limit: 200, include: [.inAppPurchasePricePoint, .territory] - )) + )), + nextCursor: { $0.meta?.paging.nextCursor } ) + let manualData = manualPages.flatMap(\.data) // Index included territories + price points for lookup. var territoriesById: [String: Domain.Territory] = [:] var pricePointsById: [String: AppStoreConnect_Swift_SDK.InAppPurchasePricePoint] = [:] - for item in manualResponse.included ?? [] { + for item in manualPages.flatMap({ $0.included ?? [] }) { switch item { case .territory(let t): territoriesById[t.id] = Domain.Territory(id: t.id, currency: t.attributes?.currency) @@ -51,7 +55,7 @@ public struct SDKInAppPurchasePriceRepository: InAppPurchasePriceRepository, @un } } - let manualPrices: [Domain.TerritoryPrice] = manualResponse.data.compactMap { price in + let manualPrices: [Domain.TerritoryPrice] = manualData.compactMap { price in guard let territoryId = price.relationships?.territory?.data?.id, let territory = territoriesById[territoryId], @@ -65,7 +69,7 @@ public struct SDKInAppPurchasePriceRepository: InAppPurchasePriceRepository, @un // Step 3: equalizations — fetch the full ~175-territory list using the manual price's // price point. If there are no manual prices we have nothing to equalize from. - let manualPricePointId = manualResponse.data.first?.relationships?.inAppPurchasePricePoint?.data?.id + let manualPricePointId = manualData.first?.relationships?.inAppPurchasePricePoint?.data?.id var equalizedPrices: [Domain.TerritoryPrice] = [] if let pricePointId = manualPricePointId { equalizedPrices = (try? await fetchEqualizedTerritoryPrices(pricePointId: pricePointId)) ?? [] diff --git a/Sources/Infrastructure/Apps/Subscriptions/OfferCodes/SDKSubscriptionOfferCodeRepository.swift b/Sources/Infrastructure/Apps/Subscriptions/OfferCodes/SDKSubscriptionOfferCodeRepository.swift index d5f84b39..6c8fa385 100644 --- a/Sources/Infrastructure/Apps/Subscriptions/OfferCodes/SDKSubscriptionOfferCodeRepository.swift +++ b/Sources/Infrastructure/Apps/Subscriptions/OfferCodes/SDKSubscriptionOfferCodeRepository.swift @@ -189,9 +189,12 @@ public struct SDKSubscriptionOfferCodeRepository: SubscriptionOfferCodeRepositor // MARK: - Prices public func listPrices(offerCodeId: String) async throws -> [Domain.SubscriptionOfferCodePrice] { - let request = APIEndpoint.v1.subscriptionOfferCodes.id(offerCodeId).prices.get() - let response = try await client.request(request) - return response.data.map { mapPrice($0, offerCodeId: offerCodeId) } + // One price per territory — follow every page to get all 175. + let pages = try await client.requestAllPages( + APIEndpoint.v1.subscriptionOfferCodes.id(offerCodeId).prices.get(parameters: .init(limit: 200)), + nextCursor: { $0.meta?.paging.nextCursor } + ) + return pages.flatMap(\.data).map { mapPrice($0, offerCodeId: offerCodeId) } } private func mapPrice( diff --git a/Sources/Infrastructure/Apps/Subscriptions/PromotionalOffers/SDKSubscriptionPromotionalOfferRepository.swift b/Sources/Infrastructure/Apps/Subscriptions/PromotionalOffers/SDKSubscriptionPromotionalOfferRepository.swift index 5ac642b6..7145a40c 100644 --- a/Sources/Infrastructure/Apps/Subscriptions/PromotionalOffers/SDKSubscriptionPromotionalOfferRepository.swift +++ b/Sources/Infrastructure/Apps/Subscriptions/PromotionalOffers/SDKSubscriptionPromotionalOfferRepository.swift @@ -69,9 +69,12 @@ public struct SDKSubscriptionPromotionalOfferRepository: SubscriptionPromotional } public func listPrices(offerId: String) async throws -> [Domain.SubscriptionPromotionalOfferPrice] { - let request = APIEndpoint.v1.subscriptionPromotionalOffers.id(offerId).prices.get() - let response = try await client.request(request) - return response.data.map { mapPrice($0, offerId: offerId) } + // One price per territory — follow every page to get all 175. + let pages = try await client.requestAllPages( + APIEndpoint.v1.subscriptionPromotionalOffers.id(offerId).prices.get(parameters: .init(limit: 200)), + nextCursor: { $0.meta?.paging.nextCursor } + ) + return pages.flatMap(\.data).map { mapPrice($0, offerId: offerId) } } private func mapOffer( diff --git a/Sources/Infrastructure/Apps/Subscriptions/SDKSubscriptionPriceRepository.swift b/Sources/Infrastructure/Apps/Subscriptions/SDKSubscriptionPriceRepository.swift index 1979873a..f43a9b37 100644 --- a/Sources/Infrastructure/Apps/Subscriptions/SDKSubscriptionPriceRepository.swift +++ b/Sources/Infrastructure/Apps/Subscriptions/SDKSubscriptionPriceRepository.swift @@ -103,23 +103,18 @@ public struct SDKSubscriptionPriceRepository: SubscriptionPriceRepository, @unch public func getPriceSchedule(subscriptionId: String) async throws -> Domain.SubscriptionPriceSchedule? { // Step 1: GET /v1/subscriptions/{id}/prices?include=territory,subscriptionPricePoint, // following every page — a subscription can have a manual price in all 175 territories. - var pricesData: [AppStoreConnect_Swift_SDK.SubscriptionPrice] = [] - var pricesIncluded: [SubscriptionPricesResponse.IncludedItem] = [] - var cursor: String? - repeat { - var request = APIEndpoint.v1.subscriptions.id(subscriptionId).prices.get(parameters: .init( + let pages = try await client.requestAllPages( + APIEndpoint.v1.subscriptions.id(subscriptionId).prices.get(parameters: .init( fieldsSubscriptionPrices: [.territory, .subscriptionPricePoint], fieldsTerritories: [.currency], fieldsSubscriptionPricePoints: [.customerPrice, .proceeds, .proceedsYear2, .territory], limit: 200, include: [.territory, .subscriptionPricePoint] - )) - if let cursor { request.query = (request.query ?? []) + [("cursor", cursor)] } - let page = try await client.request(request) - pricesData += page.data - pricesIncluded += page.included ?? [] - cursor = page.meta?.paging.nextCursor - } while cursor != nil + )), + nextCursor: { $0.meta?.paging.nextCursor } + ) + let pricesData = pages.flatMap(\.data) + let pricesIncluded = pages.flatMap { $0.included ?? [] } // No prices set → no schedule yet. guard !pricesData.isEmpty else { return nil } diff --git a/Sources/Infrastructure/Apps/Subscriptions/WinBackOffers/SDKWinBackOfferRepository.swift b/Sources/Infrastructure/Apps/Subscriptions/WinBackOffers/SDKWinBackOfferRepository.swift index 1ef91604..1f82aff7 100644 --- a/Sources/Infrastructure/Apps/Subscriptions/WinBackOffers/SDKWinBackOfferRepository.swift +++ b/Sources/Infrastructure/Apps/Subscriptions/WinBackOffers/SDKWinBackOfferRepository.swift @@ -158,9 +158,12 @@ public struct SDKWinBackOfferRepository: WinBackOfferRepository, @unchecked Send } public func listPrices(offerId: String) async throws -> [Domain.WinBackOfferPrice] { - let request = APIEndpoint.v1.winBackOffers.id(offerId).prices.get() - let response = try await client.request(request) - return response.data.map { mapPrice($0, offerId: offerId) } + // One price per territory — follow every page to get all 175. + let pages = try await client.requestAllPages( + APIEndpoint.v1.winBackOffers.id(offerId).prices.get(parameters: .init(limit: 200)), + nextCursor: { $0.meta?.paging.nextCursor } + ) + return pages.flatMap(\.data).map { mapPrice($0, offerId: offerId) } } private func mapOffer( diff --git a/Sources/Infrastructure/Client/APIClient.swift b/Sources/Infrastructure/Client/APIClient.swift index 8fe31ae7..0649e938 100644 --- a/Sources/Infrastructure/Client/APIClient.swift +++ b/Sources/Infrastructure/Client/APIClient.swift @@ -6,3 +6,23 @@ public protocol APIClient { } extension APIProvider: APIClient {} + +extension APIClient { + /// Requests every page of a paginated list, re-sending `endpoint` with the next cursor + /// until Apple stops returning one. Use it when the caller needs the complete list: + /// Apple returns only its default page size (usually 50) when no `limit` is given, and + /// per-territory lists reach 175 entries. + func requestAllPages( + _ endpoint: Request, + nextCursor: (T) -> String? + ) async throws -> [T] { + var pages: [T] = [] + var request = endpoint + while true { + let page = try await self.request(request) + pages.append(page) + guard let cursor = nextCursor(page) else { return pages } + request.query = (endpoint.query ?? []) + [("cursor", cursor)] + } + } +} diff --git a/Tests/InfrastructureTests/Apps/InAppPurchases/OfferCodes/SDKInAppPurchaseOfferCodeRepositoryTests.swift b/Tests/InfrastructureTests/Apps/InAppPurchases/OfferCodes/SDKInAppPurchaseOfferCodeRepositoryTests.swift index 6f47cba0..6474af94 100644 --- a/Tests/InfrastructureTests/Apps/InAppPurchases/OfferCodes/SDKInAppPurchaseOfferCodeRepositoryTests.swift +++ b/Tests/InfrastructureTests/Apps/InAppPurchases/OfferCodes/SDKInAppPurchaseOfferCodeRepositoryTests.swift @@ -376,4 +376,31 @@ struct SDKInAppPurchaseOfferCodeRepositoryTests { let result = try await repo.fetchOneTimeUseCodeValues(oneTimeCodeId: "otc-1") #expect(result == "CODE1\nCODE2\nCODE3\n") } + + @Test func `offer code prices include every territory beyond the first page`() async throws { + func page(_ range: Range, nextCursor: String?) -> InAppPurchaseOfferPricesResponse { + InAppPurchaseOfferPricesResponse( + data: range.map { i in + AppStoreConnect_Swift_SDK.InAppPurchaseOfferPrice( + type: .inAppPurchaseOfferPrices, id: "p-\(i)", + relationships: .init( + territory: .init(data: .init(type: .territories, id: "T\(i)")), + pricePoint: .init(data: .init(type: .inAppPurchasePricePoints, id: "pp-\(i)")) + ) + ) + }, + links: .init(this: ""), + meta: .init(paging: .init(total: 175, limit: 200, nextCursor: nextCursor)) + ) + } + let stub = StubAPIClient() + stub.willReturnPages([page(0..<100, nextCursor: "page-2"), page(100..<175, nextCursor: nil)]) + + let repo = SDKInAppPurchaseOfferCodeRepository(client: stub) + let result = try await repo.listPrices(offerCodeId: "oc-1") + + #expect(result.count == 175) + #expect(result.last?.territory == "T174") + #expect(result.last?.pricePointId == "pp-174") + } } diff --git a/Tests/InfrastructureTests/Apps/InAppPurchases/SDKInAppPurchasePriceRepositoryTests.swift b/Tests/InfrastructureTests/Apps/InAppPurchases/SDKInAppPurchasePriceRepositoryTests.swift index 263f2e10..77fb32fb 100644 --- a/Tests/InfrastructureTests/Apps/InAppPurchases/SDKInAppPurchasePriceRepositoryTests.swift +++ b/Tests/InfrastructureTests/Apps/InAppPurchases/SDKInAppPurchasePriceRepositoryTests.swift @@ -283,4 +283,46 @@ struct SDKInAppPurchasePriceRepositoryTests { #expect(byTerritory["JPN"]?.customerPrice == "1500") #expect(byTerritory["JPN"]?.territory.currency == "JPY") } + + @Test func `price schedule includes every manually priced territory beyond the first page`() async throws { + func manualPricesPage(_ range: Range, nextCursor: String?) -> InAppPurchasePricesResponse { + InAppPurchasePricesResponse( + data: range.map { i in + AppStoreConnect_Swift_SDK.InAppPurchasePrice( + type: .inAppPurchasePrices, id: "price-\(i)", + relationships: .init( + inAppPurchasePricePoint: .init(data: .init(type: .inAppPurchasePricePoints, id: "pp-\(i)")), + territory: .init(data: .init(type: .territories, id: "T\(i)")) + ) + ) + }, + included: range.flatMap { i -> [InAppPurchasePricesResponse.IncludedItem] in [ + .territory(Territory(type: .territories, id: "T\(i)", attributes: .init(currency: "USD"))), + .inAppPurchasePricePoint(InAppPurchasePricePoint( + type: .inAppPurchasePricePoints, id: "pp-\(i)", + attributes: .init(customerPrice: "\(i).99", proceeds: "\(i).50") + )), + ] }, + links: .init(this: ""), + meta: .init(paging: .init(total: 175, limit: 200, nextCursor: nextCursor)) + ) + } + let stub = StubAPIClient() + stub.willReturn(InAppPurchasePriceScheduleResponse( + data: AppStoreConnect_Swift_SDK.InAppPurchasePriceSchedule(type: .inAppPurchasePriceSchedules, id: "iap-7"), + links: .init(this: "") + )) + stub.willReturnPages([ + manualPricesPage(0..<100, nextCursor: "page-2"), + manualPricesPage(100..<175, nextCursor: nil), + ]) + stub.willReturn(InAppPurchasePricePointsResponse(data: [], links: .init(this: ""))) + + let repo = SDKInAppPurchasePriceRepository(client: stub) + let result = try await repo.getPriceSchedule(iapId: "iap-7") + + let prices = result?.territoryPrices ?? [] + #expect(prices.count == 175) + #expect(prices.first { $0.territory.id == "T174" }?.customerPrice == "174.99") + } } diff --git a/Tests/InfrastructureTests/Apps/Subscriptions/OfferCodes/SDKSubscriptionOfferCodeRepositoryTests.swift b/Tests/InfrastructureTests/Apps/Subscriptions/OfferCodes/SDKSubscriptionOfferCodeRepositoryTests.swift index 3b1dd764..8afb363e 100644 --- a/Tests/InfrastructureTests/Apps/Subscriptions/OfferCodes/SDKSubscriptionOfferCodeRepositoryTests.swift +++ b/Tests/InfrastructureTests/Apps/Subscriptions/OfferCodes/SDKSubscriptionOfferCodeRepositoryTests.swift @@ -411,4 +411,31 @@ struct SDKSubscriptionOfferCodeRepositoryTests { let result = try await repo.fetchOneTimeUseCodeValues(oneTimeCodeId: "otc-1") #expect(result == "ABC\nDEF\n") } + + @Test func `offer code prices include every territory beyond the first page`() async throws { + func page(_ range: Range, nextCursor: String?) -> SubscriptionOfferCodePricesResponse { + SubscriptionOfferCodePricesResponse( + data: range.map { i in + AppStoreConnect_Swift_SDK.SubscriptionOfferCodePrice( + type: .subscriptionOfferCodePrices, id: "p-\(i)", + relationships: .init( + territory: .init(data: .init(type: .territories, id: "T\(i)")), + subscriptionPricePoint: .init(data: .init(type: .subscriptionPricePoints, id: "pp-\(i)")) + ) + ) + }, + links: .init(this: ""), + meta: .init(paging: .init(total: 175, limit: 200, nextCursor: nextCursor)) + ) + } + let stub = StubAPIClient() + stub.willReturnPages([page(0..<100, nextCursor: "page-2"), page(100..<175, nextCursor: nil)]) + + let repo = SDKSubscriptionOfferCodeRepository(client: stub) + let result = try await repo.listPrices(offerCodeId: "oc-1") + + #expect(result.count == 175) + #expect(result.last?.territory == "T174") + #expect(result.last?.subscriptionPricePointId == "pp-174") + } } diff --git a/Tests/InfrastructureTests/Apps/Subscriptions/SDKSubscriptionPromotionalOfferRepositoryTests.swift b/Tests/InfrastructureTests/Apps/Subscriptions/SDKSubscriptionPromotionalOfferRepositoryTests.swift index ec5cda03..20582ad9 100644 --- a/Tests/InfrastructureTests/Apps/Subscriptions/SDKSubscriptionPromotionalOfferRepositoryTests.swift +++ b/Tests/InfrastructureTests/Apps/Subscriptions/SDKSubscriptionPromotionalOfferRepositoryTests.swift @@ -106,4 +106,31 @@ struct SDKSubscriptionPromotionalOfferRepositoryTests { #expect(result[0].territory == "USA") #expect(result[0].subscriptionPricePointId == "spp-9") } + + @Test func `promotional offer prices include every territory beyond the first page`() async throws { + func page(_ range: Range, nextCursor: String?) -> SubscriptionPromotionalOfferPricesResponse { + SubscriptionPromotionalOfferPricesResponse( + data: range.map { i in + AppStoreConnect_Swift_SDK.SubscriptionPromotionalOfferPrice( + type: .subscriptionPromotionalOfferPrices, id: "p-\(i)", + relationships: .init( + territory: .init(data: .init(type: .territories, id: "T\(i)")), + subscriptionPricePoint: .init(data: .init(type: .subscriptionPricePoints, id: "pp-\(i)")) + ) + ) + }, + links: .init(this: ""), + meta: .init(paging: .init(total: 175, limit: 200, nextCursor: nextCursor)) + ) + } + let stub = StubAPIClient() + stub.willReturnPages([page(0..<100, nextCursor: "page-2"), page(100..<175, nextCursor: nil)]) + + let repo = SDKSubscriptionPromotionalOfferRepository(client: stub) + let result = try await repo.listPrices(offerId: "po-1") + + #expect(result.count == 175) + #expect(result.last?.territory == "T174") + #expect(result.last?.subscriptionPricePointId == "pp-174") + } } diff --git a/Tests/InfrastructureTests/Apps/Subscriptions/SDKWinBackOfferRepositoryTests.swift b/Tests/InfrastructureTests/Apps/Subscriptions/SDKWinBackOfferRepositoryTests.swift index 5f63926c..8bc096e9 100644 --- a/Tests/InfrastructureTests/Apps/Subscriptions/SDKWinBackOfferRepositoryTests.swift +++ b/Tests/InfrastructureTests/Apps/Subscriptions/SDKWinBackOfferRepositoryTests.swift @@ -165,4 +165,31 @@ struct SDKWinBackOfferRepositoryTests { #expect(result[0].territory == "USA") #expect(result[0].subscriptionPricePointId == "spp-9") } + + @Test func `win-back offer prices include every territory beyond the first page`() async throws { + func page(_ range: Range, nextCursor: String?) -> WinBackOfferPricesResponse { + WinBackOfferPricesResponse( + data: range.map { i in + AppStoreConnect_Swift_SDK.WinBackOfferPrice( + type: .winBackOfferPrices, id: "p-\(i)", + relationships: .init( + territory: .init(data: .init(type: .territories, id: "T\(i)")), + subscriptionPricePoint: .init(data: .init(type: .subscriptionPricePoints, id: "pp-\(i)")) + ) + ) + }, + links: .init(this: ""), + meta: .init(paging: .init(total: 175, limit: 200, nextCursor: nextCursor)) + ) + } + let stub = StubAPIClient() + stub.willReturnPages([page(0..<100, nextCursor: "page-2"), page(100..<175, nextCursor: nil)]) + + let repo = SDKWinBackOfferRepository(client: stub) + let result = try await repo.listPrices(offerId: "wb-1") + + #expect(result.count == 175) + #expect(result.last?.territory == "T174") + #expect(result.last?.subscriptionPricePointId == "pp-174") + } }