RCNPersonalizationTest.m 12 KB

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