diff --git a/FirebaseInstallations/Source/Library/Errors/FIRInstallationsHTTPError.h b/FirebaseInstallations/Source/Library/Errors/FIRInstallationsHTTPError.h index a70ae777de8..f292864dde1 100644 --- a/FirebaseInstallations/Source/Library/Errors/FIRInstallationsHTTPError.h +++ b/FirebaseInstallations/Source/Library/Errors/FIRInstallationsHTTPError.h @@ -31,3 +31,14 @@ NS_ASSUME_NONNULL_BEGIN @end NS_ASSUME_NONNULL_END + +/** Possible response HTTP codes for `CreateInstallation` API request. */ +typedef NS_ENUM(NSInteger, FIRInstallationsRegistrationHTTPCode) { + FIRInstallationsRegistrationHTTPCodeSuccess = 201, + FIRInstallationsRegistrationHTTPCodeInvalidArgument = 400, + FIRInstallationsRegistrationHTTPCodeInvalidAPIKey = 401, + FIRInstallationsRegistrationHTTPCodeAPIKeyToProjectIDMismatch = 403, + FIRInstallationsRegistrationHTTPCodeProjectNotFound = 404, + FIRInstallationsRegistrationHTTPCodeTooManyRequests = 429, + FIRInstallationsRegistrationHTTPCodeServerInternalError = 500 +}; diff --git a/FirebaseInstallations/Source/Library/FIRInstallationsItem.h b/FirebaseInstallations/Source/Library/FIRInstallationsItem.h index 932556ef87e..55bee483bed 100644 --- a/FirebaseInstallations/Source/Library/FIRInstallationsItem.h +++ b/FirebaseInstallations/Source/Library/FIRInstallationsItem.h @@ -20,6 +20,8 @@ @class FIRInstallationsStoredItem; @class FIRInstallationsStoredAuthToken; +@class FIRInstallationsStoredRegistrationError; +@class FIRInstallationsStoredRegistrationParameters; NS_ASSUME_NONNULL_BEGIN @@ -43,6 +45,8 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic, nullable) FIRInstallationsStoredAuthToken *authToken; @property(nonatomic, assign) FIRInstallationsStatus registrationStatus; +@property(nonatomic, nullable) FIRInstallationsStoredRegistrationError *registrationError; + - (instancetype)initWithAppID:(NSString *)appID firebaseAppName:(NSString *)firebaseAppName; /** @@ -77,6 +81,15 @@ NS_ASSUME_NONNULL_BEGIN */ + (NSString *)generateFID; +/** + * Updates `registrationStatus` and `registrationError` accordingly. + * @param error The error for the Installation Registration API request. + * @param registrationParameters The parameters used for the Installation Registration API request. + */ +- (void)updateWithRegistrationError:(NSError *)error + registrationParameters: + (FIRInstallationsStoredRegistrationParameters *)registrationParameters; + @end NS_ASSUME_NONNULL_END diff --git a/FirebaseInstallations/Source/Library/FIRInstallationsItem.m b/FirebaseInstallations/Source/Library/FIRInstallationsItem.m index 59bfc7e6b0d..64e5096d8f7 100644 --- a/FirebaseInstallations/Source/Library/FIRInstallationsItem.m +++ b/FirebaseInstallations/Source/Library/FIRInstallationsItem.m @@ -18,6 +18,7 @@ #import "FIRInstallationsStoredAuthToken.h" #import "FIRInstallationsStoredItem.h" +#import "FIRInstallationsStoredRegistrationError.h" @implementation FIRInstallationsItem @@ -46,6 +47,7 @@ - (void)updateWithStoredItem:(FIRInstallationsStoredItem *)item { self.refreshToken = item.refreshToken; self.authToken = item.authToken; self.registrationStatus = item.registrationStatus; + self.registrationError = item.registrationError; } - (FIRInstallationsStoredItem *)storedItem { @@ -54,6 +56,7 @@ - (FIRInstallationsStoredItem *)storedItem { storedItem.refreshToken = self.refreshToken; storedItem.authToken = self.authToken; storedItem.registrationStatus = self.registrationStatus; + storedItem.registrationError = self.registrationError; return storedItem; } @@ -99,4 +102,15 @@ + (NSString *)base64URLEncodedStringWithData:(NSData *)data { return string; } +- (void)updateWithRegistrationError:(NSError *)error + registrationParameters: + (FIRInstallationsStoredRegistrationParameters *)registrationParameters { + self.registrationStatus = FIRInstallationStatusRegistrationFailed; + self.registrationError = [[FIRInstallationsStoredRegistrationError alloc] + initWithRegistrationParameters:registrationParameters + APIError:error]; + self.authToken = nil; + self.refreshToken = nil; +} + @end diff --git a/FirebaseInstallations/Source/Library/FIRInstallationsLogger.h b/FirebaseInstallations/Source/Library/FIRInstallationsLogger.h index 44c73cc3af7..84ae557d818 100644 --- a/FirebaseInstallations/Source/Library/FIRInstallationsLogger.h +++ b/FirebaseInstallations/Source/Library/FIRInstallationsLogger.h @@ -35,9 +35,17 @@ extern NSString *const kFIRInstallationsMessageCodeAPIResponseParsingAuthTokenSu extern NSString *const kFIRInstallationsMessageCodeNewGetInstallationOperationCreated; extern NSString *const kFIRInstallationsMessageCodeNewGetAuthTokenOperationCreated; extern NSString *const kFIRInstallationsMessageCodeNewDeleteInstallationOperationCreated; +extern NSString *const kFIRInstallationsMessageCodeInvalidFirebaseConfiguration; // FIRInstallationsStoredItem.m extern NSString *const kFIRInstallationsMessageCodeInstallationCoderVersionMismatch; // FIRInstallationsStoredAuthToken.m extern NSString *const kFIRInstallationsMessageCodeAuthTokenCoderVersionMismatch; + +// FIRInstallationsStoredRegistrationError.m +extern NSString *const kFIRInstallationsMessageCodeRegistrationErrorCoderVersionMismatch; +extern NSString *const kFIRInstallationsMessageCodeRegistrationErrorFailedToDecode; + +// FIRInstallationsStoredRegistrationParameters.m +extern NSString *const kFIRInstallationsMessageCodeRegistrationParametersCoderVersionMismatch; diff --git a/FirebaseInstallations/Source/Library/FIRInstallationsLogger.m b/FirebaseInstallations/Source/Library/FIRInstallationsLogger.m index 1199b65acc4..031d89a306e 100644 --- a/FirebaseInstallations/Source/Library/FIRInstallationsLogger.m +++ b/FirebaseInstallations/Source/Library/FIRInstallationsLogger.m @@ -33,9 +33,18 @@ NSString *const kFIRInstallationsMessageCodeNewGetInstallationOperationCreated = @"I-FIS002000"; NSString *const kFIRInstallationsMessageCodeNewGetAuthTokenOperationCreated = @"I-FIS002001"; NSString *const kFIRInstallationsMessageCodeNewDeleteInstallationOperationCreated = @"I-FIS002002"; +NSString *const kFIRInstallationsMessageCodeInvalidFirebaseConfiguration = @"I-FIS002003"; // FIRInstallationsStoredItem.m NSString *const kFIRInstallationsMessageCodeInstallationCoderVersionMismatch = @"I-FIS003000"; // FIRInstallationsStoredAuthToken.m NSString *const kFIRInstallationsMessageCodeAuthTokenCoderVersionMismatch = @"I-FIS004000"; + +// FIRInstallationsStoredRegistrationError.m +NSString *const kFIRInstallationsMessageCodeRegistrationErrorCoderVersionMismatch = @"I-FIS005000"; +NSString *const kFIRInstallationsMessageCodeRegistrationErrorFailedToDecode = @"I-FIS005001"; + +// FIRInstallationsStoredRegistrationParameters.m +NSString *const kFIRInstallationsMessageCodeRegistrationParametersCoderVersionMismatch = + @"I-FIS006000"; diff --git a/FirebaseInstallations/Source/Library/InstallationsAPI/FIRInstallationsAPIService.h b/FirebaseInstallations/Source/Library/InstallationsAPI/FIRInstallationsAPIService.h index f0988634fa0..a510e2d9378 100644 --- a/FirebaseInstallations/Source/Library/InstallationsAPI/FIRInstallationsAPIService.h +++ b/FirebaseInstallations/Source/Library/InstallationsAPI/FIRInstallationsAPIService.h @@ -26,6 +26,9 @@ NS_ASSUME_NONNULL_BEGIN */ @interface FIRInstallationsAPIService : NSObject +@property(nonatomic, readonly) NSString *APIKey; +@property(nonatomic, readonly) NSString *projectID; + /** * The default initializer. * @param APIKey The Firebase project API key (see `FIROptions.APIKey`). diff --git a/FirebaseInstallations/Source/Library/InstallationsAPI/FIRInstallationsAPIService.m b/FirebaseInstallations/Source/Library/InstallationsAPI/FIRInstallationsAPIService.m index 5a0a2d88cf6..e8fff9101ec 100644 --- a/FirebaseInstallations/Source/Library/InstallationsAPI/FIRInstallationsAPIService.m +++ b/FirebaseInstallations/Source/Library/InstallationsAPI/FIRInstallationsAPIService.m @@ -55,8 +55,6 @@ - (instancetype)initWithResponse:(NSHTTPURLResponse *)response data:(nullable NS @interface FIRInstallationsAPIService () @property(nonatomic, readonly) NSURLSession *URLSession; -@property(nonatomic, readonly) NSString *APIKey; -@property(nonatomic, readonly) NSString *projectID; @end NS_ASSUME_NONNULL_END diff --git a/FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsIDController.m b/FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsIDController.m index 2ff44435b61..63ad51bc0b9 100644 --- a/FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsIDController.m +++ b/FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsIDController.m @@ -31,9 +31,13 @@ #import "FIRInstallationsLogger.h" #import "FIRInstallationsSingleOperationPromiseCache.h" #import "FIRInstallationsStore.h" -#import "FIRInstallationsStoredAuthToken.h" #import "FIRSecureStorage.h" +#import "FIRInstallationsHTTPError.h" +#import "FIRInstallationsStoredAuthToken.h" +#import "FIRInstallationsStoredRegistrationError.h" +#import "FIRInstallationsStoredRegistrationParameters.h" + const NSNotificationName FIRInstallationIDDidChangeNotification = @"FIRInstallationIDDidChangeNotification"; NSString *const kFIRInstallationIDDidChangeNotificationAppNameKey = @@ -135,9 +139,21 @@ - (instancetype)initWithGoogleAppID:(NSString *)appID __PRETTY_FUNCTION__, self.appName); FBLPromise *installationItemPromise = - [self getStoredInstallation].recover(^id(NSError *error) { - return [self createAndSaveFID]; - }); + [self getStoredInstallation] + .recover(^id(NSError *error) { + return [self createAndSaveFID]; + }) + .then(^id(FIRInstallationsItem *installation) { + // Validate if a previous registration attempt failed with an error requiring Firebase + // configuration changes. + if (installation.registrationStatus == FIRInstallationStatusRegistrationFailed && + [self areInstallationRegistrationParametersEqualToCurrent: + installation.registrationError.registrationParameters]) { + return installation.registrationError.APIError; + } + + return installation; + }); // Initiate registration process on success if needed, but return the installation without waiting // for it. @@ -156,6 +172,7 @@ - (instancetype)initWithGoogleAppID:(NSString *)appID switch (installation.registrationStatus) { case FIRInstallationStatusUnregistered: case FIRInstallationStatusRegistered: + case FIRInstallationStatusRegistrationFailed: isValid = YES; break; @@ -201,6 +218,14 @@ - (instancetype)initWithGoogleAppID:(NSString *)appID }); } +- (BOOL)areInstallationRegistrationParametersEqualToCurrent: + (FIRInstallationsStoredRegistrationParameters *)parameters { + NSString *APIKey = self.APIService.APIKey; + NSString *projectID = self.APIService.projectID; + return (parameters.APIKey == APIKey || [parameters.APIKey isEqual:APIKey]) && + (parameters.projectID == projectID || [parameters.projectID isEqual:projectID]); +} + #pragma mark - FID registration - (FBLPromise *)registerInstallationIfNeeded: @@ -212,11 +237,15 @@ - (instancetype)initWithGoogleAppID:(NSString *)appID case FIRInstallationStatusUnknown: case FIRInstallationStatusUnregistered: + case FIRInstallationStatusRegistrationFailed: // Registration required. Proceed. break; } return [self.APIService registerInstallation:installation] + .recover(^id(NSError *error) { + return [self handleRegistrationRequestError:error installation:installation]; + }) .then(^id(FIRInstallationsItem *registeredInstallation) { // Expected successful result: @[FIRInstallationsItem *registeredInstallation, NSNull] return [FBLPromise all:@[ @@ -234,6 +263,56 @@ - (instancetype)initWithGoogleAppID:(NSString *)appID }); } +- (FBLPromise *)handleRegistrationRequestError:(NSError *)error + installation: + (FIRInstallationsItem *)installation { + if ([self doesRegistrationErrorRequireConfigChange:error]) { + FIRLogError(kFIRLoggerInstallations, kFIRInstallationsMessageCodeInvalidFirebaseConfiguration, + @"Firebase Installation registration failed for app with name: %@, error: " + @"%@\nPlease make sure you use valid GoogleService-Info.plist", + self.appName, error); + + FIRInstallationsItem *failedInstallation = [installation copy]; + [failedInstallation updateWithRegistrationError:error + registrationParameters:[self currentRegistrationParameters]]; + + // Save the error and then fail with the API error. + return + [self.installationsStore saveInstallation:failedInstallation].then(^NSError *(id result) { + return error; + }); + } + + FBLPromise *errorPromise = [FBLPromise pendingPromise]; + [errorPromise reject:error]; + return errorPromise; +} + +- (BOOL)doesRegistrationErrorRequireConfigChange:(NSError *)error { + FIRInstallationsHTTPError *HTTPError = (FIRInstallationsHTTPError *)error; + if (![HTTPError isKindOfClass:[FIRInstallationsHTTPError class]]) { + return NO; + } + + switch (HTTPError.HTTPResponse.statusCode) { + // These are the errors that require Firebase configuration change. + case FIRInstallationsRegistrationHTTPCodeInvalidArgument: + case FIRInstallationsRegistrationHTTPCodeInvalidAPIKey: + case FIRInstallationsRegistrationHTTPCodeAPIKeyToProjectIDMismatch: + case FIRInstallationsRegistrationHTTPCodeProjectNotFound: + return YES; + + default: + return NO; + } +} + +- (FIRInstallationsStoredRegistrationParameters *)currentRegistrationParameters { + return [[FIRInstallationsStoredRegistrationParameters alloc] + initWithAPIKey:self.APIService.APIKey + projectID:self.APIService.projectID]; +} + #pragma mark - Auth Token - (FBLPromise *)getAuthTokenForcingRefresh:(BOOL)forceRefresh { @@ -321,6 +400,7 @@ - (instancetype)initWithGoogleAppID:(NSString *)appID switch (installation.registrationStatus) { case FIRInstallationStatusUnknown: case FIRInstallationStatusUnregistered: + case FIRInstallationStatusRegistrationFailed: // The installation is not registered, so it is safe to be deleted as is, so return early. return [FBLPromise resolvedWith:installation]; break; diff --git a/FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsStatus.h b/FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsStatus.h index 3edc6920114..4fc747de5ca 100644 --- a/FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsStatus.h +++ b/FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsStatus.h @@ -32,4 +32,7 @@ typedef NS_ENUM(NSInteger, FIRInstallationsStatus) { FIRInstallationStatusUnregistered, /// The Firebase Installation has successfully been registered with FIS. FIRInstallationStatusRegistered, + /// The Firebase Installation registration with FIS failed. Firebase configuration changes are + /// required. + FIRInstallationStatusRegistrationFailed, }; diff --git a/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredAuthToken.h b/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredAuthToken.h index d55cc3f51ad..f6e4282882f 100644 --- a/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredAuthToken.h +++ b/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredAuthToken.h @@ -51,7 +51,7 @@ typedef NS_ENUM(NSInteger, FIRInstallationsAuthTokenStatus) { @property(nullable, copy) NSDate *expirationDate; /// The version of local storage. -@property(nonatomic) NSInteger storageVersion; +@property(nonatomic, readonly) NSInteger storageVersion; @end diff --git a/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredAuthToken.m b/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredAuthToken.m index 7a9f0162a50..b21f6dd235b 100644 --- a/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredAuthToken.m +++ b/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredAuthToken.m @@ -27,12 +27,15 @@ @implementation FIRInstallationsStoredAuthToken +- (NSInteger)storageVersion { + return kFIRInstallationsStoredAuthTokenStorageVersion; +} + - (nonnull id)copyWithZone:(nullable NSZone *)zone { FIRInstallationsStoredAuthToken *clone = [[FIRInstallationsStoredAuthToken alloc] init]; clone.status = self.status; clone.token = [self.token copy]; clone.expirationDate = self.expirationDate; - clone.storageVersion = self.storageVersion; return clone; } @@ -41,7 +44,7 @@ - (void)encodeWithCoder:(nonnull NSCoder *)aCoder { [aCoder encodeObject:self.token forKey:kFIRInstallationsStoredAuthTokenTokenKey]; [aCoder encodeObject:self.expirationDate forKey:kFIRInstallationsStoredAuthTokenExpirationDateKey]; - [aCoder encodeInteger:kFIRInstallationsStoredAuthTokenStorageVersion + [aCoder encodeInteger:self.storageVersion forKey:kFIRInstallationsStoredAuthTokenStorageVersionKey]; } diff --git a/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredItem.h b/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredItem.h index 29ccdca6969..b4fe146b337 100644 --- a/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredItem.h +++ b/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredItem.h @@ -19,6 +19,7 @@ #import "FIRInstallationsStatus.h" @class FIRInstallationsStoredAuthToken; +@class FIRInstallationsStoredRegistrationError; NS_ASSUME_NONNULL_BEGIN @@ -40,8 +41,10 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic, nullable) FIRInstallationsStoredAuthToken *authToken; @property(nonatomic) FIRInstallationsStatus registrationStatus; +@property(nonatomic, nullable) FIRInstallationsStoredRegistrationError *registrationError; + /// The version of local storage. -@property(nonatomic) NSInteger storageVersion; +@property(nonatomic, readonly) NSInteger storageVersion; @end NS_ASSUME_NONNULL_END diff --git a/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredItem.m b/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredItem.m index bbadf342cd2..fefec145809 100644 --- a/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredItem.m +++ b/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredItem.m @@ -18,17 +18,23 @@ #import "FIRInstallationsLogger.h" #import "FIRInstallationsStoredAuthToken.h" +#import "FIRInstallationsStoredRegistrationError.h" NSString *const kFIRInstallationsStoredItemFirebaseInstallationIDKey = @"firebaseInstallationID"; NSString *const kFIRInstallationsStoredItemRefreshTokenKey = @"refreshToken"; NSString *const kFIRInstallationsStoredItemAuthTokenKey = @"authToken"; NSString *const kFIRInstallationsStoredItemRegistrationStatusKey = @"registrationStatus"; +NSString *const kFIRInstallationsStoredItemRegistrationErrorKey = @"registrationError"; NSString *const kFIRInstallationsStoredItemStorageVersionKey = @"storageVersion"; NSInteger const kFIRInstallationsStoredItemStorageVersion = 1; @implementation FIRInstallationsStoredItem +- (NSInteger)storageVersion { + return kFIRInstallationsStoredItemStorageVersion; +} + - (void)encodeWithCoder:(nonnull NSCoder *)aCoder { [aCoder encodeObject:self.firebaseInstallationID forKey:kFIRInstallationsStoredItemFirebaseInstallationIDKey]; @@ -36,13 +42,15 @@ - (void)encodeWithCoder:(nonnull NSCoder *)aCoder { [aCoder encodeObject:self.authToken forKey:kFIRInstallationsStoredItemAuthTokenKey]; [aCoder encodeInteger:self.registrationStatus forKey:kFIRInstallationsStoredItemRegistrationStatusKey]; + [aCoder encodeObject:self.registrationError + forKey:kFIRInstallationsStoredItemRegistrationErrorKey]; [aCoder encodeInteger:self.storageVersion forKey:kFIRInstallationsStoredItemStorageVersionKey]; } - (nullable instancetype)initWithCoder:(nonnull NSCoder *)aDecoder { NSInteger storageVersion = [aDecoder decodeIntegerForKey:kFIRInstallationsStoredItemStorageVersionKey]; - if (storageVersion > kFIRInstallationsStoredItemStorageVersion) { + if (storageVersion > self.storageVersion) { FIRLogWarning(kFIRLoggerInstallations, kFIRInstallationsMessageCodeInstallationCoderVersionMismatch, @"FIRInstallationsStoredItem was encoded by a newer coder version %ld. Current " @@ -60,7 +68,9 @@ - (nullable instancetype)initWithCoder:(nonnull NSCoder *)aDecoder { forKey:kFIRInstallationsStoredItemAuthTokenKey]; item.registrationStatus = [aDecoder decodeIntegerForKey:kFIRInstallationsStoredItemRegistrationStatusKey]; - item.storageVersion = [aDecoder decodeIntegerForKey:kFIRInstallationsStoredItemStorageVersionKey]; + item.registrationError = + [aDecoder decodeObjectOfClass:[FIRInstallationsStoredRegistrationError class] + forKey:kFIRInstallationsStoredItemRegistrationErrorKey]; return item; } diff --git a/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredRegistrationError.h b/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredRegistrationError.h new file mode 100644 index 00000000000..55268e3a807 --- /dev/null +++ b/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredRegistrationError.h @@ -0,0 +1,47 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FIRInstallationsStoredRegistrationParameters; + +NS_ASSUME_NONNULL_BEGIN + +/** + * This class serializes and deserializes the installation data into/from `NSData` to be stored in + * Keychain. This class is primarily used by `FIRInstallationsStore`. It is also used on the logic + * level as a data object (see `FIRInstallationsItem.registrationError`). + * + * WARNING: Modification of the class properties can lead to incompatibility with the stored data + * encoded by the previous class versions. Any modification must be evaluated and, if it is really + * needed, the `storageVersion` must be bumped and proper migration code added. + */ + +@interface FIRInstallationsStoredRegistrationError : NSObject + +@property(nonatomic, readonly) FIRInstallationsStoredRegistrationParameters *registrationParameters; +@property(nonatomic, readonly) NSError *APIError; + +/// The version of local storage. +@property(nonatomic, readonly) NSInteger storageVersion; + +- (instancetype)initWithRegistrationParameters: + (FIRInstallationsStoredRegistrationParameters *)registrationParameters + APIError:(NSError *)error; + +@end + +NS_ASSUME_NONNULL_END diff --git a/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredRegistrationError.m b/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredRegistrationError.m new file mode 100644 index 00000000000..76d9e684aae --- /dev/null +++ b/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredRegistrationError.m @@ -0,0 +1,93 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstallationsStoredRegistrationError.h" + +#import "FIRInstallationsHTTPError.h" +#import "FIRInstallationsStoredRegistrationParameters.h" + +#import "FIRInstallationsLogger.h" + +NSString *const kFIRInstallationsStoredRegistrationErrorRegistrationParametersKey = + @"registrationParameters"; +NSString *const kFIRInstallationsStoredRegistrationErrorAPIErrorKey = @"APIError"; +NSString *const kFIRInstallationsStoredRegistrationErrorStorageVersionKey = @"storageVersion"; + +NSInteger const kFIRInstallationsStoredRegistrationErrorStorageVersion = 1; + +@implementation FIRInstallationsStoredRegistrationError + +- (instancetype)initWithRegistrationParameters: + (FIRInstallationsStoredRegistrationParameters *)registrationParameters + APIError:(NSError *)error { + self = [super init]; + if (self) { + _registrationParameters = registrationParameters; + _APIError = error; + } + return self; +} + +- (NSInteger)storageVersion { + return kFIRInstallationsStoredRegistrationErrorStorageVersion; +} + +#pragma mark - NSSecureCoding + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (void)encodeWithCoder:(nonnull NSCoder *)coder { + [coder encodeObject:self.registrationParameters + forKey:kFIRInstallationsStoredRegistrationErrorRegistrationParametersKey]; + [coder encodeObject:self.APIError forKey:kFIRInstallationsStoredRegistrationErrorAPIErrorKey]; + [coder encodeInteger:kFIRInstallationsStoredRegistrationErrorStorageVersion + forKey:kFIRInstallationsStoredRegistrationErrorStorageVersionKey]; +} + +- (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder { + NSInteger storageVersion = + [coder decodeIntegerForKey:kFIRInstallationsStoredRegistrationErrorStorageVersionKey]; + if (storageVersion > self.storageVersion) { + FIRLogWarning(kFIRLoggerInstallations, + kFIRInstallationsMessageCodeRegistrationErrorCoderVersionMismatch, + @"FIRInstallationsStoredRegistrationError was encoded by a newer coder version " + @"%ld. Current coder version is %ld. Some installation data may be lost.", + (long)storageVersion, (long)self.storageVersion); + } + + FIRInstallationsStoredRegistrationParameters *registrationParameters = + [coder decodeObjectOfClass:[FIRInstallationsStoredRegistrationParameters class] + forKey:kFIRInstallationsStoredRegistrationErrorRegistrationParametersKey]; + + NSSet *allowedErrorClasses = + [NSSet setWithArray:@ [[FIRInstallationsHTTPError class], [NSError class]]]; + NSError *APIError = + [coder decodeObjectOfClasses:allowedErrorClasses + forKey:kFIRInstallationsStoredRegistrationErrorAPIErrorKey]; + + if (registrationParameters == nil || APIError == nil) { + FIRLogWarning(kFIRLoggerInstallations, + kFIRInstallationsMessageCodeRegistrationErrorFailedToDecode, + @"Failed to decode FIRInstallationsStoredRegistrationError."); + return nil; + } + + return [self initWithRegistrationParameters:registrationParameters APIError:APIError]; +} + +@end diff --git a/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredRegistrationParameters.h b/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredRegistrationParameters.h new file mode 100644 index 00000000000..9b787d6d5c9 --- /dev/null +++ b/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredRegistrationParameters.h @@ -0,0 +1,43 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * This class serializes and deserializes the installation data into/from `NSData` to be stored in + * Keychain. This class is primarily used by `FIRInstallationsStore`. It is also used on the logic + * level as a data object (see `FIRInstallationsStoredRegistrationError`). + * + * WARNING: Modification of the class properties can lead to incompatibility with the stored data + * encoded by the previous class versions. Any modification must be evaluated and, if it is really + * needed, the `storageVersion` must be bumped and proper migration code added. + */ + +@interface FIRInstallationsStoredRegistrationParameters : NSObject + +@property(nonatomic, readonly, nullable) NSString *APIKey; +@property(nonatomic, readonly, nullable) NSString *projectID; + +/// The version of local storage. +@property(nonatomic, readonly) NSInteger storageVersion; + +- (instancetype)initWithAPIKey:(nullable NSString *)APIKey projectID:(nullable NSString *)projectID; + +@end + +NS_ASSUME_NONNULL_END diff --git a/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredRegistrationParameters.m b/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredRegistrationParameters.m new file mode 100644 index 00000000000..c091db0527c --- /dev/null +++ b/FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredRegistrationParameters.m @@ -0,0 +1,74 @@ +/* + * Copyright 2019 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRInstallationsStoredRegistrationParameters.h" + +#import "FIRInstallationsLogger.h" + +NSString *const kFIRInstallationsStoredRegistrationParametersAPIKeyKey = @"APIKey"; +NSString *const kFIRInstallationsStoredRegistrationParametersProjectID = @"projectID"; +NSString *const FIRInstallationsStoredRegistrationParametersStorageVersionKey = @"storageVersion"; + +NSInteger const FIRInstallationsStoredRegistrationParametersStorageVersion = 1; + +@implementation FIRInstallationsStoredRegistrationParameters + +- (instancetype)initWithAPIKey:(NSString *)APIKey projectID:(NSString *)projectID { + self = [super init]; + if (self) { + _APIKey = APIKey; + _projectID = projectID; + } + return self; +} + +- (NSInteger)storageVersion { + return FIRInstallationsStoredRegistrationParametersStorageVersion; +} + +#pragma mark - NSSecureCoding + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (void)encodeWithCoder:(nonnull NSCoder *)coder { + [coder encodeObject:self.APIKey forKey:kFIRInstallationsStoredRegistrationParametersAPIKeyKey]; + [coder encodeObject:self.projectID forKey:kFIRInstallationsStoredRegistrationParametersProjectID]; + [coder encodeInteger:self.storageVersion + forKey:FIRInstallationsStoredRegistrationParametersStorageVersionKey]; +} + +- (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder { + NSInteger storageVersion = + [coder decodeIntegerForKey:FIRInstallationsStoredRegistrationParametersStorageVersionKey]; + if (storageVersion > self.storageVersion) { + FIRLogWarning(kFIRLoggerInstallations, + kFIRInstallationsMessageCodeRegistrationParametersCoderVersionMismatch, + @"FIRInstallationsStoredRegistrationParameters was encoded by a newer coder " + @"version %ld. Current coder version is %ld. Some installation data may be lost.", + (long)storageVersion, (long)self.storageVersion); + } + + NSString *APIKey = + [coder decodeObjectForKey:kFIRInstallationsStoredRegistrationParametersAPIKeyKey]; + NSString *projectID = + [coder decodeObjectForKey:kFIRInstallationsStoredRegistrationParametersProjectID]; + + return [self initWithAPIKey:APIKey projectID:projectID]; +} + +@end diff --git a/FirebaseInstallations/Source/Library/Public/FIRInstallations.h b/FirebaseInstallations/Source/Library/Public/FIRInstallations.h index cc88b6f29f0..1bf7a219e4e 100644 --- a/FirebaseInstallations/Source/Library/Public/FIRInstallations.h +++ b/FirebaseInstallations/Source/Library/Public/FIRInstallations.h @@ -16,9 +16,8 @@ #import -#import - @class FIRApp; +@class FIRInstallationsAuthTokenResult; NS_ASSUME_NONNULL_BEGIN @@ -56,12 +55,14 @@ typedef void (^FIRInstallationsTokenHandler)( NS_SWIFT_NAME(Installations) @interface FIRInstallations : NSObject +- (instancetype)init NS_UNAVAILABLE; + /** * Returns a default instance of `Installations`. * @return Returns an instance of `Installations` for `FirebaseApp.defaultApp(). Throws an exception * if the default app is not configured yet. */ -+ (FIRInstallations *)installations; ++ (FIRInstallations *)installations NS_SWIFT_NAME(installations()); /** * Returns an instance of `Installations` for an application. diff --git a/FirebaseInstallations/Source/Library/Public/FIRInstallationsAuthTokenResult.h b/FirebaseInstallations/Source/Library/Public/FIRInstallationsAuthTokenResult.h index dece5662b59..7753132d9b0 100644 --- a/FirebaseInstallations/Source/Library/Public/FIRInstallationsAuthTokenResult.h +++ b/FirebaseInstallations/Source/Library/Public/FIRInstallationsAuthTokenResult.h @@ -25,7 +25,7 @@ NS_SWIFT_NAME(InstallationsAuthTokenResult) /** The authorization token string. */ @property(nonatomic, readonly) NSString *authToken; -/** The auth token experation date. */ +/** The auth token expiration date. */ @property(nonatomic, readonly) NSDate *expirationDate; @end diff --git a/FirebaseInstallations/Source/Library/Public/FirebaseInstallations.h b/FirebaseInstallations/Source/Library/Public/FirebaseInstallations.h index 474ed6ab715..accc9ac6b22 100644 --- a/FirebaseInstallations/Source/Library/Public/FirebaseInstallations.h +++ b/FirebaseInstallations/Source/Library/Public/FirebaseInstallations.h @@ -15,3 +15,6 @@ */ #import "FIRInstallations.h" +#import "FIRInstallationsAuthTokenResult.h" +#import "FIRInstallationsErrors.h" +#import "FIRInstallationsVersion.h" diff --git a/FirebaseInstallations/Source/Tests/Integration/FIRInstallationsIntegrationTests.m b/FirebaseInstallations/Source/Tests/Integration/FIRInstallationsIntegrationTests.m index 21ed7e746aa..fa533453d86 100644 --- a/FirebaseInstallations/Source/Tests/Integration/FIRInstallationsIntegrationTests.m +++ b/FirebaseInstallations/Source/Tests/Integration/FIRInstallationsIntegrationTests.m @@ -190,6 +190,34 @@ - (FIRInstallationsAuthTokenResult *)getAuthToken { return retrievedTokenResult; } +- (void)testGetInstallationIDWithInvalidFirebaseApp { + NSString *appName = [[NSUUID UUID] UUIDString]; + FIRApp *app = [self createAndConfigureAppWithName:appName]; + + XCTestExpectation *getIDExpectation1 = [self expectationWithDescription:@"getIDExpectation1"]; + [[FIRInstallations installationsWithApp:app] + installationIDWithCompletion:^(NSString *_Nullable identifier, NSError *_Nullable error) { + XCTAssertNotNil(identifier); + XCTAssertNil(error); + [getIDExpectation1 fulfill]; + }]; + + [self waitForExpectations:@[ getIDExpectation1 ] timeout:5]; + + // Wait for the registration request to be sent. + FBLWaitForPromisesWithTimeout(10); + + XCTestExpectation *getIDExpectation2 = [self expectationWithDescription:@"getIDExpectation2"]; + [[FIRInstallations installationsWithApp:app] + installationIDWithCompletion:^(NSString *_Nullable identifier, NSError *_Nullable error) { + XCTAssertNil(identifier); + XCTAssertNotNil(error); + [getIDExpectation2 fulfill]; + }]; + + [self waitForExpectations:@[ getIDExpectation2 ] timeout:5]; +} + - (FIRInstallations *)assertInstallationsWithAppNamed:(NSString *)appName { FIRApp *app = [self createAndConfigureAppWithName:appName]; FIRInstallations *installations = [FIRInstallations installationsWithApp:app]; diff --git a/FirebaseInstallations/Source/Tests/Unit/FIRInstallationsIDControllerTests.m b/FirebaseInstallations/Source/Tests/Unit/FIRInstallationsIDControllerTests.m index 276cd1944df..910a582346b 100644 --- a/FirebaseInstallations/Source/Tests/Unit/FIRInstallationsIDControllerTests.m +++ b/FirebaseInstallations/Source/Tests/Unit/FIRInstallationsIDControllerTests.m @@ -30,7 +30,10 @@ #import "FIRInstallationsIDController.h" #import "FIRInstallationsIIDStore.h" #import "FIRInstallationsStore.h" + #import "FIRInstallationsStoredAuthToken.h" +#import "FIRInstallationsStoredRegistrationError.h" +#import "FIRInstallationsStoredRegistrationParameters.h" @interface FIRInstallationsIDController (Tests) - (instancetype)initWithGoogleAppID:(NSString *)appID @@ -47,6 +50,9 @@ @interface FIRInstallationsIDControllerTests : XCTestCase @property(nonatomic) id mockIIDStore; @property(nonatomic) NSString *appID; @property(nonatomic) NSString *appName; + +@property(nonatomic) NSString *APIKey; +@property(nonatomic) NSString *projectID; @end @implementation FIRInstallationsIDControllerTests @@ -58,10 +64,15 @@ - (void)setUp { - (void)setUpWithAppName:(NSString *)appName { self.appID = @"appID"; self.appName = appName; + self.APIKey = @"APIKey"; + self.projectID = @"projectID"; + self.mockInstallationsStore = OCMStrictClassMock([FIRInstallationsStore class]); self.mockAPIService = OCMStrictClassMock([FIRInstallationsAPIService class]); self.mockIIDStore = OCMStrictClassMock([FIRInstallationsIIDStore class]); + [self stubAPIServiceParameters]; + self.controller = [[FIRInstallationsIDController alloc] initWithGoogleAppID:self.appID appName:self.appName @@ -79,6 +90,11 @@ - (void)tearDown { self.appName = nil; } +- (void)stubAPIServiceParameters { + OCMStub([self.mockAPIService APIKey]).andReturn(self.APIKey); + OCMStub([self.mockAPIService projectID]).andReturn(self.projectID); +} + #pragma mark - Get Installation - (void)testGetInstallationItem_WhenFIDExists_ThenItIsReturned { @@ -948,6 +964,146 @@ - (void)testRegisterInstallation_WhenServerRespondsWithDifferentFID_ThenFIDDidCh OCMVerifyAll(self.mockAPIService); } +#pragma mark - Registration Failures + +- (void)testRegisterInstallation_WhenServerRespondsWith429_ThenErrorIsNotStoredAndReturned { + // 1.1. Expect installation to be requested from the store. + FIRInstallationsItem *storedInstallation = + [FIRInstallationsItem createUnregisteredInstallationItem]; + OCMExpect([self.mockInstallationsStore installationForAppID:self.appID appName:self.appName]) + .andReturn([FBLPromise resolvedWith:storedInstallation]); + + // 1.2. Receive HTTP 429. + NSError *APIError = [FIRInstallationsErrorUtil APIErrorWithHTTPCode:429]; + FBLPromise *rejectedAPIPromise = [FBLPromise pendingPromise]; + [rejectedAPIPromise reject:APIError]; + OCMExpect([self.mockAPIService registerInstallation:storedInstallation]) + .andReturn(rejectedAPIPromise); + + // 1.3. Don't expect the installation to be stored with the error. + OCMReject([self.mockInstallationsStore saveInstallation:[OCMArg any]]); + + // 2. Request Installation. + FBLPromise *promise = [self.controller getInstallationItem]; + XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); + + // 4. Check. + XCTAssertNil(promise.error); + XCTAssertNotNil(promise.value); + + // The unregistered installation should be returned before the registration attempt. + XCTAssertEqual(promise.value.registrationStatus, FIRInstallationStatusUnregistered); + + OCMVerifyAll(self.mockInstallationsStore); + OCMVerifyAll(self.mockAPIService); +} + +- (void)testRegisterInstallation_WhenServerRespondsWith400_ThenErrorStoredAndReturned { + // 1.1. Expect installation to be requested from the store. + FIRInstallationsItem *storedInstallation = + [FIRInstallationsItem createUnregisteredInstallationItem]; + OCMExpect([self.mockInstallationsStore installationForAppID:self.appID appName:self.appName]) + .andReturn([FBLPromise resolvedWith:storedInstallation]); + + // 1.2. Receive HTTP 400. + NSError *APIError = [FIRInstallationsErrorUtil APIErrorWithHTTPCode:400]; + FBLPromise *rejectedAPIPromise = [FBLPromise pendingPromise]; + [rejectedAPIPromise reject:APIError]; + OCMExpect([self.mockAPIService registerInstallation:storedInstallation]) + .andReturn(rejectedAPIPromise); + + // 1.3. Expect the installation to be stored with the error. + OCMExpect([self.mockInstallationsStore + saveInstallation:[OCMArg checkWithBlock:^BOOL(FIRInstallationsItem *installation) { + XCTAssertEqual(installation.registrationStatus, + FIRInstallationStatusRegistrationFailed); + XCTAssertEqualObjects(installation.registrationError.APIError, APIError); + XCTAssertEqualObjects( + installation.registrationError.registrationParameters.APIKey, self.APIKey); + XCTAssertEqualObjects( + installation.registrationError.registrationParameters.projectID, + self.projectID); + return YES; + }]]) + .andReturn([FBLPromise resolvedWith:[NSNull null]]); + + // 2. Request Installation. + FBLPromise *promise = [self.controller getInstallationItem]; + XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); + + // 4. Check. + XCTAssertNil(promise.error); + XCTAssertNotNil(promise.value); + + // The unregistered installation should be returned before the registration attempt. + XCTAssertEqual(promise.value.registrationStatus, FIRInstallationStatusUnregistered); + + OCMVerifyAll(self.mockInstallationsStore); + OCMVerifyAll(self.mockAPIService); +} + +- (void)testGetInstallation_WhenRegistrationErrorStoredAndConfigurationIsTheSame_ThenFails { + // 1.1. Expect installation to be requested from the store. + FIRInstallationsItem *storedInstallation = + [self createFailedToRegisterInstallationWithParameters:[self currentRegistrationParameters]]; + OCMExpect([self.mockInstallationsStore installationForAppID:self.appID appName:self.appName]) + .andReturn([FBLPromise resolvedWith:storedInstallation]); + + // 1.2. Don't expect registration API request to be sent. + OCMReject([self.mockAPIService registerInstallation:[OCMArg any]]); + + // 2. Request Installation. + FBLPromise *promise = [self.controller getInstallationItem]; + XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); + + // 3. Check. + XCTAssertFalse(promise.isFulfilled); + XCTAssertEqualObjects(promise.error, storedInstallation.registrationError.APIError); + + OCMVerifyAll(self.mockInstallationsStore); + OCMVerifyAll(self.mockAPIService); +} + +- (void) + testGetInstallation_WhenRegistrationErrorStoredAndConfigurationDifferent_ThenSendsAPIRequest { + __block FBLPromise *storedInstallationPromise; + OCMExpect([self.mockInstallationsStore installationForAppID:self.appID appName:self.appName]) + .andDo(^(NSInvocation *invocation) { + [invocation setReturnValue:&storedInstallationPromise]; + }); + + // 1.1. Expect installation to be requested from the store. + FIRInstallationsItem *storedInstallation = + [self createFailedToRegisterInstallationWithParameters:[self otherRegistrationParameters]]; + storedInstallationPromise = [FBLPromise resolvedWith:storedInstallation]; + + // 1.2. Expect registration API request to be sent. + FIRInstallationsItem *registeredInstallation = + [FIRInstallationsItem createRegisteredInstallationItem]; + OCMExpect([self.mockAPIService registerInstallation:storedInstallation]) + .andReturn([FBLPromise resolvedWith:registeredInstallation]); + + // 1.3. Expect registered Installation to be stored. + OCMExpect([self.mockInstallationsStore saveInstallation:[OCMArg checkWithBlock:^BOOL(id obj) { + XCTAssertEqualObjects(obj, registeredInstallation); + storedInstallationPromise = + [FBLPromise resolvedWith:obj]; + return YES; + }]]) + .andReturn([FBLPromise resolvedWith:[NSNull null]]); + + // 2. Request Installation. + FBLPromise *promise = [self.controller getInstallationItem]; + XCTAssert(FBLWaitForPromisesWithTimeout(0.5)); + + // 3. Check. + XCTAssertEqualObjects(promise.value.identifier, registeredInstallation.identifier); + XCTAssertNil(promise.error); + + OCMVerifyAll(self.mockInstallationsStore); + OCMVerifyAll(self.mockAPIService); +} + #pragma mark - Helpers - (void)expectInstallationsStoreGetInstallationNotFound { @@ -982,4 +1138,25 @@ - (XCTestExpectation *)installationIDDidChangeNotificationExpectation { return notificationExpectation; } +- (FIRInstallationsItem *)createFailedToRegisterInstallationWithParameters: + (FIRInstallationsStoredRegistrationParameters *)registrationParameters { + FIRInstallationsStoredRegistrationError *error = [[FIRInstallationsStoredRegistrationError alloc] + initWithRegistrationParameters:registrationParameters + APIError:[FIRInstallationsErrorUtil APIErrorWithHTTPCode:400]]; + FIRInstallationsItem *installation = [FIRInstallationsItem createWithRegistrationFailure:error]; + return installation; +} + +- (FIRInstallationsStoredRegistrationParameters *)currentRegistrationParameters { + return [[FIRInstallationsStoredRegistrationParameters alloc] initWithAPIKey:self.APIKey + projectID:self.projectID]; +} + +- (FIRInstallationsStoredRegistrationParameters *)otherRegistrationParameters { + NSString *APIKey = [@"another" stringByAppendingString:self.APIKey]; + NSString *projectID = [@"another" stringByAppendingString:self.projectID]; + return [[FIRInstallationsStoredRegistrationParameters alloc] initWithAPIKey:APIKey + projectID:projectID]; +} + @end diff --git a/FirebaseInstallations/Source/Tests/Unit/FIRInstallationsStoredItemTests.m b/FirebaseInstallations/Source/Tests/Unit/FIRInstallationsStoredItemTests.m index d7bc4203e87..7f120827ccf 100644 --- a/FirebaseInstallations/Source/Tests/Unit/FIRInstallationsStoredItemTests.m +++ b/FirebaseInstallations/Source/Tests/Unit/FIRInstallationsStoredItemTests.m @@ -20,6 +20,8 @@ #import "FIRInstallationsStoredAuthToken.h" #import "FIRInstallationsStoredItem.h" +#import "FIRInstallationsStoredRegistrationError.h" +#import "FIRInstallationsStoredRegistrationParameters.h" @interface FIRInstallationsStoredItemTests : XCTestCase @@ -38,6 +40,7 @@ - (void)testItemArchivingUnarchiving { item.refreshToken = @"refresh-token"; item.authToken = authToken; item.registrationStatus = FIRInstallationStatusRegistered; + item.registrationError = [self createRegistrationError]; NSError *error; NSData *archivedItem = [FIRKeyedArchivingUtils archivedDataWithRootObject:item error:&error]; @@ -54,6 +57,29 @@ - (void)testItemArchivingUnarchiving { XCTAssertEqualObjects(unarchivedItem.authToken.token, item.authToken.token); XCTAssertEqualObjects(unarchivedItem.authToken.expirationDate, item.authToken.expirationDate); XCTAssertEqual(unarchivedItem.registrationStatus, item.registrationStatus); + + XCTAssertEqualObjects(unarchivedItem.registrationError.APIError, item.registrationError.APIError); + XCTAssertEqualObjects(unarchivedItem.registrationError.registrationParameters.APIKey, + item.registrationError.registrationParameters.APIKey); + XCTAssertEqualObjects(unarchivedItem.registrationError.registrationParameters.projectID, + item.registrationError.registrationParameters.projectID); +} + +- (FIRInstallationsStoredRegistrationError *)createRegistrationError { + FIRInstallationsStoredRegistrationParameters *params = + [[FIRInstallationsStoredRegistrationParameters alloc] initWithAPIKey:@"key" projectID:@"id"]; + XCTAssertEqualObjects(params.APIKey, @"key"); + XCTAssertEqualObjects(params.projectID, @"id"); + + NSError *error = [NSError errorWithDomain:@"FIRInstallationsStoredItemTests" + code:-1 + userInfo:@{NSLocalizedFailureReasonErrorKey : @"value"}]; + FIRInstallationsStoredRegistrationError *registrationError = + [[FIRInstallationsStoredRegistrationError alloc] initWithRegistrationParameters:params + APIError:error]; + XCTAssertEqualObjects(registrationError.APIError, error); + XCTAssertEqualObjects(registrationError.registrationParameters, params); + return registrationError; } @end diff --git a/FirebaseInstallations/Source/Tests/Utils/FIRInstallationsItem+Tests.h b/FirebaseInstallations/Source/Tests/Utils/FIRInstallationsItem+Tests.h index 83a6ee6a2cc..2e7118027e5 100644 --- a/FirebaseInstallations/Source/Tests/Utils/FIRInstallationsItem+Tests.h +++ b/FirebaseInstallations/Source/Tests/Utils/FIRInstallationsItem+Tests.h @@ -26,6 +26,9 @@ NS_ASSUME_NONNULL_BEGIN + (FIRInstallationsItem *)createRegisteredInstallationItemWithAppID:(NSString *)appID appName:(NSString *)appName; ++ (FIRInstallationsItem *)createWithRegistrationFailure: + (FIRInstallationsStoredRegistrationError *)error; + @end NS_ASSUME_NONNULL_END diff --git a/FirebaseInstallations/Source/Tests/Utils/FIRInstallationsItem+Tests.m b/FirebaseInstallations/Source/Tests/Utils/FIRInstallationsItem+Tests.m index 10c3628ec82..4127e18ecc1 100644 --- a/FirebaseInstallations/Source/Tests/Utils/FIRInstallationsItem+Tests.m +++ b/FirebaseInstallations/Source/Tests/Utils/FIRInstallationsItem+Tests.m @@ -56,4 +56,15 @@ + (FIRInstallationsItem *)createRegisteredInstallationItemWithAppID:(NSString *) return item; } ++ (FIRInstallationsItem *)createWithRegistrationFailure: + (FIRInstallationsStoredRegistrationError *)error { + FIRInstallationsItem *item = [[FIRInstallationsItem alloc] initWithAppID:@"appID" + firebaseAppName:kFIRDefaultAppName]; + item.firebaseInstallationID = @"firebaseInstallationID"; + item.registrationStatus = FIRInstallationStatusRegistrationFailed; + item.registrationError = error; + + return item; +} + @end