فهرست منبع

Remove Protobuf from the ABT SDK (#5890)

* Remove Protobuf dependency, add struct-ish class that mimics ABTExperimentPayload

* Remove a lot of the compiler errors, but not all of them. We need to figure out how RC serializes the NSData objects passed to FIRExperimentController in updateExperimentsWithServiceOrigin:

* Add ABTExperimentPayload class that replaces generated proto, refactor classes that depend on it

* Add unit testing class for ABTExperimentPayload

* Updates to ABTExperimentPayload API, tests for ABTConditionalUserPropertyTest, add tests for ABTExperimentPayload

* Update podspec to pull in testing resources

* Add utility class for parsing test files. Use it in ABTExperimentPayloadTests

* Fix FIRExperimentController tests

* Better documentation

* Run scripts/styles.sh

* Fix whitespace

* Remove protobuf imports

* Bump pod spec version for ABT SDK, depend on new ABT SDK in RC. Fix up RemoteConfigExperiment to use new ABTExperimentPayload object

* scripts/style.sh

* Update In-app messaging to use new ABTExperimentPayload class, which involves extending the ABTExperimentPayload initializer to accept different values for overflowPolicy and experimentStartTime

* Delete test payload

* Revert scripts/check_imports.swift

* Better initialization tags for ABTExperimentLite

* Update CHANGELOGs

* Remove private headers in podspec, use integerValue to parse integers

* Refactor ABTExperimentPayload header into Private headers. Fix integer types for millisecond metadata

* scripts/styles.sh

* Fix integer parsing

* Fix copyrights

* Fix CHANGELOG typo:

* Add private_header_files to public_header_files

* Try just using NSInteger for experimentStartTimeMillis

* Bump FIAM dependency on ABTesting

* Use long long for milli parameters (32-bit apps)

* Use longlong in test timestamps

* Don't cast to nsnumber in tests

* Use in64_t

* Fix Test Overflow

* Bump major version on ABT SDK

* Bump ABTesting version in Firebase.podspec

Co-authored-by: Paul Beusterien <paulbeusterien@google.com>
christibbs 5 سال پیش
والد
کامیت
9b484519f2
33فایلهای تغییر یافته به همراه901 افزوده شده و 895 حذف شده
  1. 1 1
      Firebase.podspec
  2. 5 6
      FirebaseABTesting.podspec
  3. 3 0
      FirebaseABTesting/CHANGELOG.md
  4. 2 3
      FirebaseABTesting/Sources/ABTConditionalUserPropertyController.h
  5. 9 9
      FirebaseABTesting/Sources/ABTConditionalUserPropertyController.m
  6. 151 0
      FirebaseABTesting/Sources/ABTExperimentPayload.m
  7. 11 10
      FirebaseABTesting/Sources/FIRExperimentController.m
  8. 96 0
      FirebaseABTesting/Sources/Private/ABTExperimentPayload.h
  9. 0 3
      FirebaseABTesting/Sources/Protos/PortableProtoFilterTemplate.asciipb
  10. 0 179
      FirebaseABTesting/Sources/Protos/developers/mobile/abt/proto/ExperimentPayload.pbobjc.h
  11. 0 330
      FirebaseABTesting/Sources/Protos/developers/mobile/abt/proto/ExperimentPayload.pbobjc.m
  12. 4 4
      FirebaseABTesting/Sources/Public/FIRExperimentController.h
  13. 109 81
      FirebaseABTesting/Tests/Unit/ABTConditionalUserPropertyControllerTest.m
  14. 124 0
      FirebaseABTesting/Tests/Unit/ABTExperimentPayloadTest.m
  15. 99 164
      FirebaseABTesting/Tests/Unit/FIRExperimentControllerTest.m
  16. 19 0
      FirebaseABTesting/Tests/Unit/Resources/TestABTPayload1.txt
  17. 18 0
      FirebaseABTesting/Tests/Unit/Resources/TestABTPayload2.txt
  18. 18 0
      FirebaseABTesting/Tests/Unit/Resources/TestABTPayload3.txt
  19. 18 0
      FirebaseABTesting/Tests/Unit/Resources/TestABTPayload4.txt
  20. 12 0
      FirebaseABTesting/Tests/Unit/Resources/TestABTPayload5.txt
  21. 13 0
      FirebaseABTesting/Tests/Unit/Resources/TestABTPayload6.txt
  22. 33 0
      FirebaseABTesting/Tests/Unit/Utilities/ABTTestUtilities.h
  23. 78 0
      FirebaseABTesting/Tests/Unit/Utilities/ABTTestUtilities.m
  24. 1 1
      FirebaseInAppMessaging.podspec
  25. 2 1
      FirebaseInAppMessaging/CHANGELOG.md
  26. 3 13
      FirebaseInAppMessaging/Sources/Data/FIRIAMFetchResponseParser.m
  27. 1 1
      FirebaseInAppMessaging/Sources/Private/Data/FIRIAMMessageDefinition.h
  28. 12 7
      FirebaseInAppMessaging/Tests/Unit/FIRIAMDisplayExecutorTests.m
  29. 3 2
      FirebaseRemoteConfig.podspec
  30. 1 0
      FirebaseRemoteConfig/CHANGELOG.md
  31. 9 68
      FirebaseRemoteConfig/Sources/RCNConfigExperiment.m
  32. 27 12
      FirebaseRemoteConfig/Tests/Unit/RCNConfigExperimentTest.m
  33. 19 0
      FirebaseRemoteConfig/Tests/Unit/TestABTPayload.txt

+ 1 - 1
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|

+ 5 - 6
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

+ 3 - 0
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:)`.

+ 2 - 3
FirebaseABTesting/Sources/ABTConditionalUserPropertyController.h

@@ -12,10 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#import <FirebaseABTesting/ABTExperimentPayload.h>
 #import <Foundation/Foundation.h>
 
-#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.

+ 9 - 9
FirebaseABTesting/Sources/ABTConditionalUserPropertyController.m

@@ -73,7 +73,7 @@
 - (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 @@
   }
 
   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 @@
          [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

+ 151 - 0
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<NSString *, id> *)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<ABTExperimentLite *> *ongoingExperiments = [[NSMutableArray alloc] init];
+
+    NSArray<NSDictionary<NSString *, NSString *> *> *ongoingExperimentsArray =
+        dictionary[kExperimentPayloadKeyOngoingExperiments];
+
+    for (NSDictionary<NSString *, NSString *> *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

+ 11 - 10
FirebaseABTesting/Sources/FIRExperimentController.m

@@ -14,6 +14,7 @@
 
 #import <FirebaseABTesting/FIRExperimentController.h>
 
+#import <FirebaseABTesting/ABTExperimentPayload.h>
 #import <FirebaseABTesting/FIRLifecycleEvents.h>
 #import "FirebaseABTesting/Sources/ABTConditionalUserPropertyController.h"
 #import "FirebaseABTesting/Sources/ABTConstants.h"
@@ -34,19 +35,19 @@ add -DFIRABTesting_VERSION=... to the build invocation"
 #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 @@ NSArray *ABTExperimentsToClearFromPayloads(
 
 - (void)updateExperimentsWithServiceOrigin:(NSString *)origin
                                     events:(FIRLifecycleEvents *)events
-                                    policy:(ABTExperimentPayload_ExperimentOverflowPolicy)policy
+                                    policy:(ABTExperimentPayloadExperimentOverflowPolicy)policy
                              lastStartTime:(NSTimeInterval)lastStartTime
                                   payloads:(NSArray<NSData *> *)payloads
                          completionHandler:
@@ -192,7 +193,7 @@ NSArray *ABTExperimentsToClearFromPayloads(
 
 - (void)updateExperimentsWithServiceOrigin:(NSString *)origin
                                     events:(FIRLifecycleEvents *)events
-                                    policy:(ABTExperimentPayload_ExperimentOverflowPolicy)policy
+                                    policy:(ABTExperimentPayloadExperimentOverflowPolicy)policy
                              lastStartTime:(NSTimeInterval)lastStartTime
                                   payloads:(NSArray<NSData *> *)payloads {
   [self updateExperimentsWithServiceOrigin:origin
@@ -207,7 +208,7 @@ NSArray *ABTExperimentsToClearFromPayloads(
     updateExperimentConditionalUserPropertiesWithServiceOrigin:(NSString *)origin
                                                         events:(FIRLifecycleEvents *)events
                                                         policy:
-                                                            (ABTExperimentPayload_ExperimentOverflowPolicy)
+                                                            (ABTExperimentPayloadExperimentOverflowPolicy)
                                                                 policy
                                                  lastStartTime:(NSTimeInterval)lastStartTime
                                                       payloads:(NSArray<NSData *> *)payloads
@@ -325,7 +326,7 @@ NSArray *ABTExperimentsToClearFromPayloads(
   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

+ 96 - 0
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 <Foundation/Foundation.h>
+
+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<ABTExperimentLite *> *ongoingExperiments;
+
+/// Parses an ABTExperimentPayload directly from JSON data.
+/// @param data  JSON object as NSData. Must be reconstructible as an NSDictionary<NSString* , id>.
++ (instancetype)parseFromData:(NSData *)data;
+
+/// Initializes an ABTExperimentPayload from a dictionary with experiment metadata.
+- (instancetype)initWithDictionary:(NSDictionary<NSString *, id> *)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

+ 0 - 3
FirebaseABTesting/Sources/Protos/PortableProtoFilterTemplate.asciipb

@@ -1,3 +0,0 @@
-allowed_message: "developers.mobile.abt.ExperimentLite"
-allowed_message: "developers.mobile.abt.ExperimentPayload"
-allowed_enum: "developers.mobile.abt.ExperimentPayload.ExperimentOverflowPolicy"

+ 0 - 179
FirebaseABTesting/Sources/Protos/developers/mobile/abt/proto/ExperimentPayload.pbobjc.h

@@ -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 <Protobuf/GPBProtocolBuffers.h>
-#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<ABTExperimentLite*> *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)

+ 0 - 330
FirebaseABTesting/Sources/Protos/developers/mobile/abt/proto/ExperimentPayload.pbobjc.m

@@ -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 <Protobuf/GPBProtocolBuffers_RuntimeSupport.h>
-#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)

+ 4 - 4
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<NSData *> *)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<NSData *> *)payloads
     DEPRECATED_MSG_ATTRIBUTE("Please use updateExperimentsWithServiceOrigin:events:policy:"

+ 109 - 81
FirebaseABTesting/Tests/Unit/ABTConditionalUserPropertyControllerTest.m

@@ -14,6 +14,7 @@
 
 #import <XCTest/XCTest.h>
 
+#import <FirebaseABTesting/ABTExperimentPayload.h>
 #import <FirebaseABTesting/FIRExperimentController.h>
 #import <FirebaseABTesting/FIRLifecycleEvents.h>
 #import <OCMock/OCMock.h>
@@ -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 @@
 - (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<FIRAnalyticsInterop>)analytics;
 @end
@@ -47,6 +49,10 @@ typedef void (^FakeAnalyticsLogEventWithOriginNameParametersHandler)(
 }
 @end
 
+@interface ABTExperimentPayload (Testing)
+@property(nonatomic, readwrite) ABTExperimentPayloadExperimentOverflowPolicy overflowPolicy;
+@end
+
 @implementation ABTConditionalUserPropertyControllerTest
 - (void)setUp {
   [super setUp];
@@ -76,64 +82,68 @@ typedef void (^FakeAnalyticsLogEventWithOriginNameParametersHandler)(
 
 #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 @@ typedef void (^FakeAnalyticsLogEventWithOriginNameParametersHandler)(
   // 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 @@ typedef void (^FakeAnalyticsLogEventWithOriginNameParametersHandler)(
 }
 
 - (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 @@ typedef void (^FakeAnalyticsLogEventWithOriginNameParametersHandler)(
 }
 
 - (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 @@ typedef void (^FakeAnalyticsLogEventWithOriginNameParametersHandler)(
 }
 
 - (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 @@ typedef void (^FakeAnalyticsLogEventWithOriginNameParametersHandler)(
   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 @@ typedef void (^FakeAnalyticsLogEventWithOriginNameParametersHandler)(
                         @"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 @@ typedef void (^FakeAnalyticsLogEventWithOriginNameParametersHandler)(
   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 @@ typedef void (^FakeAnalyticsLogEventWithOriginNameParametersHandler)(
   NSDictionary<NSString *, NSString *> *experiment =
       @{@"name" : @"exp_1", @"value" : @"variant_control_group"};
 
-  ABTExperimentPayload *payload = [[ABTExperimentPayload alloc] init];
-  payload.experimentId = @"exp_2";
-  payload.variantId = @"variant_group_A";
+  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.");
 }

+ 124 - 0
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 <XCTest/XCTest.h>
+
+#import <FirebaseABTesting/ABTExperimentPayload.h>
+#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

+ 99 - 164
FirebaseABTesting/Tests/Unit/FIRExperimentControllerTest.m

@@ -14,6 +14,7 @@
 
 #import <XCTest/XCTest.h>
 
+#import <FirebaseABTesting/ABTExperimentPayload.h>
 #import <FirebaseABTesting/FIRExperimentController.h>
 #import <FirebaseABTesting/FIRLifecycleEvents.h>
 #import <OCMock/OCMock.h>
@@ -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 @@ extern NSArray *ABTExperimentsToClearFromPayloads(
     updateExperimentConditionalUserPropertiesWithServiceOrigin:(NSString *)origin
                                                         events:(FIRLifecycleEvents *)events
                                                         policy:
-                                                            (ABTExperimentPayload_ExperimentOverflowPolicy)
+                                                            (ABTExperimentPayloadExperimentOverflowPolicy)
                                                                 policy
                                                  lastStartTime:(NSTimeInterval)lastStartTime
                                                       payloads:(NSArray<NSData *> *)payloads
@@ -59,9 +61,9 @@ extern NSArray *ABTExperimentsToClearFromPayloads(
 - (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 @@ extern NSArray *ABTExperimentsToClearFromPayloads(
   NSString *sampleString = @"sample_invalid_payload";
   NSData *invalidData = [sampleString dataUsingEncoding:NSUTF8StringEncoding];
   XCTAssertNil(ABTDeserializeExperimentPayload(invalidData));
-  XCTAssertNotNil(ABTDeserializeExperimentPayload(nil));
 }
 
 - (void)testLifecycleEvents {
@@ -144,52 +145,35 @@ extern NSArray *ABTExperimentsToClearFromPayloads(
       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 @@ extern NSArray *ABTExperimentsToClearFromPayloads(
   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 @@ extern NSArray *ABTExperimentsToClearFromPayloads(
 - (void)testLatestExperimentStartTimestamps {
   // Mock incoming payloads
   NSMutableArray<NSData *> *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 @@ extern NSArray *ABTExperimentsToClearFromPayloads(
   NSDictionary<NSString *, NSString *> *CUP1 = @{@"name" : @"exp_1", @"value" : @"v1"};
   [currentExperiments addObject:CUP1];
 
-  NSDictionary<NSString *, NSString *> *CUP2 = @{@"name" : @"exp_2", @"value" : @"v2"};
+  NSDictionary<NSString *, NSString *> *CUP2 = @{@"name" : @"exp_2", @"value" : @"v200"};
   [currentExperiments addObject:CUP2];
 
-  // Mock incoming payloads
   NSMutableArray<NSData *> *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 @@ extern NSArray *ABTExperimentsToClearFromPayloads(
   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 @@ extern NSArray *ABTExperimentsToClearFromPayloads(
   NSDictionary<NSString *, NSString *> *CUP2 = @{@"name" : @"exp_2", @"value" : @"v2"};
   [currentExperiments addObject:CUP2];
 
-  // Mock incoming payloads
   NSMutableArray<NSData *> *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 @@ extern NSArray *ABTExperimentsToClearFromPayloads(
       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<NSData *> *payloads = [[NSMutableArray alloc] init];
@@ -342,7 +316,7 @@ extern NSArray *ABTExperimentsToClearFromPayloads(
       updateExperimentConditionalUserPropertiesWithServiceOrigin:gABTTestOrigin
                                                           events:events
                                                           policy:
-                                                              ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest  // NOLINT
+                                                              ABTExperimentPayloadExperimentOverflowPolicyDiscardOldest  // NOLINT
                                                    lastStartTime:-1
                                                         payloads:payloads
                                                completionHandler:^(NSError *_Nullable error) {
@@ -362,8 +336,8 @@ extern NSArray *ABTExperimentsToClearFromPayloads(
 
   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 @@ extern NSArray *ABTExperimentsToClearFromPayloads(
 }
 
 - (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 @@ extern NSArray *ABTExperimentsToClearFromPayloads(
 }
 
 - (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 @@ extern NSArray *ABTExperimentsToClearFromPayloads(
 }
 
 - (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 @@ extern NSArray *ABTExperimentsToClearFromPayloads(
 }
 
 - (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];
 

+ 19 - 0
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"
+    }
+  ]
+}

+ 18 - 0
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"
+    }
+  ]
+}

+ 18 - 0
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"
+    }
+  ]
+}

+ 18 - 0
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"
+    }
+  ]
+}

+ 12 - 0
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"
+}

+ 13 - 0
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,
+}

+ 33 - 0
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 <Foundation/Foundation.h>
+
+@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

+ 78 - 0
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 <FirebaseABTesting/ABTExperimentPayload.h>
+
+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

+ 1 - 1
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'
 

+ 2 - 1
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).

+ 3 - 13
FirebaseInAppMessaging/Sources/Data/FIRIAMFetchResponseParser.m

@@ -25,7 +25,7 @@
 #import "FIRIAMTimeFetcher.h"
 #import "UIColor+FIRIAMHexString.h"
 
-#import <FirebaseABTesting/ExperimentPayload.pbobjc.h>
+#import <FirebaseABTesting/ABTExperimentPayload.h>
 
 @interface FIRIAMFetchResponseParser ()
 @property(nonatomic) id<FIRIAMTimeFetcher> timeFetcher;
@@ -161,18 +161,8 @@
     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;

+ 1 - 1
FirebaseInAppMessaging/Sources/Private/Data/FIRIAMMessageDefinition.h

@@ -20,7 +20,7 @@
 
 @class FIRIAMDisplayTriggerDefinition;
 
-#import <FirebaseABTesting/ExperimentPayload.pbobjc.h>
+@class ABTExperimentPayload;
 
 NS_ASSUME_NONNULL_BEGIN
 

+ 12 - 7
FirebaseInAppMessaging/Tests/Unit/FIRIAMDisplayExecutorTests.m

@@ -24,6 +24,8 @@
 #import "FIRInAppMessaging.h"
 #import "FIRInAppMessagingRenderingPrivate.h"
 
+#import <FirebaseABTesting/ABTExperimentPayload.h>
+
 // A class implementing protocol FIRIAMMessageContentData to be used for unit testing
 @interface FIRIAMMessageContentDataForTesting : NSObject <FIRIAMMessageContentData>
 @property(nonatomic, readwrite, nonnull) NSString *titleText;
@@ -323,13 +325,16 @@ typedef NS_ENUM(NSInteger, FIRInAppMessagingDelegateInteraction) {
                                              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

+ 3 - 2
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

+ 1 - 0
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)

+ 9 - 68
FirebaseRemoteConfig/Sources/RCNConfigExperiment.m

@@ -16,7 +16,7 @@
 
 #import "FirebaseRemoteConfig/Sources/RCNConfigExperiment.h"
 
-#import <FirebaseABTesting/ExperimentPayload.pbobjc.h>
+#import <FirebaseABTesting/ABTExperimentPayload.h>
 #import <FirebaseABTesting/FIRExperimentController.h>
 #import <FirebaseABTesting/FIRLifecycleEvents.h>
 #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 @@ static NSString *const kMethodNameLatestStartTime =
     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 @@ static NSString *const kMethodNameLatestStartTime =
   [_DBManager loadExperimentWithCompletionHandler:completionHandler];
 }
 
-/// This method converts the ABT experiment payload to a serialized protobuf which is consumable by
-/// the ABT SDK.
-- (NSData *)convertABTExperimentPayloadToProto:(NSDictionary<NSString *, id> *)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<NSDictionary<NSString *, id> *> *)response {
   // cache fetched experiment payloads.
   [_experimentPayloads removeAllObjects];
   [_DBManager deleteExperimentTableForKey:@RCNExperimentTableKeyPayload];
 
   for (NSDictionary<NSString *, id> *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 @@ static NSString *const kMethodNameLatestStartTime =
     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 @@ static NSString *const kMethodNameLatestStartTime =
   [self.experimentController
       updateExperimentsWithServiceOrigin:kServiceOrigin
                                   events:lifecycleEvent
-                                  policy:ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest
+                                  policy:ABTExperimentPayloadExperimentOverflowPolicyDiscardOldest
                            lastStartTime:lastStartTime
                                 payloads:_experimentPayloads];
 #pragma clang diagnostic pop

+ 27 - 12
FirebaseRemoteConfig/Tests/Unit/RCNConfigExperimentTest.m

@@ -25,7 +25,7 @@
 #import "FirebaseRemoteConfig/Sources/RCNConfigValue_Internal.h"
 #import "FirebaseRemoteConfig/Tests/Unit/RCNTestUtilities.h"
 
-#import <FirebaseABTesting/ExperimentPayload.pbobjc.h>
+#import <FirebaseABTesting/ABTExperimentPayload.h>
 #import <FirebaseABTesting/FIRExperimentController.h>
 
 #import <OCMock/OCMock.h>
@@ -220,16 +220,15 @@
           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 @@
 #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 @@
   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

+ 19 - 0
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"
+    }
+  ]
+}