Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: (null) aliases #255

Merged
merged 4 commits into from
May 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Purchases/Public/RCPurchasesErrorUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,14 @@ NS_SWIFT_NAME(Purchases.ErrorUtils)
*/
+ (NSError *)missingReceiptFileError;

/**
* Constructs an NSError with the [RCInvalidAppUserIdError] code.
*
* @note This error is used when the appUserID can't be found in user defaults. This can happen if user defaults
* are removed manually or if the OS deletes entries when running out of space.
*/
+ (NSError *)missingAppUserIDError;

/**
* Maps an SKErrorCode code to a RCPurchasesErrorCode code. Constructs an NSError with the mapped code and adds a
* [RCUnderlyingErrorKey] in the NSError.userInfo dictionary. The SKErrorCode code will be mapped using
Expand Down
46 changes: 18 additions & 28 deletions Purchases/Public/RCPurchasesErrorUtils.m
Original file line number Diff line number Diff line change
Expand Up @@ -201,36 +201,31 @@ case CODE_IF_TARGET_IPHONE(SKErrorStoreProductNotAvailable, 5):

@implementation RCPurchasesErrorUtils

+ (NSError *)errorWithCode:(RCPurchasesErrorCode)code
{
+ (NSError *)errorWithCode:(RCPurchasesErrorCode)code {
return [self errorWithCode:code message:nil];
}

+ (NSError *)errorWithCode:(RCPurchasesErrorCode)code
message:(nullable NSString *)message
{
message:(nullable NSString *)message {
return [self errorWithCode:code message:message underlyingError:nil];
}


+ (NSError *)errorWithCode:(RCPurchasesErrorCode)code
underlyingError:(nullable NSError *)underlyingError
{
underlyingError:(nullable NSError *)underlyingError {
return [self errorWithCode:code message:nil underlyingError:underlyingError extraUserInfo:nil];
}

+ (NSError *)errorWithCode:(RCPurchasesErrorCode)code
message:(nullable NSString *)message
underlyingError:(nullable NSError *)underlyingError
{
underlyingError:(nullable NSError *)underlyingError {
return [self errorWithCode:code message:message underlyingError:underlyingError extraUserInfo:nil];
}

+ (NSError *)errorWithCode:(RCPurchasesErrorCode)code
message:(nullable NSString *)message
underlyingError:(nullable NSError *)underlyingError
extraUserInfo:(nullable NSDictionary *)extraUserInfo
{
extraUserInfo:(nullable NSDictionary *)extraUserInfo {

NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:extraUserInfo];
userInfo[NSLocalizedDescriptionKey] = message ?: RCPurchasesErrorDescription(code);
Expand All @@ -242,21 +237,18 @@ + (NSError *)errorWithCode:(RCPurchasesErrorCode)code
}

+ (NSError *)errorWithCode:(RCPurchasesErrorCode)code
userInfo:(NSDictionary *)userInfo
{
userInfo:(NSDictionary *)userInfo {
RCErrorLog(@"%@", RCPurchasesErrorDescription(code));
return [NSError errorWithDomain:RCPurchasesErrorDomain code:code userInfo:userInfo];
}

+ (NSError *)networkErrorWithUnderlyingError:(NSError *)underlyingError
{
+ (NSError *)networkErrorWithUnderlyingError:(NSError *)underlyingError {
return [self errorWithCode:RCNetworkError
underlyingError:underlyingError];
}

+ (NSError *)backendUnderlyingError:(nullable NSNumber *)backendCode
backendMessage:(nullable NSString *)backendMessage
{
backendMessage:(nullable NSString *)backendMessage {

return [NSError errorWithDomain:RCBackendErrorDomain
code:[backendCode integerValue] ?: RCUnknownError
Expand All @@ -266,15 +258,13 @@ + (NSError *)backendUnderlyingError:(nullable NSNumber *)backendCode
}

+ (NSError *)backendErrorWithBackendCode:(nullable NSNumber *)backendCode
backendMessage:(nullable NSString *)backendMessage
{
backendMessage:(nullable NSString *)backendMessage {
return [self backendErrorWithBackendCode:backendCode backendMessage:backendMessage extraUserInfo:nil];
}

+ (NSError *)backendErrorWithBackendCode:(nullable NSNumber *)backendCode
backendMessage:(nullable NSString *)backendMessage
finishable:(BOOL)finishable
{
finishable:(BOOL)finishable {
return [self backendErrorWithBackendCode:backendCode
backendMessage:backendMessage
extraUserInfo:@{
Expand All @@ -284,8 +274,7 @@ + (NSError *)backendErrorWithBackendCode:(nullable NSNumber *)backendCode

+ (NSError *)backendErrorWithBackendCode:(nullable NSNumber *)backendCode
backendMessage:(nullable NSString *)backendMessage
extraUserInfo:(nullable NSDictionary *)extraUserInfo
{
extraUserInfo:(nullable NSDictionary *)extraUserInfo {
RCPurchasesErrorCode errorCode;
if (backendCode != nil) {
errorCode = RCPurchasesErrorCodeFromRCBackendErrorCode((RCBackendErrorCode) [backendCode integerValue]);
Expand All @@ -299,18 +288,19 @@ + (NSError *)backendErrorWithBackendCode:(nullable NSNumber *)backendCode
extraUserInfo:extraUserInfo];
}

+ (NSError *)unexpectedBackendResponseError
{
+ (NSError *)unexpectedBackendResponseError {
return [self errorWithCode:RCUnexpectedBackendResponseError];
}

+ (NSError *)missingReceiptFileError
{
+ (NSError *)missingReceiptFileError {
return [self errorWithCode:RCMissingReceiptFileError];
}

+ (NSError *)purchasesErrorWithSKError:(NSError *)skError
{
+ (NSError *)missingAppUserIDError {
return [self errorWithCode:RCInvalidAppUserIdError];
}

+ (NSError *)purchasesErrorWithSKError:(NSError *)skError {

RCPurchasesErrorCode errorCode = RCPurchasesErrorCodeFromSKError(skError);
return [self errorWithCode:errorCode
Expand Down
2 changes: 1 addition & 1 deletion Purchases/Public/RCPurchasesErrors.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ typedef NS_ERROR_ENUM(RCPurchasesErrorDomain, RCPurchasesErrorCode) {
RCIneligibleError,
RCInsufficientPermissionsError,
RCPaymentPendingError,
RCInvalidSubscriberAttributesError
RCInvalidSubscriberAttributesError,
} NS_SWIFT_NAME(Purchases.ErrorCode);

/**
Expand Down
17 changes: 13 additions & 4 deletions Purchases/Purchasing/RCIdentityManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
#import "RCIdentityManager.h"
#import "RCLogUtils.h"
#import "RCBackend.h"
#import "RCPurchasesErrorUtils.h"


@interface RCIdentityManager ()

Expand Down Expand Up @@ -64,11 +66,18 @@ - (void)saveAppUserID:(NSString *)appUserID {
}

- (void)createAlias:(NSString *)alias withCompletionBlock:(void (^)(NSError *_Nullable error))completion {
RCDebugLog(@"Creating an alias to %@ from %@", self.currentAppUserID, alias);
[self.backend createAliasForAppUserID:self.currentAppUserID withNewAppUserID:alias completion:^(NSError *_Nullable error) {
NSString *currentAppUserID = self.currentAppUserID;
if (!currentAppUserID) {
RCDebugLog(@"Couldn't create an alias because the currentAppUserID is null. "
"This might happen if the entry in UserDefaults is missing.");
completion(RCPurchasesErrorUtils.missingAppUserIDError);
return;
}
RCDebugLog(@"Creating an alias to %@ from %@", currentAppUserID, alias);
[self.backend createAliasForAppUserID:currentAppUserID withNewAppUserID:alias completion:^(NSError *_Nullable error) {
if (error == nil) {
RCDebugLog(@"Alias created");
[self.deviceCache clearCachesForAppUserID:self.currentAppUserID andSaveNewUserID:alias];
[self.deviceCache clearCachesForAppUserID:currentAppUserID andSaveNewUserID:alias];
}
completion(error);
}];
Expand All @@ -89,4 +98,4 @@ - (BOOL)currentUserIsAnonymous {
return currentAppUserIDLooksAnonymous || isLegacyAnonymousAppUserID;
}

@end
@end
35 changes: 35 additions & 0 deletions PurchasesTests/IdentityManagerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,43 @@ class IdentityManagerTests: XCTestCase {

func testCreateAliasCallsBackend() {
self.mockBackend.aliasCalled = false
self.mockDeviceCache.stubbedAppUserID = "appUserID"

self.identityManager.createAlias("cesar") { (error: Error?) in
}

expect(self.mockBackend.aliasCalled).toEventually(beTrue())
}

func testCreateAliasNoOpsIfNilAppUserID() {
self.mockBackend.aliasCalled = false
self.mockDeviceCache.stubbedAppUserID = nil
self.identityManager.createAlias("cesar") { (error: Error?) in
}

expect(self.mockBackend.aliasCalled).toEventually(beFalse())
}

func testCreateAliasCallsCompletionWithErrorIfNilAppUserID() {
self.mockBackend.aliasCalled = false
self.mockDeviceCache.stubbedAppUserID = nil
var completionCalled = false
var receivedNSError: NSError?
self.identityManager.createAlias("cesar") { (error: Error?) in
completionCalled = true

guard let receivedError = error else { fatalError() }
receivedNSError = receivedError as NSError
expect(receivedNSError!.code) == Purchases.ErrorCode.invalidAppUserIdError.rawValue
}

expect(completionCalled).toEventually(beTrue())
expect(receivedNSError).toNotEventually(beNil())
}

func testCreateAliasIdentifiesWhenSuccessful() {
self.mockDeviceCache.cacheAppUserID("appUserID")

self.identityManager.createAlias("cesar") { (error: Error?) in
}
assertCorrectlyIdentified(expectedAppUserID: "cesar")
Expand All @@ -128,6 +158,8 @@ class IdentityManagerTests: XCTestCase {
func testCreateAliasForwardsErrors() {
self.mockBackend.aliasError = Purchases.ErrorUtils.backendError(withBackendCode: Purchases.RevenueCatBackendErrorCode.invalidAPIKey.rawValue as NSNumber, backendMessage: "Invalid credentials", finishable: false)
var error: Error? = nil
self.mockDeviceCache.stubbedAppUserID = "appUserID"

self.identityManager.createAlias("cesar") { (newError: Error?) in
error = newError
}
Expand All @@ -150,12 +182,15 @@ class IdentityManagerTests: XCTestCase {
func testIdentifyingWhenUserIsAnonymousCreatesAlias() {
self.identityManager.configure(withAppUserID: nil)
self.mockBackend.aliasError = nil
self.mockDeviceCache.cacheAppUserID("$RCAnonymousID:5d73fc46744f4e0b99e524c6763dd7fc")

self.identityManager.identifyAppUserID("cesar") { (error: Error?) in }
expect(self.mockBackend.aliasCalled).toEventually(beTrue())
}

func testMigrationFromRandomIDConfiguringAnonymously() {
self.mockDeviceCache.stubbedLegacyAppUserID = "an_old_random"

self.identityManager.configure(withAppUserID: nil)
assertCorrectlyIdentifiedWithAnonymous(usingOldID: true)
expect(self.identityManager.currentAppUserID).to(equal("an_old_random"))
Expand Down