From a66711a5b408c8667fddc3d5e54207150aaba774 Mon Sep 17 00:00:00 2001 From: Justin Abrahms Date: Wed, 11 May 2022 14:38:22 -0700 Subject: [PATCH] Set variants on noop provider & add provider tests. --- lib/src/main/java/javasdk/NoOpProvider.java | 3 + lib/src/test/java/javasdk/HookSpecTests.java | 1 + .../test/java/javasdk/ProviderSpecTests.java | 81 +++++++++++++++++++ 3 files changed, 85 insertions(+) diff --git a/lib/src/main/java/javasdk/NoOpProvider.java b/lib/src/main/java/javasdk/NoOpProvider.java index 39b82260..66004398 100644 --- a/lib/src/main/java/javasdk/NoOpProvider.java +++ b/lib/src/main/java/javasdk/NoOpProvider.java @@ -10,6 +10,7 @@ public class NoOpProvider implements FeatureProvider { public ProviderEvaluation getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx, FlagEvaluationOptions options) { return ProviderEvaluation.builder() .value(defaultValue) + .variant("Passed in default") .reason(Reason.DEFAULT) .build(); } @@ -18,6 +19,7 @@ public class NoOpProvider implements FeatureProvider { public ProviderEvaluation getStringEvaluation(String key, String defaultValue, EvaluationContext ctx, FlagEvaluationOptions options) { return ProviderEvaluation.builder() .value(defaultValue) + .variant("Passed in default") .reason(Reason.DEFAULT) .build(); } @@ -26,6 +28,7 @@ public class NoOpProvider implements FeatureProvider { public ProviderEvaluation getIntegerEvaluation(String key, Integer defaultValue, EvaluationContext ctx, FlagEvaluationOptions options) { return ProviderEvaluation.builder() .value(defaultValue) + .variant("Passed in default") .reason(Reason.DEFAULT) .build(); } diff --git a/lib/src/test/java/javasdk/HookSpecTests.java b/lib/src/test/java/javasdk/HookSpecTests.java index f02b77b3..1e7e22fc 100644 --- a/lib/src/test/java/javasdk/HookSpecTests.java +++ b/lib/src/test/java/javasdk/HookSpecTests.java @@ -389,6 +389,7 @@ public class HookSpecTests { verify(hook, times(1)).error(any(), any(), any()); verify(hook2, times(1)).error(any(), any(), any()); } + @Specification(spec="hooks", number="1.4", text="The evaluation context MUST be mutable only within the before hook.") @Specification(spec="hooks", number="3.1", text="Hooks MUST specify at least one stage.") @Specification(spec="hooks", number="5.3", text="HookHints MUST be passed to each hook through a parameter. It is merged into the object in the precedence order API -> Client -> Invocation (last wins).") diff --git a/lib/src/test/java/javasdk/ProviderSpecTests.java b/lib/src/test/java/javasdk/ProviderSpecTests.java index 51b8539a..c7f4a256 100644 --- a/lib/src/test/java/javasdk/ProviderSpecTests.java +++ b/lib/src/test/java/javasdk/ProviderSpecTests.java @@ -3,7 +3,88 @@ package javasdk; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + public class ProviderSpecTests { + NoOpProvider p = new NoOpProvider(); + + @Specification(spec="provider", number="2.1", text="The provider interface MUST define a name field or accessor, " + + "which identifies the provider implementation.") + @Test void name_accessor() { + assertNotNull(p.getName()); + } + + @Specification(spec="provider", number="2.4", text="In cases of normal execution, the provider MUST populate the " + + "flag resolution structure's value field with the resolved flag value.") + @Specification(spec="provider", number="2.2", text="The feature provider interface MUST define methods to resolve " + + "flag values, with parameters flag key (string, required), default value " + + "(boolean | number | string | structure, required), evaluation context (optional), and " + + "evaluation options (optional), which returns a flag resolution structure.") + @Specification(spec="provider", number="2.9.1", text="The flag resolution structure SHOULD accept a generic " + + "argument (or use an equivalent language feature) which indicates the type of the wrapped value field.") + @Test void flag_value_set() { + ProviderEvaluation int_result = p.getIntegerEvaluation("key", 4, new EvaluationContext(), FlagEvaluationOptions.builder().build()); + assertNotNull(int_result.getValue()); + + ProviderEvaluation string_result = p.getStringEvaluation("key", "works", new EvaluationContext(), FlagEvaluationOptions.builder().build()); + assertNotNull(string_result.getValue()); + + ProviderEvaluation boolean_result = p.getBooleanEvaluation("key", false, new EvaluationContext(), FlagEvaluationOptions.builder().build()); + assertNotNull(boolean_result.getValue()); + } + + @Specification(spec="provider", number="2.6", text="The provider SHOULD populate the flag resolution structure's " + + "reason field with a string indicating the semantic reason for the returned flag value.") + @Test void has_reason() { + ProviderEvaluation result = p.getBooleanEvaluation("key", false, new EvaluationContext(), FlagEvaluationOptions.builder().build()); + assertEquals(Reason.DEFAULT, result.getReason()); + } + + @Specification(spec="provider", number="2.7", text="In cases of normal execution, the provider MUST NOT populate " + + "the flag resolution structure's error code field, or otherwise must populate it with a null or falsy value.") + @Test void no_error_code_by_default() { + ProviderEvaluation result = p.getBooleanEvaluation("key", false, new EvaluationContext(), FlagEvaluationOptions.builder().build()); + assertNull(result.getErrorCode()); + } + + @Specification(spec="provider", number="2.3.1", text="The feature provider interface MUST define methods for typed " + + "flag resolution, including boolean, numeric, string, and structure.") + @Disabled("Don't yet support structures") @Test void structure_validation() { + + } + + @Specification(spec="provider", number="2.8", text="In cases of abnormal execution, the provider MUST indicate an " + + "error using the idioms of the implementation language, with an associated error code having possible " + + "values PROVIDER_NOT_READY, FLAG_NOT_FOUND, PARSE_ERROR, TYPE_MISMATCH, or GENERAL.") + @Disabled("I don't think we expect the provider to do all the exception catching.. right?") + @Test void error_populates_error_code() { + AlwaysBrokenProvider broken = new AlwaysBrokenProvider(); + ProviderEvaluation result = broken.getBooleanEvaluation("key", false, new EvaluationContext(), FlagEvaluationOptions.builder().build()); + assertEquals(ErrorCode.GENERAL, result.getErrorCode()); + } + + @Specification(spec="provider", number="2.5", text="In cases of normal execution, the provider SHOULD populate the " + + "flag resolution structure's variant field with a string identifier corresponding to the returned flag value.") + @Test void variant_set() { + ProviderEvaluation int_result = p.getIntegerEvaluation("key", 4, new EvaluationContext(), FlagEvaluationOptions.builder().build()); + assertNotNull(int_result.getReason()); + + ProviderEvaluation string_result = p.getStringEvaluation("key", "works", new EvaluationContext(), FlagEvaluationOptions.builder().build()); + assertNotNull(string_result.getReason()); + + ProviderEvaluation boolean_result = p.getBooleanEvaluation("key", false, new EvaluationContext(), FlagEvaluationOptions.builder().build()); + assertNotNull(boolean_result.getReason()); + } + + @Specification(spec="provider", number="2.11.1", text="If the implementation includes a context transformer, the " + + "provider SHOULD accept a generic argument (or use an equivalent language feature) indicating the type of " + + "the transformed context. If such type information is supplied, more accurate type information can be " + + "supplied in the flag resolution methods.") + @Specification(spec="provider", number="2.10", text="The provider interface MAY define a context transformer method " + + "or function, which can be optionally implemented in order to transform the evaluation context prior to " + + "flag value resolution.") + @Disabled("I don't think we should do that until we figure out the call signature differences") + @Test void not_doing() {} @Specification(spec="provider", number="2.1", text="The provider interface MUST define a name field or accessor, which identifies the provider implementation.") @Specification(spec="provider", number="2.2", text="The feature provider interface MUST define methods to resolve flag values, with parameters flag key (string, required), default value (boolean | number | string | structure, required), evaluation context (optional), and evaluation options (optional), which returns a flag resolution structure.") @Specification(spec="provider", number="2.3.1", text="The feature provider interface MUST define methods for typed flag resolution, including boolean, numeric, string, and structure.")