diff --git a/src/main/java/io/split/android/client/SplitClientFactoryImpl.java b/src/main/java/io/split/android/client/SplitClientFactoryImpl.java index 22a4195d6..69e7dc998 100644 --- a/src/main/java/io/split/android/client/SplitClientFactoryImpl.java +++ b/src/main/java/io/split/android/client/SplitClientFactoryImpl.java @@ -21,6 +21,7 @@ import io.split.android.client.storage.common.SplitStorageContainer; import io.split.android.client.storage.attributes.AttributesStorage; import io.split.android.client.storage.attributes.PersistentAttributesStorage; +import io.split.android.client.storage.splits.SplitsStorage; import io.split.android.client.telemetry.TelemetrySynchronizer; import io.split.android.client.telemetry.storage.TelemetryInitProducer; import io.split.android.client.validators.AttributesValidatorImpl; @@ -75,6 +76,7 @@ public SplitClientFactoryImpl(@NonNull SplitFactory splitFactory, mStorageContainer.getPersistentAttributesStorage()); mSplitParser = new SplitParser(mStorageContainer.getMySegmentsStorageContainer()); mSplitValidator = new SplitValidatorImpl(); + SplitsStorage splitsStorage = mStorageContainer.getSplitsStorage(); mTreatmentManagerFactory = new TreatmentManagerFactoryImpl( keyValidator, mSplitValidator, @@ -82,8 +84,9 @@ public SplitClientFactoryImpl(@NonNull SplitFactory splitFactory, config.labelsEnabled(), new AttributesMergerImpl(), mStorageContainer.getTelemetryStorage(), - new EvaluatorImpl(mStorageContainer.getSplitsStorage(), mSplitParser), - checkNotNull(configuredFlagSets) + mSplitParser, + configuredFlagSets, + splitsStorage ); } diff --git a/src/main/java/io/split/android/client/localhost/LocalhostSplitClient.java b/src/main/java/io/split/android/client/localhost/LocalhostSplitClient.java index d01393122..eeb8627d7 100644 --- a/src/main/java/io/split/android/client/localhost/LocalhostSplitClient.java +++ b/src/main/java/io/split/android/client/localhost/LocalhostSplitClient.java @@ -70,7 +70,8 @@ public LocalhostSplitClient(@NonNull LocalhostSplitFactory container, mTreatmentManager = new TreatmentManagerImpl(mKey.matchingKey(), mKey.bucketingKey(), mEvaluator, new KeyValidatorImpl(), new SplitValidatorImpl(), getImpressionsListener(splitClientConfig), - splitClientConfig.labelsEnabled(), eventsManager, attributesManager, attributesMerger, telemetryStorageProducer, configuredFlagSets); + splitClientConfig.labelsEnabled(), eventsManager, attributesManager, attributesMerger, + telemetryStorageProducer, configuredFlagSets, splitsStorage); } @Override diff --git a/src/main/java/io/split/android/client/service/splits/SplitsSyncHelper.java b/src/main/java/io/split/android/client/service/splits/SplitsSyncHelper.java index c3c8e8b9a..8f1b8ced5 100644 --- a/src/main/java/io/split/android/client/service/splits/SplitsSyncHelper.java +++ b/src/main/java/io/split/android/client/service/splits/SplitsSyncHelper.java @@ -84,6 +84,7 @@ private SplitTaskExecutionInfo sync(long till, boolean clearBeforeUpdate, boolea mTelemetryRuntimeProducer.recordSyncError(OperationType.SPLITS, e.getHttpStatus()); if (HttpStatus.fromCode(e.getHttpStatus()) == HttpStatus.URI_TOO_LONG) { + Logger.e("SDK initialization: the amount of flag sets provided is big, causing URI length error"); return SplitTaskExecutionInfo.error(SplitTaskType.SPLITS_SYNC, Collections.singletonMap(SplitTaskExecutionInfo.DO_NOT_RETRY, true)); } diff --git a/src/main/java/io/split/android/client/telemetry/model/Method.java b/src/main/java/io/split/android/client/telemetry/model/Method.java index da5a4d6a8..2303325d0 100644 --- a/src/main/java/io/split/android/client/telemetry/model/Method.java +++ b/src/main/java/io/split/android/client/telemetry/model/Method.java @@ -5,6 +5,10 @@ public enum Method { TREATMENTS("getTreatments"), TREATMENT_WITH_CONFIG("getTreatmentWithConfig"), TREATMENTS_WITH_CONFIG("getTreatmentsWithConfig"), + TREATMENTS_BY_FLAG_SET("getTreatmentsByFlagSet"), + TREATMENTS_BY_FLAG_SETS("getTreatmentsByFlagSets"), + TREATMENTS_WITH_CONFIG_BY_FLAG_SET("getTreatmentsWithConfigByFlagSet"), + TREATMENTS_WITH_CONFIG_BY_FLAG_SETS("getTreatmentsWithConfigByFlagSets"), TRACK("track"); private final String _method; diff --git a/src/main/java/io/split/android/client/validators/FlagSetsValidatorImpl.java b/src/main/java/io/split/android/client/validators/FlagSetsValidatorImpl.java index 72daeb055..2be41e83f 100644 --- a/src/main/java/io/split/android/client/validators/FlagSetsValidatorImpl.java +++ b/src/main/java/io/split/android/client/validators/FlagSetsValidatorImpl.java @@ -49,4 +49,9 @@ public List cleanup(List values) { return new ArrayList<>(cleanedUpSets); } + + @Override + public boolean isValid(String value) { + return value != null && value.trim().matches(FLAG_SET_REGEX); + } } diff --git a/src/main/java/io/split/android/client/validators/SplitFilterValidator.java b/src/main/java/io/split/android/client/validators/SplitFilterValidator.java index 95a521972..d605f17c0 100644 --- a/src/main/java/io/split/android/client/validators/SplitFilterValidator.java +++ b/src/main/java/io/split/android/client/validators/SplitFilterValidator.java @@ -5,4 +5,6 @@ public interface SplitFilterValidator { List cleanup(List values); + + boolean isValid(String value); } diff --git a/src/main/java/io/split/android/client/validators/TreatmentManager.java b/src/main/java/io/split/android/client/validators/TreatmentManager.java index 0f0d87895..fbb790052 100644 --- a/src/main/java/io/split/android/client/validators/TreatmentManager.java +++ b/src/main/java/io/split/android/client/validators/TreatmentManager.java @@ -1,5 +1,8 @@ package io.split.android.client.validators; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import java.util.List; import java.util.Map; import io.split.android.client.SplitResult; @@ -13,4 +16,12 @@ public interface TreatmentManager { Map getTreatments(List splits, Map attributes, boolean isClientDestroyed); Map getTreatmentsWithConfig(List splits, Map attributes, boolean isClientDestroyed); + + Map getTreatmentsByFlagSet(@NonNull String flagSet, @Nullable Map attributes, boolean isClientDestroyed); + + Map getTreatmentsByFlagSets(@NonNull List flagSets, @Nullable Map attributes, boolean isClientDestroyed); + + Map getTreatmentsWithConfigByFlagSet(@NonNull String flagSet, @Nullable Map attributes, boolean isClientDestroyed); + + Map getTreatmentsWithConfigByFlagSets(@NonNull List flagSets, @Nullable Map attributes, boolean isClientDestroyed); } diff --git a/src/main/java/io/split/android/client/validators/TreatmentManagerFactoryImpl.java b/src/main/java/io/split/android/client/validators/TreatmentManagerFactoryImpl.java index ce95d1441..956a94b32 100644 --- a/src/main/java/io/split/android/client/validators/TreatmentManagerFactoryImpl.java +++ b/src/main/java/io/split/android/client/validators/TreatmentManagerFactoryImpl.java @@ -7,12 +7,16 @@ import java.util.Set; import io.split.android.client.Evaluator; +import io.split.android.client.EvaluatorImpl; import io.split.android.client.api.Key; import io.split.android.client.attributes.AttributesManager; import io.split.android.client.attributes.AttributesMerger; +import io.split.android.client.events.ISplitEventsManager; import io.split.android.client.events.ListenableEventsManager; import io.split.android.client.impressions.ImpressionListener; +import io.split.android.client.storage.splits.SplitsStorage; import io.split.android.client.telemetry.storage.TelemetryStorageProducer; +import io.split.android.engine.experiments.SplitParser; public class TreatmentManagerFactoryImpl implements TreatmentManagerFactory { @@ -24,6 +28,7 @@ public class TreatmentManagerFactoryImpl implements TreatmentManagerFactory { private final TelemetryStorageProducer mTelemetryStorageProducer; private final Evaluator mEvaluator; private final Set mConfiguredFlagSets; + private final SplitsStorage mSplitsStorage; public TreatmentManagerFactoryImpl(@NonNull KeyValidator keyValidator, @NonNull SplitValidator splitValidator, @@ -31,16 +36,18 @@ public TreatmentManagerFactoryImpl(@NonNull KeyValidator keyValidator, boolean labelsEnabled, @NonNull AttributesMerger attributesMerger, @NonNull TelemetryStorageProducer telemetryStorageProducer, - @NonNull Evaluator evaluator, - @NonNull Set configuredFlagSets) { + @NonNull SplitParser splitParser, + @NonNull Set configuredFlagSets, + @NonNull SplitsStorage splitsStorage) { mKeyValidator = checkNotNull(keyValidator); mSplitValidator = checkNotNull(splitValidator); mCustomerImpressionListener = checkNotNull(customerImpressionListener); mLabelsEnabled = labelsEnabled; mAttributesMerger = checkNotNull(attributesMerger); mTelemetryStorageProducer = checkNotNull(telemetryStorageProducer); - mEvaluator = checkNotNull(evaluator); - mConfiguredFlagSets = configuredFlagSets; + mEvaluator = new EvaluatorImpl(splitsStorage, splitParser); + mConfiguredFlagSets = checkNotNull(configuredFlagSets); + mSplitsStorage = checkNotNull(splitsStorage); } @Override @@ -57,7 +64,8 @@ public TreatmentManager getTreatmentManager(Key key, ListenableEventsManager eve attributesManager, mAttributesMerger, mTelemetryStorageProducer, - mConfiguredFlagSets + mConfiguredFlagSets, + mSplitsStorage ); } } diff --git a/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java b/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java index ae52d9377..8aa242ebd 100644 --- a/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java +++ b/src/main/java/io/split/android/client/validators/TreatmentManagerImpl.java @@ -3,8 +3,12 @@ import static com.google.common.base.Preconditions.checkNotNull; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -19,6 +23,7 @@ import io.split.android.client.events.SplitEvent; import io.split.android.client.impressions.Impression; import io.split.android.client.impressions.ImpressionListener; +import io.split.android.client.storage.splits.SplitsStorage; import io.split.android.client.telemetry.model.Method; import io.split.android.client.telemetry.storage.TelemetryStorageProducer; import io.split.android.client.utils.logger.Logger; @@ -31,6 +36,10 @@ private static class ValidationTag { public static final String GET_TREATMENTS = "getTreatments"; public static final String GET_TREATMENT_WITH_CONFIG = "getTreatmentWithConfig"; public static final String GET_TREATMENTS_WITH_CONFIG = "getTreatmentsWithConfig"; + public static final String GET_TREATMENTS_BY_FLAG_SET = "getTreatmentsByFlagSet"; + public static final String GET_TREATMENTS_BY_FLAG_SETS = "getTreatmentsByFlagSets"; + public static final String GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET = "getTreatmentsWithConfigByFlagSet"; + public static final String GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS = "getTreatmentsWithConfigByFlagSets"; } private final String CLIENT_DESTROYED_MESSAGE = "Client has already been destroyed - no calls possible"; @@ -50,6 +59,8 @@ private static class ValidationTag { private final AttributesMerger mAttributesMerger; private final TelemetryStorageProducer mTelemetryStorageProducer; private final Set mConfiguredFlagSets; + private final SplitsStorage mSplitsStorage; + private final SplitFilterValidator mFlagSetsValidator; public TreatmentManagerImpl(String matchingKey, String bucketingKey, @@ -62,7 +73,8 @@ public TreatmentManagerImpl(String matchingKey, @NonNull AttributesManager attributesManager, @NonNull AttributesMerger attributesMerger, @NonNull TelemetryStorageProducer telemetryStorageProducer, - @NonNull Set configuredFlagSets) { + @NonNull Set configuredFlagSets, + @NonNull SplitsStorage splitsStorage) { mEvaluator = evaluator; mKeyValidator = keyValidator; mSplitValidator = splitValidator; @@ -76,6 +88,8 @@ public TreatmentManagerImpl(String matchingKey, mAttributesMerger = checkNotNull(attributesMerger); mTelemetryStorageProducer = checkNotNull(telemetryStorageProducer); mConfiguredFlagSets = checkNotNull(configuredFlagSets); + mSplitsStorage = checkNotNull(splitsStorage); + mFlagSetsValidator = new FlagSetsValidatorImpl(); } @Override @@ -166,6 +180,74 @@ public Map getTreatmentsWithConfig(List splits, Map return result; } + @Override + public Map getTreatmentsByFlagSet(@NonNull String flagSet, @Nullable Map attributes, boolean isClientDestroyed) { + String validationTag = ValidationTag.GET_TREATMENTS_BY_FLAG_SET; + Set names = getNamesFromSet(validationTag, Collections.singletonList(flagSet)); + if (isClientDestroyed) { + mValidationLogger.e(CLIENT_DESTROYED_MESSAGE, validationTag); + return controlTreatmentsForSplits(new ArrayList<>(names), validationTag); + } + + long start = System.currentTimeMillis(); + try { + return evaluateFeatures(names, attributes, validationTag, SplitResult::treatment); + } finally { + recordLatency(Method.TREATMENTS_BY_FLAG_SET, start); + } + } + + @Override + public Map getTreatmentsByFlagSets(@NonNull List flagSets, @Nullable Map attributes, boolean isClientDestroyed) { + String validationTag = ValidationTag.GET_TREATMENTS_BY_FLAG_SETS; + Set names = getNamesFromSet(validationTag, flagSets); + if (isClientDestroyed) { + mValidationLogger.e(CLIENT_DESTROYED_MESSAGE, validationTag); + return controlTreatmentsForSplits(new ArrayList<>(names), validationTag); + } + + long start = System.currentTimeMillis(); + try { + return evaluateFeatures(names, attributes, validationTag, SplitResult::treatment); + } finally { + recordLatency(Method.TREATMENTS_BY_FLAG_SETS, start); + } + } + + @Override + public Map getTreatmentsWithConfigByFlagSet(@NonNull String flagSet, @Nullable Map attributes, boolean isClientDestroyed) { + String validationTag = ValidationTag.GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET; + Set names = getNamesFromSet(validationTag, Collections.singletonList(flagSet)); + if (isClientDestroyed) { + mValidationLogger.e(CLIENT_DESTROYED_MESSAGE, validationTag); + return controlTreatmentsForSplitsWithConfig(new ArrayList<>(names), validationTag); + } + + long start = System.currentTimeMillis(); + try { + return evaluateFeatures(names, attributes, validationTag, ResultTransformer::identity); + } finally { + recordLatency(Method.TREATMENTS_WITH_CONFIG_BY_FLAG_SET, start); + } + } + + @Override + public Map getTreatmentsWithConfigByFlagSets(@NonNull List flagSets, @Nullable Map attributes, boolean isClientDestroyed) { + String validationTag = ValidationTag.GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS; + Set names = getNamesFromSet(validationTag, flagSets); + if (isClientDestroyed) { + mValidationLogger.e(CLIENT_DESTROYED_MESSAGE, validationTag); + return controlTreatmentsForSplitsWithConfig(new ArrayList<>(names), validationTag); + } + + long start = System.currentTimeMillis(); + try { + return evaluateFeatures(names, attributes, validationTag, ResultTransformer::identity); + } finally { + recordLatency(Method.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, start); + } + } + private SplitResult getTreatmentWithConfigWithoutMetrics(String split, Map attributes, String validationTag) { ValidationErrorInfo errorInfo = mKeyValidator.validate(mMatchingKey, mBucketingKey); @@ -285,4 +367,55 @@ private EvaluationResult evaluateIfReady(String splitName, private void recordLatency(Method treatment, long startTime) { mTelemetryStorageProducer.recordLatency(treatment, System.currentTimeMillis() - startTime); } + + @NonNull + private Set getNamesFromSet(String validationTag, + @NonNull List flagSets) { + + if (flagSets == null) { + return new HashSet<>(); + } + + List setsToEvaluate = new ArrayList<>(); + for (String flagSet : flagSets) { + if (setsToEvaluate.contains(flagSet)) { + continue; + } + + boolean isValid = mFlagSetsValidator.isValid(flagSet); + boolean isConfigured = mConfiguredFlagSets.isEmpty() || mConfiguredFlagSets.contains(flagSet); + + if (!isValid) { + mValidationLogger.e("you passed " + flagSet + " which is not valid.", validationTag); + } else if (!isConfigured) { + mValidationLogger.e("you passed " + flagSet + " which is not defined in the configuration.", validationTag); + } else { + setsToEvaluate.add(flagSet); + } + } + + if (setsToEvaluate.isEmpty()) { + return new HashSet<>(); + } + + return mSplitsStorage.getNamesByFlagSets(setsToEvaluate); + } + + private Map evaluateFeatures(Set names, @Nullable Map attributes, String validationTag, ResultTransformer transformer) { + Map result = new HashMap<>(); + for (String featureFlagName : names) { + SplitResult splitResult = getTreatmentWithConfigWithoutMetrics(featureFlagName, attributes, validationTag); + result.put(featureFlagName, transformer.transform(splitResult)); + } + return result; + } + + private interface ResultTransformer { + + T transform(SplitResult splitResult); + + static SplitResult identity(SplitResult splitResult) { + return splitResult; + } + } } diff --git a/src/test/java/io/split/android/client/TreatmentManagerTelemetryTest.java b/src/test/java/io/split/android/client/TreatmentManagerTelemetryTest.java index b93f59aa2..feb47d7ce 100644 --- a/src/test/java/io/split/android/client/TreatmentManagerTelemetryTest.java +++ b/src/test/java/io/split/android/client/TreatmentManagerTelemetryTest.java @@ -7,6 +7,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; @@ -23,6 +24,7 @@ import io.split.android.client.events.ListenableEventsManager; import io.split.android.client.events.SplitEvent; import io.split.android.client.impressions.ImpressionListener; +import io.split.android.client.storage.splits.SplitsStorage; import io.split.android.client.telemetry.model.Method; import io.split.android.client.telemetry.storage.TelemetryStorageProducer; import io.split.android.client.validators.KeyValidator; @@ -47,13 +49,16 @@ public class TreatmentManagerTelemetryTest { AttributesMerger attributesMerger; @Mock TelemetryStorageProducer telemetryStorageProducer; + @Mock + private SplitsStorage mSplitsStorage; private Set mConfiguredFlagSets = new HashSet<>(); private TreatmentManagerImpl treatmentManager; + private AutoCloseable mAutoCloseable; @Before public void setUp() { - MockitoAnnotations.openMocks(this); + mAutoCloseable = MockitoAnnotations.openMocks(this); treatmentManager = new TreatmentManagerImpl( "test_key", @@ -67,11 +72,21 @@ public void setUp() { attributesManager, attributesMerger, telemetryStorageProducer, - mConfiguredFlagSets); + mConfiguredFlagSets, + mSplitsStorage); when(evaluator.getTreatment(anyString(), anyString(), anyString(), anyMap())).thenReturn(new EvaluationResult("test", "label")); } + @After + public void tearDown() { + try { + mAutoCloseable.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + @Test public void getTreatmentRecordsLatencyInTelemetry() { diff --git a/src/test/java/io/split/android/client/TreatmentManagerTest.java b/src/test/java/io/split/android/client/TreatmentManagerTest.java index d1e8794a1..edb116fd3 100644 --- a/src/test/java/io/split/android/client/TreatmentManagerTest.java +++ b/src/test/java/io/split/android/client/TreatmentManagerTest.java @@ -7,7 +7,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; - import static io.split.android.client.TreatmentLabels.DEFINITION_NOT_FOUND; import com.google.common.base.Strings; @@ -57,10 +56,12 @@ public class TreatmentManagerTest { TelemetryStorageProducer telemetryStorageProducer = mock(TelemetryStorageProducer.class); private Set mConfiguredFlagSets; TreatmentManagerImpl treatmentManager; + private SplitsStorage mSplitsStorage; @Before public void loadSplitsFromFile() { mConfiguredFlagSets = new HashSet<>(); + mSplitsStorage = mock(SplitsStorage.class); treatmentManager = initializeTreatmentManager(); if (evaluator == null) { FileHelper fileHelper = new FileHelper(); @@ -322,7 +323,9 @@ private TreatmentManager createTreatmentManager(String matchingKey, String bucke return new TreatmentManagerImpl( matchingKey, bucketingKey, evaluator, new KeyValidatorImpl(), new SplitValidatorImpl(), - new ImpressionListenerMock(), config.labelsEnabled(), eventsManagerStub, mock(AttributesManager.class), mock(AttributesMerger.class), mock(TelemetryStorageProducer.class), mConfiguredFlagSets); + new ImpressionListenerMock(), config.labelsEnabled(), eventsManagerStub, + mock(AttributesManager.class), mock(AttributesMerger.class), + mock(TelemetryStorageProducer.class), mConfiguredFlagSets, mSplitsStorage); } private TreatmentManagerImpl initializeTreatmentManager() { @@ -350,7 +353,8 @@ private TreatmentManagerImpl initializeTreatmentManager(Evaluator evaluator) { attributesManager, mock(AttributesMerger.class), telemetryStorageProducer, - mConfiguredFlagSets); + mConfiguredFlagSets, + mSplitsStorage); } private Map splitsMap(List splits) { diff --git a/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java b/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java new file mode 100644 index 000000000..90539b17b --- /dev/null +++ b/src/test/java/io/split/android/client/TreatmentManagerWithFlagSetsTest.java @@ -0,0 +1,442 @@ +package io.split.android.client; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyMap; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import io.split.android.client.attributes.AttributesManager; +import io.split.android.client.attributes.AttributesMerger; +import io.split.android.client.events.ListenableEventsManager; +import io.split.android.client.events.SplitEvent; +import io.split.android.client.impressions.ImpressionListener; +import io.split.android.client.storage.splits.SplitsStorage; +import io.split.android.client.telemetry.model.Method; +import io.split.android.client.telemetry.storage.TelemetryStorageProducer; +import io.split.android.client.validators.KeyValidator; +import io.split.android.client.validators.SplitValidator; +import io.split.android.client.validators.TreatmentManagerImpl; + +public class TreatmentManagerWithFlagSetsTest { + + @Mock + private Evaluator mEvaluator; + @Mock + private KeyValidator mKeyValidator; + @Mock + private SplitValidator mSplitValidator; + @Mock + private ImpressionListener mImpressionListener; + @Mock + private ListenableEventsManager mEventsManager; + @Mock + private AttributesManager mAttributesManager; + @Mock + private AttributesMerger mAttributesMerger; + @Mock + private TelemetryStorageProducer mTelemetryStorageProducer; + @Mock + private SplitsStorage mSplitsStorage; + + private Set mConfiguredFlagSets; + private TreatmentManagerImpl mTreatmentManager; + private AutoCloseable mAutoCloseable; + + @Before + public void setUp() { + mAutoCloseable = MockitoAnnotations.openMocks(this); + + mConfiguredFlagSets = new HashSet<>(); + when(mEventsManager.eventAlreadyTriggered(SplitEvent.SDK_READY)).thenReturn(true); + when(mEventsManager.eventAlreadyTriggered(SplitEvent.SDK_READY_FROM_CACHE)).thenReturn(true); + + mTreatmentManager = new TreatmentManagerImpl( + "matching_key", + "bucketing_key", + mEvaluator, + mKeyValidator, + mSplitValidator, + mImpressionListener, + SplitClientConfig.builder().build().labelsEnabled(), + mEventsManager, + mAttributesManager, + mAttributesMerger, + mTelemetryStorageProducer, + mConfiguredFlagSets, + mSplitsStorage); + + when(mEvaluator.getTreatment(anyString(), anyString(), eq("test_1"), anyMap())) + .thenReturn(new EvaluationResult("result_1", "label")); + when(mEvaluator.getTreatment(anyString(), anyString(), eq("test_2"), anyMap())) + .thenReturn(new EvaluationResult("result_2", "label")); + } + + @After + public void tearDown() { + try { + mAutoCloseable.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Test + public void getTreatmentsByFlagSetDestroyedDoesNotUseEvaluator() { + mTreatmentManager.getTreatmentsByFlagSet("set_1", null, true); + + verify(mSplitsStorage).getNamesByFlagSets(any()); + verify(mEvaluator, times(0)).getTreatment(any(), any(), any(), anyMap()); + } + + @Test + public void getTreatmentsByFlagSetWithNoConfiguredSetsQueriesStorageAndUsesEvaluator() { + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) + .thenReturn(new HashSet<>(Collections.singletonList("test_1"))); + + mTreatmentManager.getTreatmentsByFlagSet("set_1", null, false); + + verify(mSplitsStorage).getNamesByFlagSets(Collections.singletonList("set_1")); + verify(mEvaluator).getTreatment(eq("matching_key"), eq("bucketing_key"), eq("test_1"), anyMap()); + } + + @Test + public void getTreatmentsByFlagSetWithNoConfiguredSetsInvalidSetDoesNotQueryStorageNorUseEvaluator() { + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) + .thenReturn(new HashSet<>(Collections.singletonList("test_split"))); + + mTreatmentManager.getTreatmentsByFlagSet("SET!", null, false); + + verify(mSplitsStorage, times(0)).getNamesByFlagSets(any()); + verify(mEvaluator, times(0)).getTreatment(any(), any(), any(), anyMap()); + } + + @Test + public void getTreatmentsByFlagSetWithConfiguredSetsExistingSetQueriesStorageAndUsesEvaluator() { + mConfiguredFlagSets.add("set_1"); + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) + .thenReturn(new HashSet<>(Collections.singletonList("test_1"))); + + mTreatmentManager.getTreatmentsByFlagSet("set_1", null, false); + + verify(mSplitsStorage).getNamesByFlagSets(Collections.singletonList("set_1")); + verify(mEvaluator).getTreatment(eq("matching_key"), eq("bucketing_key"), eq("test_1"), anyMap()); + } + + @Test + public void getTreatmentsByFlagSetWithConfiguredSetsNonExistingSetDoesNotQueryStorageNorUseEvaluator() { + mConfiguredFlagSets.add("set_1"); + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) + .thenReturn(new HashSet<>(Collections.singletonList("test_split"))); + + mTreatmentManager.getTreatmentsByFlagSet("set_2", null, false); + + verify(mSplitsStorage, times(0)).getNamesByFlagSets(any()); + verify(mEvaluator, times(0)).getTreatment(any(), any(), any(), anyMap()); + } + + @Test + public void getTreatmentsByFlagSetReturnsCorrectFormat() { + Set mockNames = new HashSet<>(); + mockNames.add("test_1"); + mockNames.add("test_2"); + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))).thenReturn(mockNames); + mConfiguredFlagSets.add("set_1"); + + Map result = mTreatmentManager.getTreatmentsByFlagSet("set_1", null, false); + + assertEquals(2, result.size()); + assertEquals("result_1", result.get("test_1")); + assertEquals("result_2", result.get("test_2")); + } + + @Test + public void getTreatmentsByFlagSetRecordsTelemetry() { + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))).thenReturn(Collections.singleton("test_1")); + + mTreatmentManager.getTreatmentsByFlagSet("set_1", null, false); + + verify(mTelemetryStorageProducer).recordLatency(eq(Method.TREATMENTS_BY_FLAG_SET), anyLong()); + } + + /// + @Test + public void getTreatmentsByFlagSetsDestroyedDoesNotUseEvaluator() { + mTreatmentManager.getTreatmentsByFlagSets(Collections.singletonList("set_1"), null, true); + + verify(mSplitsStorage).getNamesByFlagSets(any()); + verify(mEvaluator, times(0)).getTreatment(any(), any(), any(), anyMap()); + } + + @Test + public void getTreatmentsByFlagSetsWithNoConfiguredSetsQueriesStorageAndUsesEvaluator() { + when(mSplitsStorage.getNamesByFlagSets(Arrays.asList("set_1", "set_2"))) + .thenReturn(new HashSet<>(Arrays.asList("test_1", "test_2"))); + + mTreatmentManager.getTreatmentsByFlagSets(Arrays.asList("set_1", "set_2"), null, false); + + verify(mSplitsStorage).getNamesByFlagSets(Arrays.asList("set_1", "set_2")); + verify(mEvaluator).getTreatment(anyString(), anyString(), eq("test_1"), anyMap()); + verify(mEvaluator).getTreatment(anyString(), anyString(), eq("test_2"), anyMap()); + } + + @Test + public void getTreatmentsByFlagSetsWithNoConfiguredSetsInvalidSetDoesNotQueryStorageForInvalidSet() { + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) + .thenReturn(new HashSet<>(Collections.singletonList("test_1"))); + + mTreatmentManager.getTreatmentsByFlagSets(Arrays.asList("set_1", "SET!"), null, false); + + verify(mSplitsStorage).getNamesByFlagSets(Collections.singletonList("set_1")); + verify(mEvaluator).getTreatment(any(), any(), eq("test_1"), anyMap()); + } + + @Test + public void getTreatmentsByFlagSetsWithConfiguredSetsExistingSetQueriesStorageForConfiguredSetOnlyAndUsesEvaluator() { + mConfiguredFlagSets.add("set_1"); + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) + .thenReturn(new HashSet<>(Collections.singletonList("test_1"))); + + mTreatmentManager.getTreatmentsByFlagSets(Arrays.asList("set_1", "set_2"), null, false); + + verify(mSplitsStorage).getNamesByFlagSets(Collections.singletonList("set_1")); + verify(mEvaluator).getTreatment(anyString(), anyString(), eq("test_1"), anyMap()); + } + + @Test + public void getTreatmentsByFlagSetsWithConfiguredSetsNonExistingSetDoesNotQueryStorageNorUseEvaluator() { + mConfiguredFlagSets.add("set_1"); + + mTreatmentManager.getTreatmentsByFlagSets(Arrays.asList("set_2", "set_3"), null, false); + + verify(mSplitsStorage, times(0)).getNamesByFlagSets(any()); + verify(mEvaluator, times(0)).getTreatment(any(), any(), any(), anyMap()); + } + + @Test + public void getTreatmentsByFlagSetsReturnsCorrectFormat() { + Set mockNames = new HashSet<>(); + mockNames.add("test_1"); + mockNames.add("test_2"); + when(mSplitsStorage.getNamesByFlagSets(Arrays.asList("set_1", "set_2"))).thenReturn(mockNames); + + Map result = mTreatmentManager.getTreatmentsByFlagSets(Arrays.asList("set_1", "set_2"), null, false); + + assertEquals(2, result.size()); + assertEquals("result_1", result.get("test_1")); + assertEquals("result_2", result.get("test_2")); + } + + @Test + public void getTreatmentsByFlagSetsWithDuplicatedSetDeduplicates() { + mTreatmentManager.getTreatmentsByFlagSets(Arrays.asList("set_1", "set_1"), null, false); + + verify(mSplitsStorage).getNamesByFlagSets(Collections.singletonList("set_1")); + } + + @Test + public void getTreatmentsByFlagSetsWithNullSetListReturnsEmpty() { + Map result = mTreatmentManager.getTreatmentsByFlagSets(null, null, false); + + verify(mSplitsStorage, times(0)).getNamesByFlagSets(any()); + verify(mEvaluator, times(0)).getTreatment(any(), any(), any(), anyMap()); + assertEquals(0, result.size()); + } + + @Test + public void getTreatmentsByFlagSetsRecordsTelemetry() { + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))).thenReturn(Collections.singleton("test_1")); + + mTreatmentManager.getTreatmentsByFlagSets(Arrays.asList("set_1", "set_2"), null, false); + + verify(mTelemetryStorageProducer).recordLatency(eq(Method.TREATMENTS_BY_FLAG_SETS), anyLong()); + } + + /// + @Test + public void getTreatmentsWithConfigByFlagSetDestroyedDoesNotUseEvaluator() { + mTreatmentManager.getTreatmentsWithConfigByFlagSet("set_1", null, true); + + verify(mSplitsStorage).getNamesByFlagSets(any()); + verify(mEvaluator, times(0)).getTreatment(any(), any(), any(), anyMap()); + } + + @Test + public void getTreatmentsWithConfigByFlagSetWithNoConfiguredSetsQueriesStorageAndUsesEvaluator() { + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) + .thenReturn(new HashSet<>(Collections.singletonList("test_1"))); + + mTreatmentManager.getTreatmentsWithConfigByFlagSet("set_1", null, false); + + verify(mSplitsStorage).getNamesByFlagSets(Collections.singletonList("set_1")); + verify(mEvaluator).getTreatment(eq("matching_key"), eq("bucketing_key"), eq("test_1"), anyMap()); + } + + @Test + public void getTreatmentsWithConfigByFlagSetWithNoConfiguredSetsInvalidSetDoesNotQueryStorageNorUseEvaluator() { + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) + .thenReturn(new HashSet<>(Collections.singletonList("test_split"))); + + mTreatmentManager.getTreatmentsWithConfigByFlagSet("SET!", null, false); + + verify(mSplitsStorage, times(0)).getNamesByFlagSets(any()); + verify(mEvaluator, times(0)).getTreatment(any(), any(), any(), anyMap()); + } + + @Test + public void getTreatmentsWithConfigByFlagSetWithConfiguredSetsExistingSetQueriesStorageAndUsesEvaluator() { + mConfiguredFlagSets.add("set_1"); + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) + .thenReturn(new HashSet<>(Collections.singletonList("test_1"))); + + mTreatmentManager.getTreatmentsWithConfigByFlagSet("set_1", null, false); + + verify(mSplitsStorage).getNamesByFlagSets(Collections.singletonList("set_1")); + verify(mEvaluator).getTreatment(eq("matching_key"), eq("bucketing_key"), eq("test_1"), anyMap()); + } + + @Test + public void getTreatmentsWithConfigByFlagSetWithConfiguredSetsNonExistingSetDoesNotQueryStorageNorUseEvaluator() { + mConfiguredFlagSets.add("set_1"); + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) + .thenReturn(new HashSet<>(Collections.singletonList("test_split"))); + + mTreatmentManager.getTreatmentsWithConfigByFlagSet("set_2", null, false); + + verify(mSplitsStorage, times(0)).getNamesByFlagSets(any()); + verify(mEvaluator, times(0)).getTreatment(any(), any(), any(), anyMap()); + } + + @Test + public void getTreatmentsWithConfigByFlagSetReturnsCorrectFormat() { + Set mockNames = new HashSet<>(); + mockNames.add("test_1"); + mockNames.add("test_2"); + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))).thenReturn(mockNames); + mConfiguredFlagSets.add("set_1"); + + Map result = mTreatmentManager.getTreatmentsWithConfigByFlagSet("set_1", null, false); + + assertEquals(2, result.size()); + assertEquals("result_1", result.get("test_1").treatment()); + assertEquals("result_2", result.get("test_2").treatment()); + } + + @Test + public void getTreatmentsWithConfigByFlagSetRecordsTelemetry() { + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))).thenReturn(Collections.singleton("test_1")); + + mTreatmentManager.getTreatmentsWithConfigByFlagSet("set_1", null, false); + + verify(mTelemetryStorageProducer).recordLatency(eq(Method.TREATMENTS_WITH_CONFIG_BY_FLAG_SET), anyLong()); + } + + /// + @Test + public void getTreatmentsWithConfigByFlagSetsDestroyedDoesNotUseEvaluator() { + mTreatmentManager.getTreatmentsWithConfigByFlagSets(Collections.singletonList("set_1"), null, true); + + verify(mSplitsStorage).getNamesByFlagSets(any()); + verify(mEvaluator, times(0)).getTreatment(any(), any(), any(), anyMap()); + } + + @Test + public void getTreatmentsWithConfigByFlagSetsWithNoConfiguredSetsQueriesStorageAndUsesEvaluator() { + when(mSplitsStorage.getNamesByFlagSets(Arrays.asList("set_1", "set_2"))) + .thenReturn(new HashSet<>(Arrays.asList("test_1", "test_2"))); + + mTreatmentManager.getTreatmentsWithConfigByFlagSets(Arrays.asList("set_1", "set_2"), null, false); + + verify(mSplitsStorage).getNamesByFlagSets(Arrays.asList("set_1", "set_2")); + verify(mEvaluator).getTreatment(anyString(), anyString(), eq("test_1"), anyMap()); + verify(mEvaluator).getTreatment(anyString(), anyString(), eq("test_2"), anyMap()); + } + + @Test + public void getTreatmentsWithConfigByFlagSetsWithNoConfiguredSetsInvalidSetDoesNotQueryStorageForInvalidSet() { + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) + .thenReturn(new HashSet<>(Collections.singletonList("test_1"))); + + mTreatmentManager.getTreatmentsWithConfigByFlagSets(Arrays.asList("set_1", "SET!"), null, false); + + verify(mSplitsStorage).getNamesByFlagSets(Collections.singletonList("set_1")); + verify(mEvaluator).getTreatment(any(), any(), eq("test_1"), anyMap()); + } + + @Test + public void getTreatmentsWithConfigByFlagSetsWithConfiguredSetsExistingSetQueriesStorageForConfiguredSetOnlyAndUsesEvaluator() { + mConfiguredFlagSets.add("set_1"); + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))) + .thenReturn(new HashSet<>(Collections.singletonList("test_1"))); + + mTreatmentManager.getTreatmentsWithConfigByFlagSets(Arrays.asList("set_1", "set_2"), null, false); + + verify(mSplitsStorage).getNamesByFlagSets(Collections.singletonList("set_1")); + verify(mEvaluator).getTreatment(anyString(), anyString(), eq("test_1"), anyMap()); + } + + @Test + public void getTreatmentsWithConfigByFlagSetsWithConfiguredSetsNonExistingSetDoesNotQueryStorageNorUseEvaluator() { + mConfiguredFlagSets.add("set_1"); + + mTreatmentManager.getTreatmentsWithConfigByFlagSets(Arrays.asList("set_2", "set_3"), null, false); + + verify(mSplitsStorage, times(0)).getNamesByFlagSets(any()); + verify(mEvaluator, times(0)).getTreatment(any(), any(), any(), anyMap()); + } + + @Test + public void getTreatmentsWithConfigByFlagSetsReturnsCorrectFormat() { + Set mockNames = new HashSet<>(); + mockNames.add("test_1"); + mockNames.add("test_2"); + when(mSplitsStorage.getNamesByFlagSets(Arrays.asList("set_1", "set_2"))).thenReturn(mockNames); + + Map result = mTreatmentManager.getTreatmentsWithConfigByFlagSets(Arrays.asList("set_1", "set_2"), null, false); + + assertEquals(2, result.size()); + assertEquals("result_1", result.get("test_1").treatment()); + assertEquals("result_2", result.get("test_2").treatment()); + } + + @Test + public void getTreatmentsWithConfigByFlagSetsWithDuplicatedSetDeduplicates() { + mTreatmentManager.getTreatmentsWithConfigByFlagSets(Arrays.asList("set_1", "set_1"), null, false); + + verify(mSplitsStorage).getNamesByFlagSets(Collections.singletonList("set_1")); + } + + @Test + public void getTreatmentsWithConfigByFlagSetsWithNullSetListReturnsEmpty() { + Map result = mTreatmentManager.getTreatmentsWithConfigByFlagSets(null, null, false); + + verify(mSplitsStorage, times(0)).getNamesByFlagSets(any()); + verify(mEvaluator, times(0)).getTreatment(any(), any(), any(), anyMap()); + assertEquals(0, result.size()); + } + + @Test + public void getTreatmentsWithConfigByFlagSetsRecordsTelemetry() { + when(mSplitsStorage.getNamesByFlagSets(Collections.singletonList("set_1"))).thenReturn(Collections.singleton("test_1")); + + mTreatmentManager.getTreatmentsWithConfigByFlagSets(Arrays.asList("set_1", "set_2"), null, false); + + verify(mTelemetryStorageProducer).recordLatency(eq(Method.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS), anyLong()); + } +} diff --git a/src/test/java/io/split/android/client/utils/SplitClientImplFactory.java b/src/test/java/io/split/android/client/utils/SplitClientImplFactory.java index 3a8cb6388..03fe34446 100644 --- a/src/test/java/io/split/android/client/utils/SplitClientImplFactory.java +++ b/src/test/java/io/split/android/client/utils/SplitClientImplFactory.java @@ -38,8 +38,8 @@ public static SplitClientImpl get(Key key, SplitsStorage splitsStorage) { TelemetryStorage telemetryStorage = mock(TelemetryStorage.class); TreatmentManagerFactory treatmentManagerFactory = new TreatmentManagerFactoryImpl( new KeyValidatorImpl(), new SplitValidatorImpl(), new ImpressionListener.NoopImpressionListener(), - false, new AttributesMergerImpl(), telemetryStorage, new EvaluatorImpl(splitsStorage, splitParser), - Collections.emptySet()); + false, new AttributesMergerImpl(), telemetryStorage, splitParser, + Collections.emptySet(), splitsStorage); AttributesManager attributesManager = mock(AttributesManager.class); SplitClientImpl c = new SplitClientImpl(