RCNPersonalizationTest.m 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  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. fetchTypeHeader:(NSString *)fetchTypeHeader
  30. completionHandler:
  31. (RCNConfigFetcherCompletion)fetcherCompletion;
  32. - (void)fetchWithUserProperties:(NSDictionary *)userProperties
  33. fetchTypeHeader:(NSString *)fetchTypeHeader
  34. completionHandler:(FIRRemoteConfigFetchCompletion)completionHandler
  35. updateCompletionHandler:(RCNConfigFetchCompletion)updateCompletionHandler;
  36. @end
  37. @interface RCNPersonalizationTest : XCTestCase {
  38. NSDictionary *_configContainer;
  39. NSMutableArray<NSDictionary *> *_fakeLogs;
  40. id _analyticsMock;
  41. RCNPersonalization *_personalization;
  42. FIRRemoteConfig *_configInstance;
  43. }
  44. @end
  45. @implementation RCNPersonalizationTest
  46. - (void)setUp {
  47. [super setUp];
  48. _configContainer = @{
  49. RCNFetchResponseKeyEntries : @{
  50. @"key1" : [[FIRRemoteConfigValue alloc]
  51. initWithData:[@"value1" dataUsingEncoding:NSUTF8StringEncoding]
  52. source:FIRRemoteConfigSourceRemote],
  53. @"key2" : [[FIRRemoteConfigValue alloc]
  54. initWithData:[@"value2" dataUsingEncoding:NSUTF8StringEncoding]
  55. source:FIRRemoteConfigSourceRemote],
  56. @"key3" : [[FIRRemoteConfigValue alloc]
  57. initWithData:[@"value3" dataUsingEncoding:NSUTF8StringEncoding]
  58. source:FIRRemoteConfigSourceRemote]
  59. },
  60. RCNFetchResponseKeyPersonalizationMetadata : @{
  61. @"key1" : @{
  62. kPersonalizationId : @"p13n1",
  63. kArmIndex : @0,
  64. kChoiceId : @"id1",
  65. kGroup : @"BASELINE"
  66. },
  67. @"key2" :
  68. @{kPersonalizationId : @"p13n2", kArmIndex : @1, kChoiceId : @"id2", kGroup : @"P13N"}
  69. }
  70. };
  71. _fakeLogs = [[NSMutableArray alloc] init];
  72. _analyticsMock = OCMProtocolMock(@protocol(FIRAnalyticsInterop));
  73. OCMStub([_analyticsMock logEventWithOrigin:kAnalyticsOriginPersonalization
  74. name:[OCMArg isKindOfClass:[NSString class]]
  75. parameters:[OCMArg isKindOfClass:[NSDictionary class]]])
  76. .andDo(^(NSInvocation *invocation) {
  77. __unsafe_unretained NSDictionary *bundle;
  78. [invocation getArgument:&bundle atIndex:4];
  79. [self->_fakeLogs addObject:bundle];
  80. });
  81. _personalization = [[RCNPersonalization alloc] initWithAnalytics:_analyticsMock];
  82. // Always remove the database at the start of testing.
  83. NSString *DBPath = [RCNTestUtilities remoteConfigPathForTestDatabase];
  84. id DBMock = OCMClassMock([RCNConfigDBManager class]);
  85. OCMStub([DBMock remoteConfigPathForDatabase]).andReturn(DBPath);
  86. RCNConfigContent *configContent = [[RCNConfigContent alloc] initWithDBManager:DBMock];
  87. // Create a mock FIRRemoteConfig instance.
  88. _configInstance = OCMPartialMock([[FIRRemoteConfig alloc]
  89. initWithAppName:@"testApp"
  90. FIROptions:[[FIROptions alloc] initWithGoogleAppID:@"1:123:ios:test"
  91. GCMSenderID:@"testSender"]
  92. namespace:@"namespace"
  93. DBManager:DBMock
  94. configContent:configContent
  95. analytics:_analyticsMock]);
  96. [_configInstance setValue:[RCNPersonalizationTest mockFetchRequest] forKey:@"_configFetch"];
  97. }
  98. - (void)tearDown {
  99. [super tearDown];
  100. }
  101. - (void)testNonPersonalizationKey {
  102. [_fakeLogs removeAllObjects];
  103. [_personalization logArmActive:@"key3" config:_configContainer];
  104. OCMVerify(never(),
  105. [_analyticsMock logEventWithOrigin:kAnalyticsOriginPersonalization
  106. name:[OCMArg checkWithBlock:^BOOL(NSString *value) {
  107. return [value isEqualToString:kExternalEvent] ||
  108. [value isEqualToString:kInternalEvent];
  109. }]
  110. parameters:[OCMArg isKindOfClass:[NSDictionary class]]]);
  111. XCTAssertEqual([_fakeLogs count], 0);
  112. }
  113. - (void)testSinglePersonalizationKey {
  114. [_fakeLogs removeAllObjects];
  115. [_personalization logArmActive:@"key1" config:_configContainer];
  116. OCMVerify(times(2),
  117. [_analyticsMock logEventWithOrigin:kAnalyticsOriginPersonalization
  118. name:[OCMArg checkWithBlock:^BOOL(NSString *value) {
  119. return [value isEqualToString:kExternalEvent] ||
  120. [value isEqualToString:kInternalEvent];
  121. }]
  122. parameters:[OCMArg isKindOfClass:[NSDictionary class]]]);
  123. XCTAssertEqual([_fakeLogs count], 2);
  124. NSDictionary *logParams = @{
  125. kExternalRcParameterParam : @"key1",
  126. kExternalArmValueParam : @"value1",
  127. kExternalPersonalizationIdParam : @"p13n1",
  128. kExternalArmIndexParam : @0,
  129. kExternalGroupParam : @"BASELINE"
  130. };
  131. XCTAssertEqualObjects(_fakeLogs[0], logParams);
  132. NSDictionary *internalLogParams = @{kInternalChoiceIdParam : @"id1"};
  133. XCTAssertEqualObjects(_fakeLogs[1], internalLogParams);
  134. }
  135. - (void)testMultiplePersonalizationKeys {
  136. [_fakeLogs removeAllObjects];
  137. [_personalization logArmActive:@"key1" config:_configContainer];
  138. [_personalization logArmActive:@"key2" config:_configContainer];
  139. [_personalization logArmActive:@"key1" config:_configContainer];
  140. OCMVerify(times(4),
  141. [_analyticsMock logEventWithOrigin:kAnalyticsOriginPersonalization
  142. name:[OCMArg checkWithBlock:^BOOL(NSString *value) {
  143. return [value isEqualToString:kExternalEvent] ||
  144. [value isEqualToString:kInternalEvent];
  145. }]
  146. parameters:[OCMArg isKindOfClass:[NSDictionary class]]]);
  147. XCTAssertEqual([_fakeLogs count], 4);
  148. NSDictionary *logParams1 = @{
  149. kExternalRcParameterParam : @"key1",
  150. kExternalArmValueParam : @"value1",
  151. kExternalPersonalizationIdParam : @"p13n1",
  152. kExternalArmIndexParam : @0,
  153. kExternalGroupParam : @"BASELINE"
  154. };
  155. XCTAssertEqualObjects(_fakeLogs[0], logParams1);
  156. NSDictionary *internalLogParams1 = @{kInternalChoiceIdParam : @"id1"};
  157. XCTAssertEqualObjects(_fakeLogs[1], internalLogParams1);
  158. NSDictionary *logParams2 = @{
  159. kExternalRcParameterParam : @"key2",
  160. kExternalArmValueParam : @"value2",
  161. kExternalPersonalizationIdParam : @"p13n2",
  162. kExternalArmIndexParam : @1,
  163. kExternalGroupParam : @"P13N"
  164. };
  165. XCTAssertEqualObjects(_fakeLogs[2], logParams2);
  166. NSDictionary *internalLogParams2 = @{kInternalChoiceIdParam : @"id2"};
  167. XCTAssertEqualObjects(_fakeLogs[3], internalLogParams2);
  168. }
  169. - (void)testRemoteConfigIntegration {
  170. [_fakeLogs removeAllObjects];
  171. FIRRemoteConfigFetchAndActivateCompletion fetchAndActivateCompletion =
  172. ^void(FIRRemoteConfigFetchAndActivateStatus status, NSError *error) {
  173. OCMVerify(times(4), [self->_analyticsMock
  174. logEventWithOrigin:kAnalyticsOriginPersonalization
  175. name:[OCMArg checkWithBlock:^BOOL(NSString *value) {
  176. return [value isEqualToString:kExternalEvent] ||
  177. [value isEqualToString:kInternalEvent];
  178. }]
  179. parameters:[OCMArg isKindOfClass:[NSDictionary class]]]);
  180. XCTAssertEqual([self->_fakeLogs count], 4);
  181. NSDictionary *logParams1 = @{
  182. kExternalRcParameterParam : @"key1",
  183. kExternalArmValueParam : @"value1",
  184. kExternalPersonalizationIdParam : @"p13n1",
  185. kExternalArmIndexParam : @0,
  186. kExternalGroupParam : @"BASELINE"
  187. };
  188. XCTAssertEqualObjects(self->_fakeLogs[0], logParams1);
  189. NSDictionary *internalLogParams1 = @{kInternalChoiceIdParam : @"id1"};
  190. XCTAssertEqualObjects(self->_fakeLogs[1], internalLogParams1);
  191. NSDictionary *logParams2 = @{
  192. kExternalRcParameterParam : @"key1",
  193. kExternalArmValueParam : @"value1",
  194. kExternalPersonalizationIdParam : @"p13n1",
  195. kExternalArmIndexParam : @0,
  196. kExternalGroupParam : @"BASELINE"
  197. };
  198. XCTAssertEqualObjects(self->_fakeLogs[2], logParams2);
  199. NSDictionary *internalLogParams2 = @{kInternalChoiceIdParam : @"id2"};
  200. XCTAssertEqualObjects(self->_fakeLogs[3], internalLogParams2);
  201. };
  202. [_configInstance fetchAndActivateWithCompletionHandler:fetchAndActivateCompletion];
  203. [_configInstance configValueForKey:@"key1"];
  204. [_configInstance configValueForKey:@"key2"];
  205. }
  206. + (id)mockFetchRequest {
  207. id configFetch = OCMClassMock([RCNConfigFetch class]);
  208. OCMStub([configFetch fetchConfigWithExpirationDuration:0 completionHandler:OCMOCK_ANY])
  209. .ignoringNonObjectArgs()
  210. .andDo(^(NSInvocation *invocation) {
  211. __unsafe_unretained FIRRemoteConfigFetchCompletion handler;
  212. [invocation getArgument:&handler atIndex:3];
  213. [configFetch fetchWithUserProperties:[[NSDictionary alloc] init]
  214. fetchTypeHeader:@"Base/1"
  215. completionHandler:handler
  216. updateCompletionHandler:nil];
  217. });
  218. OCMExpect([configFetch
  219. URLSessionDataTaskWithContent:[OCMArg any]
  220. fetchTypeHeader:@"Base/1"
  221. completionHandler:[RCNPersonalizationTest mockResponseHandler]])
  222. .andReturn(nil);
  223. return configFetch;
  224. }
  225. + (id)mockResponseHandler {
  226. NSDictionary *response = @{
  227. RCNFetchResponseKeyState : RCNFetchResponseKeyStateUpdate,
  228. RCNFetchResponseKeyEntries : @{@"key1" : @"value1", @"key2" : @"value2", @"key3" : @"value3"},
  229. RCNFetchResponseKeyPersonalizationMetadata : @{
  230. @"key1" : @{
  231. kPersonalizationId : @"p13n1",
  232. kArmIndex : @0,
  233. kChoiceId : @"id1",
  234. kGroup : @"BASELINE"
  235. },
  236. @"key2" :
  237. @{kPersonalizationId : @"p13n2", kArmIndex : @1, kChoiceId : @"id2", kGroup : @"P13N"}
  238. }
  239. };
  240. return [OCMArg invokeBlockWithArgs:[NSJSONSerialization dataWithJSONObject:response
  241. options:0
  242. error:nil],
  243. [[NSHTTPURLResponse alloc]
  244. initWithURL:[NSURL URLWithString:@"https://firebase.com"]
  245. statusCode:200
  246. HTTPVersion:nil
  247. headerFields:@{@"etag" : @"etag1"}],
  248. [NSNull null], nil];
  249. }
  250. @end