RCNPersonalizationTest.m 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. /*
  2. * Copyright 2019 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import <OCMock/OCMock.h>
  17. #import <XCTest/XCTest.h>
  18. #import "FirebaseCore/Extension/FirebaseCoreInternal.h"
  19. #import "FirebaseRemoteConfig/Sources/Private/FIRRemoteConfig_Private.h"
  20. #import "FirebaseRemoteConfig/Sources/Private/RCNConfigFetch.h"
  21. #import "FirebaseRemoteConfig/Sources/RCNConfigConstants.h"
  22. #import "FirebaseRemoteConfig/Sources/RCNConfigDBManager.h"
  23. #import "FirebaseRemoteConfig/Sources/RCNConfigValue_Internal.h"
  24. #import "FirebaseRemoteConfig/Sources/RCNPersonalization.h"
  25. #import "FirebaseRemoteConfig/Tests/Unit/RCNTestUtilities.h"
  26. #import "Interop/Analytics/Public/FIRAnalyticsInterop.h"
  27. @interface RCNConfigFetch (ForTest)
  28. - (NSURLSessionDataTask *)URLSessionDataTaskWithContent:(NSData *)content
  29. completionHandler:
  30. (RCNConfigFetcherCompletion)fetcherCompletion
  31. excludeEtagHeaderForRealtime:(bool)excludeEtagHeaderForRealtime;
  32. - (void)fetchWithUserProperties:(NSDictionary *)userProperties
  33. completionHandler:(FIRRemoteConfigFetchCompletion)completionHandler
  34. excludeEtagHeaderForRealtime:(bool)excludeEtagHeaderForRealtime;
  35. @end
  36. @interface RCNPersonalizationTest : XCTestCase {
  37. NSDictionary *_configContainer;
  38. NSMutableArray<NSDictionary *> *_fakeLogs;
  39. id _analyticsMock;
  40. RCNPersonalization *_personalization;
  41. FIRRemoteConfig *_configInstance;
  42. }
  43. @end
  44. @implementation RCNPersonalizationTest
  45. - (void)setUp {
  46. [super setUp];
  47. _configContainer = @{
  48. RCNFetchResponseKeyEntries : @{
  49. @"key1" : [[FIRRemoteConfigValue alloc]
  50. initWithData:[@"value1" dataUsingEncoding:NSUTF8StringEncoding]
  51. source:FIRRemoteConfigSourceRemote],
  52. @"key2" : [[FIRRemoteConfigValue alloc]
  53. initWithData:[@"value2" dataUsingEncoding:NSUTF8StringEncoding]
  54. source:FIRRemoteConfigSourceRemote],
  55. @"key3" : [[FIRRemoteConfigValue alloc]
  56. initWithData:[@"value3" dataUsingEncoding:NSUTF8StringEncoding]
  57. source:FIRRemoteConfigSourceRemote]
  58. },
  59. RCNFetchResponseKeyPersonalizationMetadata : @{
  60. @"key1" : @{
  61. kPersonalizationId : @"p13n1",
  62. kArmIndex : @0,
  63. kChoiceId : @"id1",
  64. kGroup : @"BASELINE"
  65. },
  66. @"key2" :
  67. @{kPersonalizationId : @"p13n2", kArmIndex : @1, kChoiceId : @"id2", kGroup : @"P13N"}
  68. }
  69. };
  70. _fakeLogs = [[NSMutableArray alloc] init];
  71. _analyticsMock = OCMProtocolMock(@protocol(FIRAnalyticsInterop));
  72. OCMStub([_analyticsMock logEventWithOrigin:kAnalyticsOriginPersonalization
  73. name:[OCMArg isKindOfClass:[NSString class]]
  74. parameters:[OCMArg isKindOfClass:[NSDictionary class]]])
  75. .andDo(^(NSInvocation *invocation) {
  76. __unsafe_unretained NSDictionary *bundle;
  77. [invocation getArgument:&bundle atIndex:4];
  78. [self->_fakeLogs addObject:bundle];
  79. });
  80. _personalization = [[RCNPersonalization alloc] initWithAnalytics:_analyticsMock];
  81. // Always remove the database at the start of testing.
  82. NSString *DBPath = [RCNTestUtilities remoteConfigPathForTestDatabase];
  83. id DBMock = OCMClassMock([RCNConfigDBManager class]);
  84. OCMStub([DBMock remoteConfigPathForDatabase]).andReturn(DBPath);
  85. RCNConfigContent *configContent = [[RCNConfigContent alloc] initWithDBManager:DBMock];
  86. // Create a mock FIRRemoteConfig instance.
  87. _configInstance = OCMPartialMock([[FIRRemoteConfig alloc]
  88. initWithAppName:@"testApp"
  89. FIROptions:[[FIROptions alloc] initWithGoogleAppID:@"1:123:ios:test"
  90. GCMSenderID:@"testSender"]
  91. namespace:@"namespace"
  92. DBManager:DBMock
  93. configContent:configContent
  94. analytics:_analyticsMock]);
  95. [_configInstance setValue:[RCNPersonalizationTest mockFetchRequest] forKey:@"_configFetch"];
  96. }
  97. - (void)tearDown {
  98. [super tearDown];
  99. }
  100. - (void)testNonPersonalizationKey {
  101. [_fakeLogs removeAllObjects];
  102. [_personalization logArmActive:@"key3" config:_configContainer];
  103. OCMVerify(never(),
  104. [_analyticsMock logEventWithOrigin:kAnalyticsOriginPersonalization
  105. name:[OCMArg checkWithBlock:^BOOL(NSString *value) {
  106. return [value isEqualToString:kExternalEvent] ||
  107. [value isEqualToString:kInternalEvent];
  108. }]
  109. parameters:[OCMArg isKindOfClass:[NSDictionary class]]]);
  110. XCTAssertEqual([_fakeLogs count], 0);
  111. }
  112. - (void)testSinglePersonalizationKey {
  113. [_fakeLogs removeAllObjects];
  114. [_personalization logArmActive:@"key1" config:_configContainer];
  115. OCMVerify(times(2),
  116. [_analyticsMock logEventWithOrigin:kAnalyticsOriginPersonalization
  117. name:[OCMArg checkWithBlock:^BOOL(NSString *value) {
  118. return [value isEqualToString:kExternalEvent] ||
  119. [value isEqualToString:kInternalEvent];
  120. }]
  121. parameters:[OCMArg isKindOfClass:[NSDictionary class]]]);
  122. XCTAssertEqual([_fakeLogs count], 2);
  123. NSDictionary *logParams = @{
  124. kExternalRcParameterParam : @"key1",
  125. kExternalArmValueParam : @"value1",
  126. kExternalPersonalizationIdParam : @"p13n1",
  127. kExternalArmIndexParam : @0,
  128. kExternalGroupParam : @"BASELINE"
  129. };
  130. XCTAssertEqualObjects(_fakeLogs[0], logParams);
  131. NSDictionary *internalLogParams = @{kInternalChoiceIdParam : @"id1"};
  132. XCTAssertEqualObjects(_fakeLogs[1], internalLogParams);
  133. }
  134. - (void)testMultiplePersonalizationKeys {
  135. [_fakeLogs removeAllObjects];
  136. [_personalization logArmActive:@"key1" config:_configContainer];
  137. [_personalization logArmActive:@"key2" config:_configContainer];
  138. [_personalization logArmActive:@"key1" config:_configContainer];
  139. OCMVerify(times(4),
  140. [_analyticsMock logEventWithOrigin:kAnalyticsOriginPersonalization
  141. name:[OCMArg checkWithBlock:^BOOL(NSString *value) {
  142. return [value isEqualToString:kExternalEvent] ||
  143. [value isEqualToString:kInternalEvent];
  144. }]
  145. parameters:[OCMArg isKindOfClass:[NSDictionary class]]]);
  146. XCTAssertEqual([_fakeLogs count], 4);
  147. NSDictionary *logParams1 = @{
  148. kExternalRcParameterParam : @"key1",
  149. kExternalArmValueParam : @"value1",
  150. kExternalPersonalizationIdParam : @"p13n1",
  151. kExternalArmIndexParam : @0,
  152. kExternalGroupParam : @"BASELINE"
  153. };
  154. XCTAssertEqualObjects(_fakeLogs[0], logParams1);
  155. NSDictionary *internalLogParams1 = @{kInternalChoiceIdParam : @"id1"};
  156. XCTAssertEqualObjects(_fakeLogs[1], internalLogParams1);
  157. NSDictionary *logParams2 = @{
  158. kExternalRcParameterParam : @"key2",
  159. kExternalArmValueParam : @"value2",
  160. kExternalPersonalizationIdParam : @"p13n2",
  161. kExternalArmIndexParam : @1,
  162. kExternalGroupParam : @"P13N"
  163. };
  164. XCTAssertEqualObjects(_fakeLogs[2], logParams2);
  165. NSDictionary *internalLogParams2 = @{kInternalChoiceIdParam : @"id2"};
  166. XCTAssertEqualObjects(_fakeLogs[3], internalLogParams2);
  167. }
  168. - (void)testRemoteConfigIntegration {
  169. [_fakeLogs removeAllObjects];
  170. FIRRemoteConfigFetchAndActivateCompletion fetchAndActivateCompletion =
  171. ^void(FIRRemoteConfigFetchAndActivateStatus status, NSError *error) {
  172. OCMVerify(times(4), [self->_analyticsMock
  173. logEventWithOrigin:kAnalyticsOriginPersonalization
  174. name:[OCMArg checkWithBlock:^BOOL(NSString *value) {
  175. return [value isEqualToString:kExternalEvent] ||
  176. [value isEqualToString:kInternalEvent];
  177. }]
  178. parameters:[OCMArg isKindOfClass:[NSDictionary class]]]);
  179. XCTAssertEqual([self->_fakeLogs count], 4);
  180. NSDictionary *logParams1 = @{
  181. kExternalRcParameterParam : @"key1",
  182. kExternalArmValueParam : @"value1",
  183. kExternalPersonalizationIdParam : @"p13n1",
  184. kExternalArmIndexParam : @0,
  185. kExternalGroupParam : @"BASELINE"
  186. };
  187. XCTAssertEqualObjects(self->_fakeLogs[0], logParams1);
  188. NSDictionary *internalLogParams1 = @{kInternalChoiceIdParam : @"id1"};
  189. XCTAssertEqualObjects(self->_fakeLogs[1], internalLogParams1);
  190. NSDictionary *logParams2 = @{
  191. kExternalRcParameterParam : @"key1",
  192. kExternalArmValueParam : @"value1",
  193. kExternalPersonalizationIdParam : @"p13n1",
  194. kExternalArmIndexParam : @0,
  195. kExternalGroupParam : @"BASELINE"
  196. };
  197. XCTAssertEqualObjects(self->_fakeLogs[2], logParams2);
  198. NSDictionary *internalLogParams2 = @{kInternalChoiceIdParam : @"id2"};
  199. XCTAssertEqualObjects(self->_fakeLogs[3], internalLogParams2);
  200. };
  201. [_configInstance fetchAndActivateWithCompletionHandler:fetchAndActivateCompletion];
  202. [_configInstance configValueForKey:@"key1"];
  203. [_configInstance configValueForKey:@"key2"];
  204. }
  205. + (id)mockFetchRequest {
  206. id configFetch = OCMClassMock([RCNConfigFetch class]);
  207. OCMStub([configFetch fetchConfigWithExpirationDuration:0
  208. completionHandler:OCMOCK_ANY
  209. excludeEtagHeaderForRealtime:false])
  210. .ignoringNonObjectArgs()
  211. .andDo(^(NSInvocation *invocation) {
  212. __unsafe_unretained FIRRemoteConfigFetchCompletion handler;
  213. [invocation getArgument:&handler atIndex:3];
  214. [configFetch fetchWithUserProperties:[[NSDictionary alloc] init]
  215. completionHandler:handler
  216. excludeEtagHeaderForRealtime:false];
  217. });
  218. OCMExpect([configFetch URLSessionDataTaskWithContent:[OCMArg any]
  219. completionHandler:[RCNPersonalizationTest mockResponseHandler]
  220. excludeEtagHeaderForRealtime:false])
  221. .andReturn(nil);
  222. return configFetch;
  223. }
  224. + (id)mockResponseHandler {
  225. NSDictionary *response = @{
  226. RCNFetchResponseKeyState : RCNFetchResponseKeyStateUpdate,
  227. RCNFetchResponseKeyEntries : @{@"key1" : @"value1", @"key2" : @"value2", @"key3" : @"value3"},
  228. RCNFetchResponseKeyPersonalizationMetadata : @{
  229. @"key1" : @{
  230. kPersonalizationId : @"p13n1",
  231. kArmIndex : @0,
  232. kChoiceId : @"id1",
  233. kGroup : @"BASELINE"
  234. },
  235. @"key2" :
  236. @{kPersonalizationId : @"p13n2", kArmIndex : @1, kChoiceId : @"id2", kGroup : @"P13N"}
  237. }
  238. };
  239. return [OCMArg invokeBlockWithArgs:[NSJSONSerialization dataWithJSONObject:response
  240. options:0
  241. error:nil],
  242. [[NSHTTPURLResponse alloc]
  243. initWithURL:[NSURL URLWithString:@"https://firebase.com"]
  244. statusCode:200
  245. HTTPVersion:nil
  246. headerFields:@{@"etag" : @"etag1"}],
  247. [NSNull null], nil];
  248. }
  249. @end