FIRExperimentControllerTest.m 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545
  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 <FirebaseAnalyticsInterop/FIRAnalyticsInterop.h>
  18. #import <FirebaseCore/FIRAppInternal.h>
  19. #import <FirebaseCore/FIROptionsInternal.h>
  20. #import <OCMock/OCMock.h>
  21. #import "FirebaseABTesting/Sources/ABTConditionalUserPropertyController.h"
  22. #import "FirebaseABTesting/Sources/ABTConstants.h"
  23. #import "FirebaseABTesting/Tests/Unit/ABTFakeFIRAConditionalUserPropertyController.h"
  24. #import "FirebaseABTesting/Tests/Unit/ABTTestUniversalConstants.h"
  25. extern ABTExperimentPayload *ABTDeserializeExperimentPayload(NSData *payload);
  26. extern NSArray<ABTExperimentPayload *> *ABTExperimentsToSetFromPayloads(
  27. NSArray<NSData *> *payloads,
  28. NSArray<NSDictionary<NSString *, NSString *> *> *experiments,
  29. id<FIRAnalyticsInterop> _Nullable analytics);
  30. extern NSArray *ABTExperimentsToClearFromPayloads(
  31. NSArray<NSData *> *payloads,
  32. NSArray<NSDictionary<NSString *, NSString *> *> *experiments,
  33. id<FIRAnalyticsInterop> _Nullable analytics);
  34. @interface FIRExperimentController (ExposedForTest)
  35. - (void)
  36. updateExperimentConditionalUserPropertiesWithServiceOrigin:(NSString *)origin
  37. events:(FIRLifecycleEvents *)events
  38. policy:
  39. (ABTExperimentPayload_ExperimentOverflowPolicy)
  40. policy
  41. lastStartTime:(NSTimeInterval)lastStartTime
  42. payloads:(NSArray<NSData *> *)payloads
  43. completionHandler:
  44. (nullable void (^)(NSError *_Nullable error))
  45. completionHandler;
  46. /// Surface internal initializer to avoid singleton usage during tests.
  47. - (instancetype)initWithAnalytics:(nullable id<FIRAnalyticsInterop>)analytics;
  48. @end
  49. @interface ABTConditionalUserPropertyController (ExposedForTest)
  50. - (void)maxNumberOfExperimentsOfOrigin:(NSString *)origin
  51. completionHandler:(void (^)(int32_t))completionHandler;
  52. - (int32_t)maxNumberOfExperimentsOfOrigin:(NSString *)origin;
  53. - (id)createExperimentFromOrigin:(NSString *)origin
  54. payload:(ABTExperimentPayload *)payload
  55. events:(FIRLifecycleEvents *)events;
  56. - (ABTExperimentPayload_ExperimentOverflowPolicy)
  57. overflowPolicyWithPayload:(ABTExperimentPayload *)payload
  58. originalPolicy:(ABTExperimentPayload_ExperimentOverflowPolicy)originalPolicy;
  59. @end
  60. @interface FIRExperimentControllerTest : XCTestCase {
  61. FIRExperimentController *_experimentController;
  62. ABTFakeFIRAConditionalUserPropertyController *_fakeController;
  63. id _mockCUPController;
  64. }
  65. @end
  66. @implementation FIRExperimentControllerTest
  67. - (void)setUp {
  68. [super setUp];
  69. _fakeController = [ABTFakeFIRAConditionalUserPropertyController sharedInstance];
  70. id<FIRAnalyticsInterop> fakeAnalytics =
  71. [[FakeAnalytics alloc] initWithFakeController:_fakeController];
  72. _experimentController = [[FIRExperimentController alloc] initWithAnalytics:fakeAnalytics];
  73. ABTConditionalUserPropertyController *controller =
  74. [ABTConditionalUserPropertyController sharedInstanceWithAnalytics:fakeAnalytics];
  75. _mockCUPController = OCMPartialMock(controller);
  76. OCMStub([_mockCUPController maxNumberOfExperimentsOfOrigin:[OCMArg any]]).andReturn(3);
  77. }
  78. - (void)tearDown {
  79. [_fakeController resetExperiments];
  80. [_mockCUPController stopMocking];
  81. [super tearDown];
  82. }
  83. - (void)testDeserializeInvalidPayload {
  84. FIRExperimentController *controller = _experimentController;
  85. XCTAssertNotNil(controller);
  86. NSString *sampleString = @"sample_invalid_payload";
  87. NSData *invalidData = [sampleString dataUsingEncoding:NSUTF8StringEncoding];
  88. XCTAssertNil(ABTDeserializeExperimentPayload(invalidData));
  89. XCTAssertNotNil(ABTDeserializeExperimentPayload(nil));
  90. }
  91. - (void)testLifecycleEvents {
  92. FIRLifecycleEvents *events = [[FIRLifecycleEvents alloc] init];
  93. XCTAssertEqualObjects(FIRSetExperimentEventName, events.setExperimentEventName);
  94. XCTAssertEqualObjects(FIRActivateExperimentEventName, events.activateExperimentEventName);
  95. XCTAssertEqualObjects(FIRTimeoutExperimentEventName, events.timeoutExperimentEventName);
  96. XCTAssertEqualObjects(FIRExpireExperimentEventName, events.expireExperimentEventName);
  97. XCTAssertEqualObjects(FIRClearExperimentEventName, events.clearExperimentEventName);
  98. // Should be able to override event name values.
  99. events.setExperimentEventName = @"_new_set_experiment";
  100. XCTAssertEqualObjects(events.setExperimentEventName, @"_new_set_experiment");
  101. events.setExperimentEventName = @"name_without_prefix";
  102. XCTAssertEqualObjects(FIRSetExperimentEventName, events.setExperimentEventName);
  103. events.activateExperimentEventName = @"_new_activate_experiment";
  104. XCTAssertEqualObjects(events.activateExperimentEventName, @"_new_activate_experiment");
  105. events.activateExperimentEventName = @"";
  106. XCTAssertEqualObjects(FIRActivateExperimentEventName, events.activateExperimentEventName);
  107. events.timeoutExperimentEventName = @"__";
  108. XCTAssertEqualObjects(events.timeoutExperimentEventName, @"__");
  109. events.timeoutExperimentEventName = @"name_with_";
  110. XCTAssertEqualObjects(FIRTimeoutExperimentEventName, events.timeoutExperimentEventName);
  111. events.expireExperimentEventName = @"_";
  112. XCTAssertEqualObjects(events.expireExperimentEventName, @"_");
  113. #pragma clang diagnostic push
  114. #pragma clang diagnostic ignored "-Wnonnull"
  115. events.expireExperimentEventName = nil;
  116. #pragma clang diagnostic pop
  117. XCTAssertEqualObjects(FIRExpireExperimentEventName, events.expireExperimentEventName);
  118. events.clearExperimentEventName = @"_new_set_experiment";
  119. XCTAssertEqualObjects(events.clearExperimentEventName, @"_new_set_experiment");
  120. events.clearExperimentEventName = @"";
  121. XCTAssertEqualObjects(FIRClearExperimentEventName, events.clearExperimentEventName);
  122. }
  123. - (void)testSetExperimentWithBadPayload {
  124. [[_mockCUPController reject]
  125. setExperimentWithOrigin:[OCMArg any]
  126. payload:[OCMArg any]
  127. events:[OCMArg any]
  128. policy:ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest];
  129. NSString *sampleString = @"sample_invalid_payload";
  130. NSData *invalidData = [sampleString dataUsingEncoding:NSUTF8StringEncoding];
  131. XCTAssertNil(ABTDeserializeExperimentPayload(invalidData));
  132. }
  133. - (void)testUpdateExperiments {
  134. NSTimeInterval now = [[NSDate date] timeIntervalSince1970];
  135. ABTExperimentPayload *payload2 = [[ABTExperimentPayload alloc] init];
  136. payload2.experimentId = @"exp_2";
  137. payload2.variantId = @"v200";
  138. payload2.experimentStartTimeMillis =
  139. (now + 1500) * ABT_MSEC_PER_SEC; // start time > last start time, do set
  140. ABTExperimentLite *ongoingExperiment = [[ABTExperimentLite alloc] init];
  141. ongoingExperiment.experimentId = @"exp_1";
  142. [payload2.ongoingExperimentsArray addObject:ongoingExperiment];
  143. ABTExperimentPayload *payload3 = [[ABTExperimentPayload alloc] init];
  144. payload3.experimentId = @"exp_3";
  145. payload3.variantId = @"v200";
  146. payload3.experimentStartTimeMillis =
  147. (now + 900) * ABT_MSEC_PER_SEC; // start time > last start time, do set
  148. ongoingExperiment = [[ABTExperimentLite alloc] init];
  149. ongoingExperiment.experimentId = @"exp_2";
  150. [payload3.ongoingExperimentsArray addObject:ongoingExperiment];
  151. ABTExperimentPayload *payload4 = [[ABTExperimentPayload alloc] init];
  152. payload4.experimentId = @"exp_4";
  153. payload4.variantId = @"v200";
  154. payload4.experimentStartTimeMillis =
  155. (now - 900) * ABT_MSEC_PER_SEC; // start time < last start time, do not set.
  156. ongoingExperiment = [[ABTExperimentLite alloc] init];
  157. ongoingExperiment.experimentId = @"exp_2";
  158. [payload4.ongoingExperimentsArray addObject:ongoingExperiment];
  159. __block BOOL completionHandlerCalled = NO;
  160. FIRLifecycleEvents *events = [[FIRLifecycleEvents alloc] init];
  161. NSArray *payloads = @[ [payload2 data], [payload3 data], [payload4 data] ];
  162. [_experimentController
  163. updateExperimentConditionalUserPropertiesWithServiceOrigin:gABTTestOrigin
  164. events:events
  165. policy:
  166. ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest // NOLINT
  167. lastStartTime:now
  168. payloads:payloads
  169. completionHandler:^(NSError *_Nullable error) {
  170. completionHandlerCalled = YES;
  171. }];
  172. XCTAssertEqual([_mockCUPController experimentsWithOrigin:gABTTestOrigin].count, 2);
  173. XCTAssertTrue(completionHandlerCalled);
  174. // Second time update exp_1 no longer exist, should be cleared from experiments.
  175. payloads = @[ [payload3 data], [payload4 data] ];
  176. [_experimentController
  177. updateExperimentConditionalUserPropertiesWithServiceOrigin:gABTTestOrigin
  178. events:events
  179. policy:
  180. ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest // NOLINT
  181. lastStartTime:now
  182. payloads:payloads
  183. completionHandler:nil];
  184. XCTAssertEqual([_mockCUPController experimentsWithOrigin:gABTTestOrigin].count, 1);
  185. }
  186. - (void)testLatestExperimentStartTimestamps {
  187. // Mock incoming payloads
  188. NSMutableArray<NSData *> *payloads = [[NSMutableArray alloc] init];
  189. NSTimeInterval now = [[NSDate date] timeIntervalSince1970];
  190. ABTExperimentPayload *payload1 = [[ABTExperimentPayload alloc] init];
  191. payload1.experimentId = @"exp_1";
  192. payload1.variantId = @"v3";
  193. payload1.experimentStartTimeMillis = now * ABT_MSEC_PER_SEC;
  194. [payloads addObject:[payload1 data]];
  195. ABTExperimentPayload *payload2 = [[ABTExperimentPayload alloc] init];
  196. payload2.experimentId = @"exp_2";
  197. payload2.variantId = @"v2";
  198. payload2.experimentStartTimeMillis = (now + 500) * ABT_MSEC_PER_SEC;
  199. [payloads addObject:[payload2 data]];
  200. NSString *sampleString = @"sample_invalid_payload";
  201. NSData *invalidPayload = [sampleString dataUsingEncoding:NSUTF8StringEncoding];
  202. [payloads addObject:invalidPayload];
  203. XCTAssertEqualWithAccuracy(
  204. now + 500,
  205. [_experimentController latestExperimentStartTimestampBetweenTimestamp:now + 200
  206. andPayloads:payloads],
  207. 1);
  208. XCTAssertEqualWithAccuracy(
  209. now + 1000,
  210. [_experimentController latestExperimentStartTimestampBetweenTimestamp:now + 1000
  211. andPayloads:payloads],
  212. 1);
  213. XCTAssertEqualWithAccuracy(
  214. now + 500,
  215. [_experimentController latestExperimentStartTimestampBetweenTimestamp:now - 10000
  216. andPayloads:payloads],
  217. 1);
  218. }
  219. - (void)testExperimentsToSetFromPayloads {
  220. // Mock conditional user property objects in experiments.
  221. NSMutableArray *currentExperiments = [[NSMutableArray alloc] init];
  222. NSDictionary<NSString *, NSString *> *CUP1 = @{@"name" : @"exp_1", @"value" : @"v1"};
  223. [currentExperiments addObject:CUP1];
  224. NSDictionary<NSString *, NSString *> *CUP2 = @{@"name" : @"exp_2", @"value" : @"v2"};
  225. [currentExperiments addObject:CUP2];
  226. // Mock incoming payloads
  227. NSMutableArray<NSData *> *payloads = [[NSMutableArray alloc] init];
  228. ABTExperimentPayload *payload1 = [[ABTExperimentPayload alloc] init];
  229. payload1.experimentId = @"exp_1";
  230. payload1.variantId = @"v3";
  231. [payloads addObject:[payload1 data]];
  232. ABTExperimentPayload *payload2 = [[ABTExperimentPayload alloc] init];
  233. payload2.experimentId = @"exp_2";
  234. payload2.variantId = @"v2";
  235. [payloads addObject:[payload2 data]];
  236. NSString *sampleString = @"sample_invalid_payload";
  237. NSData *invalidPayload = [sampleString dataUsingEncoding:NSUTF8StringEncoding];
  238. [payloads addObject:invalidPayload];
  239. NSArray<ABTExperimentPayload *> *experimentsToSet =
  240. ABTExperimentsToSetFromPayloads(payloads, currentExperiments, nil);
  241. XCTAssertEqual(experimentsToSet.count, 1);
  242. ABTExperimentPayload *payloadToAdd = experimentsToSet.firstObject;
  243. XCTAssertEqualObjects(payloadToAdd.experimentId, @"exp_1");
  244. XCTAssertEqualObjects(payloadToAdd.variantId, @"v3");
  245. }
  246. - (void)testExperimentsToClearFromPaylods {
  247. // Mock conditional user property objects in experiments.
  248. NSMutableArray *currentExperiments = [[NSMutableArray alloc] init];
  249. NSDictionary<NSString *, NSString *> *CUP1 = @{@"name" : @"exp_1", @"value" : @"v1"};
  250. [currentExperiments addObject:CUP1];
  251. NSDictionary<NSString *, NSString *> *CUP2 = @{@"name" : @"exp_2", @"value" : @"v2"};
  252. [currentExperiments addObject:CUP2];
  253. // Mock incoming payloads
  254. NSMutableArray<NSData *> *payloads = [[NSMutableArray alloc] init];
  255. ABTExperimentPayload *payload1 = [[ABTExperimentPayload alloc] init];
  256. payload1.experimentId = @"exp_1";
  257. payload1.variantId = @"v3";
  258. [payloads addObject:[payload1 data]];
  259. ABTExperimentPayload *payload2 = [[ABTExperimentPayload alloc] init];
  260. payload2.experimentId = @"exp_2";
  261. payload2.variantId = @"v2";
  262. [payloads addObject:[payload2 data]];
  263. NSString *sampleString = @"sample_invalid_payload";
  264. NSData *invalidPayload = [sampleString dataUsingEncoding:NSUTF8StringEncoding];
  265. [payloads addObject:invalidPayload];
  266. NSArray<NSDictionary<NSString *, NSString *> *> *experimentsToClear =
  267. ABTExperimentsToClearFromPayloads(payloads, currentExperiments, nil);
  268. XCTAssertEqual(experimentsToClear.count, 1);
  269. NSDictionary<NSString *, NSString *> *experimentToRemove = experimentsToClear.firstObject;
  270. XCTAssertEqualObjects(experimentToRemove[@"name"], @"exp_1");
  271. XCTAssertEqualObjects(experimentToRemove[@"value"], @"v1");
  272. }
  273. - (void)testInvalidExperiments {
  274. [[_mockCUPController reject]
  275. setExperimentWithOrigin:[OCMArg any]
  276. payload:[OCMArg any]
  277. events:[OCMArg any]
  278. policy:ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest];
  279. [[_mockCUPController reject]
  280. setExperimentWithOrigin:[OCMArg any]
  281. payload:[OCMArg any]
  282. events:[OCMArg any]
  283. policy:ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest];
  284. OCMStub([_mockCUPController experimentsWithOrigin:gABTTestOrigin]).andReturn(nil);
  285. NSMutableArray<NSData *> *payloads = [[NSMutableArray alloc] init];
  286. __block BOOL completionHandlerWithErrorCalled = NO;
  287. FIRLifecycleEvents *events = [[FIRLifecycleEvents alloc] init];
  288. [_experimentController
  289. updateExperimentConditionalUserPropertiesWithServiceOrigin:gABTTestOrigin
  290. events:events
  291. policy:
  292. ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest // NOLINT
  293. lastStartTime:-1
  294. payloads:payloads
  295. completionHandler:^(NSError *_Nullable error) {
  296. if (error &&
  297. error.code ==
  298. kABTInternalErrorFailedToFetchConditionalUserProperties) {
  299. completionHandlerWithErrorCalled = YES;
  300. }
  301. }];
  302. // Verify completion handler is still called.
  303. XCTAssertTrue(completionHandlerWithErrorCalled);
  304. }
  305. - (void)testUpdateExperimentsWithNoCompletion {
  306. id experimentControllerMock = OCMPartialMock(_experimentController);
  307. NSString *mockOrigin = @"mockOrigin";
  308. FIRLifecycleEvents *mockLifecycleEvents = [[FIRLifecycleEvents alloc] init];
  309. ABTExperimentPayload_ExperimentOverflowPolicy mockOverflowPolicy =
  310. ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest;
  311. NSTimeInterval mockLastStartTime = 100;
  312. NSArray *mockPayloads = @[];
  313. [[experimentControllerMock expect] updateExperimentsWithServiceOrigin:mockOrigin
  314. events:mockLifecycleEvents
  315. policy:mockOverflowPolicy
  316. lastStartTime:mockLastStartTime
  317. payloads:mockPayloads
  318. completionHandler:nil];
  319. // Expect that updateExperimentsWithServiceOrigin:events:policy:lastStartTime:payloads: calls the
  320. // full method with completion handler as nil.
  321. #pragma clang diagnostic push
  322. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  323. [experimentControllerMock updateExperimentsWithServiceOrigin:mockOrigin
  324. events:mockLifecycleEvents
  325. policy:mockOverflowPolicy
  326. lastStartTime:mockLastStartTime
  327. payloads:mockPayloads];
  328. #pragma clang diagnostic pop
  329. [experimentControllerMock verify];
  330. }
  331. - (void)testValidateRunningExperimentsWithEmptyArray {
  332. NSTimeInterval now = [[NSDate date] timeIntervalSince1970];
  333. ABTExperimentPayload *payload2 = [[ABTExperimentPayload alloc] init];
  334. payload2.experimentId = @"exp_2";
  335. payload2.variantId = @"v200";
  336. payload2.experimentStartTimeMillis =
  337. (now + 1500) * ABT_MSEC_PER_SEC; // start time > last start time, do set
  338. ABTExperimentLite *ongoingExperiment = [[ABTExperimentLite alloc] init];
  339. ongoingExperiment.experimentId = @"exp_1";
  340. [payload2.ongoingExperimentsArray addObject:ongoingExperiment];
  341. ABTExperimentPayload *payload3 = [[ABTExperimentPayload alloc] init];
  342. payload3.experimentId = @"exp_3";
  343. payload3.variantId = @"v200";
  344. payload3.experimentStartTimeMillis =
  345. (now + 900) * ABT_MSEC_PER_SEC; // start time > last start time, do set
  346. ongoingExperiment = [[ABTExperimentLite alloc] init];
  347. ongoingExperiment.experimentId = @"exp_2";
  348. [payload3.ongoingExperimentsArray addObject:ongoingExperiment];
  349. FIRLifecycleEvents *events = [[FIRLifecycleEvents alloc] init];
  350. NSArray *payloads = @[ [payload2 data], [payload3 data] ];
  351. [_experimentController
  352. updateExperimentConditionalUserPropertiesWithServiceOrigin:gABTTestOrigin
  353. events:events
  354. policy:
  355. ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest // NOLINT
  356. lastStartTime:now
  357. payloads:payloads
  358. completionHandler:nil];
  359. XCTAssertEqual([_mockCUPController experimentsWithOrigin:gABTTestOrigin].count, 2);
  360. [_experimentController validateRunningExperimentsForServiceOrigin:gABTTestOrigin
  361. runningExperimentPayloads:[NSArray array]];
  362. // Expect all experiments have been cleared.
  363. XCTAssertEqual([_mockCUPController experimentsWithOrigin:gABTTestOrigin].count, 0);
  364. }
  365. - (void)testValidateRunningExperimentsClearingOne {
  366. NSTimeInterval now = [[NSDate date] timeIntervalSince1970];
  367. ABTExperimentPayload *payload2 = [[ABTExperimentPayload alloc] init];
  368. payload2.experimentId = @"exp_2";
  369. payload2.variantId = @"v200";
  370. payload2.experimentStartTimeMillis =
  371. (now + 1500) * ABT_MSEC_PER_SEC; // start time > last start time, do set
  372. ABTExperimentLite *ongoingExperiment = [[ABTExperimentLite alloc] init];
  373. ongoingExperiment.experimentId = @"exp_1";
  374. [payload2.ongoingExperimentsArray addObject:ongoingExperiment];
  375. ABTExperimentPayload *payload3 = [[ABTExperimentPayload alloc] init];
  376. payload3.experimentId = @"exp_3";
  377. payload3.variantId = @"v200";
  378. payload3.experimentStartTimeMillis =
  379. (now + 900) * ABT_MSEC_PER_SEC; // start time > last start time, do set
  380. ongoingExperiment = [[ABTExperimentLite alloc] init];
  381. ongoingExperiment.experimentId = @"exp_2";
  382. [payload3.ongoingExperimentsArray addObject:ongoingExperiment];
  383. FIRLifecycleEvents *events = [[FIRLifecycleEvents alloc] init];
  384. NSArray *payloads = @[ [payload2 data], [payload3 data] ];
  385. [_experimentController
  386. updateExperimentConditionalUserPropertiesWithServiceOrigin:gABTTestOrigin
  387. events:events
  388. policy:
  389. ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest // NOLINT
  390. lastStartTime:now
  391. payloads:payloads
  392. completionHandler:nil];
  393. XCTAssertEqual([_mockCUPController experimentsWithOrigin:gABTTestOrigin].count, 2);
  394. ABTExperimentPayload *validatingPayload2 = [[ABTExperimentPayload alloc] init];
  395. validatingPayload2.experimentId = @"exp_2";
  396. validatingPayload2.variantId = @"v200";
  397. [_experimentController validateRunningExperimentsForServiceOrigin:gABTTestOrigin
  398. runningExperimentPayloads:@[ validatingPayload2 ]];
  399. // Expect no experiments have been cleared.
  400. XCTAssertEqual([_mockCUPController experimentsWithOrigin:gABTTestOrigin].count, 1);
  401. }
  402. - (void)testValidateRunningExperimentsKeepingAll {
  403. NSTimeInterval now = [[NSDate date] timeIntervalSince1970];
  404. ABTExperimentPayload *payload2 = [[ABTExperimentPayload alloc] init];
  405. payload2.experimentId = @"exp_2";
  406. payload2.variantId = @"v200";
  407. payload2.experimentStartTimeMillis =
  408. (now + 1500) * ABT_MSEC_PER_SEC; // start time > last start time, do set
  409. ABTExperimentLite *ongoingExperiment = [[ABTExperimentLite alloc] init];
  410. ongoingExperiment.experimentId = @"exp_1";
  411. [payload2.ongoingExperimentsArray addObject:ongoingExperiment];
  412. ABTExperimentPayload *payload3 = [[ABTExperimentPayload alloc] init];
  413. payload3.experimentId = @"exp_3";
  414. payload3.variantId = @"v200";
  415. payload3.experimentStartTimeMillis =
  416. (now + 900) * ABT_MSEC_PER_SEC; // start time > last start time, do set
  417. ongoingExperiment = [[ABTExperimentLite alloc] init];
  418. ongoingExperiment.experimentId = @"exp_2";
  419. [payload3.ongoingExperimentsArray addObject:ongoingExperiment];
  420. FIRLifecycleEvents *events = [[FIRLifecycleEvents alloc] init];
  421. NSArray *payloads = @[ [payload2 data], [payload3 data] ];
  422. [_experimentController
  423. updateExperimentConditionalUserPropertiesWithServiceOrigin:gABTTestOrigin
  424. events:events
  425. policy:
  426. ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest // NOLINT
  427. lastStartTime:now
  428. payloads:payloads
  429. completionHandler:nil];
  430. XCTAssertEqual([_mockCUPController experimentsWithOrigin:gABTTestOrigin].count, 2);
  431. ABTExperimentPayload *validatingPayload2 = [[ABTExperimentPayload alloc] init];
  432. validatingPayload2.experimentId = @"exp_2";
  433. validatingPayload2.variantId = @"v200";
  434. ABTExperimentPayload *validatingPayload3 = [[ABTExperimentPayload alloc] init];
  435. validatingPayload3.experimentId = @"exp_3";
  436. validatingPayload3.variantId = @"v200";
  437. [_experimentController
  438. validateRunningExperimentsForServiceOrigin:gABTTestOrigin
  439. runningExperimentPayloads:@[ validatingPayload2, validatingPayload3 ]];
  440. // Expect no experiments have been cleared.
  441. XCTAssertEqual([_mockCUPController experimentsWithOrigin:gABTTestOrigin].count, 2);
  442. }
  443. - (void)testActivateExperiment {
  444. ABTExperimentPayload *activeExperiment = [[ABTExperimentPayload alloc] init];
  445. activeExperiment.experimentId = @"exp_3";
  446. activeExperiment.variantId = @"v200";
  447. activeExperiment.triggerEvent = @"trigger";
  448. [_experimentController activateExperiment:activeExperiment forServiceOrigin:gABTTestOrigin];
  449. NSArray *experiments = [_mockCUPController experimentsWithOrigin:gABTTestOrigin];
  450. FIRAConditionalUserProperty *userPropertyForExperiment = [experiments firstObject];
  451. // Verify that the triggerEventName is cleared, making this experiment active.
  452. XCTAssertNil([userPropertyForExperiment valueForKeyPath:@"triggerEventName"]);
  453. }
  454. @end