ABTConditionalUserPropertyControllerTest.m 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. // Copyright 2019 Google
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #import <XCTest/XCTest.h>
  15. #import <FirebaseABTesting/FIRExperimentController.h>
  16. #import <FirebaseABTesting/FIRLifecycleEvents.h>
  17. #import <OCMock/OCMock.h>
  18. #import "FirebaseABTesting/Sources/ABTConditionalUserPropertyController.h"
  19. #import "FirebaseABTesting/Sources/ABTConstants.h"
  20. #import "FirebaseABTesting/Tests/Unit/ABTFakeFIRAConditionalUserPropertyController.h"
  21. #import "FirebaseABTesting/Tests/Unit/ABTTestUniversalConstants.h"
  22. #import "FirebaseCore/Sources/Private/FirebaseCoreInternal.h"
  23. @interface ABTConditionalUserPropertyController (ExposedForTest)
  24. - (NSInteger)maxNumberOfExperimentsOfOrigin:(NSString *)origin;
  25. - (void)maxNumberOfExperimentsOfOrigin:(NSString *)origin
  26. completionHandler:(void (^)(int32_t))completionHandler;
  27. - (id)createExperimentFromOrigin:(NSString *)origin
  28. payload:(ABTExperimentPayload *)payload
  29. events:(FIRLifecycleEvents *)events;
  30. - (ABTExperimentPayload_ExperimentOverflowPolicy)
  31. overflowPolicyWithPayload:(ABTExperimentPayload *)payload
  32. originalPolicy:(ABTExperimentPayload_ExperimentOverflowPolicy)originalPolicy;
  33. /// Surface internal initializer to avoid singleton usage during tests.
  34. - (instancetype)initWithAnalytics:(nullable id<FIRAnalyticsInterop>)analytics;
  35. @end
  36. typedef void (^FakeAnalyticsLogEventWithOriginNameParametersHandler)(
  37. NSString *origin, NSString *name, NSDictionary<NSString *, id> *parameters);
  38. @interface ABTConditionalUserPropertyControllerTest : XCTestCase {
  39. ABTConditionalUserPropertyController *_ABTCUPController;
  40. ABTFakeFIRAConditionalUserPropertyController *_fakeController;
  41. id _mockCUPController;
  42. }
  43. @end
  44. @implementation ABTConditionalUserPropertyControllerTest
  45. - (void)setUp {
  46. [super setUp];
  47. _fakeController = [ABTFakeFIRAConditionalUserPropertyController sharedInstance];
  48. _ABTCUPController = [[ABTConditionalUserPropertyController alloc]
  49. initWithAnalytics:[[FakeAnalytics alloc] initWithFakeController:_fakeController]];
  50. _mockCUPController = OCMPartialMock(_ABTCUPController);
  51. OCMStub([_mockCUPController maxNumberOfExperimentsOfOrigin:[OCMArg any]]).andReturn(3);
  52. // Must initialize FIRApp before calling set experiment as Firebase Analytics internal event
  53. // logging requires it.
  54. NSDictionary *optionsDictionary = @{
  55. kFIRGoogleAppID : @"1:123456789012:ios:1234567890123456",
  56. @"GCM_SENDER_ID" : @"123456789012"
  57. };
  58. FIROptions *options = [[FIROptions alloc] initInternalWithOptionsDictionary:optionsDictionary];
  59. [FIRApp configureWithOptions:options];
  60. }
  61. - (void)tearDown {
  62. [_fakeController resetExperiments];
  63. [_mockCUPController stopMocking];
  64. [FIRApp resetApps];
  65. [super tearDown];
  66. }
  67. #pragma mark - test proxy methods on Firebase Analytics
  68. - (void)testSetExperiment {
  69. ABTExperimentPayload *payload = [[ABTExperimentPayload alloc] init];
  70. payload.experimentId = @"exp_0";
  71. FIRLifecycleEvents *events = [[FIRLifecycleEvents alloc] init];
  72. [_ABTCUPController
  73. setExperimentWithOrigin:gABTTestOrigin
  74. payload:payload
  75. events:events
  76. policy:ABTExperimentPayload_ExperimentOverflowPolicy_IgnoreNewest];
  77. NSArray *experiments = [_ABTCUPController experimentsWithOrigin:gABTTestOrigin];
  78. XCTAssertEqual(experiments.count, 1);
  79. }
  80. - (void)testSetExperimentWhenOverflow {
  81. ABTExperimentPayload *payload = [[ABTExperimentPayload alloc] init];
  82. payload.experimentId = @"exp_1";
  83. payload.variantId = @"v1";
  84. FIRLifecycleEvents *events = [[FIRLifecycleEvents alloc] init];
  85. [_ABTCUPController
  86. setExperimentWithOrigin:gABTTestOrigin
  87. payload:payload
  88. events:events
  89. policy:ABTExperimentPayload_ExperimentOverflowPolicy_IgnoreNewest];
  90. NSArray *experiments = [_ABTCUPController experimentsWithOrigin:gABTTestOrigin];
  91. XCTAssertEqual(experiments.count, 1);
  92. payload.experimentId = @"exp_2";
  93. payload.variantId = @"v1";
  94. [_ABTCUPController
  95. setExperimentWithOrigin:gABTTestOrigin
  96. payload:payload
  97. events:events
  98. policy:ABTExperimentPayload_ExperimentOverflowPolicy_IgnoreNewest];
  99. experiments = [_ABTCUPController experimentsWithOrigin:gABTTestOrigin];
  100. XCTAssertEqual(experiments.count, 2);
  101. payload.experimentId = @"exp_3";
  102. payload.variantId = @"v1";
  103. [_ABTCUPController
  104. setExperimentWithOrigin:gABTTestOrigin
  105. payload:payload
  106. events:events
  107. policy:ABTExperimentPayload_ExperimentOverflowPolicy_IgnoreNewest];
  108. experiments = [_ABTCUPController experimentsWithOrigin:gABTTestOrigin];
  109. XCTAssertEqual(experiments.count, 3);
  110. // Now it's overflowed, try setting a new experiment exp_4.
  111. payload.experimentId = @"exp_4";
  112. payload.variantId = @"v1";
  113. // Try setting a new experiment with ignore newest policy.
  114. [_ABTCUPController
  115. setExperimentWithOrigin:gABTTestOrigin
  116. payload:payload
  117. events:events
  118. policy:ABTExperimentPayload_ExperimentOverflowPolicy_IgnoreNewest];
  119. experiments = [_ABTCUPController experimentsWithOrigin:gABTTestOrigin];
  120. XCTAssertEqual(experiments.count, 3);
  121. XCTAssertTrue([self isExperimentID:@"exp_1" variantID:@"v1" inExperiments:experiments]);
  122. XCTAssertTrue([self isExperimentID:@"exp_2" variantID:@"v1" inExperiments:experiments]);
  123. XCTAssertTrue([self isExperimentID:@"exp_3" variantID:@"v1" inExperiments:experiments]);
  124. XCTAssertFalse([self isExperimentID:@"exp_4" variantID:@"v1" inExperiments:experiments]);
  125. // Try setting a new experiment with discard oldest policy.
  126. [_ABTCUPController
  127. setExperimentWithOrigin:gABTTestOrigin
  128. payload:payload
  129. events:events
  130. policy:ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest];
  131. experiments = [_ABTCUPController experimentsWithOrigin:gABTTestOrigin];
  132. XCTAssertEqual(experiments.count, 3);
  133. XCTAssertFalse([self isExperimentID:@"exp_1" variantID:@"v1" inExperiments:experiments]);
  134. XCTAssertTrue([self isExperimentID:@"exp_4" variantID:@"v1" inExperiments:experiments]);
  135. // Try setting a new experiment with unspecified policy
  136. payload.experimentId = @"exp_5";
  137. payload.variantId = @"v1";
  138. [_ABTCUPController
  139. setExperimentWithOrigin:gABTTestOrigin
  140. payload:payload
  141. events:events
  142. policy:ABTExperimentPayload_ExperimentOverflowPolicy_PolicyUnspecified];
  143. experiments = [_ABTCUPController experimentsWithOrigin:gABTTestOrigin];
  144. XCTAssertEqual(experiments.count, 3);
  145. XCTAssertFalse([self isExperimentID:@"exp_2" variantID:@"v1" inExperiments:experiments]);
  146. XCTAssertTrue([self isExperimentID:@"exp_3" variantID:@"v1" inExperiments:experiments]);
  147. XCTAssertTrue([self isExperimentID:@"exp_4" variantID:@"v1" inExperiments:experiments]);
  148. XCTAssertTrue([self isExperimentID:@"exp_5" variantID:@"v1" inExperiments:experiments]);
  149. }
  150. - (void)testSetExperimentWithTheSameVariantID {
  151. ABTExperimentPayload *payload = [[ABTExperimentPayload alloc] init];
  152. payload.experimentId = @"exp_1";
  153. payload.variantId = @"v1";
  154. FIRLifecycleEvents *events = [[FIRLifecycleEvents alloc] init];
  155. [_ABTCUPController
  156. setExperimentWithOrigin:gABTTestOrigin
  157. payload:payload
  158. events:events
  159. policy:ABTExperimentPayload_ExperimentOverflowPolicy_IgnoreNewest];
  160. NSArray *experiments = [_ABTCUPController experimentsWithOrigin:gABTTestOrigin];
  161. XCTAssertEqual(experiments.count, 1);
  162. XCTAssertTrue([self isExperimentID:@"exp_1" variantID:@"v1" inExperiments:experiments]);
  163. payload.experimentId = @"exp_1";
  164. payload.variantId = @"v2";
  165. [_ABTCUPController
  166. setExperimentWithOrigin:gABTTestOrigin
  167. payload:payload
  168. events:events
  169. policy:ABTExperimentPayload_ExperimentOverflowPolicy_IgnoreNewest];
  170. experiments = [_ABTCUPController experimentsWithOrigin:gABTTestOrigin];
  171. XCTAssertEqual(experiments.count, 1);
  172. XCTAssertTrue([self isExperimentID:@"exp_1" variantID:@"v2" inExperiments:experiments]);
  173. }
  174. - (BOOL)isExperimentID:(NSString *)experimentID
  175. variantID:(NSString *)variantID
  176. inExperiments:(NSArray *)experiments {
  177. for (NSDictionary<NSString *, NSString *> *experiment in experiments) {
  178. if ([experiment[@"name"] isEqualToString:experimentID] &&
  179. [experiment[@"value"] isEqualToString:variantID]) {
  180. return YES;
  181. }
  182. }
  183. return NO;
  184. }
  185. - (void)testClearExperiment {
  186. ABTExperimentPayload *payload = [[ABTExperimentPayload alloc] init];
  187. payload.experimentId = @"exp_1";
  188. payload.variantId = @"v1";
  189. // TODO(chliang) to check this name is logged in scion.
  190. payload.clearEventToLog = @"override_clear_event";
  191. FIRLifecycleEvents *events = [[FIRLifecycleEvents alloc] init];
  192. [_ABTCUPController
  193. setExperimentWithOrigin:gABTTestOrigin
  194. payload:payload
  195. events:events
  196. policy:ABTExperimentPayload_ExperimentOverflowPolicy_IgnoreNewest];
  197. NSArray *experiments = [_ABTCUPController experimentsWithOrigin:gABTTestOrigin];
  198. XCTAssertEqual(experiments.count, 1);
  199. [_ABTCUPController clearExperiment:@"exp_1"
  200. variantID:@"v1"
  201. withOrigin:gABTTestOrigin
  202. payload:payload
  203. events:events];
  204. experiments = [_ABTCUPController experimentsWithOrigin:gABTTestOrigin];
  205. XCTAssertEqual(experiments.count, 0);
  206. }
  207. - (void)testMaxNumberOfExperiments {
  208. XCTAssertEqual([_ABTCUPController maxNumberOfExperimentsOfOrigin:gABTTestOrigin], 3);
  209. }
  210. - (void)testCreateExperiment {
  211. ABTExperimentPayload *payload = [[ABTExperimentPayload alloc] init];
  212. payload.experimentId = @"exp_1";
  213. payload.variantId = @"variant_B";
  214. NSTimeInterval now = [[NSDate date] timeIntervalSince1970];
  215. payload.experimentStartTimeMillis = now * ABT_MSEC_PER_SEC;
  216. payload.triggerEvent = @"";
  217. int64_t triggerTimeout = now + 1500;
  218. payload.triggerTimeoutMillis = triggerTimeout * ABT_MSEC_PER_SEC;
  219. payload.timeToLiveMillis = (now + 60000) * ABT_MSEC_PER_SEC;
  220. FIRLifecycleEvents *events = [[FIRLifecycleEvents alloc] init];
  221. events.activateExperimentEventName = @"_lifecycle_override_activate";
  222. events.expireExperimentEventName = @"lifecycle_override_time_to_live";
  223. NSDictionary<NSString *, id> *experiment =
  224. [_ABTCUPController createExperimentFromOrigin:gABTTestOrigin payload:payload events:events];
  225. NSDictionary<NSString *, id> *triggeredEvent = [experiment objectForKey:@"triggeredEvent"];
  226. XCTAssertEqualObjects([experiment objectForKey:@"name"], @"exp_1");
  227. XCTAssertEqualObjects([experiment objectForKey:@"value"], @"variant_B");
  228. XCTAssertEqualObjects(gABTTestOrigin, [experiment objectForKey:@"origin"]);
  229. XCTAssertEqualWithAccuracy(
  230. now, [(NSNumber *)[experiment objectForKey:@"creationTimestamp"] doubleValue], 1.0);
  231. // Trigger event
  232. XCTAssertEqualObjects(gABTTestOrigin, triggeredEvent[@"origin"]);
  233. XCTAssertEqualObjects(triggeredEvent[@"name"], @"_lifecycle_override_activate",
  234. @"Activate event name is overrided by lifecycle events.");
  235. // Timeout event
  236. NSDictionary<NSString *, id> *timedOutEvent = [experiment objectForKey:@"timedOutEvent"];
  237. XCTAssertEqualObjects(gABTTestOrigin, timedOutEvent[@"origin"]);
  238. XCTAssertEqualObjects(FIRTimeoutExperimentEventName, timedOutEvent[@"name"],
  239. @"payload doesn't have timeout event name, use default one");
  240. // Expired event
  241. NSDictionary<NSString *, id> *expiredEvent = [experiment objectForKey:@"expiredEvent"];
  242. XCTAssertEqualObjects(gABTTestOrigin, expiredEvent[@"origin"]);
  243. XCTAssertEqualObjects(
  244. @"lifecycle_override_time_to_live", expiredEvent[@"name"],
  245. @"payload doesn't have expiry event name, but lifecycle event does, use lifecycle event");
  246. // Trigger event name
  247. XCTAssertEqualObjects(nil, [experiment objectForKey:@"triggerEventName"],
  248. @"Empty trigger event must be set to nil");
  249. // trigger timeout
  250. XCTAssertEqualWithAccuracy(
  251. now + 1500, [(NSNumber *)([experiment objectForKey:@"triggerTimeout"]) doubleValue], 1.0);
  252. // time to live
  253. XCTAssertEqualWithAccuracy(
  254. now + 60000, [(NSNumber *)[experiment objectForKey:@"timeToLive"] doubleValue], 1.0);
  255. // Overwrite all event names
  256. payload.activateEventToLog = @"payload_override_activate";
  257. payload.ttlExpiryEventToLog = @"payload_override_time_to_live";
  258. payload.timeoutEventToLog = @"payload_override_timeout";
  259. payload.triggerEvent = @"payload_override_trigger_event";
  260. experiment = [_ABTCUPController createExperimentFromOrigin:gABTTestOrigin
  261. payload:payload
  262. events:events];
  263. triggeredEvent = [experiment objectForKey:@"triggeredEvent"];
  264. XCTAssertEqual(triggeredEvent[@"name"], @"payload_override_activate");
  265. timedOutEvent = [experiment objectForKey:@"timedOutEvent"];
  266. XCTAssertEqualObjects(timedOutEvent[@"name"], @"payload_override_timeout");
  267. expiredEvent = [experiment objectForKey:@"expiredEvent"];
  268. XCTAssertEqual(expiredEvent[@"name"], @"payload_override_time_to_live");
  269. XCTAssertEqual([experiment objectForKey:@"triggerEventName"], @"payload_override_trigger_event");
  270. }
  271. #pragma mark - helpers
  272. - (void)testIsExperimentTheSameAsPayload {
  273. NSDictionary<NSString *, NSString *> *experiment =
  274. @{@"name" : @"exp_1", @"value" : @"variant_control_group"};
  275. ABTExperimentPayload *payload = [[ABTExperimentPayload alloc] init];
  276. payload.experimentId = @"exp_2";
  277. payload.variantId = @"variant_group_A";
  278. XCTAssertFalse([_ABTCUPController isExperiment:experiment theSameAsPayload:payload]);
  279. payload.experimentId = @"exp_1";
  280. XCTAssertFalse([_ABTCUPController isExperiment:experiment theSameAsPayload:payload]);
  281. payload.variantId = @"variant_control_group";
  282. XCTAssertTrue([_ABTCUPController isExperiment:experiment theSameAsPayload:payload]);
  283. }
  284. - (void)testOverflowPolicyWithPayload {
  285. ABTExperimentPayload *payload = [[ABTExperimentPayload alloc] init];
  286. XCTAssertEqual(ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest,
  287. [_ABTCUPController overflowPolicyWithPayload:payload originalPolicy:-1000],
  288. @"Payload policy is unspecified, original policy is invalid, should return "
  289. @"default: DiscardOldest.");
  290. XCTAssertEqual(
  291. ABTExperimentPayload_ExperimentOverflowPolicy_IgnoreNewest,
  292. [_ABTCUPController
  293. overflowPolicyWithPayload:payload
  294. originalPolicy:ABTExperimentPayload_ExperimentOverflowPolicy_IgnoreNewest],
  295. @"Payload policy is unspecified, original policy is valid, use "
  296. @"original policy.");
  297. payload.overflowPolicy = ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest;
  298. XCTAssertEqual(
  299. ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest,
  300. [_ABTCUPController
  301. overflowPolicyWithPayload:payload
  302. originalPolicy:ABTExperimentPayload_ExperimentOverflowPolicy_IgnoreNewest],
  303. @"Payload policy is specified, original policy is valid, but "
  304. @"use Payload because Payload always wins.");
  305. }
  306. @end