diff --git a/Firebase.podspec b/Firebase.podspec index b357a53bf0f..b05ec4223b5 100644 --- a/Firebase.podspec +++ b/Firebase.podspec @@ -61,7 +61,7 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'ABTesting' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseABTesting', '~> 3.3.0' + ss.dependency 'FirebaseABTesting', '~> 4.0.0' end s.subspec 'AdMob' do |ss| diff --git a/FirebaseABTesting.podspec b/FirebaseABTesting.podspec index 2379972f378..e671c7174b3 100644 --- a/FirebaseABTesting.podspec +++ b/FirebaseABTesting.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseABTesting' - s.version = '3.3.0' + s.version = '4.0.0' s.summary = 'Firebase ABTesting' s.description = <<-DESC @@ -35,20 +35,19 @@ Firebase Cloud Messaging and Firebase Remote Config in your app. 'FirebaseCore/Sources/Private/*.h', ] s.requires_arc = base_dir + '*.m' - s.public_header_files = base_dir + 'Public/*.h', base_dir + 'Protos/developers/mobile/abt/proto/*.h' - s.private_header_files = base_dir + 'Protos/developers/mobile/abt/proto/*.h' + s.public_header_files = base_dir + 'Public/*.h', base_dir + 'Private/*.h' + s.private_header_files = base_dir + 'Private/*.h' s.pod_target_xcconfig = { 'GCC_C_LANGUAGE_STANDARD' => 'c99', 'GCC_PREPROCESSOR_DEFINITIONS' => - 'GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS=1 ' + 'FIRABTesting_VERSION=' + String(s.version), 'HEADER_SEARCH_PATHS' => '"${PODS_TARGET_SRCROOT}"' } s.dependency 'FirebaseCore', '~> 6.8' - s.dependency 'Protobuf', '~> 3.9', '>= 3.9.2' s.test_spec 'unit' do |unit_tests| - unit_tests.source_files = 'FirebaseABTesting/Tests/Unit/*.[mh]' + unit_tests.source_files = 'FirebaseABTesting/Tests/Unit/**/*.[mh]' + unit_tests.resources = 'FirebaseABTesting/Tests/Unit/Resources/*.txt' unit_tests.requires_app_host = true unit_tests.dependency 'OCMock' end diff --git a/FirebaseABTesting/CHANGELOG.md b/FirebaseABTesting/CHANGELOG.md index 203b9d02f9b..4a046f9f7a5 100644 --- a/FirebaseABTesting/CHANGELOG.md +++ b/FirebaseABTesting/CHANGELOG.md @@ -1,3 +1,6 @@ +# v4.0.0 +- [changed] Removed Protobuf dependency (#5890). + # v3.2.0 - [added] Added completion handler for FIRExperimentController's updateExperimentsWithServiceOrigin method. - [deprecated] Deprecated `FIRExperimentController.updateExperiments(serviceOrigin:events:policy:lastStartTime:payloads:)`. diff --git a/FirebaseABTesting/Sources/ABTConditionalUserPropertyController.h b/FirebaseABTesting/Sources/ABTConditionalUserPropertyController.h index f022ff0607e..d4eb3fc4d57 100644 --- a/FirebaseABTesting/Sources/ABTConditionalUserPropertyController.h +++ b/FirebaseABTesting/Sources/ABTConditionalUserPropertyController.h @@ -12,10 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +#import #import -#import "FirebaseABTesting/Sources/Protos/developers/mobile/abt/proto/ExperimentPayload.pbobjc.h" - #import "Interop/Analytics/Public/FIRAnalyticsInterop.h" NS_ASSUME_NONNULL_BEGIN @@ -69,7 +68,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)setExperimentWithOrigin:(NSString *)origin payload:(ABTExperimentPayload *)payload events:(FIRLifecycleEvents *)events - policy:(ABTExperimentPayload_ExperimentOverflowPolicy)policy; + policy:(ABTExperimentPayloadExperimentOverflowPolicy)policy; /** * Unavailable. Use sharedInstanceWithAnalytics: instead. diff --git a/FirebaseABTesting/Sources/ABTConditionalUserPropertyController.m b/FirebaseABTesting/Sources/ABTConditionalUserPropertyController.m index abd2ee1787d..3475916ceb9 100644 --- a/FirebaseABTesting/Sources/ABTConditionalUserPropertyController.m +++ b/FirebaseABTesting/Sources/ABTConditionalUserPropertyController.m @@ -73,7 +73,7 @@ - (void)clearExperiment:(NSString *)experimentID - (void)setExperimentWithOrigin:(NSString *)origin payload:(ABTExperimentPayload *)payload events:(FIRLifecycleEvents *)events - policy:(ABTExperimentPayload_ExperimentOverflowPolicy)policy { + policy:(ABTExperimentPayloadExperimentOverflowPolicy)policy { NSInteger maxNumOfExperiments = [self maxNumberOfExperimentsOfOrigin:origin]; if (maxNumOfExperiments < 0) { return; @@ -88,10 +88,10 @@ - (void)setExperimentWithOrigin:(NSString *)origin } if (maxNumOfExperiments <= experiments.count) { - ABTExperimentPayload_ExperimentOverflowPolicy overflowPolicy = + ABTExperimentPayloadExperimentOverflowPolicy overflowPolicy = [self overflowPolicyWithPayload:payload originalPolicy:policy]; id experimentToClear = experiments.firstObject; - if (overflowPolicy == ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest && + if (overflowPolicy == ABTExperimentPayloadExperimentOverflowPolicyDiscardOldest && experimentToClear) { NSString *expID = [self experimentIDOfExperiment:experimentToClear]; NSString *varID = [self variantIDOfExperiment:experimentToClear]; @@ -265,17 +265,17 @@ - (BOOL)isExperiment:(id)experiment theSameAsPayload:(ABTExperimentPayload *)pay [variantID isEqualToString:payload.variantId]; } -- (ABTExperimentPayload_ExperimentOverflowPolicy) +- (ABTExperimentPayloadExperimentOverflowPolicy) overflowPolicyWithPayload:(ABTExperimentPayload *)payload - originalPolicy:(ABTExperimentPayload_ExperimentOverflowPolicy)originalPolicy { - if (payload.overflowPolicy != ABTExperimentPayload_ExperimentOverflowPolicy_PolicyUnspecified) { + originalPolicy:(ABTExperimentPayloadExperimentOverflowPolicy)originalPolicy { + if ([payload overflowPolicyIsValid]) { return payload.overflowPolicy; } - if (originalPolicy != ABTExperimentPayload_ExperimentOverflowPolicy_PolicyUnspecified && - ABTExperimentPayload_ExperimentOverflowPolicy_IsValidValue(originalPolicy)) { + if (originalPolicy == ABTExperimentPayloadExperimentOverflowPolicyIgnoreNewest || + originalPolicy == ABTExperimentPayloadExperimentOverflowPolicyDiscardOldest) { return originalPolicy; } - return ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest; + return ABTExperimentPayloadExperimentOverflowPolicyDiscardOldest; } @end diff --git a/FirebaseABTesting/Sources/ABTExperimentPayload.m b/FirebaseABTesting/Sources/ABTExperimentPayload.m new file mode 100644 index 00000000000..6a90d777e1b --- /dev/null +++ b/FirebaseABTesting/Sources/ABTExperimentPayload.m @@ -0,0 +1,151 @@ +// Copyright 2020 Google LLC +// +// 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 "ABTExperimentPayload.h" + +static NSString *const kExperimentPayloadKeyExperimentID = @"experimentId"; +static NSString *const kExperimentPayloadKeyVariantID = @"variantId"; + +// Start time can either be a date string or integer (milliseconds since 1970). +static NSString *const kExperimentPayloadKeyExperimentStartTime = @"experimentStartTime"; +static NSString *const kExperimentPayloadKeyExperimentStartTimeMillis = + @"experimentStartTimeMillis"; +static NSString *const kExperimentPayloadKeyTriggerEvent = @"triggerEvent"; +static NSString *const kExperimentPayloadKeyTriggerTimeoutMillis = @"triggerTimeoutMillis"; +static NSString *const kExperimentPayloadKeyTimeToLiveMillis = @"timeToLiveMillis"; +static NSString *const kExperimentPayloadKeySetEventToLog = @"setEventToLog"; +static NSString *const kExperimentPayloadKeyActivateEventToLog = @"activateEventToLog"; +static NSString *const kExperimentPayloadKeyClearEventToLog = @"clearEventToLog"; +static NSString *const kExperimentPayloadKeyTimeoutEventToLog = @"timeoutEventToLog"; +static NSString *const kExperimentPayloadKeyTTLExpiryEventToLog = @"ttlExpiryEventToLog"; + +static NSString *const kExperimentPayloadKeyOverflowPolicy = @"overflowPolicy"; +static NSString *const kExperimentPayloadValueDiscardOldestOverflowPolicy = @"DISCARD_OLDEST"; +static NSString *const kExperimentPayloadValueIgnoreNewestOverflowPolicy = @"IGNORE_NEWEST"; + +static NSString *const kExperimentPayloadKeyOngoingExperiments = @"ongoingExperiments"; + +@implementation ABTExperimentLite + +- (instancetype)initWithExperimentId:(NSString *)experimentId { + if (self = [super init]) { + _experimentId = experimentId; + } + return self; +} + +@end + +@implementation ABTExperimentPayload + ++ (NSDateFormatter *)experimentStartTimeFormatter { + NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; + [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"]; + [dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; + // Locale needs to be hardcoded. See + // https://developer.apple.com/library/ios/#qa/qa1480/_index.html for more details. + [dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]]; + [dateFormatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]]; + return dateFormatter; +} + ++ (instancetype)parseFromData:(NSData *)data { + NSError *error; + NSDictionary *experimentDictionary = + [NSJSONSerialization JSONObjectWithData:data + options:NSJSONReadingAllowFragments + error:&error]; + if (error != nil) { + return nil; + } else { + return [[ABTExperimentPayload alloc] initWithDictionary:experimentDictionary]; + } +} + +- (instancetype)initWithDictionary:(NSDictionary *)dictionary { + if (self = [super init]) { + _experimentId = dictionary[kExperimentPayloadKeyExperimentID]; + _variantId = dictionary[kExperimentPayloadKeyVariantID]; + _triggerEvent = dictionary[kExperimentPayloadKeyTriggerEvent]; + _setEventToLog = dictionary[kExperimentPayloadKeySetEventToLog]; + _activateEventToLog = dictionary[kExperimentPayloadKeyActivateEventToLog]; + _clearEventToLog = dictionary[kExperimentPayloadKeyClearEventToLog]; + _timeoutEventToLog = dictionary[kExperimentPayloadKeyTimeoutEventToLog]; + _ttlExpiryEventToLog = dictionary[kExperimentPayloadKeyTTLExpiryEventToLog]; + + // Experiment start time can either be in the form of a date string or milliseconds since 1970. + if (dictionary[kExperimentPayloadKeyExperimentStartTime]) { + // Convert from date string. + NSDate *experimentStartTime = [[[self class] experimentStartTimeFormatter] + dateFromString:dictionary[kExperimentPayloadKeyExperimentStartTime]]; + _experimentStartTimeMillis = + [@([experimentStartTime timeIntervalSince1970] * 1000) longLongValue]; + } else if (dictionary[kExperimentPayloadKeyExperimentStartTimeMillis]) { + // Simply store milliseconds. + _experimentStartTimeMillis = + [dictionary[kExperimentPayloadKeyExperimentStartTimeMillis] longLongValue]; + ; + } + + _triggerTimeoutMillis = [dictionary[kExperimentPayloadKeyTriggerTimeoutMillis] longLongValue]; + _timeToLiveMillis = [dictionary[kExperimentPayloadKeyTimeToLiveMillis] longLongValue]; + + // Overflow policy can be an integer, or string e.g. "DISCARD_OLDEST" or "IGNORE_NEWEST". + if ([dictionary[kExperimentPayloadKeyOverflowPolicy] isKindOfClass:[NSString class]]) { + // If it's a string, pick against the preset string values. + NSString *policy = dictionary[kExperimentPayloadKeyOverflowPolicy]; + if ([policy isEqualToString:kExperimentPayloadValueDiscardOldestOverflowPolicy]) { + _overflowPolicy = ABTExperimentPayloadExperimentOverflowPolicyDiscardOldest; + } else if ([policy isEqualToString:kExperimentPayloadValueIgnoreNewestOverflowPolicy]) { + _overflowPolicy = ABTExperimentPayloadExperimentOverflowPolicyIgnoreNewest; + } else { + _overflowPolicy = ABTExperimentPayloadExperimentOverflowPolicyUnrecognizedValue; + } + } else { + _overflowPolicy = [dictionary[kExperimentPayloadKeyOverflowPolicy] intValue]; + } + + NSMutableArray *ongoingExperiments = [[NSMutableArray alloc] init]; + + NSArray *> *ongoingExperimentsArray = + dictionary[kExperimentPayloadKeyOngoingExperiments]; + + for (NSDictionary *experimentDictionary in ongoingExperimentsArray) { + NSString *experimentId = experimentDictionary[kExperimentPayloadKeyExperimentID]; + if (experimentId) { + ABTExperimentLite *liteExperiment = + [[ABTExperimentLite alloc] initWithExperimentId:experimentId]; + [ongoingExperiments addObject:liteExperiment]; + } + } + + _ongoingExperiments = [ongoingExperiments copy]; + } + return self; +} + +- (void)clearTriggerEvent { + _triggerEvent = nil; +} + +- (BOOL)overflowPolicyIsValid { + return self.overflowPolicy == ABTExperimentPayloadExperimentOverflowPolicyIgnoreNewest || + self.overflowPolicy == ABTExperimentPayloadExperimentOverflowPolicyDiscardOldest; +} + +- (void)setOverflowPolicy:(ABTExperimentPayloadExperimentOverflowPolicy)overflowPolicy { + _overflowPolicy = overflowPolicy; +} + +@end diff --git a/FirebaseABTesting/Sources/FIRExperimentController.m b/FirebaseABTesting/Sources/FIRExperimentController.m index 176b343b860..c14a337e78f 100644 --- a/FirebaseABTesting/Sources/FIRExperimentController.m +++ b/FirebaseABTesting/Sources/FIRExperimentController.m @@ -14,6 +14,7 @@ #import +#import #import #import "FirebaseABTesting/Sources/ABTConditionalUserPropertyController.h" #import "FirebaseABTesting/Sources/ABTConstants.h" @@ -34,19 +35,19 @@ #define STR_EXPAND(x) #x /// Default experiment overflow policy. -const ABTExperimentPayload_ExperimentOverflowPolicy FIRDefaultExperimentOverflowPolicy = - ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest; +const ABTExperimentPayloadExperimentOverflowPolicy FIRDefaultExperimentOverflowPolicy = + ABTExperimentPayloadExperimentOverflowPolicyDiscardOldest; /// Deserialize the experiment payloads. ABTExperimentPayload *ABTDeserializeExperimentPayload(NSData *payload) { + // Verify that we have a JSON object. NSError *error; - ABTExperimentPayload *experimentPayload = [ABTExperimentPayload parseFromData:payload - error:&error]; - if (error) { + id JSONObject = [NSJSONSerialization JSONObjectWithData:payload options:kNilOptions error:&error]; + if (JSONObject == nil) { FIRLogError(kFIRLoggerABTesting, @"I-ABT000001", @"Failed to parse experiment payload: %@", error.debugDescription); } - return experimentPayload; + return [ABTExperimentPayload parseFromData:payload]; } /// Returns a list of experiments to be set given the payloads and current list of experiments from @@ -173,7 +174,7 @@ + (FIRExperimentController *)sharedInstance { - (void)updateExperimentsWithServiceOrigin:(NSString *)origin events:(FIRLifecycleEvents *)events - policy:(ABTExperimentPayload_ExperimentOverflowPolicy)policy + policy:(ABTExperimentPayloadExperimentOverflowPolicy)policy lastStartTime:(NSTimeInterval)lastStartTime payloads:(NSArray *)payloads completionHandler: @@ -192,7 +193,7 @@ - (void)updateExperimentsWithServiceOrigin:(NSString *)origin - (void)updateExperimentsWithServiceOrigin:(NSString *)origin events:(FIRLifecycleEvents *)events - policy:(ABTExperimentPayload_ExperimentOverflowPolicy)policy + policy:(ABTExperimentPayloadExperimentOverflowPolicy)policy lastStartTime:(NSTimeInterval)lastStartTime payloads:(NSArray *)payloads { [self updateExperimentsWithServiceOrigin:origin @@ -207,7 +208,7 @@ - (void)updateExperimentsWithServiceOrigin:(NSString *)origin updateExperimentConditionalUserPropertiesWithServiceOrigin:(NSString *)origin events:(FIRLifecycleEvents *)events policy: - (ABTExperimentPayload_ExperimentOverflowPolicy) + (ABTExperimentPayloadExperimentOverflowPolicy) policy lastStartTime:(NSTimeInterval)lastStartTime payloads:(NSArray *)payloads @@ -325,7 +326,7 @@ - (void)activateExperiment:(ABTExperimentPayload *)experimentPayload FIRLifecycleEvents *lifecycleEvents = [[FIRLifecycleEvents alloc] init]; // Ensure that trigger event is nil, which will immediately set the experiment to active. - experimentPayload.triggerEvent = nil; + [experimentPayload clearTriggerEvent]; [controller setExperimentWithOrigin:origin payload:experimentPayload diff --git a/FirebaseABTesting/Sources/Private/ABTExperimentPayload.h b/FirebaseABTesting/Sources/Private/ABTExperimentPayload.h new file mode 100644 index 00000000000..f550a0a05c2 --- /dev/null +++ b/FirebaseABTesting/Sources/Private/ABTExperimentPayload.h @@ -0,0 +1,96 @@ +// Copyright 2020 Google LLC +// +// 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 + +NS_ASSUME_NONNULL_BEGIN + +/// Policy for handling the case where there's an overflow of experiments for an installation +/// instance. +typedef NS_ENUM(int32_t, ABTExperimentPayloadExperimentOverflowPolicy) { + ABTExperimentPayloadExperimentOverflowPolicyUnrecognizedValue = 999, + ABTExperimentPayloadExperimentOverflowPolicyUnspecified = 0, + ABTExperimentPayloadExperimentOverflowPolicyDiscardOldest = 1, + ABTExperimentPayloadExperimentOverflowPolicyIgnoreNewest = 2, +}; + +@interface ABTExperimentLite : NSObject +@property(nonatomic, readonly, copy) NSString *experimentId; + +- (instancetype)initWithExperimentId:(NSString *)experimentId NS_DESIGNATED_INITIALIZER; + +- (instancetype)init NS_UNAVAILABLE; + +@end + +@interface ABTExperimentPayload : NSObject + +/// Unique identifier for this experiment. +@property(nonatomic, readonly, copy) NSString *experimentId; + +/// Unique identifier for the variant to which an installation instance has been assigned. +@property(nonatomic, readonly, copy) NSString *variantId; + +/// Epoch time that represents when the experiment was started. +@property(nonatomic, readonly) int64_t experimentStartTimeMillis; + +/// The event that triggers this experiment into ON state. +@property(nonatomic, nullable, readonly, copy) NSString *triggerEvent; + +/// Duration in milliseconds for which the experiment can stay in STANDBY state (un-triggered). +@property(nonatomic, readonly) int64_t triggerTimeoutMillis; + +/// Duration in milliseconds for which the experiment can stay in ON state (triggered). +@property(nonatomic, readonly) int64_t timeToLiveMillis; + +/// The event logged when impact service sets the experiment. +@property(nonatomic, readonly, copy) NSString *setEventToLog; + +/// The event logged when an experiment goes to the ON state. +@property(nonatomic, readonly, copy) NSString *activateEventToLog; + +/// The event logged when an experiment is cleared. +@property(nonatomic, readonly, copy) NSString *clearEventToLog; + +/// The event logged when an experiment times out after `triggerTimeoutMillis` milliseconds. +@property(nonatomic, readonly, copy) NSString *timeoutEventToLog; + +/// The event logged when an experiment times out after `timeToLiveMillis` milliseconds. +@property(nonatomic, readonly, copy) NSString *ttlExpiryEventToLog; + +@property(nonatomic, readonly) ABTExperimentPayloadExperimentOverflowPolicy overflowPolicy; + +/// A list of all other ongoing (started, and not yet stopped) experiments at the time this +/// experiment was started. Does not include this experiment; only the others. +@property(nonatomic, readonly) NSArray *ongoingExperiments; + +/// Parses an ABTExperimentPayload directly from JSON data. +/// @param data JSON object as NSData. Must be reconstructible as an NSDictionary. ++ (instancetype)parseFromData:(NSData *)data; + +/// Initializes an ABTExperimentPayload from a dictionary with experiment metadata. +- (instancetype)initWithDictionary:(NSDictionary *)dictionary + NS_DESIGNATED_INITIALIZER; + +- (instancetype)init NS_UNAVAILABLE; + +/// Clears the trigger event associated with this payload. +- (void)clearTriggerEvent; + +/// Checks if the overflow policy is a valid enum object. +- (BOOL)overflowPolicyIsValid; + +@end + +NS_ASSUME_NONNULL_END diff --git a/FirebaseABTesting/Sources/Protos/PortableProtoFilterTemplate.asciipb b/FirebaseABTesting/Sources/Protos/PortableProtoFilterTemplate.asciipb deleted file mode 100644 index 5e858c58d91..00000000000 --- a/FirebaseABTesting/Sources/Protos/PortableProtoFilterTemplate.asciipb +++ /dev/null @@ -1,3 +0,0 @@ -allowed_message: "developers.mobile.abt.ExperimentLite" -allowed_message: "developers.mobile.abt.ExperimentPayload" -allowed_enum: "developers.mobile.abt.ExperimentPayload.ExperimentOverflowPolicy" diff --git a/FirebaseABTesting/Sources/Protos/developers/mobile/abt/proto/ExperimentPayload.pbobjc.h b/FirebaseABTesting/Sources/Protos/developers/mobile/abt/proto/ExperimentPayload.pbobjc.h deleted file mode 100644 index 3fba2c4a367..00000000000 --- a/FirebaseABTesting/Sources/Protos/developers/mobile/abt/proto/ExperimentPayload.pbobjc.h +++ /dev/null @@ -1,179 +0,0 @@ -// 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. - -// Generated by the protocol buffer compiler. DO NOT EDIT! -// source: developers/mobile/abt/proto/experiment_payload.proto - -// This CPP symbol can be defined to use imports that match up to the framework -// imports needed when using CocoaPods. -#if !defined(GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS) - #define GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS 0 -#endif - -#if GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS - #import -#else - #import "GPBProtocolBuffers.h" -#endif - -#if GOOGLE_PROTOBUF_OBJC_VERSION < 30002 -#error This file was generated by a newer version of protoc which is incompatible with your Protocol Buffer library sources. -#endif -#if 30002 < GOOGLE_PROTOBUF_OBJC_MIN_SUPPORTED_VERSION -#error This file was generated by an older version of protoc which is incompatible with your Protocol Buffer library sources. -#endif - -// @@protoc_insertion_point(imports) - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - -CF_EXTERN_C_BEGIN - -@class ABTExperimentLite; - -NS_ASSUME_NONNULL_BEGIN - -#pragma mark - Enum ABTExperimentPayload_ExperimentOverflowPolicy - -typedef GPB_ENUM(ABTExperimentPayload_ExperimentOverflowPolicy) { - /** - * Value used if any message's field encounters a value that is not defined - * by this enum. The message will also have C functions to get/set the rawValue - * of the field. - **/ - ABTExperimentPayload_ExperimentOverflowPolicy_GPBUnrecognizedEnumeratorValue = kGPBUnrecognizedEnumeratorValue, - ABTExperimentPayload_ExperimentOverflowPolicy_PolicyUnspecified = 0, - ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest = 1, - ABTExperimentPayload_ExperimentOverflowPolicy_IgnoreNewest = 2, -}; - -GPBEnumDescriptor *ABTExperimentPayload_ExperimentOverflowPolicy_EnumDescriptor(void); - -/** - * Checks to see if the given value is defined by the enum or was not known at - * the time this source was generated. - **/ -BOOL ABTExperimentPayload_ExperimentOverflowPolicy_IsValidValue(int32_t value); - -#pragma mark - ABTExperimentPayloadRoot - -/** - * Exposes the extension registry for this file. - * - * The base class provides: - * @code - * + (GPBExtensionRegistry *)extensionRegistry; - * @endcode - * which is a @c GPBExtensionRegistry that includes all the extensions defined by - * this file and all files that it depends on. - **/ -@interface ABTExperimentPayloadRoot : GPBRootObject -@end - -#pragma mark - ABTExperimentLite - -typedef GPB_ENUM(ABTExperimentLite_FieldNumber) { - ABTExperimentLite_FieldNumber_ExperimentId = 1, -}; - -@interface ABTExperimentLite : GPBMessage - - -@property(nonatomic, readwrite, copy, null_resettable) NSString *experimentId; - -@end - -#pragma mark - ABTExperimentPayload - -typedef GPB_ENUM(ABTExperimentPayload_FieldNumber) { - ABTExperimentPayload_FieldNumber_ExperimentId = 1, - ABTExperimentPayload_FieldNumber_VariantId = 2, - ABTExperimentPayload_FieldNumber_ExperimentStartTimeMillis = 3, - ABTExperimentPayload_FieldNumber_TriggerEvent = 4, - ABTExperimentPayload_FieldNumber_TriggerTimeoutMillis = 5, - ABTExperimentPayload_FieldNumber_TimeToLiveMillis = 6, - ABTExperimentPayload_FieldNumber_SetEventToLog = 7, - ABTExperimentPayload_FieldNumber_ActivateEventToLog = 8, - ABTExperimentPayload_FieldNumber_ClearEventToLog = 9, - ABTExperimentPayload_FieldNumber_TimeoutEventToLog = 10, - ABTExperimentPayload_FieldNumber_TtlExpiryEventToLog = 11, - ABTExperimentPayload_FieldNumber_OverflowPolicy = 12, - ABTExperimentPayload_FieldNumber_OngoingExperimentsArray = 13, -}; - -@interface ABTExperimentPayload : GPBMessage - - -@property(nonatomic, readwrite, copy, null_resettable) NSString *experimentId; - - -@property(nonatomic, readwrite, copy, null_resettable) NSString *variantId; - - -@property(nonatomic, readwrite) int64_t experimentStartTimeMillis; - - -@property(nonatomic, readwrite, copy, null_resettable) NSString *triggerEvent; - - -@property(nonatomic, readwrite) int64_t triggerTimeoutMillis; - - -@property(nonatomic, readwrite) int64_t timeToLiveMillis; - - -@property(nonatomic, readwrite, copy, null_resettable) NSString *setEventToLog; - - -@property(nonatomic, readwrite, copy, null_resettable) NSString *activateEventToLog; - - -@property(nonatomic, readwrite, copy, null_resettable) NSString *clearEventToLog; - - -@property(nonatomic, readwrite, copy, null_resettable) NSString *timeoutEventToLog; - - -@property(nonatomic, readwrite, copy, null_resettable) NSString *ttlExpiryEventToLog; - - -@property(nonatomic, readwrite) ABTExperimentPayload_ExperimentOverflowPolicy overflowPolicy; - - -@property(nonatomic, readwrite, strong, null_resettable) NSMutableArray *ongoingExperimentsArray; -/** The number of items in @c ongoingExperimentsArray without causing the array to be created. */ -@property(nonatomic, readonly) NSUInteger ongoingExperimentsArray_Count; - -@end - -/** - * Fetches the raw value of a @c ABTExperimentPayload's @c overflowPolicy property, even - * if the value was not defined by the enum at the time the code was generated. - **/ -int32_t ABTExperimentPayload_OverflowPolicy_RawValue(ABTExperimentPayload *message); -/** - * Sets the raw value of an @c ABTExperimentPayload's @c overflowPolicy property, allowing - * it to be set to a value that was not defined by the enum at the time the code - * was generated. - **/ -void SetABTExperimentPayload_OverflowPolicy_RawValue(ABTExperimentPayload *message, int32_t value); - -NS_ASSUME_NONNULL_END - -CF_EXTERN_C_END - -#pragma clang diagnostic pop - -// @@protoc_insertion_point(global_scope) diff --git a/FirebaseABTesting/Sources/Protos/developers/mobile/abt/proto/ExperimentPayload.pbobjc.m b/FirebaseABTesting/Sources/Protos/developers/mobile/abt/proto/ExperimentPayload.pbobjc.m deleted file mode 100644 index 58192931446..00000000000 --- a/FirebaseABTesting/Sources/Protos/developers/mobile/abt/proto/ExperimentPayload.pbobjc.m +++ /dev/null @@ -1,330 +0,0 @@ -// 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. - -// Generated by the protocol buffer compiler. DO NOT EDIT! -// source: developers/mobile/abt/proto/experiment_payload.proto - -// This CPP symbol can be defined to use imports that match up to the framework -// imports needed when using CocoaPods. -#if !defined(GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS) - #define GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS 0 -#endif - -#if GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS - #import -#else - #import "GPBProtocolBuffers_RuntimeSupport.h" -#endif - - #import "FirebaseABTesting/Sources/Protos/developers/mobile/abt/proto/ExperimentPayload.pbobjc.h" -// @@protoc_insertion_point(imports) - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - -#pragma mark - ABTExperimentPayloadRoot - -@implementation ABTExperimentPayloadRoot - -// No extensions in the file and no imports, so no need to generate -// +extensionRegistry. - -@end - -#pragma mark - ABTExperimentPayloadRoot_FileDescriptor - -static GPBFileDescriptor *ABTExperimentPayloadRoot_FileDescriptor(void) { - // This is called by +initialize so there is no need to worry - // about thread safety of the singleton. - static GPBFileDescriptor *descriptor = NULL; - if (!descriptor) { - GPB_DEBUG_CHECK_RUNTIME_VERSIONS(); - descriptor = [[GPBFileDescriptor alloc] initWithPackage:@"developers.mobile.abt" - objcPrefix:@"ABT" - syntax:GPBFileSyntaxProto3]; - } - return descriptor; -} - -#pragma mark - ABTExperimentLite - -@implementation ABTExperimentLite - -@dynamic experimentId; - -typedef struct ABTExperimentLite__storage_ { - uint32_t _has_storage_[1]; - NSString *experimentId; -} ABTExperimentLite__storage_; - -// This method is threadsafe because it is initially called -// in +initialize for each subclass. -+ (GPBDescriptor *)descriptor { - static GPBDescriptor *descriptor = nil; - if (!descriptor) { - static GPBMessageFieldDescription fields[] = { - { - .name = "experimentId", - .dataTypeSpecific.className = NULL, - .number = ABTExperimentLite_FieldNumber_ExperimentId, - .hasIndex = 0, - .offset = (uint32_t)offsetof(ABTExperimentLite__storage_, experimentId), - .flags = GPBFieldOptional, - .dataType = GPBDataTypeString, - }, - }; - GPBDescriptor *localDescriptor = - [GPBDescriptor allocDescriptorForClass:[ABTExperimentLite class] - rootClass:[ABTExperimentPayloadRoot class] - file:ABTExperimentPayloadRoot_FileDescriptor() - fields:fields - fieldCount:(uint32_t)(sizeof(fields) / sizeof(GPBMessageFieldDescription)) - storageSize:sizeof(ABTExperimentLite__storage_) - flags:GPBDescriptorInitializationFlag_None]; - NSAssert(descriptor == nil, @"Startup recursed!"); - descriptor = localDescriptor; - } - return descriptor; -} - -@end - -#pragma mark - ABTExperimentPayload - -@implementation ABTExperimentPayload - -@dynamic experimentId; -@dynamic variantId; -@dynamic experimentStartTimeMillis; -@dynamic triggerEvent; -@dynamic triggerTimeoutMillis; -@dynamic timeToLiveMillis; -@dynamic setEventToLog; -@dynamic activateEventToLog; -@dynamic clearEventToLog; -@dynamic timeoutEventToLog; -@dynamic ttlExpiryEventToLog; -@dynamic overflowPolicy; -@dynamic ongoingExperimentsArray, ongoingExperimentsArray_Count; - -typedef struct ABTExperimentPayload__storage_ { - uint32_t _has_storage_[1]; - ABTExperimentPayload_ExperimentOverflowPolicy overflowPolicy; - NSString *experimentId; - NSString *variantId; - NSString *triggerEvent; - NSString *setEventToLog; - NSString *activateEventToLog; - NSString *clearEventToLog; - NSString *timeoutEventToLog; - NSString *ttlExpiryEventToLog; - NSMutableArray *ongoingExperimentsArray; - int64_t experimentStartTimeMillis; - int64_t triggerTimeoutMillis; - int64_t timeToLiveMillis; -} ABTExperimentPayload__storage_; - -// This method is threadsafe because it is initially called -// in +initialize for each subclass. -+ (GPBDescriptor *)descriptor { - static GPBDescriptor *descriptor = nil; - if (!descriptor) { - static GPBMessageFieldDescription fields[] = { - { - .name = "experimentId", - .dataTypeSpecific.className = NULL, - .number = ABTExperimentPayload_FieldNumber_ExperimentId, - .hasIndex = 0, - .offset = (uint32_t)offsetof(ABTExperimentPayload__storage_, experimentId), - .flags = GPBFieldOptional, - .dataType = GPBDataTypeString, - }, - { - .name = "variantId", - .dataTypeSpecific.className = NULL, - .number = ABTExperimentPayload_FieldNumber_VariantId, - .hasIndex = 1, - .offset = (uint32_t)offsetof(ABTExperimentPayload__storage_, variantId), - .flags = GPBFieldOptional, - .dataType = GPBDataTypeString, - }, - { - .name = "experimentStartTimeMillis", - .dataTypeSpecific.className = NULL, - .number = ABTExperimentPayload_FieldNumber_ExperimentStartTimeMillis, - .hasIndex = 2, - .offset = (uint32_t)offsetof(ABTExperimentPayload__storage_, experimentStartTimeMillis), - .flags = GPBFieldOptional, - .dataType = GPBDataTypeInt64, - }, - { - .name = "triggerEvent", - .dataTypeSpecific.className = NULL, - .number = ABTExperimentPayload_FieldNumber_TriggerEvent, - .hasIndex = 3, - .offset = (uint32_t)offsetof(ABTExperimentPayload__storage_, triggerEvent), - .flags = GPBFieldOptional, - .dataType = GPBDataTypeString, - }, - { - .name = "triggerTimeoutMillis", - .dataTypeSpecific.className = NULL, - .number = ABTExperimentPayload_FieldNumber_TriggerTimeoutMillis, - .hasIndex = 4, - .offset = (uint32_t)offsetof(ABTExperimentPayload__storage_, triggerTimeoutMillis), - .flags = GPBFieldOptional, - .dataType = GPBDataTypeInt64, - }, - { - .name = "timeToLiveMillis", - .dataTypeSpecific.className = NULL, - .number = ABTExperimentPayload_FieldNumber_TimeToLiveMillis, - .hasIndex = 5, - .offset = (uint32_t)offsetof(ABTExperimentPayload__storage_, timeToLiveMillis), - .flags = GPBFieldOptional, - .dataType = GPBDataTypeInt64, - }, - { - .name = "setEventToLog", - .dataTypeSpecific.className = NULL, - .number = ABTExperimentPayload_FieldNumber_SetEventToLog, - .hasIndex = 6, - .offset = (uint32_t)offsetof(ABTExperimentPayload__storage_, setEventToLog), - .flags = GPBFieldOptional, - .dataType = GPBDataTypeString, - }, - { - .name = "activateEventToLog", - .dataTypeSpecific.className = NULL, - .number = ABTExperimentPayload_FieldNumber_ActivateEventToLog, - .hasIndex = 7, - .offset = (uint32_t)offsetof(ABTExperimentPayload__storage_, activateEventToLog), - .flags = GPBFieldOptional, - .dataType = GPBDataTypeString, - }, - { - .name = "clearEventToLog", - .dataTypeSpecific.className = NULL, - .number = ABTExperimentPayload_FieldNumber_ClearEventToLog, - .hasIndex = 8, - .offset = (uint32_t)offsetof(ABTExperimentPayload__storage_, clearEventToLog), - .flags = GPBFieldOptional, - .dataType = GPBDataTypeString, - }, - { - .name = "timeoutEventToLog", - .dataTypeSpecific.className = NULL, - .number = ABTExperimentPayload_FieldNumber_TimeoutEventToLog, - .hasIndex = 9, - .offset = (uint32_t)offsetof(ABTExperimentPayload__storage_, timeoutEventToLog), - .flags = GPBFieldOptional, - .dataType = GPBDataTypeString, - }, - { - .name = "ttlExpiryEventToLog", - .dataTypeSpecific.className = NULL, - .number = ABTExperimentPayload_FieldNumber_TtlExpiryEventToLog, - .hasIndex = 10, - .offset = (uint32_t)offsetof(ABTExperimentPayload__storage_, ttlExpiryEventToLog), - .flags = GPBFieldOptional, - .dataType = GPBDataTypeString, - }, - { - .name = "overflowPolicy", - .dataTypeSpecific.enumDescFunc = ABTExperimentPayload_ExperimentOverflowPolicy_EnumDescriptor, - .number = ABTExperimentPayload_FieldNumber_OverflowPolicy, - .hasIndex = 11, - .offset = (uint32_t)offsetof(ABTExperimentPayload__storage_, overflowPolicy), - .flags = (GPBFieldFlags)(GPBFieldOptional | GPBFieldHasEnumDescriptor), - .dataType = GPBDataTypeEnum, - }, - { - .name = "ongoingExperimentsArray", - .dataTypeSpecific.className = GPBStringifySymbol(ABTExperimentLite), - .number = ABTExperimentPayload_FieldNumber_OngoingExperimentsArray, - .hasIndex = GPBNoHasBit, - .offset = (uint32_t)offsetof(ABTExperimentPayload__storage_, ongoingExperimentsArray), - .flags = GPBFieldRepeated, - .dataType = GPBDataTypeMessage, - }, - }; - GPBDescriptor *localDescriptor = - [GPBDescriptor allocDescriptorForClass:[ABTExperimentPayload class] - rootClass:[ABTExperimentPayloadRoot class] - file:ABTExperimentPayloadRoot_FileDescriptor() - fields:fields - fieldCount:(uint32_t)(sizeof(fields) / sizeof(GPBMessageFieldDescription)) - storageSize:sizeof(ABTExperimentPayload__storage_) - flags:GPBDescriptorInitializationFlag_None]; - NSAssert(descriptor == nil, @"Startup recursed!"); - descriptor = localDescriptor; - } - return descriptor; -} - -@end - -int32_t ABTExperimentPayload_OverflowPolicy_RawValue(ABTExperimentPayload *message) { - GPBDescriptor *descriptor = [ABTExperimentPayload descriptor]; - GPBFieldDescriptor *field = [descriptor fieldWithNumber:ABTExperimentPayload_FieldNumber_OverflowPolicy]; - return GPBGetMessageInt32Field(message, field); -} - -void SetABTExperimentPayload_OverflowPolicy_RawValue(ABTExperimentPayload *message, int32_t value) { - GPBDescriptor *descriptor = [ABTExperimentPayload descriptor]; - GPBFieldDescriptor *field = [descriptor fieldWithNumber:ABTExperimentPayload_FieldNumber_OverflowPolicy]; - GPBSetInt32IvarWithFieldInternal(message, field, value, descriptor.file.syntax); -} - -#pragma mark - Enum ABTExperimentPayload_ExperimentOverflowPolicy - -GPBEnumDescriptor *ABTExperimentPayload_ExperimentOverflowPolicy_EnumDescriptor(void) { - static GPBEnumDescriptor *descriptor = NULL; - if (!descriptor) { - static const char *valueNames = - "PolicyUnspecified\000DiscardOldest\000IgnoreNe" - "west\000"; - static const int32_t values[] = { - ABTExperimentPayload_ExperimentOverflowPolicy_PolicyUnspecified, - ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest, - ABTExperimentPayload_ExperimentOverflowPolicy_IgnoreNewest, - }; - GPBEnumDescriptor *worker = - [GPBEnumDescriptor allocDescriptorForName:GPBNSStringifySymbol(ABTExperimentPayload_ExperimentOverflowPolicy) - valueNames:valueNames - values:values - count:(uint32_t)(sizeof(values) / sizeof(int32_t)) - enumVerifier:ABTExperimentPayload_ExperimentOverflowPolicy_IsValidValue]; - if (!OSAtomicCompareAndSwapPtrBarrier(nil, worker, (void * volatile *)&descriptor)) { - [worker release]; - } - } - return descriptor; -} - -BOOL ABTExperimentPayload_ExperimentOverflowPolicy_IsValidValue(int32_t value__) { - switch (value__) { - case ABTExperimentPayload_ExperimentOverflowPolicy_PolicyUnspecified: - case ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest: - case ABTExperimentPayload_ExperimentOverflowPolicy_IgnoreNewest: - return YES; - default: - return NO; - } -} - - -#pragma clang diagnostic pop - -// @@protoc_insertion_point(global_scope) diff --git a/FirebaseABTesting/Sources/Public/FIRExperimentController.h b/FirebaseABTesting/Sources/Public/FIRExperimentController.h index 2982cdcdefe..acff7392ef6 100644 --- a/FirebaseABTesting/Sources/Public/FIRExperimentController.h +++ b/FirebaseABTesting/Sources/Public/FIRExperimentController.h @@ -17,7 +17,7 @@ @class ABTExperimentPayload; // Forward declaration to avoid importing into the module header -typedef NS_ENUM(int32_t, ABTExperimentPayload_ExperimentOverflowPolicy); +typedef NS_ENUM(int32_t, ABTExperimentPayloadExperimentOverflowPolicy); NS_ASSUME_NONNULL_BEGIN @@ -25,7 +25,7 @@ NS_ASSUME_NONNULL_BEGIN /// The default experiment overflow policy, that is to discard the experiment with the oldest start /// time when users start the experiment on the web console. -extern const ABTExperimentPayload_ExperimentOverflowPolicy FIRDefaultExperimentOverflowPolicy; +extern const ABTExperimentPayloadExperimentOverflowPolicy FIRDefaultExperimentOverflowPolicy; /// This class is for Firebase services to handle experiments updates to Firebase Analytics. /// Experiments can be set, cleared and updated through this controller. @@ -51,7 +51,7 @@ NS_SWIFT_NAME(ExperimentController) /// thread. - (void)updateExperimentsWithServiceOrigin:(NSString *)origin events:(FIRLifecycleEvents *)events - policy:(ABTExperimentPayload_ExperimentOverflowPolicy)policy + policy:(ABTExperimentPayloadExperimentOverflowPolicy)policy lastStartTime:(NSTimeInterval)lastStartTime payloads:(NSArray *)payloads completionHandler: @@ -71,7 +71,7 @@ NS_SWIFT_NAME(ExperimentController) /// @param payloads List of experiment metadata. - (void)updateExperimentsWithServiceOrigin:(NSString *)origin events:(FIRLifecycleEvents *)events - policy:(ABTExperimentPayload_ExperimentOverflowPolicy)policy + policy:(ABTExperimentPayloadExperimentOverflowPolicy)policy lastStartTime:(NSTimeInterval)lastStartTime payloads:(NSArray *)payloads DEPRECATED_MSG_ATTRIBUTE("Please use updateExperimentsWithServiceOrigin:events:policy:" diff --git a/FirebaseABTesting/Tests/Unit/ABTConditionalUserPropertyControllerTest.m b/FirebaseABTesting/Tests/Unit/ABTConditionalUserPropertyControllerTest.m index 0875ceef6a7..881c18f8dc9 100644 --- a/FirebaseABTesting/Tests/Unit/ABTConditionalUserPropertyControllerTest.m +++ b/FirebaseABTesting/Tests/Unit/ABTConditionalUserPropertyControllerTest.m @@ -14,6 +14,7 @@ #import +#import #import #import #import @@ -21,6 +22,7 @@ #import "FirebaseABTesting/Sources/ABTConstants.h" #import "FirebaseABTesting/Tests/Unit/ABTFakeFIRAConditionalUserPropertyController.h" #import "FirebaseABTesting/Tests/Unit/ABTTestUniversalConstants.h" +#import "FirebaseABTesting/Tests/Unit/Utilities/ABTTestUtilities.h" #import "FirebaseCore/Sources/Private/FirebaseCoreInternal.h" @interface ABTConditionalUserPropertyController (ExposedForTest) @@ -30,9 +32,9 @@ - (void)maxNumberOfExperimentsOfOrigin:(NSString *)origin - (id)createExperimentFromOrigin:(NSString *)origin payload:(ABTExperimentPayload *)payload events:(FIRLifecycleEvents *)events; -- (ABTExperimentPayload_ExperimentOverflowPolicy) +- (ABTExperimentPayloadExperimentOverflowPolicy) overflowPolicyWithPayload:(ABTExperimentPayload *)payload - originalPolicy:(ABTExperimentPayload_ExperimentOverflowPolicy)originalPolicy; + originalPolicy:(ABTExperimentPayloadExperimentOverflowPolicy)originalPolicy; /// Surface internal initializer to avoid singleton usage during tests. - (instancetype)initWithAnalytics:(nullable id)analytics; @end @@ -47,6 +49,10 @@ @interface ABTConditionalUserPropertyControllerTest : XCTestCase { } @end +@interface ABTExperimentPayload (Testing) +@property(nonatomic, readwrite) ABTExperimentPayloadExperimentOverflowPolicy overflowPolicy; +@end + @implementation ABTConditionalUserPropertyControllerTest - (void)setUp { [super setUp]; @@ -76,64 +82,68 @@ - (void)tearDown { #pragma mark - test proxy methods on Firebase Analytics - (void)testSetExperiment { - ABTExperimentPayload *payload = [[ABTExperimentPayload alloc] init]; - payload.experimentId = @"exp_0"; + id payload = OCMClassMock([ABTExperimentPayload class]); + OCMStub([payload experimentId]).andReturn(@"exp_0"); + OCMStub([payload variantId]).andReturn(@"v1"); FIRLifecycleEvents *events = [[FIRLifecycleEvents alloc] init]; [_ABTCUPController setExperimentWithOrigin:gABTTestOrigin payload:payload events:events - policy:ABTExperimentPayload_ExperimentOverflowPolicy_IgnoreNewest]; + policy:ABTExperimentPayloadExperimentOverflowPolicyIgnoreNewest]; NSArray *experiments = [_ABTCUPController experimentsWithOrigin:gABTTestOrigin]; XCTAssertEqual(experiments.count, 1); } - (void)testSetExperimentWhenOverflow { - ABTExperimentPayload *payload = [[ABTExperimentPayload alloc] init]; - payload.experimentId = @"exp_1"; - payload.variantId = @"v1"; + id payload1 = OCMClassMock([ABTExperimentPayload class]); + OCMStub([payload1 experimentId]).andReturn(@"exp_1"); + OCMStub([payload1 variantId]).andReturn(@"v1"); FIRLifecycleEvents *events = [[FIRLifecycleEvents alloc] init]; [_ABTCUPController setExperimentWithOrigin:gABTTestOrigin - payload:payload + payload:payload1 events:events - policy:ABTExperimentPayload_ExperimentOverflowPolicy_IgnoreNewest]; + policy:ABTExperimentPayloadExperimentOverflowPolicyIgnoreNewest]; NSArray *experiments = [_ABTCUPController experimentsWithOrigin:gABTTestOrigin]; XCTAssertEqual(experiments.count, 1); - payload.experimentId = @"exp_2"; - payload.variantId = @"v1"; + id payload2 = OCMClassMock([ABTExperimentPayload class]); + OCMStub([payload2 experimentId]).andReturn(@"exp_2"); + OCMStub([payload2 variantId]).andReturn(@"v1"); [_ABTCUPController setExperimentWithOrigin:gABTTestOrigin - payload:payload + payload:payload2 events:events - policy:ABTExperimentPayload_ExperimentOverflowPolicy_IgnoreNewest]; + policy:ABTExperimentPayloadExperimentOverflowPolicyIgnoreNewest]; experiments = [_ABTCUPController experimentsWithOrigin:gABTTestOrigin]; XCTAssertEqual(experiments.count, 2); - payload.experimentId = @"exp_3"; - payload.variantId = @"v1"; + id payload3 = OCMClassMock([ABTExperimentPayload class]); + OCMStub([payload3 experimentId]).andReturn(@"exp_3"); + OCMStub([payload3 variantId]).andReturn(@"v1"); [_ABTCUPController setExperimentWithOrigin:gABTTestOrigin - payload:payload + payload:payload3 events:events - policy:ABTExperimentPayload_ExperimentOverflowPolicy_IgnoreNewest]; + policy:ABTExperimentPayloadExperimentOverflowPolicyIgnoreNewest]; 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"; + id payload4 = OCMClassMock([ABTExperimentPayload class]); + OCMStub([payload4 experimentId]).andReturn(@"exp_4"); + OCMStub([payload4 variantId]).andReturn(@"v1"); // Try setting a new experiment with ignore newest policy. [_ABTCUPController setExperimentWithOrigin:gABTTestOrigin - payload:payload + payload:payload4 events:events - policy:ABTExperimentPayload_ExperimentOverflowPolicy_IgnoreNewest]; + policy:ABTExperimentPayloadExperimentOverflowPolicyIgnoreNewest]; experiments = [_ABTCUPController experimentsWithOrigin:gABTTestOrigin]; XCTAssertEqual(experiments.count, 3); @@ -146,22 +156,23 @@ - (void)testSetExperimentWhenOverflow { // Try setting a new experiment with discard oldest policy. [_ABTCUPController setExperimentWithOrigin:gABTTestOrigin - payload:payload + payload:payload4 events:events - policy:ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest]; + policy:ABTExperimentPayloadExperimentOverflowPolicyDiscardOldest]; 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"; + id payload5 = OCMClassMock([ABTExperimentPayload class]); + OCMStub([payload5 experimentId]).andReturn(@"exp_5"); + OCMStub([payload5 variantId]).andReturn(@"v1"); [_ABTCUPController setExperimentWithOrigin:gABTTestOrigin - payload:payload + payload:payload5 events:events - policy:ABTExperimentPayload_ExperimentOverflowPolicy_PolicyUnspecified]; + policy:ABTExperimentPayloadExperimentOverflowPolicyUnspecified]; experiments = [_ABTCUPController experimentsWithOrigin:gABTTestOrigin]; XCTAssertEqual(experiments.count, 3); @@ -172,27 +183,28 @@ - (void)testSetExperimentWhenOverflow { } - (void)testSetExperimentWithTheSameVariantID { - ABTExperimentPayload *payload = [[ABTExperimentPayload alloc] init]; - payload.experimentId = @"exp_1"; - payload.variantId = @"v1"; + id payload1 = OCMClassMock([ABTExperimentPayload class]); + OCMStub([payload1 experimentId]).andReturn(@"exp_1"); + OCMStub([payload1 variantId]).andReturn(@"v1"); FIRLifecycleEvents *events = [[FIRLifecycleEvents alloc] init]; [_ABTCUPController setExperimentWithOrigin:gABTTestOrigin - payload:payload + payload:payload1 events:events - policy:ABTExperimentPayload_ExperimentOverflowPolicy_IgnoreNewest]; + policy:ABTExperimentPayloadExperimentOverflowPolicyIgnoreNewest]; 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"; + id payload2 = OCMClassMock([ABTExperimentPayload class]); + OCMStub([payload2 experimentId]).andReturn(@"exp_1"); + OCMStub([payload2 variantId]).andReturn(@"v2"); [_ABTCUPController setExperimentWithOrigin:gABTTestOrigin - payload:payload + payload:payload2 events:events - policy:ABTExperimentPayload_ExperimentOverflowPolicy_IgnoreNewest]; + policy:ABTExperimentPayloadExperimentOverflowPolicyIgnoreNewest]; experiments = [_ABTCUPController experimentsWithOrigin:gABTTestOrigin]; XCTAssertEqual(experiments.count, 1); @@ -212,17 +224,16 @@ - (BOOL)isExperimentID:(NSString *)experimentID } - (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"; + id payload = OCMClassMock([ABTExperimentPayload class]); + OCMStub([payload experimentId]).andReturn(@"exp_1"); + OCMStub([payload variantId]).andReturn(@"v1"); + OCMStub([payload clearEventToLog]).andReturn(@"override_clear_event"); FIRLifecycleEvents *events = [[FIRLifecycleEvents alloc] init]; [_ABTCUPController setExperimentWithOrigin:gABTTestOrigin payload:payload events:events - policy:ABTExperimentPayload_ExperimentOverflowPolicy_IgnoreNewest]; + policy:ABTExperimentPayloadExperimentOverflowPolicyIgnoreNewest]; NSArray *experiments = [_ABTCUPController experimentsWithOrigin:gABTTestOrigin]; XCTAssertEqual(experiments.count, 1); @@ -241,15 +252,18 @@ - (void)testMaxNumberOfExperiments { } - (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; + + id payload = OCMClassMock([ABTExperimentPayload class]); + OCMStub([payload experimentId]).andReturn(@"exp_1"); + OCMStub([payload variantId]).andReturn(@"variant_B"); + int64_t startTimeMillis = now * ABT_MSEC_PER_SEC; + OCMStub([payload experimentStartTimeMillis]).andReturn(startTimeMillis); + OCMStub([payload triggerEvent]).andReturn(@""); + int64_t triggerTimeoutMillis = (now + 1500) * ABT_MSEC_PER_SEC; + OCMStub([payload triggerTimeoutMillis]).andReturn(triggerTimeoutMillis); + int64_t timeToLiveMillis = (now + 60000) * ABT_MSEC_PER_SEC; + OCMStub([payload timeToLiveMillis]).andReturn(timeToLiveMillis); FIRLifecycleEvents *events = [[FIRLifecycleEvents alloc] init]; events.activateExperimentEventName = @"_lifecycle_override_activate"; @@ -262,8 +276,8 @@ - (void)testCreateExperiment { 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); + XCTAssertEqualWithAccuracy(now, [[experiment objectForKey:@"creationTimestamp"] longLongValue], + 1.0); // Trigger event XCTAssertEqualObjects(gABTTestOrigin, triggeredEvent[@"origin"]); @@ -288,21 +302,25 @@ - (void)testCreateExperiment { @"Empty trigger event must be set to nil"); // trigger timeout - XCTAssertEqualWithAccuracy( - now + 1500, [(NSNumber *)([experiment objectForKey:@"triggerTimeout"]) doubleValue], 1.0); + XCTAssertEqualWithAccuracy(now + 1500, + [[experiment objectForKey:@"triggerTimeout"] longLongValue], 1.0); // time to live - XCTAssertEqualWithAccuracy( - now + 60000, [(NSNumber *)[experiment objectForKey:@"timeToLive"] doubleValue], 1.0); + XCTAssertEqualWithAccuracy(now + 60000, [[experiment objectForKey:@"timeToLive"] longLongValue], + 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"; + id payloadWithCustomEventNames = OCMClassMock([ABTExperimentPayload class]); + OCMStub([payloadWithCustomEventNames experimentId]).andReturn(@"exp_1"); + OCMStub([payloadWithCustomEventNames variantId]).andReturn(@"variant_B"); + OCMStub([payloadWithCustomEventNames activateEventToLog]).andReturn(@"payload_override_activate"); + OCMStub([payloadWithCustomEventNames ttlExpiryEventToLog]) + .andReturn(@"payload_override_time_to_live"); + OCMStub([payloadWithCustomEventNames timeoutEventToLog]).andReturn(@"payload_override_timeout"); + OCMStub([payloadWithCustomEventNames triggerEvent]).andReturn(@"payload_override_trigger_event"); experiment = [_ABTCUPController createExperimentFromOrigin:gABTTestOrigin - payload:payload + payload:payloadWithCustomEventNames events:events]; triggeredEvent = [experiment objectForKey:@"triggeredEvent"]; XCTAssertEqual(triggeredEvent[@"name"], @"payload_override_activate"); @@ -310,7 +328,8 @@ - (void)testCreateExperiment { 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"); + XCTAssertEqualObjects([experiment objectForKey:@"triggerEventName"], + @"payload_override_trigger_event"); } #pragma mark - helpers @@ -319,41 +338,50 @@ - (void)testIsExperimentTheSameAsPayload { NSDictionary *experiment = @{@"name" : @"exp_1", @"value" : @"variant_control_group"}; - ABTExperimentPayload *payload = [[ABTExperimentPayload alloc] init]; - payload.experimentId = @"exp_2"; - payload.variantId = @"variant_group_A"; + id payload2 = OCMClassMock([ABTExperimentPayload class]); + OCMStub([payload2 experimentId]).andReturn(@"exp_2"); + OCMStub([payload2 variantId]).andReturn(@"variant_group_A"); + + XCTAssertFalse([_ABTCUPController isExperiment:experiment theSameAsPayload:payload2]); - XCTAssertFalse([_ABTCUPController isExperiment:experiment theSameAsPayload:payload]); + id payload1 = OCMClassMock([ABTExperimentPayload class]); + OCMStub([payload1 experimentId]).andReturn(@"exp_1"); + OCMStub([payload1 variantId]).andReturn(@"variant_group_A"); - payload.experimentId = @"exp_1"; - XCTAssertFalse([_ABTCUPController isExperiment:experiment theSameAsPayload:payload]); + XCTAssertFalse([_ABTCUPController isExperiment:experiment theSameAsPayload:payload1]); - payload.variantId = @"variant_control_group"; - XCTAssertTrue([_ABTCUPController isExperiment:experiment theSameAsPayload:payload]); + id payloadJustRight = OCMClassMock([ABTExperimentPayload class]); + OCMStub([payloadJustRight experimentId]).andReturn(@"exp_1"); + OCMStub([payloadJustRight variantId]).andReturn(@"variant_control_group"); + XCTAssertTrue([_ABTCUPController isExperiment:experiment theSameAsPayload:payloadJustRight]); } - (void)testOverflowPolicyWithPayload { - ABTExperimentPayload *payload = [[ABTExperimentPayload alloc] init]; + ABTExperimentPayload *payloadUnspecifiedPolicy = + [ABTTestUtilities payloadFromTestFilename:@"TestABTPayload3"]; - XCTAssertEqual(ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest, - [_ABTCUPController overflowPolicyWithPayload:payload originalPolicy:-1000], + XCTAssertEqual(ABTExperimentPayloadExperimentOverflowPolicyDiscardOldest, + [_ABTCUPController overflowPolicyWithPayload:payloadUnspecifiedPolicy + originalPolicy:-1000], @"Payload policy is unspecified, original policy is invalid, should return " @"default: DiscardOldest."); XCTAssertEqual( - ABTExperimentPayload_ExperimentOverflowPolicy_IgnoreNewest, + ABTExperimentPayloadExperimentOverflowPolicyIgnoreNewest, [_ABTCUPController - overflowPolicyWithPayload:payload - originalPolicy:ABTExperimentPayload_ExperimentOverflowPolicy_IgnoreNewest], + overflowPolicyWithPayload:payloadUnspecifiedPolicy + originalPolicy:ABTExperimentPayloadExperimentOverflowPolicyIgnoreNewest], @"Payload policy is unspecified, original policy is valid, use " @"original policy."); - payload.overflowPolicy = ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest; + ABTExperimentPayload *payloadDiscardOldest = + [ABTTestUtilities payloadFromTestFilename:@"TestABTPayload1"]; + payloadDiscardOldest.overflowPolicy = ABTExperimentPayloadExperimentOverflowPolicyDiscardOldest; XCTAssertEqual( - ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest, + ABTExperimentPayloadExperimentOverflowPolicyDiscardOldest, [_ABTCUPController - overflowPolicyWithPayload:payload - originalPolicy:ABTExperimentPayload_ExperimentOverflowPolicy_IgnoreNewest], + overflowPolicyWithPayload:payloadDiscardOldest + originalPolicy:ABTExperimentPayloadExperimentOverflowPolicyIgnoreNewest], @"Payload policy is specified, original policy is valid, but " @"use Payload because Payload always wins."); } diff --git a/FirebaseABTesting/Tests/Unit/ABTExperimentPayloadTest.m b/FirebaseABTesting/Tests/Unit/ABTExperimentPayloadTest.m new file mode 100644 index 00000000000..7ac33f44dac --- /dev/null +++ b/FirebaseABTesting/Tests/Unit/ABTExperimentPayloadTest.m @@ -0,0 +1,124 @@ +// Copyright 2020 Google LLC +// +// 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 + +#import +#import "ABTConstants.h" +#import "ABTTestUtilities.h" + +@interface ABTExperimentPayload (ClassTesting) + ++ (NSDateFormatter *)experimentStartTimeFormatter; + +@end + +@interface ABTExperimentPayloadTest : XCTestCase + +@end + +@implementation ABTExperimentPayloadTest + +- (void)testPayloadWithTrigger { + ABTExperimentPayload *testPayload = [ABTTestUtilities payloadFromTestFilename:@"TestABTPayload1"]; + XCTAssertEqualObjects(testPayload.experimentId, @"exp_1"); + XCTAssertEqualObjects(testPayload.variantId, @"var_1"); + XCTAssertEqualObjects(testPayload.triggerEvent, @"customTrigger"); + + // From the experiment resource file. + NSString *startTimeString = @"2020-04-08T16:44:39.023Z"; + NSDate *startTime = [self dateFromFormattedDateString:startTimeString]; + NSTimeInterval startTimeInterval = [startTime timeIntervalSince1970]; + XCTAssertEqual(testPayload.experimentStartTimeMillis, startTimeInterval * ABT_MSEC_PER_SEC); + + XCTAssertEqual(testPayload.triggerTimeoutMillis, 15552000000); + XCTAssertEqual(testPayload.timeToLiveMillis, 15552000000); + XCTAssertEqualObjects(testPayload.setEventToLog, @"set_event"); + XCTAssertEqualObjects(testPayload.activateEventToLog, @"activate_event"); + XCTAssertEqualObjects(testPayload.clearEventToLog, @"clear_event"); + XCTAssertEqualObjects(testPayload.timeoutEventToLog, @"timeout_event"); + XCTAssertEqualObjects(testPayload.ttlExpiryEventToLog, @"ttl_expiry_event"); + XCTAssertEqual(testPayload.overflowPolicy, + ABTExperimentPayloadExperimentOverflowPolicyIgnoreNewest); + XCTAssertEqual(testPayload.ongoingExperiments.count, 1); + ABTExperimentLite *liteExperiment = testPayload.ongoingExperiments.firstObject; + XCTAssertEqualObjects(liteExperiment.experimentId, @"exp_1"); +} + +- (void)testPayloadWithoutTrigger { + ABTExperimentPayload *testPayload = [ABTTestUtilities payloadFromTestFilename:@"TestABTPayload2"]; + XCTAssertEqualObjects(testPayload.experimentId, @"exp_2"); + XCTAssertEqualObjects(testPayload.variantId, @"v200"); + XCTAssertNil(testPayload.triggerEvent); + + // From the experiment resource file. + NSString *startTimeString = @"2020-06-01T16:00:00.000Z"; + NSDate *startTime = [self dateFromFormattedDateString:startTimeString]; + NSTimeInterval startTimeInterval = [startTime timeIntervalSince1970]; + XCTAssertEqual(testPayload.experimentStartTimeMillis, startTimeInterval * ABT_MSEC_PER_SEC); + + XCTAssertEqual(testPayload.triggerTimeoutMillis, 15452000000); + XCTAssertEqual(testPayload.timeToLiveMillis, 15452000000); + XCTAssertEqualObjects(testPayload.setEventToLog, @"set_event_override"); + XCTAssertEqualObjects(testPayload.activateEventToLog, @"activate_event_override"); + XCTAssertEqualObjects(testPayload.clearEventToLog, @"clear_event_override"); + XCTAssertEqualObjects(testPayload.timeoutEventToLog, @"timeout_event_override"); + XCTAssertEqualObjects(testPayload.ttlExpiryEventToLog, @"ttl_expiry_event_override"); + XCTAssertEqual(testPayload.overflowPolicy, + ABTExperimentPayloadExperimentOverflowPolicyDiscardOldest); +} + +/// Verifies that we initialize the payload if it has a start time parameter with millis, rather +/// than a date string. +- (void)testPayloadInitializesStartTimeWithMillis { + ABTExperimentPayload *testPayload = [ABTTestUtilities payloadFromTestFilename:@"TestABTPayload5"]; + XCTAssertEqual(testPayload.experimentStartTimeMillis, 143); +} + +- (void)testPayloadInitializationWithString { + ABTExperimentPayload *unrecognizedOverflowPolicyString = + [[ABTExperimentPayload alloc] initWithDictionary:@{@"overflowPolicy" : @"WWDC"}]; + XCTAssertEqual(unrecognizedOverflowPolicyString.overflowPolicy, + ABTExperimentPayloadExperimentOverflowPolicyUnrecognizedValue); + + ABTExperimentPayload *ignoreNewestOverflowPolicyString = + [[ABTExperimentPayload alloc] initWithDictionary:@{@"overflowPolicy" : @"IGNORE_NEWEST"}]; + XCTAssertEqual(ignoreNewestOverflowPolicyString.overflowPolicy, + ABTExperimentPayloadExperimentOverflowPolicyIgnoreNewest); + + ABTExperimentPayload *discardOldestOverflowPolicyString = + [[ABTExperimentPayload alloc] initWithDictionary:@{@"overflowPolicy" : @"DISCARD_OLDEST"}]; + XCTAssertEqual(discardOldestOverflowPolicyString.overflowPolicy, + ABTExperimentPayloadExperimentOverflowPolicyDiscardOldest); +} + +- (void)testUtilityMethods { + ABTExperimentPayload *testPayload1 = + [ABTTestUtilities payloadFromTestFilename:@"TestABTPayload1"]; + XCTAssertTrue([testPayload1 overflowPolicyIsValid]); + + // Clear trigger event and make sure it's now nil. + [testPayload1 clearTriggerEvent]; + + // This one has an unspecified overflow policy. + ABTExperimentPayload *testPayload3 = + [ABTTestUtilities payloadFromTestFilename:@"TestABTPayload3"]; + XCTAssertFalse([testPayload3 overflowPolicyIsValid]); +} + +- (NSDate *)dateFromFormattedDateString:(NSString *)dateString { + return [[ABTExperimentPayload experimentStartTimeFormatter] dateFromString:dateString]; +} + +@end diff --git a/FirebaseABTesting/Tests/Unit/FIRExperimentControllerTest.m b/FirebaseABTesting/Tests/Unit/FIRExperimentControllerTest.m index c984c68c108..8f11ba366d6 100644 --- a/FirebaseABTesting/Tests/Unit/FIRExperimentControllerTest.m +++ b/FirebaseABTesting/Tests/Unit/FIRExperimentControllerTest.m @@ -14,6 +14,7 @@ #import +#import #import #import #import @@ -21,6 +22,7 @@ #import "FirebaseABTesting/Sources/ABTConstants.h" #import "FirebaseABTesting/Tests/Unit/ABTFakeFIRAConditionalUserPropertyController.h" #import "FirebaseABTesting/Tests/Unit/ABTTestUniversalConstants.h" +#import "FirebaseABTesting/Tests/Unit/Utilities/ABTTestUtilities.h" #import "FirebaseCore/Sources/Private/FirebaseCoreInternal.h" #import "Interop/Analytics/Public/FIRAnalyticsInterop.h" @@ -40,7 +42,7 @@ @interface FIRExperimentController (ExposedForTest) updateExperimentConditionalUserPropertiesWithServiceOrigin:(NSString *)origin events:(FIRLifecycleEvents *)events policy: - (ABTExperimentPayload_ExperimentOverflowPolicy) + (ABTExperimentPayloadExperimentOverflowPolicy) policy lastStartTime:(NSTimeInterval)lastStartTime payloads:(NSArray *)payloads @@ -59,9 +61,9 @@ - (int32_t)maxNumberOfExperimentsOfOrigin:(NSString *)origin; - (id)createExperimentFromOrigin:(NSString *)origin payload:(ABTExperimentPayload *)payload events:(FIRLifecycleEvents *)events; -- (ABTExperimentPayload_ExperimentOverflowPolicy) +- (ABTExperimentPayloadExperimentOverflowPolicy) overflowPolicyWithPayload:(ABTExperimentPayload *)payload - originalPolicy:(ABTExperimentPayload_ExperimentOverflowPolicy)originalPolicy; + originalPolicy:(ABTExperimentPayloadExperimentOverflowPolicy)originalPolicy; @end @interface FIRExperimentControllerTest : XCTestCase { @@ -98,7 +100,6 @@ - (void)testDeserializeInvalidPayload { NSString *sampleString = @"sample_invalid_payload"; NSData *invalidData = [sampleString dataUsingEncoding:NSUTF8StringEncoding]; XCTAssertNil(ABTDeserializeExperimentPayload(invalidData)); - XCTAssertNotNil(ABTDeserializeExperimentPayload(nil)); } - (void)testLifecycleEvents { @@ -144,52 +145,35 @@ - (void)testSetExperimentWithBadPayload { setExperimentWithOrigin:[OCMArg any] payload:[OCMArg any] events:[OCMArg any] - policy:ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest]; + policy:ABTExperimentPayloadExperimentOverflowPolicyDiscardOldest]; NSString *sampleString = @"sample_invalid_payload"; NSData *invalidData = [sampleString dataUsingEncoding:NSUTF8StringEncoding]; XCTAssertNil(ABTDeserializeExperimentPayload(invalidData)); } - (void)testUpdateExperiments { - NSTimeInterval now = [[NSDate date] timeIntervalSince1970]; - - ABTExperimentPayload *payload2 = [[ABTExperimentPayload alloc] init]; - payload2.experimentId = @"exp_2"; - payload2.variantId = @"v200"; - payload2.experimentStartTimeMillis = - (now + 1500) * ABT_MSEC_PER_SEC; // start time > last start time, do set - ABTExperimentLite *ongoingExperiment = [[ABTExperimentLite alloc] init]; - ongoingExperiment.experimentId = @"exp_1"; - [payload2.ongoingExperimentsArray addObject:ongoingExperiment]; - - ABTExperimentPayload *payload3 = [[ABTExperimentPayload alloc] init]; - payload3.experimentId = @"exp_3"; - payload3.variantId = @"v200"; - payload3.experimentStartTimeMillis = - (now + 900) * ABT_MSEC_PER_SEC; // start time > last start time, do set - ongoingExperiment = [[ABTExperimentLite alloc] init]; - ongoingExperiment.experimentId = @"exp_2"; - [payload3.ongoingExperimentsArray addObject:ongoingExperiment]; - - ABTExperimentPayload *payload4 = [[ABTExperimentPayload alloc] init]; - payload4.experimentId = @"exp_4"; - payload4.variantId = @"v200"; - payload4.experimentStartTimeMillis = - (now - 900) * ABT_MSEC_PER_SEC; // start time < last start time, do not set. - ongoingExperiment = [[ABTExperimentLite alloc] init]; - ongoingExperiment.experimentId = @"exp_2"; - [payload4.ongoingExperimentsArray addObject:ongoingExperiment]; + NSDate *now = [NSDate date]; + + NSData *payload2Data = + [ABTTestUtilities payloadJSONDataFromFile:@"TestABTPayload2" + modifiedStartTime:[now dateByAddingTimeInterval:1500]]; + NSData *payload3Data = + [ABTTestUtilities payloadJSONDataFromFile:@"TestABTPayload3" + modifiedStartTime:[now dateByAddingTimeInterval:900]]; + NSData *payload4Data = + [ABTTestUtilities payloadJSONDataFromFile:@"TestABTPayload4" + modifiedStartTime:[now dateByAddingTimeInterval:-900]]; __block BOOL completionHandlerCalled = NO; FIRLifecycleEvents *events = [[FIRLifecycleEvents alloc] init]; - NSArray *payloads = @[ [payload2 data], [payload3 data], [payload4 data] ]; + NSArray *payloads = @[ payload2Data, payload3Data, payload4Data ]; [_experimentController updateExperimentConditionalUserPropertiesWithServiceOrigin:gABTTestOrigin events:events policy: - ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest // NOLINT - lastStartTime:now + ABTExperimentPayloadExperimentOverflowPolicyDiscardOldest // NOLINT + lastStartTime:[now timeIntervalSince1970] payloads:payloads completionHandler:^(NSError *_Nullable error) { completionHandlerCalled = YES; @@ -199,13 +183,13 @@ - (void)testUpdateExperiments { XCTAssertTrue(completionHandlerCalled); // Second time update exp_1 no longer exist, should be cleared from experiments. - payloads = @[ [payload3 data], [payload4 data] ]; + payloads = @[ payload3Data, payload4Data ]; [_experimentController updateExperimentConditionalUserPropertiesWithServiceOrigin:gABTTestOrigin events:events policy: - ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest // NOLINT - lastStartTime:now + ABTExperimentPayloadExperimentOverflowPolicyDiscardOldest // NOLINT + lastStartTime:[now timeIntervalSince1970] payloads:payloads completionHandler:nil]; @@ -215,37 +199,35 @@ - (void)testUpdateExperiments { - (void)testLatestExperimentStartTimestamps { // Mock incoming payloads NSMutableArray *payloads = [[NSMutableArray alloc] init]; - NSTimeInterval now = [[NSDate date] timeIntervalSince1970]; - ABTExperimentPayload *payload1 = [[ABTExperimentPayload alloc] init]; - payload1.experimentId = @"exp_1"; - payload1.variantId = @"v3"; - payload1.experimentStartTimeMillis = now * ABT_MSEC_PER_SEC; - [payloads addObject:[payload1 data]]; + NSDate *now = [NSDate date]; + NSTimeInterval nowInterval = [now timeIntervalSince1970]; - ABTExperimentPayload *payload2 = [[ABTExperimentPayload alloc] init]; - payload2.experimentId = @"exp_2"; - payload2.variantId = @"v2"; - payload2.experimentStartTimeMillis = (now + 500) * ABT_MSEC_PER_SEC; - [payloads addObject:[payload2 data]]; + NSData *payload2Data = [ABTTestUtilities payloadJSONDataFromFile:@"TestABTPayload2" + modifiedStartTime:now]; + [payloads addObject:payload2Data]; + NSData *payload3Data = + [ABTTestUtilities payloadJSONDataFromFile:@"TestABTPayload3" + modifiedStartTime:[now dateByAddingTimeInterval:500]]; + [payloads addObject:payload3Data]; NSString *sampleString = @"sample_invalid_payload"; NSData *invalidPayload = [sampleString dataUsingEncoding:NSUTF8StringEncoding]; [payloads addObject:invalidPayload]; XCTAssertEqualWithAccuracy( - now + 500, - [_experimentController latestExperimentStartTimestampBetweenTimestamp:now + 200 + [now timeIntervalSince1970] + 500, + [_experimentController latestExperimentStartTimestampBetweenTimestamp:nowInterval + 200 andPayloads:payloads], 1); XCTAssertEqualWithAccuracy( - now + 1000, - [_experimentController latestExperimentStartTimestampBetweenTimestamp:now + 1000 + [now timeIntervalSince1970] + 1000, + [_experimentController latestExperimentStartTimestampBetweenTimestamp:nowInterval + 1000 andPayloads:payloads], 1); XCTAssertEqualWithAccuracy( - now + 500, - [_experimentController latestExperimentStartTimestampBetweenTimestamp:now - 10000 + [now timeIntervalSince1970] + 500, + [_experimentController latestExperimentStartTimestampBetweenTimestamp:nowInterval - 10000 andPayloads:payloads], 1); } @@ -257,20 +239,16 @@ - (void)testExperimentsToSetFromPayloads { NSDictionary *CUP1 = @{@"name" : @"exp_1", @"value" : @"v1"}; [currentExperiments addObject:CUP1]; - NSDictionary *CUP2 = @{@"name" : @"exp_2", @"value" : @"v2"}; + NSDictionary *CUP2 = @{@"name" : @"exp_2", @"value" : @"v200"}; [currentExperiments addObject:CUP2]; - // Mock incoming payloads NSMutableArray *payloads = [[NSMutableArray alloc] init]; - ABTExperimentPayload *payload1 = [[ABTExperimentPayload alloc] init]; - payload1.experimentId = @"exp_1"; - payload1.variantId = @"v3"; - [payloads addObject:[payload1 data]]; - - ABTExperimentPayload *payload2 = [[ABTExperimentPayload alloc] init]; - payload2.experimentId = @"exp_2"; - payload2.variantId = @"v2"; - [payloads addObject:[payload2 data]]; + NSData *payload1Data = [ABTTestUtilities payloadJSONDataFromFile:@"TestABTPayload1" + modifiedStartTime:nil]; + [payloads addObject:payload1Data]; + NSData *payload2Data = [ABTTestUtilities payloadJSONDataFromFile:@"TestABTPayload2" + modifiedStartTime:nil]; + [payloads addObject:payload2Data]; NSString *sampleString = @"sample_invalid_payload"; NSData *invalidPayload = [sampleString dataUsingEncoding:NSUTF8StringEncoding]; @@ -282,10 +260,10 @@ - (void)testExperimentsToSetFromPayloads { XCTAssertEqual(experimentsToSet.count, 1); ABTExperimentPayload *payloadToAdd = experimentsToSet.firstObject; XCTAssertEqualObjects(payloadToAdd.experimentId, @"exp_1"); - XCTAssertEqualObjects(payloadToAdd.variantId, @"v3"); + XCTAssertEqualObjects(payloadToAdd.variantId, @"var_1"); } -- (void)testExperimentsToClearFromPaylods { +- (void)testExperimentsToClearFromPayloads { // Mock conditional user property objects in experiments. NSMutableArray *currentExperiments = [[NSMutableArray alloc] init]; @@ -295,17 +273,13 @@ - (void)testExperimentsToClearFromPaylods { NSDictionary *CUP2 = @{@"name" : @"exp_2", @"value" : @"v2"}; [currentExperiments addObject:CUP2]; - // Mock incoming payloads NSMutableArray *payloads = [[NSMutableArray alloc] init]; - ABTExperimentPayload *payload1 = [[ABTExperimentPayload alloc] init]; - payload1.experimentId = @"exp_1"; - payload1.variantId = @"v3"; - [payloads addObject:[payload1 data]]; - - ABTExperimentPayload *payload2 = [[ABTExperimentPayload alloc] init]; - payload2.experimentId = @"exp_2"; - payload2.variantId = @"v2"; - [payloads addObject:[payload2 data]]; + NSData *payload1Data = [ABTTestUtilities payloadJSONDataFromFile:@"TestABTPayload4" + modifiedStartTime:nil]; + [payloads addObject:payload1Data]; + NSData *payload2Data = [ABTTestUtilities payloadJSONDataFromFile:@"TestABTPayload5" + modifiedStartTime:nil]; + [payloads addObject:payload2Data]; NSString *sampleString = @"sample_invalid_payload"; NSData *invalidPayload = [sampleString dataUsingEncoding:NSUTF8StringEncoding]; @@ -325,12 +299,12 @@ - (void)testInvalidExperiments { setExperimentWithOrigin:[OCMArg any] payload:[OCMArg any] events:[OCMArg any] - policy:ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest]; + policy:ABTExperimentPayloadExperimentOverflowPolicyDiscardOldest]; [[_mockCUPController reject] setExperimentWithOrigin:[OCMArg any] payload:[OCMArg any] events:[OCMArg any] - policy:ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest]; + policy:ABTExperimentPayloadExperimentOverflowPolicyDiscardOldest]; OCMStub([_mockCUPController experimentsWithOrigin:gABTTestOrigin]).andReturn(nil); NSMutableArray *payloads = [[NSMutableArray alloc] init]; @@ -342,7 +316,7 @@ - (void)testInvalidExperiments { updateExperimentConditionalUserPropertiesWithServiceOrigin:gABTTestOrigin events:events policy: - ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest // NOLINT + ABTExperimentPayloadExperimentOverflowPolicyDiscardOldest // NOLINT lastStartTime:-1 payloads:payloads completionHandler:^(NSError *_Nullable error) { @@ -362,8 +336,8 @@ - (void)testUpdateExperimentsWithNoCompletion { NSString *mockOrigin = @"mockOrigin"; FIRLifecycleEvents *mockLifecycleEvents = [[FIRLifecycleEvents alloc] init]; - ABTExperimentPayload_ExperimentOverflowPolicy mockOverflowPolicy = - ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest; + ABTExperimentPayloadExperimentOverflowPolicy mockOverflowPolicy = + ABTExperimentPayloadExperimentOverflowPolicyDiscardOldest; NSTimeInterval mockLastStartTime = 100; NSArray *mockPayloads = @[]; @@ -390,34 +364,23 @@ - (void)testUpdateExperimentsWithNoCompletion { } - (void)testValidateRunningExperimentsWithEmptyArray { - NSTimeInterval now = [[NSDate date] timeIntervalSince1970]; - - ABTExperimentPayload *payload2 = [[ABTExperimentPayload alloc] init]; - payload2.experimentId = @"exp_2"; - payload2.variantId = @"v200"; - payload2.experimentStartTimeMillis = - (now + 1500) * ABT_MSEC_PER_SEC; // start time > last start time, do set - ABTExperimentLite *ongoingExperiment = [[ABTExperimentLite alloc] init]; - ongoingExperiment.experimentId = @"exp_1"; - [payload2.ongoingExperimentsArray addObject:ongoingExperiment]; - - ABTExperimentPayload *payload3 = [[ABTExperimentPayload alloc] init]; - payload3.experimentId = @"exp_3"; - payload3.variantId = @"v200"; - payload3.experimentStartTimeMillis = - (now + 900) * ABT_MSEC_PER_SEC; // start time > last start time, do set - ongoingExperiment = [[ABTExperimentLite alloc] init]; - ongoingExperiment.experimentId = @"exp_2"; - [payload3.ongoingExperimentsArray addObject:ongoingExperiment]; + NSDate *now = [NSDate date]; + + NSData *payload2Data = + [ABTTestUtilities payloadJSONDataFromFile:@"TestABTPayload2" + modifiedStartTime:[now dateByAddingTimeInterval:1500]]; + NSData *payload3Data = + [ABTTestUtilities payloadJSONDataFromFile:@"TestABTPayload3" + modifiedStartTime:[now dateByAddingTimeInterval:900]]; FIRLifecycleEvents *events = [[FIRLifecycleEvents alloc] init]; - NSArray *payloads = @[ [payload2 data], [payload3 data] ]; + NSArray *payloads = @[ payload2Data, payload3Data ]; [_experimentController updateExperimentConditionalUserPropertiesWithServiceOrigin:gABTTestOrigin events:events policy: - ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest // NOLINT - lastStartTime:now + ABTExperimentPayloadExperimentOverflowPolicyDiscardOldest // NOLINT + lastStartTime:[now timeIntervalSince1970] payloads:payloads completionHandler:nil]; @@ -431,42 +394,30 @@ - (void)testValidateRunningExperimentsWithEmptyArray { } - (void)testValidateRunningExperimentsClearingOne { - NSTimeInterval now = [[NSDate date] timeIntervalSince1970]; - - ABTExperimentPayload *payload2 = [[ABTExperimentPayload alloc] init]; - payload2.experimentId = @"exp_2"; - payload2.variantId = @"v200"; - payload2.experimentStartTimeMillis = - (now + 1500) * ABT_MSEC_PER_SEC; // start time > last start time, do set - ABTExperimentLite *ongoingExperiment = [[ABTExperimentLite alloc] init]; - ongoingExperiment.experimentId = @"exp_1"; - [payload2.ongoingExperimentsArray addObject:ongoingExperiment]; - - ABTExperimentPayload *payload3 = [[ABTExperimentPayload alloc] init]; - payload3.experimentId = @"exp_3"; - payload3.variantId = @"v200"; - payload3.experimentStartTimeMillis = - (now + 900) * ABT_MSEC_PER_SEC; // start time > last start time, do set - ongoingExperiment = [[ABTExperimentLite alloc] init]; - ongoingExperiment.experimentId = @"exp_2"; - [payload3.ongoingExperimentsArray addObject:ongoingExperiment]; + NSDate *now = [NSDate date]; + + NSData *payload2Data = + [ABTTestUtilities payloadJSONDataFromFile:@"TestABTPayload2" + modifiedStartTime:[now dateByAddingTimeInterval:1500]]; + NSData *payload3Data = + [ABTTestUtilities payloadJSONDataFromFile:@"TestABTPayload3" + modifiedStartTime:[now dateByAddingTimeInterval:900]]; FIRLifecycleEvents *events = [[FIRLifecycleEvents alloc] init]; - NSArray *payloads = @[ [payload2 data], [payload3 data] ]; + NSArray *payloads = @[ payload2Data, payload3Data ]; [_experimentController updateExperimentConditionalUserPropertiesWithServiceOrigin:gABTTestOrigin events:events policy: - ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest // NOLINT - lastStartTime:now + ABTExperimentPayloadExperimentOverflowPolicyDiscardOldest // NOLINT + lastStartTime:[now timeIntervalSince1970] payloads:payloads completionHandler:nil]; XCTAssertEqual([_mockCUPController experimentsWithOrigin:gABTTestOrigin].count, 2); - ABTExperimentPayload *validatingPayload2 = [[ABTExperimentPayload alloc] init]; - validatingPayload2.experimentId = @"exp_2"; - validatingPayload2.variantId = @"v200"; + ABTExperimentPayload *validatingPayload2 = + [ABTTestUtilities payloadFromTestFilename:@"TestABTPayload2"]; [_experimentController validateRunningExperimentsForServiceOrigin:gABTTestOrigin runningExperimentPayloads:@[ validatingPayload2 ]]; @@ -476,46 +427,32 @@ - (void)testValidateRunningExperimentsClearingOne { } - (void)testValidateRunningExperimentsKeepingAll { - NSTimeInterval now = [[NSDate date] timeIntervalSince1970]; - - ABTExperimentPayload *payload2 = [[ABTExperimentPayload alloc] init]; - payload2.experimentId = @"exp_2"; - payload2.variantId = @"v200"; - payload2.experimentStartTimeMillis = - (now + 1500) * ABT_MSEC_PER_SEC; // start time > last start time, do set - ABTExperimentLite *ongoingExperiment = [[ABTExperimentLite alloc] init]; - ongoingExperiment.experimentId = @"exp_1"; - [payload2.ongoingExperimentsArray addObject:ongoingExperiment]; - - ABTExperimentPayload *payload3 = [[ABTExperimentPayload alloc] init]; - payload3.experimentId = @"exp_3"; - payload3.variantId = @"v200"; - payload3.experimentStartTimeMillis = - (now + 900) * ABT_MSEC_PER_SEC; // start time > last start time, do set - ongoingExperiment = [[ABTExperimentLite alloc] init]; - ongoingExperiment.experimentId = @"exp_2"; - [payload3.ongoingExperimentsArray addObject:ongoingExperiment]; + NSDate *now = [NSDate date]; + + NSData *payload2Data = + [ABTTestUtilities payloadJSONDataFromFile:@"TestABTPayload2" + modifiedStartTime:[now dateByAddingTimeInterval:1500]]; + NSData *payload3Data = + [ABTTestUtilities payloadJSONDataFromFile:@"TestABTPayload3" + modifiedStartTime:[now dateByAddingTimeInterval:900]]; FIRLifecycleEvents *events = [[FIRLifecycleEvents alloc] init]; - NSArray *payloads = @[ [payload2 data], [payload3 data] ]; + NSArray *payloads = @[ payload2Data, payload3Data ]; [_experimentController updateExperimentConditionalUserPropertiesWithServiceOrigin:gABTTestOrigin events:events policy: - ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest // NOLINT - lastStartTime:now + ABTExperimentPayloadExperimentOverflowPolicyDiscardOldest // NOLINT + lastStartTime:[now timeIntervalSince1970] payloads:payloads completionHandler:nil]; XCTAssertEqual([_mockCUPController experimentsWithOrigin:gABTTestOrigin].count, 2); - ABTExperimentPayload *validatingPayload2 = [[ABTExperimentPayload alloc] init]; - validatingPayload2.experimentId = @"exp_2"; - validatingPayload2.variantId = @"v200"; - - ABTExperimentPayload *validatingPayload3 = [[ABTExperimentPayload alloc] init]; - validatingPayload3.experimentId = @"exp_3"; - validatingPayload3.variantId = @"v200"; + ABTExperimentPayload *validatingPayload2 = + [ABTTestUtilities payloadFromTestFilename:@"TestABTPayload2"]; + ABTExperimentPayload *validatingPayload3 = + [ABTTestUtilities payloadFromTestFilename:@"TestABTPayload3"]; [_experimentController validateRunningExperimentsForServiceOrigin:gABTTestOrigin @@ -526,10 +463,8 @@ - (void)testValidateRunningExperimentsKeepingAll { } - (void)testActivateExperiment { - ABTExperimentPayload *activeExperiment = [[ABTExperimentPayload alloc] init]; - activeExperiment.experimentId = @"exp_3"; - activeExperiment.variantId = @"v200"; - activeExperiment.triggerEvent = @"trigger"; + ABTExperimentPayload *activeExperiment = + [ABTTestUtilities payloadFromTestFilename:@"TestABTPayload1"]; [_experimentController activateExperiment:activeExperiment forServiceOrigin:gABTTestOrigin]; diff --git a/FirebaseABTesting/Tests/Unit/Resources/TestABTPayload1.txt b/FirebaseABTesting/Tests/Unit/Resources/TestABTPayload1.txt new file mode 100644 index 00000000000..6c6d6b43779 --- /dev/null +++ b/FirebaseABTesting/Tests/Unit/Resources/TestABTPayload1.txt @@ -0,0 +1,19 @@ +{ + "experimentId": "exp_1", + "variantId": "var_1", + "triggerEvent": "customTrigger", + "experimentStartTime": "2020-04-08T16:44:39.023Z", + "triggerTimeoutMillis": 15552000000, + "timeToLiveMillis": 15552000000, + "setEventToLog": "set_event", + "activateEventToLog": "activate_event", + "clearEventToLog": "clear_event", + "timeoutEventToLog": "timeout_event", + "ttlExpiryEventToLog": "ttl_expiry_event", + "overflowPolicy": 2, + "ongoingExperiments": [ + { + "experimentId": "exp_1" + } + ] +} diff --git a/FirebaseABTesting/Tests/Unit/Resources/TestABTPayload2.txt b/FirebaseABTesting/Tests/Unit/Resources/TestABTPayload2.txt new file mode 100644 index 00000000000..e30faaff96d --- /dev/null +++ b/FirebaseABTesting/Tests/Unit/Resources/TestABTPayload2.txt @@ -0,0 +1,18 @@ +{ + "experimentId": "exp_2", + "variantId": "v200", + "experimentStartTime": "2020-06-01T16:00:00.000Z", + "triggerTimeoutMillis": 15452000000, + "timeToLiveMillis": 15452000000, + "setEventToLog": "set_event_override", + "activateEventToLog": "activate_event_override", + "clearEventToLog": "clear_event_override", + "timeoutEventToLog": "timeout_event_override", + "ttlExpiryEventToLog": "ttl_expiry_event_override", + "overflowPolicy": 1, + "ongoingExperiments": [ + { + "experimentId": "exp_1" + } + ] +} diff --git a/FirebaseABTesting/Tests/Unit/Resources/TestABTPayload3.txt b/FirebaseABTesting/Tests/Unit/Resources/TestABTPayload3.txt new file mode 100644 index 00000000000..1de83a9a4db --- /dev/null +++ b/FirebaseABTesting/Tests/Unit/Resources/TestABTPayload3.txt @@ -0,0 +1,18 @@ +{ + "experimentId": "exp_3", + "variantId": "v200", + "experimentStartTime": "2020-08-29T00:00:00.000Z", + "triggerTimeoutMillis": 19552000000, + "timeToLiveMillis": 19552000000, + "setEventToLog": "set_event", + "activateEventToLog": "activate_event", + "clearEventToLog": "clear_event", + "timeoutEventToLog": "timeout_event", + "ttlExpiryEventToLog": "ttl_expiry_event", + "overflowPolicy": 0, + "ongoingExperiments": [ + { + "experimentId": "exp_2" + } + ] +} diff --git a/FirebaseABTesting/Tests/Unit/Resources/TestABTPayload4.txt b/FirebaseABTesting/Tests/Unit/Resources/TestABTPayload4.txt new file mode 100644 index 00000000000..e98a24c00d1 --- /dev/null +++ b/FirebaseABTesting/Tests/Unit/Resources/TestABTPayload4.txt @@ -0,0 +1,18 @@ +{ + "experimentId": "exp_1", + "variantId": "v3", + "experimentStartTime": "2021-05-09T00:00:00.000Z", + "triggerTimeoutMillis": 15892000000, + "timeToLiveMillis": 15892000000, + "setEventToLog": "set_event", + "activateEventToLog": "activate_event", + "clearEventToLog": "clear_event", + "timeoutEventToLog": "timeout_event", + "ttlExpiryEventToLog": "ttl_expiry_event", + "overflowPolicy": 2, + "ongoingExperiments": [ + { + "experimentId": "exp_2" + } + ] +} diff --git a/FirebaseABTesting/Tests/Unit/Resources/TestABTPayload5.txt b/FirebaseABTesting/Tests/Unit/Resources/TestABTPayload5.txt new file mode 100644 index 00000000000..f5192b2c8db --- /dev/null +++ b/FirebaseABTesting/Tests/Unit/Resources/TestABTPayload5.txt @@ -0,0 +1,12 @@ +{ + "experimentId": "exp_2", + "variantId": "v2", + "experimentStartTimeMillis": "143", + "triggerTimeoutMillis": 17392000000, + "timeToLiveMillis": 17392000000, + "setEventToLog": "set_event", + "activateEventToLog": "activate_event", + "clearEventToLog": "clear_event", + "timeoutEventToLog": "timeout_event", + "ttlExpiryEventToLog": "ttl_expiry_event" +} diff --git a/FirebaseABTesting/Tests/Unit/Resources/TestABTPayload6.txt b/FirebaseABTesting/Tests/Unit/Resources/TestABTPayload6.txt new file mode 100644 index 00000000000..308978ba462 --- /dev/null +++ b/FirebaseABTesting/Tests/Unit/Resources/TestABTPayload6.txt @@ -0,0 +1,13 @@ +{ + "experimentId": "exp_1", + "variantId": "var_2", + "experimentStartTime": "2020-04-08T16:44:39.023Z", + "triggerTimeoutMillis": 15552000000, + "timeToLiveMillis": 15552000000, + "setEventToLog": "set_event", + "activateEventToLog": "activate_event", + "clearEventToLog": "clear_event", + "timeoutEventToLog": "timeout_event", + "ttlExpiryEventToLog": "ttl_expiry_event", + "overflowPolicy": 2, +} diff --git a/FirebaseABTesting/Tests/Unit/Utilities/ABTTestUtilities.h b/FirebaseABTesting/Tests/Unit/Utilities/ABTTestUtilities.h new file mode 100644 index 00000000000..7fd27d3205b --- /dev/null +++ b/FirebaseABTesting/Tests/Unit/Utilities/ABTTestUtilities.h @@ -0,0 +1,33 @@ +// Copyright 2020 Google LLC +// +// 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 + +@class ABTExperimentPayload; + +NS_ASSUME_NONNULL_BEGIN + +@interface ABTTestUtilities : NSObject + +/// Generates an ABTExperimentPayload object from the test file directory. ++ (ABTExperimentPayload *)payloadFromTestFilename:(NSString *)filename; + +/// Generates a serialized JSON object from the test file directory. +/// @param modifiedStartTime clobbers the start time for the experiment from the test file. ++ (NSData *)payloadJSONDataFromFile:(NSString *)filename + modifiedStartTime:(nullable NSDate *)modifiedStartTime; + +@end + +NS_ASSUME_NONNULL_END diff --git a/FirebaseABTesting/Tests/Unit/Utilities/ABTTestUtilities.m b/FirebaseABTesting/Tests/Unit/Utilities/ABTTestUtilities.m new file mode 100644 index 00000000000..31c48acf63f --- /dev/null +++ b/FirebaseABTesting/Tests/Unit/Utilities/ABTTestUtilities.m @@ -0,0 +1,78 @@ +// Copyright 2020 Google LLC +// +// 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 "ABTTestUtilities.h" + +#import + +NS_ASSUME_NONNULL_BEGIN + +@implementation ABTTestUtilities + ++ (ABTExperimentPayload *)payloadFromTestFilename:(NSString *)filename { + NSString *testJsonDataFilePath = + [[NSBundle bundleForClass:[ABTTestUtilities class]] pathForResource:filename ofType:@"txt"]; + NSError *readTextError = nil; + NSString *fileText = [[NSString alloc] initWithContentsOfFile:testJsonDataFilePath + encoding:NSUTF8StringEncoding + error:&readTextError]; + if (readTextError) { + NSAssert(NO, readTextError.localizedDescription); + return nil; + } + return [ABTExperimentPayload parseFromData:[fileText dataUsingEncoding:NSUTF8StringEncoding]]; +} + ++ (NSData *)payloadJSONDataFromFile:(NSString *)filename + modifiedStartTime:(nullable NSDate *)modifiedStartTime { + NSString *testJsonDataFilePath = + [[NSBundle bundleForClass:[ABTTestUtilities class]] pathForResource:filename ofType:@"txt"]; + NSError *readTextError = nil; + NSString *fileText = [[NSString alloc] initWithContentsOfFile:testJsonDataFilePath + encoding:NSUTF8StringEncoding + error:&readTextError]; + + NSData *fileData = [fileText dataUsingEncoding:kCFStringEncodingUTF8]; + + NSError *jsonDictionaryError = nil; + NSMutableDictionary *jsonDictionary = + [[NSJSONSerialization JSONObjectWithData:fileData + options:kNilOptions + error:&jsonDictionaryError] mutableCopy]; + if (modifiedStartTime) { + jsonDictionary[@"experimentStartTime"] = + [[ABTTestUtilities class] dateStringForStartTime:modifiedStartTime]; + } + + NSError *jsonDataError = nil; + return [NSJSONSerialization dataWithJSONObject:jsonDictionary + options:kNilOptions + error:&jsonDataError]; +} + ++ (NSString *)dateStringForStartTime:(NSDate *)startTime { + NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; + [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"]; + [dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; + // Locale needs to be hardcoded. See + // https://developer.apple.com/library/ios/#qa/qa1480/_index.html for more details. + [dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]]; + [dateFormatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]]; + + return [dateFormatter stringFromDate:startTime]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/FirebaseInAppMessaging.podspec b/FirebaseInAppMessaging.podspec index da5707a33f2..281378ce139 100644 --- a/FirebaseInAppMessaging.podspec +++ b/FirebaseInAppMessaging.podspec @@ -51,7 +51,7 @@ See more product details at https://firebase.google.com/products/in-app-messagin s.dependency 'FirebaseCore', '~> 6.8' s.dependency 'FirebaseInstallations', '~> 1.1' - s.dependency 'FirebaseABTesting', '~> 3.2' + s.dependency 'FirebaseABTesting', '~> 4.0' s.dependency 'GoogleUtilities/Environment', '~> 6.7' s.dependency 'nanopb', '~> 1.30905.0' diff --git a/FirebaseInAppMessaging/CHANGELOG.md b/FirebaseInAppMessaging/CHANGELOG.md index 6d19ef4957e..6d629e44216 100644 --- a/FirebaseInAppMessaging/CHANGELOG.md +++ b/FirebaseInAppMessaging/CHANGELOG.md @@ -1,5 +1,6 @@ -# 2020-07 -- v0.22.0 +# 2020-07-06 -- v0.22.0 - [changed] Functionally neutral updated import references for dependencies. (#5902) +- [changed] Updated In-App Messaging to consume the Protobuf-less AB Testing SDK (#5890). # 2020-06-02 -- v0.20.2 - [fixed] Fixed log message for in-app messaging test on device flow (#5680). diff --git a/FirebaseInAppMessaging/Sources/Data/FIRIAMFetchResponseParser.m b/FirebaseInAppMessaging/Sources/Data/FIRIAMFetchResponseParser.m index 7536c84a7f0..54cceddb9f6 100644 --- a/FirebaseInAppMessaging/Sources/Data/FIRIAMFetchResponseParser.m +++ b/FirebaseInAppMessaging/Sources/Data/FIRIAMFetchResponseParser.m @@ -25,7 +25,7 @@ #import "FIRIAMTimeFetcher.h" #import "UIColor+FIRIAMHexString.h" -#import +#import @interface FIRIAMFetchResponseParser () @property(nonatomic) id timeFetcher; @@ -161,18 +161,8 @@ - (FIRIAMMessageDefinition *)convertToMessageDefinitionWithMessageDict:(NSDictio NSDictionary *experimentPayloadDictionary = payloadNode[@"experimentPayload"]; if (experimentPayloadDictionary) { - experimentPayload = [ABTExperimentPayload message]; - experimentPayload.experimentId = experimentPayloadDictionary[@"experimentId"]; - experimentPayload.experimentStartTimeMillis = - [experimentPayloadDictionary[@"experimentStartTimeMillis"] integerValue]; - // FIAM experiments always use the "discard oldest" overflow policy. - experimentPayload.overflowPolicy = - ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest; - experimentPayload.timeToLiveMillis = - [experimentPayloadDictionary[@"timeToLiveMillis"] integerValue]; - experimentPayload.triggerTimeoutMillis = - [experimentPayloadDictionary[@"triggerTimeoutMillis"] integerValue]; - experimentPayload.variantId = experimentPayloadDictionary[@"variantId"]; + experimentPayload = + [[ABTExperimentPayload alloc] initWithDictionary:experimentPayloadDictionary]; } NSTimeInterval startTimeInSeconds = 0; diff --git a/FirebaseInAppMessaging/Sources/Private/Data/FIRIAMMessageDefinition.h b/FirebaseInAppMessaging/Sources/Private/Data/FIRIAMMessageDefinition.h index 65fd02e816b..49ca18a4531 100644 --- a/FirebaseInAppMessaging/Sources/Private/Data/FIRIAMMessageDefinition.h +++ b/FirebaseInAppMessaging/Sources/Private/Data/FIRIAMMessageDefinition.h @@ -20,7 +20,7 @@ @class FIRIAMDisplayTriggerDefinition; -#import +@class ABTExperimentPayload; NS_ASSUME_NONNULL_BEGIN diff --git a/FirebaseInAppMessaging/Tests/Unit/FIRIAMDisplayExecutorTests.m b/FirebaseInAppMessaging/Tests/Unit/FIRIAMDisplayExecutorTests.m index 036b11fa4f2..6d265a4b2d7 100644 --- a/FirebaseInAppMessaging/Tests/Unit/FIRIAMDisplayExecutorTests.m +++ b/FirebaseInAppMessaging/Tests/Unit/FIRIAMDisplayExecutorTests.m @@ -24,6 +24,8 @@ #import "FIRInAppMessaging.h" #import "FIRInAppMessagingRenderingPrivate.h" +#import + // A class implementing protocol FIRIAMMessageContentData to be used for unit testing @interface FIRIAMMessageContentDataForTesting : NSObject @property(nonatomic, readwrite, nonnull) NSString *titleText; @@ -323,13 +325,16 @@ - (void)setupMessageTexture { contentData:m4ContentData renderingEffect:renderSetting4]; - ABTExperimentPayload *experimentPayload = [ABTExperimentPayload message]; - experimentPayload.experimentId = @"_exp_1"; - experimentPayload.experimentStartTimeMillis = 1582143484729; - experimentPayload.overflowPolicy = ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest; - experimentPayload.timeToLiveMillis = 15552000000; - experimentPayload.triggerTimeoutMillis = 15552000000; - experimentPayload.variantId = @"1"; + NSDictionary *experimentPayloadDictionary = @{ + @"experimentId" : @"_exp_1", + @"experimentStartTimeMillis" : @1582143484729, + @"overflowPolicy" : @"DISCARD_OLDEST", + @"timeToLiveMillis" : @15552000000, + @"triggerTimeoutMillis" : @15552000000, + @"variantId" : @"1" + }; + ABTExperimentPayload *experimentPayload = + [[ABTExperimentPayload alloc] initWithDictionary:experimentPayloadDictionary]; self.m4 = [[FIRIAMMessageDefinition alloc] initWithRenderData:renderData4 startTime:activeStartTime diff --git a/FirebaseRemoteConfig.podspec b/FirebaseRemoteConfig.podspec index 91497af6891..515d138af7a 100644 --- a/FirebaseRemoteConfig.podspec +++ b/FirebaseRemoteConfig.podspec @@ -44,7 +44,7 @@ app update. 'FIRRemoteConfig_VERSION=' + String(s.version), 'HEADER_SEARCH_PATHS' => '"${PODS_TARGET_SRCROOT}"' } - s.dependency 'FirebaseABTesting', '~> 3.1' + s.dependency 'FirebaseABTesting', '~> 4.0' s.dependency 'FirebaseCore', '~> 6.8' s.dependency 'FirebaseInstallations', '~> 1.1' s.dependency 'GoogleUtilities/Environment', '~> 6.7' @@ -70,7 +70,8 @@ app update. # Supply plist custom plist testing. unit_tests.resources = 'FirebaseRemoteConfig/Tests/Unit/Defaults-testInfo.plist', - 'FirebaseRemoteConfig/Tests/Unit/SecondApp-GoogleService-Info.plist' + 'FirebaseRemoteConfig/Tests/Unit/SecondApp-GoogleService-Info.plist', + 'FirebaseRemoteConfig/Tests/Unit/TestABTPayload.txt' unit_tests.requires_app_host = true unit_tests.dependency 'OCMock' unit_tests.requires_arc = true diff --git a/FirebaseRemoteConfig/CHANGELOG.md b/FirebaseRemoteConfig/CHANGELOG.md index 5f1015d1c63..6fdb239cd0d 100644 --- a/FirebaseRemoteConfig/CHANGELOG.md +++ b/FirebaseRemoteConfig/CHANGELOG.md @@ -1,5 +1,6 @@ # v4.7.0 - [changed] Functionally neutral updated import references for dependencies. (#5824) +- [changed] Updated Remote Config to consume the Protobuf-less AB Testing SDK (#5890). # v4.6.0 - [changed] Removed typedefs from public API method signatures to improve Swift API usage from Xcode. (#5748) diff --git a/FirebaseRemoteConfig/Sources/RCNConfigExperiment.m b/FirebaseRemoteConfig/Sources/RCNConfigExperiment.m index 682cc0a1547..0c4c3cd2352 100644 --- a/FirebaseRemoteConfig/Sources/RCNConfigExperiment.m +++ b/FirebaseRemoteConfig/Sources/RCNConfigExperiment.m @@ -16,7 +16,7 @@ #import "FirebaseRemoteConfig/Sources/RCNConfigExperiment.h" -#import +#import #import #import #import "FirebaseCore/Sources/Private/FirebaseCoreInternal.h" @@ -24,20 +24,6 @@ #import "FirebaseRemoteConfig/Sources/RCNConfigDefines.h" static NSString *const kExperimentMetadataKeyLastStartTime = @"last_experiment_start_time"; -/// Based on proto: -/// http://google3/googlemac/iPhone/Firebase/ABTesting/Source/Protos/developers/mobile/abt/proto/ExperimentPayload.pbobjc.m -static NSString *const kExperimentPayloadKeyExperimentID = @"experimentId"; -static NSString *const kExperimentPayloadKeyVariantID = @"variantId"; -static NSString *const kExperimentPayloadKeyExperimentStartTime = @"experimentStartTime"; -static NSString *const kExperimentPayloadKeyTriggerEvent = @"triggerEvent"; -static NSString *const kExperimentPayloadKeyTriggerTimeoutMillis = @"triggerTimeoutMillis"; -static NSString *const kExperimentPayloadKeyTimeToLiveMillis = @"timeToLiveMillis"; -static NSString *const kExperimentPayloadKeySetEventToLog = @"setEventToLog"; -static NSString *const kExperimentPayloadKeyActivateEventToLog = @"activateEventToLog"; -static NSString *const kExperimentPayloadKeyClearEventToLog = @"clearEventToLog"; -static NSString *const kExperimentPayloadKeyTimeoutEventToLog = @"timeoutEventToLog"; -static NSString *const kExperimentPayloadKeyTTLExpiryEventToLog = @"ttlExpiryEventToLog"; -static NSString *const kExperimentPayloadKeyOverflowPolicy = @"overflowPolicy"; static NSString *const kServiceOrigin = @"frc"; static NSString *const kMethodNameLatestStartTime = @@ -90,20 +76,11 @@ - (void)loadExperimentFromTable { if (result[@RCNExperimentTableKeyPayload]) { [strongSelf->_experimentPayloads removeAllObjects]; for (NSData *experiment in result[@RCNExperimentTableKeyPayload]) { - // Try to parse the experimentpayload as JSON. - NSError *error; - id experimentPayloadJSON = [NSJSONSerialization JSONObjectWithData:experiment - options:kNilOptions - error:&error]; - if (!experimentPayloadJSON || error) { + if (![NSJSONSerialization isValidJSONObject:experiment]) { FIRLogWarning(kFIRLoggerRemoteConfig, @"I-RCN000031", @"Experiment payload could not be parsed as JSON."); - // Add this as serialized proto. - [strongSelf->_experimentPayloads addObject:experiment]; } else { - // Convert to protobuf. - NSData *protoPayload = [self convertABTExperimentPayloadToProto:experimentPayloadJSON]; - [strongSelf->_experimentPayloads addObject:protoPayload]; + [strongSelf->_experimentPayloads addObject:experiment]; } } } @@ -114,49 +91,12 @@ - (void)loadExperimentFromTable { [_DBManager loadExperimentWithCompletionHandler:completionHandler]; } -/// This method converts the ABT experiment payload to a serialized protobuf which is consumable by -/// the ABT SDK. -- (NSData *)convertABTExperimentPayloadToProto:(NSDictionary *)experimentPayload { - ABTExperimentPayload *ABTExperiment = [[ABTExperimentPayload alloc] init]; - ABTExperiment.experimentId = experimentPayload[kExperimentPayloadKeyExperimentID]; - ABTExperiment.variantId = experimentPayload[kExperimentPayloadKeyVariantID]; - NSDate *experimentStartTime = [self.experimentStartTimeDateFormatter - dateFromString:experimentPayload[kExperimentPayloadKeyExperimentStartTime]]; - ABTExperiment.experimentStartTimeMillis = - [@([experimentStartTime timeIntervalSince1970] * 1000) longLongValue]; - ABTExperiment.triggerEvent = experimentPayload[kExperimentPayloadKeyTriggerEvent]; - ABTExperiment.triggerTimeoutMillis = - experimentPayload[kExperimentPayloadKeyTriggerTimeoutMillis] - ? atoll([experimentPayload[kExperimentPayloadKeyTriggerTimeoutMillis] UTF8String]) - : 0; - ABTExperiment.timeToLiveMillis = - experimentPayload[kExperimentPayloadKeyTimeToLiveMillis] - ? atoll([experimentPayload[kExperimentPayloadKeyTimeToLiveMillis] UTF8String]) - : 0; - ABTExperiment.setEventToLog = experimentPayload[kExperimentPayloadKeySetEventToLog]; - ABTExperiment.activateEventToLog = experimentPayload[kExperimentPayloadKeyActivateEventToLog]; - ABTExperiment.clearEventToLog = experimentPayload[kExperimentPayloadKeyClearEventToLog]; - ABTExperiment.timeoutEventToLog = experimentPayload[kExperimentPayloadKeyTimeoutEventToLog]; - ABTExperiment.ttlExpiryEventToLog = experimentPayload[kExperimentPayloadKeyTTLExpiryEventToLog]; - ABTExperiment.overflowPolicy = [experimentPayload[kExperimentPayloadKeyOverflowPolicy] intValue]; - - // Serialize the experiment payload. - NSData *serializedABTExperiment = ABTExperiment.data; - return serializedABTExperiment; -} - - (void)updateExperimentsWithResponse:(NSArray *> *)response { // cache fetched experiment payloads. [_experimentPayloads removeAllObjects]; [_DBManager deleteExperimentTableForKey:@RCNExperimentTableKeyPayload]; for (NSDictionary *experiment in response) { - NSData *protoPayload = [self convertABTExperimentPayloadToProto:experiment]; - [_experimentPayloads addObject:protoPayload]; - // We will add the new serialized JSON data to the database. - // TODO: (b/129272809). Eventually, RC and ABT need to be migrated to move off protos once - // (most) customers have migrated to using the new SDK (and hence saving the new JSON based - // payload in the database). NSError *error; NSData *JSONPayload = [NSJSONSerialization dataWithJSONObject:experiment options:kNilOptions @@ -164,11 +104,12 @@ - (void)updateExperimentsWithResponse:(NSArray *> * if (!JSONPayload || error) { FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000030", @"Invalid experiment payload to be serialized."); + } else { + [_experimentPayloads addObject:JSONPayload]; + [_DBManager insertExperimentTableWithKey:@RCNExperimentTableKeyPayload + value:JSONPayload + completionHandler:nil]; } - - [_DBManager insertExperimentTableWithKey:@RCNExperimentTableKeyPayload - value:JSONPayload - completionHandler:nil]; } } @@ -186,7 +127,7 @@ - (void)updateExperiments { [self.experimentController updateExperimentsWithServiceOrigin:kServiceOrigin events:lifecycleEvent - policy:ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest + policy:ABTExperimentPayloadExperimentOverflowPolicyDiscardOldest lastStartTime:lastStartTime payloads:_experimentPayloads]; #pragma clang diagnostic pop diff --git a/FirebaseRemoteConfig/Tests/Unit/RCNConfigExperimentTest.m b/FirebaseRemoteConfig/Tests/Unit/RCNConfigExperimentTest.m index 856e07feaf3..58b686b7fac 100644 --- a/FirebaseRemoteConfig/Tests/Unit/RCNConfigExperimentTest.m +++ b/FirebaseRemoteConfig/Tests/Unit/RCNConfigExperimentTest.m @@ -25,7 +25,7 @@ #import "FirebaseRemoteConfig/Sources/RCNConfigValue_Internal.h" #import "FirebaseRemoteConfig/Tests/Unit/RCNTestUtilities.h" -#import +#import #import #import @@ -220,16 +220,15 @@ - (void)testUpdateExperiments { updateExperimentsWithServiceOrigin:[OCMArg any] events:[OCMArg any] policy: - ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest // NOLINT + ABTExperimentPayloadExperimentOverflowPolicyDiscardOldest // NOLINT lastStartTime:lastStartTime payloads:[OCMArg any]]) .andDo(nil); #pragma clang diagnostic pop - ABTExperimentPayload *payload = [[ABTExperimentPayload alloc] init]; - payload.experimentStartTimeMillis = 12345678000; + NSData *payloadData = [[self class] payloadDataFromTestFile]; - experiment.experimentPayloads = [@[ payload.data ] mutableCopy]; + experiment.experimentPayloads = [@[ payloadData ] mutableCopy]; [experiment updateExperiments]; XCTAssertEqualObjects(experiment.experimentMetadata[@"last_experiment_start_time"], @(12345678)); @@ -238,13 +237,7 @@ - (void)testUpdateExperiments { #pragma mark Helpers. - (ABTExperimentPayload *)deserializeABTData:(NSData *)payload { - NSError *error; - ABTExperimentPayload *experimentPayload = [ABTExperimentPayload parseFromData:payload - error:&error]; - if (error) { - return nil; - } - return experimentPayload; + return [ABTExperimentPayload parseFromData:payload]; } - (int64_t)convertTimeToMillis:(NSString *)time { @@ -258,4 +251,26 @@ - (int64_t)convertTimeToMillis:(NSString *)time { NSDate *experimentStartTime = [dateFormatter dateFromString:time]; return [@([experimentStartTime timeIntervalSince1970] * 1000) longLongValue]; } + ++ (NSData *)payloadDataFromTestFile { + NSString *testJsonDataFilePath = + [[NSBundle bundleForClass:[self class]] pathForResource:@"TestABTPayload" ofType:@"txt"]; + NSError *readTextError = nil; + NSString *fileText = [[NSString alloc] initWithContentsOfFile:testJsonDataFilePath + encoding:NSUTF8StringEncoding + error:&readTextError]; + + NSData *fileData = [fileText dataUsingEncoding:kCFStringEncodingUTF8]; + + NSError *jsonDictionaryError = nil; + NSMutableDictionary *jsonDictionary = + [[NSJSONSerialization JSONObjectWithData:fileData + options:kNilOptions + error:&jsonDictionaryError] mutableCopy]; + NSError *jsonDataError = nil; + return [NSJSONSerialization dataWithJSONObject:jsonDictionary + options:kNilOptions + error:&jsonDataError]; +} + @end diff --git a/FirebaseRemoteConfig/Tests/Unit/TestABTPayload.txt b/FirebaseRemoteConfig/Tests/Unit/TestABTPayload.txt new file mode 100644 index 00000000000..fb0e71cc54f --- /dev/null +++ b/FirebaseRemoteConfig/Tests/Unit/TestABTPayload.txt @@ -0,0 +1,19 @@ +{ + "experimentId": "exp_1", + "variantId": "var_1", + "triggerEvent": "customTrigger", + "experimentStartTime": "1970-05-23T21:21:18.000Z", + "triggerTimeoutMillis": 15552000000, + "timeToLiveMillis": 15552000000, + "setEventToLog": "set_event", + "activateEventToLog": "activate_event", + "clearEventToLog": "clear_event", + "timeoutEventToLog": "timeout_event", + "ttlExpiryEventToLog": "ttl_expiry_event", + "overflowPolicy": 2, + "ongoingExperiments": [ + { + "experimentId": "exp_1" + } + ] +}