diff --git a/client/src/main/java/io/split/Spec.java b/client/src/main/java/io/split/Spec.java index 9e03a59ab..847e19e1c 100644 --- a/client/src/main/java/io/split/Spec.java +++ b/client/src/main/java/io/split/Spec.java @@ -6,6 +6,8 @@ private Spec() { // restrict instantiation } - public static final String SPEC_VERSION = "1.1"; + public static String SPEC_VERSION = "1.3"; + public static final String SPEC_1_3 = "1.3"; + public static final String SPEC_1_1 = "1.1"; } diff --git a/client/src/main/java/io/split/client/HttpSplitChangeFetcher.java b/client/src/main/java/io/split/client/HttpSplitChangeFetcher.java index a3e234a3e..980bc067c 100644 --- a/client/src/main/java/io/split/client/HttpSplitChangeFetcher.java +++ b/client/src/main/java/io/split/client/HttpSplitChangeFetcher.java @@ -2,6 +2,7 @@ import com.google.common.annotations.VisibleForTesting; +import io.split.Spec; import io.split.client.dtos.SplitChange; import io.split.client.dtos.SplitHttpResponse; import io.split.client.exceptions.UriTooLongException; @@ -31,6 +32,7 @@ public final class HttpSplitChangeFetcher implements SplitChangeFetcher { private static final Logger _log = LoggerFactory.getLogger(HttpSplitChangeFetcher.class); private static final String SINCE = "since"; + private static final String RB_SINCE = "rbSince"; private static final String TILL = "till"; private static final String SETS = "sets"; private static final String SPEC = "s"; @@ -56,38 +58,48 @@ long makeRandomTill() { } @Override - public SplitChange fetch(long since, FetchOptions options) { + public SplitChange fetch(long since, long sinceRBS, FetchOptions options) { long start = System.currentTimeMillis(); + for (int i=0; i<2; i++) { + try { + URIBuilder uriBuilder = new URIBuilder(_target).addParameter(SPEC, "" + SPEC_VERSION); + uriBuilder.addParameter(SINCE, "" + since); + if (SPEC_VERSION.equals(Spec.SPEC_1_3)) { + uriBuilder.addParameter(RB_SINCE, "" + sinceRBS); + } + if (!options.flagSetsFilter().isEmpty()) { + uriBuilder.addParameter(SETS, "" + options.flagSetsFilter()); + } + if (options.hasCustomCN()) { + uriBuilder.addParameter(TILL, "" + options.targetCN()); + } + URI uri = uriBuilder.build(); + SplitHttpResponse response = _client.get(uri, options, null); - try { - URIBuilder uriBuilder = new URIBuilder(_target).addParameter(SPEC, "" + SPEC_VERSION); - uriBuilder.addParameter(SINCE, "" + since); - if (!options.flagSetsFilter().isEmpty()) { - uriBuilder.addParameter(SETS, "" + options.flagSetsFilter()); - } - if (options.hasCustomCN()) { - uriBuilder.addParameter(TILL, "" + options.targetCN()); - } - URI uri = uriBuilder.build(); - SplitHttpResponse response = _client.get(uri, options, null); - - if (response.statusCode() < HttpStatus.SC_OK || response.statusCode() >= HttpStatus.SC_MULTIPLE_CHOICES) { - if (response.statusCode() == HttpStatus.SC_REQUEST_URI_TOO_LONG) { - _log.error("The amount of flag sets provided are big causing uri length error."); - throw new UriTooLongException(String.format("Status code: %s. Message: %s", response.statusCode(), response.statusMessage())); + if (response.statusCode() < HttpStatus.SC_OK || response.statusCode() >= HttpStatus.SC_MULTIPLE_CHOICES) { + if (response.statusCode() == HttpStatus.SC_REQUEST_URI_TOO_LONG) { + _log.error("The amount of flag sets provided are big causing uri length error."); + throw new UriTooLongException(String.format("Status code: %s. Message: %s", response.statusCode(), response.statusMessage())); + } + if (response.statusCode() == HttpStatus.SC_BAD_REQUEST && response.statusMessage().equals("unknown spec")) { + _log.warn(String.format("Detected old spec response, falling back to spec 1.1")); + SPEC_VERSION = Spec.SPEC_1_1; + continue; + } + _telemetryRuntimeProducer.recordSyncError(ResourceEnum.SPLIT_SYNC, response.statusCode()); + throw new IllegalStateException( + String.format("Could not retrieve splitChanges since %s; http return code %s", since, response.statusCode()) + ); } - _telemetryRuntimeProducer.recordSyncError(ResourceEnum.SPLIT_SYNC, response.statusCode()); - throw new IllegalStateException( - String.format("Could not retrieve splitChanges since %s; http return code %s", since, response.statusCode()) - ); + return Json.fromJson(response.body(), SplitChange.class); + } catch (Exception e) { + throw new IllegalStateException(String.format("Problem fetching splitChanges since %s: %s", since, e), e); + } finally { + _telemetryRuntimeProducer.recordSyncLatency(HTTPLatenciesEnum.SPLITS, System.currentTimeMillis() - start); } - return Json.fromJson(response.body(), SplitChange.class); - } catch (Exception e) { - throw new IllegalStateException(String.format("Problem fetching splitChanges since %s: %s", since, e), e); - } finally { - _telemetryRuntimeProducer.recordSyncLatency(HTTPLatenciesEnum.SPLITS, System.currentTimeMillis()-start); } + return null; } @VisibleForTesting diff --git a/client/src/main/java/io/split/client/JsonLocalhostSplitChangeFetcher.java b/client/src/main/java/io/split/client/JsonLocalhostSplitChangeFetcher.java index e2cb5d5c9..502d44182 100644 --- a/client/src/main/java/io/split/client/JsonLocalhostSplitChangeFetcher.java +++ b/client/src/main/java/io/split/client/JsonLocalhostSplitChangeFetcher.java @@ -29,7 +29,7 @@ public JsonLocalhostSplitChangeFetcher(InputStreamProvider inputStreamProvider) } @Override - public SplitChange fetch(long since, FetchOptions options) { + public SplitChange fetch(long since, long RBsince, FetchOptions options) { try { JsonReader jsonReader = new JsonReader(new BufferedReader(new InputStreamReader(_inputStreamProvider.get(), StandardCharsets.UTF_8))); SplitChange splitChange = Json.fromJson(jsonReader, SplitChange.class); diff --git a/client/src/main/java/io/split/client/LegacyLocalhostSplitChangeFetcher.java b/client/src/main/java/io/split/client/LegacyLocalhostSplitChangeFetcher.java index a35c92cfe..f2f83d653 100644 --- a/client/src/main/java/io/split/client/LegacyLocalhostSplitChangeFetcher.java +++ b/client/src/main/java/io/split/client/LegacyLocalhostSplitChangeFetcher.java @@ -34,7 +34,7 @@ public LegacyLocalhostSplitChangeFetcher(String directory) { } @Override - public SplitChange fetch(long since, FetchOptions options) { + public SplitChange fetch(long since, long sinceRBS, FetchOptions options) { try (BufferedReader reader = new BufferedReader(new FileReader(_splitFile))) { SplitChange splitChange = new SplitChange(); diff --git a/client/src/main/java/io/split/client/SplitClient.java b/client/src/main/java/io/split/client/SplitClient.java index a07718b1f..e12b5319f 100644 --- a/client/src/main/java/io/split/client/SplitClient.java +++ b/client/src/main/java/io/split/client/SplitClient.java @@ -2,6 +2,7 @@ import io.split.client.api.Key; import io.split.client.api.SplitResult; +import io.split.client.dtos.EvaluationOptions; import java.util.List; import java.util.Map; @@ -96,7 +97,7 @@ public interface SplitClient { /** * Same as {@link #getTreatment(String, String)} but it returns the configuration associated to the - * matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null. + * matching treatment if any. Otherwise {@link SplitResult.config()} will be null. *

*

* Examples include showing a different treatment to users on trial plan @@ -112,7 +113,7 @@ public interface SplitClient { /** * Same as {@link #getTreatment(String, String, Map)} but it returns the configuration associated to the - * matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null. + * matching treatment if any. Otherwise {@link SplitResult.config()} will be null. *

*

* Examples include showing a different treatment to users on trial plan @@ -129,7 +130,7 @@ public interface SplitClient { /** * Same as {@link #getTreatment(Key, String, Map)} but it returns the configuration associated to the - * matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null. + * matching treatment if any. Otherwise {@link SplitResult.config()} will be null. * * @param key the matching and bucketing keys. MUST NOT be null. * @param featureFlagName the name of the feature flag we want to evaluate. MUST NOT be null. @@ -224,7 +225,7 @@ public interface SplitClient { /** * Same as {@link #getTreatments(String, List)} but it returns the configuration associated to the - * matching treatments if any. Otherwise {@link SplitResult.configurations()} will be null. + * matching treatments if any. Otherwise {@link SplitResult.config()} will be null. *

*

* Examples include showing a different treatment to users on trial plan @@ -240,7 +241,7 @@ public interface SplitClient { /** * Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the - * matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null. + * matching treatment if any. Otherwise {@link SplitResult.config()} will be null. *

*

* Examples include showing a different treatment to users on trial plan @@ -257,7 +258,7 @@ public interface SplitClient { /** * Same as {@link #getTreatments(Key, List, Map)} but it returns for each feature flag the configuration associated to the - * matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null. + * matching treatment if any. Otherwise {@link SplitResult.config()} will be null. * * @param key the matching and bucketing keys. MUST NOT be null. * @param featureFlagNames the names of the feature flags we want to evaluate. MUST NOT be null. @@ -270,7 +271,7 @@ public interface SplitClient { /** * Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the - * matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null. + * matching treatment if any. Otherwise {@link SplitResult.config()} will be null. *

*

* Examples include showing a different treatment to users on trial plan @@ -285,7 +286,7 @@ public interface SplitClient { /** * Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the - * matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null. + * matching treatment if any. Otherwise {@link SplitResult.config()} will be null. *

*

* Examples include showing a different treatment to users on trial plan @@ -301,7 +302,7 @@ public interface SplitClient { /** * Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the - * matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null. + * matching treatment if any. Otherwise {@link SplitResult.config()} will be null. *

*

* Examples include showing a different treatment to users on trial plan @@ -317,7 +318,7 @@ public interface SplitClient { /** * Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the - * matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null. + * matching treatment if any. Otherwise {@link SplitResult.config()} will be null. *

*

* Examples include showing a different treatment to users on trial plan @@ -332,7 +333,7 @@ public interface SplitClient { /** * Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the - * matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null. + * matching treatment if any. Otherwise {@link SplitResult.config()} will be null. *

*

* Examples include showing a different treatment to users on trial plan @@ -348,7 +349,7 @@ public interface SplitClient { /** * Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the - * matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null. + * matching treatment if any. Otherwise {@link SplitResult.config()} will be null. *

*

* Examples include showing a different treatment to users on trial plan @@ -364,7 +365,7 @@ public interface SplitClient { /** * Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the - * matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null. + * matching treatment if any. Otherwise {@link SplitResult.config()} will be null. *

*

* Examples include showing a different treatment to users on trial plan @@ -380,7 +381,7 @@ public interface SplitClient { /** * Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the - * matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null. + * matching treatment if any. Otherwise {@link SplitResult.config()} will be null. *

*

* Examples include showing a different treatment to users on trial plan @@ -397,7 +398,7 @@ public interface SplitClient { /** * Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the - * matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null. + * matching treatment if any. Otherwise {@link SplitResult.config()} will be null. *

*

* Examples include showing a different treatment to users on trial plan @@ -414,7 +415,7 @@ public interface SplitClient { /** * Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the - * matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null. + * matching treatment if any. Otherwise {@link SplitResult.config()} will be null. *

*

* Examples include showing a different treatment to users on trial plan @@ -430,7 +431,7 @@ public interface SplitClient { /** * Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the - * matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null. + * matching treatment if any. Otherwise {@link SplitResult.config()} will be null. *

*

* Examples include showing a different treatment to users on trial plan @@ -447,7 +448,7 @@ public interface SplitClient { /** * Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the - * matching treatment if any. Otherwise {@link SplitResult.configurations()} will be null. + * matching treatment if any. Otherwise {@link SplitResult.config()} will be null. *

*

* Examples include showing a different treatment to users on trial plan @@ -462,6 +463,488 @@ public interface SplitClient { */ Map getTreatmentsWithConfigByFlagSets(Key key, List flagSets, Map attributes); + /** + * Returns the treatment to show this key for this feature flag. The set of treatments + * for a feature flag can be configured on the Split user interface. + *

+ *

+ * This method returns the string 'control' if: + *

    + *
  1. Any of the parameters were null
  2. + *
  3. There was an exception in evaluating the treatment
  4. + *
  5. The SDK does not know of the existence of this feature flag
  6. + *
  7. The feature flag was deleted through the Split user interface.
  8. + *
+ * 'control' is a reserved treatment (you cannot create a treatment with the + * same name) to highlight these exceptional circumstances. + *

+ *

+ * The sdk returns the default treatment of this feature flag if: + *

    + *
  1. The feature flag was killed
  2. + *
  3. The key did not match any of the conditions in the feature flag roll-out plan
  4. + *
+ * The default treatment of a feature flag is set on the Split user interface. + *

+ *

+ * This method does not throw any exceptions. It also never returns null. + * + * @param key a unique key of your customer (e.g. user_id, user_email, account_id, etc.) MUST not be null. + * @param featureFlagName the name of the feature flag we want to evaluate. MUST NOT be null. + * @param evaluationOptions additional data to attach to the impression. + * @return the evaluated treatment, the default treatment of this feature flag, or 'control'. + */ + String getTreatment(String key, String featureFlagName, EvaluationOptions evaluationOptions); + + /** + * This method is useful when you want to determine the treatment to show + * to an customer (user, account etc.) based on an attribute of that customer + * instead of it's key. + *

+ *

+ * Examples include showing a different treatment to users on trial plan + * vs. premium plan. Another example is to show a different treatment + * to users created after a certain date. + * + * @param key a unique key of your customer (e.g. user_id, user_email, account_id, etc.) MUST not be null. + * @param featureFlagName the name of the feature flag we want to evaluate. MUST NOT be null. + * @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty. + * @param evaluationOptions additional data to attach to the impression. + * @return the evaluated treatment, the default treatment of this feature flag, or 'control'. + */ + String getTreatment(String key, String featureFlagName, Map attributes, EvaluationOptions evaluationOptions); + + /** + * To understand why this method is useful, consider the following simple Feature Flag as an example: + * + * if user is in segment employees then feature flag 100%:on + * else if user is in segment all then feature flag 20%:on,80%:off + * + * There are two concepts here: matching and bucketing. Matching + * refers to ‘user is in segment employees’ or ‘user is in segment + * all’ whereas bucketing refers to ‘100%:on’ or ‘20%:on,80%:off’. + * + * By default, the same customer key is used for both matching and + * bucketing. However, for some advanced use cases, you may want + * to use different keys. For such cases, use this method. + * + * As an example, suppose you want to rollout to percentages of + * users in specific accounts. You can achieve that by matching + * via account id, but bucketing by user id. + * + * Another example is when you want to ensure that a user continues to get + * the same treatment after they sign up for your product that they used + * to get when they were simply a visitor to your site. In that case, + * before they sign up, you can use their visitor id for both matching and bucketing, but + * post log-in you can use their user id for matching and visitor id for bucketing. + * + * + * @param key the matching and bucketing keys. MUST NOT be null. + * @param featureFlagName the name of the feature flag we want to evaluate. MUST NOT be null. + * @param attributes of the entity (user, account etc.) to use in evaluation. Can be null or empty. + * @param evaluationOptions additional data to attach to the impression. + * + * @return the evaluated treatment, the default treatment of this feature flag, or 'control'. + */ + String getTreatment(Key key, String featureFlagName, Map attributes, EvaluationOptions evaluationOptions); + + /** + * Returns a map of feature flag name and treatments to show this key for these feature flags. The set of treatments + * for a feature flag can be configured on the Split user interface. + *

+ *

+ * This method returns for each feature flag the string 'control' if: + *

    + *
  1. Any of the parameters were null
  2. + *
  3. There was an exception in evaluating the treatment
  4. + *
  5. The SDK does not know of the existence of this feature flag
  6. + *
  7. The feature flag was deleted through the Split user interface.
  8. + *
+ * 'control' is a reserved treatment (you cannot create a treatment with the + * same name) to highlight these exceptional circumstances. + *

+ *

+ * The sdk returns for each feature flag the default treatment of this feature flag if: + *

    + *
  1. The feature flag was killed
  2. + *
  3. The key did not match any of the conditions in the feature flag roll-out plan
  4. + *
+ * The default treatment of a feature flag is set on the Split user interface. + *

+ *

+ * This method does not throw any exceptions. It also never returns null. + * + * @param key a unique key of your customer (e.g. user_id, user_email, account_id, etc.) MUST not be null. + * @param featureFlagNames the names of the feature flags we want to evaluate. MUST NOT be null. + * @param evaluationOptions additional data to attach to the impression. + * @return for each feature flag the evaluated treatment, the default treatment for each feature flag, or 'control'. + */ + Map getTreatments(String key, List featureFlagNames, EvaluationOptions evaluationOptions); + + /** + * This method is useful when you want to determine the treatments to show + * to a customer (user, account etc.) based on an attribute of that customer + * instead of their key. + *

+ *

+ * Examples include showing different treatments to users on trial plan + * vs. premium plan. Another example is to show different treatments + * to users created after a certain date. + * + * @param key a unique key of your customer (e.g. user_id, user_email, account_id, etc.) MUST not be null. + * @param featureFlagNames the names of the feature flags we want to evaluate. MUST NOT be null. + * @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty. + * @param evaluationOptions additional data to attach to the impression. + * @return the evaluated treatment, the default treatment of this feature flag, or 'control'. + */ + Map getTreatments(String key, List featureFlagNames, Map attributes, EvaluationOptions evaluationOptions); + + /** + * To understand why this method is useful, consider the following simple Feature Flag as an example: + * + * if user is in segment employees then feature flag 100%:on + * else if user is in segment all then feature flag 20%:on,80%:off + * + * There are two concepts here: matching and bucketing. Matching + * refers to ‘user is in segment employees’ or ‘user is in segment + * all’ whereas bucketing refers to ‘100%:on’ or ‘20%:on,80%:off’. + * + * By default, the same customer key is used for both matching and + * bucketing. However, for some advanced use cases, you may want + * to use different keys. For such cases, use this method. + * + * As an example, suppose you want to rollout to percentages of + * users in specific accounts. You can achieve that by matching + * via account id, but bucketing by user id. + * + * Another example is when you want to ensure that a user continues to get + * the same treatment after they sign up for your product that they used + * to get when they were simply a visitor to your site. In that case, + * before they sign up, you can use their visitor id for both matching and bucketing, but + * post log-in you can use their user id for matching and visitor id for bucketing. + * + * + * @param key the matching and bucketing keys. MUST NOT be null. + * @param featureFlagNames the names of the feature flags we want to evaluate. MUST NOT be null. + * @param attributes of the entity (user, account etc.) to use in evaluation. Can be null or empty. + * @param evaluationOptions additional data to attach to the impression. + * + * @return for each feature flag the evaluated treatment, the default treatment of the feature flag, or 'control'. + */ + Map getTreatments(Key key, List featureFlagNames, Map attributes, EvaluationOptions evaluationOptions); + + /** + * Same as {@link #getTreatment(String, String)} but it returns the configuration associated to the + * matching treatment if any. Otherwise {@link SplitResult.config()} will be null. + *

+ *

+ * Examples include showing a different treatment to users on trial plan + * vs. premium plan. Another example is to show a different treatment + * to users created after a certain date. + * + * @param key a unique key of your customer (e.g. user_id, user_email, account_id, etc.) MUST not be null. + * @param featureFlagName the name of the feature flag we want to evaluate. MUST NOT be null. + * @param evaluationOptions additional data to attach to the impression. + * @return SplitResult containing the evaluated treatment (the default treatment of this feature flag, or 'control') and + * a configuration associated to this treatment if set. + */ + SplitResult getTreatmentWithConfig(String key, String featureFlagName, EvaluationOptions evaluationOptions); + + /** + * Same as {@link #getTreatment(Key, String, Map)} but it returns the configuration associated to the + * matching treatment if any. Otherwise {@link SplitResult.config()} will be null. + * + * @param key the matching and bucketing keys. MUST NOT be null. + * @param featureFlagName the name of the feature flag we want to evaluate. MUST NOT be null. + * @param attributes of the entity (user, account etc.) to use in evaluation. Can be null or empty. + * @param evaluationOptions additional data to attach to the impression. + * + * @return SplitResult containing the evaluated treatment (the default treatment of this feature flag, or 'control') and + * a configuration associated to this treatment if set. + */ + SplitResult getTreatmentWithConfig(Key key, String featureFlagName, Map attributes, EvaluationOptions evaluationOptions); + + /** + * Same as {@link #getTreatment(String, String, Map)} but it returns the configuration associated to the + * matching treatment if any. Otherwise {@link SplitResult.config()} will be null. + *

+ *

+ * Examples include showing a different treatment to users on trial plan + * vs. premium plan. Another example is to show a different treatment + * to users created after a certain date. + * + * @param key a unique key of your customer (e.g. user_id, user_email, account_id, etc.) MUST not be null. + * @param featureFlagName the name of the feature flag we want to evaluate. MUST NOT be null. + * @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty. + * @param evaluationOptions additional data to attach to the impression. + * @return SplitResult containing the evaluated treatment (the default treatment of this feature flag, or 'control') and + * a configuration associated to this treatment if set. + */ + SplitResult getTreatmentWithConfig(String key, String featureFlagName, Map attributes, EvaluationOptions evaluationOptions); + + /** + * Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the + * matching treatment if any. Otherwise {@link SplitResult.config()} will be null. + *

+ *

+ * Examples include showing a different treatment to users on trial plan + * vs. premium plan. Another example is to show a different treatment + * to users created after a certain date. + * + * @param key a unique key of your customer (e.g. user_id, user_email, account_id, etc.) MUST not be null. + * @param featureFlagNames the names of the feature flags we want to evaluate. MUST NOT be null. + * @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty. + * @param evaluationOptions additional data to attach to the impression. + * @return for each feature flag a SplitResult containing the evaluated treatment (the default treatment of this feature flag, or 'control') and + * a configuration associated to this treatment if set. + */ + Map getTreatmentsWithConfig(String key, List featureFlagNames, Map attributes, + EvaluationOptions evaluationOptions); + + /** + * Same as {@link #getTreatments(String, List)} but it returns the configuration associated to the + * matching treatments if any. Otherwise {@link SplitResult.config()} will be null. + *

+ *

+ * Examples include showing a different treatment to users on trial plan + * vs. premium plan. Another example is to show a different treatment + * to users created after a certain date. + * + * @param key a unique key of your customer (e.g. user_id, user_email, account_id, etc.) MUST not be null. + * @param featureFlagNames the names of the feature flags we want to evaluate. MUST NOT be null. + * @param evaluationOptions additional data to attach to the impression. + * @return Map containing for each feature flag the evaluated treatment (the default treatment of + * this feature flag, or 'control') and a configuration associated to this treatment if set. + */ + Map getTreatmentsWithConfig(String key, List featureFlagNames, EvaluationOptions evaluationOptions); + + /** + * Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the + * matching treatment if any. Otherwise {@link SplitResult.config()} will be null. + *

+ *

+ * Examples include showing a different treatment to users on trial plan + * vs. premium plan. Another example is to show a different treatment + * to users created after a certain date. + * + * @param key a unique key of your customer (e.g. user_id, user_email, account_id, etc.) MUST not be null or empty. + * @param flagSet the Flag Set name that you want to evaluate. MUST not be null or empty. + * @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty. + * @param evaluationOptions additional data to attach to the impression. + * @return for each feature flag the evaluated treatment, the default treatment of this feature flag, or 'control'. + */ + Map getTreatmentsByFlagSet(String key, String flagSet, Map attributes, EvaluationOptions evaluationOptions); + + /** + * Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the + * matching treatment if any. Otherwise {@link SplitResult.config()} will be null. + *

+ *

+ * Examples include showing a different treatment to users on trial plan + * vs. premium plan. Another example is to show a different treatment + * to users created after a certain date. + * + * @param key a unique key of your customer (e.g. user_id, user_email, account_id, etc.) MUST not be null or empty. + * @param flagSets the names of Flag Sets that you want to evaluate. MUST not be null or empty. + * @param evaluationOptions additional data to attach to the impression. + * @return for each feature flag the evaluated treatment, the default treatment of this feature flag, or 'control'. + */ + Map getTreatmentsByFlagSets(String key, List flagSets, EvaluationOptions evaluationOptions); + + /** + * Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the + * matching treatment if any. Otherwise {@link SplitResult.config()} will be null. + *

+ *

+ * Examples include showing a different treatment to users on trial plan + * vs. premium plan. Another example is to show a different treatment + * to users created after a certain date. + * + * @param key a unique key of your customer (e.g. user_id, user_email, account_id, etc.) MUST not be null or empty. + * @param flagSets the names of Flag Sets that you want to evaluate. MUST not be null or empty. + * @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty. + * @param evaluationOptions additional data to attach to the impression. + * @return for each feature flag the evaluated treatment, the default treatment of this feature flag, or 'control'. + */ + Map getTreatmentsByFlagSets(String key, List flagSets, Map attributes, + EvaluationOptions evaluationOptions); + + /** + * Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the + * matching treatment if any. Otherwise {@link SplitResult.config()} will be null. + *

+ *

+ * Examples include showing a different treatment to users on trial plan + * vs. premium plan. Another example is to show a different treatment + * to users created after a certain date. + * + * @param key a unique key of your customer (e.g. user_id, user_email, account_id, etc.) MUST not be null or empty. + * @param flagSet the Flag Set name that you want to evaluate. MUST not be null or empty. + * @param evaluationOptions additional data to attach to the impression. + * @return for each feature flag the evaluated treatment (the default treatment of this feature flag, or 'control') and a configuration + * associated to this treatment if set. + */ + Map getTreatmentsWithConfigByFlagSet(String key, String flagSet, EvaluationOptions evaluationOptions); + + /** + * Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the + * matching treatment if any. Otherwise {@link SplitResult.config()} will be null. + *

+ *

+ * Examples include showing a different treatment to users on trial plan + * vs. premium plan. Another example is to show a different treatment + * to users created after a certain date. + * + * @param key a unique key of your customer (e.g. user_id, user_email, account_id, etc.) MUST not be null or empty. + * @param flagSets the names of Flag Sets that you want to evaluate. MUST not be null or empty. + * @param evaluationOptions additional data to attach to the impression. + * @return for each feature flag the evaluated treatment (the default treatment of this feature flag, or 'control') and a configuration + * associated to this treatment if set. + */ + Map getTreatmentsWithConfigByFlagSets(String key, List flagSets, EvaluationOptions evaluationOptions); + + /** + * Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the + * matching treatment if any. Otherwise {@link SplitResult.config()} will be null. + *

+ *

+ * Examples include showing a different treatment to users on trial plan + * vs. premium plan. Another example is to show a different treatment + * to users created after a certain date. + * + * @param key a unique key of your customer (e.g. user_id, user_email, account_id, etc.) MUST not be null or empty. + * @param flagSets the names of Flag Sets that you want to evaluate. MUST not be null or empty. + * @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty. + * @param evaluationOptions additional data to attach to the impression. + * @return for each feature flag the evaluated treatment (the default treatment of this feature flag, or 'control') and a configuration + * associated to this treatment if set. + */ + Map getTreatmentsWithConfigByFlagSets(String key, List flagSets, Map attributes, + EvaluationOptions evaluationOptions); + + /** + * Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the + * matching treatment if any. Otherwise {@link SplitResult.config()} will be null. + *

+ *

+ * Examples include showing a different treatment to users on trial plan + * vs. premium plan. Another example is to show a different treatment + * to users created after a certain date. + * + * @param key a unique key of your customer (e.g. user_id, user_email, account_id, etc.) MUST not be null or empty. + * @param flagSet the Flag Set name that you want to evaluate. MUST not be null or empty. + * @param evaluationOptions additional data to attach to the impression. + * @return for each feature flag the evaluated treatment, the default treatment of this feature flag, or 'control'. + */ + Map getTreatmentsByFlagSet(String key, String flagSet, EvaluationOptions evaluationOptions); + + /** + * Same as {@link #getTreatments(Key, List, Map)} but it returns for each feature flag the configuration associated to the + * matching treatment if any. Otherwise {@link SplitResult.config()} will be null. + * + * @param key the matching and bucketing keys. MUST NOT be null. + * @param featureFlagNames the names of the feature flags we want to evaluate. MUST NOT be null. + * @param attributes of the entity (user, account etc.) to use in evaluation. Can be null or empty. + * @param evaluationOptions additional data to attach to the impression. + * + * @return for each feature flag a SplitResult containing the evaluated treatment (the default treatment of this feature flag, or 'control') and + * a configuration associated to this treatment if set. + */ + Map getTreatmentsWithConfig(Key key, List featureFlagNames, Map attributes, + EvaluationOptions evaluationOptions); + + /** + * Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the + * matching treatment if any. Otherwise {@link SplitResult.config()} will be null. + *

+ *

+ * Examples include showing a different treatment to users on trial plan + * vs. premium plan. Another example is to show a different treatment + * to users created after a certain date. + * + * @param key a unique key of your customer (e.g. user_id, user_email, account_id, etc.) MUST not be null or empty. + * @param flagSet the Flag Set name that you want to evaluate. MUST not be null or empty. + * @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty. + * @param evaluationOptions additional data to attach to the impression. + * @return for each feature flag the evaluated treatment (the default treatment of this feature flag, or 'control') and a configuration + * associated to this treatment if set. + */ + Map getTreatmentsWithConfigByFlagSet(String key, String flagSet, Map attributes, + EvaluationOptions evaluationOptions); + + /** + * Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the + * matching treatment if any. Otherwise {@link SplitResult.config()} will be null. + *

+ *

+ * Examples include showing a different treatment to users on trial plan + * vs. premium plan. Another example is to show a different treatment + * to users created after a certain date. + * + * @param key the matching and bucketing keys. MUST not be null or empty. + * @param flagSet the Flag Set name that you want to evaluate. MUST not be null or empty. + * @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty. + * @param evaluationOptions additional data to attach to the impression. + * @return for each feature flag the evaluated treatment, the default treatment of this feature flag, or 'control'. + */ + Map getTreatmentsByFlagSet(Key key, String flagSet, Map attributes, EvaluationOptions evaluationOptions); + + /** + * Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the + * matching treatment if any. Otherwise {@link SplitResult.config()} will be null. + *

+ *

+ * Examples include showing a different treatment to users on trial plan + * vs. premium plan. Another example is to show a different treatment + * to users created after a certain date. + * + * @param key the matching and bucketing keys. MUST not be null or empty. + * @param flagSets the names of Flag Sets that you want to evaluate. MUST not be null or empty. + * @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty. + * @param evaluationOptions additional data to attach to the impression. + * @return for each feature flag the evaluated treatment, the default treatment of this feature flag, or 'control'. + */ + Map getTreatmentsByFlagSets(Key key, List flagSets, Map attributes, + EvaluationOptions evaluationOptions); + + /** + * Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the + * matching treatment if any. Otherwise {@link SplitResult.config()} will be null. + *

+ *

+ * Examples include showing a different treatment to users on trial plan + * vs. premium plan. Another example is to show a different treatment + * to users created after a certain date. + * + * @param key the matching and bucketing keys. MUST not be null or empty. + * @param flagSet the Flag Set name that you want to evaluate. MUST not be null or empty. + * @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty. + * @param evaluationOptions additional data to attach to the impression. + * @return for each feature flag the evaluated treatment (the default treatment of this feature flag, or 'control') and a configuration + * associated to this treatment if set. + */ + Map getTreatmentsWithConfigByFlagSet(Key key, String flagSet, Map attributes, + EvaluationOptions evaluationOptions); + + /** + * Same as {@link #getTreatments(String, List, Map)} but it returns for each feature flag the configuration associated to the + * matching treatment if any. Otherwise {@link SplitResult.config()} will be null. + *

+ *

+ * Examples include showing a different treatment to users on trial plan + * vs. premium plan. Another example is to show a different treatment + * to users created after a certain date. + * + * @param key the matching and bucketing keys. MUST not be null or empty. + * @param flagSets the names of Flag Sets that you want to evaluate. MUST not be null or empty. + * @param attributes of the customer (user, account etc.) to use in evaluation. Can be null or empty. + * @param evaluationOptions additional data to attach to the impression. + * @return for each feature flag the evaluated treatment (the default treatment of this feature flag, or 'control') and a configuration + * associated to this treatment if set. + */ + Map getTreatmentsWithConfigByFlagSets(Key key, List flagSets, Map attributes, + EvaluationOptions evaluationOptions); + /** * Destroys the background processes and clears the cache, releasing the resources used by * the any instances of SplitClient or SplitManager generated by the client's parent SplitFactory diff --git a/client/src/main/java/io/split/client/SplitClientImpl.java b/client/src/main/java/io/split/client/SplitClientImpl.java index 1ff143ed2..b73a2c24a 100644 --- a/client/src/main/java/io/split/client/SplitClientImpl.java +++ b/client/src/main/java/io/split/client/SplitClientImpl.java @@ -1,8 +1,12 @@ package io.split.client; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonParser; import io.split.client.api.Key; import io.split.client.api.SplitResult; import io.split.client.dtos.DecoratedImpression; +import io.split.client.dtos.EvaluationOptions; import io.split.client.dtos.Event; import io.split.client.events.EventsStorageProducer; import io.split.client.impressions.Impression; @@ -17,10 +21,12 @@ import io.split.inputValidation.KeyValidator; import io.split.inputValidation.SplitNameValidator; import io.split.inputValidation.TrafficTypeValidator; +import io.split.inputValidation.ImpressionPropertiesValidator; import io.split.storages.SplitCacheConsumer; import io.split.telemetry.domain.enums.MethodEnum; import io.split.telemetry.storage.TelemetryConfigProducer; import io.split.telemetry.storage.TelemetryEvaluationProducer; +import io.split.client.utils.Json; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -93,27 +99,30 @@ public String getTreatment(String key, String featureFlagName) { @Override public String getTreatment(String key, String featureFlagName, Map attributes) { - return getTreatmentWithConfigInternal(key, null, featureFlagName, attributes, MethodEnum.TREATMENT).treatment(); + return getTreatmentWithConfigInternal(key, null, featureFlagName, attributes, new EvaluationOptions(null), MethodEnum.TREATMENT).treatment(); } @Override public String getTreatment(Key key, String featureFlagName, Map attributes) { - return getTreatmentWithConfigInternal(key.matchingKey(), key.bucketingKey(), featureFlagName, attributes, MethodEnum.TREATMENT).treatment(); + return getTreatmentWithConfigInternal(key.matchingKey(), key.bucketingKey(), featureFlagName, attributes, new EvaluationOptions(null), + MethodEnum.TREATMENT).treatment(); } @Override public SplitResult getTreatmentWithConfig(String key, String featureFlagName) { - return getTreatmentWithConfigInternal(key, null, featureFlagName, Collections.emptyMap(), MethodEnum.TREATMENT_WITH_CONFIG); + return getTreatmentWithConfigInternal(key, null, featureFlagName, Collections.emptyMap(), new EvaluationOptions(null), + MethodEnum.TREATMENT_WITH_CONFIG); } @Override public SplitResult getTreatmentWithConfig(String key, String featureFlagName, Map attributes) { - return getTreatmentWithConfigInternal(key, null, featureFlagName, attributes, MethodEnum.TREATMENT_WITH_CONFIG); + return getTreatmentWithConfigInternal(key, null, featureFlagName, attributes, new EvaluationOptions(null), MethodEnum.TREATMENT_WITH_CONFIG); } @Override public SplitResult getTreatmentWithConfig(Key key, String featureFlagName, Map attributes) { - return getTreatmentWithConfigInternal(key.matchingKey(), key.bucketingKey(), featureFlagName, attributes, MethodEnum.TREATMENT_WITH_CONFIG); + return getTreatmentWithConfigInternal(key.matchingKey(), key.bucketingKey(), featureFlagName, attributes, new EvaluationOptions(null), + MethodEnum.TREATMENT_WITH_CONFIG); } @Override @@ -123,111 +132,277 @@ public Map getTreatments(String key, List featureFlagNam @Override public Map getTreatments(String key, List featureFlagNames, Map attributes) { - return getTreatmentsWithConfigInternal(key, null, featureFlagNames, attributes, MethodEnum.TREATMENTS) + return getTreatmentsWithConfigInternal(key, null, featureFlagNames, attributes, new EvaluationOptions(null), MethodEnum.TREATMENTS) .entrySet().stream() .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().treatment())); } @Override public Map getTreatments(Key key, List featureFlagNames, Map attributes) { - return getTreatmentsWithConfigInternal(key.matchingKey(), key.bucketingKey(), featureFlagNames, attributes, MethodEnum.TREATMENTS) + return getTreatmentsWithConfigInternal(key.matchingKey(), key.bucketingKey(), featureFlagNames, attributes, + new EvaluationOptions(null), MethodEnum.TREATMENTS) .entrySet().stream() .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().treatment())); } @Override public Map getTreatmentsWithConfig(String key, List featureFlagNames) { - return getTreatmentsWithConfigInternal(key, null, featureFlagNames, Collections.emptyMap(), + return getTreatmentsWithConfigInternal(key, null, featureFlagNames, Collections.emptyMap(), new EvaluationOptions(null), MethodEnum.TREATMENTS_WITH_CONFIG); } @Override public Map getTreatmentsWithConfig(String key, List featureFlagNames, Map attributes) { - return getTreatmentsWithConfigInternal(key, null, featureFlagNames, attributes, MethodEnum.TREATMENTS_WITH_CONFIG); + return getTreatmentsWithConfigInternal(key, null, featureFlagNames, attributes, + new EvaluationOptions(null), MethodEnum.TREATMENTS_WITH_CONFIG); } @Override public Map getTreatmentsWithConfig(Key key, List featureFlagNames, Map attributes) { - return getTreatmentsWithConfigInternal(key.matchingKey(), key.bucketingKey(), featureFlagNames, attributes, + return getTreatmentsWithConfigInternal(key.matchingKey(), key.bucketingKey(), featureFlagNames, attributes, new EvaluationOptions(null), MethodEnum.TREATMENTS_WITH_CONFIG); } @Override public Map getTreatmentsByFlagSet(String key, String flagSet) { return getTreatmentsBySetsWithConfigInternal(key, null, new ArrayList<>(Arrays.asList(flagSet)), - null, MethodEnum.TREATMENTS_BY_FLAG_SET).entrySet().stream() + null, new EvaluationOptions(null), MethodEnum.TREATMENTS_BY_FLAG_SET).entrySet().stream() .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().treatment())); } @Override public Map getTreatmentsByFlagSet(String key, String flagSet, Map attributes) { return getTreatmentsBySetsWithConfigInternal(key, null, new ArrayList<>(Arrays.asList(flagSet)), - attributes, MethodEnum.TREATMENTS_BY_FLAG_SET).entrySet().stream() + attributes, new EvaluationOptions(null), MethodEnum.TREATMENTS_BY_FLAG_SET).entrySet().stream() .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().treatment())); } @Override public Map getTreatmentsByFlagSet(Key key, String flagSet, Map attributes) { return getTreatmentsBySetsWithConfigInternal(key.matchingKey(), key.bucketingKey(), new ArrayList<>(Arrays.asList(flagSet)), - attributes, MethodEnum.TREATMENTS_BY_FLAG_SET).entrySet().stream() + attributes, new EvaluationOptions(null), MethodEnum.TREATMENTS_BY_FLAG_SET).entrySet().stream() .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().treatment())); } @Override public Map getTreatmentsByFlagSets(String key, List flagSets) { return getTreatmentsBySetsWithConfigInternal(key, null, flagSets, - null, MethodEnum.TREATMENTS_BY_FLAG_SETS).entrySet().stream() + null, new EvaluationOptions(null), MethodEnum.TREATMENTS_BY_FLAG_SETS).entrySet().stream() .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().treatment())); } @Override public Map getTreatmentsByFlagSets(String key, List flagSets, Map attributes) { return getTreatmentsBySetsWithConfigInternal(key, null, flagSets, - attributes, MethodEnum.TREATMENTS_BY_FLAG_SETS).entrySet().stream() + attributes, new EvaluationOptions(null), MethodEnum.TREATMENTS_BY_FLAG_SETS).entrySet().stream() .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().treatment())); } @Override public Map getTreatmentsByFlagSets(Key key, List flagSets, Map attributes) { return getTreatmentsBySetsWithConfigInternal(key.matchingKey(), key.bucketingKey(), flagSets, - attributes, MethodEnum.TREATMENTS_BY_FLAG_SETS).entrySet().stream() + attributes, new EvaluationOptions(null), MethodEnum.TREATMENTS_BY_FLAG_SETS).entrySet().stream() .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().treatment())); } @Override public Map getTreatmentsWithConfigByFlagSet(String key, String flagSet) { return getTreatmentsBySetsWithConfigInternal(key, null, new ArrayList<>(Arrays.asList(flagSet)), - null, MethodEnum.TREATMENTS_WITH_CONFIG_BY_FLAG_SET); + null, new EvaluationOptions(null), MethodEnum.TREATMENTS_WITH_CONFIG_BY_FLAG_SET); } @Override public Map getTreatmentsWithConfigByFlagSet(String key, String flagSet, Map attributes) { return getTreatmentsBySetsWithConfigInternal(key, null, new ArrayList<>(Arrays.asList(flagSet)), - attributes, MethodEnum.TREATMENTS_WITH_CONFIG_BY_FLAG_SET); + attributes, new EvaluationOptions(null), MethodEnum.TREATMENTS_WITH_CONFIG_BY_FLAG_SET); } @Override public Map getTreatmentsWithConfigByFlagSet(Key key, String flagSet, Map attributes) { return getTreatmentsBySetsWithConfigInternal(key.matchingKey(), key.bucketingKey(), new ArrayList<>(Arrays.asList(flagSet)), - attributes, MethodEnum.TREATMENTS_WITH_CONFIG_BY_FLAG_SET); + attributes, new EvaluationOptions(null), MethodEnum.TREATMENTS_WITH_CONFIG_BY_FLAG_SET); } @Override public Map getTreatmentsWithConfigByFlagSets(String key, List flagSets) { return getTreatmentsBySetsWithConfigInternal(key, null, flagSets, - null, MethodEnum.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS); + null, new EvaluationOptions(null), MethodEnum.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS); } @Override public Map getTreatmentsWithConfigByFlagSets(String key, List flagSets, Map attributes) { return getTreatmentsBySetsWithConfigInternal(key, null, flagSets, - attributes, MethodEnum.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS); + attributes, new EvaluationOptions(null), MethodEnum.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS); } @Override public Map getTreatmentsWithConfigByFlagSets(Key key, List flagSets, Map attributes) { return getTreatmentsBySetsWithConfigInternal(key.matchingKey(), key.bucketingKey(), flagSets, - attributes, MethodEnum.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS); + attributes, new EvaluationOptions(null), MethodEnum.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS); + } + + @Override + public String getTreatment(String key, String featureFlagName, EvaluationOptions evaluationOptions) { + return getTreatment(key, featureFlagName, Collections.emptyMap(), evaluationOptions); + } + + @Override + public String getTreatment(String key, String featureFlagName, Map attributes, EvaluationOptions evaluationOptions) { + return getTreatmentWithConfigInternal(key, null, featureFlagName, attributes, evaluationOptions, MethodEnum.TREATMENT).treatment(); + } + + @Override + public String getTreatment(Key key, String featureFlagName, Map attributes, EvaluationOptions evaluationOptions) { + return getTreatmentWithConfigInternal(key.matchingKey(), key.bucketingKey(), featureFlagName, attributes, evaluationOptions, + MethodEnum.TREATMENT).treatment(); + } + + @Override + public Map getTreatments(String key, List featureFlagNames, + EvaluationOptions evaluationOptions) { + return getTreatments(key, featureFlagNames, Collections.emptyMap(), evaluationOptions); + } + + @Override + public Map getTreatments(String key, List featureFlagNames, Map attributes, + EvaluationOptions evaluationOptions) { + return getTreatmentsWithConfigInternal(key, null, featureFlagNames, attributes, evaluationOptions, MethodEnum.TREATMENTS) + .entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().treatment())); + } + + @Override + public Map getTreatments(Key key, List featureFlagNames, Map attributes, + EvaluationOptions evaluationOptions) { + return getTreatmentsWithConfigInternal(key.matchingKey(), key.bucketingKey(), featureFlagNames, attributes, evaluationOptions, + MethodEnum.TREATMENTS) + .entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().treatment())); + } + + @Override + public SplitResult getTreatmentWithConfig(String key, String featureFlagName, EvaluationOptions evaluationOptions) { + return getTreatmentWithConfigInternal(key, null, featureFlagName, Collections.emptyMap(), evaluationOptions, + MethodEnum.TREATMENT_WITH_CONFIG); + } + + @Override + public SplitResult getTreatmentWithConfig(Key key, String featureFlagName, Map attributes, + EvaluationOptions evaluationOptions) { + return getTreatmentWithConfigInternal(key.matchingKey(), key.bucketingKey(), featureFlagName, attributes, evaluationOptions, + MethodEnum.TREATMENT_WITH_CONFIG); + } + + @Override + public SplitResult getTreatmentWithConfig(String key, String featureFlagName, Map attributes, + EvaluationOptions evaluationOptions) { + return getTreatmentWithConfigInternal(key, null, featureFlagName, attributes, evaluationOptions, + MethodEnum.TREATMENT_WITH_CONFIG); + } + + @Override + public Map getTreatmentsWithConfig(String key, List featureFlagNames, Map attributes, + EvaluationOptions evaluationOptions) { + return getTreatmentsWithConfigInternal(key, null, featureFlagNames, attributes, evaluationOptions, + MethodEnum.TREATMENTS_WITH_CONFIG); + } + + @Override + public Map getTreatmentsWithConfig(String key, List featureFlagNames, EvaluationOptions evaluationOptions) { + return getTreatmentsWithConfigInternal(key, null, featureFlagNames, null, evaluationOptions, + MethodEnum.TREATMENTS_WITH_CONFIG); + } + + @Override + public Map getTreatmentsByFlagSet(String key, String flagSet, Map attributes, + EvaluationOptions evaluationOptions) { + return getTreatmentsBySetsWithConfigInternal(key, null, new ArrayList<>(Arrays.asList(flagSet)), + attributes, evaluationOptions, MethodEnum.TREATMENTS_BY_FLAG_SET).entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().treatment())); + } + + @Override + public Map getTreatmentsByFlagSets(String key, List flagSets, EvaluationOptions evaluationOptions) { + return getTreatmentsBySetsWithConfigInternal(key, null, flagSets, + null, evaluationOptions, MethodEnum.TREATMENTS_BY_FLAG_SETS).entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().treatment())); + } + + @Override + public Map getTreatmentsByFlagSets(String key, List flagSets, Map attributes, + EvaluationOptions evaluationOptions) { + return getTreatmentsBySetsWithConfigInternal(key, null, flagSets, + attributes, evaluationOptions, MethodEnum.TREATMENTS_BY_FLAG_SETS).entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().treatment())); + } + + @Override + public Map getTreatmentsWithConfigByFlagSet(String key, String flagSet, EvaluationOptions evaluationOptions) { + return getTreatmentsBySetsWithConfigInternal(key, null, new ArrayList<>(Arrays.asList(flagSet)), + null, evaluationOptions, MethodEnum.TREATMENTS_WITH_CONFIG_BY_FLAG_SET); + } + + @Override + public Map getTreatmentsWithConfigByFlagSet(String key, String flagSet, Map attributes, + EvaluationOptions evaluationOptions) { + return getTreatmentsBySetsWithConfigInternal(key, null, new ArrayList<>(Arrays.asList(flagSet)), + attributes, evaluationOptions, MethodEnum.TREATMENTS_WITH_CONFIG_BY_FLAG_SET); + } + + @Override + public Map getTreatmentsWithConfigByFlagSets(String key, List flagSets, EvaluationOptions evaluationOptions) { + return getTreatmentsBySetsWithConfigInternal(key, null, flagSets, + null, evaluationOptions, MethodEnum.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS); + } + + @Override + public Map getTreatmentsWithConfigByFlagSets(String key, List flagSets, Map attributes, + EvaluationOptions evaluationOptions) { + return getTreatmentsBySetsWithConfigInternal(key, null, flagSets, + attributes, evaluationOptions, MethodEnum.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS); + } + + @Override + public Map getTreatmentsByFlagSet(String key, String flagSet, EvaluationOptions evaluationOptions) { + return getTreatmentsBySetsWithConfigInternal(key, null, new ArrayList<>(Arrays.asList(flagSet)), + null, evaluationOptions, MethodEnum.TREATMENTS_BY_FLAG_SET).entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().treatment())); + } + + @Override + public Map getTreatmentsWithConfig(Key key, List featureFlagNames, Map attributes, + EvaluationOptions evaluationOptions) { + return getTreatmentsWithConfigInternal(key.matchingKey(), key.bucketingKey(), featureFlagNames, attributes, evaluationOptions, + MethodEnum.TREATMENTS_WITH_CONFIG); + } + + @Override + public Map getTreatmentsByFlagSet(Key key, String flagSet, Map attributes, EvaluationOptions evaluationOptions) { + return getTreatmentsBySetsWithConfigInternal(key.matchingKey(), key.bucketingKey(), new ArrayList<>(Arrays.asList(flagSet)), + attributes, evaluationOptions, MethodEnum.TREATMENTS_BY_FLAG_SET).entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().treatment())); + } + + @Override + public Map getTreatmentsByFlagSets(Key key, List flagSets, Map attributes, + EvaluationOptions evaluationOptions) { + return getTreatmentsBySetsWithConfigInternal(key.matchingKey(), key.bucketingKey(), flagSets, + attributes, evaluationOptions, MethodEnum.TREATMENTS_BY_FLAG_SETS).entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().treatment())); + } + + @Override + public Map getTreatmentsWithConfigByFlagSet(Key key, String flagSet, Map attributes, + EvaluationOptions evaluationOptions) { + return getTreatmentsBySetsWithConfigInternal(key.matchingKey(), key.bucketingKey(), new ArrayList<>(Arrays.asList(flagSet)), + attributes, evaluationOptions, MethodEnum.TREATMENTS_WITH_CONFIG_BY_FLAG_SET); + } + + @Override + public Map getTreatmentsWithConfigByFlagSets(Key key, List flagSets, Map attributes, + EvaluationOptions evaluationOptions) { + return getTreatmentsBySetsWithConfigInternal(key.matchingKey(), key.bucketingKey(), flagSets, + attributes, evaluationOptions, MethodEnum.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS); } @Override @@ -313,7 +488,7 @@ private boolean track(Event event) { } private SplitResult getTreatmentWithConfigInternal(String matchingKey, String bucketingKey, String featureFlag, Map attributes, MethodEnum methodEnum) { + Object> attributes, EvaluationOptions evaluationOptions, MethodEnum methodEnum) { long initTime = System.currentTimeMillis(); try { checkSDKReady(methodEnum, Arrays.asList(featureFlag)); @@ -336,7 +511,6 @@ private SplitResult getTreatmentWithConfigInternal(String matchingKey, String bu return SPLIT_RESULT_CONTROL; } featureFlag = splitNameResult.get(); - long start = System.currentTimeMillis(); EvaluatorImp.TreatmentLabelAndChangeNumber result = _evaluator.evaluateFeature(matchingKey, bucketingKey, featureFlag, attributes); @@ -358,7 +532,8 @@ private SplitResult getTreatmentWithConfigInternal(String matchingKey, String bu _config.labelsEnabled() ? result.label : null, result.changeNumber, attributes, - result.track + result.track, + validateProperties(evaluationOptions.getProperties()) ); _telemetryEvaluationProducer.recordLatency(methodEnum, System.currentTimeMillis() - initTime); return new SplitResult(result.treatment, result.configurations); @@ -373,8 +548,19 @@ private SplitResult getTreatmentWithConfigInternal(String matchingKey, String bu } } + private String validateProperties(Map properties) { + if (properties == null){ + return null; + } + + ImpressionPropertiesValidator.ImpressionPropertiesValidatorResult iPValidatorResult = ImpressionPropertiesValidator.propertiesAreValid( + properties); + return new GsonBuilder().create().toJson(iPValidatorResult.getValue()).toString(); + } + private Map getTreatmentsWithConfigInternal(String matchingKey, String bucketingKey, List featureFlagNames, - Map attributes, MethodEnum methodEnum) { + Map attributes, + EvaluationOptions evaluationOptions, MethodEnum methodEnum) { long initTime = System.currentTimeMillis(); if (featureFlagNames == null) { _log.error(String.format("%s: featureFlagNames must be a non-empty array", methodEnum.getMethod())); @@ -389,7 +575,9 @@ private Map getTreatmentsWithConfigInternal(String matching featureFlagNames = SplitNameValidator.areValid(featureFlagNames, methodEnum.getMethod()); Map evaluatorResult = _evaluator.evaluateFeatures(matchingKey, bucketingKey, featureFlagNames, attributes); - return processEvaluatorResult(evaluatorResult, methodEnum, matchingKey, bucketingKey, attributes, initTime); + + return processEvaluatorResult(evaluatorResult, methodEnum, matchingKey, bucketingKey, attributes, initTime, + validateProperties(evaluationOptions.getProperties())); } catch (Exception e) { try { _telemetryEvaluationProducer.recordException(methodEnum); @@ -402,7 +590,9 @@ private Map getTreatmentsWithConfigInternal(String matching } private Map getTreatmentsBySetsWithConfigInternal(String matchingKey, String bucketingKey, - List sets, Map attributes, MethodEnum methodEnum) { + List sets, Map attributes, + EvaluationOptions evaluationOptions, + MethodEnum methodEnum) { long initTime = System.currentTimeMillis(); if (sets == null || sets.isEmpty()) { @@ -423,7 +613,9 @@ private Map getTreatmentsBySetsWithConfigInternal(String ma } Map evaluatorResult = _evaluator.evaluateFeaturesByFlagSets(matchingKey, bucketingKey, new ArrayList<>(cleanFlagSets), attributes); - return processEvaluatorResult(evaluatorResult, methodEnum, matchingKey, bucketingKey, attributes, initTime); + + return processEvaluatorResult(evaluatorResult, methodEnum, matchingKey, bucketingKey, attributes, initTime, + validateProperties(evaluationOptions.getProperties())); } catch (Exception e) { try { _telemetryEvaluationProducer.recordException(methodEnum); @@ -436,7 +628,7 @@ private Map getTreatmentsBySetsWithConfigInternal(String ma } private Map processEvaluatorResult(Map evaluatorResult, MethodEnum methodEnum, String matchingKey, String bucketingKey, Map attributes, long initTime){ + Object> attributes, long initTime, String properties){ List decoratedImpressions = new ArrayList<>(); Map result = new HashMap<>(); evaluatorResult.keySet().forEach(t -> { @@ -450,7 +642,7 @@ private Map processEvaluatorResult(Map filterSetsAreInConfig(Set sets, MethodEnum methodEnu return setsToReturn; } private void recordStats(String matchingKey, String bucketingKey, String featureFlagName, long start, String result, - String operation, String label, Long changeNumber, Map attributes, boolean track) { + String operation, String label, Long changeNumber, Map attributes, boolean track, String properties) { try { _impressionManager.track(Stream.of( new DecoratedImpression( new Impression(matchingKey, bucketingKey, featureFlagName, result, System.currentTimeMillis(), - label, changeNumber, attributes), + label, changeNumber, attributes, properties), track)).collect(Collectors.toList())); } catch (Throwable t) { _log.error("Exception", t); diff --git a/client/src/main/java/io/split/client/SplitFactoryImpl.java b/client/src/main/java/io/split/client/SplitFactoryImpl.java index 848b50e86..454206d80 100644 --- a/client/src/main/java/io/split/client/SplitFactoryImpl.java +++ b/client/src/main/java/io/split/client/SplitFactoryImpl.java @@ -53,6 +53,7 @@ import io.split.engine.experiments.SplitFetcher; import io.split.engine.experiments.SplitFetcherImp; import io.split.engine.experiments.SplitParser; +import io.split.engine.experiments.RuleBasedSegmentParser; import io.split.engine.experiments.SplitSynchronizationTask; import io.split.engine.segments.SegmentChangeFetcher; import io.split.engine.segments.SegmentSynchronizationTaskImp; @@ -66,8 +67,11 @@ import io.split.storages.SplitCache; import io.split.storages.SplitCacheConsumer; import io.split.storages.SplitCacheProducer; +import io.split.storages.RuleBasedSegmentCacheConsumer; +import io.split.storages.RuleBasedSegmentCache; import io.split.storages.enums.OperationMode; import io.split.storages.memory.InMemoryCacheImp; +import io.split.storages.memory.RuleBasedSegmentCacheInMemoryImp; import io.split.storages.memory.SegmentCacheInMemoryImpl; import io.split.storages.pluggable.adapters.UserCustomEventAdapterProducer; import io.split.storages.pluggable.adapters.UserCustomImpressionAdapterConsumer; @@ -123,8 +127,7 @@ public class SplitFactoryImpl implements SplitFactory { private static final org.slf4j.Logger _log = LoggerFactory.getLogger(SplitFactoryImpl.class); private static final String LEGACY_LOG_MESSAGE = "The sdk initialize in localhost mode using Legacy file. The splitFile or " - + - "inputStream doesn't add it to the config."; + + "inputStream were not added to the config."; private final static long SSE_CONNECT_TIMEOUT = 30000; private final static long SSE_SOCKET_TIMEOUT = 70000; @@ -204,6 +207,7 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); FlagSetsFilter flagSetsFilter = new FlagSetsFilterImpl(config.getSetsFilter()); SplitCache splitCache = new InMemoryCacheImp(flagSetsFilter); + RuleBasedSegmentCache ruleBasedSegmentCache = new RuleBasedSegmentCacheInMemoryImp(); ImpressionsStorage impressionsStorage = new InMemoryImpressionsStorage(config.impressionsQueueSize()); _splitCache = splitCache; _segmentCache = segmentCache; @@ -215,8 +219,11 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn _segmentSynchronizationTaskImp = buildSegments(config, segmentCache, splitCache); SplitParser splitParser = new SplitParser(); + RuleBasedSegmentParser ruleBasedSegmentParser = new RuleBasedSegmentParser(); + // SplitFetcher - _splitFetcher = buildSplitFetcher(splitCache, splitParser, flagSetsFilter); + _splitFetcher = buildSplitFetcher(splitCache, splitParser, flagSetsFilter, ruleBasedSegmentParser, ruleBasedSegmentCache); + // SplitSynchronizationTask _splitSynchronizationTask = new SplitSynchronizationTask(_splitFetcher, @@ -244,7 +251,7 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn config.getThreadFactory()); // Evaluator - _evaluator = new EvaluatorImp(splitCache, segmentCache); + _evaluator = new EvaluatorImp(splitCache, segmentCache, ruleBasedSegmentCache); // SplitClient _client = new SplitClientImpl(this, @@ -301,6 +308,9 @@ protected SplitFactoryImpl(String apiToken, SplitClientConfig config, CustomStor customStorageWrapper); UserCustomSplitAdapterConsumer userCustomSplitAdapterConsumer = new UserCustomSplitAdapterConsumer( customStorageWrapper); + // TODO Update the instance to UserCustomRuleBasedSegmentAdapterConsumer + RuleBasedSegmentCacheConsumer userCustomRuleBasedSegmentAdapterConsumer = new RuleBasedSegmentCacheInMemoryImp(); + // TODO migrate impressions sender to Task instead manager and not instantiate // Producer here. UserCustomImpressionAdapterConsumer userCustomImpressionAdapterConsumer = new UserCustomImpressionAdapterConsumer(); @@ -333,7 +343,7 @@ protected SplitFactoryImpl(String apiToken, SplitClientConfig config, CustomStor _gates = new SDKReadinessGates(); _telemetrySynchronizer = new TelemetryConsumerSubmitter(customStorageWrapper, _sdkMetadata); - _evaluator = new EvaluatorImp(userCustomSplitAdapterConsumer, userCustomSegmentAdapterConsumer); + _evaluator = new EvaluatorImp(userCustomSplitAdapterConsumer, userCustomSegmentAdapterConsumer, userCustomRuleBasedSegmentAdapterConsumer); _impressionsSender = PluggableImpressionSender.create(customStorageWrapper); _uniqueKeysTracker = createUniqueKeysTracker(config); _impressionsManager = buildImpressionsManager(config, userCustomImpressionAdapterConsumer, @@ -391,6 +401,7 @@ protected SplitFactoryImpl(SplitClientConfig config) { SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); FlagSetsFilter flagSetsFilter = new FlagSetsFilterImpl(config.getSetsFilter()); + RuleBasedSegmentCache ruleBasedSegmentCache = new RuleBasedSegmentCacheInMemoryImp(); SplitCache splitCache = new InMemoryCacheImp(flagSetsFilter); _splitCache = splitCache; _gates = new SDKReadinessGates(); @@ -413,10 +424,11 @@ protected SplitFactoryImpl(SplitClientConfig config) { // SplitFetcher SplitChangeFetcher splitChangeFetcher = createSplitChangeFetcher(config); + RuleBasedSegmentParser ruleBasedSegmentParser = new RuleBasedSegmentParser(); SplitParser splitParser = new SplitParser(); _splitFetcher = new SplitFetcherImp(splitChangeFetcher, splitParser, splitCache, _telemetryStorageProducer, - flagSetsFilter); + flagSetsFilter, ruleBasedSegmentParser, ruleBasedSegmentCache); // SplitSynchronizationTask _splitSynchronizationTask = new SplitSynchronizationTask(_splitFetcher, splitCache, @@ -428,7 +440,7 @@ protected SplitFactoryImpl(SplitClientConfig config) { _impressionsManager, null, null, null); // Evaluator - _evaluator = new EvaluatorImp(splitCache, segmentCache); + _evaluator = new EvaluatorImp(splitCache, segmentCache, ruleBasedSegmentCache); EventsStorage eventsStorage = new NoopEventsStorageImp(); @@ -609,11 +621,12 @@ private SegmentSynchronizationTaskImp buildSegments(SplitClientConfig config, } private SplitFetcher buildSplitFetcher(SplitCacheProducer splitCacheProducer, SplitParser splitParser, - FlagSetsFilter flagSetsFilter) throws URISyntaxException { + FlagSetsFilter flagSetsFilter, RuleBasedSegmentParser ruleBasedSegmentParser, + RuleBasedSegmentCache ruleBasedSegmentCache) throws URISyntaxException { SplitChangeFetcher splitChangeFetcher = HttpSplitChangeFetcher.create(_splitHttpClient, _rootTarget, _telemetryStorageProducer); return new SplitFetcherImp(splitChangeFetcher, splitParser, splitCacheProducer, _telemetryStorageProducer, - flagSetsFilter); + flagSetsFilter,ruleBasedSegmentParser, ruleBasedSegmentCache); } private ImpressionsManagerImpl buildImpressionsManager(SplitClientConfig config, diff --git a/client/src/main/java/io/split/client/YamlLocalhostSplitChangeFetcher.java b/client/src/main/java/io/split/client/YamlLocalhostSplitChangeFetcher.java index e90ca1389..5e6836579 100644 --- a/client/src/main/java/io/split/client/YamlLocalhostSplitChangeFetcher.java +++ b/client/src/main/java/io/split/client/YamlLocalhostSplitChangeFetcher.java @@ -32,7 +32,7 @@ public YamlLocalhostSplitChangeFetcher(InputStreamProvider inputStreamProvider) } @Override - public SplitChange fetch(long since, FetchOptions options) { + public SplitChange fetch(long since, long sinceRBS, FetchOptions options) { try { Yaml yaml = new Yaml(); List>> yamlSplits = yaml.load(_inputStreamProvider.get()); diff --git a/client/src/main/java/io/split/client/dtos/EvaluationOptions.java b/client/src/main/java/io/split/client/dtos/EvaluationOptions.java new file mode 100644 index 000000000..b38125a49 --- /dev/null +++ b/client/src/main/java/io/split/client/dtos/EvaluationOptions.java @@ -0,0 +1,14 @@ +package io.split.client.dtos; + +import java.util.Map; + +public class EvaluationOptions { + private Map _properties; + + public EvaluationOptions(Map properties) { + _properties = properties; + } + public Map getProperties() { + return _properties; + }; +} diff --git a/client/src/main/java/io/split/client/dtos/Excluded.java b/client/src/main/java/io/split/client/dtos/Excluded.java new file mode 100644 index 000000000..bc544d97d --- /dev/null +++ b/client/src/main/java/io/split/client/dtos/Excluded.java @@ -0,0 +1,8 @@ +package io.split.client.dtos; + +import java.util.List; + +public class Excluded { + public List keys; + public List segments; +} diff --git a/client/src/main/java/io/split/client/dtos/KeyImpression.java b/client/src/main/java/io/split/client/dtos/KeyImpression.java index 9b6713aac..1e0ef540e 100644 --- a/client/src/main/java/io/split/client/dtos/KeyImpression.java +++ b/client/src/main/java/io/split/client/dtos/KeyImpression.java @@ -15,6 +15,9 @@ public class KeyImpression { /* package private */ static final String FIELD_TIME = "m"; /* package private */ static final String FIELD_CHANGE_NUMBER = "c"; /* package private */ static final String FIELD_PREVIOUS_TIME = "pt"; + /* package private */ static final String FIELD_PROPERTIES = "properties"; + + public static int MAX_PROPERTIES_LENGTH_BYTES = 32 * 1024; public transient String feature; // Non-serializable @@ -39,6 +42,9 @@ public class KeyImpression { @SerializedName(FIELD_PREVIOUS_TIME) public Long previousTime; + @SerializedName(FIELD_PROPERTIES) + public String properties; + @Override public boolean equals(Object o) { if (this == o) return true; @@ -50,6 +56,7 @@ public boolean equals(Object o) { if (!Objects.equals(feature, that.feature)) return false; if (!keyName.equals(that.keyName)) return false; if (!treatment.equals(that.treatment)) return false; + if (properties != null && !properties.equals(that.properties)) return false; if (bucketingKey == null) { return that.bucketingKey == null; @@ -78,6 +85,7 @@ public static KeyImpression fromImpression(Impression i) { ki.treatment = i.treatment(); ki.label = i.appliedRule(); ki.previousTime = i.pt(); + ki.properties = i.properties(); return ki; } } diff --git a/client/src/main/java/io/split/client/dtos/RuleBasedSegment.java b/client/src/main/java/io/split/client/dtos/RuleBasedSegment.java new file mode 100644 index 000000000..ec1cc68ae --- /dev/null +++ b/client/src/main/java/io/split/client/dtos/RuleBasedSegment.java @@ -0,0 +1,12 @@ +package io.split.client.dtos; + +import java.util.List; + +public class RuleBasedSegment { + public String name; + public Status status; + public String trafficTypeName; + public long changeNumber; + public List conditions; + public Excluded excluded; +} diff --git a/client/src/main/java/io/split/client/dtos/RuleBasedSegmentChange.java b/client/src/main/java/io/split/client/dtos/RuleBasedSegmentChange.java new file mode 100644 index 000000000..9f475003c --- /dev/null +++ b/client/src/main/java/io/split/client/dtos/RuleBasedSegmentChange.java @@ -0,0 +1,9 @@ +package io.split.client.dtos; + +import java.util.List; + +public class RuleBasedSegmentChange { + public List ruleBasedSegments; + public long since; + public long till; +} diff --git a/client/src/main/java/io/split/client/dtos/SplitChange.java b/client/src/main/java/io/split/client/dtos/SplitChange.java index ba1130886..f7eb9a3d7 100644 --- a/client/src/main/java/io/split/client/dtos/SplitChange.java +++ b/client/src/main/java/io/split/client/dtos/SplitChange.java @@ -6,4 +6,7 @@ public class SplitChange { public List splits; public long since; public long till; + public List ruleBasedSegments; + public long sinceRBS; + public long tillRBS; } diff --git a/client/src/main/java/io/split/client/impressions/Impression.java b/client/src/main/java/io/split/client/impressions/Impression.java index 31678f2bd..fc7b73141 100644 --- a/client/src/main/java/io/split/client/impressions/Impression.java +++ b/client/src/main/java/io/split/client/impressions/Impression.java @@ -16,10 +16,11 @@ public class Impression { private final Long _changeNumber; private Long _pt; private final Map _attributes; + private final String _properties; public Impression(String key, String bucketingKey, String featureFlag, String treatment, long time, String appliedRule, - Long changeNumber, Map atributes) { + Long changeNumber, Map atributes, String properties) { _key = key; _bucketingKey = bucketingKey; _split = featureFlag; @@ -28,6 +29,7 @@ public Impression(String key, String bucketingKey, String featureFlag, String tr _appliedRule = appliedRule; _changeNumber = changeNumber; _attributes = atributes; + _properties = properties; } public String key() { @@ -67,4 +69,8 @@ public Long pt() { } public Impression withPreviousTime(Long pt) { _pt = pt; return this; } + + public String properties() { + return _properties; + } } diff --git a/client/src/main/java/io/split/client/impressions/strategy/ProcessImpressionDebug.java b/client/src/main/java/io/split/client/impressions/strategy/ProcessImpressionDebug.java index 1fcd1615b..0ac5a20d9 100644 --- a/client/src/main/java/io/split/client/impressions/strategy/ProcessImpressionDebug.java +++ b/client/src/main/java/io/split/client/impressions/strategy/ProcessImpressionDebug.java @@ -19,6 +19,9 @@ public ProcessImpressionDebug(boolean listenerEnabled, ImpressionObserver impres @Override public ImpressionsResult process(List impressions) { for(Impression impression : impressions) { + if (impression.properties() != null) { + continue; + } impression.withPreviousTime(_impressionObserver.testAndSet(impression)); } List impressionForListener = this._listenerEnabled ? impressions : null; diff --git a/client/src/main/java/io/split/client/impressions/strategy/ProcessImpressionOptimized.java b/client/src/main/java/io/split/client/impressions/strategy/ProcessImpressionOptimized.java index 1af51a209..3bfdc8185 100644 --- a/client/src/main/java/io/split/client/impressions/strategy/ProcessImpressionOptimized.java +++ b/client/src/main/java/io/split/client/impressions/strategy/ProcessImpressionOptimized.java @@ -32,6 +32,10 @@ public ProcessImpressionOptimized(boolean listenerEnabled, ImpressionObserver im public ImpressionsResult process(List impressions) { List impressionsToQueue = new ArrayList<>(); for(Impression impression : impressions) { + if (impression.properties() != null) { + impressionsToQueue.add(impression); + continue; + } impression = impression.withPreviousTime(_impressionObserver.testAndSet(impression)); if(!Objects.isNull(impression.pt()) && impression.pt() != 0){ _impressionCounter.inc(impression.split(), impression.time(), 1); diff --git a/client/src/main/java/io/split/client/utils/FeatureFlagProcessor.java b/client/src/main/java/io/split/client/utils/FeatureFlagProcessor.java index f6e4878a9..9b62415af 100644 --- a/client/src/main/java/io/split/client/utils/FeatureFlagProcessor.java +++ b/client/src/main/java/io/split/client/utils/FeatureFlagProcessor.java @@ -1,10 +1,14 @@ package io.split.client.utils; +import io.split.client.dtos.RuleBasedSegment; import io.split.client.dtos.Split; import io.split.client.dtos.Status; import io.split.client.interceptors.FlagSetsFilter; +import io.split.engine.experiments.ParsedRuleBasedSegment; import io.split.engine.experiments.ParsedSplit; +import io.split.engine.experiments.RuleBasedSegmentParser; import io.split.engine.experiments.SplitParser; +import io.split.storages.RuleBasedSegmentCacheProducer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,4 +44,27 @@ public static FeatureFlagsToUpdate processFeatureFlagChanges(SplitParser splitPa } return new FeatureFlagsToUpdate(toAdd, toRemove, segments); } + + public static RuleBasedSegmentsToUpdate processRuleBasedSegmentChanges(RuleBasedSegmentParser ruleBasedSegmentParser, + List ruleBasedSegments) { + List toAdd = new ArrayList<>(); + List toRemove = new ArrayList<>(); + Set segments = new HashSet<>(); + for (RuleBasedSegment ruleBasedSegment : ruleBasedSegments) { + if (ruleBasedSegment.status != Status.ACTIVE) { + // archive. + toRemove.add(ruleBasedSegment.name); + continue; + } + ParsedRuleBasedSegment parsedRuleBasedSegment = ruleBasedSegmentParser.parse(ruleBasedSegment); + if (parsedRuleBasedSegment == null) { + _log.debug(String.format("We could not parse the rule based segment definition for: %s", ruleBasedSegment.name)); + continue; + } + segments.addAll(parsedRuleBasedSegment.getSegmentsNames()); + toAdd.add(parsedRuleBasedSegment); + } + return new RuleBasedSegmentsToUpdate(toAdd, toRemove, segments); + } + } \ No newline at end of file diff --git a/client/src/main/java/io/split/client/utils/RuleBasedSegmentsToUpdate.java b/client/src/main/java/io/split/client/utils/RuleBasedSegmentsToUpdate.java new file mode 100644 index 000000000..22f10fbc0 --- /dev/null +++ b/client/src/main/java/io/split/client/utils/RuleBasedSegmentsToUpdate.java @@ -0,0 +1,31 @@ +package io.split.client.utils; + +import io.split.engine.experiments.ParsedRuleBasedSegment; +import io.split.engine.experiments.ParsedSplit; + +import java.util.List; +import java.util.Set; + +public class RuleBasedSegmentsToUpdate { + List toAdd; + List toRemove; + Set segments; + + public RuleBasedSegmentsToUpdate(List toAdd, List toRemove, Set segments) { + this.toAdd = toAdd; + this.toRemove = toRemove; + this.segments = segments; + } + + public List getToAdd() { + return toAdd; + } + + public List getToRemove() { + return toRemove; + } + + public Set getSegments() { + return segments; + } +} \ No newline at end of file diff --git a/client/src/main/java/io/split/engine/common/FetchOptions.java b/client/src/main/java/io/split/engine/common/FetchOptions.java index 926137135..ff7a3d49d 100644 --- a/client/src/main/java/io/split/engine/common/FetchOptions.java +++ b/client/src/main/java/io/split/engine/common/FetchOptions.java @@ -12,6 +12,7 @@ public Builder() {} public Builder(FetchOptions opts) { _targetCN = opts._targetCN; + _targetCnRBS = opts._targetCnRBS; _cacheControlHeaders = opts._cacheControlHeaders; _flagSetsFilter = opts._flagSetsFilter; } @@ -26,16 +27,22 @@ public Builder targetChangeNumber(long targetCN) { return this; } + public Builder targetChangeNumberRBS(long targetCnRBS) { + _targetCnRBS = targetCnRBS; + return this; + } + public Builder flagSetsFilter(String flagSetsFilter) { _flagSetsFilter = flagSetsFilter; return this; } public FetchOptions build() { - return new FetchOptions(_cacheControlHeaders, _targetCN, _flagSetsFilter); + return new FetchOptions(_cacheControlHeaders, _targetCN, _targetCnRBS, _flagSetsFilter); } private long _targetCN = DEFAULT_TARGET_CHANGENUMBER; + private long _targetCnRBS = DEFAULT_TARGET_CHANGENUMBER; private boolean _cacheControlHeaders = false; private String _flagSetsFilter = ""; } @@ -46,6 +53,8 @@ public boolean cacheControlHeadersEnabled() { public long targetCN() { return _targetCN; } + public long targetCnRBS() { return _targetCnRBS; } + public boolean hasCustomCN() { return _targetCN != DEFAULT_TARGET_CHANGENUMBER; } public String flagSetsFilter() { @@ -54,9 +63,11 @@ public String flagSetsFilter() { private FetchOptions(boolean cacheControlHeaders, long targetCN, + long targetCnRBS, String flagSetsFilter) { _cacheControlHeaders = cacheControlHeaders; _targetCN = targetCN; + _targetCnRBS = targetCnRBS; _flagSetsFilter = flagSetsFilter; } @@ -70,6 +81,7 @@ public boolean equals(Object obj) { return Objects.equals(_cacheControlHeaders, other._cacheControlHeaders) && Objects.equals(_targetCN, other._targetCN) + && Objects.equals(_targetCnRBS, other._targetCnRBS) && Objects.equals(_flagSetsFilter, other._flagSetsFilter); } @@ -81,5 +93,6 @@ public int hashCode() { private final boolean _cacheControlHeaders; private final long _targetCN; + private final long _targetCnRBS; private final String _flagSetsFilter; } \ No newline at end of file diff --git a/client/src/main/java/io/split/engine/evaluator/EvaluationContext.java b/client/src/main/java/io/split/engine/evaluator/EvaluationContext.java index 7aab69578..9c3ee7036 100644 --- a/client/src/main/java/io/split/engine/evaluator/EvaluationContext.java +++ b/client/src/main/java/io/split/engine/evaluator/EvaluationContext.java @@ -1,5 +1,7 @@ package io.split.engine.evaluator; +import io.split.storages.RuleBasedSegmentCacheConsumer; + import io.split.storages.SegmentCacheConsumer; import static com.google.common.base.Preconditions.checkNotNull; @@ -7,10 +9,13 @@ public class EvaluationContext { private final Evaluator _evaluator; private final SegmentCacheConsumer _segmentCacheConsumer; + private final RuleBasedSegmentCacheConsumer _ruleBasedSegmentCacheConsumer; - public EvaluationContext(Evaluator evaluator, SegmentCacheConsumer segmentCacheConsumer) { + public EvaluationContext(Evaluator evaluator, SegmentCacheConsumer segmentCacheConsumer, + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer) { _evaluator = checkNotNull(evaluator); _segmentCacheConsumer = checkNotNull(segmentCacheConsumer); + _ruleBasedSegmentCacheConsumer = checkNotNull(ruleBasedSegmentCacheConsumer); } public Evaluator getEvaluator() { @@ -20,4 +25,8 @@ public Evaluator getEvaluator() { public SegmentCacheConsumer getSegmentCache() { return _segmentCacheConsumer; } + + public RuleBasedSegmentCacheConsumer getRuleBasedSegmentCache() { + return _ruleBasedSegmentCacheConsumer; + } } diff --git a/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java index af165ca36..350f359d8 100644 --- a/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java +++ b/client/src/main/java/io/split/engine/evaluator/EvaluatorImp.java @@ -6,8 +6,10 @@ import io.split.engine.experiments.ParsedSplit; import io.split.engine.splitter.Splitter; import io.split.grammar.Treatments; +import io.split.storages.RuleBasedSegmentCacheConsumer; import io.split.storages.SegmentCacheConsumer; import io.split.storages.SplitCacheConsumer; +import static io.split.Spec.SPEC_VERSION; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -23,13 +25,16 @@ public class EvaluatorImp implements Evaluator { private static final Logger _log = LoggerFactory.getLogger(EvaluatorImp.class); private final SegmentCacheConsumer _segmentCacheConsumer; + private final RuleBasedSegmentCacheConsumer _ruleBasedSegmentCacheConsumer; private final EvaluationContext _evaluationContext; private final SplitCacheConsumer _splitCacheConsumer; - public EvaluatorImp(SplitCacheConsumer splitCacheConsumer, SegmentCacheConsumer segmentCache) { + public EvaluatorImp(SplitCacheConsumer splitCacheConsumer, SegmentCacheConsumer segmentCache, + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer) { _splitCacheConsumer = checkNotNull(splitCacheConsumer); _segmentCacheConsumer = checkNotNull(segmentCache); - _evaluationContext = new EvaluationContext(this, _segmentCacheConsumer); + _ruleBasedSegmentCacheConsumer = checkNotNull(ruleBasedSegmentCacheConsumer); + _evaluationContext = new EvaluationContext(this, _segmentCacheConsumer, ruleBasedSegmentCacheConsumer); } @Override diff --git a/client/src/main/java/io/split/engine/experiments/ParsedRuleBasedSegment.java b/client/src/main/java/io/split/engine/experiments/ParsedRuleBasedSegment.java new file mode 100644 index 000000000..fc6b5d008 --- /dev/null +++ b/client/src/main/java/io/split/engine/experiments/ParsedRuleBasedSegment.java @@ -0,0 +1,133 @@ +package io.split.engine.experiments; + +import com.google.common.collect.ImmutableList; +import io.split.engine.matchers.AttributeMatcher; +import io.split.engine.matchers.UserDefinedSegmentMatcher; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * a value class representing an io.codigo.dtos.Experiment. Why are we not using + * that class? Because it does not have the logic of matching. ParsedExperiment + * has the matchers that also encapsulate the logic of matching. We + * can easily cache this object. + * + * @author adil + */ +public class ParsedRuleBasedSegment { + + private final String _ruleBasedSegment; + private final ImmutableList _parsedCondition; + private final String _trafficTypeName; + private final long _changeNumber; + private final List _excludedKeys; + private final List _excludedSegments; + + public static ParsedRuleBasedSegment createParsedRuleBasedSegmentForTests( + String ruleBasedSegment, + List matcherAndSplits, + String trafficTypeName, + long changeNumber, + List excludedKeys, + List excludedSegments + ) { + return new ParsedRuleBasedSegment( + ruleBasedSegment, + matcherAndSplits, + trafficTypeName, + changeNumber, + excludedKeys, + excludedSegments + ); + } + + public ParsedRuleBasedSegment( + String ruleBasedSegment, + List matcherAndSplits, + String trafficTypeName, + long changeNumber, + List excludedKeys, + List excludedSegments + ) { + _ruleBasedSegment = ruleBasedSegment; + _parsedCondition = ImmutableList.copyOf(matcherAndSplits); + _trafficTypeName = trafficTypeName; + _changeNumber = changeNumber; + _excludedKeys = excludedKeys; + _excludedSegments = excludedSegments; + } + + public String ruleBasedSegment() { + return _ruleBasedSegment; + } + + public List parsedConditions() { + return _parsedCondition; + } + + public String trafficTypeName() {return _trafficTypeName;} + + public long changeNumber() {return _changeNumber;} + + public List excludedKeys() {return _excludedKeys;} + public List excludedSegments() {return _excludedSegments;} + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + _ruleBasedSegment.hashCode(); + result = 31 * result + _parsedCondition.hashCode(); + result = 31 * result + (_trafficTypeName == null ? 0 : _trafficTypeName.hashCode()); + result = 31 * result + (int)(_changeNumber ^ (_changeNumber >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) return false; + if (this == obj) return true; + if (!(obj instanceof ParsedRuleBasedSegment)) return false; + + ParsedRuleBasedSegment other = (ParsedRuleBasedSegment) obj; + + return _ruleBasedSegment.equals(other._ruleBasedSegment) + && _parsedCondition.equals(other._parsedCondition) + && _trafficTypeName == null ? other._trafficTypeName == null : _trafficTypeName.equals(other._trafficTypeName) + && _changeNumber == other._changeNumber; + } + + @Override + public String toString() { + StringBuilder bldr = new StringBuilder(); + bldr.append("name:"); + bldr.append(_ruleBasedSegment); + bldr.append(", parsedConditions:"); + bldr.append(_parsedCondition); + bldr.append(", trafficTypeName:"); + bldr.append(_trafficTypeName); + bldr.append(", changeNumber:"); + bldr.append(_changeNumber); + return bldr.toString(); + + } + + public Set getSegmentsNames() { + return parsedConditions().stream() + .flatMap(parsedCondition -> parsedCondition.matcher().attributeMatchers().stream()) + .filter(ParsedRuleBasedSegment::isSegmentMatcher) + .map(ParsedRuleBasedSegment::asSegmentMatcherForEach) + .map(UserDefinedSegmentMatcher::getSegmentName) + .collect(Collectors.toSet()); + } + + private static boolean isSegmentMatcher(AttributeMatcher attributeMatcher) { + return ((AttributeMatcher.NegatableMatcher) attributeMatcher.matcher()).delegate() instanceof UserDefinedSegmentMatcher; + } + + private static UserDefinedSegmentMatcher asSegmentMatcherForEach(AttributeMatcher attributeMatcher) { + return (UserDefinedSegmentMatcher) ((AttributeMatcher.NegatableMatcher) attributeMatcher.matcher()).delegate(); + } + +} diff --git a/client/src/main/java/io/split/engine/experiments/RuleBasedSegmentParser.java b/client/src/main/java/io/split/engine/experiments/RuleBasedSegmentParser.java new file mode 100644 index 000000000..c734f425a --- /dev/null +++ b/client/src/main/java/io/split/engine/experiments/RuleBasedSegmentParser.java @@ -0,0 +1,220 @@ +package io.split.engine.experiments; + +import com.google.common.collect.Lists; +import io.split.client.dtos.*; +import io.split.client.dtos.Matcher; +import io.split.engine.evaluator.Labels; +import io.split.engine.matchers.*; +import io.split.engine.matchers.collections.ContainsAllOfSetMatcher; +import io.split.engine.matchers.collections.ContainsAnyOfSetMatcher; +import io.split.engine.matchers.collections.EqualToSetMatcher; +import io.split.engine.matchers.collections.PartOfSetMatcher; +import io.split.engine.matchers.strings.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Objects; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Converts io.codigo.dtos.Experiment to io.codigo.engine.splits.ParsedExperiment. + * + * @author adil + */ +public final class RuleBasedSegmentParser { + + private static final Logger _log = LoggerFactory.getLogger(RuleBasedSegmentParser.class); + + public RuleBasedSegmentParser() { + } + + public ParsedRuleBasedSegment parse(RuleBasedSegment ruleBasedSegment) { + try { + return parseWithoutExceptionHandling(ruleBasedSegment); + } catch (Throwable t) { + _log.error("Could not parse rule based segment: " + ruleBasedSegment, t); + return null; + } + } + + private ParsedRuleBasedSegment parseWithoutExceptionHandling(RuleBasedSegment ruleBasedSegment) { + List parsedConditionList = Lists.newArrayList(); + for (Condition condition : ruleBasedSegment.conditions) { + List partitions = condition.partitions; + if (checkUnsupportedMatcherExist(condition.matcherGroup.matchers)) { + _log.error("Unsupported matcher type found for rule based segment: " + ruleBasedSegment.name + + " , will revert to default template matcher."); + parsedConditionList.clear(); + parsedConditionList.add(getTemplateCondition()); + break; + } + CombiningMatcher matcher = toMatcher(condition.matcherGroup); + parsedConditionList.add(new ParsedCondition(condition.conditionType, matcher, partitions, condition.label)); + } + + return new ParsedRuleBasedSegment( + ruleBasedSegment.name, + parsedConditionList, + ruleBasedSegment.trafficTypeName, + ruleBasedSegment.changeNumber, + ruleBasedSegment.excluded.keys, + ruleBasedSegment.excluded.segments); + } + + private boolean checkUnsupportedMatcherExist(List matchers) { + MatcherType typeCheck = null; + for (Matcher matcher : matchers) { + typeCheck = null; + try { + typeCheck = matcher.matcherType; + } catch (NullPointerException e) { + // If the exception is caught, it means unsupported matcher + break; + } + } + if (typeCheck != null) return false; + return true; + } + + private ParsedCondition getTemplateCondition() { + List templatePartitions = Lists.newArrayList(); + Partition partition = new Partition(); + partition.treatment = "control"; + partition.size = 100; + templatePartitions.add(partition); + return new ParsedCondition( + ConditionType.ROLLOUT, + CombiningMatcher.of(new AllKeysMatcher()), + templatePartitions, + Labels.UNSUPPORTED_MATCHER); + } + + private CombiningMatcher toMatcher(MatcherGroup matcherGroup) { + List matchers = matcherGroup.matchers; + checkArgument(!matchers.isEmpty()); + + List toCombine = Lists.newArrayList(); + + for (Matcher matcher : matchers) { + toCombine.add(toMatcher(matcher)); + } + + return new CombiningMatcher(matcherGroup.combiner, toCombine); + } + + + private AttributeMatcher toMatcher(Matcher matcher) { + io.split.engine.matchers.Matcher delegate = null; + switch (matcher.matcherType) { + case ALL_KEYS: + delegate = new AllKeysMatcher(); + break; + case IN_SEGMENT: + checkNotNull(matcher.userDefinedSegmentMatcherData); + String segmentName = matcher.userDefinedSegmentMatcherData.segmentName; + delegate = new UserDefinedSegmentMatcher(segmentName); + break; + case WHITELIST: + checkNotNull(matcher.whitelistMatcherData); + delegate = new WhitelistMatcher(matcher.whitelistMatcherData.whitelist); + break; + case EQUAL_TO: + checkNotNull(matcher.unaryNumericMatcherData); + delegate = new EqualToMatcher(matcher.unaryNumericMatcherData.value, matcher.unaryNumericMatcherData.dataType); + break; + case GREATER_THAN_OR_EQUAL_TO: + checkNotNull(matcher.unaryNumericMatcherData); + delegate = new GreaterThanOrEqualToMatcher(matcher.unaryNumericMatcherData.value, matcher.unaryNumericMatcherData.dataType); + break; + case LESS_THAN_OR_EQUAL_TO: + checkNotNull(matcher.unaryNumericMatcherData); + delegate = new LessThanOrEqualToMatcher(matcher.unaryNumericMatcherData.value, matcher.unaryNumericMatcherData.dataType); + break; + case BETWEEN: + checkNotNull(matcher.betweenMatcherData); + delegate = new BetweenMatcher(matcher.betweenMatcherData.start, matcher.betweenMatcherData.end, matcher.betweenMatcherData.dataType); + break; + case EQUAL_TO_SET: + checkNotNull(matcher.whitelistMatcherData); + delegate = new EqualToSetMatcher(matcher.whitelistMatcherData.whitelist); + break; + case PART_OF_SET: + checkNotNull(matcher.whitelistMatcherData); + delegate = new PartOfSetMatcher(matcher.whitelistMatcherData.whitelist); + break; + case CONTAINS_ALL_OF_SET: + checkNotNull(matcher.whitelistMatcherData); + delegate = new ContainsAllOfSetMatcher(matcher.whitelistMatcherData.whitelist); + break; + case CONTAINS_ANY_OF_SET: + checkNotNull(matcher.whitelistMatcherData); + delegate = new ContainsAnyOfSetMatcher(matcher.whitelistMatcherData.whitelist); + break; + case STARTS_WITH: + checkNotNull(matcher.whitelistMatcherData); + delegate = new StartsWithAnyOfMatcher(matcher.whitelistMatcherData.whitelist); + break; + case ENDS_WITH: + checkNotNull(matcher.whitelistMatcherData); + delegate = new EndsWithAnyOfMatcher(matcher.whitelistMatcherData.whitelist); + break; + case CONTAINS_STRING: + checkNotNull(matcher.whitelistMatcherData); + delegate = new ContainsAnyOfMatcher(matcher.whitelistMatcherData.whitelist); + break; + case MATCHES_STRING: + checkNotNull(matcher.stringMatcherData); + delegate = new RegularExpressionMatcher(matcher.stringMatcherData); + break; + case IN_SPLIT_TREATMENT: + checkNotNull(matcher.dependencyMatcherData, + "MatcherType is " + matcher.matcherType + + ". matcher.dependencyMatcherData() MUST NOT BE null"); + delegate = new DependencyMatcher(matcher.dependencyMatcherData.split, matcher.dependencyMatcherData.treatments); + break; + case EQUAL_TO_BOOLEAN: + checkNotNull(matcher.booleanMatcherData, + "MatcherType is " + matcher.matcherType + + ". matcher.booleanMatcherData() MUST NOT BE null"); + delegate = new BooleanMatcher(matcher.booleanMatcherData); + break; + case EQUAL_TO_SEMVER: + checkNotNull(matcher.stringMatcherData, "stringMatcherData is required for EQUAL_TO_SEMVER matcher type"); + delegate = new EqualToSemverMatcher(matcher.stringMatcherData); + break; + case GREATER_THAN_OR_EQUAL_TO_SEMVER: + checkNotNull(matcher.stringMatcherData, "stringMatcherData is required for GREATER_THAN_OR_EQUAL_TO_SEMVER matcher type"); + delegate = new GreaterThanOrEqualToSemverMatcher(matcher.stringMatcherData); + break; + case LESS_THAN_OR_EQUAL_TO_SEMVER: + checkNotNull(matcher.stringMatcherData, "stringMatcherData is required for LESS_THAN_OR_EQUAL_SEMVER matcher type"); + delegate = new LessThanOrEqualToSemverMatcher(matcher.stringMatcherData); + break; + case IN_LIST_SEMVER: + checkNotNull(matcher.whitelistMatcherData, "whitelistMatcherData is required for IN_LIST_SEMVER matcher type"); + delegate = new InListSemverMatcher(matcher.whitelistMatcherData.whitelist); + break; + case BETWEEN_SEMVER: + checkNotNull(matcher.betweenStringMatcherData, "betweenStringMatcherData is required for BETWEEN_SEMVER matcher type"); + delegate = new BetweenSemverMatcher(matcher.betweenStringMatcherData.start, matcher.betweenStringMatcherData.end); + break; + default: + throw new IllegalArgumentException("Unknown matcher type: " + matcher.matcherType); + } + + checkNotNull(delegate, "We were not able to create a matcher for: " + matcher.matcherType); + + String attribute = null; + if (matcher.keySelector != null && matcher.keySelector.attribute != null) { + attribute = matcher.keySelector.attribute; + } + + boolean negate = matcher.negate; + + + return new AttributeMatcher(attribute, delegate, negate); + } +} \ No newline at end of file diff --git a/client/src/main/java/io/split/engine/experiments/SplitChangeFetcher.java b/client/src/main/java/io/split/engine/experiments/SplitChangeFetcher.java index 7c5fbe76e..da6e185fa 100644 --- a/client/src/main/java/io/split/engine/experiments/SplitChangeFetcher.java +++ b/client/src/main/java/io/split/engine/experiments/SplitChangeFetcher.java @@ -32,5 +32,5 @@ public interface SplitChangeFetcher { * @return SegmentChange * @throws java.lang.RuntimeException if there was a problem computing split changes */ - SplitChange fetch(long since, FetchOptions options); + SplitChange fetch(long since, long sinceRBS, FetchOptions options); } diff --git a/client/src/main/java/io/split/engine/experiments/SplitFetcherImp.java b/client/src/main/java/io/split/engine/experiments/SplitFetcherImp.java index 84b6a287d..6834ecaeb 100644 --- a/client/src/main/java/io/split/engine/experiments/SplitFetcherImp.java +++ b/client/src/main/java/io/split/engine/experiments/SplitFetcherImp.java @@ -1,9 +1,12 @@ package io.split.engine.experiments; +import io.split.Spec; import io.split.client.dtos.SplitChange; import io.split.client.exceptions.UriTooLongException; import io.split.client.interceptors.FlagSetsFilter; import io.split.client.utils.FeatureFlagsToUpdate; +import io.split.client.utils.RuleBasedSegmentsToUpdate; +import io.split.storages.RuleBasedSegmentCacheProducer; import io.split.storages.SplitCacheProducer; import io.split.telemetry.domain.enums.LastSynchronizationRecordsEnum; import io.split.telemetry.storage.TelemetryRuntimeProducer; @@ -16,6 +19,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import static io.split.client.utils.FeatureFlagProcessor.processFeatureFlagChanges; +import static io.split.client.utils.FeatureFlagProcessor.processRuleBasedSegmentChanges; /** * An ExperimentFetcher that refreshes experiment definitions periodically. @@ -32,6 +36,8 @@ public class SplitFetcherImp implements SplitFetcher { private final Object _lock = new Object(); private final TelemetryRuntimeProducer _telemetryRuntimeProducer; private final FlagSetsFilter _flagSetsFilter; + private final RuleBasedSegmentCacheProducer _ruleBasedSegmentCacheProducer; + private final RuleBasedSegmentParser _parserRBS; /** * Contains all the traffic types that are currently being used by the splits and also the count @@ -44,10 +50,13 @@ public class SplitFetcherImp implements SplitFetcher { */ public SplitFetcherImp(SplitChangeFetcher splitChangeFetcher, SplitParser parser, SplitCacheProducer splitCacheProducer, - TelemetryRuntimeProducer telemetryRuntimeProducer, FlagSetsFilter flagSetsFilter) { + TelemetryRuntimeProducer telemetryRuntimeProducer, FlagSetsFilter flagSetsFilter, + RuleBasedSegmentParser parserRBS, RuleBasedSegmentCacheProducer ruleBasedSegmentCacheProducer) { _splitChangeFetcher = checkNotNull(splitChangeFetcher); _parser = checkNotNull(parser); + _parserRBS = checkNotNull(parserRBS); _splitCacheProducer = checkNotNull(splitCacheProducer); + _ruleBasedSegmentCacheProducer = checkNotNull(ruleBasedSegmentCacheProducer); _telemetryRuntimeProducer = checkNotNull(telemetryRuntimeProducer); _flagSetsFilter = flagSetsFilter; } @@ -56,21 +65,31 @@ public SplitFetcherImp(SplitChangeFetcher splitChangeFetcher, SplitParser parser public FetchResult forceRefresh(FetchOptions options) { _log.debug("Force Refresh feature flags starting ..."); final long INITIAL_CN = _splitCacheProducer.getChangeNumber(); + final long RBS_INITIAL_CN = _ruleBasedSegmentCacheProducer.getChangeNumber(); Set segments = new HashSet<>(); try { while (true) { long start = _splitCacheProducer.getChangeNumber(); + long startRBS = _ruleBasedSegmentCacheProducer.getChangeNumber(); segments.addAll(runWithoutExceptionHandling(options)); long end = _splitCacheProducer.getChangeNumber(); + long endRBS = _ruleBasedSegmentCacheProducer.getChangeNumber(); + long targetChaneNumber = -1; + long targetChaneNumberRBS = -1; // If the previous execution was the first one, clear the `cdnBypass` flag // for the next fetches. (This will clear a local copy of the fetch options, // not the original object that was passed to this method). - if (INITIAL_CN == start) { - options = new FetchOptions.Builder(options).targetChangeNumber(FetchOptions.DEFAULT_TARGET_CHANGENUMBER).build(); + if (((INITIAL_CN == start || RBS_INITIAL_CN == startRBS) && Spec.SPEC_VERSION.equals(Spec.SPEC_1_3)) || + (INITIAL_CN == start && Spec.SPEC_VERSION.equals(Spec.SPEC_1_1))) { + if (INITIAL_CN == start) targetChaneNumber = FetchOptions.DEFAULT_TARGET_CHANGENUMBER; + if (RBS_INITIAL_CN == startRBS) targetChaneNumberRBS = FetchOptions.DEFAULT_TARGET_CHANGENUMBER; + options = new FetchOptions.Builder(options).targetChangeNumber(targetChaneNumber). + targetChangeNumberRBS(targetChaneNumberRBS).build(); } - if (start >= end) { + if ((start >= end && startRBS >= endRBS && Spec.SPEC_VERSION.equals(Spec.SPEC_1_3)) || + (start >= end && Spec.SPEC_VERSION.equals(Spec.SPEC_1_1))) { return new FetchResult(true, false, segments); } } @@ -82,6 +101,7 @@ public FetchResult forceRefresh(FetchOptions options) { return new FetchResult(false, true, new HashSet<>()); } catch (Exception e) { _log.error("RefreshableSplitFetcher failed: " + e.getMessage()); + _log.error("Reason:", e); if (_log.isDebugEnabled()) { _log.debug("Reason:", e); } @@ -95,36 +115,64 @@ public void run() { } private Set runWithoutExceptionHandling(FetchOptions options) throws InterruptedException, UriTooLongException { - SplitChange change = _splitChangeFetcher.fetch(_splitCacheProducer.getChangeNumber(), options); + SplitChange change = _splitChangeFetcher.fetch(_splitCacheProducer.getChangeNumber(), + _ruleBasedSegmentCacheProducer.getChangeNumber(), options); Set segments = new HashSet<>(); if (change == null) { throw new IllegalStateException("SplitChange was null"); } - if (change.since != _splitCacheProducer.getChangeNumber() || change.till < _splitCacheProducer.getChangeNumber()) { - // some other thread may have updated the shared state. exit + if (checkExitConditions(change)) { return segments; } - if (change.splits.isEmpty()) { - // there are no changes. weird! - _splitCacheProducer.setChangeNumber(change.till); - return segments; + if ((Spec.SPEC_VERSION.equals(Spec.SPEC_1_3) && (change.splits.isEmpty() || change.ruleBasedSegments.isEmpty())) || + (change.splits.isEmpty() && Spec.SPEC_VERSION.equals(Spec.SPEC_1_1))) { + if (change.splits.isEmpty()) _splitCacheProducer.setChangeNumber(change.till); + if (Spec.SPEC_VERSION.equals(Spec.SPEC_1_3) && change.ruleBasedSegments.isEmpty()) + _ruleBasedSegmentCacheProducer.setChangeNumber(change.till); + if (Spec.SPEC_VERSION.equals(Spec.SPEC_1_3) && (change.splits.isEmpty() && change.ruleBasedSegments.isEmpty()) || + (change.splits.isEmpty() && Spec.SPEC_VERSION.equals(Spec.SPEC_1_1))) return segments; } synchronized (_lock) { // check state one more time. - if (change.since != _splitCacheProducer.getChangeNumber() - || change.till < _splitCacheProducer.getChangeNumber()) { + if (checkReturnConditions(change)) { // some other thread may have updated the shared state. exit return segments; } FeatureFlagsToUpdate featureFlagsToUpdate = processFeatureFlagChanges(_parser, change.splits, _flagSetsFilter); segments = featureFlagsToUpdate.getSegments(); _splitCacheProducer.update(featureFlagsToUpdate.getToAdd(), featureFlagsToUpdate.getToRemove(), change.till); + + if (Spec.SPEC_VERSION.equals(Spec.SPEC_1_3)) { + RuleBasedSegmentsToUpdate ruleBasedSegmentsToUpdate = processRuleBasedSegmentChanges(_parserRBS, change.ruleBasedSegments); + segments = ruleBasedSegmentsToUpdate.getSegments(); + _ruleBasedSegmentCacheProducer.update(ruleBasedSegmentsToUpdate.getToAdd(), ruleBasedSegmentsToUpdate.getToRemove(), change.tillRBS); + } _telemetryRuntimeProducer.recordSuccessfulSync(LastSynchronizationRecordsEnum.SPLITS, System.currentTimeMillis()); } return segments; } + + private boolean checkExitConditions(SplitChange change) { + if (Spec.SPEC_VERSION.equals(Spec.SPEC_1_3)) { + return ((change.since != _splitCacheProducer.getChangeNumber() || change.till < _splitCacheProducer.getChangeNumber()) + || (change.sinceRBS != _ruleBasedSegmentCacheProducer.getChangeNumber() || + change.tillRBS < _ruleBasedSegmentCacheProducer.getChangeNumber())); + } else { + return (change.since != _splitCacheProducer.getChangeNumber() || change.till < _splitCacheProducer.getChangeNumber()); + } + } + + private boolean checkReturnConditions(SplitChange change) { + if (Spec.SPEC_VERSION.equals(Spec.SPEC_1_3)) { + return ((change.since != _splitCacheProducer.getChangeNumber() || change.till < _splitCacheProducer.getChangeNumber()) && + (change.sinceRBS != _ruleBasedSegmentCacheProducer.getChangeNumber() || + change.tillRBS < _ruleBasedSegmentCacheProducer.getChangeNumber())); + } else { + return (change.since != _splitCacheProducer.getChangeNumber() || change.till < _splitCacheProducer.getChangeNumber()); + } + } } \ No newline at end of file diff --git a/client/src/main/java/io/split/engine/matchers/RuleBasedSegmentMatcher.java b/client/src/main/java/io/split/engine/matchers/RuleBasedSegmentMatcher.java new file mode 100644 index 000000000..ba5b8f41e --- /dev/null +++ b/client/src/main/java/io/split/engine/matchers/RuleBasedSegmentMatcher.java @@ -0,0 +1,83 @@ +package io.split.engine.matchers; + +import io.split.engine.evaluator.EvaluationContext; +import io.split.engine.experiments.ParsedCondition; +import io.split.engine.experiments.ParsedRuleBasedSegment; + +import java.util.List; +import java.util.Map; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * A matcher that checks if the key is part of a user defined segment. This class + * assumes that the logic for refreshing what keys are part of a segment is delegated + * to SegmentFetcher. + * + * @author adil + */ +public class RuleBasedSegmentMatcher implements Matcher { + private final String _segmentName; + + public RuleBasedSegmentMatcher(String segmentName) { + _segmentName = checkNotNull(segmentName); + } + + @Override + public boolean match(Object matchValue, String bucketingKey, Map attributes, EvaluationContext evaluationContext) { + if (!(matchValue instanceof String)) { + return false; + } + ParsedRuleBasedSegment parsedRuleBasedSegment = evaluationContext.getRuleBasedSegmentCache().get(_segmentName); + if (parsedRuleBasedSegment == null) { + return false; + } + + if (parsedRuleBasedSegment.excludedKeys().contains(matchValue)) { + return false; + } + + for (String segmentName: parsedRuleBasedSegment.excludedSegments()) { + if (evaluationContext.getSegmentCache().isInSegment(segmentName, (String) matchValue)) { + return false; + } + } + List conditions = parsedRuleBasedSegment.parsedConditions(); + for (ParsedCondition parsedCondition : conditions) { + if (parsedCondition.matcher().match((String) matchValue, bucketingKey, attributes, evaluationContext)) { + return true; + } + } + return false; + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + _segmentName.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) return false; + if (this == obj) return true; + if (!(obj instanceof RuleBasedSegmentMatcher)) return false; + + RuleBasedSegmentMatcher other = (RuleBasedSegmentMatcher) obj; + + return _segmentName.equals(other._segmentName); + } + + @Override + public String toString() { + StringBuilder bldr = new StringBuilder(); + bldr.append("in segment "); + bldr.append(_segmentName); + return bldr.toString(); + } + + public String getSegmentName() { + return _segmentName; + } +} diff --git a/client/src/main/java/io/split/inputValidation/ImpressionPropertiesValidator.java b/client/src/main/java/io/split/inputValidation/ImpressionPropertiesValidator.java new file mode 100644 index 000000000..474699343 --- /dev/null +++ b/client/src/main/java/io/split/inputValidation/ImpressionPropertiesValidator.java @@ -0,0 +1,18 @@ +package io.split.inputValidation; + +import java.util.Map; + +public class ImpressionPropertiesValidator { + + public static ImpressionPropertiesValidatorResult propertiesAreValid(Map properties) { + EventsValidator.EventValidatorResult result = EventsValidator.propertiesAreValid(properties); + return new ImpressionPropertiesValidatorResult(result.getSuccess(), result.getEventSize(), result.getValue()); + } + + public static class ImpressionPropertiesValidatorResult extends EventsValidator.EventValidatorResult { + public ImpressionPropertiesValidatorResult(boolean success, int eventSize, Map value) { + super(success, eventSize, value); + } + } +} + diff --git a/client/src/main/java/io/split/storages/RuleBasedSegmentCache.java b/client/src/main/java/io/split/storages/RuleBasedSegmentCache.java new file mode 100644 index 000000000..5ba55b819 --- /dev/null +++ b/client/src/main/java/io/split/storages/RuleBasedSegmentCache.java @@ -0,0 +1,4 @@ +package io.split.storages; + +public interface RuleBasedSegmentCache extends RuleBasedSegmentCacheConsumer, RuleBasedSegmentCacheProducer { +} diff --git a/client/src/main/java/io/split/storages/RuleBasedSegmentCacheCommons.java b/client/src/main/java/io/split/storages/RuleBasedSegmentCacheCommons.java new file mode 100644 index 000000000..39a558ea7 --- /dev/null +++ b/client/src/main/java/io/split/storages/RuleBasedSegmentCacheCommons.java @@ -0,0 +1,8 @@ +package io.split.storages; + +import java.util.Set; + +public interface RuleBasedSegmentCacheCommons { + long getChangeNumber(); + Set getSegments(); +} diff --git a/client/src/main/java/io/split/storages/RuleBasedSegmentCacheConsumer.java b/client/src/main/java/io/split/storages/RuleBasedSegmentCacheConsumer.java new file mode 100644 index 000000000..fe582a97f --- /dev/null +++ b/client/src/main/java/io/split/storages/RuleBasedSegmentCacheConsumer.java @@ -0,0 +1,13 @@ +package io.split.storages; + +import io.split.engine.experiments.ParsedRuleBasedSegment; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +public interface RuleBasedSegmentCacheConsumer extends RuleBasedSegmentCacheCommons { + ParsedRuleBasedSegment get(String name); + Collection getAll(); + List ruleBasedSegmentNames(); +} \ No newline at end of file diff --git a/client/src/main/java/io/split/storages/RuleBasedSegmentCacheProducer.java b/client/src/main/java/io/split/storages/RuleBasedSegmentCacheProducer.java new file mode 100644 index 000000000..e3c480478 --- /dev/null +++ b/client/src/main/java/io/split/storages/RuleBasedSegmentCacheProducer.java @@ -0,0 +1,11 @@ +package io.split.storages; + +import io.split.engine.experiments.ParsedRuleBasedSegment; + +import java.util.List; + +public interface RuleBasedSegmentCacheProducer extends RuleBasedSegmentCacheCommons{ + boolean remove(String name); + void setChangeNumber(long changeNumber); + void update(List toAdd, List toRemove, long changeNumber); +} diff --git a/client/src/main/java/io/split/storages/SplitCacheConsumer.java b/client/src/main/java/io/split/storages/SplitCacheConsumer.java index 7fbc57486..abe5b3430 100644 --- a/client/src/main/java/io/split/storages/SplitCacheConsumer.java +++ b/client/src/main/java/io/split/storages/SplitCacheConsumer.java @@ -7,7 +7,7 @@ import java.util.List; import java.util.Map; -public interface SplitCacheConsumer extends SplitCacheCommons{ +public interface SplitCacheConsumer extends SplitCacheCommons{ ParsedSplit get(String name); Collection getAll(); Map fetchMany(List names); diff --git a/client/src/main/java/io/split/storages/memory/RuleBasedSegmentCacheInMemoryImp.java b/client/src/main/java/io/split/storages/memory/RuleBasedSegmentCacheInMemoryImp.java new file mode 100644 index 000000000..a1f93fd8f --- /dev/null +++ b/client/src/main/java/io/split/storages/memory/RuleBasedSegmentCacheInMemoryImp.java @@ -0,0 +1,101 @@ +package io.split.storages.memory; + +import com.google.common.collect.Maps; +import io.split.engine.experiments.ParsedRuleBasedSegment; +import io.split.storages.RuleBasedSegmentCache; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.ArrayList; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; + +public class RuleBasedSegmentCacheInMemoryImp implements RuleBasedSegmentCache { + + private static final Logger _log = LoggerFactory.getLogger(RuleBasedSegmentCacheInMemoryImp.class); + + private final ConcurrentMap _concurrentMap; + + private AtomicLong _changeNumber; + + public RuleBasedSegmentCacheInMemoryImp() { + this(-1); + } + + public RuleBasedSegmentCacheInMemoryImp(long startingChangeNumber) { + _concurrentMap = Maps.newConcurrentMap(); + _changeNumber = new AtomicLong(startingChangeNumber); + } + + @Override + public boolean remove(String name) { + ParsedRuleBasedSegment removed = _concurrentMap.remove(name); + return removed != null; + } + + @Override + public ParsedRuleBasedSegment get(String name) { + return _concurrentMap.get(name); + } + + @Override + public Collection getAll() { + return _concurrentMap.values(); + } + + @Override + public long getChangeNumber() { + return _changeNumber.get(); + } + + @Override + public void setChangeNumber(long changeNumber) { + if (changeNumber < _changeNumber.get()) { + _log.error("ChangeNumber for feature flags cache is less than previous"); + } + + _changeNumber.set(changeNumber); + } + + @Override + public List ruleBasedSegmentNames() { + List ruleBasedSegmentNamesList = new ArrayList<>(); + for (String key: _concurrentMap.keySet()) { + ruleBasedSegmentNamesList.add(_concurrentMap.get(key).ruleBasedSegment()); + } + return ruleBasedSegmentNamesList; + } + + public void clear() { + _concurrentMap.clear(); + } + + public void putMany(List ruleBasedSegments) { + for (ParsedRuleBasedSegment ruleBasedSegment : ruleBasedSegments) { + _concurrentMap.put(ruleBasedSegment.ruleBasedSegment(), ruleBasedSegment); + } + } + + @Override + public void update(List toAdd, List toRemove, long changeNumber) { + if(toAdd != null) { + putMany(toAdd); + } + if(toRemove != null) { + for(String ruleBasedSegment : toRemove) { + remove(ruleBasedSegment); + } + } + setChangeNumber(changeNumber); + } + + public Set getSegments() { + return _concurrentMap.values().stream() + .flatMap(parsedRuleBasedSegment -> parsedRuleBasedSegment.getSegmentsNames().stream()).collect(Collectors.toSet()); + } +} \ No newline at end of file diff --git a/client/src/test/java/io/split/client/HttpSplitChangeFetcherTest.java b/client/src/test/java/io/split/client/HttpSplitChangeFetcherTest.java index 8d18f8456..33a1250f9 100644 --- a/client/src/test/java/io/split/client/HttpSplitChangeFetcherTest.java +++ b/client/src/test/java/io/split/client/HttpSplitChangeFetcherTest.java @@ -94,7 +94,7 @@ public void testFetcherWithSpecialCharacters() throws URISyntaxException, Invoca HttpSplitChangeFetcher fetcher = HttpSplitChangeFetcher.create(splitHtpClient, rootTarget, TELEMETRY_STORAGE); - SplitChange change = fetcher.fetch(1234567, new FetchOptions.Builder().cacheControlHeaders(true).build()); + SplitChange change = fetcher.fetch(1234567, -1, new FetchOptions.Builder().cacheControlHeaders(true).build()); Assert.assertNotNull(change); Assert.assertEquals(1, change.splits.size()); @@ -131,8 +131,8 @@ public void testFetcherWithCDNBypassOption() throws IOException, URISyntaxExcept HttpSplitChangeFetcher fetcher = HttpSplitChangeFetcher.create(splitHtpClient, rootTarget, Mockito.mock(TelemetryRuntimeProducer.class)); - fetcher.fetch(-1, new FetchOptions.Builder().targetChangeNumber(123).build()); - fetcher.fetch(-1, new FetchOptions.Builder().build()); + fetcher.fetch(-1, -1, new FetchOptions.Builder().targetChangeNumber(123).build()); + fetcher.fetch(-1, -1, new FetchOptions.Builder().build()); List captured = requestCaptor.getAllValues(); Assert.assertEquals(captured.size(), 2); Assert.assertTrue(captured.get(0).getUri().toString().contains("till=123")); @@ -188,7 +188,7 @@ public void testURLTooLong() throws IOException, URISyntaxException, IllegalAcce String result = sets.stream() .map(n -> String.valueOf(n)) .collect(Collectors.joining(",", "", "")); - fetcher.fetch(-1, new FetchOptions.Builder().flagSetsFilter(result).cacheControlHeaders(false).build()); + fetcher.fetch(-1, -1, new FetchOptions.Builder().flagSetsFilter(result).cacheControlHeaders(false).build()); } private SDKMetadata metadata() { diff --git a/client/src/test/java/io/split/client/JsonLocalhostSplitChangeFetcherTest.java b/client/src/test/java/io/split/client/JsonLocalhostSplitChangeFetcherTest.java index c355734aa..4589a2878 100644 --- a/client/src/test/java/io/split/client/JsonLocalhostSplitChangeFetcherTest.java +++ b/client/src/test/java/io/split/client/JsonLocalhostSplitChangeFetcherTest.java @@ -39,7 +39,7 @@ public void testParseSplitChange() throws FileNotFoundException { JsonLocalhostSplitChangeFetcher localhostSplitChangeFetcher = new JsonLocalhostSplitChangeFetcher(inputStreamProvider); FetchOptions fetchOptions = Mockito.mock(FetchOptions.class); - SplitChange splitChange = localhostSplitChangeFetcher.fetch(-1L, fetchOptions); + SplitChange splitChange = localhostSplitChangeFetcher.fetch(-1L, -1, fetchOptions); List split = splitChange.splits; Assert.assertEquals(7, split.size()); @@ -54,7 +54,7 @@ public void testSinceAndTillSanitization() throws FileNotFoundException { JsonLocalhostSplitChangeFetcher localhostSplitChangeFetcher = new JsonLocalhostSplitChangeFetcher(inputStreamProvider); FetchOptions fetchOptions = Mockito.mock(FetchOptions.class); - SplitChange splitChange = localhostSplitChangeFetcher.fetch(-1L, fetchOptions); + SplitChange splitChange = localhostSplitChangeFetcher.fetch(-1L, -1, fetchOptions); Assert.assertEquals(-1L, splitChange.till); Assert.assertEquals(-1L, splitChange.since); @@ -67,7 +67,7 @@ public void testSplitChangeWithoutSplits() throws FileNotFoundException { JsonLocalhostSplitChangeFetcher localhostSplitChangeFetcher = new JsonLocalhostSplitChangeFetcher(inputStreamProvider); FetchOptions fetchOptions = Mockito.mock(FetchOptions.class); - SplitChange splitChange = localhostSplitChangeFetcher.fetch(-1L, fetchOptions); + SplitChange splitChange = localhostSplitChangeFetcher.fetch(-1L, -1, fetchOptions); Assert.assertEquals(0, splitChange.splits.size()); } @@ -79,7 +79,7 @@ public void testSplitChangeSplitsToSanitize() throws FileNotFoundException { JsonLocalhostSplitChangeFetcher localhostSplitChangeFetcher = new JsonLocalhostSplitChangeFetcher(inputStreamProvider); FetchOptions fetchOptions = Mockito.mock(FetchOptions.class); - SplitChange splitChange = localhostSplitChangeFetcher.fetch(-1L, fetchOptions); + SplitChange splitChange = localhostSplitChangeFetcher.fetch(-1L, -1, fetchOptions); Assert.assertEquals(1, splitChange.splits.size()); Split split = splitChange.splits.get(0); @@ -96,7 +96,7 @@ public void testSplitChangeSplitsToSanitizeMatchersNull() throws FileNotFoundExc JsonLocalhostSplitChangeFetcher localhostSplitChangeFetcher = new JsonLocalhostSplitChangeFetcher(inputStreamProvider); FetchOptions fetchOptions = Mockito.mock(FetchOptions.class); - SplitChange splitChange = localhostSplitChangeFetcher.fetch(-1L, fetchOptions); + SplitChange splitChange = localhostSplitChangeFetcher.fetch(-1L, -1, fetchOptions); Assert.assertEquals(1, splitChange.splits.size()); Split split = splitChange.splits.get(0); @@ -119,7 +119,7 @@ public void testSplitChangeSplitsDifferentScenarios() throws IOException { FetchOptions fetchOptions = Mockito.mock(FetchOptions.class); // 0) The CN from storage is -1, till and since are -1, and sha doesn't exist in the hash. It's going to return a split change with updates. - SplitChange splitChange = localhostSplitChangeFetcher.fetch(-1L, fetchOptions); + SplitChange splitChange = localhostSplitChangeFetcher.fetch(-1L, -1, fetchOptions); Assert.assertEquals(1, splitChange.splits.size()); Assert.assertEquals(-1, splitChange.till); Assert.assertEquals(-1, splitChange.since); @@ -128,7 +128,7 @@ public void testSplitChangeSplitsDifferentScenarios() throws IOException { com.google.common.io.Files.write(test, file); // 1) The CN from storage is -1, till and since are -1, and sha is different than before. It's going to return a split change with updates. - splitChange = localhostSplitChangeFetcher.fetch(-1L, fetchOptions); + splitChange = localhostSplitChangeFetcher.fetch(-1L, -1, fetchOptions); Assert.assertEquals(2, splitChange.splits.size()); Assert.assertEquals(-1, splitChange.till); Assert.assertEquals(-1, splitChange.since); @@ -137,7 +137,7 @@ public void testSplitChangeSplitsDifferentScenarios() throws IOException { com.google.common.io.Files.write(test, file); // 2) The CN from storage is -1, till is 2323, and since is -1, and sha is the same as before. It's going to return a split change with the same data. - splitChange = localhostSplitChangeFetcher.fetch(-1L, fetchOptions); + splitChange = localhostSplitChangeFetcher.fetch(-1L, -1, fetchOptions); Assert.assertEquals(2, splitChange.splits.size()); Assert.assertEquals(-1, splitChange.till); Assert.assertEquals(-1, splitChange.since); @@ -146,7 +146,7 @@ public void testSplitChangeSplitsDifferentScenarios() throws IOException { com.google.common.io.Files.write(test, file); // 3) The CN from storage is -1, till is 2323, and since is -1, sha is different than before. It's going to return a split change with updates. - splitChange = localhostSplitChangeFetcher.fetch(-1L, fetchOptions); + splitChange = localhostSplitChangeFetcher.fetch(-1L, -1, fetchOptions); Assert.assertEquals(1, splitChange.splits.size()); Assert.assertEquals(2323, splitChange.till); Assert.assertEquals(-1, splitChange.since); @@ -155,7 +155,7 @@ public void testSplitChangeSplitsDifferentScenarios() throws IOException { com.google.common.io.Files.write(test, file); // 4) The CN from storage is 2323, till is 445345, and since is -1, and sha is the same as before. It's going to return a split change with same data. - splitChange = localhostSplitChangeFetcher.fetch(2323, fetchOptions); + splitChange = localhostSplitChangeFetcher.fetch(2323, -1, fetchOptions); Assert.assertEquals(1, splitChange.splits.size()); Assert.assertEquals(2323, splitChange.till); Assert.assertEquals(2323, splitChange.since); @@ -164,7 +164,7 @@ public void testSplitChangeSplitsDifferentScenarios() throws IOException { com.google.common.io.Files.write(test, file); // 5) The CN from storage is 2323, till and since are -1, and sha is different than before. It's going to return a split change with updates. - splitChange = localhostSplitChangeFetcher.fetch(2323, fetchOptions); + splitChange = localhostSplitChangeFetcher.fetch(2323, -1, fetchOptions); Assert.assertEquals(2, splitChange.splits.size()); Assert.assertEquals(2323, splitChange.till); Assert.assertEquals(2323, splitChange.since); @@ -176,6 +176,6 @@ public void processTestForException() { JsonLocalhostSplitChangeFetcher localhostSplitChangeFetcher = new JsonLocalhostSplitChangeFetcher(inputStreamProvider); FetchOptions fetchOptions = Mockito.mock(FetchOptions.class); - SplitChange splitChange = localhostSplitChangeFetcher.fetch(-1L, fetchOptions); + SplitChange splitChange = localhostSplitChangeFetcher.fetch(-1L, -1, fetchOptions); } } \ No newline at end of file diff --git a/client/src/test/java/io/split/client/LegacyLocalhostSplitChangeFetcherTest.java b/client/src/test/java/io/split/client/LegacyLocalhostSplitChangeFetcherTest.java index 90f958cc1..e9ecebd51 100644 --- a/client/src/test/java/io/split/client/LegacyLocalhostSplitChangeFetcherTest.java +++ b/client/src/test/java/io/split/client/LegacyLocalhostSplitChangeFetcherTest.java @@ -32,7 +32,7 @@ public void testParseSplitChange() throws IOException { LegacyLocalhostSplitChangeFetcher localhostSplitChangeFetcher = new LegacyLocalhostSplitChangeFetcher(folder.getRoot().getAbsolutePath()); FetchOptions fetchOptions = Mockito.mock(FetchOptions.class); - SplitChange splitChange = localhostSplitChangeFetcher.fetch(-1L, fetchOptions); + SplitChange splitChange = localhostSplitChangeFetcher.fetch(-1L, -1, fetchOptions); Assert.assertEquals(2, splitChange.splits.size()); Assert.assertEquals(-1, splitChange.since); diff --git a/client/src/test/java/io/split/client/LocalhostSplitFactoryTest.java b/client/src/test/java/io/split/client/LocalhostSplitFactoryTest.java index 8c4ad4e0c..c47b49d5c 100644 --- a/client/src/test/java/io/split/client/LocalhostSplitFactoryTest.java +++ b/client/src/test/java/io/split/client/LocalhostSplitFactoryTest.java @@ -1,6 +1,7 @@ package io.split.client; import com.google.common.collect.Maps; +import io.split.Spec; import io.split.client.utils.LocalhostUtils; import io.split.grammar.Treatments; import org.junit.Rule; @@ -36,6 +37,7 @@ public void works() throws IOException, URISyntaxException, InterruptedException LocalhostUtils.writeFile(file, map); SplitClientConfig config = SplitClientConfig.builder().splitFile(folder.getRoot().getAbsolutePath()).build(); + Spec.SPEC_VERSION = Spec.SPEC_1_1; // check old spec SplitFactory splitFactory = SplitFactoryBuilder.build("localhost", config); SplitClient client = splitFactory.client(); diff --git a/client/src/test/java/io/split/client/LocalhostSplitFactoryYamlCompactSampleTest.java b/client/src/test/java/io/split/client/LocalhostSplitFactoryYamlCompactSampleTest.java index 97110cc8d..170562dad 100644 --- a/client/src/test/java/io/split/client/LocalhostSplitFactoryYamlCompactSampleTest.java +++ b/client/src/test/java/io/split/client/LocalhostSplitFactoryYamlCompactSampleTest.java @@ -1,5 +1,6 @@ package io.split.client; +import io.split.Spec; import io.split.grammar.Treatments; import org.junit.Test; @@ -24,6 +25,7 @@ public void works() throws IOException, URISyntaxException { String file = getClass().getClassLoader().getResource("split_compact.yaml").getFile(); SplitClientConfig config = SplitClientConfig.builder().splitFile(file).build(); + Spec.SPEC_VERSION = Spec.SPEC_1_1; // check old spec SplitFactory splitFactory = SplitFactoryBuilder.build("localhost", config); SplitClient client = splitFactory.client(); @@ -49,6 +51,7 @@ public void worksYML() throws IOException, URISyntaxException { String file = getClass().getClassLoader().getResource("split_compact.yml").getFile(); SplitClientConfig config = SplitClientConfig.builder().splitFile(file).build(); + Spec.SPEC_VERSION = Spec.SPEC_1_1; // check old spec SplitFactory splitFactory = SplitFactoryBuilder.build("localhost", config); SplitClient client = splitFactory.client(); diff --git a/client/src/test/java/io/split/client/LocalhostSplitFactoryYamlSampleTest.java b/client/src/test/java/io/split/client/LocalhostSplitFactoryYamlSampleTest.java index c77aef3a2..82534ddf1 100644 --- a/client/src/test/java/io/split/client/LocalhostSplitFactoryYamlSampleTest.java +++ b/client/src/test/java/io/split/client/LocalhostSplitFactoryYamlSampleTest.java @@ -1,5 +1,6 @@ package io.split.client; +import io.split.Spec; import io.split.grammar.Treatments; import org.junit.Test; @@ -24,6 +25,7 @@ public void works() throws IOException, URISyntaxException { String file = getClass().getClassLoader().getResource(SplitClientConfig.LOCALHOST_DEFAULT_FILE).getFile(); SplitClientConfig config = SplitClientConfig.builder().splitFile(file).build(); + Spec.SPEC_VERSION = Spec.SPEC_1_1; // check old spec SplitFactory splitFactory = SplitFactoryBuilder.build("localhost", config); SplitClient client = splitFactory.client(); diff --git a/client/src/test/java/io/split/client/LocalhostSplitFactoryYamlTest.java b/client/src/test/java/io/split/client/LocalhostSplitFactoryYamlTest.java index 0a154f7d4..975e304b1 100644 --- a/client/src/test/java/io/split/client/LocalhostSplitFactoryYamlTest.java +++ b/client/src/test/java/io/split/client/LocalhostSplitFactoryYamlTest.java @@ -1,5 +1,6 @@ package io.split.client; +import io.split.Spec; import io.split.client.utils.LocalhostUtils; import io.split.grammar.Treatments; import io.split.service.SplitHttpClient; @@ -84,6 +85,7 @@ public void works() throws IOException, URISyntaxException { LocalhostUtils.writeFile(file, writer); SplitClientConfig config = SplitClientConfig.builder().splitFile(file.getAbsolutePath()).build(); + Spec.SPEC_VERSION = Spec.SPEC_1_1; // check old spec SplitFactory splitFactory = SplitFactoryBuilder.build("localhost", config); SplitClient client = splitFactory.client(); diff --git a/client/src/test/java/io/split/client/SplitClientImplTest.java b/client/src/test/java/io/split/client/SplitClientImplTest.java index c35ecb988..3956f7f5a 100644 --- a/client/src/test/java/io/split/client/SplitClientImplTest.java +++ b/client/src/test/java/io/split/client/SplitClientImplTest.java @@ -11,6 +11,7 @@ import io.split.client.impressions.ImpressionsManager; import io.split.client.interceptors.FlagSetsFilter; import io.split.client.interceptors.FlagSetsFilterImpl; +import io.split.storages.RuleBasedSegmentCacheConsumer; import io.split.storages.SegmentCacheConsumer; import io.split.storages.SplitCacheConsumer; import io.split.engine.evaluator.EvaluatorImp; @@ -87,6 +88,7 @@ public void nullKeyResultsInControl() { SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); when(splitCacheConsumer.get(test)).thenReturn(parsedSplit); SplitClientImpl client = new SplitClientImpl( @@ -96,7 +98,7 @@ public void nullKeyResultsInControl() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); assertEquals(Treatments.CONTROL, client.getTreatment(null, "test1")); @@ -116,6 +118,7 @@ public void nullTestResultsInControl() { SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); when(splitCacheConsumer.get(test)).thenReturn(parsedSplit); SplitClientImpl client = new SplitClientImpl( @@ -125,7 +128,7 @@ public void nullTestResultsInControl() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); assertEquals(Treatments.CONTROL, client.getTreatment("adil@relateiq.com", null)); @@ -138,6 +141,7 @@ public void exceptionsResultInControl() { SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); when(splitCacheConsumer.get(anyString())).thenThrow(RuntimeException.class); SplitClientImpl client = new SplitClientImpl( @@ -147,7 +151,7 @@ public void exceptionsResultInControl() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); assertEquals(Treatments.CONTROL, client.getTreatment("adil@relateiq.com", "test1")); @@ -168,6 +172,7 @@ public void works() { SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); when(splitCacheConsumer.get(test)).thenReturn(parsedSplit); when(gates.isSDKReady()).thenReturn(true); @@ -178,7 +183,7 @@ public void works() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -206,6 +211,7 @@ public void worksNullConfig() { SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); when(splitCacheConsumer.get(test)).thenReturn(parsedSplit); SplitClientImpl client = new SplitClientImpl( @@ -215,7 +221,7 @@ public void worksNullConfig() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); String randomKey = RandomStringUtils.random(10); @@ -241,6 +247,7 @@ public void worksAndHasConfig() { SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); when(splitCacheConsumer.get(test)).thenReturn(parsedSplit); SplitClientImpl client = new SplitClientImpl( @@ -250,7 +257,7 @@ public void worksAndHasConfig() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -277,6 +284,7 @@ public void lastConditionIsAlwaysDefault() { SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); when(splitCacheConsumer.get(test)).thenReturn(parsedSplit); SplitClientImpl client = new SplitClientImpl( @@ -286,7 +294,7 @@ public void lastConditionIsAlwaysDefault() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -316,6 +324,7 @@ public void lastConditionIsAlwaysDefaultButWithTreatment() { SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); when(splitCacheConsumer.get(test)).thenReturn(parsedSplit); SplitClientImpl client = new SplitClientImpl( @@ -325,7 +334,7 @@ public void lastConditionIsAlwaysDefaultButWithTreatment() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -350,6 +359,7 @@ public void multipleConditionsWork() { SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); when(splitCacheConsumer.get(test)).thenReturn(parsedSplit); when(gates.isSDKReady()).thenReturn(false); @@ -360,7 +370,7 @@ public void multipleConditionsWork() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -384,6 +394,7 @@ public void killedTestAlwaysGoesToDefault() { SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); when(splitCacheConsumer.get(test)).thenReturn(parsedSplit); SplitClientImpl client = new SplitClientImpl( @@ -393,7 +404,7 @@ public void killedTestAlwaysGoesToDefault() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -423,6 +434,7 @@ public void killedTestAlwaysGoesToDefaultHasConfig() { SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); when(splitCacheConsumer.get(test)).thenReturn(parsedSplit); SplitClientImpl client = new SplitClientImpl( @@ -432,7 +444,7 @@ public void killedTestAlwaysGoesToDefaultHasConfig() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -459,6 +471,7 @@ public void dependencyMatcherOn() { SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); when(splitCacheConsumer.get(parent)).thenReturn(parentSplit); when(splitCacheConsumer.get(dependent)).thenReturn(dependentSplit); @@ -469,7 +482,7 @@ public void dependencyMatcherOn() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -493,6 +506,7 @@ public void dependencyMatcherOff() { SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); when(splitCacheConsumer.get(parent)).thenReturn(parentSplit); when(splitCacheConsumer.get(dependent)).thenReturn(dependentSplit); @@ -503,7 +517,7 @@ public void dependencyMatcherOff() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -522,6 +536,7 @@ public void dependencyMatcherControl() { SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); when(splitCacheConsumer.get(dependent)).thenReturn(dependentSplit); SplitClientImpl client = new SplitClientImpl( @@ -531,7 +546,7 @@ public void dependencyMatcherControl() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -551,6 +566,7 @@ public void attributesWork() { SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); when(splitCacheConsumer.get(test)).thenReturn(parsedSplit); SplitClientImpl client = new SplitClientImpl( @@ -560,12 +576,12 @@ public void attributesWork() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); assertEquals("on", client.getTreatment("adil@codigo.com", test)); - assertEquals("on", client.getTreatment("adil@codigo.com", test, null)); + assertEquals("on", client.getTreatment("adil@codigo.com", test, new HashMap<>())); assertEquals("on", client.getTreatment("adil@codigo.com", test, ImmutableMap.of())); assertEquals("on", client.getTreatment("pato@codigo.com", test, ImmutableMap.of("age", 10))); assertEquals("off", client.getTreatment("pato@codigo.com", test, ImmutableMap.of("age", 9))); @@ -585,6 +601,7 @@ public void attributesWork2() { SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); when(splitCacheConsumer.get(test)).thenReturn(parsedSplit); SplitClientImpl client = new SplitClientImpl( @@ -594,12 +611,12 @@ public void attributesWork2() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); assertEquals("off", client.getTreatment("adil@codigo.com", test)); - assertEquals("off", client.getTreatment("adil@codigo.com", test, null)); + assertEquals("off", client.getTreatment("adil@codigo.com", test, new HashMap<>())); assertEquals("off", client.getTreatment("adil@codigo.com", test, ImmutableMap.of())); assertEquals("off", client.getTreatment("pato@codigo.com", test, ImmutableMap.of("age", 10))); @@ -620,6 +637,7 @@ public void attributesGreaterThanNegativeNumber() { SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); when(splitCacheConsumer.get(test)).thenReturn(parsedSplit); SplitClientImpl client = new SplitClientImpl( @@ -629,12 +647,12 @@ public void attributesGreaterThanNegativeNumber() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); assertEquals("off", client.getTreatment("adil@codigo.com", test)); - assertEquals("off", client.getTreatment("adil@codigo.com", test, null)); + assertEquals("off", client.getTreatment("adil@codigo.com", test, new HashMap<>())); assertEquals("off", client.getTreatment("adil@codigo.com", test, ImmutableMap.of())); assertEquals("off", client.getTreatment("pato@codigo.com", test, ImmutableMap.of("age", 10))); assertEquals("on", client.getTreatment("pato@codigo.com", test, ImmutableMap.of("age", -20))); @@ -657,6 +675,7 @@ public void attributesForSets() { SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); when(splitCacheConsumer.get(test)).thenReturn(parsedSplit); SplitClientImpl client = new SplitClientImpl( @@ -666,12 +685,12 @@ public void attributesForSets() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer ,segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer ,segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); assertEquals("off", client.getTreatment("adil@codigo.com", test)); - assertEquals("off", client.getTreatment("adil@codigo.com", test, null)); + assertEquals("off", client.getTreatment("adil@codigo.com", test, new HashMap<>())); assertEquals("off", client.getTreatment("adil@codigo.com", test, ImmutableMap.of())); assertEquals("off", client.getTreatment("pato@codigo.com", test, ImmutableMap.of("products", Lists.newArrayList()))); @@ -699,6 +718,7 @@ public void labelsArePopulated() { SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); when(splitCacheConsumer.get(test)).thenReturn(parsedSplit); SDKReadinessGates gates = mock(SDKReadinessGates.class); @@ -710,7 +730,7 @@ public void labelsArePopulated() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -802,6 +822,7 @@ private void trafficAllocation(String key, int trafficAllocation, int trafficAll SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); when(splitCacheConsumer.get(test)).thenReturn(parsedSplit); ImpressionsManager impressionsManager = mock(ImpressionsManager.class); @@ -812,7 +833,7 @@ private void trafficAllocation(String key, int trafficAllocation, int trafficAll NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -853,6 +874,7 @@ public void notInTrafficAllocationDefaultConfig() { SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); when(splitCacheConsumer.get(test)).thenReturn(parsedSplit); ImpressionsManager impressionsManager = mock(ImpressionsManager.class); @@ -865,7 +887,7 @@ public void notInTrafficAllocationDefaultConfig() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -899,6 +921,7 @@ public void matchingBucketingKeysWork() { SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); when(splitCacheConsumer.get(test)).thenReturn(parsedSplit); SplitClientImpl client = new SplitClientImpl( @@ -908,7 +931,7 @@ public void matchingBucketingKeysWork() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -935,6 +958,7 @@ public void matchingBucketingKeysByFlagSetWork() { SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); HashMap> flagsBySets = new HashMap<>(); flagsBySets.put("set1", new HashSet<>(Arrays.asList(test))); when(splitCacheConsumer.getNamesByFlagSets(Arrays.asList("set1"))).thenReturn(flagsBySets); @@ -950,7 +974,7 @@ public void matchingBucketingKeysByFlagSetWork() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -975,6 +999,7 @@ public void matchingBucketingKeysByFlagSetsWork() { SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); HashMap> flagsBySets = new HashMap<>(); flagsBySets.put("set1", new HashSet<>(Arrays.asList(test))); when(splitCacheConsumer.getNamesByFlagSets(Arrays.asList("set1"))).thenReturn(flagsBySets); @@ -990,7 +1015,7 @@ public void matchingBucketingKeysByFlagSetsWork() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1017,6 +1042,7 @@ public void impressionMetadataIsPropagated() { SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); when(splitCacheConsumer.get(test)).thenReturn(parsedSplit); ImpressionsManager impressionsManager = mock(ImpressionsManager.class); @@ -1027,7 +1053,7 @@ public void impressionMetadataIsPropagated() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1060,6 +1086,7 @@ public void blockUntilReadyDoesNotTimeWhenSdkIsReady() throws TimeoutException, SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SDKReadinessGates ready = mock(SDKReadinessGates.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); when(ready.waitUntilInternalReady(100)).thenReturn(true); SplitClientImpl client = new SplitClientImpl( @@ -1069,7 +1096,7 @@ public void blockUntilReadyDoesNotTimeWhenSdkIsReady() throws TimeoutException, NoopEventsStorageImp.create(), config, ready, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1081,6 +1108,7 @@ public void blockUntilReadyTimesWhenSdkIsNotReady() throws TimeoutException, Int SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SDKReadinessGates ready = mock(SDKReadinessGates.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); when(ready.waitUntilInternalReady(100)).thenReturn(false); SplitClientImpl client = new SplitClientImpl( @@ -1090,7 +1118,7 @@ public void blockUntilReadyTimesWhenSdkIsNotReady() throws TimeoutException, Int NoopEventsStorageImp.create(), config, ready, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1102,6 +1130,7 @@ public void trackWithValidParameters() { SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); when(gates.isSDKReady()).thenReturn(false); SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), @@ -1110,7 +1139,7 @@ public void trackWithValidParameters() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1127,7 +1156,8 @@ public void trackWithInvalidEventTypeIds() { SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); - + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); + SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), splitCacheConsumer, @@ -1135,7 +1165,7 @@ public void trackWithInvalidEventTypeIds() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); Assert.assertFalse(client.track("validKey", "valid_traffic_type", "")); @@ -1151,7 +1181,8 @@ public void trackWithInvalidTrafficTypeNames() { SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); - + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); + SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), splitCacheConsumer, @@ -1159,7 +1190,7 @@ public void trackWithInvalidTrafficTypeNames() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1172,7 +1203,8 @@ public void trackWithInvalidKeys() { SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); - + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); + SplitClientImpl client = new SplitClientImpl( mock(SplitFactory.class), splitCacheConsumer, @@ -1180,7 +1212,7 @@ public void trackWithInvalidKeys() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1201,6 +1233,7 @@ public void getTreatmentWithInvalidKeys() { SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); when(splitCacheConsumer.get(test)).thenReturn(parsedSplit); @@ -1211,7 +1244,7 @@ public void getTreatmentWithInvalidKeys() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); Assert.assertNotEquals(Treatments.CONTROL, client.getTreatment("valid", "split")); @@ -1251,6 +1284,7 @@ public void trackWithProperties() { SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); EventsStorageProducer eventClientMock = Mockito.mock(EventsStorageProducer.class); Mockito.when(eventClientMock.track((Event) Mockito.any(), Mockito.anyInt())).thenReturn(true); @@ -1261,7 +1295,7 @@ public void trackWithProperties() { eventClientMock, config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1359,6 +1393,7 @@ public void clientCannotPerformActionsWhenDestroyed() throws InterruptedExceptio SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); when(splitCacheConsumer.get(test)).thenReturn(parsedSplit); SplitFactory mockFactory = new SplitFactory() { @@ -1384,7 +1419,7 @@ public void clientCannotPerformActionsWhenDestroyed() throws InterruptedExceptio NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1415,6 +1450,7 @@ public void worksAndHasConfigTryKetTreatmentWithKey() { SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); when(splitCacheConsumer.get(test)).thenReturn(parsedSplit); SplitClientImpl client = new SplitClientImpl( @@ -1424,7 +1460,7 @@ public void worksAndHasConfigTryKetTreatmentWithKey() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1459,6 +1495,7 @@ public void worksAndHasConfigByFlagSetTryKetTreatmentWithKey() { SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); HashMap> flagsBySets = new HashMap<>(); flagsBySets.put("set1", new HashSet<>(Arrays.asList(test))); when(splitCacheConsumer.getNamesByFlagSets(Arrays.asList("set1"))).thenReturn(flagsBySets); @@ -1474,7 +1511,7 @@ public void worksAndHasConfigByFlagSetTryKetTreatmentWithKey() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1506,6 +1543,7 @@ public void worksAndHasConfigByFlagSetsTryKetTreatmentWithKey() { SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); HashMap> flagsBySets = new HashMap<>(); flagsBySets.put("set1", new HashSet<>(Arrays.asList(test))); when(splitCacheConsumer.getNamesByFlagSets(Arrays.asList("set1"))).thenReturn(flagsBySets); @@ -1521,7 +1559,7 @@ public void worksAndHasConfigByFlagSetsTryKetTreatmentWithKey() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1548,6 +1586,7 @@ public void blockUntilReadyException() throws TimeoutException, InterruptedExcep SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); when(splitCacheConsumer.get(test)).thenReturn(parsedSplit); SplitClientConfig config = SplitClientConfig.builder().setBlockUntilReadyTimeout(-100).build(); @@ -1558,7 +1597,7 @@ public void blockUntilReadyException() throws TimeoutException, InterruptedExcep NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1578,6 +1617,7 @@ public void nullKeyResultsInControlGetTreatments() { SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); when(splitCacheConsumer.fetchMany(Collections.singletonList(test))).thenReturn(splits); SplitClientImpl client = new SplitClientImpl( @@ -1587,7 +1627,7 @@ public void nullKeyResultsInControlGetTreatments() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); assertEquals(Treatments.CONTROL, client.getTreatments(null, Collections.singletonList("test1")).get("test1")); @@ -1608,6 +1648,7 @@ public void nullSplitsResultsInEmptyGetTreatments() { SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); when(splitCacheConsumer.fetchMany(Collections.singletonList(test))).thenReturn(splits); SplitClientImpl client = new SplitClientImpl( @@ -1617,7 +1658,7 @@ public void nullSplitsResultsInEmptyGetTreatments() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); assertEquals(0, client.getTreatments("key", null).size()); @@ -1630,6 +1671,7 @@ public void exceptionsResultInControlGetTreatments() { SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); when(splitCacheConsumer.fetchMany(anyList())).thenThrow(RuntimeException.class); SplitClientImpl client = new SplitClientImpl( @@ -1639,7 +1681,7 @@ public void exceptionsResultInControlGetTreatments() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); Map result = client.getTreatments("adil@relateiq.com", Arrays.asList("test1", "test2")); @@ -1662,6 +1704,7 @@ public void getTreatmentsWorks() { SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); when(splitCacheConsumer.fetchMany(anyList())).thenReturn(splits); when(gates.isSDKReady()).thenReturn(true); @@ -1672,7 +1715,7 @@ public void getTreatmentsWorks() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); Map result = client.getTreatments("randomKey", Arrays.asList(test, "test2")); @@ -1693,6 +1736,7 @@ public void emptySplitsResultsInNullGetTreatments() { SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); when(splitCacheConsumer.fetchMany(Collections.singletonList(test))).thenReturn(splits); SplitClientImpl client = new SplitClientImpl( @@ -1702,7 +1746,7 @@ public void emptySplitsResultsInNullGetTreatments() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); Map result = client.getTreatments("key", new ArrayList<>()); @@ -1717,6 +1761,7 @@ public void exceptionsResultInControlTreatments() { SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); when(splitCacheConsumer.get(anyString())).thenThrow(RuntimeException.class); SplitClientImpl client = new SplitClientImpl( @@ -1726,7 +1771,7 @@ public void exceptionsResultInControlTreatments() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); Map result = client.getTreatments("adil@relateiq.com", Arrays.asList("test1")); @@ -1753,6 +1798,7 @@ public void worksTreatments() { SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); when(splitCacheConsumer.fetchMany(anyList())).thenReturn(parsedSplits); when(gates.isSDKReady()).thenReturn(true); @@ -1763,7 +1809,7 @@ public void worksTreatments() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); Map result = client.getTreatments("anyKey", Arrays.asList(test, test2)); @@ -1790,6 +1836,7 @@ public void worksOneControlTreatments() { SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); when(splitCacheConsumer.fetchMany(anyList())).thenReturn(parsedSplits); when(gates.isSDKReady()).thenReturn(true); @@ -1800,7 +1847,7 @@ public void worksOneControlTreatments() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1835,6 +1882,7 @@ public void treatmentsWorksAndHasConfig() { SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); when(splitCacheConsumer.fetchMany(anyList())).thenReturn(parsedSplits); SplitClientImpl client = new SplitClientImpl( @@ -1844,7 +1892,7 @@ public void treatmentsWorksAndHasConfig() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); Map attributes = new HashMap<>(); @@ -1870,6 +1918,7 @@ public void testTreatmentsByFlagSet() { SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); Map fetchManyResult = new HashMap<>(); fetchManyResult.put(test, parsedSplit); when(splitCacheConsumer.fetchMany(new ArrayList<>(Arrays.asList(test)))).thenReturn(fetchManyResult); @@ -1886,7 +1935,7 @@ public void testTreatmentsByFlagSet() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); @@ -1894,7 +1943,7 @@ public void testTreatmentsByFlagSet() { Map getTreatmentResult; for (int i = 0; i < numKeys; i++) { String randomKey = RandomStringUtils.random(10); - getTreatmentResult = client.getTreatmentsByFlagSet(randomKey, "set1", null); + getTreatmentResult = client.getTreatmentsByFlagSet(randomKey, "set1", new HashMap<>()); assertEquals("on", getTreatmentResult.get(test)); } verify(splitCacheConsumer, times(numKeys)).fetchMany(new ArrayList<>(Arrays.asList(test))); @@ -1914,6 +1963,7 @@ public void testTreatmentsByFlagSetInvalid() { SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); List sets = new ArrayList<>(); when(gates.isSDKReady()).thenReturn(true); @@ -1924,10 +1974,10 @@ public void testTreatmentsByFlagSetInvalid() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); - assertTrue(client.getTreatmentsByFlagSet(RandomStringUtils.random(10), "", null).isEmpty()); + assertTrue(client.getTreatmentsByFlagSet(RandomStringUtils.random(10), "", new HashMap<>()).isEmpty()); } @Test @@ -1946,6 +1996,7 @@ public void testTreatmentsByFlagSets() { SDKReadinessGates gates = mock(SDKReadinessGates.class); SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); Map fetchManyResult = new HashMap<>(); fetchManyResult.put(test, parsedSplit); @@ -1967,14 +2018,14 @@ public void testTreatmentsByFlagSets() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); int numKeys = 5; Map getTreatmentResult; for (int i = 0; i < numKeys; i++) { String randomKey = RandomStringUtils.random(10); - getTreatmentResult = client.getTreatmentsByFlagSets(randomKey, Arrays.asList("set1", "set3"), null); + getTreatmentResult = client.getTreatmentsByFlagSets(randomKey, Arrays.asList("set1", "set3"), new HashMap<>()); assertEquals("on", getTreatmentResult.get(test)); assertEquals("on", getTreatmentResult.get(test2)); } @@ -2003,6 +2054,7 @@ public void treatmentsWorksAndHasConfigFlagSet() { SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); when(splitCacheConsumer.fetchMany(anyList())).thenReturn(parsedSplits); List sets = new ArrayList<>(Arrays.asList("set1")); @@ -2019,7 +2071,7 @@ public void treatmentsWorksAndHasConfigFlagSet() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); Map attributes = new HashMap<>(); @@ -2053,6 +2105,7 @@ public void treatmentsWorksAndHasConfigFlagSets() { SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); when(splitCacheConsumer.fetchMany(anyList())).thenReturn(parsedSplits); List sets = new ArrayList<>(Arrays.asList("set1")); @@ -2069,7 +2122,7 @@ public void treatmentsWorksAndHasConfigFlagSets() { NoopEventsStorageImp.create(), config, gates, - new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, flagSetsFilter ); Map attributes = new HashMap<>(); @@ -2081,4 +2134,82 @@ public void treatmentsWorksAndHasConfigFlagSets() { verify(splitCacheConsumer, times(1)).fetchMany(anyList()); } + + @Test + public void impressionPropertiesTest() { + String test = "test1"; + + ParsedCondition age_equal_to_0_should_be_on = new ParsedCondition(ConditionType.ROLLOUT, + CombiningMatcher.of("age", new EqualToMatcher(-20, DataType.NUMBER)), + Lists.newArrayList(partition("on", 100)), + "foolabel" + ); + + List conditions = Lists.newArrayList(age_equal_to_0_should_be_on); + ParsedSplit parsedSplit = ParsedSplit.createParsedSplitForTests(test, 123, false, Treatments.OFF, conditions, null, 1, 1, new HashSet<>(Arrays.asList("set")), true); + Map parsedSplits = new HashMap<>(); + parsedSplits.put(test, parsedSplit); + + SplitCacheConsumer splitCacheConsumer = mock(SplitCacheConsumer.class); + SegmentCacheConsumer segmentCacheConsumer = mock(SegmentCacheConsumer.class); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = mock(RuleBasedSegmentCacheConsumer.class); + when(splitCacheConsumer.get(test)).thenReturn(parsedSplit); + when(splitCacheConsumer.fetchMany(Arrays.asList(test))).thenReturn(parsedSplits); + Map> splits = new HashMap<>(); + splits.put("set", new HashSet<>(Arrays.asList(test))); + when(splitCacheConsumer.getNamesByFlagSets(Arrays.asList("set"))).thenReturn(splits); + + SDKReadinessGates gates = mock(SDKReadinessGates.class); + ImpressionsManager impressionsManager = mock(ImpressionsManager.class); + SplitClientImpl client = new SplitClientImpl( + mock(SplitFactory.class), + splitCacheConsumer, + impressionsManager, + NoopEventsStorageImp.create(), + config, + gates, + new EvaluatorImp(splitCacheConsumer, segmentCacheConsumer, ruleBasedSegmentCacheConsumer), TELEMETRY_STORAGE, TELEMETRY_STORAGE, + new FlagSetsFilterImpl(new HashSet<>()) + ); + Map attributes = ImmutableMap.of("age", -20, "acv", "1000000"); + EvaluationOptions properties = new EvaluationOptions(new HashMap() + {{ + put("prop2", "val2"); + put("prop1", "val1"); + }}); + Map result = new HashMap<>(); + result.put(test, Treatments.ON); + List split_names = Arrays.asList(test); + + assertEquals("on", client.getTreatment("pato@codigo.com", test, attributes, properties)); + assertEquals("on", client.getTreatmentWithConfig("bilal1@codigo.com", test, attributes, properties).treatment()); + assertEquals("on", client.getTreatments("bilal2@codigo.com", Arrays.asList(test), attributes, properties).get(test)); + assertEquals("on", client.getTreatmentsWithConfig("bilal3@codigo.com", Arrays.asList(test), attributes, properties).get(test).treatment()); + assertEquals("on", client.getTreatmentsByFlagSet("bilal4@codigo.com", "set", attributes, properties).get(test)); + assertEquals("on", client.getTreatmentsByFlagSets("bilal5@codigo.com", Arrays.asList("set"), attributes, properties).get(test)); + assertEquals("on", client.getTreatmentsWithConfigByFlagSet("bilal6@codigo.com", "set", attributes, properties).get(test).treatment()); + assertEquals("on", client.getTreatmentsWithConfigByFlagSets("bilal7@codigo.com", Arrays.asList("set"), attributes, properties).get(test).treatment()); + assertEquals("on", client.getTreatment(new Key("bilal8@codigo.com", "bilal8@codigo.com"), test, attributes, properties)); + assertEquals("on", client.getTreatmentWithConfig(new Key("bilal9@codigo.com", "bilal9@codigo.com"), test, attributes, properties).treatment()); + assertEquals("on", client.getTreatments(new Key("bilal10@codigo.com", "bilal10@codigo.com"), Arrays.asList(test), attributes, properties).get(test)); + assertEquals("on", client.getTreatmentsWithConfig(new Key("bilal11@codigo.com", "bilal11@codigo.com"), Arrays.asList(test), attributes, properties).get(test).treatment()); + assertEquals("on", client.getTreatmentsByFlagSet(new Key("bilal12@codigo.com", "bilal12@codigo.com"), "set", attributes, properties).get(test)); + assertEquals("on", client.getTreatmentsByFlagSets(new Key("bilal13@codigo.com", "bilal13@codigo.com"), Arrays.asList("set"), attributes, properties).get(test)); + assertEquals("on", client.getTreatmentsWithConfigByFlagSet(new Key("bilal14@codigo.com", "bilal14@codigo.com"), "set", attributes, properties).get(test).treatment()); + assertEquals("on", client.getTreatmentsWithConfigByFlagSets(new Key("bilal15@codigo.com", "bilal15@codigo.com"), Arrays.asList("set"), attributes, properties).get(test).treatment()); + + ArgumentCaptor impressionCaptor = ArgumentCaptor.forClass(List.class); + verify(impressionsManager, times(16)).track(impressionCaptor.capture()); + assertNotNull(impressionCaptor.getValue()); + + DecoratedImpression impression = (DecoratedImpression) impressionCaptor.getAllValues().get(0).get(0); + assertEquals("pato@codigo.com", impression.impression().key()); + assertEquals("{\"prop2\":\"val2\",\"prop1\":\"val1\"}", impression.impression().properties()); + + for (int i=1; i<=15; i++) { + impression = (DecoratedImpression) impressionCaptor.getAllValues().get(i).get(0); + assertEquals("bilal" + i + "@codigo.com", impression.impression().key()); + assertEquals("{\"prop2\":\"val2\",\"prop1\":\"val1\"}", impression.impression().properties()); + } + } } \ No newline at end of file diff --git a/client/src/test/java/io/split/client/SplitClientIntegrationTest.java b/client/src/test/java/io/split/client/SplitClientIntegrationTest.java index 01636d68d..28d573e24 100644 --- a/client/src/test/java/io/split/client/SplitClientIntegrationTest.java +++ b/client/src/test/java/io/split/client/SplitClientIntegrationTest.java @@ -1,8 +1,10 @@ package io.split.client; import io.split.SSEMockServer; +import io.split.Spec; import io.split.SplitMockServer; import io.split.client.api.SplitView; +import io.split.client.dtos.EvaluationOptions; import io.split.client.impressions.ImpressionsManager; import io.split.client.utils.CustomDispatcher; import io.split.storages.enums.OperationMode; @@ -45,6 +47,7 @@ public class SplitClientIntegrationTest { @Test public void getTreatmentWithStreamingEnabled() throws Exception { + Spec.SPEC_VERSION = Spec.SPEC_1_1; // check old spec MockResponse response = new MockResponse().setBody("{\"splits\": [], \"since\":1585948850109, \"till\":1585948850109}"); MockResponse response2 = new MockResponse().setBody("{\"splits\": [], \"since\":1585948850110, \"till\":1585948850110}"); MockResponse response3 = new MockResponse().setBody("{\"splits\": [], \"since\":1585948850111, \"till\":1585948850111}"); @@ -145,6 +148,7 @@ public void getTreatmentWithStreamingEnabled() throws Exception { @Test public void getTreatmentWithStreamingEnabledAndAuthDisabled() throws Exception { + Spec.SPEC_VERSION = Spec.SPEC_1_1; // check old spec MockResponse response = new MockResponse().setBody("{\"splits\": [], \"since\":1585948850109, \"till\":1585948850109}"); Queue responses = new LinkedList<>(); responses.add(response); @@ -173,6 +177,7 @@ public void getTreatmentWithStreamingEnabledAndAuthDisabled() throws Exception { @Test public void getTreatmentWithStreamingDisabled() throws Exception { + Spec.SPEC_VERSION = Spec.SPEC_1_1; // check old spec MockResponse response = new MockResponse().setBody("{\"splits\": [], \"since\":1585948850109, \"till\":1585948850109}"); Queue responses = new LinkedList<>(); responses.add(response); @@ -206,6 +211,7 @@ public void getTreatmentWithStreamingDisabled() throws Exception { @Test public void managerSplitsWithStreamingEnabled() throws Exception { + Spec.SPEC_VERSION = Spec.SPEC_1_1; // check old spec MockResponse response = new MockResponse().setBody("{\"splits\": [], \"since\":1585948850109, \"till\":1585948850109}"); Queue responses = new LinkedList<>(); responses.add(response); @@ -246,6 +252,7 @@ public void managerSplitsWithStreamingEnabled() throws Exception { @Test public void splitClientOccupancyNotifications() throws Exception { + Spec.SPEC_VERSION = Spec.SPEC_1_1; // check old spec MockResponse response = new MockResponse().setBody("{\"splits\": [], \"since\":1585948850109, \"till\":1585948850109}"); MockResponse response2 = new MockResponse().setBody("{\"splits\": [], \"since\":1585948850110, \"till\":1585948850110}"); MockResponse response3 = new MockResponse().setBody("{\"splits\": [], \"since\":1585948850111, \"till\":1585948850111}"); @@ -321,6 +328,7 @@ public void splitClientOccupancyNotifications() throws Exception { @Test public void splitClientControlNotifications() throws Exception { + Spec.SPEC_VERSION = Spec.SPEC_1_1; // check old spec MockResponse response = new MockResponse().setBody("{\"splits\": [], \"since\":1585948850109, \"till\":1585948850109}"); MockResponse response2 = new MockResponse().setBody("{\"splits\": [], \"since\":1585948850110, \"till\":1585948850110}"); MockResponse response3 = new MockResponse().setBody("{\"splits\": [], \"since\":1585948850111, \"till\":1585948850111}"); @@ -416,6 +424,7 @@ public void splitClientControlNotifications() throws Exception { @Test public void splitClientMultiFactory() throws Exception { + Spec.SPEC_VERSION = Spec.SPEC_1_1; // check old spec MockResponse response = new MockResponse().setBody("{\"splits\": [], \"since\":1585948850109, \"till\":1585948850109}"); Queue responses = new LinkedList<>(); responses.add(response); @@ -565,6 +574,7 @@ public void splitClientMultiFactory() throws Exception { @Test public void keepAlive() throws Exception { + Spec.SPEC_VERSION = Spec.SPEC_1_1; // check old spec MockResponse response = new MockResponse().setBody("{\"splits\": [], \"since\":1585948850109, \"till\":1585948850109}"); Queue responses = new LinkedList<>(); responses.add(response); @@ -603,6 +613,7 @@ public void keepAlive() throws Exception { @Test public void testConnectionClosedByRemoteHostIsProperlyHandled() throws Exception { + Spec.SPEC_VERSION = Spec.SPEC_1_1; // check old spec MockResponse response = new MockResponse().setBody("{\"splits\": [], \"since\":1585948850109, \"till\":1585948850109}"); Queue responses = new LinkedList<>(); responses.add(response); @@ -641,6 +652,7 @@ public void testConnectionClosedByRemoteHostIsProperlyHandled() throws Exception @Test public void testConnectionClosedIsProperlyHandled() throws Exception { + Spec.SPEC_VERSION = Spec.SPEC_1_1; // check old spec MockResponse response = new MockResponse().setBody("{\"splits\": [], \"since\":1585948850109, \"till\":1585948850109}"); Queue responses = new LinkedList<>(); responses.add(response); @@ -677,6 +689,7 @@ public void testConnectionClosedIsProperlyHandled() throws Exception { @Test public void testPluggableMode() throws IOException, URISyntaxException { + Spec.SPEC_VERSION = Spec.SPEC_1_1; // check old spec CustomStorageWrapperImp customStorageWrapper = new CustomStorageWrapperImp(); SplitClientConfig config = SplitClientConfig.builder() .enableDebug() @@ -742,6 +755,7 @@ public void testPluggableMode() throws IOException, URISyntaxException { @Test public void getTreatmentFlagSetWithPolling() throws Exception { + Spec.SPEC_VERSION = Spec.SPEC_1_1; // check old spec MockResponse response = new MockResponse().setBody("{\"splits\":[{\"trafficTypeName\":\"client\",\"name\":\"workm\",\"trafficAllocation\":100,\"trafficAllocationSeed\":147392224,\"seed\":524417105,\"status\":\"ACTIVE\",\"killed\":false,\"defaultTreatment\":\"on\",\"changeNumber\":1602796638344,\"algo\":2,\"configurations\":{},\"sets\":[\"set1\",\"set2\"],\"conditions\":[{\"conditionType\":\"ROLLOUT\",\"matcherGroup\":{\"combiner\":\"AND\",\"matchers\":[{\"keySelector\":{\"trafficType\":\"client\",\"attribute\":null},\"matcherType\":\"IN_SEGMENT\",\"negate\":false,\"userDefinedSegmentMatcherData\":{\"segmentName\":\"new_segment\"},\"whitelistMatcherData\":null,\"unaryNumericMatcherData\":null,\"betweenMatcherData\":null,\"booleanMatcherData\":null,\"dependencyMatcherData\":null,\"stringMatcherData\":null}]},\"partitions\":[{\"treatment\":\"on\",\"size\":0},{\"treatment\":\"off\",\"size\":0},{\"treatment\":\"free\",\"size\":100},{\"treatment\":\"conta\",\"size\":0}],\"label\":\"in segment new_segment\"},{\"conditionType\":\"ROLLOUT\",\"matcherGroup\":{\"combiner\":\"AND\",\"matchers\":[{\"keySelector\":{\"trafficType\":\"client\",\"attribute\":null},\"matcherType\":\"ALL_KEYS\",\"negate\":false,\"userDefinedSegmentMatcherData\":null,\"whitelistMatcherData\":null,\"unaryNumericMatcherData\":null,\"betweenMatcherData\":null,\"booleanMatcherData\":null,\"dependencyMatcherData\":null,\"stringMatcherData\":null}]},\"partitions\":[{\"treatment\":\"on\",\"size\":100},{\"treatment\":\"off\",\"size\":0},{\"treatment\":\"free\",\"size\":0},{\"treatment\":\"conta\",\"size\":0}],\"label\":\"default rule\"}]},{\"trafficTypeName\":\"client\",\"name\":\"workm_set_3\",\"trafficAllocation\":100,\"trafficAllocationSeed\":147392224,\"seed\":524417105,\"status\":\"ACTIVE\",\"killed\":false,\"defaultTreatment\":\"on\",\"changeNumber\":1602796638344,\"algo\":2,\"configurations\":{},\"sets\":[\"set3\"],\"conditions\":[{\"conditionType\":\"ROLLOUT\",\"matcherGroup\":{\"combiner\":\"AND\",\"matchers\":[{\"keySelector\":{\"trafficType\":\"client\",\"attribute\":null},\"matcherType\":\"IN_SEGMENT\",\"negate\":false,\"userDefinedSegmentMatcherData\":{\"segmentName\":\"new_segment\"},\"whitelistMatcherData\":null,\"unaryNumericMatcherData\":null,\"betweenMatcherData\":null,\"booleanMatcherData\":null,\"dependencyMatcherData\":null,\"stringMatcherData\":null}]},\"partitions\":[{\"treatment\":\"on\",\"size\":0},{\"treatment\":\"off\",\"size\":0},{\"treatment\":\"free\",\"size\":100},{\"treatment\":\"conta\",\"size\":0}],\"label\":\"in segment new_segment\"},{\"conditionType\":\"ROLLOUT\",\"matcherGroup\":{\"combiner\":\"AND\",\"matchers\":[{\"keySelector\":{\"trafficType\":\"client\",\"attribute\":null},\"matcherType\":\"ALL_KEYS\",\"negate\":false,\"userDefinedSegmentMatcherData\":null,\"whitelistMatcherData\":null,\"unaryNumericMatcherData\":null,\"betweenMatcherData\":null,\"booleanMatcherData\":null,\"dependencyMatcherData\":null,\"stringMatcherData\":null}]},\"partitions\":[{\"treatment\":\"on\",\"size\":100},{\"treatment\":\"off\",\"size\":0},{\"treatment\":\"free\",\"size\":0},{\"treatment\":\"conta\",\"size\":0}],\"label\":\"default rule\"}]}],\"since\":-1,\"till\":1602796638344}"); MockResponse responseFlag = new MockResponse().setBody("{\"splits\": [], \"since\":1602796638344, \"till\":1602796638344}"); MockResponse segmentResponse = new MockResponse().setBody("{\"name\":\"new_segment\",\"added\":[\"user-1\"],\"removed\":[\"user-2\",\"user-3\"],\"since\":-1,\"till\":-1}"); @@ -773,7 +787,7 @@ public void getTreatmentFlagSetWithPolling() throws Exception { String result = client.getTreatment("admin", "workm"); Assert.assertEquals("on", result); - Assert.assertEquals("on", client.getTreatmentsByFlagSet("admin", "set1", null).get("workm")); + Assert.assertEquals("on", client.getTreatmentsByFlagSet("admin", "set1", new HashMap<>()).get("workm")); client.destroy(); splitServer.stop(); @@ -781,6 +795,7 @@ public void getTreatmentFlagSetWithPolling() throws Exception { @Test public void ImpressionToggleOptimizedModeTest() throws Exception { + Spec.SPEC_VERSION = Spec.SPEC_1_1; // check old spec String splits = new String(Files.readAllBytes(Paths.get("src/test/resources/splits_imp_toggle.json")), StandardCharsets.UTF_8); List allRequests = new ArrayList<>(); @@ -827,9 +842,9 @@ public MockResponse dispatch(RecordedRequest request) { SplitClient client = factory.client(); client.blockUntilReady(); - Assert.assertEquals("off", client.getTreatment("user1", "without_impression_toggle", null)); - Assert.assertEquals("off", client.getTreatment("user2", "impression_toggle_on", null)); - Assert.assertEquals("off", client.getTreatment("user3", "impression_toggle_off", null)); + Assert.assertEquals("off", client.getTreatment("user1", "without_impression_toggle", new HashMap<>())); + Assert.assertEquals("off", client.getTreatment("user2", "impression_toggle_on", new HashMap<>())); + Assert.assertEquals("off", client.getTreatment("user3", "impression_toggle_off", new HashMap<>())); client.destroy(); boolean check1 = false, check2 = false; for (int i=0; i < allRequests.size(); i++ ) { @@ -855,6 +870,7 @@ public MockResponse dispatch(RecordedRequest request) { @Test public void ImpressionToggleDebugModeTest() throws Exception { + Spec.SPEC_VERSION = Spec.SPEC_1_1; // check old spec String splits = new String(Files.readAllBytes(Paths.get("src/test/resources/splits_imp_toggle.json")), StandardCharsets.UTF_8); List allRequests = new ArrayList<>(); @@ -901,9 +917,9 @@ public MockResponse dispatch(RecordedRequest request) { SplitClient client = factory.client(); client.blockUntilReady(); - Assert.assertEquals("off", client.getTreatment("user1", "without_impression_toggle", null)); - Assert.assertEquals("off", client.getTreatment("user2", "impression_toggle_on", null)); - Assert.assertEquals("off", client.getTreatment("user3", "impression_toggle_off", null)); + Assert.assertEquals("off", client.getTreatment("user1", "without_impression_toggle", new HashMap<>())); + Assert.assertEquals("off", client.getTreatment("user2", "impression_toggle_on", new HashMap<>())); + Assert.assertEquals("off", client.getTreatment("user3", "impression_toggle_off", new HashMap<>())); client.destroy(); boolean check1 = false, check2 = false, check3 = false; for (int i=0; i < allRequests.size(); i++ ) { @@ -937,6 +953,7 @@ public MockResponse dispatch(RecordedRequest request) { @Test public void ImpressionToggleNoneModeTest() throws Exception { + Spec.SPEC_VERSION = Spec.SPEC_1_1; // check old spec String splits = new String(Files.readAllBytes(Paths.get("src/test/resources/splits_imp_toggle.json")), StandardCharsets.UTF_8); List allRequests = new ArrayList<>(); @@ -983,9 +1000,9 @@ public MockResponse dispatch(RecordedRequest request) { SplitClient client = factory.client(); client.blockUntilReady(); - Assert.assertEquals("off", client.getTreatment("user1", "without_impression_toggle", null)); - Assert.assertEquals("off", client.getTreatment("user2", "impression_toggle_on", null)); - Assert.assertEquals("off", client.getTreatment("user3", "impression_toggle_off", null)); + Assert.assertEquals("off", client.getTreatment("user1", "without_impression_toggle", new HashMap<>())); + Assert.assertEquals("off", client.getTreatment("user2", "impression_toggle_on", new HashMap<>())); + Assert.assertEquals("off", client.getTreatment("user3", "impression_toggle_off", new HashMap<>())); client.destroy(); boolean check1 = false, check2 = false, check3 = false; for (int i=0; i < allRequests.size(); i++ ) { @@ -1013,6 +1030,89 @@ public MockResponse dispatch(RecordedRequest request) { Assert.assertTrue(check3); } + @Test + public void ImpressionPropertiesTest() throws Exception { + Spec.SPEC_VERSION = Spec.SPEC_1_1; // check old spec + String splits = new String(Files.readAllBytes(Paths.get("src/test/resources/splits_imp_toggle.json")), StandardCharsets.UTF_8); + List allRequests = new ArrayList<>(); + + Dispatcher dispatcher = new Dispatcher() { + @Override + public MockResponse dispatch(RecordedRequest request) { + allRequests.add(request); + switch (request.getPath()) { + case "/api/splitChanges?s=1.1&since=-1": + return new MockResponse().setResponseCode(200).setBody(splits); + case "/api/splitChanges?s=1.1&since=1602796638344": + return new MockResponse().setResponseCode(200).setBody("{\"splits\": [], \"since\":1602796638344, \"till\":1602796638344}"); + case "/api/testImpressions/bulk": + return new MockResponse().setResponseCode(200); + case "/api/testImpressions/count": + return new MockResponse().setResponseCode(200); + case "/v1/keys/ss": + return new MockResponse().setResponseCode(200); + case "/v1/metrics/usage": + return new MockResponse().setResponseCode(200); + case "/v1/metrics/config": + return new MockResponse().setResponseCode(200); + } + return new MockResponse().setResponseCode(404); + } + }; + + MockWebServer server = new MockWebServer(); + server.setDispatcher(dispatcher); + + server.start(); + String serverURL = String.format("http://%s:%s", server.getHostName(), server.getPort()); + SplitClientConfig config = SplitClientConfig.builder() + .setBlockUntilReadyTimeout(10000) + .endpoint(serverURL, serverURL) + .telemetryURL(serverURL + "/v1") + .authServiceURL(String.format("%s/api/auth/enabled", serverURL)) + .streamingEnabled(false) + .featuresRefreshRate(5) + .impressionsMode(ImpressionsManager.Mode.DEBUG) + .build(); + + SplitFactory factory = SplitFactoryBuilder.build("fake-api-token", config); + SplitClient client = factory.client(); + client.blockUntilReady(); + + Assert.assertEquals("off", client.getTreatment("user1", "without_impression_toggle", new HashMap<>(), new EvaluationOptions(new HashMap() {{ put("prop1", "val1"); }}))); + Assert.assertEquals("off", client.getTreatment("user2", "impression_toggle_on", new HashMap<>(), new EvaluationOptions(new HashMap() + {{ + put("prop1", "val1"); + put("prop2", "val2"); + }}))); + Assert.assertEquals("off", client.getTreatment("user3", "impression_toggle_on", new EvaluationOptions(null))); + client.destroy(); + boolean check1 = false, check2 = false, check3 = false; + for (int i=0; i < allRequests.size(); i++ ) { + if (allRequests.get(i).getPath().equals("/api/testImpressions/bulk") ) { + String body = allRequests.get(i).getBody().readUtf8(); + if (body.contains("user1")) { + check1 = true; + Assert.assertTrue(body.contains("without_impression_toggle")); + Assert.assertTrue(body.contains("\"properties\":\"{\\\"prop1\\\":\\\"val1\\\"}\"")); + } + if (body.contains("user2")) { + check2 = true; + Assert.assertTrue(body.contains("impression_toggle_on")); + Assert.assertTrue(body.contains("\"properties\":\"{\\\"prop2\\\":\\\"val2\\\",\\\"prop1\\\":\\\"val1\\\"}\"")); + } + if (body.contains("user3")) { + check3 = true; + Assert.assertTrue(body.contains("impression_toggle_on")); + Assert.assertTrue(body.contains("\"properties\":null")); + } + } + } + server.shutdown(); + Assert.assertTrue(check1); + Assert.assertTrue(check2); + } + private SSEMockServer buildSSEMockServer(SSEMockServer.SseEventQueue eventQueue) { return new SSEMockServer(eventQueue, (token, version, channel) -> { if (!"1.1".equals(version)) { diff --git a/client/src/test/java/io/split/client/YamlLocalhostSplitChangeFetcherTest.java b/client/src/test/java/io/split/client/YamlLocalhostSplitChangeFetcherTest.java index dabd96781..a30943c12 100644 --- a/client/src/test/java/io/split/client/YamlLocalhostSplitChangeFetcherTest.java +++ b/client/src/test/java/io/split/client/YamlLocalhostSplitChangeFetcherTest.java @@ -63,7 +63,7 @@ public void testParseSplitChange() throws IOException { InputStreamProvider inputStreamProvider = new FileInputStreamProvider(file.getAbsolutePath()); YamlLocalhostSplitChangeFetcher localhostSplitChangeFetcher = new YamlLocalhostSplitChangeFetcher(inputStreamProvider); FetchOptions fetchOptions = Mockito.mock(FetchOptions.class); - SplitChange splitChange = localhostSplitChangeFetcher.fetch(-1L, fetchOptions); + SplitChange splitChange = localhostSplitChangeFetcher.fetch(-1L, -1, fetchOptions); Assert.assertEquals(2, splitChange.splits.size()); Assert.assertEquals(-1, splitChange.since); @@ -81,6 +81,6 @@ public void processTestForException() { YamlLocalhostSplitChangeFetcher localhostSplitChangeFetcher = new YamlLocalhostSplitChangeFetcher(inputStreamProvider); FetchOptions fetchOptions = Mockito.mock(FetchOptions.class); - SplitChange splitChange = localhostSplitChangeFetcher.fetch(-1L, fetchOptions); + SplitChange splitChange = localhostSplitChangeFetcher.fetch(-1L, -1, fetchOptions); } } \ No newline at end of file diff --git a/client/src/test/java/io/split/client/dtos/KeyImpressionTest.java b/client/src/test/java/io/split/client/dtos/KeyImpressionTest.java index c4ac5d12d..11ff40b33 100644 --- a/client/src/test/java/io/split/client/dtos/KeyImpressionTest.java +++ b/client/src/test/java/io/split/client/dtos/KeyImpressionTest.java @@ -25,6 +25,7 @@ public void TestShrinkedPropertyNames() { imp.changeNumber = 123L; imp.time = 456L; imp.previousTime = 789L; + imp.properties = "{\"name\": \"value\"}"; String serialized = gson.toJson(imp); Map deSerialized = gson.fromJson(serialized, new TypeToken>() { }.getType()); @@ -65,5 +66,11 @@ public void TestShrinkedPropertyNames() { assertThat(previousTime, is(notNullValue())); assertThat(previousTime, instanceOf(Double.class)); assertThat(previousTime, is(789.0)); + + Object properties = deSerialized.get(KeyImpression.FIELD_PROPERTIES); + assertThat(properties, is(notNullValue())); + assertThat(properties, instanceOf(String.class)); + assertThat(properties, is("{\"name\": \"value\"}")); + } } diff --git a/client/src/test/java/io/split/client/impressions/HttpImpressionsSenderTest.java b/client/src/test/java/io/split/client/impressions/HttpImpressionsSenderTest.java index 604f7a900..00471171e 100644 --- a/client/src/test/java/io/split/client/impressions/HttpImpressionsSenderTest.java +++ b/client/src/test/java/io/split/client/impressions/HttpImpressionsSenderTest.java @@ -140,13 +140,13 @@ public void testImpressionBulksEndpoint() throws URISyntaxException, IOException // Send impressions List toSend = Arrays.asList(new TestImpressions("t1", Arrays.asList( - KeyImpression.fromImpression(new Impression("k1", null, "t1", "on", 123L, "r1", 456L, null)), - KeyImpression.fromImpression(new Impression("k2", null, "t1", "on", 123L, "r1", 456L, null)), - KeyImpression.fromImpression(new Impression("k3", null, "t1", "on", 123L, "r1", 456L, null)))), + KeyImpression.fromImpression(new Impression("k1", null, "t1", "on", 123L, "r1", 456L, null, null)), + KeyImpression.fromImpression(new Impression("k2", null, "t1", "on", 123L, "r1", 456L, null, null)), + KeyImpression.fromImpression(new Impression("k3", null, "t1", "on", 123L, "r1", 456L, null, null)))), new TestImpressions("t2", Arrays.asList( - KeyImpression.fromImpression(new Impression("k1", null, "t2", "on", 123L, "r1", 456L, null)), - KeyImpression.fromImpression(new Impression("k2", null, "t2", "on", 123L, "r1", 456L, null)), - KeyImpression.fromImpression(new Impression("k3", null, "t2", "on", 123L, "r1", 456L, null))))); + KeyImpression.fromImpression(new Impression("k1", null, "t2", "on", 123L, "r1", 456L, null, null)), + KeyImpression.fromImpression(new Impression("k2", null, "t2", "on", 123L, "r1", 456L, null, null)), + KeyImpression.fromImpression(new Impression("k3", null, "t2", "on", 123L, "r1", 456L, null, null))))); sender.postImpressionsBulk(toSend); // Capture outgoing request and validate it diff --git a/client/src/test/java/io/split/client/impressions/ImpressionHasherTest.java b/client/src/test/java/io/split/client/impressions/ImpressionHasherTest.java index 86214d777..031c1aa8c 100644 --- a/client/src/test/java/io/split/client/impressions/ImpressionHasherTest.java +++ b/client/src/test/java/io/split/client/impressions/ImpressionHasherTest.java @@ -18,7 +18,7 @@ public void works() { System.currentTimeMillis(), "someLabel", 123L, - null); + null, null); // Different feature Impression imp2 = new Impression("someKey", @@ -28,7 +28,7 @@ public void works() { System.currentTimeMillis(), "someLabel", 123L, - null); + null, null); assertThat(ImpressionHasher.process(imp1), not(equalTo(ImpressionHasher.process(imp2)))); @@ -40,7 +40,7 @@ public void works() { System.currentTimeMillis(), "someLabel", 123L, - null); + null, null); assertThat(ImpressionHasher.process(imp1), not(equalTo(ImpressionHasher.process(imp2)))); // different changeNumber @@ -51,7 +51,7 @@ public void works() { System.currentTimeMillis(), "someLabel", 456L, - null); + null, null); assertThat(ImpressionHasher.process(imp1), not(equalTo(ImpressionHasher.process(imp2)))); // different label @@ -62,7 +62,7 @@ public void works() { System.currentTimeMillis(), "someOtherLabel", 123L, - null); + null, null); assertThat(ImpressionHasher.process(imp1), not(equalTo(ImpressionHasher.process(imp2)))); // different treatment @@ -73,7 +73,7 @@ public void works() { System.currentTimeMillis(), "someLabel", 123L, - null); + null, null); assertThat(ImpressionHasher.process(imp1), not(equalTo(ImpressionHasher.process(imp2)))); } @@ -87,7 +87,7 @@ public void doesNotCrash() { System.currentTimeMillis(), "someLabel", 123L, - null); + null, null); assertNotNull(ImpressionHasher.process(imp1)); imp1 = new Impression(null, @@ -97,7 +97,7 @@ public void doesNotCrash() { System.currentTimeMillis(), "someLabel", 123L, - null); + null, null); assertNotNull(ImpressionHasher.process(imp1)); imp1 = new Impression(null, @@ -107,7 +107,7 @@ public void doesNotCrash() { System.currentTimeMillis(), "someLabel", null, - null); + null, null); assertNotNull(ImpressionHasher.process(imp1)); imp1 = new Impression(null, @@ -117,7 +117,7 @@ public void doesNotCrash() { System.currentTimeMillis(), null, null, - null); + null, null); assertNotNull(ImpressionHasher.process(imp1)); imp1 = new Impression(null, @@ -127,7 +127,7 @@ public void doesNotCrash() { System.currentTimeMillis(), "someLabel", null, - null); + null, null); assertNotNull(ImpressionHasher.process(imp1)); assertNull(ImpressionHasher.process(null)); } diff --git a/client/src/test/java/io/split/client/impressions/ImpressionObserverTest.java b/client/src/test/java/io/split/client/impressions/ImpressionObserverTest.java index e04f26a88..57fc258aa 100644 --- a/client/src/test/java/io/split/client/impressions/ImpressionObserverTest.java +++ b/client/src/test/java/io/split/client/impressions/ImpressionObserverTest.java @@ -40,6 +40,7 @@ private List generateImpressions(long count) { System.currentTimeMillis(), (i % 2 == 0) ? "in segment all" : "whitelisted", i * i, + null, null); imps.add(imp); } @@ -54,6 +55,7 @@ public void testBasicFunctionality() { "on", System.currentTimeMillis(), "in segment all", 1234L, + null, null); // Add 5 new impressions so that the old one is evicted and re-try the test. @@ -108,6 +110,7 @@ private void caller(ImpressionObserver o, int count, ConcurrentLinkedQueue impressionList = new ArrayList<>(); - impressionList.add(new DecoratedImpression(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, ki1.changeNumber, null), false)); - impressionList.add(new DecoratedImpression(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, ki2.changeNumber, null), false)); - impressionList.add(new DecoratedImpression(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, ki3.changeNumber, null), false)); - impressionList.add(new DecoratedImpression(new Impression(ki4.keyName, null, ki4.feature, ki4.treatment, ki4.time, null, ki4.changeNumber, null), false)); + impressionList.add(new DecoratedImpression(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, ki1.changeNumber, null, null), false)); + impressionList.add(new DecoratedImpression(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, ki2.changeNumber, null, null), false)); + impressionList.add(new DecoratedImpression(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, ki3.changeNumber, null, null), false)); + impressionList.add(new DecoratedImpression(new Impression(ki4.keyName, null, ki4.feature, ki4.treatment, ki4.time, null, ki4.changeNumber, null, null), false)); treatmentLog.track(impressionList); verify(impressionListener, times(4)).log(Mockito.anyObject()); @@ -165,16 +165,16 @@ public void testImpressionListenerDebug() { ImpressionsManagerImpl treatmentLog = ImpressionsManagerImpl.instanceForTest(config, senderMock, TELEMETRY_STORAGE, storage, storage, processImpressionNone, processImpressionStrategy, impressionCounter, impressionListener); treatmentLog.start(); - KeyImpression ki1 = keyImpression("test1", "adil", "on", 1L, 1L); - KeyImpression ki2 = keyImpression("test1", "adil", "on", 1L, 1L); - KeyImpression ki3 = keyImpression("test1", "pato", "on", 3L, 2L); - KeyImpression ki4 = keyImpression("test2", "pato", "on", 4L, 3L); + KeyImpression ki1 = keyImpression("test1", "adil", "on", 1L, 1L, null); + KeyImpression ki2 = keyImpression("test1", "adil", "on", 1L, 1L, null); + KeyImpression ki3 = keyImpression("test1", "pato", "on", 3L, 2L, null); + KeyImpression ki4 = keyImpression("test2", "pato", "on", 4L, 3L, null); List impressionList = new ArrayList<>(); - impressionList.add(new DecoratedImpression(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, ki1.changeNumber, null), false)); - impressionList.add(new DecoratedImpression(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, ki2.changeNumber, null), false)); - impressionList.add(new DecoratedImpression(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, ki3.changeNumber, null), false)); - impressionList.add(new DecoratedImpression(new Impression(ki4.keyName, null, ki4.feature, ki4.treatment, ki4.time, null, ki4.changeNumber, null), false)); + impressionList.add(new DecoratedImpression(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, ki1.changeNumber, null, null), false)); + impressionList.add(new DecoratedImpression(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, ki2.changeNumber, null, null), false)); + impressionList.add(new DecoratedImpression(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, ki3.changeNumber, null, null), false)); + impressionList.add(new DecoratedImpression(new Impression(ki4.keyName, null, ki4.feature, ki4.treatment, ki4.time, null, ki4.changeNumber, null, null), false)); treatmentLog.track(impressionList); verify(impressionListener, times(4)).log(Mockito.anyObject()); @@ -209,16 +209,16 @@ public void testImpressionListenerNone() { ImpressionsManagerImpl treatmentLog = ImpressionsManagerImpl.instanceForTest(config, senderMock, TELEMETRY_STORAGE, storage, storage, processImpressionNone, processImpressionStrategy, impressionCounter, impressionListener); treatmentLog.start(); - KeyImpression ki1 = keyImpression("test1", "adil", "on", 1L, 1L); - KeyImpression ki2 = keyImpression("test1", "adil", "on", 1L, 1L); - KeyImpression ki3 = keyImpression("test1", "pato", "on", 3L, 2L); - KeyImpression ki4 = keyImpression("test2", "pato", "on", 4L, 3L); + KeyImpression ki1 = keyImpression("test1", "adil", "on", 1L, 1L, null); + KeyImpression ki2 = keyImpression("test1", "adil", "on", 1L, 1L, null); + KeyImpression ki3 = keyImpression("test1", "pato", "on", 3L, 2L, null); + KeyImpression ki4 = keyImpression("test2", "pato", "on", 4L, 3L, null); List impressionList = new ArrayList<>(); - impressionList.add(new DecoratedImpression(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, ki1.changeNumber, null), false)); - impressionList.add(new DecoratedImpression(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, ki2.changeNumber, null), false)); - impressionList.add(new DecoratedImpression(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, ki3.changeNumber, null), false)); - impressionList.add(new DecoratedImpression(new Impression(ki4.keyName, null, ki4.feature, ki4.treatment, ki4.time, null, ki4.changeNumber, null), false)); + impressionList.add(new DecoratedImpression(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, ki1.changeNumber, null, null), false)); + impressionList.add(new DecoratedImpression(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, ki2.changeNumber, null, null), false)); + impressionList.add(new DecoratedImpression(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, ki3.changeNumber, null, null), false)); + impressionList.add(new DecoratedImpression(new Impression(ki4.keyName, null, ki4.feature, ki4.treatment, ki4.time, null, ki4.changeNumber, null, null), false)); treatmentLog.track(impressionList); verify(impressionListener, times(4)).log(Mockito.anyObject()); @@ -251,15 +251,15 @@ public void worksButDropsImpressions() { treatmentLog.start(); // These 4 unique test name will cause 4 entries but we are caping at the first 3. - KeyImpression ki1 = keyImpression("test1", "adil", "on", 1L, null); - KeyImpression ki2 = keyImpression("test2", "adil", "on", 2L, null); - KeyImpression ki3 = keyImpression("test3", "pato", "on", 3L, null); - KeyImpression ki4 = keyImpression("test4", "pato", "on", 4L, null); + KeyImpression ki1 = keyImpression("test1", "adil", "on", 1L, null, null); + KeyImpression ki2 = keyImpression("test2", "adil", "on", 2L, null, null); + KeyImpression ki3 = keyImpression("test3", "pato", "on", 3L, null, null); + KeyImpression ki4 = keyImpression("test4", "pato", "on", 4L, null, null); - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, null, null), false)).collect(Collectors.toList())); - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, null, null), false)).collect(Collectors.toList())); - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, null, null), false)).collect(Collectors.toList())); - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki4.keyName, null, ki4.feature, ki4.treatment, ki4.time, null, null, null), false)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, null, null, null), false)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, null, null, null), false)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, null, null, null), false)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki4.keyName, null, ki4.feature, ki4.treatment, ki4.time, null, null, null, null), false)).collect(Collectors.toList())); // Do what the scheduler would do. treatmentLog.sendImpressions(); @@ -293,15 +293,15 @@ public void works4ImpressionsInOneTest() { treatmentLog.start(); // These 4 unique test name will cause 4 entries but we are caping at the first 3. - KeyImpression ki1 = keyImpression("test1", "adil", "on", 1L, 1L); - KeyImpression ki2 = keyImpression("test1", "adil", "on", 2L, 1L); - KeyImpression ki3 = keyImpression("test1", "pato", "on", 3L, 1L); - KeyImpression ki4 = keyImpression("test1", "pato", "on", 4L, 1L); + KeyImpression ki1 = keyImpression("test1", "adil", "on", 1L, 1L, null); + KeyImpression ki2 = keyImpression("test1", "adil", "on", 2L, 1L, null); + KeyImpression ki3 = keyImpression("test1", "pato", "on", 3L, 1L, null); + KeyImpression ki4 = keyImpression("test1", "pato", "on", 4L, 1L, null); - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, 1L, null), false)).collect(Collectors.toList())); - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, 1L, null), false)).collect(Collectors.toList())); - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, 1L, null), false)).collect(Collectors.toList())); - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki4.keyName, null, ki4.feature, ki4.treatment, ki4.time, null, 1L, null), false)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, 1L, null, null), false)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, 1L, null, null), false)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, 1L, null, null), false)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki4.keyName, null, ki4.feature, ki4.treatment, ki4.time, null, 1L, null, null), false)).collect(Collectors.toList())); // Do what the scheduler would do. treatmentLog.sendImpressions(); @@ -362,15 +362,15 @@ public void alreadySeenImpressionsAreMarked() { treatmentLog.start(); // These 4 unique test name will cause 4 entries but we are caping at the first 3. - KeyImpression ki1 = keyImpression("test1", "adil", "on", 1L, 1L); - KeyImpression ki2 = keyImpression("test1", "adil2", "on", 2L, 1L); - KeyImpression ki3 = keyImpression("test1", "pato", "on", 3L, 1L); - KeyImpression ki4 = keyImpression("test1", "pato2", "on", 4L, 1L); - - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, 1L, null), false)).collect(Collectors.toList())); - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, 1L, null), false)).collect(Collectors.toList())); - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, 1L, null), false)).collect(Collectors.toList())); - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki4.keyName, null, ki4.feature, ki4.treatment, ki4.time, null, 1L, null), false)).collect(Collectors.toList())); + KeyImpression ki1 = keyImpression("test1", "adil", "on", 1L, 1L, null); + KeyImpression ki2 = keyImpression("test1", "adil2", "on", 2L, 1L, null); + KeyImpression ki3 = keyImpression("test1", "pato", "on", 3L, 1L, null); + KeyImpression ki4 = keyImpression("test1", "pato2", "on", 4L, 1L, null); + + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, 1L, null, null), false)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, 1L, null, null), false)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, 1L, null, null), false)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki4.keyName, null, ki4.feature, ki4.treatment, ki4.time, null, 1L, null, null), false)).collect(Collectors.toList())); treatmentLog.sendImpressions(); verify(senderMock).postImpressionsBulk(impressionsCaptor.capture()); @@ -384,10 +384,10 @@ public void alreadySeenImpressionsAreMarked() { // Do it again. Now they should all have a `seenAt` value Mockito.reset(senderMock); - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, 1L, null), false)).collect(Collectors.toList())); - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, 1L, null), false)).collect(Collectors.toList())); - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, 1L, null), false)).collect(Collectors.toList())); - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki4.keyName, null, ki4.feature, ki4.treatment, ki4.time, null, 1L, null), false)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, 1L, null, null), false)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, 1L, null, null), false)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, 1L, null, null), false)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki4.keyName, null, ki4.feature, ki4.treatment, ki4.time, null, 1L, null, null), false)).collect(Collectors.toList())); treatmentLog.sendImpressions(); verify(senderMock).postImpressionsBulk(impressionsCaptor.capture()); @@ -421,15 +421,15 @@ public void testImpressionsStandaloneModeOptimizedMode() { treatmentLog.start(); // These 4 unique test name will cause 4 entries but we are caping at the first 3. - KeyImpression ki1 = keyImpression("test1", "adil", "on", 1L, 1L); - KeyImpression ki2 = keyImpression("test1", "adil", "on", 2L, 1L); - KeyImpression ki3 = keyImpression("test1", "pato", "on", 3L, 1L); - KeyImpression ki4 = keyImpression("test1", "pato", "on", 4L, 1L); - - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, 1L, null), false)).collect(Collectors.toList())); - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, 1L, null), false)).collect(Collectors.toList())); - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, 1L, null), false)).collect(Collectors.toList())); - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki4.keyName, null, ki4.feature, ki4.treatment, ki4.time, null, 1L, null), false)).collect(Collectors.toList())); + KeyImpression ki1 = keyImpression("test1", "adil", "on", 1L, 1L, null); + KeyImpression ki2 = keyImpression("test1", "adil", "on", 2L, 1L, null); + KeyImpression ki3 = keyImpression("test1", "pato", "on", 3L, 1L, null); + KeyImpression ki4 = keyImpression("test1", "pato", "on", 4L, 1L, null); + + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, 1L, null, null), false)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, 1L, null, null), false)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, 1L, null, null), false)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki4.keyName, null, ki4.feature, ki4.treatment, ki4.time, null, 1L, null, null), false)).collect(Collectors.toList())); treatmentLog.sendImpressions(); verify(senderMock).postImpressionsBulk(impressionsCaptor.capture()); @@ -442,8 +442,8 @@ public void testImpressionsStandaloneModeOptimizedMode() { } } // Only the first 2 impressions make it to the server - Assert.assertTrue(captured.get(0).keyImpressions.contains(keyImpression("test1", "adil", "on", 1L, 1L))); - Assert.assertTrue(captured.get(0).keyImpressions.contains(keyImpression("test1", "pato", "on", 3L, 1L))); + Assert.assertTrue(captured.get(0).keyImpressions.contains(keyImpression("test1", "adil", "on", 1L, 1L, null))); + Assert.assertTrue(captured.get(0).keyImpressions.contains(keyImpression("test1", "pato", "on", 3L, 1L, null))); treatmentLog.sendImpressionCounters(); verify(senderMock).postCounters(impressionCountCaptor.capture()); @@ -476,15 +476,15 @@ public void testImpressionsStandaloneModeDebugMode() { treatmentLog.start(); // These 4 unique test name will cause 4 entries but we are caping at the first 3. - KeyImpression ki1 = keyImpression("test1", "adil", "on", 1L, 1L); - KeyImpression ki2 = keyImpression("test1", "adil", "on", 2L, 1L); - KeyImpression ki3 = keyImpression("test1", "pato", "on", 3L, 1L); - KeyImpression ki4 = keyImpression("test1", "pato", "on", 4L, 1L); - - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, 1L, null), false)).collect(Collectors.toList())); - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, 1L, null), false)).collect(Collectors.toList())); - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, 1L, null), false)).collect(Collectors.toList())); - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki4.keyName, null, ki4.feature, ki4.treatment, ki4.time, null, 1L, null), false)).collect(Collectors.toList())); + KeyImpression ki1 = keyImpression("test1", "adil", "on", 1L, 1L, null); + KeyImpression ki2 = keyImpression("test1", "adil", "on", 2L, 1L, null); + KeyImpression ki3 = keyImpression("test1", "pato", "on", 3L, 1L, null); + KeyImpression ki4 = keyImpression("test1", "pato", "on", 4L, 1L, null); + + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, 1L, null, null), false)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, 1L, null, null), false)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, 1L, null, null), false)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki4.keyName, null, ki4.feature, ki4.treatment, ki4.time, null, 1L, null, null), false)).collect(Collectors.toList())); treatmentLog.sendImpressions(); verify(senderMock).postImpressionsBulk(impressionsCaptor.capture()); @@ -502,8 +502,8 @@ public void testImpressionsStandaloneModeDebugMode() { Assert.assertEquals(Optional.of(3L), Optional.of(keyImpression4.previousTime)); } // Only the first 2 impressions make it to the server - Assert.assertTrue(captured.get(0).keyImpressions.contains(keyImpression("test1", "adil", "on", 1L, 1L))); - Assert.assertTrue(captured.get(0).keyImpressions.contains(keyImpression("test1", "pato", "on", 3L, 1L))); + Assert.assertTrue(captured.get(0).keyImpressions.contains(keyImpression("test1", "adil", "on", 1L, 1L, null))); + Assert.assertTrue(captured.get(0).keyImpressions.contains(keyImpression("test1", "pato", "on", 3L, 1L, null))); } @Test @@ -528,15 +528,15 @@ public void testImpressionsStandaloneModeNoneMode() { treatmentLog.start(); // These 4 unique test name will cause 4 entries but we are caping at the first 3. - KeyImpression ki1 = keyImpression("test1", "adil", "on", 1L, 1L); - KeyImpression ki2 = keyImpression("test1", "adil", "on", 2L, 1L); - KeyImpression ki3 = keyImpression("test1", "pato", "on", 3L, 1L); - KeyImpression ki4 = keyImpression("test1", "pato", "on", 4L, 1L); - - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, 1L, null), false)).collect(Collectors.toList())); - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, 1L, null), false)).collect(Collectors.toList())); - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, 1L, null), false)).collect(Collectors.toList())); - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki4.keyName, null, ki4.feature, ki4.treatment, ki4.time, null, 1L, null), false)).collect(Collectors.toList())); + KeyImpression ki1 = keyImpression("test1", "adil", "on", 1L, 1L, null); + KeyImpression ki2 = keyImpression("test1", "adil", "on", 2L, 1L, null); + KeyImpression ki3 = keyImpression("test1", "pato", "on", 3L, 1L, null); + KeyImpression ki4 = keyImpression("test1", "pato", "on", 4L, 1L, null); + + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, 1L, null, null), false)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, 1L, null, null), false)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, 1L, null, null), false)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki4.keyName, null, ki4.feature, ki4.treatment, ki4.time, null, 1L, null, null), false)).collect(Collectors.toList())); treatmentLog.close(); uniqueKeysTracker.stop(); @@ -585,15 +585,15 @@ public void testImpressionsConsumerModeOptimizedMode() { treatmentLog.start(); // These 4 unique test name will cause 4 entries but we are caping at the first 3. - KeyImpression ki1 = keyImpression("test1", "adil", "on", 1L, 1L); - KeyImpression ki2 = keyImpression("test1", "adil", "on", 2L, 1L); - KeyImpression ki3 = keyImpression("test1", "pato", "on", 3L, 1L); - KeyImpression ki4 = keyImpression("test1", "pato", "on", 4L, 1L); - - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, 1L, null), false)).collect(Collectors.toList())); - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, 1L, null), false)).collect(Collectors.toList())); - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, 1L, null), false)).collect(Collectors.toList())); - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki4.keyName, null, ki4.feature, ki4.treatment, ki4.time, null, 1L, null), false)).collect(Collectors.toList())); + KeyImpression ki1 = keyImpression("test1", "adil", "on", 1L, 1L, null); + KeyImpression ki2 = keyImpression("test1", "adil", "on", 2L, 1L, null); + KeyImpression ki3 = keyImpression("test1", "pato", "on", 3L, 1L, null); + KeyImpression ki4 = keyImpression("test1", "pato", "on", 4L, 1L, null); + + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, 1L, null, null), false)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, 1L, null, null), false)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, 1L, null, null), false)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki4.keyName, null, ki4.feature, ki4.treatment, ki4.time, null, 1L, null, null), false)).collect(Collectors.toList())); treatmentLog.sendImpressions(); verify(senderMock).postImpressionsBulk(impressionsCaptor.capture()); @@ -606,8 +606,8 @@ public void testImpressionsConsumerModeOptimizedMode() { } } // Only the first 2 impressions make it to the server - Assert.assertTrue(captured.get(0).keyImpressions.contains(keyImpression("test1", "adil", "on", 1L, 1L))); - Assert.assertTrue(captured.get(0).keyImpressions.contains(keyImpression("test1", "pato", "on", 3L, 1L))); + Assert.assertTrue(captured.get(0).keyImpressions.contains(keyImpression("test1", "adil", "on", 1L, 1L, null))); + Assert.assertTrue(captured.get(0).keyImpressions.contains(keyImpression("test1", "pato", "on", 3L, 1L, null))); treatmentLog.sendImpressionCounters(); verify(senderMock).postCounters(impressionCountCaptor.capture()); @@ -644,15 +644,15 @@ public void testImpressionsConsumerModeNoneMode() { treatmentLog.start(); // These 4 unique test name will cause 4 entries but we are caping at the first 3. - KeyImpression ki1 = keyImpression("test1", "adil", "on", 1L, 1L); - KeyImpression ki2 = keyImpression("test1", "adil", "on", 2L, 1L); - KeyImpression ki3 = keyImpression("test1", "pato", "on", 3L, 1L); - KeyImpression ki4 = keyImpression("test1", "pato", "on", 4L, 1L); - - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, 1L, null), false)).collect(Collectors.toList())); - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, 1L, null), false)).collect(Collectors.toList())); - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, 1L, null), false)).collect(Collectors.toList())); - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki4.keyName, null, ki4.feature, ki4.treatment, ki4.time, null, 1L, null), false)).collect(Collectors.toList())); + KeyImpression ki1 = keyImpression("test1", "adil", "on", 1L, 1L, null); + KeyImpression ki2 = keyImpression("test1", "adil", "on", 2L, 1L, null); + KeyImpression ki3 = keyImpression("test1", "pato", "on", 3L, 1L, null); + KeyImpression ki4 = keyImpression("test1", "pato", "on", 4L, 1L, null); + + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, 1L, null, null), false)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, 1L, null, null), false)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, 1L, null, null), false)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki4.keyName, null, ki4.feature, ki4.treatment, ki4.time, null, 1L, null, null), false)).collect(Collectors.toList())); uniqueKeysTracker.stop(); treatmentLog.close(); @@ -700,15 +700,15 @@ public void testImpressionsConsumerModeDebugMode() { treatmentLog.start(); // These 4 unique test name will cause 4 entries but we are caping at the first 3. - KeyImpression ki1 = keyImpression("test1", "adil", "on", 1L, 1L); - KeyImpression ki2 = keyImpression("test1", "adil", "on", 2L, 1L); - KeyImpression ki3 = keyImpression("test1", "pato", "on", 3L, 1L); - KeyImpression ki4 = keyImpression("test1", "pato", "on", 4L, 1L); - - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, 1L, null), false)).collect(Collectors.toList())); - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, 1L, null), false)).collect(Collectors.toList())); - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, 1L, null), false)).collect(Collectors.toList())); - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki4.keyName, null, ki4.feature, ki4.treatment, ki4.time, null, 1L, null), false)).collect(Collectors.toList())); + KeyImpression ki1 = keyImpression("test1", "adil", "on", 1L, 1L, null); + KeyImpression ki2 = keyImpression("test1", "adil", "on", 2L, 1L, null); + KeyImpression ki3 = keyImpression("test1", "pato", "on", 3L, 1L, null); + KeyImpression ki4 = keyImpression("test1", "pato", "on", 4L, 1L, null); + + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, 1L, null, null), false)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, 1L, null, null), false)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, 1L, null, null), false)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki4.keyName, null, ki4.feature, ki4.treatment, ki4.time, null, 1L, null, null), false)).collect(Collectors.toList())); treatmentLog.sendImpressions(); verify(senderMock).postImpressionsBulk(impressionsCaptor.capture()); @@ -726,8 +726,8 @@ public void testImpressionsConsumerModeDebugMode() { Assert.assertEquals(Optional.of(3L), Optional.of(keyImpression4.previousTime)); } // Only the first 2 impressions make it to the server - Assert.assertTrue(captured.get(0).keyImpressions.contains(keyImpression("test1", "adil", "on", 1L, 1L))); - Assert.assertTrue(captured.get(0).keyImpressions.contains(keyImpression("test1", "pato", "on", 3L, 1L))); + Assert.assertTrue(captured.get(0).keyImpressions.contains(keyImpression("test1", "adil", "on", 1L, 1L, null))); + Assert.assertTrue(captured.get(0).keyImpressions.contains(keyImpression("test1", "pato", "on", 3L, 1L, null))); } @Test @@ -871,15 +871,15 @@ public void testImpressionToggleStandaloneOptimizedMode() { treatmentLog.start(); // These 4 unique test name will cause 4 entries but we are caping at the first 3. - KeyImpression ki1 = keyImpression("test1", "adil", "on", 1L, 1L); - KeyImpression ki2 = keyImpression("test1", "mati", "on", 2L, 1L); - KeyImpression ki3 = keyImpression("test1", "pato", "on", 3L, 1L); - KeyImpression ki4 = keyImpression("test1", "bilal", "on", 4L, 1L); - - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, 1L, null), false)).collect(Collectors.toList())); - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, 1L, null), true)).collect(Collectors.toList())); - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, 1L, null), false)).collect(Collectors.toList())); - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki4.keyName, null, ki4.feature, ki4.treatment, ki4.time, null, 1L, null), true)).collect(Collectors.toList())); + KeyImpression ki1 = keyImpression("test1", "adil", "on", 1L, 1L, null); + KeyImpression ki2 = keyImpression("test1", "mati", "on", 2L, 1L, null); + KeyImpression ki3 = keyImpression("test1", "pato", "on", 3L, 1L, null); + KeyImpression ki4 = keyImpression("test1", "bilal", "on", 4L, 1L, null); + + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, 1L, null, null), false)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, 1L, null, null), true)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, 1L, null, null), false)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki4.keyName, null, ki4.feature, ki4.treatment, ki4.time, null, 1L, null, null), true)).collect(Collectors.toList())); treatmentLog.sendImpressions(); verify(senderMock).postImpressionsBulk(impressionsCaptor.capture()); @@ -892,8 +892,8 @@ public void testImpressionToggleStandaloneOptimizedMode() { } } // Only the first 2 impressions make it to the server - Assert.assertTrue(captured.get(0).keyImpressions.contains(keyImpression("test1", "adil", "on", 1L, 1L))); - Assert.assertTrue(captured.get(0).keyImpressions.contains(keyImpression("test1", "pato", "on", 3L, 1L))); + Assert.assertTrue(captured.get(0).keyImpressions.contains(keyImpression("test1", "adil", "on", 1L, 1L, null))); + Assert.assertTrue(captured.get(0).keyImpressions.contains(keyImpression("test1", "pato", "on", 3L, 1L, null))); HashMap> trackedKeys = ((UniqueKeysTrackerImp) uniqueKeysTracker).popAll(); HashSet keys = new HashSet<>(); @@ -936,15 +936,15 @@ public void testImpressionToggleStandaloneModeDebugMode() { treatmentLog.start(); // These 4 unique test name will cause 4 entries but we are caping at the first 3. - KeyImpression ki1 = keyImpression("test1", "adil", "on", 1L, 1L); - KeyImpression ki2 = keyImpression("test1", "mati", "on", 2L, 1L); - KeyImpression ki3 = keyImpression("test1", "pato", "on", 3L, 1L); - KeyImpression ki4 = keyImpression("test1", "bilal", "on", 4L, 1L); - - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, 1L, null), false)).collect(Collectors.toList())); - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, 1L, null), true)).collect(Collectors.toList())); - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, 1L, null), false)).collect(Collectors.toList())); - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki4.keyName, null, ki4.feature, ki4.treatment, ki4.time, null, 1L, null), true)).collect(Collectors.toList())); + KeyImpression ki1 = keyImpression("test1", "adil", "on", 1L, 1L, null); + KeyImpression ki2 = keyImpression("test1", "mati", "on", 2L, 1L, null); + KeyImpression ki3 = keyImpression("test1", "pato", "on", 3L, 1L, null); + KeyImpression ki4 = keyImpression("test1", "bilal", "on", 4L, 1L, null); + + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, 1L, null, null), false)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, 1L, null, null), true)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, 1L, null, null), false)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki4.keyName, null, ki4.feature, ki4.treatment, ki4.time, null, 1L, null, null), true)).collect(Collectors.toList())); treatmentLog.sendImpressions(); HashMap> trackedKeys = ((UniqueKeysTrackerImp) uniqueKeysTracker).popAll(); @@ -965,8 +965,8 @@ public void testImpressionToggleStandaloneModeDebugMode() { Assert.assertEquals(null, keyImpression3.previousTime); } // Only the first 2 impressions make it to the server - Assert.assertTrue(captured.get(0).keyImpressions.contains(keyImpression("test1", "adil", "on", 1L, 1L))); - Assert.assertTrue(captured.get(0).keyImpressions.contains(keyImpression("test1", "pato", "on", 3L, 1L))); + Assert.assertTrue(captured.get(0).keyImpressions.contains(keyImpression("test1", "adil", "on", 1L, 1L, null))); + Assert.assertTrue(captured.get(0).keyImpressions.contains(keyImpression("test1", "pato", "on", 3L, 1L, null))); } @Test @@ -991,15 +991,15 @@ public void testImpressionToggleStandaloneModeNoneMode() { treatmentLog.start(); // These 4 unique test name will cause 4 entries but we are caping at the first 3. - KeyImpression ki1 = keyImpression("test1", "adil", "on", 1L, 1L); - KeyImpression ki2 = keyImpression("test1", "mati", "on", 2L, 1L); - KeyImpression ki3 = keyImpression("test1", "pato", "on", 3L, 1L); - KeyImpression ki4 = keyImpression("test1", "bilal", "on", 4L, 1L); - - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, 1L, null), false)).collect(Collectors.toList())); - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, 1L, null), true)).collect(Collectors.toList())); - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, 1L, null), false)).collect(Collectors.toList())); - treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki4.keyName, null, ki4.feature, ki4.treatment, ki4.time, null, 1L, null), true)).collect(Collectors.toList())); + KeyImpression ki1 = keyImpression("test1", "adil", "on", 1L, 1L, null); + KeyImpression ki2 = keyImpression("test1", "mati", "on", 2L, 1L, null); + KeyImpression ki3 = keyImpression("test1", "pato", "on", 3L, 1L, null); + KeyImpression ki4 = keyImpression("test1", "bilal", "on", 4L, 1L, null); + + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, 1L, null, null), false)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, 1L, null, null), true)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, 1L, null, null), false)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki4.keyName, null, ki4.feature, ki4.treatment, ki4.time, null, 1L, null, null), true)).collect(Collectors.toList())); treatmentLog.close(); HashMap> trackedKeys = ((UniqueKeysTrackerImp) uniqueKeysTracker).popAll(); uniqueKeysTracker.stop(); @@ -1023,4 +1023,115 @@ public void testImpressionToggleStandaloneModeNoneMode() { treatmentLog.sendImpressionCounters(); verify(senderMock, times(0)).postCounters(Mockito.any()); } + + @Test + public void testImpressionsPropertiesOptimizedMode() { + SplitClientConfig config = SplitClientConfig.builder() + .impressionsQueueSize(10) + .endpoint("nowhere.com", "nowhere.com") + .impressionsMode(ImpressionsManager.Mode.OPTIMIZED) + .operationMode(OperationMode.CONSUMER) + .customStorageWrapper(Mockito.mock(CustomStorageWrapper.class)) + .build(); + ImpressionsStorage storage = new InMemoryImpressionsStorage(config.impressionsQueueSize()); + + ImpressionsSender senderMock = Mockito.mock(ImpressionsSender.class); + ImpressionCounter impressionCounter = new ImpressionCounter(); + ImpressionObserver impressionObserver = new ImpressionObserver(200); + TelemetryStorageProducer telemetryStorageProducer = new InMemoryTelemetryStorage(); + + ProcessImpressionStrategy processImpressionStrategy = new ProcessImpressionOptimized(false, impressionObserver, impressionCounter, telemetryStorageProducer); + ProcessImpressionNone processImpressionNone = new ProcessImpressionNone(false, null, null); + ImpressionsManagerImpl treatmentLog = ImpressionsManagerImpl.instanceForTest(config, senderMock, TELEMETRY_STORAGE, storage, storage, processImpressionNone, processImpressionStrategy, impressionCounter, null); + treatmentLog.start(); + + // These 4 unique test name will cause 4 entries but we are caping at the first 3. + KeyImpression ki1 = keyImpression("test1", "adil", "on", 1L, 1L, "{\"prop\":\"val\"}"); + KeyImpression ki2 = keyImpression("test1", "adil", "on", 2L, 1L, null); + KeyImpression ki3 = keyImpression("test1", "pato", "on", 3L, 1L, null); + KeyImpression ki4 = keyImpression("test1", "pato", "on", 4L, 1L, null); + + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, 1L, null, ki1.properties), false)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, 1L, null, null), false)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, 1L, null, null), false)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki4.keyName, null, ki4.feature, ki4.treatment, ki4.time, null, 1L, null, null), false)).collect(Collectors.toList())); + treatmentLog.sendImpressions(); + + verify(senderMock).postImpressionsBulk(impressionsCaptor.capture()); + + List captured = impressionsCaptor.getValue(); + Assert.assertEquals(3, captured.get(0).keyImpressions.size()); + for (TestImpressions testImpressions : captured) { + for (KeyImpression keyImpression : testImpressions.keyImpressions) { + Assert.assertEquals(null, keyImpression.previousTime); + } + } + // impression with properties is not deduped + Assert.assertTrue(captured.get(0).keyImpressions.contains(keyImpression("test1", "adil", "on", 1L, 1L, "{\"prop\":\"val\"}"))); + Assert.assertTrue(captured.get(0).keyImpressions.contains(keyImpression("test1", "adil", "on", 2L, 1L, null))); + Assert.assertTrue(captured.get(0).keyImpressions.contains(keyImpression("test1", "pato", "on", 3L, 1L, null))); + + treatmentLog.sendImpressionCounters(); + verify(senderMock).postCounters(impressionCountCaptor.capture()); + HashMap capturedCounts = impressionCountCaptor.getValue(); + Assert.assertEquals(1, capturedCounts.size()); + Assert.assertTrue(capturedCounts.entrySet().contains(new AbstractMap.SimpleEntry<>(new ImpressionCounter.Key("test1", 0), 1))); + + // Assert that the sender is never called if the counters are empty. + Mockito.reset(senderMock); + treatmentLog.sendImpressionCounters(); + verify(senderMock, times(0)).postCounters(Mockito.any()); + } + + @Test + public void testImpressionsPropertiesDebugMode() { + SplitClientConfig config = SplitClientConfig.builder() + .impressionsQueueSize(10) + .endpoint("nowhere.com", "nowhere.com") + .impressionsMode(ImpressionsManager.Mode.DEBUG) + .operationMode(OperationMode.CONSUMER) + .customStorageWrapper(Mockito.mock(CustomStorageWrapper.class)) + .build(); + ImpressionsStorage storage = new InMemoryImpressionsStorage(config.impressionsQueueSize()); + + ImpressionsSender senderMock = Mockito.mock(ImpressionsSender.class); + ImpressionCounter impressionCounter = Mockito.mock(ImpressionCounter.class); + ImpressionObserver impressionObserver = new ImpressionObserver(200); + ProcessImpressionStrategy processImpressionStrategy = new ProcessImpressionDebug(false, impressionObserver); + ProcessImpressionNone processImpressionNone = new ProcessImpressionNone(false, null, null); + + ImpressionsManagerImpl treatmentLog = ImpressionsManagerImpl.instanceForTest(config, senderMock, TELEMETRY_STORAGE, storage, storage, processImpressionNone, processImpressionStrategy, impressionCounter, null); + treatmentLog.start(); + + // These 4 unique test name will cause 4 entries but we are caping at the first 3. + KeyImpression ki1 = keyImpression("test1", "adil", "on", 1L, 1L, "{\"prop\":\"val\"}"); + KeyImpression ki2 = keyImpression("test1", "adil", "on", 2L, 1L, null); + KeyImpression ki3 = keyImpression("test1", "pato", "on", 3L, 1L, null); + KeyImpression ki4 = keyImpression("test1", "pato", "on", 4L, 1L, null); + + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, 1L, null, "{\"prop\":\"val\"}"), false)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, 1L, null, null), false)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, 1L, null, null), false)).collect(Collectors.toList())); + treatmentLog.track(Stream.of(new DecoratedImpression(new Impression(ki4.keyName, null, ki4.feature, ki4.treatment, ki4.time, null, 1L, null, null), false)).collect(Collectors.toList())); + treatmentLog.sendImpressions(); + + verify(senderMock).postImpressionsBulk(impressionsCaptor.capture()); + + List captured = impressionsCaptor.getValue(); + Assert.assertEquals(4, captured.get(0).keyImpressions.size()); + for (TestImpressions testImpressions : captured) { + KeyImpression keyImpression1 = testImpressions.keyImpressions.get(0); + KeyImpression keyImpression2 = testImpressions.keyImpressions.get(1); + KeyImpression keyImpression3 = testImpressions.keyImpressions.get(2); + KeyImpression keyImpression4 = testImpressions.keyImpressions.get(3); + Assert.assertEquals(null, keyImpression1.previousTime); + Assert.assertEquals(null, keyImpression2.previousTime); + Assert.assertEquals(null, keyImpression3.previousTime); + Assert.assertEquals(Optional.of(3L), Optional.of(keyImpression4.previousTime)); + } + // impression with properties is not deduped + Assert.assertTrue(captured.get(0).keyImpressions.contains(keyImpression("test1", "adil", "on", 1L, 1L, "{\"prop\":\"val\"}"))); + Assert.assertTrue(captured.get(0).keyImpressions.contains(keyImpression("test1", "adil", "on", 1L, 1L, null))); + Assert.assertTrue(captured.get(0).keyImpressions.contains(keyImpression("test1", "pato", "on", 3L, 1L, null))); + } } \ No newline at end of file diff --git a/client/src/test/java/io/split/client/impressions/strategy/ProcessImpressionDebugTest.java b/client/src/test/java/io/split/client/impressions/strategy/ProcessImpressionDebugTest.java index d0a36eb0e..68be97c58 100644 --- a/client/src/test/java/io/split/client/impressions/strategy/ProcessImpressionDebugTest.java +++ b/client/src/test/java/io/split/client/impressions/strategy/ProcessImpressionDebugTest.java @@ -26,14 +26,14 @@ public void processImpressionsWithListener(){ ImpressionObserver impressionObserver = new ImpressionObserver(LAST_SEEN_CACHE_SIZE); ProcessImpressionDebug processImpressionDebug = new ProcessImpressionDebug(listenerEnable, impressionObserver); - KeyImpression ki1 = keyImpression("test1", "adil", "on", 1L, null); - KeyImpression ki2 = keyImpression("test2", "adil", "on", 1L, null); - KeyImpression ki3 = keyImpression("test1", "adil", "on", 1L, null); + KeyImpression ki1 = keyImpression("test1", "adil", "on", 1L, null, null); + KeyImpression ki2 = keyImpression("test2", "adil", "on", 1L, null, null); + KeyImpression ki3 = keyImpression("test1", "adil", "on", 1L, null, null); List impressions = new ArrayList<>(); - impressions.add(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, 1L, null)); - impressions.add(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, 1L, null)); - impressions.add(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, 1L, null)); + impressions.add(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, 1L, null, null)); + impressions.add(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, 1L, null, null)); + impressions.add(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, 1L, null, null)); ImpressionsResult impressionsResult1 = processImpressionDebug.process(impressions); @@ -50,14 +50,14 @@ public void processImpressionsWithoutListener(){ ImpressionObserver impressionObserver = new ImpressionObserver(LAST_SEEN_CACHE_SIZE); ProcessImpressionDebug processImpressionDebug = new ProcessImpressionDebug(listenerEnable, impressionObserver); - KeyImpression ki1 = keyImpression("test1", "adil", "on", 1L, null); - KeyImpression ki2 = keyImpression("test2", "adil", "on", 1L, null); - KeyImpression ki3 = keyImpression("test1", "adil", "on", 1L, null); + KeyImpression ki1 = keyImpression("test1", "adil", "on", 1L, null, null); + KeyImpression ki2 = keyImpression("test2", "adil", "on", 1L, null, null); + KeyImpression ki3 = keyImpression("test1", "adil", "on", 1L, null, null); List impressions = new ArrayList<>(); - impressions.add(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, 1L, null)); - impressions.add(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, 1L, null)); - impressions.add(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, 1L, null)); + impressions.add(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, 1L, null, null)); + impressions.add(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, 1L, null, null)); + impressions.add(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, 1L, null, null)); ImpressionsResult impressionsResult1 = processImpressionDebug.process(impressions); diff --git a/client/src/test/java/io/split/client/impressions/strategy/ProcessImpressionNoneTest.java b/client/src/test/java/io/split/client/impressions/strategy/ProcessImpressionNoneTest.java index 49e848c79..1debedd1e 100644 --- a/client/src/test/java/io/split/client/impressions/strategy/ProcessImpressionNoneTest.java +++ b/client/src/test/java/io/split/client/impressions/strategy/ProcessImpressionNoneTest.java @@ -29,14 +29,14 @@ public void processImpressionsWithListener(){ UniqueKeysTrackerImp uniqueKeysTracker = new UniqueKeysTrackerImp(telemetrySynchronizer, 10000, 10000, null); ProcessImpressionNone processImpressionNone = new ProcessImpressionNone(listenerEnable, uniqueKeysTracker, counter); - KeyImpression ki1 = keyImpression("test1", "adil", "on", 1L, null); - KeyImpression ki2 = keyImpression("test2", "adil", "on", 1L, null); - KeyImpression ki3 = keyImpression("test1", "adil", "on", 1L, null); + KeyImpression ki1 = keyImpression("test1", "adil", "on", 1L, null, null); + KeyImpression ki2 = keyImpression("test2", "adil", "on", 1L, null, null); + KeyImpression ki3 = keyImpression("test1", "adil", "on", 1L, null, null); List impressions = new ArrayList<>(); - impressions.add(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, 1L, null)); - impressions.add(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, 1L, null)); - impressions.add(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, 1L, null)); + impressions.add(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, 1L, null, null)); + impressions.add(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, 1L, null, null)); + impressions.add(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, 1L, null, null)); ImpressionsResult impressionsResult1 = processImpressionNone.process(impressions); Assert.assertEquals(0,impressionsResult1.getImpressionsToQueue().size()); @@ -55,14 +55,14 @@ public void processImpressionsWithoutListener(){ UniqueKeysTrackerImp uniqueKeysTracker = new UniqueKeysTrackerImp(telemetrySynchronizer, 10000, 10000, null); ProcessImpressionNone processImpressionNone = new ProcessImpressionNone(listenerEnable, uniqueKeysTracker, counter); - KeyImpression ki1 = keyImpression("test1", "adil", "on", 1L, null); - KeyImpression ki2 = keyImpression("test2", "adil", "on", 1L, null); - KeyImpression ki3 = keyImpression("test1", "adil", "on", 1L, null); + KeyImpression ki1 = keyImpression("test1", "adil", "on", 1L, null, null); + KeyImpression ki2 = keyImpression("test2", "adil", "on", 1L, null, null); + KeyImpression ki3 = keyImpression("test1", "adil", "on", 1L, null, null); List impressions = new ArrayList<>(); - impressions.add(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, 1L, null)); - impressions.add(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, 1L, null)); - impressions.add(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, 1L, null)); + impressions.add(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, 1L, null, null)); + impressions.add(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, 1L, null, null)); + impressions.add(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, 1L, null, null)); ImpressionsResult impressionsResult1 = processImpressionNone.process(impressions); Assert.assertEquals(0,impressionsResult1.getImpressionsToQueue().size()); diff --git a/client/src/test/java/io/split/client/impressions/strategy/ProcessImpressionOptimizedTest.java b/client/src/test/java/io/split/client/impressions/strategy/ProcessImpressionOptimizedTest.java index 9b5b5d53a..f7cecebe5 100644 --- a/client/src/test/java/io/split/client/impressions/strategy/ProcessImpressionOptimizedTest.java +++ b/client/src/test/java/io/split/client/impressions/strategy/ProcessImpressionOptimizedTest.java @@ -29,14 +29,14 @@ public void processImpressionsWithListener(){ ImpressionCounter counter = new ImpressionCounter(); ProcessImpressionOptimized processImpressionOptimized = new ProcessImpressionOptimized(listenerEnable, impressionObserver, counter, TELEMETRY_STORAGE); - KeyImpression ki1 = keyImpression("test1", "adil", "on", 1L, null); - KeyImpression ki2 = keyImpression("test2", "adil", "on", 1L, null); - KeyImpression ki3 = keyImpression("test1", "adil", "on", 1L, null); + KeyImpression ki1 = keyImpression("test1", "adil", "on", 1L, null, null); + KeyImpression ki2 = keyImpression("test2", "adil", "on", 1L, null, null); + KeyImpression ki3 = keyImpression("test1", "adil", "on", 1L, null, null); List impressions = new ArrayList<>(); - impressions.add(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, 1L, null)); - impressions.add(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, 1L, null)); - impressions.add(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, 1L, null)); + impressions.add(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, 1L, null, null)); + impressions.add(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, 1L, null, null)); + impressions.add(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, 1L, null, null)); ImpressionsResult impressionsResult1 = processImpressionOptimized.process(impressions); @@ -52,14 +52,14 @@ public void processImpressionsWithoutListener(){ ImpressionCounter counter = new ImpressionCounter(); ProcessImpressionOptimized processImpressionOptimized = new ProcessImpressionOptimized(listenerEnable, impressionObserver, counter, TELEMETRY_STORAGE); - KeyImpression ki1 = keyImpression("test1", "adil", "on", 1L, null); - KeyImpression ki2 = keyImpression("test2", "adil", "on", 1L, null); - KeyImpression ki3 = keyImpression("test1", "adil", "on", 1L, null); + KeyImpression ki1 = keyImpression("test1", "adil", "on", 1L, null, null); + KeyImpression ki2 = keyImpression("test2", "adil", "on", 1L, null, null); + KeyImpression ki3 = keyImpression("test1", "adil", "on", 1L, null, null); List impressions = new ArrayList<>(); - impressions.add(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, 1L, null)); - impressions.add(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, 1L, null)); - impressions.add(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, 1L, null)); + impressions.add(new Impression(ki1.keyName, null, ki1.feature, ki1.treatment, ki1.time, null, 1L, null, null)); + impressions.add(new Impression(ki2.keyName, null, ki2.feature, ki2.treatment, ki2.time, null, 1L, null, null)); + impressions.add(new Impression(ki3.keyName, null, ki3.feature, ki3.treatment, ki3.time, null, 1L, null, null)); ImpressionsResult impressionsResult1 = processImpressionOptimized.process(impressions); Assert.assertEquals(2,impressionsResult1.getImpressionsToQueue().size()); diff --git a/client/src/test/java/io/split/engine/common/LocalhostSynchronizerTest.java b/client/src/test/java/io/split/engine/common/LocalhostSynchronizerTest.java index 91be19f47..6c0029084 100644 --- a/client/src/test/java/io/split/engine/common/LocalhostSynchronizerTest.java +++ b/client/src/test/java/io/split/engine/common/LocalhostSynchronizerTest.java @@ -6,17 +6,15 @@ import io.split.client.interceptors.FlagSetsFilterImpl; import io.split.client.utils.FileInputStreamProvider; import io.split.client.utils.InputStreamProvider; -import io.split.engine.experiments.SplitChangeFetcher; -import io.split.engine.experiments.SplitFetcher; -import io.split.engine.experiments.SplitFetcherImp; -import io.split.engine.experiments.SplitParser; -import io.split.engine.experiments.SplitSynchronizationTask; +import io.split.engine.experiments.*; import io.split.engine.segments.SegmentChangeFetcher; import io.split.engine.segments.SegmentSynchronizationTaskImp; +import io.split.storages.RuleBasedSegmentCacheProducer; import io.split.storages.SegmentCacheProducer; import io.split.storages.SplitCache; import io.split.storages.SplitCacheProducer; import io.split.storages.memory.InMemoryCacheImp; +import io.split.storages.memory.RuleBasedSegmentCacheInMemoryImp; import io.split.storages.memory.SegmentCacheInMemoryImpl; import io.split.telemetry.storage.NoopTelemetryStorage; import io.split.telemetry.storage.TelemetryStorage; @@ -38,8 +36,12 @@ public void testSyncAll(){ InputStreamProvider inputStreamProvider = new FileInputStreamProvider("src/test/resources/split_init.json"); SplitChangeFetcher splitChangeFetcher = new JsonLocalhostSplitChangeFetcher(inputStreamProvider); SplitParser splitParser = new SplitParser(); + RuleBasedSegmentCacheProducer ruleBasedSegmentCacheProducer = new RuleBasedSegmentCacheInMemoryImp(); + RuleBasedSegmentParser ruleBasedSegmentParser = new RuleBasedSegmentParser(); + + SplitFetcher splitFetcher = new SplitFetcherImp(splitChangeFetcher, splitParser, splitCacheProducer, TELEMETRY_STORAGE_NOOP, FLAG_SETS_FILTER, + ruleBasedSegmentParser, ruleBasedSegmentCacheProducer); - SplitFetcher splitFetcher = new SplitFetcherImp(splitChangeFetcher, splitParser, splitCacheProducer, TELEMETRY_STORAGE_NOOP, FLAG_SETS_FILTER); SplitSynchronizationTask splitSynchronizationTask = new SplitSynchronizationTask(splitFetcher, splitCacheProducer, 1000L, null); SegmentChangeFetcher segmentChangeFetcher = new LocalhostSegmentChangeFetcher("src/test/resources/"); @@ -60,8 +62,12 @@ public void testPeriodicFetching() throws InterruptedException { SplitChangeFetcher splitChangeFetcher = Mockito.mock(JsonLocalhostSplitChangeFetcher.class); SplitParser splitParser = new SplitParser(); + RuleBasedSegmentCacheProducer ruleBasedSegmentCacheProducer = new RuleBasedSegmentCacheInMemoryImp(); + RuleBasedSegmentParser ruleBasedSegmentParser = new RuleBasedSegmentParser(); + + SplitFetcher splitFetcher = new SplitFetcherImp(splitChangeFetcher, splitParser, splitCacheProducer, TELEMETRY_STORAGE_NOOP, FLAG_SETS_FILTER, + ruleBasedSegmentParser, ruleBasedSegmentCacheProducer); - SplitFetcher splitFetcher = new SplitFetcherImp(splitChangeFetcher, splitParser, splitCacheProducer, TELEMETRY_STORAGE_NOOP, FLAG_SETS_FILTER); SplitSynchronizationTask splitSynchronizationTask = new SplitSynchronizationTask(splitFetcher, splitCacheProducer, 1000L, null); FetchOptions fetchOptions = new FetchOptions.Builder().build(); @@ -78,7 +84,7 @@ public void testPeriodicFetching() throws InterruptedException { Thread.sleep(2000); - Mockito.verify(splitChangeFetcher, Mockito.times(1)).fetch(-1, fetchOptions); + Mockito.verify(splitChangeFetcher, Mockito.times(1)).fetch(-1, -1, fetchOptions); } @Test @@ -86,14 +92,17 @@ public void testRefreshSplits() { SplitCacheProducer splitCacheProducer = new InMemoryCacheImp(FLAG_SETS_FILTER); SplitChangeFetcher splitChangeFetcher = Mockito.mock(SplitChangeFetcher.class); SplitParser splitParser = new SplitParser(); + RuleBasedSegmentCacheProducer ruleBasedSegmentCacheProducer = new RuleBasedSegmentCacheInMemoryImp(); + RuleBasedSegmentParser ruleBasedSegmentParser = new RuleBasedSegmentParser(); - SplitFetcher splitFetcher = new SplitFetcherImp(splitChangeFetcher, splitParser, splitCacheProducer, TELEMETRY_STORAGE_NOOP, FLAG_SETS_FILTER); + SplitFetcher splitFetcher = new SplitFetcherImp(splitChangeFetcher, splitParser, splitCacheProducer, TELEMETRY_STORAGE_NOOP, FLAG_SETS_FILTER, + ruleBasedSegmentParser, ruleBasedSegmentCacheProducer); SplitSynchronizationTask splitSynchronizationTask = new SplitSynchronizationTask(splitFetcher, splitCacheProducer, 1000L, null); SplitTasks splitTasks = SplitTasks.build(splitSynchronizationTask, null, null, null, null, null); LocalhostSynchronizer localhostSynchronizer = new LocalhostSynchronizer(splitTasks, splitFetcher, false); localhostSynchronizer.refreshSplits(null); - Mockito.verify(splitChangeFetcher, Mockito.times(1)).fetch(Mockito.anyLong(), Mockito.anyObject()); + Mockito.verify(splitChangeFetcher, Mockito.times(1)).fetch(Mockito.anyLong(), Mockito.anyLong(), Mockito.anyObject()); } } \ No newline at end of file diff --git a/client/src/test/java/io/split/engine/evaluator/EvaluatorIntegrationTest.java b/client/src/test/java/io/split/engine/evaluator/EvaluatorIntegrationTest.java index a58a22194..e24811e05 100644 --- a/client/src/test/java/io/split/engine/evaluator/EvaluatorIntegrationTest.java +++ b/client/src/test/java/io/split/engine/evaluator/EvaluatorIntegrationTest.java @@ -12,9 +12,11 @@ import io.split.engine.matchers.CombiningMatcher; import io.split.engine.matchers.strings.EndsWithAnyOfMatcher; import io.split.engine.matchers.strings.WhitelistMatcher; +import io.split.storages.RuleBasedSegmentCache; import io.split.storages.SegmentCache; import io.split.storages.SplitCache; import io.split.storages.memory.InMemoryCacheImp; +import io.split.storages.memory.RuleBasedSegmentCacheInMemoryImp; import io.split.storages.memory.SegmentCacheInMemoryImpl; import org.junit.Assert; import org.junit.Test; @@ -156,7 +158,8 @@ private Evaluator buildEvaluatorAndLoadCache(boolean killed, int trafficAllocati FlagSetsFilter flagSetsFilter = new FlagSetsFilterImpl(new HashSet<>()); SplitCache splitCache = new InMemoryCacheImp(flagSetsFilter); SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); - Evaluator evaluator = new EvaluatorImp(splitCache, segmentCache); + RuleBasedSegmentCache ruleBasedSegmentCache = new RuleBasedSegmentCacheInMemoryImp(); + Evaluator evaluator = new EvaluatorImp(splitCache, segmentCache, ruleBasedSegmentCache); Partition partition = new Partition(); partition.treatment = ON_TREATMENT; diff --git a/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java b/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java index 5be942bf1..2fe2d83eb 100644 --- a/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java +++ b/client/src/test/java/io/split/engine/evaluator/EvaluatorTest.java @@ -5,6 +5,7 @@ import io.split.engine.experiments.ParsedCondition; import io.split.engine.experiments.ParsedSplit; import io.split.engine.matchers.CombiningMatcher; +import io.split.storages.RuleBasedSegmentCacheConsumer; import io.split.storages.SegmentCacheConsumer; import io.split.storages.SplitCacheConsumer; import org.junit.Assert; @@ -33,6 +34,7 @@ public class EvaluatorTest { private SplitCacheConsumer _splitCacheConsumer; private SegmentCacheConsumer _segmentCacheConsumer; + private RuleBasedSegmentCacheConsumer _ruleBasedSegmentCacheConsumer; private Evaluator _evaluator; private CombiningMatcher _matcher; private Map _configurations; @@ -44,7 +46,8 @@ public class EvaluatorTest { public void before() { _splitCacheConsumer = Mockito.mock(SplitCacheConsumer.class); _segmentCacheConsumer = Mockito.mock(SegmentCacheConsumer.class); - _evaluator = new EvaluatorImp(_splitCacheConsumer, _segmentCacheConsumer); + _ruleBasedSegmentCacheConsumer = Mockito.mock(RuleBasedSegmentCacheConsumer.class); + _evaluator = new EvaluatorImp(_splitCacheConsumer, _segmentCacheConsumer, _ruleBasedSegmentCacheConsumer); _matcher = Mockito.mock(CombiningMatcher.class); _evaluationContext = Mockito.mock(EvaluationContext.class); diff --git a/client/src/test/java/io/split/engine/experiments/AChangePerCallSplitChangeFetcher.java b/client/src/test/java/io/split/engine/experiments/AChangePerCallSplitChangeFetcher.java index 63fbf1e26..6248e9961 100644 --- a/client/src/test/java/io/split/engine/experiments/AChangePerCallSplitChangeFetcher.java +++ b/client/src/test/java/io/split/engine/experiments/AChangePerCallSplitChangeFetcher.java @@ -32,7 +32,7 @@ public AChangePerCallSplitChangeFetcher(String segmentName) { @Override - public SplitChange fetch(long since, FetchOptions options) { + public SplitChange fetch(long since, long rbSince, FetchOptions options) { long latestChangeNumber = since + 1; Condition condition = null; diff --git a/client/src/test/java/io/split/engine/experiments/SplitFetcherImpTest.java b/client/src/test/java/io/split/engine/experiments/SplitFetcherImpTest.java index 8ddab8dad..f1ab4cada 100644 --- a/client/src/test/java/io/split/engine/experiments/SplitFetcherImpTest.java +++ b/client/src/test/java/io/split/engine/experiments/SplitFetcherImpTest.java @@ -1,13 +1,16 @@ package io.split.engine.experiments; +import io.split.Spec; import io.split.client.JsonLocalhostSplitChangeFetcher; import io.split.client.interceptors.FlagSetsFilter; import io.split.client.interceptors.FlagSetsFilterImpl; import io.split.client.utils.FileInputStreamProvider; import io.split.client.utils.InputStreamProvider; import io.split.engine.common.FetchOptions; +import io.split.storages.RuleBasedSegmentCacheProducer; import io.split.storages.SplitCacheProducer; import io.split.storages.memory.InMemoryCacheImp; +import io.split.storages.memory.RuleBasedSegmentCacheInMemoryImp; import io.split.telemetry.storage.NoopTelemetryStorage; import io.split.telemetry.storage.TelemetryStorage; import org.junit.Assert; @@ -33,12 +36,15 @@ public class SplitFetcherImpTest { public void testLocalHost() { FlagSetsFilter flagSetsFilter = new FlagSetsFilterImpl(new HashSet<>()); SplitCacheProducer splitCacheProducer = new InMemoryCacheImp(flagSetsFilter); + RuleBasedSegmentCacheProducer ruleBasedSegmentCacheProducer = new RuleBasedSegmentCacheInMemoryImp(); + RuleBasedSegmentParser ruleBasedSegmentParser = new RuleBasedSegmentParser(); InputStreamProvider inputStreamProvider = new FileInputStreamProvider("src/test/resources/split_init.json"); SplitChangeFetcher splitChangeFetcher = new JsonLocalhostSplitChangeFetcher(inputStreamProvider); SplitParser splitParser = new SplitParser(); FetchOptions fetchOptions = new FetchOptions.Builder().build(); - SplitFetcher splitFetcher = new SplitFetcherImp(splitChangeFetcher, splitParser, splitCacheProducer, TELEMETRY_STORAGE_NOOP, flagSetsFilter); + SplitFetcher splitFetcher = new SplitFetcherImp(splitChangeFetcher, splitParser, splitCacheProducer, TELEMETRY_STORAGE_NOOP, flagSetsFilter, + ruleBasedSegmentParser, ruleBasedSegmentCacheProducer); FetchResult fetchResult = splitFetcher.forceRefresh(fetchOptions); @@ -55,11 +61,15 @@ public void testLocalHostFlagSets() throws IOException { InputStreamProvider inputStreamProvider = new FileInputStreamProvider(file.getAbsolutePath()); FlagSetsFilter flagSetsFilter = new FlagSetsFilterImpl(new HashSet<>(Arrays.asList("set_1"))); SplitCacheProducer splitCacheProducer = new InMemoryCacheImp(flagSetsFilter); + RuleBasedSegmentCacheProducer ruleBasedSegmentCacheProducer = new RuleBasedSegmentCacheInMemoryImp(); + RuleBasedSegmentParser ruleBasedSegmentParser = new RuleBasedSegmentParser(); + Spec.SPEC_VERSION = Spec.SPEC_1_1; // check old spec SplitChangeFetcher splitChangeFetcher = new JsonLocalhostSplitChangeFetcher(inputStreamProvider); SplitParser splitParser = new SplitParser(); FetchOptions fetchOptions = new FetchOptions.Builder().build(); - SplitFetcher splitFetcher = new SplitFetcherImp(splitChangeFetcher, splitParser, splitCacheProducer, TELEMETRY_STORAGE_NOOP, flagSetsFilter); + SplitFetcher splitFetcher = new SplitFetcherImp(splitChangeFetcher, splitParser, splitCacheProducer, TELEMETRY_STORAGE_NOOP, flagSetsFilter, + ruleBasedSegmentParser, ruleBasedSegmentCacheProducer); FetchResult fetchResult = splitFetcher.forceRefresh(fetchOptions); @@ -76,11 +86,15 @@ public void testLocalHostFlagSetsNotIntersect() throws IOException { InputStreamProvider inputStreamProvider = new FileInputStreamProvider(file.getAbsolutePath()); FlagSetsFilter flagSetsFilter = new FlagSetsFilterImpl(new HashSet<>(Arrays.asList("set_4"))); SplitCacheProducer splitCacheProducer = new InMemoryCacheImp(flagSetsFilter); + RuleBasedSegmentCacheProducer ruleBasedSegmentCacheProducer = new RuleBasedSegmentCacheInMemoryImp(); + RuleBasedSegmentParser ruleBasedSegmentParser = new RuleBasedSegmentParser(); + Spec.SPEC_VERSION = Spec.SPEC_1_1; // check old spec SplitChangeFetcher splitChangeFetcher = new JsonLocalhostSplitChangeFetcher(inputStreamProvider); SplitParser splitParser = new SplitParser(); FetchOptions fetchOptions = new FetchOptions.Builder().build(); - SplitFetcher splitFetcher = new SplitFetcherImp(splitChangeFetcher, splitParser, splitCacheProducer, TELEMETRY_STORAGE_NOOP, flagSetsFilter); + SplitFetcher splitFetcher = new SplitFetcherImp(splitChangeFetcher, splitParser, splitCacheProducer, TELEMETRY_STORAGE_NOOP, flagSetsFilter, + ruleBasedSegmentParser, ruleBasedSegmentCacheProducer); FetchResult fetchResult = splitFetcher.forceRefresh(fetchOptions); diff --git a/client/src/test/java/io/split/engine/experiments/SplitFetcherTest.java b/client/src/test/java/io/split/engine/experiments/SplitFetcherTest.java index 00078bb9b..fb7167ec8 100644 --- a/client/src/test/java/io/split/engine/experiments/SplitFetcherTest.java +++ b/client/src/test/java/io/split/engine/experiments/SplitFetcherTest.java @@ -1,10 +1,13 @@ package io.split.engine.experiments; import com.google.common.collect.Lists; +import io.split.Spec; import io.split.client.interceptors.FlagSetsFilter; import io.split.client.interceptors.FlagSetsFilterImpl; +import io.split.storages.RuleBasedSegmentCacheProducer; import io.split.storages.memory.InMemoryCacheImp; import io.split.storages.SegmentCache; +import io.split.storages.memory.RuleBasedSegmentCacheInMemoryImp; import io.split.storages.memory.SegmentCacheInMemoryImpl; import io.split.storages.SplitCache; import io.split.client.dtos.*; @@ -71,7 +74,12 @@ public void worksWhenWeStartWithAnyState() throws InterruptedException { private void works(long startingChangeNumber) throws InterruptedException { AChangePerCallSplitChangeFetcher splitChangeFetcher = new AChangePerCallSplitChangeFetcher(); SplitCache cache = new InMemoryCacheImp(startingChangeNumber, FLAG_SETS_FILTER); - SplitFetcherImp fetcher = new SplitFetcherImp(splitChangeFetcher, new SplitParser(), cache, TELEMETRY_STORAGE, FLAG_SETS_FILTER); + RuleBasedSegmentCacheProducer ruleBasedSegmentCacheProducer = new RuleBasedSegmentCacheInMemoryImp(); + RuleBasedSegmentParser ruleBasedSegmentParser = new RuleBasedSegmentParser(); + Spec.SPEC_VERSION = Spec.SPEC_1_1; // check old spec + + SplitFetcherImp fetcher = new SplitFetcherImp(splitChangeFetcher, new SplitParser(), cache, TELEMETRY_STORAGE, FLAG_SETS_FILTER, + ruleBasedSegmentParser, ruleBasedSegmentCacheProducer); // execute the fetcher for a little bit. executeWaitAndTerminate(fetcher, 1, 3, TimeUnit.SECONDS); @@ -132,17 +140,22 @@ public void whenParserFailsWeRemoveTheExperiment() throws InterruptedException { noReturn.till = 1L; SplitChangeFetcher splitChangeFetcher = mock(SplitChangeFetcher.class); - when(splitChangeFetcher.fetch(Mockito.eq(-1L), Mockito.any())).thenReturn(validReturn); - when(splitChangeFetcher.fetch(Mockito.eq(0L), Mockito.any())).thenReturn(invalidReturn); - when(splitChangeFetcher.fetch(Mockito.eq(1L), Mockito.any())).thenReturn(noReturn); + when(splitChangeFetcher.fetch(Mockito.eq(-1L), Mockito.eq(-1L), Mockito.any())).thenReturn(validReturn); + when(splitChangeFetcher.fetch(Mockito.eq(0L), Mockito.eq(-1L), Mockito.any())).thenReturn(invalidReturn); + when(splitChangeFetcher.fetch(Mockito.eq(1L), Mockito.eq(-1L), Mockito.any())).thenReturn(noReturn); SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); SplitCache cache = new InMemoryCacheImp(-1, FLAG_SETS_FILTER); + RuleBasedSegmentCacheProducer ruleBasedSegmentCacheProducer = new RuleBasedSegmentCacheInMemoryImp(); + RuleBasedSegmentParser ruleBasedSegmentParser = new RuleBasedSegmentParser(); + Spec.SPEC_VERSION = Spec.SPEC_1_1; // check old spec SegmentChangeFetcher segmentChangeFetcher = mock(SegmentChangeFetcher.class); SegmentSynchronizationTask segmentSynchronizationTask = new SegmentSynchronizationTaskImp(segmentChangeFetcher, 1,10, segmentCache, TELEMETRY_STORAGE, cache, null); segmentSynchronizationTask.start(); - SplitFetcherImp fetcher = new SplitFetcherImp(splitChangeFetcher, new SplitParser(), cache, TELEMETRY_STORAGE, FLAG_SETS_FILTER); + SplitFetcherImp fetcher = new SplitFetcherImp(splitChangeFetcher, new SplitParser(), cache, TELEMETRY_STORAGE, FLAG_SETS_FILTER, + ruleBasedSegmentParser, ruleBasedSegmentCacheProducer); + // execute the fetcher for a little bit. executeWaitAndTerminate(fetcher, 1, 5, TimeUnit.SECONDS); @@ -157,13 +170,16 @@ public void ifThereIsAProblemTalkingToSplitChangeCountDownLatchIsNotDecremented( SplitCache cache = new InMemoryCacheImp(-1, FLAG_SETS_FILTER); SplitChangeFetcher splitChangeFetcher = mock(SplitChangeFetcher.class); - when(splitChangeFetcher.fetch(-1L, new FetchOptions.Builder().build())).thenThrow(new RuntimeException()); + when(splitChangeFetcher.fetch(-1L, -1, new FetchOptions.Builder().build())).thenThrow(new RuntimeException()); SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); + RuleBasedSegmentCacheProducer ruleBasedSegmentCacheProducer = new RuleBasedSegmentCacheInMemoryImp(); + RuleBasedSegmentParser ruleBasedSegmentParser = new RuleBasedSegmentParser(); SegmentChangeFetcher segmentChangeFetcher = mock(SegmentChangeFetcher.class); SegmentSynchronizationTask segmentSynchronizationTask = new SegmentSynchronizationTaskImp(segmentChangeFetcher, 1,10, segmentCache, TELEMETRY_STORAGE, cache, null); segmentSynchronizationTask.start(); - SplitFetcherImp fetcher = new SplitFetcherImp(splitChangeFetcher, new SplitParser(), cache, TELEMETRY_STORAGE, FLAG_SETS_FILTER); + SplitFetcherImp fetcher = new SplitFetcherImp(splitChangeFetcher, new SplitParser(), cache, TELEMETRY_STORAGE, FLAG_SETS_FILTER, + ruleBasedSegmentParser, ruleBasedSegmentCacheProducer); // execute the fetcher for a little bit. executeWaitAndTerminate(fetcher, 1, 5, TimeUnit.SECONDS); @@ -191,10 +207,13 @@ public void addFeatureFlags() throws InterruptedException { validReturn.till = 0L; SplitChangeFetcher splitChangeFetcher = mock(SplitChangeFetcher.class); - when(splitChangeFetcher.fetch(Mockito.eq(-1L), Mockito.any())).thenReturn(validReturn); + when(splitChangeFetcher.fetch(Mockito.eq(-1L), Mockito.eq(-1L), Mockito.any())).thenReturn(validReturn); + RuleBasedSegmentCacheProducer ruleBasedSegmentCacheProducer = new RuleBasedSegmentCacheInMemoryImp(); + RuleBasedSegmentParser ruleBasedSegmentParser = new RuleBasedSegmentParser(); FlagSetsFilter flagSetsFilter = new FlagSetsFilterImpl(new HashSet<>(Arrays.asList("set_1", "set_2"))); - SplitFetcherImp fetcher = new SplitFetcherImp(splitChangeFetcher, new SplitParser(), cache, TELEMETRY_STORAGE, flagSetsFilter); + SplitFetcherImp fetcher = new SplitFetcherImp(splitChangeFetcher, new SplitParser(), cache, TELEMETRY_STORAGE, flagSetsFilter, + ruleBasedSegmentParser, ruleBasedSegmentCacheProducer); executeWaitAndTerminate(fetcher, 1, 5, TimeUnit.SECONDS); @@ -208,7 +227,7 @@ public void addFeatureFlags() throws InterruptedException { validReturn.since = 0L; validReturn.till = 1L; - when(splitChangeFetcher.fetch(Mockito.eq(0L), Mockito.any())).thenReturn(validReturn); + when(splitChangeFetcher.fetch(Mockito.eq(0L), Mockito.eq(-1L), Mockito.any())).thenReturn(validReturn); executeWaitAndTerminate(fetcher, 1, 5, TimeUnit.SECONDS); @@ -243,13 +262,16 @@ public void worksWithUserDefinedSegments() throws Exception { AChangePerCallSplitChangeFetcher experimentChangeFetcher = new AChangePerCallSplitChangeFetcher(segmentName); SplitCache cache = new InMemoryCacheImp(startingChangeNumber, FLAG_SETS_FILTER); SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); + RuleBasedSegmentCacheProducer ruleBasedSegmentCacheProducer = new RuleBasedSegmentCacheInMemoryImp(); + RuleBasedSegmentParser ruleBasedSegmentParser = new RuleBasedSegmentParser(); SegmentChangeFetcher segmentChangeFetcher = mock(SegmentChangeFetcher.class); SegmentChange segmentChange = getSegmentChange(0L, 0L, segmentName); when(segmentChangeFetcher.fetch(anyString(), anyLong(), any())).thenReturn(segmentChange); SegmentSynchronizationTask segmentSynchronizationTask = new SegmentSynchronizationTaskImp(segmentChangeFetcher, 1,10, segmentCache, Mockito.mock(TelemetryStorage.class), cache, null); segmentSynchronizationTask.start(); - SplitFetcherImp fetcher = new SplitFetcherImp(experimentChangeFetcher, new SplitParser(), cache, TELEMETRY_STORAGE, FLAG_SETS_FILTER); + SplitFetcherImp fetcher = new SplitFetcherImp(experimentChangeFetcher, new SplitParser(), cache, TELEMETRY_STORAGE, FLAG_SETS_FILTER, + ruleBasedSegmentParser, ruleBasedSegmentCacheProducer); // execute the fetcher for a little bit. executeWaitAndTerminate(fetcher, 1, 5, TimeUnit.SECONDS); @@ -269,8 +291,11 @@ public void testBypassCdnClearedAfterFirstHit() { SplitChangeFetcher mockFetcher = Mockito.mock(SplitChangeFetcher.class); SplitParser mockParser = new SplitParser(); SplitCache mockCache = new InMemoryCacheImp(FLAG_SETS_FILTER); - SplitFetcherImp fetcher = new SplitFetcherImp(mockFetcher, mockParser, mockCache, Mockito.mock(TelemetryRuntimeProducer.class), FLAG_SETS_FILTER); - + RuleBasedSegmentCacheProducer ruleBasedSegmentCacheProducer = new RuleBasedSegmentCacheInMemoryImp(); + RuleBasedSegmentParser ruleBasedSegmentParser = new RuleBasedSegmentParser(); + Spec.SPEC_VERSION = Spec.SPEC_1_1; // check old spec + SplitFetcherImp fetcher = new SplitFetcherImp(mockFetcher, mockParser, mockCache, Mockito.mock(TelemetryRuntimeProducer.class), FLAG_SETS_FILTER, + ruleBasedSegmentParser, ruleBasedSegmentCacheProducer); SplitChange response1 = new SplitChange(); response1.splits = new ArrayList<>(); @@ -285,9 +310,10 @@ public void testBypassCdnClearedAfterFirstHit() { ArgumentCaptor optionsCaptor = ArgumentCaptor.forClass(FetchOptions.class); ArgumentCaptor cnCaptor = ArgumentCaptor.forClass(Long.class); - when(mockFetcher.fetch(cnCaptor.capture(), optionsCaptor.capture())).thenReturn(response1, response2); + ArgumentCaptor rbsCnCaptor = ArgumentCaptor.forClass(Long.class); + when(mockFetcher.fetch(cnCaptor.capture(), rbsCnCaptor.capture(), optionsCaptor.capture())).thenReturn(response1, response2); - FetchOptions originalOptions = new FetchOptions.Builder().targetChangeNumber(123).build(); + FetchOptions originalOptions = new FetchOptions.Builder().targetChangeNumber(123).targetChangeNumberRBS(-1).build(); fetcher.forceRefresh(originalOptions); List capturedCNs = cnCaptor.getAllValues(); List capturedOptions = optionsCaptor.getAllValues(); diff --git a/client/src/test/java/io/split/engine/experiments/SplitSynchronizationTaskTest.java b/client/src/test/java/io/split/engine/experiments/SplitSynchronizationTaskTest.java index ca7ceab77..ce04294cb 100644 --- a/client/src/test/java/io/split/engine/experiments/SplitSynchronizationTaskTest.java +++ b/client/src/test/java/io/split/engine/experiments/SplitSynchronizationTaskTest.java @@ -4,8 +4,10 @@ import io.split.client.interceptors.FlagSetsFilter; import io.split.client.interceptors.FlagSetsFilterImpl; import io.split.engine.common.FetchOptions; +import io.split.storages.RuleBasedSegmentCacheProducer; import io.split.storages.SplitCacheProducer; import io.split.storages.memory.InMemoryCacheImp; +import io.split.storages.memory.RuleBasedSegmentCacheInMemoryImp; import io.split.telemetry.storage.NoopTelemetryStorage; import io.split.telemetry.storage.TelemetryStorage; import org.junit.Test; @@ -25,7 +27,10 @@ public void testLocalhost() throws InterruptedException { SplitChangeFetcher splitChangeFetcher = Mockito.mock(JsonLocalhostSplitChangeFetcher.class); SplitParser splitParser = new SplitParser(); FetchOptions fetchOptions = new FetchOptions.Builder().build(); - SplitFetcher splitFetcher = new SplitFetcherImp(splitChangeFetcher, splitParser, splitCacheProducer, TELEMETRY_STORAGE_NOOP, FLAG_SETS_FILTER); + RuleBasedSegmentCacheProducer ruleBasedSegmentCacheProducer = new RuleBasedSegmentCacheInMemoryImp(); + RuleBasedSegmentParser ruleBasedSegmentParser = new RuleBasedSegmentParser(); + SplitFetcher splitFetcher = new SplitFetcherImp(splitChangeFetcher, splitParser, splitCacheProducer, TELEMETRY_STORAGE_NOOP, FLAG_SETS_FILTER, + ruleBasedSegmentParser, ruleBasedSegmentCacheProducer); SplitSynchronizationTask splitSynchronizationTask = new SplitSynchronizationTask(splitFetcher, splitCacheProducer, 1000, null); @@ -33,7 +38,7 @@ public void testLocalhost() throws InterruptedException { Thread.sleep(2000); - Mockito.verify(splitChangeFetcher, Mockito.times(1)).fetch(-1, fetchOptions); + Mockito.verify(splitChangeFetcher, Mockito.times(1)).fetch(-1, -1, fetchOptions); } @Test diff --git a/client/src/test/java/io/split/engine/matchers/NegatableMatcherTest.java b/client/src/test/java/io/split/engine/matchers/NegatableMatcherTest.java index 97e4aebe2..f80f38739 100644 --- a/client/src/test/java/io/split/engine/matchers/NegatableMatcherTest.java +++ b/client/src/test/java/io/split/engine/matchers/NegatableMatcherTest.java @@ -4,7 +4,9 @@ import io.split.engine.evaluator.EvaluationContext; import io.split.engine.evaluator.Evaluator; import io.split.engine.matchers.strings.WhitelistMatcher; +import io.split.storages.RuleBasedSegmentCache; import io.split.storages.SegmentCache; +import io.split.storages.memory.RuleBasedSegmentCacheInMemoryImp; import io.split.storages.memory.SegmentCacheInMemoryImpl; import org.junit.Assert; import org.junit.Test; @@ -26,19 +28,20 @@ public void worksAllKeys() { AllKeysMatcher delegate = new AllKeysMatcher(); AttributeMatcher.NegatableMatcher matcher = new AttributeMatcher.NegatableMatcher(delegate, true); - test(matcher, "foo", false, Mockito.mock(SegmentCache.class)); + test(matcher, "foo", false, Mockito.mock(SegmentCache.class), Mockito.mock(RuleBasedSegmentCache.class)); } @Test public void worksSegment() { SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); + RuleBasedSegmentCache ruleBasedSegmentCache = new RuleBasedSegmentCacheInMemoryImp(); segmentCache.updateSegment("foo", Stream.of("a","b").collect(Collectors.toList()), new ArrayList<>(), 1L); UserDefinedSegmentMatcher delegate = new UserDefinedSegmentMatcher("foo"); AttributeMatcher.NegatableMatcher matcher = new AttributeMatcher.NegatableMatcher(delegate, true); - test(matcher, "a", false, segmentCache); - test(matcher, "b", false, segmentCache); - test(matcher, "c", true, segmentCache); + test(matcher, "a", false, segmentCache, ruleBasedSegmentCache); + test(matcher, "b", false, segmentCache, ruleBasedSegmentCache); + test(matcher, "c", true, segmentCache, ruleBasedSegmentCache); } @Test @@ -46,14 +49,14 @@ public void worksWhitelist() { WhitelistMatcher delegate = new WhitelistMatcher(Lists.newArrayList("a", "b")); AttributeMatcher.NegatableMatcher matcher = new AttributeMatcher.NegatableMatcher(delegate, true); - test(matcher, "a", false, Mockito.mock(SegmentCache.class)); - test(matcher, "b", false, Mockito.mock(SegmentCache.class)); - test(matcher, "c", true, Mockito.mock(SegmentCache.class)); + test(matcher, "a", false, Mockito.mock(SegmentCache.class), Mockito.mock(RuleBasedSegmentCache.class)); + test(matcher, "b", false, Mockito.mock(SegmentCache.class), Mockito.mock(RuleBasedSegmentCache.class)); + test(matcher, "c", true, Mockito.mock(SegmentCache.class), Mockito.mock(RuleBasedSegmentCache.class)); } - private void test(AttributeMatcher.NegatableMatcher negationMatcher, String key, boolean expected, SegmentCache segmentCache) { - Assert.assertEquals(expected, negationMatcher.match(key, null, null, new EvaluationContext(Mockito.mock(Evaluator.class), segmentCache))); - Assert.assertNotEquals(expected, negationMatcher.delegate().match(key, null, null, new EvaluationContext(Mockito.mock(Evaluator.class), segmentCache))); + private void test(AttributeMatcher.NegatableMatcher negationMatcher, String key, boolean expected, SegmentCache segmentCache, RuleBasedSegmentCache ruleBasedSegmentCache) { + Assert.assertEquals(expected, negationMatcher.match(key, null, null, new EvaluationContext(Mockito.mock(Evaluator.class), segmentCache, ruleBasedSegmentCache))); + Assert.assertNotEquals(expected, negationMatcher.delegate().match(key, null, null, new EvaluationContext(Mockito.mock(Evaluator.class), segmentCache, ruleBasedSegmentCache))); } diff --git a/client/src/test/java/io/split/engine/matchers/UserDefinedSegmentMatcherTest.java b/client/src/test/java/io/split/engine/matchers/UserDefinedSegmentMatcherTest.java index 0d59e3c54..b957f73d0 100644 --- a/client/src/test/java/io/split/engine/matchers/UserDefinedSegmentMatcherTest.java +++ b/client/src/test/java/io/split/engine/matchers/UserDefinedSegmentMatcherTest.java @@ -3,7 +3,10 @@ import com.google.common.collect.Sets; import io.split.engine.evaluator.EvaluationContext; import io.split.engine.evaluator.Evaluator; +import io.split.storages.RuleBasedSegmentCacheConsumer; +import io.split.storages.RuleBasedSegmentCacheProducer; import io.split.storages.SegmentCache; +import io.split.storages.memory.RuleBasedSegmentCacheInMemoryImp; import io.split.storages.memory.SegmentCacheInMemoryImpl; import org.junit.Test; import org.mockito.Mockito; @@ -27,7 +30,8 @@ public void works() { Set keys = Sets.newHashSet("a", "b"); Evaluator evaluator = Mockito.mock(Evaluator.class); SegmentCache segmentCache = new SegmentCacheInMemoryImpl(); - EvaluationContext evaluationContext = new EvaluationContext(evaluator, segmentCache); + RuleBasedSegmentCacheConsumer ruleBasedSegmentCacheConsumer = new RuleBasedSegmentCacheInMemoryImp(); + EvaluationContext evaluationContext = new EvaluationContext(evaluator, segmentCache, ruleBasedSegmentCacheConsumer); segmentCache.updateSegment("foo", Stream.of("a","b").collect(Collectors.toList()), new ArrayList<>(), 1L); UserDefinedSegmentMatcher matcher = new UserDefinedSegmentMatcher("foo"); diff --git a/client/src/test/java/io/split/engine/segments/SegmentSynchronizationTaskImpTest.java b/client/src/test/java/io/split/engine/segments/SegmentSynchronizationTaskImpTest.java index 027d09f5f..494c99c53 100644 --- a/client/src/test/java/io/split/engine/segments/SegmentSynchronizationTaskImpTest.java +++ b/client/src/test/java/io/split/engine/segments/SegmentSynchronizationTaskImpTest.java @@ -1,6 +1,7 @@ package io.split.engine.segments; import com.google.common.collect.Maps; +import io.split.Spec; import io.split.client.LocalhostSegmentChangeFetcher; import io.split.client.JsonLocalhostSplitChangeFetcher; import io.split.client.interceptors.FlagSetsFilter; @@ -8,15 +9,13 @@ import io.split.client.utils.InputStreamProvider; import io.split.client.utils.StaticContentInputStreamProvider; import io.split.engine.common.FetchOptions; -import io.split.engine.experiments.SplitChangeFetcher; -import io.split.engine.experiments.SplitFetcher; -import io.split.engine.experiments.SplitFetcherImp; -import io.split.engine.experiments.SplitParser; -import io.split.engine.experiments.SplitSynchronizationTask; +import io.split.engine.experiments.*; +import io.split.storages.RuleBasedSegmentCacheProducer; import io.split.storages.SegmentCacheProducer; import io.split.storages.SplitCache; import io.split.storages.SplitCacheConsumer; import io.split.storages.memory.InMemoryCacheImp; +import io.split.storages.memory.RuleBasedSegmentCacheInMemoryImp; import io.split.storages.memory.SegmentCacheInMemoryImpl; import io.split.telemetry.storage.InMemoryTelemetryStorage; import io.split.telemetry.storage.NoopTelemetryStorage; @@ -162,9 +161,14 @@ public void testLocalhostSegmentChangeFetcher() throws InterruptedException, Fil SplitChangeFetcher splitChangeFetcher = new JsonLocalhostSplitChangeFetcher(inputStreamProvider); SplitParser splitParser = new SplitParser(); FetchOptions fetchOptions = new FetchOptions.Builder().build(); - SplitFetcher splitFetcher = new SplitFetcherImp(splitChangeFetcher, splitParser, splitCacheProducer, TELEMETRY_STORAGE_NOOP, flagSetsFilter); + RuleBasedSegmentCacheProducer ruleBasedSegmentCacheProducer = new RuleBasedSegmentCacheInMemoryImp(); + RuleBasedSegmentParser ruleBasedSegmentParser = new RuleBasedSegmentParser(); + + SplitFetcher splitFetcher = new SplitFetcherImp(splitChangeFetcher, splitParser, splitCacheProducer, TELEMETRY_STORAGE_NOOP, flagSetsFilter, + ruleBasedSegmentParser, ruleBasedSegmentCacheProducer); SplitSynchronizationTask splitSynchronizationTask = new SplitSynchronizationTask(splitFetcher, splitCacheProducer, 1000, null); + Spec.SPEC_VERSION = Spec.SPEC_1_1; // check old spec splitSynchronizationTask.start(); diff --git a/client/src/test/java/io/split/inputValidation/ImpressionPropertiesValidatorTest.java b/client/src/test/java/io/split/inputValidation/ImpressionPropertiesValidatorTest.java new file mode 100644 index 000000000..e5ae96b87 --- /dev/null +++ b/client/src/test/java/io/split/inputValidation/ImpressionPropertiesValidatorTest.java @@ -0,0 +1,36 @@ +package io.split.inputValidation; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import io.split.grammar.Treatments; +import org.junit.Assert; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +public class ImpressionPropertiesValidatorTest { + @Test + public void propertiesAreValidWorks() { + Map properties = new HashMap() + {{ + put("prop1", 1); + put("prop2", 2L); + put("prop3", 7.56); + put("prop4", "something"); + put("prop5", true); + put("prop6", null); + }}; + ImpressionPropertiesValidator.ImpressionPropertiesValidatorResult result = ImpressionPropertiesValidator.propertiesAreValid(properties); + Assert.assertTrue(result.getSuccess()); + Assert.assertEquals(1063, result.getEventSize()); + Assert.assertEquals(6, result.getValue().size()); + + // when properties size is > Event.MAX_PROPERTIES_LENGTH_BYTES + for (int i = 7; i <= (32 * 1024); i++) { + properties.put("prop" + i, "something-" + i); + } + result = ImpressionPropertiesValidator.propertiesAreValid(properties); + Assert.assertFalse(result.getSuccess()); + } +} diff --git a/client/src/test/java/io/split/service/HttpSplitClientTest.java b/client/src/test/java/io/split/service/HttpSplitClientTest.java index 4d18a080d..ec8cd5e52 100644 --- a/client/src/test/java/io/split/service/HttpSplitClientTest.java +++ b/client/src/test/java/io/split/service/HttpSplitClientTest.java @@ -112,13 +112,13 @@ public void testPost() throws URISyntaxException, IOException, IllegalAccessExce // Send impressions List toSend = Arrays.asList(new TestImpressions("t1", Arrays.asList( - KeyImpression.fromImpression(new Impression("k1", null, "t1", "on", 123L, "r1", 456L, null)), - KeyImpression.fromImpression(new Impression("k2", null, "t1", "on", 123L, "r1", 456L, null)), - KeyImpression.fromImpression(new Impression("k3", null, "t1", "on", 123L, "r1", 456L, null)))), + KeyImpression.fromImpression(new Impression("k1", null, "t1", "on", 123L, "r1", 456L, null, null)), + KeyImpression.fromImpression(new Impression("k2", null, "t1", "on", 123L, "r1", 456L, null, null)), + KeyImpression.fromImpression(new Impression("k3", null, "t1", "on", 123L, "r1", 456L, null, null)))), new TestImpressions("t2", Arrays.asList( - KeyImpression.fromImpression(new Impression("k1", null, "t2", "on", 123L, "r1", 456L, null)), - KeyImpression.fromImpression(new Impression("k2", null, "t2", "on", 123L, "r1", 456L, null)), - KeyImpression.fromImpression(new Impression("k3", null, "t2", "on", 123L, "r1", 456L, null))))); + KeyImpression.fromImpression(new Impression("k1", null, "t2", "on", 123L, "r1", 456L, null, null)), + KeyImpression.fromImpression(new Impression("k2", null, "t2", "on", 123L, "r1", 456L, null, null)), + KeyImpression.fromImpression(new Impression("k3", null, "t2", "on", 123L, "r1", 456L, null, null))))); Map> additionalHeaders = Collections.singletonMap("SplitSDKImpressionsMode", Collections.singletonList("OPTIMIZED")); diff --git a/testing/src/main/java/io/split/client/testing/SplitClientForTest.java b/testing/src/main/java/io/split/client/testing/SplitClientForTest.java index 0c9173457..2ddb13b1f 100644 --- a/testing/src/main/java/io/split/client/testing/SplitClientForTest.java +++ b/testing/src/main/java/io/split/client/testing/SplitClientForTest.java @@ -3,12 +3,13 @@ import io.split.client.SplitClient; import io.split.client.api.Key; import io.split.client.api.SplitResult; +import io.split.client.dtos.EvaluationOptions; import io.split.grammar.Treatments; +import io.split.telemetry.domain.enums.MethodEnum; -import java.util.HashMap; -import java.util.Map; -import java.util.List; +import java.util.*; import java.util.concurrent.TimeoutException; +import java.util.stream.Collectors; public class SplitClientForTest implements SplitClient { private Map _tests; @@ -192,6 +193,140 @@ public Map getTreatmentsWithConfigByFlagSets(Key key, List< return new HashMap<>(); } + @Override + public String getTreatment(String key, String featureFlagName, EvaluationOptions evaluationOptions) { + return null; + } + + @Override + public String getTreatment(String key, String featureFlagName, Map attributes, EvaluationOptions evaluationOptions) { + return null; + } + + @Override + public String getTreatment(Key key, String featureFlagName, Map attributes, EvaluationOptions evaluationOptions) { + return null; + } + + @Override + public Map getTreatments(String key, List featureFlagNames, EvaluationOptions evaluationOptions) { + return new HashMap<>(); + } + + @Override + public Map getTreatments(String key, List featureFlagNames, Map attributes, + EvaluationOptions evaluationOptions) { + return new HashMap<>(); + } + + @Override + public Map getTreatments(Key key, List featureFlagNames, Map attributes, + EvaluationOptions evaluationOptions) { + return new HashMap<>(); + } + + @Override + public SplitResult getTreatmentWithConfig(String key, String featureFlagName, EvaluationOptions evaluationOptions) { + return null; + } + + @Override + public SplitResult getTreatmentWithConfig(Key key, String featureFlagName, Map attributes, + EvaluationOptions evaluationOptions) { + return null; + } + + @Override + public SplitResult getTreatmentWithConfig(String key, String featureFlagName, Map attributes, + EvaluationOptions evaluationOptions) { + return null; + } + + @Override + public Map getTreatmentsWithConfig(String key, List featureFlagNames, Map attributes, + EvaluationOptions evaluationOptions) { + return new HashMap<>(); + } + + @Override + public Map getTreatmentsWithConfig(String key, List featureFlagNames, EvaluationOptions evaluationOptions) { + return new HashMap<>(); + } + + @Override + public Map getTreatmentsByFlagSet(String key, String flagSet, Map attributes, + EvaluationOptions evaluationOptions) { + return new HashMap<>(); + } + + @Override + public Map getTreatmentsByFlagSets(String key, List flagSets, EvaluationOptions evaluationOptions) { + return new HashMap<>(); + } + + @Override + public Map getTreatmentsByFlagSets(String key, List flagSets, Map attributes, + EvaluationOptions evaluationOptions) { + return new HashMap<>(); + } + + @Override + public Map getTreatmentsWithConfigByFlagSet(String key, String flagSet, EvaluationOptions evaluationOptions) { + return new HashMap<>(); + } + + @Override + public Map getTreatmentsWithConfigByFlagSet(String key, String flagSet, Map attributes, + EvaluationOptions evaluationOptions) { + return new HashMap<>(); + } + + @Override + public Map getTreatmentsWithConfigByFlagSets(String key, List flagSets, EvaluationOptions evaluationOptions) { + return new HashMap<>(); + } + + @Override + public Map getTreatmentsWithConfigByFlagSets(String key, List flagSets, Map attributes, + EvaluationOptions evaluationOptions) { + return new HashMap<>(); + } + + @Override + public Map getTreatmentsByFlagSet(String key, String flagSet, EvaluationOptions evaluationOptions) { + return new HashMap<>(); + } + + @Override + public Map getTreatmentsWithConfig(Key key, List featureFlagNames, Map attributes, + EvaluationOptions evaluationOptions) { + return new HashMap<>(); + } + + @Override + public Map getTreatmentsByFlagSet(Key key, String flagSet, Map attributes, + EvaluationOptions evaluationOptions) { + return new HashMap<>(); + } + + @Override + public Map getTreatmentsByFlagSets(Key key, List flagSets, Map attributes, + EvaluationOptions evaluationOptions) { + return new HashMap<>(); + } + + @Override + public Map getTreatmentsWithConfigByFlagSet(Key key, String flagSet, Map attributes, + EvaluationOptions evaluationOptions) { + return new HashMap<>(); + } + + @Override + public Map getTreatmentsWithConfigByFlagSets(Key key, List flagSets, Map attributes, + EvaluationOptions evaluationOptions) { + return new HashMap<>(); + } + @Override public void destroy() {