diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4e27f2a..c2e1758 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,6 +13,8 @@ jobs: steps: - uses: actions/checkout@v3 + with: + submodules: 'recursive' - name: Set up JDK 11 uses: actions/setup-java@v3 with: diff --git a/.gitmodules b/.gitmodules index bf7d22b..478d3d6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,4 @@ -[submodule "src/test/resources/test/server-sdk-specification"] - path = src/test/resources/test/server-sdk-specification - url = https://github.com/FeatureProbe/server-sdk-specification.git + +[submodule "src/test/resources/test"] + path = src/test/resources/test + url = git@github.com:FeatureProbe/server-sdk-specification.git diff --git a/pom.xml b/pom.xml index c73b298..6290496 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.featureprobe server-sdk-java - 2.1.0-SNAPSHOT + 2.1.0 server-sdk-java FeatureProbe Server Side SDK for Java @@ -218,6 +218,9 @@ **/*.md + + src/test/resources/test/** + @@ -297,6 +300,21 @@ + + org.apache.maven.plugins + maven-dependency-plugin + 3.3.0 + + + analyze-only-in-package + + analyze-only + + package + + + + org.apache.maven.plugins maven-javadoc-plugin @@ -330,34 +348,6 @@ - - org.apache.maven.plugins - maven-assembly-plugin - 3.4.1 - - false - - jar-with-dependencies - - - - true - lib/ - com.featureprobe.sdk.example.FeatureProbeDemo - - - - - - make-assembly - - single - - package - - - - diff --git a/src/main/java/com/featureprobe/sdk/server/FPConfig.java b/src/main/java/com/featureprobe/sdk/server/FPConfig.java index 840574b..023e8d5 100644 --- a/src/main/java/com/featureprobe/sdk/server/FPConfig.java +++ b/src/main/java/com/featureprobe/sdk/server/FPConfig.java @@ -32,6 +32,8 @@ public final class FPConfig { static final Long DEFAULT_START_WAIT = TimeUnit.SECONDS.toNanos(5); + static final Integer DEFAULT_MAX_DEPENDENT_DEEP = 20; + protected static final FPConfig DEFAULT = new Builder().build(); final Duration refreshInterval; @@ -40,6 +42,8 @@ public final class FPConfig { final URI remoteUri; + final Integer prerequisiteDeep; + URL synchronizerUrl; URL eventUrl; @@ -71,6 +75,8 @@ protected FPConfig(Builder builder) { this.eventUrl = builder.eventUrl; this.realtimeUri = builder.realtimeUri; this.startWait = builder.startWait == null ? DEFAULT_START_WAIT : builder.startWait; + this.prerequisiteDeep = + builder.prerequisiteDeep == null ? DEFAULT_MAX_DEPENDENT_DEEP : builder.prerequisiteDeep; } public static Builder builder() { @@ -99,6 +105,8 @@ public static class Builder { private Long startWait; + private Integer prerequisiteDeep; + public Builder() { } @@ -177,6 +185,11 @@ public Builder startWait(Long startWaitTime, TimeUnit unit) { return this; } + public Builder prerequisiteDeep(Integer prerequisiteDeep) { + this.prerequisiteDeep = prerequisiteDeep; + return this; + } + public FPConfig build() { return new FPConfig(this); } diff --git a/src/main/java/com/featureprobe/sdk/server/FeatureProbe.java b/src/main/java/com/featureprobe/sdk/server/FeatureProbe.java index 682deed..ed5cfcc 100644 --- a/src/main/java/com/featureprobe/sdk/server/FeatureProbe.java +++ b/src/main/java/com/featureprobe/sdk/server/FeatureProbe.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.featureprobe.sdk.server.exceptions.PrerequisitesDeepOverflowException; import com.featureprobe.sdk.server.model.Segment; import com.featureprobe.sdk.server.model.Toggle; import com.google.common.annotations.VisibleForTesting; @@ -58,10 +59,14 @@ public final class FeatureProbe { @VisibleForTesting EventProcessor eventProcessor; + @VisibleForTesting + FPConfig config; + @VisibleForTesting private FeatureProbe(DataRepository dataRepository) { this.dataRepository = dataRepository; FPConfig config = FPConfig.DEFAULT; + this.config = config; final FPContext context = new FPContext("test", config); eventProcessor = config.eventProcessorFactory.createEventProcessor(context); } @@ -86,6 +91,7 @@ public FeatureProbe(String serverSDKKey, FPConfig config) { throw new IllegalArgumentException("serverSDKKey must not be blank"); } final FPContext context = new FPContext(serverSDKKey, config); + this.config = config; this.eventProcessor = config.eventProcessorFactory.createEventProcessor(context); this.dataRepository = config.dataRepositoryFactory.createDataRepository(context); this.synchronizer = config.synchronizerFactory.createSynchronizer(context, dataRepository); @@ -255,8 +261,10 @@ private T jsonEvaluate(String toggleKey, FPUser user, T defaultValue, Class< try { Toggle toggle = dataRepository.getToggle(toggleKey); Map segments = dataRepository.getAllSegment(); + Map toggles = dataRepository.getAllToggle(); if (Objects.nonNull(toggle)) { - EvaluationResult evalResult = toggle.eval(user, segments, defaultValue); + EvaluationResult evalResult = toggle.eval(user, toggles, segments, defaultValue, + config.prerequisiteDeep); String value = mapper.writeValueAsString(evalResult.getValue()); eventProcessor.push(buildAccessEvent(toggle, evalResult, user)); return mapper.readValue(value, clazz); @@ -273,8 +281,10 @@ private T genericEvaluate(String toggleKey, FPUser user, T defaultValue, Cla try { Toggle toggle = dataRepository.getToggle(toggleKey); Map segments = dataRepository.getAllSegment(); + Map toggles = dataRepository.getAllToggle(); if (Objects.nonNull(toggle)) { - EvaluationResult evalResult = toggle.eval(user, segments, defaultValue); + EvaluationResult evalResult = toggle.eval(user, toggles, segments, defaultValue, + config.prerequisiteDeep); eventProcessor.push(buildAccessEvent(toggle, evalResult, user)); return clazz.cast(evalResult.getValue()); } @@ -308,6 +318,9 @@ private FPDetail genericEvaluateDetail(String toggleKey, FPUser user, T d } catch (ClassCastException | JsonProcessingException e) { logger.error(LOG_CONVERSION_ERROR, toggleKey, e); detail.setReason(REASON_TYPE_MISMATCH); + } catch (PrerequisitesDeepOverflowException e) { + logger.error(e.getMessage(), toggleKey, e); + detail.setReason(e.getMessage()); } catch (Exception e) { logger.error(LOG_HANDLE_ERROR, toggleKey, e); detail.setReason(REASON_HANDLE_ERROR); @@ -323,8 +336,10 @@ private FPDetail getEvaluateDetail(String toggleKey, FPUser user, T defau if (this.dataRepository.initialized()) { Toggle toggle = dataRepository.getToggle(toggleKey); Map segments = dataRepository.getAllSegment(); + Map toggles = dataRepository.getAllToggle(); if (Objects.nonNull(toggle)) { - EvaluationResult evalResult = toggle.eval(user, segments, defaultValue); + EvaluationResult evalResult = toggle.eval(user, toggles, segments, defaultValue, + config.prerequisiteDeep); if (isJson) { String res = mapper.writeValueAsString(evalResult.getValue()); detail.setValue(mapper.readValue(res, clazz)); diff --git a/src/main/java/com/featureprobe/sdk/server/exceptions/PrerequisitesDeepOverflowException.java b/src/main/java/com/featureprobe/sdk/server/exceptions/PrerequisitesDeepOverflowException.java new file mode 100644 index 0000000..ec6cd4a --- /dev/null +++ b/src/main/java/com/featureprobe/sdk/server/exceptions/PrerequisitesDeepOverflowException.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.featureprobe.sdk.server.exceptions; + +public class PrerequisitesDeepOverflowException extends RuntimeException { + + public PrerequisitesDeepOverflowException(String message) { + super(message); + } + +} diff --git a/src/main/java/com/featureprobe/sdk/server/model/Split.java b/src/main/java/com/featureprobe/sdk/server/model/Split.java index 7c1b791..65c4225 100644 --- a/src/main/java/com/featureprobe/sdk/server/model/Split.java +++ b/src/main/java/com/featureprobe/sdk/server/model/Split.java @@ -96,7 +96,10 @@ private int hash(String hashKey, String hashSalt, int bucketSize) { } private String getHashSalt(String toggleKey) { - return StringUtils.defaultString(salt, toggleKey); + if (StringUtils.isNotBlank(salt)) { + return salt; + } + return toggleKey; } public List>> getDistribution() { diff --git a/src/main/java/com/featureprobe/sdk/server/model/Toggle.java b/src/main/java/com/featureprobe/sdk/server/model/Toggle.java index a656b0a..a740f37 100644 --- a/src/main/java/com/featureprobe/sdk/server/model/Toggle.java +++ b/src/main/java/com/featureprobe/sdk/server/model/Toggle.java @@ -20,9 +20,11 @@ import com.featureprobe.sdk.server.EvaluationResult; import com.featureprobe.sdk.server.FPUser; import com.featureprobe.sdk.server.HitResult; +import com.featureprobe.sdk.server.exceptions.PrerequisitesDeepOverflowException; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; public final class Toggle { @@ -49,13 +51,23 @@ public final class Toggle { private Boolean forClient; - public EvaluationResult eval(FPUser user, Map segments, Object defaultValue) { + public EvaluationResult eval(FPUser user, Map toggles, Map segments, + Object defaultValue, int deep) { + String warning = ""; if (!enabled) { return createDisabledResult(user, this.key, defaultValue); } + if (deep <= 0) { + throw new PrerequisitesDeepOverflowException("prerequisite deep overflow"); + } + + if (!prerequisite(user, toggles, segments, deep)) { + return createDefaultResult(user, key, defaultValue, warning); + } + if (rules != null && rules.size() > 0) { for (int i = 0; i < rules.size(); i++) { Rule rule = rules.get(i); @@ -84,6 +96,27 @@ private EvaluationResult createDefaultResult(FPUser user, String toggleKey, Obje return defaultResult; } + private boolean prerequisite(FPUser user, Map toggles, Map segments, int deep) { + if (Objects.isNull(prerequisites) || prerequisites.isEmpty()) { + return true; + } + try { + for (Prerequisite prerequisite : prerequisites) { + Toggle toggle = toggles.get(prerequisite.getKey()); + if (Objects.isNull(toggle)) + return false; + EvaluationResult eval = toggle.eval(user, toggles, segments, null, deep - 1); + if (Objects.isNull(eval.getValue())) + return false; + if (!eval.getValue().equals(prerequisite.getValue())) + return false; + } + } catch (PrerequisitesDeepOverflowException e) { + throw e; + } + return true; + } + private EvaluationResult hitValue(HitResult hitResult, Object defaultValue, Optional ruleIndex) { EvaluationResult res = new EvaluationResult(defaultValue, ruleIndex, hitResult.getIndex(), this.version, hitResult.getReason().orElse("")); diff --git a/src/test/groovy/com/featureprobe/sdk/server/FeatureProbeSpec.groovy b/src/test/groovy/com/featureprobe/sdk/server/FeatureProbeSpec.groovy index 65ec0f9..ea854ea 100644 --- a/src/test/groovy/com/featureprobe/sdk/server/FeatureProbeSpec.groovy +++ b/src/test/groovy/com/featureprobe/sdk/server/FeatureProbeSpec.groovy @@ -10,7 +10,7 @@ import java.nio.charset.Charset class FeatureProbeSpec extends Specification { - def test_data_local = "test/server-sdk-specification/spec/toggle_simple_spec.json"; + def test_data_local = "test/spec/toggle_simple_spec.json"; def FeatureProbe featureProbe def ObjectMapper mapper def JsonNode testCase diff --git a/src/test/groovy/com/featureprobe/sdk/server/SplitSpec.groovy b/src/test/groovy/com/featureprobe/sdk/server/SplitSpec.groovy index a5dce9b..979d475 100644 --- a/src/test/groovy/com/featureprobe/sdk/server/SplitSpec.groovy +++ b/src/test/groovy/com/featureprobe/sdk/server/SplitSpec.groovy @@ -27,6 +27,13 @@ class SplitSpec extends Specification { } } + def "Get hash key" () { + when: + def hash = split.hash("13", "tutorial_rollout", 10000) + then: + hash == 9558 + } + def "User not has key"() { when: user = new FPUser() diff --git a/src/test/resources/test b/src/test/resources/test new file mode 160000 index 0000000..4c31394 --- /dev/null +++ b/src/test/resources/test @@ -0,0 +1 @@ +Subproject commit 4c31394c23852033e43396543293da0a139d607b diff --git a/src/test/resources/test/server-sdk-specification/spec/toggle_simple_spec.json b/src/test/resources/test/server-sdk-specification/spec/toggle_simple_spec.json deleted file mode 100644 index be9d8a9..0000000 --- a/src/test/resources/test/server-sdk-specification/spec/toggle_simple_spec.json +++ /dev/null @@ -1,1038 +0,0 @@ -{ - "tests": [ - { - "scenario": "Get none exist toggle scenarios", - "fixture": { - "segments": {}, - "toggles": {} - }, - "cases": [ - { - "name": "test bool_value.", - "user": { - "key": "user id", - "customValues": [ - { - "key": "city", - "value": "1" - }, - { - "key": "email", - "value": "test@a.com" - } - ] - }, - "function": { - "name": "bool_value", - "toggle": "none_exit_toggle", - "default": true - }, - "expectResult": { - "value": true - } - }, - { - "name": "test string_value.", - "user": { - "key": "user id", - "customValues": [ - { - "key": "city", - "value": "1" - }, - { - "key": "email", - "value": "test@a.com" - } - ] - }, - "function": { - "name": "string_value", - "toggle": "none_exit_toggle", - "default": "" - }, - "expectResult": { - "value": "" - } - }, - { - "name": "test number_value.", - "user": { - "key": "user id", - "customValues": [ - { - "key": "city", - "value": "1" - }, - { - "key": "email", - "value": "test@a.com" - } - ] - }, - "function": { - "name": "number_value", - "toggle": "none_exit_toggle", - "default": 0 - }, - "expectResult": { - "value": 0 - } - }, - { - "name": "test json_value.", - "user": { - "key": "user id", - "customValues": [ - { - "key": "city", - "value": "1" - }, - { - "key": "email", - "value": "test@a.com" - } - ] - }, - "function": { - "name": "json_value", - "toggle": "none_exit_toggle", - "default": {} - }, - "expectResult": { - "value": {} - } - }, - { - "name": "test bool_detail", - "user": { - "key": "user id", - "customValues": [ - ] - }, - "function": { - "name": "bool_detail", - "toggle": "none_exit_toggle", - "default": false - }, - "expectResult": { - "value": false, - "reason": "not exist", - "ruleIndex": 0, - "conditionIndex": 0 - } - }, - { - "name": "test number_detail", - "user": { - "key": "user id", - "customValues": [ - ] - }, - "function": { - "name": "number_detail", - "toggle": "none_exit_toggle", - "default": 0 - }, - "expectResult": { - "value": 0, - "reason": "not exist", - "ruleIndex": 0, - "conditionIndex": 0 - } - }, - { - "name": "test json_detail", - "user": { - "key": "user id", - "customValues": [ - ] - }, - "function": { - "name": "json_detail", - "toggle": "none_exit_toggle", - "default": {} - }, - "expectResult": { - "value": {}, - "reason": "not exist", - "ruleIndex": 0, - "conditionIndex": 0 - } - }, - { - "name": "test string_detail", - "user": { - "key": "user id", - "customValues": [ - ] - }, - "function": { - "name": "string_detail", - "toggle": "none_exit_toggle", - "default": "" - }, - "expectResult": { - "value": "", - "reason": "not exist", - "ruleIndex": 0, - "conditionIndex": 0 - } - } - ] - }, - { - "scenario": "Get disabled toggle scenarios", - "fixture": { - "segments": {}, - "toggles": { - "string_toggle": { - "key": "string_toggle", - "enabled": false, - "forClient": true, - "version": 11, - "disabledServe": { - "select": 4 - }, - "defaultServe": { - "select": 3 - }, - "rules": [ - { - "serve": { - "select": 0 - }, - "conditions": [ - { - "type": "string", - "subject": "city", - "predicate": "is one of", - "objects": [ - "1", - "2", - "3" - ] - } - ] - }, - { - "serve": { - "select": 1 - }, - "conditions": [ - { - "type": "string", - "subject": "email", - "predicate": "is one of", - "objects": [ - "test@a.com" - ] - } - ] - }, - { - "serve": { - "select": 2 - }, - "conditions": [ - { - "type": "string", - "subject": "city", - "predicate": "is one of", - "objects": [ - "10" - ] - }, - { - "type": "string", - "subject": "email", - "predicate": "is one of", - "objects": [ - "another@a.com" - ] - } - ] - } - ], - "variations": [ - "1", - "2", - "3", - "4", - "5" - ] - } - } - }, - "cases": [ - { - "name": "test string_value.", - "user": { - "key": "user id", - "customValues": [ - { - "key": "city", - "value": "1" - }, - { - "key": "email", - "value": "test@a.com" - } - ] - }, - "function": { - "name": "string_value", - "toggle": "string_toggle", - "default": "" - }, - "expectResult": { - "value": "5" - } - }, - { - "name": "test string_detail", - "user": { - "key": "user id", - "customValues": [ - ] - }, - "function": { - "name": "string_detail", - "toggle": "string_toggle", - "default": "6" - }, - "expectResult": { - "value": "5", - "reason": "disabled", - "no_rule_index": true, - "version": 11 - } - } - ] - }, - { - "scenario": "Toggle split rollout scenarios", - "fixture": { - "segments": {}, - "toggles": { - "name_toggle": { - "key": "name_toggle", - "enabled": true, - "forClient": true, - "version": 11, - "disabledServe": { - "select": 0 - }, - "defaultServe": { - "split": { - "distribution": [ - [ - [ - 0, - 2647 - ] - ], - [ - [ - 2647, - 2648 - ] - ], - [ - [ - 2648, - 10000 - ] - ] - ], - "bucketBy": "name", - "salt": "salt" - } - }, - "rules": [ - ], - "variations": [ - "1", - "2", - "3" - ] - }, - "key_toggle": { - "key": "key_toggle", - "enabled": true, - "forClient": true, - "version": 11, - "disabledServe": { - "select": 0 - }, - "defaultServe": { - "split": { - "distribution": [ - [ - [ - 0, - 2647 - ] - ], - [ - [ - 2647, - 2648 - ] - ], - [ - [ - 2648, - 10000 - ] - ] - ], - "salt": "salt" - } - }, - "rules": [ - ], - "variations": [ - "1", - "2", - "3" - ] - } - } - }, - "cases": [ - { - "name": "test bucket by name in exact bucket.", - "user": { - "key": "user id", - "customValues": [ - { - "key": "name", - "value": "key" - } - ] - }, - "function": { - "name": "string_value", - "toggle": "name_toggle", - "default": "" - }, - "expectResult": { - "value": "2" - } - }, - { - "name": "test bucket by a none exist attribute.", - "user": { - "key": "user id", - "customValues": [ - ] - }, - "function": { - "name": "string_detail", - "toggle": "name_toggle", - "default": "" - }, - "expectResult": { - "value": "" - } - }, - { - "name": "test bucket by key in exact bucket", - "user": { - "key": "key", - "customValues": [ - ] - }, - "function": { - "name": "string_detail", - "toggle": "key_toggle", - "default": "6" - }, - "expectResult": { - "value": "2" - } - } - ] - }, - { - "scenario": "Toggle multi-condition scenarios", - "fixture": { - "segments": {}, - "toggles": { - "mc_toggle": { - "key": "mc_toggle", - "enabled": true, - "forClient": false, - "version": 11, - "disabledServe": { - "select": 0 - }, - "defaultServe": { - "select": 0 - }, - "rules": [ - { - "serve": { - "select": 1 - }, - "conditions": [ - { - "type": "string", - "subject": "city", - "predicate": "is one of", - "objects": [ - "1", - "2", - "3" - ] - }, - { - "type": "string", - "subject": "email", - "predicate": "is one of", - "objects": [ - "test@a.com" - ] - } - ] - } - ], - "variations": [ - "default", - "rule 1" - ] - } - } - }, - "cases": [ - { - "name": "test first single condition not match.", - "user": { - "key": "user id", - "customValues": [ - { - "key": "city", - "value": "1" - } - ] - }, - "function": { - "name": "string_value", - "toggle": "mc_toggle", - "default": "" - }, - "expectResult": { - "value": "default" - } - }, - { - "name": "single second condition not match", - "user": { - "key": "user id", - "customValues": [ - { - "key": "email", - "value": "test@a.com" - } - ] - }, - "function": { - "name": "string_detail", - "toggle": "mc_toggle", - "default": "" - }, - "expectResult": { - "value": "default" - } - }, - { - "name": "test both condition match.", - "user": { - "key": "user id", - "customValues": [ - { - "key": "email", - "value": "test@a.com" - }, - { - "key": "city", - "value": "1" - } - ] - }, - "function": { - "name": "string_detail", - "toggle": "mc_toggle", - "default": "" - }, - "expectResult": { - "value": "rule 1" - } - } - ] - }, - { - "scenario": "String Type predicate scenarios", - "fixture": { - "segments": {}, - "toggles": { - "string_toggle": { - "key": "string_toggle", - "enabled": true, - "forClient": false, - "version": 11, - "disabledServe": { - "select": 0 - }, - "defaultServe": { - "select": 0 - }, - "rules": [ - { - "serve": { - "select": 1 - }, - "conditions": [ - { - "type": "string", - "subject": "city", - "predicate": "is one of", - "objects": [ - "1", - "2" - ] - } - ] - }, - { - "serve": { - "select": 2 - }, - "conditions": [ - { - "type": "string", - "subject": "name", - "predicate": "ends with", - "objects": [ - "First", - "Bob" - ] - } - ] - }, - { - "serve": { - "select": 3 - }, - "conditions": [ - { - "type": "string", - "subject": "name", - "predicate": "starts with", - "objects": [ - "John" - ] - } - ] - }, - { - "serve": { - "select": 4 - }, - "conditions": [ - { - "type": "string", - "subject": "name", - "predicate": "contains", - "objects": [ - "Middle" - ] - } - ] - }, - { - "serve": { - "select": 5 - }, - "conditions": [ - { - "type": "string", - "subject": "name", - "predicate": "matches regex", - "objects": [ - "\\d\\d\\d" - ] - } - ] - }, - { - "serve": { - "select": 6 - }, - "conditions": [ - { - "type": "string", - "subject": "name", - "predicate": "is one of", - "objects": [ - "is_not_any_of" - ] - }, - { - "type": "string", - "subject": "city", - "predicate": "is not any of", - "objects": [ - "1", - "2", - "3" - ] - } - ] - } - ], - "variations": [ - "error", - "is one of", - "ends with", - "starts with", - "contains", - "matches regex", - "is not any of" - ] - } - } - }, - "cases": [ - { - "name": "test is one of", - "user": { - "key": "user id", - "customValues": [ - { - "key": "city", - "value": "2" - } - ] - }, - "function": { - "name": "string_value", - "toggle": "string_toggle", - "default": "" - }, - "expectResult": { - "value": "is one of" - } - }, - { - "name": "test ends with", - "user": { - "key": "user id", - "customValues": [ - { - "key": "name", - "value": "Martin Bob" - } - ] - }, - "function": { - "name": "string_detail", - "toggle": "string_toggle", - "default": "6" - }, - "expectResult": { - "value": "ends with" - } - }, - { - "name": "test starts with", - "user": { - "key": "user id", - "customValues": [ - { - "key": "name", - "value": "John Smith" - } - ] - }, - "function": { - "name": "string_detail", - "toggle": "string_toggle", - "default": "6" - }, - "expectResult": { - "value": "starts with" - } - }, - { - "name": "test contains", - "user": { - "key": "user id", - "customValues": [ - { - "key": "name", - "value": "A Middle B" - } - ] - }, - "function": { - "name": "string_detail", - "toggle": "string_toggle", - "default": "6" - }, - "expectResult": { - "value": "contains" - } - }, - { - "name": "test regex", - "user": { - "key": "user id", - "customValues": [ - { - "key": "name", - "value": "some 666 word" - } - ] - }, - "function": { - "name": "string_detail", - "toggle": "string_toggle", - "default": "6" - }, - "expectResult": { - "value": "matches regex" - } - }, - { - "name": "test is not any of.", - "user": { - "key": "user id", - "customValues": [ - { - "key": "name", - "value": "is_not_any_of" - }, - { - "key": "city", - "value": "10" - } - ] - }, - "function": { - "name": "string_value", - "toggle": "string_toggle", - "default": "" - }, - "expectResult": { - "value": "is not any of" - } - } - ] - }, - { - "scenario": "String value get detail scenarios", - "fixture": { - "segments": {}, - "toggles": { - "string_toggle": { - "key": "string_toggle", - "enabled": true, - "forClient": true, - "version": 1, - "disabledServe": { - "select": 1 - }, - "defaultServe": { - "select": 3 - }, - "rules": [ - { - "serve": { - "select": 0 - }, - "conditions": [ - { - "type": "string", - "subject": "city", - "predicate": "is one of", - "objects": [ - "1", - "2", - "3" - ] - } - ] - }, - { - "serve": { - "select": 1 - }, - "conditions": [ - { - "type": "string", - "subject": "email", - "predicate": "is one of", - "objects": [ - "test@a.com" - ] - } - ] - }, - { - "serve": { - "select": 2 - }, - "conditions": [ - { - "type": "string", - "subject": "city", - "predicate": "is one of", - "objects": [ - "10" - ] - }, - { - "type": "string", - "subject": "email", - "predicate": "is one of", - "objects": [ - "another@a.com" - ] - } - ] - } - ], - "variations": [ - "1", - "2", - "3", - "4" - ] - } - } - }, - "cases": [ - { - "name": "test string_detail match third rule.", - "user": { - "key": "user id", - "customValues": [ - { - "key": "email", - "value": "another@a.com" - }, - { - "key": "city", - "value": "10" - } - ] - }, - "function": { - "name": "string_detail", - "toggle": "string_toggle", - "default": "" - }, - "expectResult": { - "value": "3", - "rule_index": 2 - } - }, - { - "name": "test string_detail match first rule.", - "user": { - "key": "user id", - "customValues": [ - { - "key": "email", - "value": "test@a.com" - }, - { - "key": "city", - "value": "2" - } - ] - }, - "function": { - "name": "string_detail", - "toggle": "string_toggle", - "default": "" - }, - "expectResult": { - "value": "1", - "rule_index": 0 - } - }, - { - "name": "test string_detail missing attribute in second rule.", - "user": { - "key": "user id", - "customValues": [ - { - "key": "city", - "value": "12" - } - ] - }, - "function": { - "name": "string_detail", - "toggle": "string_toggle", - "default": "" - }, - "expectResult": { - "value": "4", - "no_rule_index": true - } - }, - { - "name": "test string_detail match no rule, use default serve.", - "user": { - "key": "user id", - "customValues": [ - { - "key": "email", - "value": "notest@a.com" - } - ] - }, - "function": { - "name": "string_detail", - "toggle": "string_toggle", - "default": "" - }, - "expectResult": { - "value": "4", - "no_rule_index": true, - "reason": "default" - } - }, - { - "name": "test string variation get with bool_detail.", - "user": { - "key": "user id", - "customValues": [ - { - "key": "email", - "value": "test@a.com" - }, - { - "key": "city", - "value": "2" - } - ] - }, - "function": { - "name": "bool_detail", - "toggle": "string_toggle", - "default": true - }, - "expectResult": { - "value": true, - "reason": "type mismatch" - } - } - ] - } - ] -} \ No newline at end of file