Files
firebase-ios-sdk/FirebaseRemoteConfig/Tests/Unit/RCNPersonalizationTest.m
T
4e5b83f2d9 Merge Realtime into Remote Config (#10060)
* Realtime rc dev (#9807)

* Remove commits from non-CLA account. Move TypeDef to private RCNRealtimeConfig file.

* Remove completion handler typedef

* Format file

* Update listener registration description

* Realtime rc dev (#9835)

* Remove commits from non-CLA account. Move TypeDef to private RCNRealtimeConfig file.

* Remove completion handler typedef

* Format file

* Update listener registration description

* Add http streaming and auto fetch. Also add template version number to cache for autofetch.

* Format file.

* Add unit tests

* Format files

* fix test

* update test and header file

* Change templateVersionNumber to ivar

* Fix async tests and update NSDictionary changes

* Format file

* Update fetch time

* remove define header

* Wrap auto fetch in async queue

* Add auto fetch test

* Change template version extraction check.

* Change realtime error code to start at 8001

* Realtime rc dev (#9914)

* Remove commits from non-CLA account. Move TypeDef to private RCNRealtimeConfig file.

* Remove completion handler typedef

* Format file

* Update listener registration description

* Add http streaming and auto fetch. Also add template version number to cache for autofetch.

* Format file.

* Add unit tests

* Format files

* fix test

* update test and header file

* Change templateVersionNumber to ivar

* Fix async tests and update NSDictionary changes

* Format file

* Update fetch time

* remove define header

* Add http stream retry and background change listener.

* Wrap auto fetch in async queue

* Expose realtime method in public

* Update comments

* Add auto fetch test

* Change template version extraction check.

* Change realtime error code to start at 8001

* Change from NSTimer to dispatch. Add flag to guard against retrying.

* Format and add comments.

* Update tests

* Qdpham13 patch 1 (#9913)

* Delete .SecondApp-GoogleService-Info.plist

* Delete SecondApp-GoogleService-Info.plist

* Delete GoogleService-Info.plist

* Delete SecondApp-GoogleService-Info.plist

* Update ViewController.m

* Delete clang-format directory

* format

* Change to NSExtensionHostWillEnterForegroundNotification

* Change to UIApplicationWillEnterForegroundNotification

* add import

* Remove import, use string literal instead of UIKit enum

* add test for new public method

* Update template version number retrieval function and update delay

* Fix test

* Fix test

* Update retry seconds and fetch interval seconds

* Update ddos to DDoS

* Update variable according to style guide.

* Replace NSLog with kFIRLoggerRemoteConfig (#10062)

* Replace NSLog with kFIRLoggerRemoteConfig

* Remove logs and change others from info to debug.

* update log messages.

* Format files

* Realtime rc failsafe (#10067)

* Add failsafe logic to request, response and application logic.

* Update flag name and conditions.

* Format file

* Add check for template version number

* Format files

* Changes based on PR comments

* move response parsing logic to separate function and add test for failsafe flag

* Update based on PR comments and add method to propagate error to listeners in queue.

* Realtime rc merge patch v1 (#10089)

* Fix foreground reconnection naming, add background check and flag, add check to see if fetch is currently in progress.

* Add check so fetches come only when the template version is greater than the current one.

* Format file changes

* Add debug log

* Make changes based on PR comments.

* Resolve failsafe changes

* Updating instance var names

* change template version check for auto fetch from >= to >

* Cherry-pick #10123 into Firebase 9.5.0

* [RC] Fix retry logic to respect max retry count (#10143)

* Fix retry logic to respect max retry count

* Rename to isRequestInProgress

* Save template version in settings (#10162)

* Save template version in settings

* Format files

* set last template version to 0

* Store template version in userDefaults instead of deviceContents

* return nil for unsuccessful fetches

* fix typo

* Realtime rc no change (#10188)

* Save template version in settings

* Format files

* set last template version to 0

* Store template version in userDefaults instead of deviceContents

* return nil for unsuccessful fetches

* fix typo

* put addEtagToHeader flag to fetch and propagate it to surrounding functions

* Add excludeEtagHeaderForRealtime flag to RCNConfigFetch

* Update mocks for Swift test

* Fix flag

* Add commentting

* reconcile master branch merge conflicts

* Revert "Realtime rc no change (#10188)" (#10346)

This reverts commit abe7939602.
In part of implementing a different solution.

* Add Http status check to Realtime stream (#10406)

* Check status code before retrying

* Change time out to 5 and a half minutes

* Throw exception if unable to establish connection due to non-retryable status code

* Format file

* Replace old realtime exceptions with new exceptions (#10482)

* Replace old realtime exceptions with new exceptions

* Fix word type

* Realtime RC Fetch ID (#10499)

* Add Realtime specific fetch method and fetch type header changes

* Add test

* Add comments and format

* Fix tests

* Update fetch header name

* Update tests and edit realtime fetch name and params

* refactor remainingAttempts var

* Realtime rc backoff (#10483)

* Change Realtime back off to use same system as Fetch and persist backoff state

* format and fix merge conflict

* Save retry count along with other throttling metadata

* Update var name

* Fix naming

* Return ConfigUpdate to realtime listeners (#10578)

* diff fetched and active config on realtime fetch

* Some refactoring and added tests

* Revert some unrelated changes to fetch

* Fix formatting

* Update branch and fix swift tests

* Rename updatedParams to updatedKeys (#10683)

* Update Realtime RC service URL. (#10713)

* Compare p13n metadata values as dictionaries. (#10756)

* Realtime RC 1P Bug Fix: Add checks for retryHttpConnection method (#10770)

* Add checks to retry method

* Move background check to beginning of retry

* Capitalize FetchType header (#10774)

* Remove Fetch Duplicate error and fix API misuse error (#10814)

* Remove fetch duplicate error

* Move completionHandler to bottom of didReceiveData

* Remove unused var

* Return Server produced error if Http status code is 403  (#10844)

* Add API message to enable Realtime

* Update error message parsing to return error message from backend

* Format file

* Add comment and remove unnecessary string

* Format and update error checks

* Change matcher string

* Update matcher string

* Address PR comments (#10845)

* Address PR comments

* Add Realtime Swift mocks

* Remove unnecessary mocks

* Format files

* Format tests

* Add Realtime mock

* Add missing quote

* Fix bugs

* Remove import

* Update import

* Change import

* Don't make connection call for Realtime mock

* Move around import of RCNConfigSettings

* Fix andDo

* Update test

* Add mockedRealtime flags

* trigger integration test

* Add integration and unit test

* change constant value

* Add new RC key

* Update update

* Remove deleted item

* Add registration removal

* Fix and format files

* remove semicolon

* Update error check

* remove space

* Update capitalization

* Add space

* Remove spaces

* remove more spaces

* Remove comment and add swift name

* format

* Add tests and sampleApp changes for updatedKeys (#10879)

* Update tests and sample app

* change key name

* remove unused var

* Add comment

* update sampleApp comments

* Update comments for public methods

* Update backoff commenting

* Add changelog entry for real-time updates. (#10942)

* Add changelog entry for RC real time updates.

* Hyphenation.

* Change doc references to hyphenate real-time (#10948)

* Change doc references to hyphenate real-time

* Run formatter.

* Re-word real-time paragraph to flow better.

---------

Co-authored-by: Andrew Heard <andrew@wizheard.com>
Co-authored-by: karenyz <58443706+karenyz@users.noreply.github.com>
Co-authored-by: Karen Zeng <karenzeng@google.com>
Co-authored-by: Dana Silver <dsilver1221@gmail.com>
Co-authored-by: Dana Silver <danasilver@google.com>
2023-03-13 16:02:56 -07:00

290 lines
12 KiB
Objective-C

/*
* 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 <OCMock/OCMock.h>
#import <XCTest/XCTest.h>
#import "FirebaseCore/Extension/FirebaseCoreInternal.h"
#import "FirebaseRemoteConfig/Sources/Private/FIRRemoteConfig_Private.h"
#import "FirebaseRemoteConfig/Sources/Private/RCNConfigFetch.h"
#import "FirebaseRemoteConfig/Sources/RCNConfigConstants.h"
#import "FirebaseRemoteConfig/Sources/RCNConfigDBManager.h"
#import "FirebaseRemoteConfig/Sources/RCNConfigValue_Internal.h"
#import "FirebaseRemoteConfig/Sources/RCNPersonalization.h"
#import "FirebaseRemoteConfig/Tests/Unit/RCNTestUtilities.h"
#import "Interop/Analytics/Public/FIRAnalyticsInterop.h"
@interface RCNConfigFetch (ForTest)
- (NSURLSessionDataTask *)URLSessionDataTaskWithContent:(NSData *)content
fetchTypeHeader:(NSString *)fetchTypeHeader
completionHandler:
(RCNConfigFetcherCompletion)fetcherCompletion;
- (void)fetchWithUserProperties:(NSDictionary *)userProperties
fetchTypeHeader:(NSString *)fetchTypeHeader
completionHandler:(FIRRemoteConfigFetchCompletion)completionHandler
updateCompletionHandler:(RCNConfigFetchCompletion)updateCompletionHandler;
@end
@interface RCNPersonalizationTest : XCTestCase {
NSDictionary *_configContainer;
NSMutableArray<NSDictionary *> *_fakeLogs;
id _analyticsMock;
RCNPersonalization *_personalization;
FIRRemoteConfig *_configInstance;
}
@end
@implementation RCNPersonalizationTest
- (void)setUp {
[super setUp];
_configContainer = @{
RCNFetchResponseKeyEntries : @{
@"key1" : [[FIRRemoteConfigValue alloc]
initWithData:[@"value1" dataUsingEncoding:NSUTF8StringEncoding]
source:FIRRemoteConfigSourceRemote],
@"key2" : [[FIRRemoteConfigValue alloc]
initWithData:[@"value2" dataUsingEncoding:NSUTF8StringEncoding]
source:FIRRemoteConfigSourceRemote],
@"key3" : [[FIRRemoteConfigValue alloc]
initWithData:[@"value3" dataUsingEncoding:NSUTF8StringEncoding]
source:FIRRemoteConfigSourceRemote]
},
RCNFetchResponseKeyPersonalizationMetadata : @{
@"key1" : @{
kPersonalizationId : @"p13n1",
kArmIndex : @0,
kChoiceId : @"id1",
kGroup : @"BASELINE"
},
@"key2" :
@{kPersonalizationId : @"p13n2", kArmIndex : @1, kChoiceId : @"id2", kGroup : @"P13N"}
}
};
_fakeLogs = [[NSMutableArray alloc] init];
_analyticsMock = OCMProtocolMock(@protocol(FIRAnalyticsInterop));
OCMStub([_analyticsMock logEventWithOrigin:kAnalyticsOriginPersonalization
name:[OCMArg isKindOfClass:[NSString class]]
parameters:[OCMArg isKindOfClass:[NSDictionary class]]])
.andDo(^(NSInvocation *invocation) {
__unsafe_unretained NSDictionary *bundle;
[invocation getArgument:&bundle atIndex:4];
[self->_fakeLogs addObject:bundle];
});
_personalization = [[RCNPersonalization alloc] initWithAnalytics:_analyticsMock];
// Always remove the database at the start of testing.
NSString *DBPath = [RCNTestUtilities remoteConfigPathForTestDatabase];
id DBMock = OCMClassMock([RCNConfigDBManager class]);
OCMStub([DBMock remoteConfigPathForDatabase]).andReturn(DBPath);
RCNConfigContent *configContent = [[RCNConfigContent alloc] initWithDBManager:DBMock];
// Create a mock FIRRemoteConfig instance.
_configInstance = OCMPartialMock([[FIRRemoteConfig alloc]
initWithAppName:@"testApp"
FIROptions:[[FIROptions alloc] initWithGoogleAppID:@"1:123:ios:test"
GCMSenderID:@"testSender"]
namespace:@"namespace"
DBManager:DBMock
configContent:configContent
analytics:_analyticsMock]);
[_configInstance setValue:[RCNPersonalizationTest mockFetchRequest] forKey:@"_configFetch"];
}
- (void)tearDown {
[super tearDown];
}
- (void)testNonPersonalizationKey {
[_fakeLogs removeAllObjects];
[_personalization logArmActive:@"key3" config:_configContainer];
OCMVerify(never(),
[_analyticsMock logEventWithOrigin:kAnalyticsOriginPersonalization
name:[OCMArg checkWithBlock:^BOOL(NSString *value) {
return [value isEqualToString:kExternalEvent] ||
[value isEqualToString:kInternalEvent];
}]
parameters:[OCMArg isKindOfClass:[NSDictionary class]]]);
XCTAssertEqual([_fakeLogs count], 0);
}
- (void)testSinglePersonalizationKey {
[_fakeLogs removeAllObjects];
[_personalization logArmActive:@"key1" config:_configContainer];
OCMVerify(times(2),
[_analyticsMock logEventWithOrigin:kAnalyticsOriginPersonalization
name:[OCMArg checkWithBlock:^BOOL(NSString *value) {
return [value isEqualToString:kExternalEvent] ||
[value isEqualToString:kInternalEvent];
}]
parameters:[OCMArg isKindOfClass:[NSDictionary class]]]);
XCTAssertEqual([_fakeLogs count], 2);
NSDictionary *logParams = @{
kExternalRcParameterParam : @"key1",
kExternalArmValueParam : @"value1",
kExternalPersonalizationIdParam : @"p13n1",
kExternalArmIndexParam : @0,
kExternalGroupParam : @"BASELINE"
};
XCTAssertEqualObjects(_fakeLogs[0], logParams);
NSDictionary *internalLogParams = @{kInternalChoiceIdParam : @"id1"};
XCTAssertEqualObjects(_fakeLogs[1], internalLogParams);
}
- (void)testMultiplePersonalizationKeys {
[_fakeLogs removeAllObjects];
[_personalization logArmActive:@"key1" config:_configContainer];
[_personalization logArmActive:@"key2" config:_configContainer];
[_personalization logArmActive:@"key1" config:_configContainer];
OCMVerify(times(4),
[_analyticsMock logEventWithOrigin:kAnalyticsOriginPersonalization
name:[OCMArg checkWithBlock:^BOOL(NSString *value) {
return [value isEqualToString:kExternalEvent] ||
[value isEqualToString:kInternalEvent];
}]
parameters:[OCMArg isKindOfClass:[NSDictionary class]]]);
XCTAssertEqual([_fakeLogs count], 4);
NSDictionary *logParams1 = @{
kExternalRcParameterParam : @"key1",
kExternalArmValueParam : @"value1",
kExternalPersonalizationIdParam : @"p13n1",
kExternalArmIndexParam : @0,
kExternalGroupParam : @"BASELINE"
};
XCTAssertEqualObjects(_fakeLogs[0], logParams1);
NSDictionary *internalLogParams1 = @{kInternalChoiceIdParam : @"id1"};
XCTAssertEqualObjects(_fakeLogs[1], internalLogParams1);
NSDictionary *logParams2 = @{
kExternalRcParameterParam : @"key2",
kExternalArmValueParam : @"value2",
kExternalPersonalizationIdParam : @"p13n2",
kExternalArmIndexParam : @1,
kExternalGroupParam : @"P13N"
};
XCTAssertEqualObjects(_fakeLogs[2], logParams2);
NSDictionary *internalLogParams2 = @{kInternalChoiceIdParam : @"id2"};
XCTAssertEqualObjects(_fakeLogs[3], internalLogParams2);
}
- (void)testRemoteConfigIntegration {
[_fakeLogs removeAllObjects];
FIRRemoteConfigFetchAndActivateCompletion fetchAndActivateCompletion =
^void(FIRRemoteConfigFetchAndActivateStatus status, NSError *error) {
OCMVerify(times(4), [self->_analyticsMock
logEventWithOrigin:kAnalyticsOriginPersonalization
name:[OCMArg checkWithBlock:^BOOL(NSString *value) {
return [value isEqualToString:kExternalEvent] ||
[value isEqualToString:kInternalEvent];
}]
parameters:[OCMArg isKindOfClass:[NSDictionary class]]]);
XCTAssertEqual([self->_fakeLogs count], 4);
NSDictionary *logParams1 = @{
kExternalRcParameterParam : @"key1",
kExternalArmValueParam : @"value1",
kExternalPersonalizationIdParam : @"p13n1",
kExternalArmIndexParam : @0,
kExternalGroupParam : @"BASELINE"
};
XCTAssertEqualObjects(self->_fakeLogs[0], logParams1);
NSDictionary *internalLogParams1 = @{kInternalChoiceIdParam : @"id1"};
XCTAssertEqualObjects(self->_fakeLogs[1], internalLogParams1);
NSDictionary *logParams2 = @{
kExternalRcParameterParam : @"key1",
kExternalArmValueParam : @"value1",
kExternalPersonalizationIdParam : @"p13n1",
kExternalArmIndexParam : @0,
kExternalGroupParam : @"BASELINE"
};
XCTAssertEqualObjects(self->_fakeLogs[2], logParams2);
NSDictionary *internalLogParams2 = @{kInternalChoiceIdParam : @"id2"};
XCTAssertEqualObjects(self->_fakeLogs[3], internalLogParams2);
};
[_configInstance fetchAndActivateWithCompletionHandler:fetchAndActivateCompletion];
[_configInstance configValueForKey:@"key1"];
[_configInstance configValueForKey:@"key2"];
}
+ (id)mockFetchRequest {
id configFetch = OCMClassMock([RCNConfigFetch class]);
OCMStub([configFetch fetchConfigWithExpirationDuration:0 completionHandler:OCMOCK_ANY])
.ignoringNonObjectArgs()
.andDo(^(NSInvocation *invocation) {
__unsafe_unretained FIRRemoteConfigFetchCompletion handler;
[invocation getArgument:&handler atIndex:3];
[configFetch fetchWithUserProperties:[[NSDictionary alloc] init]
fetchTypeHeader:@"Base/1"
completionHandler:handler
updateCompletionHandler:nil];
});
OCMExpect([configFetch
URLSessionDataTaskWithContent:[OCMArg any]
fetchTypeHeader:@"Base/1"
completionHandler:[RCNPersonalizationTest mockResponseHandler]])
.andReturn(nil);
return configFetch;
}
+ (id)mockResponseHandler {
NSDictionary *response = @{
RCNFetchResponseKeyState : RCNFetchResponseKeyStateUpdate,
RCNFetchResponseKeyEntries : @{@"key1" : @"value1", @"key2" : @"value2", @"key3" : @"value3"},
RCNFetchResponseKeyPersonalizationMetadata : @{
@"key1" : @{
kPersonalizationId : @"p13n1",
kArmIndex : @0,
kChoiceId : @"id1",
kGroup : @"BASELINE"
},
@"key2" :
@{kPersonalizationId : @"p13n2", kArmIndex : @1, kChoiceId : @"id2", kGroup : @"P13N"}
}
};
return [OCMArg invokeBlockWithArgs:[NSJSONSerialization dataWithJSONObject:response
options:0
error:nil],
[[NSHTTPURLResponse alloc]
initWithURL:[NSURL URLWithString:@"https://firebase.com"]
statusCode:200
HTTPVersion:nil
headerFields:@{@"etag" : @"etag1"}],
[NSNull null], nil];
}
@end