| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362 |
- // Copyright 2019 Google
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- #import <XCTest/XCTest.h>
- #import <FirebaseABTesting/FIRExperimentController.h>
- #import <FirebaseABTesting/FIRLifecycleEvents.h>
- #import <FirebaseCore/FIRAppInternal.h>
- #import <FirebaseCore/FIROptionsInternal.h>
- #import <OCMock/OCMock.h>
- #import "FirebaseABTesting/Sources/ABTConditionalUserPropertyController.h"
- #import "FirebaseABTesting/Sources/ABTConstants.h"
- #import "FirebaseABTesting/Tests/Unit/ABTFakeFIRAConditionalUserPropertyController.h"
- #import "FirebaseABTesting/Tests/Unit/ABTTestUniversalConstants.h"
- @interface ABTConditionalUserPropertyController (ExposedForTest)
- - (NSInteger)maxNumberOfExperimentsOfOrigin:(NSString *)origin;
- - (void)maxNumberOfExperimentsOfOrigin:(NSString *)origin
- completionHandler:(void (^)(int32_t))completionHandler;
- - (id)createExperimentFromOrigin:(NSString *)origin
- payload:(ABTExperimentPayload *)payload
- events:(FIRLifecycleEvents *)events;
- - (ABTExperimentPayload_ExperimentOverflowPolicy)
- overflowPolicyWithPayload:(ABTExperimentPayload *)payload
- originalPolicy:(ABTExperimentPayload_ExperimentOverflowPolicy)originalPolicy;
- /// Surface internal initializer to avoid singleton usage during tests.
- - (instancetype)initWithAnalytics:(nullable id<FIRAnalyticsInterop>)analytics;
- @end
- typedef void (^FakeAnalyticsLogEventWithOriginNameParametersHandler)(
- NSString *origin, NSString *name, NSDictionary<NSString *, id> *parameters);
- @interface ABTConditionalUserPropertyControllerTest : XCTestCase {
- ABTConditionalUserPropertyController *_ABTCUPController;
- ABTFakeFIRAConditionalUserPropertyController *_fakeController;
- id _mockCUPController;
- }
- @end
- @implementation ABTConditionalUserPropertyControllerTest
- - (void)setUp {
- [super setUp];
- _fakeController = [ABTFakeFIRAConditionalUserPropertyController sharedInstance];
- _ABTCUPController = [[ABTConditionalUserPropertyController alloc]
- initWithAnalytics:[[FakeAnalytics alloc] initWithFakeController:_fakeController]];
- _mockCUPController = OCMPartialMock(_ABTCUPController);
- OCMStub([_mockCUPController maxNumberOfExperimentsOfOrigin:[OCMArg any]]).andReturn(3);
- // Must initialize FIRApp before calling set experiment as Firebase Analytics internal event
- // logging requires it.
- NSDictionary *optionsDictionary = @{
- kFIRGoogleAppID : @"1:123456789012:ios:1234567890123456",
- @"GCM_SENDER_ID" : @"123456789012"
- };
- FIROptions *options = [[FIROptions alloc] initInternalWithOptionsDictionary:optionsDictionary];
- [FIRApp configureWithOptions:options];
- }
- - (void)tearDown {
- [_fakeController resetExperiments];
- [_mockCUPController stopMocking];
- [FIRApp resetApps];
- [super tearDown];
- }
- #pragma mark - test proxy methods on Firebase Analytics
- - (void)testSetExperiment {
- ABTExperimentPayload *payload = [[ABTExperimentPayload alloc] init];
- payload.experimentId = @"exp_0";
- FIRLifecycleEvents *events = [[FIRLifecycleEvents alloc] init];
- [_ABTCUPController
- setExperimentWithOrigin:gABTTestOrigin
- payload:payload
- events:events
- policy:ABTExperimentPayload_ExperimentOverflowPolicy_IgnoreNewest];
- NSArray *experiments = [_ABTCUPController experimentsWithOrigin:gABTTestOrigin];
- XCTAssertEqual(experiments.count, 1);
- }
- - (void)testSetExperimentWhenOverflow {
- ABTExperimentPayload *payload = [[ABTExperimentPayload alloc] init];
- payload.experimentId = @"exp_1";
- payload.variantId = @"v1";
- FIRLifecycleEvents *events = [[FIRLifecycleEvents alloc] init];
- [_ABTCUPController
- setExperimentWithOrigin:gABTTestOrigin
- payload:payload
- events:events
- policy:ABTExperimentPayload_ExperimentOverflowPolicy_IgnoreNewest];
- NSArray *experiments = [_ABTCUPController experimentsWithOrigin:gABTTestOrigin];
- XCTAssertEqual(experiments.count, 1);
- payload.experimentId = @"exp_2";
- payload.variantId = @"v1";
- [_ABTCUPController
- setExperimentWithOrigin:gABTTestOrigin
- payload:payload
- events:events
- policy:ABTExperimentPayload_ExperimentOverflowPolicy_IgnoreNewest];
- experiments = [_ABTCUPController experimentsWithOrigin:gABTTestOrigin];
- XCTAssertEqual(experiments.count, 2);
- payload.experimentId = @"exp_3";
- payload.variantId = @"v1";
- [_ABTCUPController
- setExperimentWithOrigin:gABTTestOrigin
- payload:payload
- events:events
- policy:ABTExperimentPayload_ExperimentOverflowPolicy_IgnoreNewest];
- experiments = [_ABTCUPController experimentsWithOrigin:gABTTestOrigin];
- XCTAssertEqual(experiments.count, 3);
- // Now it's overflowed, try setting a new experiment exp_4.
- payload.experimentId = @"exp_4";
- payload.variantId = @"v1";
- // Try setting a new experiment with ignore newest policy.
- [_ABTCUPController
- setExperimentWithOrigin:gABTTestOrigin
- payload:payload
- events:events
- policy:ABTExperimentPayload_ExperimentOverflowPolicy_IgnoreNewest];
- experiments = [_ABTCUPController experimentsWithOrigin:gABTTestOrigin];
- XCTAssertEqual(experiments.count, 3);
- XCTAssertTrue([self isExperimentID:@"exp_1" variantID:@"v1" inExperiments:experiments]);
- XCTAssertTrue([self isExperimentID:@"exp_2" variantID:@"v1" inExperiments:experiments]);
- XCTAssertTrue([self isExperimentID:@"exp_3" variantID:@"v1" inExperiments:experiments]);
- XCTAssertFalse([self isExperimentID:@"exp_4" variantID:@"v1" inExperiments:experiments]);
- // Try setting a new experiment with discard oldest policy.
- [_ABTCUPController
- setExperimentWithOrigin:gABTTestOrigin
- payload:payload
- events:events
- policy:ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest];
- experiments = [_ABTCUPController experimentsWithOrigin:gABTTestOrigin];
- XCTAssertEqual(experiments.count, 3);
- XCTAssertFalse([self isExperimentID:@"exp_1" variantID:@"v1" inExperiments:experiments]);
- XCTAssertTrue([self isExperimentID:@"exp_4" variantID:@"v1" inExperiments:experiments]);
- // Try setting a new experiment with unspecified policy
- payload.experimentId = @"exp_5";
- payload.variantId = @"v1";
- [_ABTCUPController
- setExperimentWithOrigin:gABTTestOrigin
- payload:payload
- events:events
- policy:ABTExperimentPayload_ExperimentOverflowPolicy_PolicyUnspecified];
- experiments = [_ABTCUPController experimentsWithOrigin:gABTTestOrigin];
- XCTAssertEqual(experiments.count, 3);
- XCTAssertFalse([self isExperimentID:@"exp_2" variantID:@"v1" inExperiments:experiments]);
- XCTAssertTrue([self isExperimentID:@"exp_3" variantID:@"v1" inExperiments:experiments]);
- XCTAssertTrue([self isExperimentID:@"exp_4" variantID:@"v1" inExperiments:experiments]);
- XCTAssertTrue([self isExperimentID:@"exp_5" variantID:@"v1" inExperiments:experiments]);
- }
- - (void)testSetExperimentWithTheSameVariantID {
- ABTExperimentPayload *payload = [[ABTExperimentPayload alloc] init];
- payload.experimentId = @"exp_1";
- payload.variantId = @"v1";
- FIRLifecycleEvents *events = [[FIRLifecycleEvents alloc] init];
- [_ABTCUPController
- setExperimentWithOrigin:gABTTestOrigin
- payload:payload
- events:events
- policy:ABTExperimentPayload_ExperimentOverflowPolicy_IgnoreNewest];
- NSArray *experiments = [_ABTCUPController experimentsWithOrigin:gABTTestOrigin];
- XCTAssertEqual(experiments.count, 1);
- XCTAssertTrue([self isExperimentID:@"exp_1" variantID:@"v1" inExperiments:experiments]);
- payload.experimentId = @"exp_1";
- payload.variantId = @"v2";
- [_ABTCUPController
- setExperimentWithOrigin:gABTTestOrigin
- payload:payload
- events:events
- policy:ABTExperimentPayload_ExperimentOverflowPolicy_IgnoreNewest];
- experiments = [_ABTCUPController experimentsWithOrigin:gABTTestOrigin];
- XCTAssertEqual(experiments.count, 1);
- XCTAssertTrue([self isExperimentID:@"exp_1" variantID:@"v2" inExperiments:experiments]);
- }
- - (BOOL)isExperimentID:(NSString *)experimentID
- variantID:(NSString *)variantID
- inExperiments:(NSArray *)experiments {
- for (NSDictionary<NSString *, NSString *> *experiment in experiments) {
- if ([experiment[@"name"] isEqualToString:experimentID] &&
- [experiment[@"value"] isEqualToString:variantID]) {
- return YES;
- }
- }
- return NO;
- }
- - (void)testClearExperiment {
- ABTExperimentPayload *payload = [[ABTExperimentPayload alloc] init];
- payload.experimentId = @"exp_1";
- payload.variantId = @"v1";
- // TODO(chliang) to check this name is logged in scion.
- payload.clearEventToLog = @"override_clear_event";
- FIRLifecycleEvents *events = [[FIRLifecycleEvents alloc] init];
- [_ABTCUPController
- setExperimentWithOrigin:gABTTestOrigin
- payload:payload
- events:events
- policy:ABTExperimentPayload_ExperimentOverflowPolicy_IgnoreNewest];
- NSArray *experiments = [_ABTCUPController experimentsWithOrigin:gABTTestOrigin];
- XCTAssertEqual(experiments.count, 1);
- [_ABTCUPController clearExperiment:@"exp_1"
- variantID:@"v1"
- withOrigin:gABTTestOrigin
- payload:payload
- events:events];
- experiments = [_ABTCUPController experimentsWithOrigin:gABTTestOrigin];
- XCTAssertEqual(experiments.count, 0);
- }
- - (void)testMaxNumberOfExperiments {
- XCTAssertEqual([_ABTCUPController maxNumberOfExperimentsOfOrigin:gABTTestOrigin], 3);
- }
- - (void)testCreateExperiment {
- ABTExperimentPayload *payload = [[ABTExperimentPayload alloc] init];
- payload.experimentId = @"exp_1";
- payload.variantId = @"variant_B";
- NSTimeInterval now = [[NSDate date] timeIntervalSince1970];
- payload.experimentStartTimeMillis = now * ABT_MSEC_PER_SEC;
- payload.triggerEvent = @"";
- int64_t triggerTimeout = now + 1500;
- payload.triggerTimeoutMillis = triggerTimeout * ABT_MSEC_PER_SEC;
- payload.timeToLiveMillis = (now + 60000) * ABT_MSEC_PER_SEC;
- FIRLifecycleEvents *events = [[FIRLifecycleEvents alloc] init];
- events.activateExperimentEventName = @"_lifecycle_override_activate";
- events.expireExperimentEventName = @"lifecycle_override_time_to_live";
- NSDictionary<NSString *, id> *experiment =
- [_ABTCUPController createExperimentFromOrigin:gABTTestOrigin payload:payload events:events];
- NSDictionary<NSString *, id> *triggeredEvent = [experiment objectForKey:@"triggeredEvent"];
- XCTAssertEqualObjects([experiment objectForKey:@"name"], @"exp_1");
- XCTAssertEqualObjects([experiment objectForKey:@"value"], @"variant_B");
- XCTAssertEqualObjects(gABTTestOrigin, [experiment objectForKey:@"origin"]);
- XCTAssertEqualWithAccuracy(
- now, [(NSNumber *)[experiment objectForKey:@"creationTimestamp"] doubleValue], 1.0);
- // Trigger event
- XCTAssertEqualObjects(gABTTestOrigin, triggeredEvent[@"origin"]);
- XCTAssertEqualObjects(triggeredEvent[@"name"], @"_lifecycle_override_activate",
- @"Activate event name is overrided by lifecycle events.");
- // Timeout event
- NSDictionary<NSString *, id> *timedOutEvent = [experiment objectForKey:@"timedOutEvent"];
- XCTAssertEqualObjects(gABTTestOrigin, timedOutEvent[@"origin"]);
- XCTAssertEqualObjects(FIRTimeoutExperimentEventName, timedOutEvent[@"name"],
- @"payload doesn't have timeout event name, use default one");
- // Expired event
- NSDictionary<NSString *, id> *expiredEvent = [experiment objectForKey:@"expiredEvent"];
- XCTAssertEqualObjects(gABTTestOrigin, expiredEvent[@"origin"]);
- XCTAssertEqualObjects(
- @"lifecycle_override_time_to_live", expiredEvent[@"name"],
- @"payload doesn't have expiry event name, but lifecycle event does, use lifecycle event");
- // Trigger event name
- XCTAssertEqualObjects(nil, [experiment objectForKey:@"triggerEventName"],
- @"Empty trigger event must be set to nil");
- // trigger timeout
- XCTAssertEqualWithAccuracy(
- now + 1500, [(NSNumber *)([experiment objectForKey:@"triggerTimeout"]) doubleValue], 1.0);
- // time to live
- XCTAssertEqualWithAccuracy(
- now + 60000, [(NSNumber *)[experiment objectForKey:@"timeToLive"] doubleValue], 1.0);
- // Overwrite all event names
- payload.activateEventToLog = @"payload_override_activate";
- payload.ttlExpiryEventToLog = @"payload_override_time_to_live";
- payload.timeoutEventToLog = @"payload_override_timeout";
- payload.triggerEvent = @"payload_override_trigger_event";
- experiment = [_ABTCUPController createExperimentFromOrigin:gABTTestOrigin
- payload:payload
- events:events];
- triggeredEvent = [experiment objectForKey:@"triggeredEvent"];
- XCTAssertEqual(triggeredEvent[@"name"], @"payload_override_activate");
- timedOutEvent = [experiment objectForKey:@"timedOutEvent"];
- XCTAssertEqualObjects(timedOutEvent[@"name"], @"payload_override_timeout");
- expiredEvent = [experiment objectForKey:@"expiredEvent"];
- XCTAssertEqual(expiredEvent[@"name"], @"payload_override_time_to_live");
- XCTAssertEqual([experiment objectForKey:@"triggerEventName"], @"payload_override_trigger_event");
- }
- #pragma mark - helpers
- - (void)testIsExperimentTheSameAsPayload {
- NSDictionary<NSString *, NSString *> *experiment =
- @{@"name" : @"exp_1", @"value" : @"variant_control_group"};
- ABTExperimentPayload *payload = [[ABTExperimentPayload alloc] init];
- payload.experimentId = @"exp_2";
- payload.variantId = @"variant_group_A";
- XCTAssertFalse([_ABTCUPController isExperiment:experiment theSameAsPayload:payload]);
- payload.experimentId = @"exp_1";
- XCTAssertFalse([_ABTCUPController isExperiment:experiment theSameAsPayload:payload]);
- payload.variantId = @"variant_control_group";
- XCTAssertTrue([_ABTCUPController isExperiment:experiment theSameAsPayload:payload]);
- }
- - (void)testOverflowPolicyWithPayload {
- ABTExperimentPayload *payload = [[ABTExperimentPayload alloc] init];
- XCTAssertEqual(ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest,
- [_ABTCUPController overflowPolicyWithPayload:payload originalPolicy:-1000],
- @"Payload policy is unspecified, original policy is invalid, should return "
- @"default: DiscardOldest.");
- XCTAssertEqual(
- ABTExperimentPayload_ExperimentOverflowPolicy_IgnoreNewest,
- [_ABTCUPController
- overflowPolicyWithPayload:payload
- originalPolicy:ABTExperimentPayload_ExperimentOverflowPolicy_IgnoreNewest],
- @"Payload policy is unspecified, original policy is valid, use "
- @"original policy.");
- payload.overflowPolicy = ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest;
- XCTAssertEqual(
- ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest,
- [_ABTCUPController
- overflowPolicyWithPayload:payload
- originalPolicy:ABTExperimentPayload_ExperimentOverflowPolicy_IgnoreNewest],
- @"Payload policy is specified, original policy is valid, but "
- @"use Payload because Payload always wins.");
- }
- @end
|