From 6b10f68abc1627accced571e311e07ab2c286f37 Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Mon, 15 Aug 2022 12:40:34 -0700 Subject: [PATCH] Created `PurchasesLoginTests` --- RevenueCat.xcodeproj/project.pbxproj | 4 + Sources/Networking/IdentityAPI.swift | 3 +- .../UnitTests/Mocks/MockIdentityManager.swift | 24 ++- .../Purchases/PurchasesLogInTests.swift | 143 ++++++++++++++++++ 4 files changed, 171 insertions(+), 3 deletions(-) create mode 100644 Tests/UnitTests/Purchasing/Purchases/PurchasesLogInTests.swift diff --git a/RevenueCat.xcodeproj/project.pbxproj b/RevenueCat.xcodeproj/project.pbxproj index 84b02bd6b4..b6d77b831f 100644 --- a/RevenueCat.xcodeproj/project.pbxproj +++ b/RevenueCat.xcodeproj/project.pbxproj @@ -303,6 +303,7 @@ 57D5412E27F6311C004CC35C /* OfferingsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57D5412D27F6311C004CC35C /* OfferingsResponse.swift */; }; 57D5414227F656D9004CC35C /* NetworkError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57D5414127F656D9004CC35C /* NetworkError.swift */; }; 57D56FCA2853C005009E8E1E /* StringExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57D56FC92853C005009E8E1E /* StringExtensionsTests.swift */; }; + 57DBFA5D28AADA43002D18CA /* PurchasesLogInTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57DBFA5C28AADA43002D18CA /* PurchasesLogInTests.swift */; }; 57DC9F4627CC2E4900DA6AF9 /* HTTPRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57DC9F4527CC2E4900DA6AF9 /* HTTPRequest.swift */; }; 57DC9F4A27CD37BA00DA6AF9 /* HTTPStatusCodeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57DC9F4927CD37BA00DA6AF9 /* HTTPStatusCodeTests.swift */; }; 57DE806D28074976008D6C6F /* Storefront.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57DE806C28074976008D6C6F /* Storefront.swift */; }; @@ -797,6 +798,7 @@ 57D5414127F656D9004CC35C /* NetworkError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkError.swift; sourceTree = ""; }; 57D56FC92853C005009E8E1E /* StringExtensionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtensionsTests.swift; sourceTree = ""; }; 57DB9EE7281B3C6100BBAA21 /* __Snapshots__ */ = {isa = PBXFileReference; lastKnownFileType = folder; path = __Snapshots__; sourceTree = ""; }; + 57DBFA5C28AADA43002D18CA /* PurchasesLogInTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PurchasesLogInTests.swift; sourceTree = ""; }; 57DC9F4527CC2E4900DA6AF9 /* HTTPRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPRequest.swift; sourceTree = ""; }; 57DC9F4927CD37BA00DA6AF9 /* HTTPStatusCodeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPStatusCodeTests.swift; sourceTree = ""; }; 57DE806C28074976008D6C6F /* Storefront.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storefront.swift; sourceTree = ""; }; @@ -1707,6 +1709,7 @@ 57E415FE28469EAB00EA5460 /* PurchasesGetProductsTests.swift */, 57FDAA952846BDE2009A48F1 /* PurchasesTransactionHandlingTests.swift */, 57FDAA992846C2BD009A48F1 /* PurchasesDelegateTests.swift */, + 57DBFA5C28AADA43002D18CA /* PurchasesLogInTests.swift */, ); path = Purchases; sourceTree = ""; @@ -2615,6 +2618,7 @@ 35D8330A262FBA9A00E60AC5 /* MockUserDefaults.swift in Sources */, 2DDF41DF24F6F527005BC22D /* MockProductsManager.swift in Sources */, 351B514F26D44ACE00BD2BD7 /* PurchasesSubscriberAttributesTests.swift in Sources */, + 57DBFA5D28AADA43002D18CA /* PurchasesLogInTests.swift in Sources */, 57D04BB827D947C6006DAC06 /* HTTPResponseTests.swift in Sources */, 5796A38127D6B78500653165 /* BaseBackendTest.swift in Sources */, 351B516226D44BEE00BD2BD7 /* CustomerInfoManagerTests.swift in Sources */, diff --git a/Sources/Networking/IdentityAPI.swift b/Sources/Networking/IdentityAPI.swift index 45eb09bd52..02f9fce23e 100644 --- a/Sources/Networking/IdentityAPI.swift +++ b/Sources/Networking/IdentityAPI.swift @@ -15,7 +15,8 @@ import Foundation class IdentityAPI { - typealias LogInResponseHandler = (Result<(info: CustomerInfo, created: Bool), BackendError>) -> Void + typealias LogInResponse = Result<(info: CustomerInfo, created: Bool), BackendError> + typealias LogInResponseHandler = (LogInResponse) -> Void private let logInCallbacksCache: CallbackCache private let backendConfig: BackendConfiguration diff --git a/Tests/UnitTests/Mocks/MockIdentityManager.swift b/Tests/UnitTests/Mocks/MockIdentityManager.swift index 1be1a3d96b..31fd016835 100644 --- a/Tests/UnitTests/Mocks/MockIdentityManager.swift +++ b/Tests/UnitTests/Mocks/MockIdentityManager.swift @@ -47,12 +47,32 @@ class MockIdentityManager: IdentityManager { return mockIsAnonymous } + // MARK: - LogIn + + var mockLogInResult: IdentityAPI.LogInResponse! + var invokedLogIn = false + var invokedLogInCount = 0 + var invokedLogInParametersList: [String] = [] + override func logIn(appUserID: String, completion: @escaping IdentityAPI.LogInResponseHandler) { - fatalError("Logging in not supported on mock") + self.invokedLogIn = true + self.invokedLogInCount += 1 + self.invokedLogInParametersList.append(appUserID) + + completion(self.mockLogInResult) } + // MARK: - LogOut + + var mockLogOutError: BackendError? + var invokedLogOut = false + var invokedLogOutCount = 0 + override func logOut(completion: @escaping (Error?) -> Void) { - fatalError("Logging out not supported on mock") + self.invokedLogOut = true + self.invokedLogOutCount += 1 + + completion(self.mockLogOutError) } } diff --git a/Tests/UnitTests/Purchasing/Purchases/PurchasesLogInTests.swift b/Tests/UnitTests/Purchasing/Purchases/PurchasesLogInTests.swift new file mode 100644 index 0000000000..24c90883d7 --- /dev/null +++ b/Tests/UnitTests/Purchasing/Purchases/PurchasesLogInTests.swift @@ -0,0 +1,143 @@ +// +// 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 +// +// PurchasesLogInTests.swift +// +// Created by Nacho Soto on 8/15/22. + +import Nimble +import StoreKit +import XCTest + +@testable import RevenueCat + +class PurchasesLogInTests: BasePurchasesTests { + + private typealias LogInResult = Result<(customerInfo: CustomerInfo, created: Bool), Error> + private typealias LogOutResult = Result + + override func setUpWithError() throws { + try super.setUpWithError() + + self.setupAnonPurchases() + } + + func testLogInWithSuccess() { + let created = Bool.random() + + self.identityManager.mockLogInResult = .success((Self.mockLoggedInInfo, created)) + + var result: LogInResult! + + self.purchases.logIn(Self.appUserID) { customerInfo, created, error in + result = Self.logInResult(customerInfo, created, error) + } + + expect(result).toEventuallyNot(beNil()) + + expect(result).to(beSuccess()) + expect(result.value) == (Self.mockLoggedInInfo, created) + expect(self.identityManager.invokedLogInCount) == 1 + expect(self.identityManager.invokedLogInParametersList) == [Self.appUserID] + } + + func testLogInWithFailure() { + let error: BackendError = .networkError(.offlineConnection()) + self.identityManager.mockLogInResult = .failure(error) + + var result: LogInResult! + + self.purchases.logIn(Self.appUserID) { customerInfo, created, error in + result = Self.logInResult(customerInfo, created, error) + } + + expect(result).toEventuallyNot(beNil()) + + expect(result).to(beFailure()) + expect(result.error).to(matchError(error)) + expect(self.identityManager.invokedLogInCount) == 1 + expect(self.identityManager.invokedLogInParametersList) == [Self.appUserID] + } + + func testLogOutWithSuccess() { + self.identityManager.mockLogOutError = nil + self.backend.overrideCustomerInfoResult = .success(Self.mockLoggedOutInfo) + + expect(self.backend.getSubscriberCallCount) == 1 + + var result: Result! + self.purchases.logOut { customerInfo, error in + result = .init(customerInfo, error) + } + + expect(result).toEventuallyNot(beNil()) + expect(result).to(beSuccess()) + expect(result.value) == Self.mockLoggedOutInfo + + expect(self.backend.getSubscriberCallCount) == 2 + expect(self.identityManager.invokedLogOutCount) == 1 + } + + func testLogOutWithFailure() { + let error: BackendError = .networkError(.offlineConnection()) + + self.identityManager.mockLogOutError = error + + var result: Result! + self.purchases.logOut { customerInfo, error in + result = .init(customerInfo, error) + } + + expect(result).toEventuallyNot(beNil()) + expect(result).to(beFailure()) + expect(result.error).to(matchError(error)) + + expect(self.backend.getSubscriberCallCount) == 1 + expect(self.identityManager.invokedLogOutCount) == 1 + } + +} + +// MARK: - + +private extension PurchasesLogInTests { + + // swiftlint:disable force_try + static let mockLoggedInInfo = try! CustomerInfo(data: PurchasesLogInTests.loggedInCustomerInfoData) + static let mockLoggedOutInfo = try! CustomerInfo(data: PurchasesLogInTests.loggedOutCustomerInfoData) + // swiftlint:enable force_try + + private static let loggedInCustomerInfoData: [String: Any] = [ + "request_date": "2019-08-16T10:30:42Z", + "subscriber": [ + "first_seen": "2019-07-17T00:05:54Z", + "original_app_user_id": "user", + "subscriptions": [:], + "other_purchases": [:], + "original_application_version": NSNull() + ] + ] + + private static let loggedOutCustomerInfoData: [String: Any] = [ + "request_date": "2019-08-16T10:30:42Z", + "subscriber": [ + "first_seen": "2019-07-17T00:05:54Z", + "original_app_user_id": "$RCAnonymousID:5b6fdbad3a0c4f879e43d269ecdf9ba1", + "subscriptions": [:], + "other_purchases": [:], + "original_application_version": NSNull() + ] + ] + + /// Converts the result of `Purchases.logIn` into `LogInResult` + private static func logInResult(_ info: CustomerInfo?, _ created: Bool, _ error: Error?) -> LogInResult { + return .init(info.map { ($0, created) }, error) + } + +}