ABTConditionalUserPropertyControllerTest.m 16 KB

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