mirror of
https://github.com/RevenueCat/purchases-ios
synced 2026-11-11 22:55:55 +05:45
399 lines
17 KiB
Swift
399 lines
17 KiB
Swift
//
|
|
// Copyright RevenueCat Inc. All Rights Reserved.
|
|
//
|
|
// Licensed under the MIT License (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// https://opensource.org/licenses/MIT
|
|
//
|
|
// BackendGetCustomerCenterConfigTests.swift
|
|
//
|
|
// Created by Cesar de la Vega on 29/6/24.
|
|
|
|
import Foundation
|
|
import Nimble
|
|
import XCTest
|
|
|
|
@testable import RevenueCat
|
|
|
|
class BackendGetCustomerCenterConfigTests: BaseBackendTests {
|
|
|
|
override func createClient() -> MockHTTPClient {
|
|
super.createClient(#file)
|
|
}
|
|
|
|
func testGetCustomerCenterConfigCallsHTTPMethod() {
|
|
self.httpClient.mock(
|
|
requestPath: .getCustomerCenterConfig(appUserID: Self.userID),
|
|
response: .init(statusCode: .success, response: Self.customerCenterResponse as [String: Any])
|
|
)
|
|
|
|
let result = waitUntilValue { completed in
|
|
self.customerCenterConfig.getCustomerCenterConfig(appUserID: Self.userID,
|
|
isAppBackgrounded: false,
|
|
completion: completed)
|
|
}
|
|
|
|
expect(result).to(beSuccess())
|
|
expect(self.httpClient.calls).to(haveCount(1))
|
|
expect(self.operationDispatcher.invokedDispatchOnWorkerThreadDelayParam) == JitterableDelay.none
|
|
}
|
|
|
|
func testGetCustomerCenterConfigPassesLocales() {
|
|
self.createDependencies(localesProvider: MockPreferredLocalesProvider(stubbedLocales: ["en_EN", "es_ES"]))
|
|
|
|
self.httpClient.mock(
|
|
requestPath: .getCustomerCenterConfig(appUserID: Self.userID),
|
|
response: .init(statusCode: .success, response: Self.customerCenterResponse as [String: Any])
|
|
)
|
|
|
|
let result = waitUntilValue { completed in
|
|
self.customerCenterConfig.getCustomerCenterConfig(appUserID: Self.userID,
|
|
isAppBackgrounded: false,
|
|
completion: completed)
|
|
}
|
|
|
|
expect(result).to(beSuccess())
|
|
expect(self.httpClient.calls).to(haveCount(1))
|
|
expect(self.httpClient.calls[0].headers["X-Preferred-Locales"]) == "en_EN,es_ES"
|
|
}
|
|
|
|
func testGetCustomerCenterConfigCallsHTTPMethodWithRandomDelay() {
|
|
self.httpClient.mock(
|
|
requestPath: .getCustomerCenterConfig(appUserID: Self.userID),
|
|
response: .init(statusCode: .success, response: Self.customerCenterResponse as [String: Any])
|
|
)
|
|
|
|
let result = waitUntilValue { completed in
|
|
self.customerCenterConfig.getCustomerCenterConfig(appUserID: Self.userID,
|
|
isAppBackgrounded: true,
|
|
completion: completed)
|
|
}
|
|
|
|
expect(result).to(beSuccess())
|
|
expect(self.httpClient.calls).to(haveCount(1))
|
|
expect(self.operationDispatcher.invokedDispatchOnWorkerThreadDelayParam) == .default
|
|
}
|
|
|
|
func testGetCustomerCenterConfigCachesForSameUserID() {
|
|
self.httpClient.mock(
|
|
requestPath: .getCustomerCenterConfig(appUserID: Self.userID),
|
|
response: .init(statusCode: .success,
|
|
response: Self.customerCenterResponse as [String: Any],
|
|
delay: .milliseconds(10))
|
|
)
|
|
self.customerCenterConfig.getCustomerCenterConfig(appUserID: Self.userID, isAppBackgrounded: false) { _ in }
|
|
self.customerCenterConfig.getCustomerCenterConfig(appUserID: Self.userID, isAppBackgrounded: false) { _ in }
|
|
|
|
expect(self.httpClient.calls).toEventually(haveCount(1))
|
|
}
|
|
|
|
func testRepeatedRequestsLogDebugMessage() {
|
|
self.httpClient.mock(
|
|
requestPath: .getCustomerCenterConfig(appUserID: Self.userID),
|
|
response: .init(statusCode: .success,
|
|
response: Self.customerCenterResponse as [String: Any],
|
|
delay: .milliseconds(10))
|
|
)
|
|
self.customerCenterConfig.getCustomerCenterConfig(appUserID: Self.userID, isAppBackgrounded: false) { _ in }
|
|
self.customerCenterConfig.getCustomerCenterConfig(appUserID: Self.userID, isAppBackgrounded: false) { _ in }
|
|
|
|
expect(self.httpClient.calls).toEventually(haveCount(1))
|
|
|
|
self.logger.verifyMessageWasLogged(
|
|
"Network operation '\(GetCustomerCenterConfigOperation.self)' found with the same cache key",
|
|
level: .debug
|
|
)
|
|
}
|
|
|
|
func testGetCustomerConfigDoesntCacheForMultipleUserID() {
|
|
let response = MockHTTPClient.Response(statusCode: .success,
|
|
response: Self.customerCenterResponse as [String: Any])
|
|
let userID2 = "user_id_2"
|
|
|
|
self.httpClient.mock(requestPath: .getCustomerCenterConfig(appUserID: Self.userID), response: response)
|
|
self.httpClient.mock(requestPath: .getCustomerCenterConfig(appUserID: userID2), response: response)
|
|
|
|
self.customerCenterConfig.getCustomerCenterConfig(appUserID: Self.userID,
|
|
isAppBackgrounded: false,
|
|
completion: { _ in })
|
|
self.customerCenterConfig.getCustomerCenterConfig(appUserID: userID2,
|
|
isAppBackgrounded: false,
|
|
completion: { _ in })
|
|
|
|
expect(self.httpClient.calls).toEventually(haveCount(2))
|
|
}
|
|
|
|
func testGetCustomerCenterConfig() throws {
|
|
self.httpClient.mock(
|
|
requestPath: .getCustomerCenterConfig(appUserID: Self.userID),
|
|
response: .init(statusCode: .success, response: Self.customerCenterResponse as [String: Any])
|
|
)
|
|
|
|
let result: Atomic<Result<CustomerCenterConfigResponse, BackendError>?> = nil
|
|
self.customerCenterConfig.getCustomerCenterConfig(appUserID: Self.userID, isAppBackgrounded: false) {
|
|
result.value = $0
|
|
}
|
|
|
|
expect(result.value).toEventuallyNot(beNil())
|
|
|
|
let response = try XCTUnwrap(result.value?.value)
|
|
let customerCenter = try XCTUnwrap(response.customerCenter)
|
|
let appearance = try XCTUnwrap(customerCenter.appearance)
|
|
let support = try XCTUnwrap(customerCenter.support)
|
|
|
|
expect(customerCenter.localization.locale) == "en_US"
|
|
expect(customerCenter.localization.localizedStrings).to(haveCount(2))
|
|
|
|
expect(appearance.dark.accentColor) == "#ffffff"
|
|
expect(appearance.dark.backgroundColor) == "#000000"
|
|
expect(appearance.dark.textColor) == "#000000"
|
|
expect(appearance.light.accentColor) == "#000000"
|
|
expect(appearance.light.backgroundColor) == "#ffffff"
|
|
expect(appearance.light.textColor) == "#ffffff"
|
|
|
|
let screens = try XCTUnwrap(customerCenter.screens)
|
|
expect(screens).to(haveCount(2))
|
|
|
|
let noActiveScreen = try XCTUnwrap(customerCenter.screens[
|
|
CustomerCenterConfigData.Screen.ScreenType.noActive.rawValue
|
|
])
|
|
expect(noActiveScreen.title) == "No subscriptions found"
|
|
expect(noActiveScreen.subtitle) == "We can try checking your account for any previous purchases"
|
|
|
|
let managementScreen = try XCTUnwrap(customerCenter.screens[
|
|
CustomerCenterConfigData.Screen.ScreenType.management.rawValue
|
|
])
|
|
expect(managementScreen.type) == .management
|
|
expect(managementScreen.title) == "How can we help?"
|
|
|
|
let noActiveScreenPaths = noActiveScreen.paths
|
|
expect(noActiveScreenPaths).to(haveCount(1))
|
|
|
|
let managementPaths = managementScreen.paths
|
|
expect(managementPaths).to(haveCount(4))
|
|
|
|
let path1 = managementPaths[0]
|
|
expect(path1.id) == "ownmsldfow"
|
|
expect(path1.title) == "Didn't receive purchase"
|
|
expect(path1.type) == .missingPurchase
|
|
|
|
let path2 = managementPaths[1]
|
|
expect(path2.id) == "nwodkdnfaoeb"
|
|
expect(path2.title) == "Request a refund"
|
|
expect(path2.type) == .refundRequest
|
|
let promotionalOffer1 = try XCTUnwrap(path2.promotionalOffer)
|
|
expect(promotionalOffer1.iosOfferId) == "rc-refund-offer"
|
|
|
|
let path3 = managementPaths[2]
|
|
expect(path3.id) == "nfoaiodifj9"
|
|
expect(path3.title) == "Change plans"
|
|
expect(path3.type) == .changePlans
|
|
|
|
let path4 = managementPaths[3]
|
|
expect(path4.id) == "jnkasldfhas"
|
|
expect(path4.title) == "Cancel subscription"
|
|
expect(path4.type) == .cancel
|
|
|
|
let feedbackSurvey = try XCTUnwrap(path4.feedbackSurvey)
|
|
expect(feedbackSurvey.title) == "Why are you cancelling?"
|
|
expect(feedbackSurvey.options).to(haveCount(3))
|
|
|
|
let option1 = feedbackSurvey.options[0]
|
|
expect(option1.id) == "iewrthals"
|
|
expect(option1.title) == "Too expensive"
|
|
let promotionalOffer2 = try XCTUnwrap(option1.promotionalOffer)
|
|
expect(promotionalOffer2.iosOfferId) == "rc-cancel-offer"
|
|
|
|
let option2 = feedbackSurvey.options[1]
|
|
expect(option2.id) == "qklpadsfj"
|
|
expect(option2.title) == "Don't use the app"
|
|
let promotionalOffer3 = try XCTUnwrap(option2.promotionalOffer)
|
|
expect(promotionalOffer3.iosOfferId) == "rc-cancel-offer"
|
|
|
|
let option3 = feedbackSurvey.options[2]
|
|
expect(option3.id) == "jargnapocps"
|
|
expect(option3.title) == "Bought by mistake"
|
|
|
|
expect(support.email) == "support@revenuecat.com"
|
|
expect(support.displayPurchaseHistoryLink) == true
|
|
expect(support.shouldWarnCustomerToUpdate) == false
|
|
}
|
|
|
|
func testGetCustomerCenterConfigFailSendsNil() {
|
|
self.httpClient.mock(
|
|
requestPath: .getCustomerCenterConfig(appUserID: Self.userID),
|
|
response: .init(error: .unexpectedResponse(nil))
|
|
)
|
|
|
|
let result = waitUntilValue { completed in
|
|
self.customerCenterConfig.getCustomerCenterConfig(appUserID: Self.userID,
|
|
isAppBackgrounded: false,
|
|
completion: completed)
|
|
}
|
|
|
|
expect(result).to(beFailure())
|
|
}
|
|
|
|
func testGetCustomerCenterConfigNetworkErrorSendsError() {
|
|
let mockedError: NetworkError = .unexpectedResponse(nil)
|
|
|
|
self.httpClient.mock(
|
|
requestPath: .getCustomerCenterConfig(appUserID: Self.userID),
|
|
response: .init(error: mockedError)
|
|
)
|
|
|
|
let result = waitUntilValue { completed in
|
|
self.customerCenterConfig.getCustomerCenterConfig(appUserID: Self.userID,
|
|
isAppBackgrounded: false,
|
|
completion: completed)
|
|
}
|
|
|
|
expect(result).to(beFailure())
|
|
expect(result?.error) == .networkError(mockedError)
|
|
}
|
|
|
|
func testGetCustomerCenterConfigSkipsBackendCallIfAppUserIDIsEmpty() {
|
|
waitUntil { completed in
|
|
self.customerCenterConfig.getCustomerCenterConfig(appUserID: "", isAppBackgrounded: false) { _ in
|
|
completed()
|
|
}
|
|
}
|
|
|
|
expect(self.httpClient.calls).to(beEmpty())
|
|
}
|
|
|
|
func testGetCustomerCenterConfigCallsCompletionWithErrorIfAppUserIDIsEmpty() {
|
|
let receivedError = waitUntilValue { completed in
|
|
self.customerCenterConfig.getCustomerCenterConfig(appUserID: "", isAppBackgrounded: false) { result in
|
|
completed(result.error)
|
|
}
|
|
}
|
|
|
|
expect(receivedError) == .missingAppUserID()
|
|
}
|
|
|
|
}
|
|
|
|
private extension BackendGetCustomerCenterConfigTests {
|
|
|
|
static let customerCenterResponse: [String: Any] = [
|
|
"customer_center": [
|
|
"localization": [
|
|
"locale": "en_US",
|
|
"localized_strings": [
|
|
"cancel": "Cancel",
|
|
"back": "Back"
|
|
] as [String: Any],
|
|
"supported": [
|
|
"en_US"
|
|
] as [Any]
|
|
] as [String: Any],
|
|
"screens": [
|
|
"MANAGEMENT": [
|
|
"paths": [
|
|
[
|
|
"id": "ownmsldfow",
|
|
"title": "Didn't receive purchase",
|
|
"type": "MISSING_PURCHASE"
|
|
] as [String: Any],
|
|
[
|
|
"id": "nwodkdnfaoeb",
|
|
"promotional_offer": [
|
|
"ios_offer_id": "rc-refund-offer",
|
|
"eligible": true,
|
|
"title": "Wait!",
|
|
"subtitle": "Here's an offer for you",
|
|
"product_mapping": [
|
|
"product_id": "offer_id"
|
|
]
|
|
] as [String: Any],
|
|
"title": "Request a refund",
|
|
"type": "REFUND_REQUEST"
|
|
] as [String: Any],
|
|
[
|
|
"id": "nfoaiodifj9",
|
|
"title": "Change plans",
|
|
"type": "CHANGE_PLANS"
|
|
] as [String: Any],
|
|
[
|
|
"feedback_survey": [
|
|
"options": [
|
|
[
|
|
"id": "iewrthals",
|
|
"promotional_offer": [
|
|
"ios_offer_id": "rc-cancel-offer",
|
|
"eligible": false,
|
|
"title": "Wait!",
|
|
"subtitle": "Here's an offer for you",
|
|
"product_mapping": [
|
|
"product_id": "offer_id"
|
|
]
|
|
] as [String: Any],
|
|
"title": "Too expensive"
|
|
] as [String: Any],
|
|
[
|
|
"id": "qklpadsfj",
|
|
"promotional_offer": [
|
|
"ios_offer_id": "rc-cancel-offer",
|
|
"eligible": false,
|
|
"title": "Wait!",
|
|
"subtitle": "Here's an offer for you",
|
|
"product_mapping": [
|
|
"product_id": "offer_id"
|
|
]
|
|
] as [String: Any],
|
|
"title": "Don't use the app"
|
|
] as [String: Any],
|
|
[
|
|
"id": "jargnapocps",
|
|
"title": "Bought by mistake"
|
|
] as [String: Any]
|
|
] as [Any],
|
|
"title": "Why are you cancelling?"
|
|
] as [String: Any],
|
|
"id": "jnkasldfhas",
|
|
"title": "Cancel subscription",
|
|
"type": "CANCEL"
|
|
] as [String: Any]
|
|
] as [Any],
|
|
"title": "How can we help?",
|
|
"type": "MANAGEMENT"
|
|
] as [String: Any],
|
|
"NO_ACTIVE": [
|
|
"paths": [
|
|
[
|
|
"id": "9q9719171o",
|
|
"title": "Check purchases",
|
|
"type": "MISSING_PURCHASE"
|
|
] as [String: Any]
|
|
] as [Any],
|
|
"subtitle": "We can try checking your account for any previous purchases",
|
|
"title": "No subscriptions found",
|
|
"type": "NO_ACTIVE"
|
|
] as [String: Any]
|
|
] as [String: Any],
|
|
"appearance": [
|
|
"dark": [
|
|
"accent_color": "#ffffff",
|
|
"background_color": "#000000",
|
|
"text_color": "#000000"
|
|
] as [String: Any],
|
|
"light": [
|
|
"accent_color": "#000000",
|
|
"background_color": "#ffffff",
|
|
"text_color": "#ffffff"
|
|
] as [String: Any],
|
|
"mode": "CUSTOM"
|
|
] as [String: Any],
|
|
"support": [
|
|
"email": "support@revenuecat.com",
|
|
"should_warn_customer_to_update": false,
|
|
"display_purchase_history_link": true
|
|
] as [String: Any]
|
|
] as [String: Any]
|
|
]
|
|
|
|
}
|