From b17745923b845054159152d3691d3b4133e57f5e Mon Sep 17 00:00:00 2001 From: Simon Schrottner Date: Sun, 21 Jul 2024 22:12:16 +0200 Subject: [PATCH 1/8] feat: Add JUnit5 extension for OpenFeature Signed-off-by: Simon Schrottner --- pom.xml | 1 + providers/flagd/schemas | 2 +- providers/flagd/test-harness | 2 +- tools/junit-openfeature/README.md | 155 ++++++++++++ tools/junit-openfeature/pom.xml | 49 ++++ .../contrib/tools/junitopenfeature/Flag.java | 19 ++ .../contrib/tools/junitopenfeature/Flags.java | 17 ++ .../tools/junitopenfeature/OpenFeature.java | 17 ++ .../OpenFeatureDefaultDomain.java | 14 ++ .../OpenFeatureExtension.java | 118 +++++++++ .../tools/junitopenfeature/OpenFeatures.java | 14 ++ .../OpenFeatureExtensionTest.java | 236 ++++++++++++++++++ 12 files changed, 642 insertions(+), 2 deletions(-) create mode 100644 tools/junit-openfeature/README.md create mode 100644 tools/junit-openfeature/pom.xml create mode 100644 tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/Flag.java create mode 100644 tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/Flags.java create mode 100644 tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeature.java create mode 100644 tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureDefaultDomain.java create mode 100644 tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureExtension.java create mode 100644 tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatures.java create mode 100644 tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureExtensionTest.java diff --git a/pom.xml b/pom.xml index 1db5b8da4..95d9aac17 100644 --- a/pom.xml +++ b/pom.xml @@ -28,6 +28,7 @@ hooks/open-telemetry + tools/junit-openfeature providers/flagd providers/flagsmith providers/go-feature-flag diff --git a/providers/flagd/schemas b/providers/flagd/schemas index 2aa89b314..8c72c14ee 160000 --- a/providers/flagd/schemas +++ b/providers/flagd/schemas @@ -1 +1 @@ -Subproject commit 2aa89b31432284507af3873de9b0bb7b68dd02c7 +Subproject commit 8c72c14eebff2a5b20fe2afb90c7ad44c1184ae8 diff --git a/providers/flagd/test-harness b/providers/flagd/test-harness index ed7e0ba66..25544e43d 160000 --- a/providers/flagd/test-harness +++ b/providers/flagd/test-harness @@ -1 +1 @@ -Subproject commit ed7e0ba660b01e1a22849e1b28ec37453921552e +Subproject commit 25544e43d65500ea085d6f0e242b9a616a51f776 diff --git a/tools/junit-openfeature/README.md b/tools/junit-openfeature/README.md new file mode 100644 index 000000000..4d513db4e --- /dev/null +++ b/tools/junit-openfeature/README.md @@ -0,0 +1,155 @@ +# JUnit Open Feature extension + +A JUnit5 extension to reduce boilerplate code for testing code which utilizes OpenFeature. + +## Getting Started + +We are supporting two different flavors for testing, a [simple](#simple-configuration) and an [extended](#extended-configuration) configuration. + +### Simple Configuration + +Choose the simple configuration if you are only testing in one domain. +Per default, it will be used in the global domain. + +```java +@Test +@Flag(name = "BOOLEAN_FLAG", value = "true") +void test() { + // your test code +} +``` + +#### Multiple flags + +The `@Flag` annotation can be also repeated multiple times. + +```java +@Test +@Flag(name = "BOOLEAN_FLAG", value = "true") +@Flag(name = "BOOLEAN_FLAG2", value = "true") +void test() { + // your test code +} +``` + +#### Defining Flags for a whole test-class + +`@Flags` can be defined on the class-level too, but method-level +annotations will superseded class-level annotations. + +```java +@Flag(name = "BOOLEAN_FLAG", value = "true") +@Flag(name = "BOOLEAN_FLAG2", value = "false") +class Test { + @Test + @Flag(name = "BOOLEAN_FLAG2", value = "true") // will be used + void test() { + // your test code + } +} +``` + +#### Setting a different domain + +You can define an own domain on the test-class-level with `@OpenFeatureDefaultDomain` like: + +```java +@OpenFeatureDefaultDomain("domain") +class Test { + @Test + @Flag(name = "BOOLEAN_FLAG", value = "true") + // this flag will be available in the `domain` domain + void test() { + // your test code + } +} +``` + +### Extended Configuration + +Use the extended configuration when your code needs to use multiple domains. + +```java +@Test +@OpenFeature({ + @Flag(name = "BOOLEAN_FLAG", value = "true") +}) +void test() { + // your test code +} +``` + + +#### Multiple flags + +The `@Flag` annotation can be also repeated multiple times. + +```java +@Test +@OpenFeature({ + @Flag(name = "BOOLEAN_FLAG", value = "true"), + @Flag(name = "BOOLEAN_FLAG2", value = "true") +}) +void test() { + // your test code +} +``` + +#### Defining Flags for a whole test-class + +`@Flags` can be defined on the class-level too, but method-level +annotations will superseded class-level annotations. + +```java +@OpenFeature({ + @Flag(name = "BOOLEAN_FLAG", value = "true"), + @Flag(name = "BOOLEAN_FLAG2", value = "false") +}) +class Test { + @Test + @OpenFeature({ + @Flag(name = "BOOLEAN_FLAG2", value = "true") // will be used + }) + void test() { + // your test code + } +} +``` + +#### Setting a different domain + +You can define an own domain for each usage of the `@OpenFeature` annotation with the `domain` property: + +```java +@Test +@OpenFeature( + domain = "domain", + value = { + @Flag(name = "BOOLEAN_FLAG2", value = "true") // will be used +}) + // this flag will be available in the `domain` domain +void test() { + // your test code +} +``` + +#### Multiple Configurations for multiple domains + +Following testcode will generate two providers, with different flag configurations for a test. + +```java +@Test +@OpenFeature({ + @Flag(name = "BOOLEAN_FLAG", value = "true"), + @Flag(name = "BOOLEAN_FLAG2", value = "true") +}) +@OpenFeature( + domain = "domain", + value = { + @Flag(name = "BOOLEAN_FLAG2", value = "true") // will be used + }) +void test() { + // your test code +} +``` + diff --git a/tools/junit-openfeature/pom.xml b/tools/junit-openfeature/pom.xml new file mode 100644 index 000000000..2fb7117e5 --- /dev/null +++ b/tools/junit-openfeature/pom.xml @@ -0,0 +1,49 @@ + + + 4.0.0 + + dev.openfeature.contrib + parent + 0.1.0 + ../../pom.xml + + dev.openfeature.contrib.tools + junitopenfeature + 3.1.2 + + junit-openfeature-extension + JUnit5 Extension for OpenFeature + https://openfeature.dev + + + + aepfli + Simon Schrottner + OpenFeature + https://openfeature.dev/ + + + + + + dev.openfeature + sdk + [1.4,2.0) + provided + + + + org.junit.jupiter + junit-jupiter-api + ${junit.jupiter.version} + + + + org.junit-pioneer + junit-pioneer + 1.9.1 + + + + diff --git a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/Flag.java b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/Flag.java new file mode 100644 index 000000000..6312f2df4 --- /dev/null +++ b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/Flag.java @@ -0,0 +1,19 @@ +package dev.openfeature.contrib.tools.junitopenfeature; + +import org.junit.jupiter.api.extension.ExtendWith; + +import java.lang.annotation.*; + +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Repeatable(Flags.class) +@ExtendWith(OpenFeatureExtension.class) +public @interface Flag { + String name(); + + String value(); + + Class valueType() default Boolean.class; +} + + diff --git a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/Flags.java b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/Flags.java new file mode 100644 index 000000000..185f22249 --- /dev/null +++ b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/Flags.java @@ -0,0 +1,17 @@ +package dev.openfeature.contrib.tools.junitopenfeature; + +import org.junit.jupiter.api.extension.ExtendWith; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@ExtendWith(OpenFeatureExtension.class) +public @interface Flags { + Flag[] value() default {}; +} + + diff --git a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeature.java b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeature.java new file mode 100644 index 000000000..e05cbca89 --- /dev/null +++ b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeature.java @@ -0,0 +1,17 @@ +package dev.openfeature.contrib.tools.junitopenfeature; + +import dev.openfeature.sdk.FlagValueType; +import org.junit.jupiter.api.extension.ExtendWith; + +import java.lang.annotation.*; + +@Target({ ElementType.METHOD, ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Repeatable(value = OpenFeatures.class) +@ExtendWith(OpenFeatureExtension.class) +public @interface OpenFeature { + String domain() default ""; + Flag[] value(); +} + + diff --git a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureDefaultDomain.java b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureDefaultDomain.java new file mode 100644 index 000000000..156a683e0 --- /dev/null +++ b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureDefaultDomain.java @@ -0,0 +1,14 @@ +package dev.openfeature.contrib.tools.junitopenfeature; + +import org.junit.jupiter.api.extension.ExtendWith; + +import java.lang.annotation.*; + +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@ExtendWith(OpenFeatureExtension.class) +public @interface OpenFeatureDefaultDomain { + String value() default ""; +} + + diff --git a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureExtension.java b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureExtension.java new file mode 100644 index 000000000..1ef14969b --- /dev/null +++ b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureExtension.java @@ -0,0 +1,118 @@ +package dev.openfeature.contrib.tools.junitopenfeature; + +import dev.openfeature.sdk.NoOpProvider; +import dev.openfeature.sdk.OpenFeatureAPI; +import dev.openfeature.sdk.providers.memory.Flag; +import dev.openfeature.sdk.providers.memory.InMemoryProvider; +import org.apache.commons.lang3.BooleanUtils; +import org.junit.jupiter.api.extension.*; +import org.junitpioneer.internal.PioneerAnnotationUtils; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +public class OpenFeatureExtension implements BeforeEachCallback, AfterEachCallback, ParameterResolver { + + OpenFeatureAPI api = OpenFeatureAPI.getInstance(); + + + @Override + public void afterEach(ExtensionContext extensionContext) throws Exception { + + @SuppressWarnings("unchecked") Map>> configuration = + (Map>>) getStore(extensionContext).get("config"); + for (Map.Entry>> stringMapEntry : configuration.entrySet()) { + InMemoryProvider inMemoryProvider = new InMemoryProvider(stringMapEntry.getValue()); + if (stringMapEntry.getKey().isEmpty()) { + api.setProvider(new NoOpProvider()); + } else { + api.setProvider(stringMapEntry.getKey(), new NoOpProvider()); + } + } + } + + + @Override + public void beforeEach(ExtensionContext extensionContext) throws Exception { + Map>> configuration = handleSimpleConfiguration(extensionContext); + configuration.putAll(handleExtendedConfiguration(extensionContext, configuration)); + + for (Map.Entry>> stringMapEntry : configuration.entrySet()) { + InMemoryProvider inMemoryProvider = new InMemoryProvider(stringMapEntry.getValue()); + if (stringMapEntry.getKey().isEmpty()) { + api.setProvider(inMemoryProvider); + } else { + api.setProvider(stringMapEntry.getKey(), inMemoryProvider); + } + } + + getStore(extensionContext).put("config", configuration); + } + + private static Map>> handleExtendedConfiguration(ExtensionContext extensionContext, Map>> configuration) { + PioneerAnnotationUtils.findAllEnclosingRepeatableAnnotations(extensionContext, OpenFeature.class) + .forEachOrdered(annotation -> { + Map> domainFlags = configuration.getOrDefault(annotation.domain(), new HashMap<>()); + + Arrays.stream(annotation.value()) + .filter(flag -> !domainFlags.containsKey(flag.name())) + .forEach(flag -> { + Flag.FlagBuilder builder = generateFlagBuilder(flag); + domainFlags.put(flag.name(), builder.build()); + }); + configuration.put(annotation.domain(), domainFlags); + }); + return configuration; + } + + private static Map>> handleSimpleConfiguration(ExtensionContext extensionContext) { + Map>> configuration = new HashMap<>(); + String defaultDomain = PioneerAnnotationUtils.findClosestEnclosingAnnotation(extensionContext, OpenFeatureDefaultDomain.class) + .map(OpenFeatureDefaultDomain::value) + .orElse(""); + PioneerAnnotationUtils.findAllEnclosingRepeatableAnnotations(extensionContext, dev.openfeature.contrib.tools.junitopenfeature.Flag.class) + .forEachOrdered(flag -> { + Map> domainFlags = configuration.getOrDefault(defaultDomain, new HashMap<>()); + if (!domainFlags.containsKey(flag.name())) { + Flag.FlagBuilder builder = generateFlagBuilder(flag); + domainFlags.put(flag.name(), builder.build()); + configuration.put(defaultDomain, domainFlags); + } + }); + + return configuration; + } + + private static Flag.FlagBuilder generateFlagBuilder(dev.openfeature.contrib.tools.junitopenfeature.Flag flag) { + Flag.FlagBuilder builder; + switch (flag.valueType().getSimpleName()) { + case "Boolean": + builder = Flag.builder(); + builder.variant(flag.value(), BooleanUtils.toBoolean(flag.value())); + break; + case "String": + builder = Flag.builder(); + builder.variant(flag.value(), flag.value()); + break; + default: + throw new IllegalArgumentException("Unsupported flag type: " + flag.value()); + } + builder.defaultVariant(flag.value()); + return builder; + } + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { + return false; + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { + return null; + } + + private ExtensionContext.Store getStore(ExtensionContext context) { + return context.getStore(ExtensionContext.Namespace.create(getClass())); + } +} diff --git a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatures.java b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatures.java new file mode 100644 index 000000000..ab4d9c20d --- /dev/null +++ b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatures.java @@ -0,0 +1,14 @@ +package dev.openfeature.contrib.tools.junitopenfeature; + +import org.junit.jupiter.api.extension.ExtendWith; + +import java.lang.annotation.*; + +@Target({ ElementType.METHOD, ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@ExtendWith(OpenFeatureExtension.class) +public @interface OpenFeatures { + OpenFeature[] value() default {}; +} + + diff --git a/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureExtensionTest.java b/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureExtensionTest.java new file mode 100644 index 000000000..dd79278a5 --- /dev/null +++ b/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureExtensionTest.java @@ -0,0 +1,236 @@ +package dev.openfeature.contrib.tools.junitopenfeature; + +import dev.openfeature.sdk.Client; +import dev.openfeature.sdk.NoOpProvider; +import dev.openfeature.sdk.OpenFeatureAPI; +import dev.openfeature.sdk.providers.memory.InMemoryProvider; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; + +import static org.assertj.core.api.Assertions.assertThat; + +@Execution(ExecutionMode.SAME_THREAD) +class OpenFeatureExtensionTest { + + OpenFeatureAPI api = OpenFeatureAPI.getInstance(); + + @Nested + class Initialization { + + @Nested + class OnMethod { + @Test + @OpenFeature({}) + void clientIsSet() { + assertThat(api).isNotNull(); + assertThat(api.getProvider()).isInstanceOf(InMemoryProvider.class); + } + + @OpenFeature({}) + @OpenFeature(domain = "test", value = {}) + void clientIsSetMultipleTimes() { + assertThat(api).isNotNull(); + assertThat(api.getProvider()).isInstanceOf(InMemoryProvider.class); + assertThat(api.getProvider("test")).isInstanceOf(InMemoryProvider.class); + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client).isNotNull(); + Client clientTest = OpenFeatureAPI.getInstance().getClient("test"); + assertThat(clientTest).isNotNull(); + } + + @Test + @OpenFeature(domain = "domain", value = {}) + void clientIsSetWithDomain() { + assertThat(api).isNotNull(); + assertThat(api.getProvider("domain")).isInstanceOf(InMemoryProvider.class); + assertThat(api.getProvider()).isInstanceOf(NoOpProvider.class); + Client client = OpenFeatureAPI.getInstance().getClient("domain"); + assertThat(client).isNotNull(); + } + } + + @Nested + @OpenFeature({}) + class OnClass { + @Test + void clientIsSet() { + assertThat(api).isNotNull(); + assertThat(api.getProvider()).isInstanceOf(InMemoryProvider.class); + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client).isNotNull(); + } + } + + @Nested + @OpenFeature(domain = "domain", value = {}) + class OnClassWithDomain { + @Test + void clientIsSetWithDomain() { + assertThat(api).isNotNull(); + assertThat(api.getProvider("domain")).isInstanceOf(InMemoryProvider.class); + assertThat(api.getProvider()).isInstanceOf(NoOpProvider.class); + Client client = OpenFeatureAPI.getInstance().getClient("domain"); + assertThat(client).isNotNull(); + } + } + + } + + private static final String BOOLEAN_FLAG = "boolean-flag"; + + @Nested + class Boolean { + @Nested + class SimpleConfig { + + @Nested + @Flag(name = BOOLEAN_FLAG, value = "true") + @Flag(name = BOOLEAN_FLAG + "2", value = "true") + @Flag(name = BOOLEAN_FLAG + "3", value = "true") + class onClass { + @Test + void multipleFlagsSimple() { + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client.getBooleanValue(BOOLEAN_FLAG, false)).isTrue(); + assertThat(client.getBooleanValue(BOOLEAN_FLAG + "2", false)).isTrue(); + assertThat(client.getBooleanValue(BOOLEAN_FLAG + "3", false)).isTrue(); + } + } + @Test + @Flag(name = BOOLEAN_FLAG, value = "true") + void existingSimpleFlagIsRetrieved() { + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client.getBooleanValue(BOOLEAN_FLAG, false)).isTrue(); + } + + @Test + @Flag(name = BOOLEAN_FLAG, value = "true") + @Flag(name = BOOLEAN_FLAG + "2", value = "true") + @Flag(name = BOOLEAN_FLAG + "3", value = "true") + void multipleFlagsSimple() { + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client.getBooleanValue(BOOLEAN_FLAG, false)).isTrue(); + assertThat(client.getBooleanValue(BOOLEAN_FLAG + "2", false)).isTrue(); + assertThat(client.getBooleanValue(BOOLEAN_FLAG + "3", false)).isTrue(); + } + } + + @Nested + @OpenFeatureDefaultDomain("testSpecific") + class SimpleConfigWithDefault { + @Nested + @Flag(name = BOOLEAN_FLAG, value = "true") + @Flag(name = BOOLEAN_FLAG + "2", value = "true") + @Flag(name = BOOLEAN_FLAG + "3", value = "true") + class onClass { + @Test + void multipleFlagsSimple() { + Client client = OpenFeatureAPI.getInstance().getClient("testSpecific"); + assertThat(client.getBooleanValue(BOOLEAN_FLAG, false)).isTrue(); + assertThat(client.getBooleanValue(BOOLEAN_FLAG + "2", false)).isTrue(); + assertThat(client.getBooleanValue(BOOLEAN_FLAG + "3", false)).isTrue(); + } + } + @Test + @Flag(name = BOOLEAN_FLAG, value = "true") + void existingSimpleFlagIsRetrieved() { + Client client = OpenFeatureAPI.getInstance().getClient("testSpecific"); + assertThat(client.getBooleanValue(BOOLEAN_FLAG, false)).isTrue(); + } + + @Test + @Flag(name = BOOLEAN_FLAG, value = "true") + @Flag(name = BOOLEAN_FLAG + "2", value = "true") + @Flag(name = BOOLEAN_FLAG + "3", value = "true") + void multipleFlagsSimple() { + Client client = OpenFeatureAPI.getInstance().getClient("testSpecific"); + assertThat(client.getBooleanValue(BOOLEAN_FLAG, false)).isTrue(); + assertThat(client.getBooleanValue(BOOLEAN_FLAG + "2", false)).isTrue(); + assertThat(client.getBooleanValue(BOOLEAN_FLAG + "3", false)).isTrue(); + } + } + + @Nested + class ExtendedConfig { + @Test + @OpenFeature({ + @Flag(name = BOOLEAN_FLAG, value = "true") + }) + void existingFlagIsRetrieved() { + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client.getBooleanValue(BOOLEAN_FLAG, false)).isTrue(); + } + + @Test + @OpenFeature( + @Flag(name = BOOLEAN_FLAG, value = "truesadf") + ) + void strangeFlagValue() { + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client.getBooleanValue(BOOLEAN_FLAG, false)).isFalse(); + } + + @Test + @OpenFeature( + @Flag(name = BOOLEAN_FLAG, value = "true") + ) + void nonExistingFlagIsFallbacked() { + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client.getBooleanValue("nonSetFlag", false)).isFalse(); + } + + @Test + @OpenFeature({ + @Flag(name = BOOLEAN_FLAG, value = "true"), + @Flag(name = BOOLEAN_FLAG + "2", value = "true"), + @Flag(name = BOOLEAN_FLAG + "3", value = "true"), + }) + void multipleFlags() { + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client.getBooleanValue(BOOLEAN_FLAG, false)).isTrue(); + assertThat(client.getBooleanValue(BOOLEAN_FLAG + "2", false)).isTrue(); + assertThat(client.getBooleanValue(BOOLEAN_FLAG + "3", false)).isTrue(); + } + + @Nested + @OpenFeature({ + @Flag(name = BOOLEAN_FLAG, value = "true"), + @Flag(name = BOOLEAN_FLAG + "2", value = "false"), + }) + class MultipleFlags { + @Test + @OpenFeature({ + @Flag(name = BOOLEAN_FLAG + "2", value = "true"), + @Flag(name = BOOLEAN_FLAG + "3", value = "true"), + }) + void multipleFlags() { + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client.getBooleanValue(BOOLEAN_FLAG, false)).isTrue(); + assertThat(client.getBooleanValue(BOOLEAN_FLAG + "2", false)).isTrue(); + assertThat(client.getBooleanValue(BOOLEAN_FLAG + "3", false)).isTrue(); + } + + @Test + @OpenFeature( + domain = "testSpecific", + value = { + @Flag(name = BOOLEAN_FLAG + "2", value = "true"), + @Flag(name = BOOLEAN_FLAG + "3", value = "true"), + }) + void multipleFlagsOnMultipleDomains() { + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client.getBooleanValue(BOOLEAN_FLAG, false)).isTrue(); + assertThat(client.getBooleanValue(BOOLEAN_FLAG + "2", true)).isFalse(); + assertThat(client.getBooleanValue(BOOLEAN_FLAG + "3", false)).isFalse(); + + Client testSpecific = OpenFeatureAPI.getInstance().getClient("testSpecific"); + assertThat(testSpecific.getBooleanValue(BOOLEAN_FLAG, false)).isFalse(); + assertThat(testSpecific.getBooleanValue(BOOLEAN_FLAG + "2", false)).isTrue(); + assertThat(testSpecific.getBooleanValue(BOOLEAN_FLAG + "3", false)).isTrue(); + } + } + } + } +} From 61a7bc07f2eb2911407adbe8b6f4481c19882efa Mon Sep 17 00:00:00 2001 From: Simon Schrottner Date: Mon, 22 Jul 2024 12:28:51 +0200 Subject: [PATCH 2/8] chore: checkstyle issues Signed-off-by: Simon Schrottner --- .../contrib/tools/junitopenfeature/Flag.java | 19 +++- .../contrib/tools/junitopenfeature/Flags.java | 6 ++ .../tools/junitopenfeature/OpenFeature.java | 17 +++- .../OpenFeatureDefaultDomain.java | 11 ++- .../OpenFeatureExtension.java | 91 +++++++++---------- .../tools/junitopenfeature/OpenFeatures.java | 13 ++- 6 files changed, 105 insertions(+), 52 deletions(-) diff --git a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/Flag.java b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/Flag.java index 6312f2df4..5870c60cd 100644 --- a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/Flag.java +++ b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/Flag.java @@ -2,17 +2,34 @@ import org.junit.jupiter.api.extension.ExtendWith; -import java.lang.annotation.*; +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +/** + * Annotation for Flag Configuration for the default domain. + * Can be used as a standalone flag configuration but also within {@link OpenFeature}. + */ @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Repeatable(Flags.class) @ExtendWith(OpenFeatureExtension.class) public @interface Flag { + /** + * The key of the FeatureFlag. + */ String name(); + /** + * The value of the FeatureFlag. + */ String value(); + /** + * The type of the FeatureFlag. + */ Class valueType() default Boolean.class; } diff --git a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/Flags.java b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/Flags.java index 185f22249..eecdba428 100644 --- a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/Flags.java +++ b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/Flags.java @@ -7,10 +7,16 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * Collection of {@link Flag} configurations. + */ @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @ExtendWith(OpenFeatureExtension.class) public @interface Flags { + /** + * Collection of {@link Flag} configurations. + */ Flag[] value() default {}; } diff --git a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeature.java b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeature.java index e05cbca89..aaae83302 100644 --- a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeature.java +++ b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeature.java @@ -1,16 +1,29 @@ package dev.openfeature.contrib.tools.junitopenfeature; -import dev.openfeature.sdk.FlagValueType; import org.junit.jupiter.api.extension.ExtendWith; -import java.lang.annotation.*; +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +/** + * Annotation for generating an extended configuration for OpenFeature. + * This annotation allows you to specify a list of flags for a specific domain. + */ @Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Repeatable(value = OpenFeatures.class) @ExtendWith(OpenFeatureExtension.class) public @interface OpenFeature { + /** + * the provider domain used for this configuration. + */ String domain() default ""; + /** + * Collection of {@link Flag} configurations for this domain. + */ Flag[] value(); } diff --git a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureDefaultDomain.java b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureDefaultDomain.java index 156a683e0..f88fcbbe4 100644 --- a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureDefaultDomain.java +++ b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureDefaultDomain.java @@ -2,12 +2,21 @@ import org.junit.jupiter.api.extension.ExtendWith; -import java.lang.annotation.*; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +/** + * Configuration of a default domain for standalone {@link Flag} configurations. + */ @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @ExtendWith(OpenFeatureExtension.class) public @interface OpenFeatureDefaultDomain { + /** + * the default domain. + */ String value() default ""; } diff --git a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureExtension.java b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureExtension.java index 1ef14969b..6d5a677ba 100644 --- a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureExtension.java +++ b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureExtension.java @@ -5,53 +5,28 @@ import dev.openfeature.sdk.providers.memory.Flag; import dev.openfeature.sdk.providers.memory.InMemoryProvider; import org.apache.commons.lang3.BooleanUtils; -import org.junit.jupiter.api.extension.*; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; import org.junitpioneer.internal.PioneerAnnotationUtils; import java.util.Arrays; import java.util.HashMap; import java.util.Map; -public class OpenFeatureExtension implements BeforeEachCallback, AfterEachCallback, ParameterResolver { +/** + * JUnit5 Extension for OpenFeature. + */ +public class OpenFeatureExtension implements BeforeEachCallback, AfterEachCallback { OpenFeatureAPI api = OpenFeatureAPI.getInstance(); - - @Override - public void afterEach(ExtensionContext extensionContext) throws Exception { - - @SuppressWarnings("unchecked") Map>> configuration = - (Map>>) getStore(extensionContext).get("config"); - for (Map.Entry>> stringMapEntry : configuration.entrySet()) { - InMemoryProvider inMemoryProvider = new InMemoryProvider(stringMapEntry.getValue()); - if (stringMapEntry.getKey().isEmpty()) { - api.setProvider(new NoOpProvider()); - } else { - api.setProvider(stringMapEntry.getKey(), new NoOpProvider()); - } - } - } - - - @Override - public void beforeEach(ExtensionContext extensionContext) throws Exception { - Map>> configuration = handleSimpleConfiguration(extensionContext); - configuration.putAll(handleExtendedConfiguration(extensionContext, configuration)); - - for (Map.Entry>> stringMapEntry : configuration.entrySet()) { - InMemoryProvider inMemoryProvider = new InMemoryProvider(stringMapEntry.getValue()); - if (stringMapEntry.getKey().isEmpty()) { - api.setProvider(inMemoryProvider); - } else { - api.setProvider(stringMapEntry.getKey(), inMemoryProvider); - } - } - - getStore(extensionContext).put("config", configuration); - } - - private static Map>> handleExtendedConfiguration(ExtensionContext extensionContext, Map>> configuration) { - PioneerAnnotationUtils.findAllEnclosingRepeatableAnnotations(extensionContext, OpenFeature.class) + private static Map>> handleExtendedConfiguration( + ExtensionContext extensionContext, + Map>> configuration + ) { + PioneerAnnotationUtils + .findAllEnclosingRepeatableAnnotations(extensionContext, OpenFeature.class) .forEachOrdered(annotation -> { Map> domainFlags = configuration.getOrDefault(annotation.domain(), new HashMap<>()); @@ -68,10 +43,13 @@ private static Map>> handleExtendedConfiguration(Ext private static Map>> handleSimpleConfiguration(ExtensionContext extensionContext) { Map>> configuration = new HashMap<>(); - String defaultDomain = PioneerAnnotationUtils.findClosestEnclosingAnnotation(extensionContext, OpenFeatureDefaultDomain.class) - .map(OpenFeatureDefaultDomain::value) - .orElse(""); - PioneerAnnotationUtils.findAllEnclosingRepeatableAnnotations(extensionContext, dev.openfeature.contrib.tools.junitopenfeature.Flag.class) + String defaultDomain = PioneerAnnotationUtils + .findClosestEnclosingAnnotation(extensionContext, OpenFeatureDefaultDomain.class) + .map(OpenFeatureDefaultDomain::value).orElse(""); + PioneerAnnotationUtils + .findAllEnclosingRepeatableAnnotations( + extensionContext, + dev.openfeature.contrib.tools.junitopenfeature.Flag.class) .forEachOrdered(flag -> { Map> domainFlags = configuration.getOrDefault(defaultDomain, new HashMap<>()); if (!domainFlags.containsKey(flag.name())) { @@ -103,13 +81,34 @@ private static Flag.FlagBuilder generateFlagBuilder(dev.openfeature.contrib.t } @Override - public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { - return false; + public void afterEach(ExtensionContext extensionContext) throws Exception { + + @SuppressWarnings("unchecked") Map>> configuration = + (Map>>) getStore(extensionContext).get("config"); + for (Map.Entry>> stringMapEntry : configuration.entrySet()) { + if (stringMapEntry.getKey().isEmpty()) { + api.setProvider(new NoOpProvider()); + } else { + api.setProvider(stringMapEntry.getKey(), new NoOpProvider()); + } + } } @Override - public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { - return null; + public void beforeEach(ExtensionContext extensionContext) throws Exception { + Map>> configuration = handleSimpleConfiguration(extensionContext); + configuration.putAll(handleExtendedConfiguration(extensionContext, configuration)); + + for (Map.Entry>> stringMapEntry : configuration.entrySet()) { + InMemoryProvider inMemoryProvider = new InMemoryProvider(stringMapEntry.getValue()); + if (stringMapEntry.getKey().isEmpty()) { + api.setProvider(inMemoryProvider); + } else { + api.setProvider(stringMapEntry.getKey(), inMemoryProvider); + } + } + + getStore(extensionContext).put("config", configuration); } private ExtensionContext.Store getStore(ExtensionContext context) { diff --git a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatures.java b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatures.java index ab4d9c20d..c06002378 100644 --- a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatures.java +++ b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatures.java @@ -2,12 +2,21 @@ import org.junit.jupiter.api.extension.ExtendWith; -import java.lang.annotation.*; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; -@Target({ ElementType.METHOD, ElementType.TYPE }) +/** + * Collection of {@link OpenFeature} configurations. + */ +@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @ExtendWith(OpenFeatureExtension.class) public @interface OpenFeatures { + /** + * Collection of {@link OpenFeature} configurations. + */ OpenFeature[] value() default {}; } From 5787a4a06e5f747c51bcf6ca617c70b86b469992 Mon Sep 17 00:00:00 2001 From: Simon Schrottner Date: Mon, 22 Jul 2024 14:31:13 +0200 Subject: [PATCH 3/8] chore: more types and tests Signed-off-by: Simon Schrottner --- .../OpenFeatureExtension.java | 8 + .../junitopenfeature/BooleanFlagTest.java | 169 +++++++++++++++++ .../junitopenfeature/DoubleFlagTest.java | 176 ++++++++++++++++++ .../junitopenfeature/IntegerFlagTest.java | 176 ++++++++++++++++++ .../OpenFeatureExtensionTest.java | 157 ---------------- .../junitopenfeature/StringFlagTest.java | 174 +++++++++++++++++ 6 files changed, 703 insertions(+), 157 deletions(-) create mode 100644 tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/BooleanFlagTest.java create mode 100644 tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/DoubleFlagTest.java create mode 100644 tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/IntegerFlagTest.java create mode 100644 tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/StringFlagTest.java diff --git a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureExtension.java b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureExtension.java index 6d5a677ba..fb18e52ae 100644 --- a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureExtension.java +++ b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureExtension.java @@ -73,6 +73,14 @@ private static Flag.FlagBuilder generateFlagBuilder(dev.openfeature.contrib.t builder = Flag.builder(); builder.variant(flag.value(), flag.value()); break; + case "Integer": + builder = Flag.builder(); + builder.variant(flag.value(), Integer.parseInt(flag.value())); + break; + case "Double": + builder = Flag.builder(); + builder.variant(flag.value(), Double.parseDouble(flag.value())); + break; default: throw new IllegalArgumentException("Unsupported flag type: " + flag.value()); } diff --git a/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/BooleanFlagTest.java b/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/BooleanFlagTest.java new file mode 100644 index 000000000..a671d16f6 --- /dev/null +++ b/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/BooleanFlagTest.java @@ -0,0 +1,169 @@ +package dev.openfeature.contrib.tools.junitopenfeature; + +import dev.openfeature.sdk.Client; +import dev.openfeature.sdk.OpenFeatureAPI; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; + +import static org.assertj.core.api.Assertions.assertThat; + +@Execution(ExecutionMode.SAME_THREAD) +class BooleanFlagTest { + + private static final String FLAG = "boolean-flag"; + + @Nested + class SimpleConfig { + + @Nested + @Flag(name = FLAG, value = "true") + @Flag(name = FLAG + "2", value = "true") + @Flag(name = FLAG + "3", value = "true") + class onClass { + @Test + void multipleFlagsSimple() { + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client.getBooleanValue(FLAG, false)).isTrue(); + assertThat(client.getBooleanValue(FLAG + "2", false)).isTrue(); + assertThat(client.getBooleanValue(FLAG + "3", false)).isTrue(); + } + } + + @Test + @Flag(name = FLAG, value = "true") + void existingSimpleFlagIsRetrieved() { + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client.getBooleanValue(FLAG, false)).isTrue(); + } + + @Test + @Flag(name = FLAG, value = "true") + @Flag(name = FLAG + "2", value = "true") + @Flag(name = FLAG + "3", value = "true") + void multipleFlagsSimple() { + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client.getBooleanValue(FLAG, false)).isTrue(); + assertThat(client.getBooleanValue(FLAG + "2", false)).isTrue(); + assertThat(client.getBooleanValue(FLAG + "3", false)).isTrue(); + } + } + + @Nested + @OpenFeatureDefaultDomain("testSpecific") + class SimpleConfigWithDefault { + @Nested + @Flag(name = FLAG, value = "true") + @Flag(name = FLAG + "2", value = "true") + @Flag(name = FLAG + "3", value = "true") + class onClass { + @Test + void multipleFlagsSimple() { + Client client = OpenFeatureAPI.getInstance().getClient("testSpecific"); + assertThat(client.getBooleanValue(FLAG, false)).isTrue(); + assertThat(client.getBooleanValue(FLAG + "2", false)).isTrue(); + assertThat(client.getBooleanValue(FLAG + "3", false)).isTrue(); + } + } + + @Test + @Flag(name = FLAG, value = "true") + void existingSimpleFlagIsRetrieved() { + Client client = OpenFeatureAPI.getInstance().getClient("testSpecific"); + assertThat(client.getBooleanValue(FLAG, false)).isTrue(); + } + + @Test + @Flag(name = FLAG, value = "true") + @Flag(name = FLAG + "2", value = "true") + @Flag(name = FLAG + "3", value = "true") + void multipleFlagsSimple() { + Client client = OpenFeatureAPI.getInstance().getClient("testSpecific"); + assertThat(client.getBooleanValue(FLAG, false)).isTrue(); + assertThat(client.getBooleanValue(FLAG + "2", false)).isTrue(); + assertThat(client.getBooleanValue(FLAG + "3", false)).isTrue(); + } + } + + @Nested + class ExtendedConfig { + @Test + @OpenFeature({ + @Flag(name = FLAG, value = "true") + }) + void existingFlagIsRetrieved() { + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client.getBooleanValue(FLAG, false)).isTrue(); + } + + @Test + @OpenFeature( + @Flag(name = FLAG, value = "truesadf") + ) + void strangeFlagValue() { + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client.getBooleanValue(FLAG, false)).isFalse(); + } + + @Test + @OpenFeature( + @Flag(name = FLAG, value = "true") + ) + void nonExistingFlagIsFallbacked() { + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client.getBooleanValue("nonSetFlag", false)).isFalse(); + } + + @Test + @OpenFeature({ + @Flag(name = FLAG, value = "true"), + @Flag(name = FLAG + "2", value = "true"), + @Flag(name = FLAG + "3", value = "true"), + }) + void multipleFlags() { + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client.getBooleanValue(FLAG, false)).isTrue(); + assertThat(client.getBooleanValue(FLAG + "2", false)).isTrue(); + assertThat(client.getBooleanValue(FLAG + "3", false)).isTrue(); + } + + @Nested + @OpenFeature({ + @Flag(name = FLAG, value = "true"), + @Flag(name = FLAG + "2", value = "false"), + }) + class MultipleFlags { + @Test + @OpenFeature({ + @Flag(name = FLAG + "2", value = "true"), + @Flag(name = FLAG + "3", value = "true"), + }) + void multipleFlags() { + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client.getBooleanValue(FLAG, false)).isTrue(); + assertThat(client.getBooleanValue(FLAG + "2", false)).isTrue(); + assertThat(client.getBooleanValue(FLAG + "3", false)).isTrue(); + } + + @Test + @OpenFeature( + domain = "testSpecific", + value = { + @Flag(name = FLAG + "2", value = "true"), + @Flag(name = FLAG + "3", value = "true"), + }) + void multipleFlagsOnMultipleDomains() { + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client.getBooleanValue(FLAG, false)).isTrue(); + assertThat(client.getBooleanValue(FLAG + "2", true)).isFalse(); + assertThat(client.getBooleanValue(FLAG + "3", false)).isFalse(); + + Client testSpecific = OpenFeatureAPI.getInstance().getClient("testSpecific"); + assertThat(testSpecific.getBooleanValue(FLAG, false)).isFalse(); + assertThat(testSpecific.getBooleanValue(FLAG + "2", false)).isTrue(); + assertThat(testSpecific.getBooleanValue(FLAG + "3", false)).isTrue(); + } + } + } +} diff --git a/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/DoubleFlagTest.java b/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/DoubleFlagTest.java new file mode 100644 index 000000000..81fee0cdc --- /dev/null +++ b/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/DoubleFlagTest.java @@ -0,0 +1,176 @@ +package dev.openfeature.contrib.tools.junitopenfeature; + +import dev.openfeature.sdk.Client; +import dev.openfeature.sdk.OpenFeatureAPI; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; + +import static org.assertj.core.api.Assertions.assertThat; + +@Execution(ExecutionMode.SAME_THREAD) +class DoubleFlagTest { + + private static final String FLAG = "double-flag"; + private static final Double FALLBACK = -1.d; + private static final Double FLAG_VALUE = 1.d; + private static final String FLAG_VALUE_STRING = "1"; + private static final Double FLAG_VALUE_ALTERNATIVE = 0.d; + private static final String FLAG_VALUE_STRING_ALTERNATIVE = "0"; + private static final String SPECIFIC_DOMAIN = "testSpecific"; + + + @Nested + class SimpleConfig { + + @Test + @Flag(name = FLAG, value = FLAG_VALUE_STRING, valueType = Double.class) + void existingSimpleFlagIsRetrieved() { + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client.getDoubleValue(FLAG, FALLBACK)).isEqualTo(FLAG_VALUE); + } + + @Test + @Flag(name = FLAG, value = FLAG_VALUE_STRING, valueType = Double.class) + @Flag(name = FLAG + "2", value = FLAG_VALUE_STRING, valueType = Double.class) + @Flag(name = FLAG + "3", value = FLAG_VALUE_STRING, valueType = Double.class) + void multipleFlagsSimple() { + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client.getDoubleValue(FLAG, FALLBACK)).isEqualTo(FLAG_VALUE); + assertThat(client.getDoubleValue(FLAG + "2", FALLBACK)).isEqualTo(FLAG_VALUE); + assertThat(client.getDoubleValue(FLAG + "3", FALLBACK)).isEqualTo(FLAG_VALUE); + } + + @Nested + @Flag(name = FLAG, value = FLAG_VALUE_STRING , valueType = Double.class) + @Flag(name = FLAG + "2", value = FLAG_VALUE_STRING, valueType = Double.class) + @Flag(name = FLAG + "3", value = FLAG_VALUE_STRING, valueType = Double.class) + class onClass { + @Test + void multipleFlagsSimple() { + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client.getDoubleValue(FLAG, FALLBACK)).isEqualTo(FLAG_VALUE); + assertThat(client.getDoubleValue(FLAG + "2", FALLBACK)).isEqualTo(FLAG_VALUE); + assertThat(client.getDoubleValue(FLAG + "3", FALLBACK)).isEqualTo(FLAG_VALUE); + } + } + } + + @Nested + @OpenFeatureDefaultDomain(SPECIFIC_DOMAIN) + class SimpleConfigWithDefault { + @Test + @Flag(name = FLAG, value = FLAG_VALUE_STRING, valueType = Double.class) + void existingSimpleFlagIsRetrieved() { + Client client = OpenFeatureAPI.getInstance().getClient(SPECIFIC_DOMAIN); + assertThat(client.getDoubleValue(FLAG, FALLBACK)).isEqualTo(FLAG_VALUE); + } + + @Test + @Flag(name = FLAG, value = FLAG_VALUE_STRING, valueType = Double.class) + @Flag(name = FLAG + "2", value = FLAG_VALUE_STRING, valueType = Double.class) + @Flag(name = FLAG + "3", value = FLAG_VALUE_STRING, valueType = Double.class) + void multipleFlagsSimple() { + Client client = OpenFeatureAPI.getInstance().getClient(SPECIFIC_DOMAIN); + assertThat(client.getDoubleValue(FLAG, FALLBACK)).isEqualTo(FLAG_VALUE); + assertThat(client.getDoubleValue(FLAG + "2", FALLBACK)).isEqualTo(FLAG_VALUE); + assertThat(client.getDoubleValue(FLAG + "3", FALLBACK)).isEqualTo(FLAG_VALUE); + } + + @Nested + @Flag(name = FLAG, value = FLAG_VALUE_STRING, valueType = Double.class) + @Flag(name = FLAG + "2", value = FLAG_VALUE_STRING, valueType = Double.class) + @Flag(name = FLAG + "3", value = FLAG_VALUE_STRING, valueType = Double.class) + class onClass { + @Test + void multipleFlagsSimple() { + Client client = OpenFeatureAPI.getInstance().getClient("testSpecific"); + assertThat(client.getDoubleValue(FLAG, FALLBACK)).isEqualTo(FLAG_VALUE); + assertThat(client.getDoubleValue(FLAG + "2", FALLBACK)).isEqualTo(FLAG_VALUE); + assertThat(client.getDoubleValue(FLAG + "3", FALLBACK)).isEqualTo(FLAG_VALUE); + } + } + } + + @Nested + class ExtendedConfig { + @Test + @OpenFeature({ + @Flag(name = FLAG, value = FLAG_VALUE_STRING, valueType = Double.class) + }) + void existingFlagIsRetrieved() { + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client.getDoubleValue(FLAG, FALLBACK)).isEqualTo(FLAG_VALUE); + } + + @Test + @OpenFeature( + @Flag(name = FLAG, value = "truesadf") + ) + void strangeFlagValue() { + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client.getDoubleValue(FLAG, FALLBACK)).isEqualTo(FALLBACK); + } + + @Test + @OpenFeature( + @Flag(name = FLAG, value = FLAG_VALUE_STRING, valueType = Double.class) + ) + void nonExistingFlagIsFallbacked() { + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client.getDoubleValue("nonSetFlag", FALLBACK)).isEqualTo(FALLBACK); + } + + @Test + @OpenFeature({ + @Flag(name = FLAG, value = FLAG_VALUE_STRING, valueType = Double.class), + @Flag(name = FLAG + "2", value = FLAG_VALUE_STRING, valueType = Double.class), + @Flag(name = FLAG + "3", value = FLAG_VALUE_STRING, valueType = Double.class), + }) + void multipleFlags() { + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client.getDoubleValue(FLAG, FALLBACK)).isEqualTo(FLAG_VALUE); + assertThat(client.getDoubleValue(FLAG + "2", FALLBACK)).isEqualTo(FLAG_VALUE); + assertThat(client.getDoubleValue(FLAG + "3", FALLBACK)).isEqualTo(FLAG_VALUE); + } + + @Nested + @OpenFeature({ + @Flag(name = FLAG, value = FLAG_VALUE_STRING, valueType = Double.class), + @Flag(name = FLAG + "2", value = FLAG_VALUE_STRING_ALTERNATIVE, valueType = Double.class), + }) + class MultipleFlags { + @Test + @OpenFeature({ + @Flag(name = FLAG + "2", value = FLAG_VALUE_STRING, valueType = Double.class), + @Flag(name = FLAG + "3", value = FLAG_VALUE_STRING, valueType = Double.class), + }) + void multipleFlags() { + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client.getDoubleValue(FLAG, FALLBACK)).isEqualTo(FLAG_VALUE); + assertThat(client.getDoubleValue(FLAG + "2", FALLBACK)).isEqualTo(FLAG_VALUE); + assertThat(client.getDoubleValue(FLAG + "3", FALLBACK)).isEqualTo(FLAG_VALUE); + } + + @Test + @OpenFeature( + domain = SPECIFIC_DOMAIN, + value = { + @Flag(name = FLAG + "2", value = FLAG_VALUE_STRING, valueType = Double.class), + @Flag(name = FLAG + "3", value = FLAG_VALUE_STRING, valueType = Double.class), + }) + void multipleFlagsOnMultipleDomains() { + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client.getDoubleValue(FLAG, FALLBACK)).isEqualTo(FLAG_VALUE); + assertThat(client.getDoubleValue(FLAG + "2", FALLBACK)).isEqualTo(FLAG_VALUE_ALTERNATIVE); + assertThat(client.getDoubleValue(FLAG + "3", FALLBACK)).isEqualTo(FALLBACK); + + Client testSpecific = OpenFeatureAPI.getInstance().getClient(SPECIFIC_DOMAIN); + assertThat(testSpecific.getDoubleValue(FLAG, FALLBACK)).isEqualTo(FALLBACK); + assertThat(testSpecific.getDoubleValue(FLAG + "2", FALLBACK)).isEqualTo(FLAG_VALUE); + assertThat(testSpecific.getDoubleValue(FLAG + "3", FALLBACK)).isEqualTo(FLAG_VALUE); + } + } + } +} diff --git a/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/IntegerFlagTest.java b/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/IntegerFlagTest.java new file mode 100644 index 000000000..dcd43fef3 --- /dev/null +++ b/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/IntegerFlagTest.java @@ -0,0 +1,176 @@ +package dev.openfeature.contrib.tools.junitopenfeature; + +import dev.openfeature.sdk.Client; +import dev.openfeature.sdk.OpenFeatureAPI; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; + +import static org.assertj.core.api.Assertions.assertThat; + +@Execution(ExecutionMode.SAME_THREAD) +class IntegerFlagTest { + + private static final String FLAG = "integer-flag"; + private static final Integer FALLBACK = -1; + private static final Integer FLAG_VALUE = 1; + private static final String FLAG_VALUE_STRING = "1"; + private static final Integer FLAG_VALUE_ALTERNATIVE = 0; + private static final String FLAG_VALUE_STRING_ALTERNATIVE = "0"; + private static final String SPECIFIC_DOMAIN = "testSpecific"; + + + @Nested + class SimpleConfig { + + @Test + @Flag(name = FLAG, value = FLAG_VALUE_STRING, valueType = Integer.class) + void existingSimpleFlagIsRetrieved() { + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client.getIntegerValue(FLAG, FALLBACK)).isEqualTo(FLAG_VALUE); + } + + @Test + @Flag(name = FLAG, value = FLAG_VALUE_STRING, valueType = Integer.class) + @Flag(name = FLAG + "2", value = FLAG_VALUE_STRING, valueType = Integer.class) + @Flag(name = FLAG + "3", value = FLAG_VALUE_STRING, valueType = Integer.class) + void multipleFlagsSimple() { + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client.getIntegerValue(FLAG, FALLBACK)).isEqualTo(FLAG_VALUE); + assertThat(client.getIntegerValue(FLAG + "2", FALLBACK)).isEqualTo(FLAG_VALUE); + assertThat(client.getIntegerValue(FLAG + "3", FALLBACK)).isEqualTo(FLAG_VALUE); + } + + @Nested + @Flag(name = FLAG, value = FLAG_VALUE_STRING , valueType = Integer.class) + @Flag(name = FLAG + "2", value = FLAG_VALUE_STRING, valueType = Integer.class) + @Flag(name = FLAG + "3", value = FLAG_VALUE_STRING, valueType = Integer.class) + class onClass { + @Test + void multipleFlagsSimple() { + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client.getIntegerValue(FLAG, FALLBACK)).isEqualTo(FLAG_VALUE); + assertThat(client.getIntegerValue(FLAG + "2", FALLBACK)).isEqualTo(FLAG_VALUE); + assertThat(client.getIntegerValue(FLAG + "3", FALLBACK)).isEqualTo(FLAG_VALUE); + } + } + } + + @Nested + @OpenFeatureDefaultDomain(SPECIFIC_DOMAIN) + class SimpleConfigWithDefault { + @Test + @Flag(name = FLAG, value = FLAG_VALUE_STRING, valueType = Integer.class) + void existingSimpleFlagIsRetrieved() { + Client client = OpenFeatureAPI.getInstance().getClient(SPECIFIC_DOMAIN); + assertThat(client.getIntegerValue(FLAG, FALLBACK)).isEqualTo(FLAG_VALUE); + } + + @Test + @Flag(name = FLAG, value = FLAG_VALUE_STRING, valueType = Integer.class) + @Flag(name = FLAG + "2", value = FLAG_VALUE_STRING, valueType = Integer.class) + @Flag(name = FLAG + "3", value = FLAG_VALUE_STRING, valueType = Integer.class) + void multipleFlagsSimple() { + Client client = OpenFeatureAPI.getInstance().getClient(SPECIFIC_DOMAIN); + assertThat(client.getIntegerValue(FLAG, FALLBACK)).isEqualTo(FLAG_VALUE); + assertThat(client.getIntegerValue(FLAG + "2", FALLBACK)).isEqualTo(FLAG_VALUE); + assertThat(client.getIntegerValue(FLAG + "3", FALLBACK)).isEqualTo(FLAG_VALUE); + } + + @Nested + @Flag(name = FLAG, value = FLAG_VALUE_STRING, valueType = Integer.class) + @Flag(name = FLAG + "2", value = FLAG_VALUE_STRING, valueType = Integer.class) + @Flag(name = FLAG + "3", value = FLAG_VALUE_STRING, valueType = Integer.class) + class onClass { + @Test + void multipleFlagsSimple() { + Client client = OpenFeatureAPI.getInstance().getClient("testSpecific"); + assertThat(client.getIntegerValue(FLAG, FALLBACK)).isEqualTo(FLAG_VALUE); + assertThat(client.getIntegerValue(FLAG + "2", FALLBACK)).isEqualTo(FLAG_VALUE); + assertThat(client.getIntegerValue(FLAG + "3", FALLBACK)).isEqualTo(FLAG_VALUE); + } + } + } + + @Nested + class ExtendedConfig { + @Test + @OpenFeature({ + @Flag(name = FLAG, value = FLAG_VALUE_STRING, valueType = Integer.class) + }) + void existingFlagIsRetrieved() { + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client.getIntegerValue(FLAG, FALLBACK)).isEqualTo(FLAG_VALUE); + } + + @Test + @OpenFeature( + @Flag(name = FLAG, value = "truesadf") + ) + void strangeFlagValue() { + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client.getIntegerValue(FLAG, FALLBACK)).isEqualTo(FALLBACK); + } + + @Test + @OpenFeature( + @Flag(name = FLAG, value = FLAG_VALUE_STRING, valueType = Integer.class) + ) + void nonExistingFlagIsFallbacked() { + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client.getIntegerValue("nonSetFlag", FALLBACK)).isEqualTo(FALLBACK); + } + + @Test + @OpenFeature({ + @Flag(name = FLAG, value = FLAG_VALUE_STRING, valueType = Integer.class), + @Flag(name = FLAG + "2", value = FLAG_VALUE_STRING, valueType = Integer.class), + @Flag(name = FLAG + "3", value = FLAG_VALUE_STRING, valueType = Integer.class), + }) + void multipleFlags() { + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client.getIntegerValue(FLAG, FALLBACK)).isEqualTo(FLAG_VALUE); + assertThat(client.getIntegerValue(FLAG + "2", FALLBACK)).isEqualTo(FLAG_VALUE); + assertThat(client.getIntegerValue(FLAG + "3", FALLBACK)).isEqualTo(FLAG_VALUE); + } + + @Nested + @OpenFeature({ + @Flag(name = FLAG, value = FLAG_VALUE_STRING, valueType = Integer.class), + @Flag(name = FLAG + "2", value = FLAG_VALUE_STRING_ALTERNATIVE, valueType = Integer.class), + }) + class MultipleFlags { + @Test + @OpenFeature({ + @Flag(name = FLAG + "2", value = FLAG_VALUE_STRING, valueType = Integer.class), + @Flag(name = FLAG + "3", value = FLAG_VALUE_STRING, valueType = Integer.class), + }) + void multipleFlags() { + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client.getIntegerValue(FLAG, FALLBACK)).isEqualTo(FLAG_VALUE); + assertThat(client.getIntegerValue(FLAG + "2", FALLBACK)).isEqualTo(FLAG_VALUE); + assertThat(client.getIntegerValue(FLAG + "3", FALLBACK)).isEqualTo(FLAG_VALUE); + } + + @Test + @OpenFeature( + domain = SPECIFIC_DOMAIN, + value = { + @Flag(name = FLAG + "2", value = FLAG_VALUE_STRING, valueType = Integer.class), + @Flag(name = FLAG + "3", value = FLAG_VALUE_STRING, valueType = Integer.class), + }) + void multipleFlagsOnMultipleDomains() { + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client.getIntegerValue(FLAG, FALLBACK)).isEqualTo(FLAG_VALUE); + assertThat(client.getIntegerValue(FLAG + "2", FALLBACK)).isEqualTo(FLAG_VALUE_ALTERNATIVE); + assertThat(client.getIntegerValue(FLAG + "3", FALLBACK)).isEqualTo(FALLBACK); + + Client testSpecific = OpenFeatureAPI.getInstance().getClient(SPECIFIC_DOMAIN); + assertThat(testSpecific.getIntegerValue(FLAG, FALLBACK)).isEqualTo(FALLBACK); + assertThat(testSpecific.getIntegerValue(FLAG + "2", FALLBACK)).isEqualTo(FLAG_VALUE); + assertThat(testSpecific.getIntegerValue(FLAG + "3", FALLBACK)).isEqualTo(FLAG_VALUE); + } + } + } +} diff --git a/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureExtensionTest.java b/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureExtensionTest.java index dd79278a5..8d72b9af7 100644 --- a/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureExtensionTest.java +++ b/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureExtensionTest.java @@ -75,162 +75,5 @@ void clientIsSetWithDomain() { assertThat(client).isNotNull(); } } - - } - - private static final String BOOLEAN_FLAG = "boolean-flag"; - - @Nested - class Boolean { - @Nested - class SimpleConfig { - - @Nested - @Flag(name = BOOLEAN_FLAG, value = "true") - @Flag(name = BOOLEAN_FLAG + "2", value = "true") - @Flag(name = BOOLEAN_FLAG + "3", value = "true") - class onClass { - @Test - void multipleFlagsSimple() { - Client client = OpenFeatureAPI.getInstance().getClient(); - assertThat(client.getBooleanValue(BOOLEAN_FLAG, false)).isTrue(); - assertThat(client.getBooleanValue(BOOLEAN_FLAG + "2", false)).isTrue(); - assertThat(client.getBooleanValue(BOOLEAN_FLAG + "3", false)).isTrue(); - } - } - @Test - @Flag(name = BOOLEAN_FLAG, value = "true") - void existingSimpleFlagIsRetrieved() { - Client client = OpenFeatureAPI.getInstance().getClient(); - assertThat(client.getBooleanValue(BOOLEAN_FLAG, false)).isTrue(); - } - - @Test - @Flag(name = BOOLEAN_FLAG, value = "true") - @Flag(name = BOOLEAN_FLAG + "2", value = "true") - @Flag(name = BOOLEAN_FLAG + "3", value = "true") - void multipleFlagsSimple() { - Client client = OpenFeatureAPI.getInstance().getClient(); - assertThat(client.getBooleanValue(BOOLEAN_FLAG, false)).isTrue(); - assertThat(client.getBooleanValue(BOOLEAN_FLAG + "2", false)).isTrue(); - assertThat(client.getBooleanValue(BOOLEAN_FLAG + "3", false)).isTrue(); - } - } - - @Nested - @OpenFeatureDefaultDomain("testSpecific") - class SimpleConfigWithDefault { - @Nested - @Flag(name = BOOLEAN_FLAG, value = "true") - @Flag(name = BOOLEAN_FLAG + "2", value = "true") - @Flag(name = BOOLEAN_FLAG + "3", value = "true") - class onClass { - @Test - void multipleFlagsSimple() { - Client client = OpenFeatureAPI.getInstance().getClient("testSpecific"); - assertThat(client.getBooleanValue(BOOLEAN_FLAG, false)).isTrue(); - assertThat(client.getBooleanValue(BOOLEAN_FLAG + "2", false)).isTrue(); - assertThat(client.getBooleanValue(BOOLEAN_FLAG + "3", false)).isTrue(); - } - } - @Test - @Flag(name = BOOLEAN_FLAG, value = "true") - void existingSimpleFlagIsRetrieved() { - Client client = OpenFeatureAPI.getInstance().getClient("testSpecific"); - assertThat(client.getBooleanValue(BOOLEAN_FLAG, false)).isTrue(); - } - - @Test - @Flag(name = BOOLEAN_FLAG, value = "true") - @Flag(name = BOOLEAN_FLAG + "2", value = "true") - @Flag(name = BOOLEAN_FLAG + "3", value = "true") - void multipleFlagsSimple() { - Client client = OpenFeatureAPI.getInstance().getClient("testSpecific"); - assertThat(client.getBooleanValue(BOOLEAN_FLAG, false)).isTrue(); - assertThat(client.getBooleanValue(BOOLEAN_FLAG + "2", false)).isTrue(); - assertThat(client.getBooleanValue(BOOLEAN_FLAG + "3", false)).isTrue(); - } - } - - @Nested - class ExtendedConfig { - @Test - @OpenFeature({ - @Flag(name = BOOLEAN_FLAG, value = "true") - }) - void existingFlagIsRetrieved() { - Client client = OpenFeatureAPI.getInstance().getClient(); - assertThat(client.getBooleanValue(BOOLEAN_FLAG, false)).isTrue(); - } - - @Test - @OpenFeature( - @Flag(name = BOOLEAN_FLAG, value = "truesadf") - ) - void strangeFlagValue() { - Client client = OpenFeatureAPI.getInstance().getClient(); - assertThat(client.getBooleanValue(BOOLEAN_FLAG, false)).isFalse(); - } - - @Test - @OpenFeature( - @Flag(name = BOOLEAN_FLAG, value = "true") - ) - void nonExistingFlagIsFallbacked() { - Client client = OpenFeatureAPI.getInstance().getClient(); - assertThat(client.getBooleanValue("nonSetFlag", false)).isFalse(); - } - - @Test - @OpenFeature({ - @Flag(name = BOOLEAN_FLAG, value = "true"), - @Flag(name = BOOLEAN_FLAG + "2", value = "true"), - @Flag(name = BOOLEAN_FLAG + "3", value = "true"), - }) - void multipleFlags() { - Client client = OpenFeatureAPI.getInstance().getClient(); - assertThat(client.getBooleanValue(BOOLEAN_FLAG, false)).isTrue(); - assertThat(client.getBooleanValue(BOOLEAN_FLAG + "2", false)).isTrue(); - assertThat(client.getBooleanValue(BOOLEAN_FLAG + "3", false)).isTrue(); - } - - @Nested - @OpenFeature({ - @Flag(name = BOOLEAN_FLAG, value = "true"), - @Flag(name = BOOLEAN_FLAG + "2", value = "false"), - }) - class MultipleFlags { - @Test - @OpenFeature({ - @Flag(name = BOOLEAN_FLAG + "2", value = "true"), - @Flag(name = BOOLEAN_FLAG + "3", value = "true"), - }) - void multipleFlags() { - Client client = OpenFeatureAPI.getInstance().getClient(); - assertThat(client.getBooleanValue(BOOLEAN_FLAG, false)).isTrue(); - assertThat(client.getBooleanValue(BOOLEAN_FLAG + "2", false)).isTrue(); - assertThat(client.getBooleanValue(BOOLEAN_FLAG + "3", false)).isTrue(); - } - - @Test - @OpenFeature( - domain = "testSpecific", - value = { - @Flag(name = BOOLEAN_FLAG + "2", value = "true"), - @Flag(name = BOOLEAN_FLAG + "3", value = "true"), - }) - void multipleFlagsOnMultipleDomains() { - Client client = OpenFeatureAPI.getInstance().getClient(); - assertThat(client.getBooleanValue(BOOLEAN_FLAG, false)).isTrue(); - assertThat(client.getBooleanValue(BOOLEAN_FLAG + "2", true)).isFalse(); - assertThat(client.getBooleanValue(BOOLEAN_FLAG + "3", false)).isFalse(); - - Client testSpecific = OpenFeatureAPI.getInstance().getClient("testSpecific"); - assertThat(testSpecific.getBooleanValue(BOOLEAN_FLAG, false)).isFalse(); - assertThat(testSpecific.getBooleanValue(BOOLEAN_FLAG + "2", false)).isTrue(); - assertThat(testSpecific.getBooleanValue(BOOLEAN_FLAG + "3", false)).isTrue(); - } - } - } } } diff --git a/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/StringFlagTest.java b/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/StringFlagTest.java new file mode 100644 index 000000000..2f7e9abc7 --- /dev/null +++ b/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/StringFlagTest.java @@ -0,0 +1,174 @@ +package dev.openfeature.contrib.tools.junitopenfeature; + +import dev.openfeature.sdk.Client; +import dev.openfeature.sdk.OpenFeatureAPI; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; + +import static org.assertj.core.api.Assertions.assertThat; + +@Execution(ExecutionMode.SAME_THREAD) +class StringFlagTest { + + private static final String FLAG = "string-flag"; + private static final String FALLBACK = "fallback"; + private static final String FLAG_VALUE = "true"; + private static final String FLAG_VALUE_ALTERNATIVE = "false"; + private static final String SPECIFIC_DOMAIN = "testSpecific"; + + + @Nested + class SimpleConfig { + + @Nested + @Flag(name = FLAG, value = FLAG_VALUE, valueType = String.class) + @Flag(name = FLAG + "2", value = FLAG_VALUE, valueType = String.class) + @Flag(name = FLAG + "3", value = FLAG_VALUE, valueType = String.class) + class onClass { + @Test + void multipleFlagsSimple() { + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client.getStringValue(FLAG, FALLBACK)).isEqualTo(FLAG_VALUE); + assertThat(client.getStringValue(FLAG + "2", FALLBACK)).isEqualTo(FLAG_VALUE); + assertThat(client.getStringValue(FLAG + "3", FALLBACK)).isEqualTo(FLAG_VALUE); + } + } + + @Test + @Flag(name = FLAG, value = FLAG_VALUE, valueType = String.class) + void existingSimpleFlagIsRetrieved() { + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client.getStringValue(FLAG, FALLBACK)).isEqualTo(FLAG_VALUE); + } + + @Test + @Flag(name = FLAG, value = FLAG_VALUE, valueType = String.class) + @Flag(name = FLAG + "2", value = FLAG_VALUE, valueType = String.class) + @Flag(name = FLAG + "3", value = FLAG_VALUE, valueType = String.class) + void multipleFlagsSimple() { + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client.getStringValue(FLAG, FALLBACK)).isEqualTo(FLAG_VALUE); + assertThat(client.getStringValue(FLAG + "2", FALLBACK)).isEqualTo(FLAG_VALUE); + assertThat(client.getStringValue(FLAG + "3", FALLBACK)).isEqualTo(FLAG_VALUE); + } + } + + @Nested + @OpenFeatureDefaultDomain(SPECIFIC_DOMAIN) + class SimpleConfigWithDefault { + @Nested + @Flag(name = FLAG , value = FLAG_VALUE, valueType = String.class) + @Flag(name = FLAG + "2" , value = FLAG_VALUE, valueType = String.class) + @Flag(name = FLAG + "3" , value = FLAG_VALUE, valueType = String.class) + class onClass { + @Test + void multipleFlagsSimple() { + Client client = OpenFeatureAPI.getInstance().getClient("testSpecific"); + assertThat(client.getStringValue(FLAG, FALLBACK)).isEqualTo(FLAG_VALUE); + assertThat(client.getStringValue(FLAG + "2", FALLBACK)).isEqualTo(FLAG_VALUE); + assertThat(client.getStringValue(FLAG + "3", FALLBACK)).isEqualTo(FLAG_VALUE); + } + } + + @Test + @Flag(name = FLAG , value = FLAG_VALUE, valueType = String.class) + void existingSimpleFlagIsRetrieved() { + Client client = OpenFeatureAPI.getInstance().getClient(SPECIFIC_DOMAIN); + assertThat(client.getStringValue(FLAG, FALLBACK)).isEqualTo(FLAG_VALUE); + } + + @Test + @Flag(name = FLAG , value = FLAG_VALUE, valueType = String.class) + @Flag(name = FLAG + "2" , value = FLAG_VALUE, valueType = String.class) + @Flag(name = FLAG + "3" , value = FLAG_VALUE, valueType = String.class) + void multipleFlagsSimple() { + Client client = OpenFeatureAPI.getInstance().getClient(SPECIFIC_DOMAIN); + assertThat(client.getStringValue(FLAG, FALLBACK)).isEqualTo(FLAG_VALUE); + assertThat(client.getStringValue(FLAG + "2", FALLBACK)).isEqualTo(FLAG_VALUE); + assertThat(client.getStringValue(FLAG + "3", FALLBACK)).isEqualTo(FLAG_VALUE); + } + } + + @Nested + class ExtendedConfig { + @Test + @OpenFeature({ + @Flag(name = FLAG , value = FLAG_VALUE, valueType = String.class) + }) + void existingFlagIsRetrieved() { + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client.getStringValue(FLAG, FALLBACK)).isEqualTo(FLAG_VALUE); + } + + @Test + @OpenFeature( + @Flag(name = FLAG, value = "truesadf") + ) + void strangeFlagValue() { + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client.getStringValue(FLAG, FALLBACK)).isEqualTo(FALLBACK); + } + + @Test + @OpenFeature( + @Flag(name = FLAG , value = FLAG_VALUE, valueType = String.class) + ) + void nonExistingFlagIsFallbacked() { + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client.getStringValue("nonSetFlag", FALLBACK)).isEqualTo(FALLBACK); + } + + @Test + @OpenFeature({ + @Flag(name = FLAG , value = FLAG_VALUE, valueType = String.class), + @Flag(name = FLAG + "2" , value = FLAG_VALUE, valueType = String.class), + @Flag(name = FLAG + "3" , value = FLAG_VALUE, valueType = String.class), + }) + void multipleFlags() { + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client.getStringValue(FLAG, FALLBACK)).isEqualTo(FLAG_VALUE); + assertThat(client.getStringValue(FLAG + "2", FALLBACK)).isEqualTo(FLAG_VALUE); + assertThat(client.getStringValue(FLAG + "3", FALLBACK)).isEqualTo(FLAG_VALUE); + } + + @Nested + @OpenFeature({ + @Flag(name = FLAG , value = FLAG_VALUE, valueType = String.class), + @Flag(name = FLAG + "2", value = FLAG_VALUE_ALTERNATIVE, valueType = String.class), + }) + class MultipleFlags { + @Test + @OpenFeature({ + @Flag(name = FLAG + "2" , value = FLAG_VALUE, valueType = String.class), + @Flag(name = FLAG + "3" , value = FLAG_VALUE, valueType = String.class), + }) + void multipleFlags() { + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client.getStringValue(FLAG, FALLBACK)).isEqualTo(FLAG_VALUE); + assertThat(client.getStringValue(FLAG + "2", FALLBACK)).isEqualTo(FLAG_VALUE); + assertThat(client.getStringValue(FLAG + "3", FALLBACK)).isEqualTo(FLAG_VALUE); + } + + @Test + @OpenFeature( + domain = SPECIFIC_DOMAIN, + value = { + @Flag(name = FLAG + "2" , value = FLAG_VALUE, valueType = String.class), + @Flag(name = FLAG + "3" , value = FLAG_VALUE, valueType = String.class), + }) + void multipleFlagsOnMultipleDomains() { + Client client = OpenFeatureAPI.getInstance().getClient(); + assertThat(client.getStringValue(FLAG, FALLBACK)).isEqualTo(FLAG_VALUE); + assertThat(client.getStringValue(FLAG + "2", FALLBACK)).isEqualTo(FLAG_VALUE_ALTERNATIVE); + assertThat(client.getStringValue(FLAG + "3", FALLBACK)).isEqualTo(FALLBACK); + + Client testSpecific = OpenFeatureAPI.getInstance().getClient(SPECIFIC_DOMAIN); + assertThat(testSpecific.getStringValue(FLAG, FALLBACK)).isEqualTo(FALLBACK); + assertThat(testSpecific.getStringValue(FLAG + "2", FALLBACK)).isEqualTo(FLAG_VALUE); + assertThat(testSpecific.getStringValue(FLAG + "3", FALLBACK)).isEqualTo(FLAG_VALUE); + } + } + } +} From b9dbf75f1a1c315af0b76e2cd50d1a65a7cad708 Mon Sep 17 00:00:00 2001 From: Simon Schrottner Date: Tue, 23 Jul 2024 13:55:23 +0200 Subject: [PATCH 4/8] chore: adding own testProvider with some Thread magic Signed-off-by: Simon Schrottner --- tools/junit-openfeature/README.md | 3 +- tools/junit-openfeature/pom.xml | 2 +- .../OpenFeatureExtension.java | 68 +++++++--- .../tools/junitopenfeature/TestProvider.java | 128 ++++++++++++++++++ .../{ => annotations}/Flag.java | 3 +- .../{ => annotations}/Flags.java | 3 +- .../{ => annotations}/OpenFeature.java | 3 +- .../OpenFeatureDefaultDomain.java | 3 +- .../{ => annotations}/OpenFeatures.java | 3 +- .../junitopenfeature/BooleanFlagTest.java | 6 +- .../junitopenfeature/DoubleFlagTest.java | 6 +- .../junitopenfeature/IntegerFlagTest.java | 6 +- .../OpenFeatureExtensionTest.java | 20 +-- .../junitopenfeature/StringFlagTest.java | 6 +- 14 files changed, 209 insertions(+), 51 deletions(-) create mode 100644 tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/TestProvider.java rename tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/{ => annotations}/Flag.java (85%) rename tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/{ => annotations}/Flags.java (78%) rename tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/{ => annotations}/OpenFeature.java (85%) rename tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/{ => annotations}/OpenFeatureDefaultDomain.java (78%) rename tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/{ => annotations}/OpenFeatures.java (79%) diff --git a/tools/junit-openfeature/README.md b/tools/junit-openfeature/README.md index 4d513db4e..ae0f8f1a5 100644 --- a/tools/junit-openfeature/README.md +++ b/tools/junit-openfeature/README.md @@ -5,7 +5,8 @@ A JUnit5 extension to reduce boilerplate code for testing code which utilizes Op ## Getting Started We are supporting two different flavors for testing, a [simple](#simple-configuration) and an [extended](#extended-configuration) configuration. - + +Notice: We are most likely not multithread compatible! ### Simple Configuration Choose the simple configuration if you are only testing in one domain. diff --git a/tools/junit-openfeature/pom.xml b/tools/junit-openfeature/pom.xml index 2fb7117e5..4d44f95cf 100644 --- a/tools/junit-openfeature/pom.xml +++ b/tools/junit-openfeature/pom.xml @@ -10,7 +10,7 @@ dev.openfeature.contrib.tools junitopenfeature - 3.1.2 + 0.0.1 junit-openfeature-extension JUnit5 Extension for OpenFeature diff --git a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureExtension.java b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureExtension.java index fb18e52ae..3396cef3b 100644 --- a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureExtension.java +++ b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureExtension.java @@ -1,15 +1,18 @@ package dev.openfeature.contrib.tools.junitopenfeature; -import dev.openfeature.sdk.NoOpProvider; +import dev.openfeature.contrib.tools.junitopenfeature.annotations.OpenFeature; +import dev.openfeature.contrib.tools.junitopenfeature.annotations.OpenFeatureDefaultDomain; import dev.openfeature.sdk.OpenFeatureAPI; import dev.openfeature.sdk.providers.memory.Flag; -import dev.openfeature.sdk.providers.memory.InMemoryProvider; import org.apache.commons.lang3.BooleanUtils; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.InvocationInterceptor; +import org.junit.jupiter.api.extension.ReflectiveInvocationContext; import org.junitpioneer.internal.PioneerAnnotationUtils; +import java.lang.reflect.Method; import java.util.Arrays; import java.util.HashMap; import java.util.Map; @@ -17,7 +20,7 @@ /** * JUnit5 Extension for OpenFeature. */ -public class OpenFeatureExtension implements BeforeEachCallback, AfterEachCallback { +public class OpenFeatureExtension implements BeforeEachCallback, AfterEachCallback, InvocationInterceptor { OpenFeatureAPI api = OpenFeatureAPI.getInstance(); @@ -49,7 +52,7 @@ private static Map>> handleSimpleConfiguration(Exten PioneerAnnotationUtils .findAllEnclosingRepeatableAnnotations( extensionContext, - dev.openfeature.contrib.tools.junitopenfeature.Flag.class) + dev.openfeature.contrib.tools.junitopenfeature.annotations.Flag.class) .forEachOrdered(flag -> { Map> domainFlags = configuration.getOrDefault(defaultDomain, new HashMap<>()); if (!domainFlags.containsKey(flag.name())) { @@ -62,7 +65,9 @@ private static Map>> handleSimpleConfiguration(Exten return configuration; } - private static Flag.FlagBuilder generateFlagBuilder(dev.openfeature.contrib.tools.junitopenfeature.Flag flag) { + private static Flag.FlagBuilder generateFlagBuilder( + dev.openfeature.contrib.tools.junitopenfeature.annotations.Flag flag + ) { Flag.FlagBuilder builder; switch (flag.valueType().getSimpleName()) { case "Boolean": @@ -89,17 +94,18 @@ private static Flag.FlagBuilder generateFlagBuilder(dev.openfeature.contrib.t } @Override - public void afterEach(ExtensionContext extensionContext) throws Exception { + public void interceptTestMethod( + Invocation invocation, + ReflectiveInvocationContext invocationContext, + ExtensionContext extensionContext + ) throws Throwable { + TestProvider.CURRENT_NAMESPACE.set(getNamespace(extensionContext)); + invocation.proceed(); + TestProvider.CURRENT_NAMESPACE.remove(); + } - @SuppressWarnings("unchecked") Map>> configuration = - (Map>>) getStore(extensionContext).get("config"); - for (Map.Entry>> stringMapEntry : configuration.entrySet()) { - if (stringMapEntry.getKey().isEmpty()) { - api.setProvider(new NoOpProvider()); - } else { - api.setProvider(stringMapEntry.getKey(), new NoOpProvider()); - } - } + @Override + public void afterEach(ExtensionContext extensionContext) throws Exception { } @Override @@ -108,15 +114,39 @@ public void beforeEach(ExtensionContext extensionContext) throws Exception { configuration.putAll(handleExtendedConfiguration(extensionContext, configuration)); for (Map.Entry>> stringMapEntry : configuration.entrySet()) { - InMemoryProvider inMemoryProvider = new InMemoryProvider(stringMapEntry.getValue()); - if (stringMapEntry.getKey().isEmpty()) { - api.setProvider(inMemoryProvider); + + if (!stringMapEntry.getKey().isEmpty()) { + String domain = stringMapEntry.getKey(); + if (api.getProvider(domain) instanceof TestProvider && api.getProvider(domain) != api.getProvider()) { + ((TestProvider) api.getProvider(domain)) + .addFlags(getNamespace(extensionContext), stringMapEntry.getValue()); + } else { + api.setProvider(domain, new TestProvider( + getNamespace(extensionContext), + stringMapEntry.getValue())); + } } else { - api.setProvider(stringMapEntry.getKey(), inMemoryProvider); + if (api.getProvider() instanceof TestProvider) { + ((TestProvider) api.getProvider()) + .addFlags(getNamespace(extensionContext), stringMapEntry.getValue()); + } else { + api.setProvider(new TestProvider( + getNamespace(extensionContext), + stringMapEntry.getValue())); + } } + } getStore(extensionContext).put("config", configuration); + + } + + private ExtensionContext.Namespace getNamespace(ExtensionContext extensionContext) { + return ExtensionContext.Namespace.create( + getClass(), + extensionContext.getRequiredTestMethod() + ); } private ExtensionContext.Store getStore(ExtensionContext context) { diff --git a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/TestProvider.java b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/TestProvider.java new file mode 100644 index 000000000..f00e19820 --- /dev/null +++ b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/TestProvider.java @@ -0,0 +1,128 @@ +package dev.openfeature.contrib.tools.junitopenfeature; + +import dev.openfeature.sdk.EvaluationContext; +import dev.openfeature.sdk.EventProvider; +import dev.openfeature.sdk.Metadata; +import dev.openfeature.sdk.ProviderEvaluation; +import dev.openfeature.sdk.ProviderState; +import dev.openfeature.sdk.Reason; +import dev.openfeature.sdk.Value; +import dev.openfeature.sdk.exceptions.FlagNotFoundError; +import dev.openfeature.sdk.exceptions.GeneralError; +import dev.openfeature.sdk.exceptions.OpenFeatureError; +import dev.openfeature.sdk.exceptions.ProviderNotReadyError; +import dev.openfeature.sdk.exceptions.TypeMismatchError; +import dev.openfeature.sdk.providers.memory.Flag; +import lombok.Getter; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.extension.ExtensionContext; + +import java.util.HashMap; +import java.util.Map; + +/** + * TestProvider based on InMemoryProvider but with another dimension added to the maps of flags. + */ +@Slf4j +public class TestProvider extends EventProvider { + public static final ThreadLocal CURRENT_NAMESPACE = new ThreadLocal<>(); + public Map>> flagsMap = new HashMap<>(); + + @Getter + private static final String NAME = "TestingProvider"; + + @Getter + private ProviderState state = ProviderState.NOT_READY; + + @Override + public Metadata getMetadata() { + return () -> NAME; + } + + public TestProvider(ExtensionContext.Namespace namespace, Map> flags) { + flagsMap.put(namespace, flags); + } + + /** + * Initialize the provider. + * @param evaluationContext evaluation context + * @throws Exception on error + */ + @Override + public void initialize(EvaluationContext evaluationContext) throws Exception { + super.initialize(evaluationContext); + state = ProviderState.READY; + log.debug("finished initializing provider, state: {}", state); + } + + /** + * Updating provider flags configuration, replacing existing flags. + * @param flags the flags to use instead of the previous flags. + */ + public void addFlags(ExtensionContext.Namespace namespace, Map> flags) { + flagsMap.put(namespace, flags); + } + + @Override + public ProviderEvaluation getBooleanEvaluation(String key, Boolean defaultValue, + EvaluationContext evaluationContext) { + return getEvaluation(key, evaluationContext, Boolean.class); + } + + @Override + public ProviderEvaluation getStringEvaluation(String key, String defaultValue, + EvaluationContext evaluationContext) { + return getEvaluation(key, evaluationContext, String.class); + } + + @Override + public ProviderEvaluation getIntegerEvaluation(String key, Integer defaultValue, + EvaluationContext evaluationContext) { + return getEvaluation(key, evaluationContext, Integer.class); + } + + @Override + public ProviderEvaluation getDoubleEvaluation(String key, Double defaultValue, + EvaluationContext evaluationContext) { + return getEvaluation(key, evaluationContext, Double.class); + } + + @SneakyThrows + @Override + public ProviderEvaluation getObjectEvaluation(String key, Value defaultValue, + EvaluationContext evaluationContext) { + return getEvaluation(key, evaluationContext, Value.class); + } + + @SuppressWarnings("unchecked") + private ProviderEvaluation getEvaluation( + String key, EvaluationContext evaluationContext, Class expectedType + ) throws OpenFeatureError { + if (!ProviderState.READY.equals(state)) { + if (ProviderState.NOT_READY.equals(state)) { + throw new ProviderNotReadyError("provider not yet initialized"); + } + throw new GeneralError("unknown error"); + } + Map>> flagsMap1 = flagsMap; + ExtensionContext.Namespace key1 = CURRENT_NAMESPACE.get(); + Flag flag = flagsMap1.getOrDefault(key1, new HashMap<>()).get(key); + if (flag == null) { + throw new FlagNotFoundError("flag " + key + "not found"); + } + T value; + if (flag.getContextEvaluator() != null) { + value = (T) flag.getContextEvaluator().evaluate(flag, evaluationContext); + } else if (!expectedType.isInstance(flag.getVariants().get(flag.getDefaultVariant()))) { + throw new TypeMismatchError("flag " + key + "is not of expected type"); + } else { + value = (T) flag.getVariants().get(flag.getDefaultVariant()); + } + return ProviderEvaluation.builder() + .value(value) + .variant(flag.getDefaultVariant()) + .reason(Reason.STATIC.toString()) + .build(); + } +} diff --git a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/Flag.java b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/annotations/Flag.java similarity index 85% rename from tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/Flag.java rename to tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/annotations/Flag.java index 5870c60cd..e2f789df1 100644 --- a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/Flag.java +++ b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/annotations/Flag.java @@ -1,5 +1,6 @@ -package dev.openfeature.contrib.tools.junitopenfeature; +package dev.openfeature.contrib.tools.junitopenfeature.annotations; +import dev.openfeature.contrib.tools.junitopenfeature.OpenFeatureExtension; import org.junit.jupiter.api.extension.ExtendWith; import java.lang.annotation.ElementType; diff --git a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/Flags.java b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/annotations/Flags.java similarity index 78% rename from tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/Flags.java rename to tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/annotations/Flags.java index eecdba428..b8ef1d7d2 100644 --- a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/Flags.java +++ b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/annotations/Flags.java @@ -1,5 +1,6 @@ -package dev.openfeature.contrib.tools.junitopenfeature; +package dev.openfeature.contrib.tools.junitopenfeature.annotations; +import dev.openfeature.contrib.tools.junitopenfeature.OpenFeatureExtension; import org.junit.jupiter.api.extension.ExtendWith; import java.lang.annotation.ElementType; diff --git a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeature.java b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/annotations/OpenFeature.java similarity index 85% rename from tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeature.java rename to tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/annotations/OpenFeature.java index aaae83302..a60a831f2 100644 --- a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeature.java +++ b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/annotations/OpenFeature.java @@ -1,5 +1,6 @@ -package dev.openfeature.contrib.tools.junitopenfeature; +package dev.openfeature.contrib.tools.junitopenfeature.annotations; +import dev.openfeature.contrib.tools.junitopenfeature.OpenFeatureExtension; import org.junit.jupiter.api.extension.ExtendWith; import java.lang.annotation.ElementType; diff --git a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureDefaultDomain.java b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/annotations/OpenFeatureDefaultDomain.java similarity index 78% rename from tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureDefaultDomain.java rename to tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/annotations/OpenFeatureDefaultDomain.java index f88fcbbe4..b7f9af994 100644 --- a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureDefaultDomain.java +++ b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/annotations/OpenFeatureDefaultDomain.java @@ -1,5 +1,6 @@ -package dev.openfeature.contrib.tools.junitopenfeature; +package dev.openfeature.contrib.tools.junitopenfeature.annotations; +import dev.openfeature.contrib.tools.junitopenfeature.OpenFeatureExtension; import org.junit.jupiter.api.extension.ExtendWith; import java.lang.annotation.ElementType; diff --git a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatures.java b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/annotations/OpenFeatures.java similarity index 79% rename from tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatures.java rename to tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/annotations/OpenFeatures.java index c06002378..2dcf6bea4 100644 --- a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatures.java +++ b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/annotations/OpenFeatures.java @@ -1,5 +1,6 @@ -package dev.openfeature.contrib.tools.junitopenfeature; +package dev.openfeature.contrib.tools.junitopenfeature.annotations; +import dev.openfeature.contrib.tools.junitopenfeature.OpenFeatureExtension; import org.junit.jupiter.api.extension.ExtendWith; import java.lang.annotation.ElementType; diff --git a/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/BooleanFlagTest.java b/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/BooleanFlagTest.java index a671d16f6..b11788bae 100644 --- a/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/BooleanFlagTest.java +++ b/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/BooleanFlagTest.java @@ -1,15 +1,15 @@ package dev.openfeature.contrib.tools.junitopenfeature; +import dev.openfeature.contrib.tools.junitopenfeature.annotations.Flag; +import dev.openfeature.contrib.tools.junitopenfeature.annotations.OpenFeature; +import dev.openfeature.contrib.tools.junitopenfeature.annotations.OpenFeatureDefaultDomain; import dev.openfeature.sdk.Client; import dev.openfeature.sdk.OpenFeatureAPI; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.parallel.Execution; -import org.junit.jupiter.api.parallel.ExecutionMode; import static org.assertj.core.api.Assertions.assertThat; -@Execution(ExecutionMode.SAME_THREAD) class BooleanFlagTest { private static final String FLAG = "boolean-flag"; diff --git a/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/DoubleFlagTest.java b/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/DoubleFlagTest.java index 81fee0cdc..8f0903939 100644 --- a/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/DoubleFlagTest.java +++ b/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/DoubleFlagTest.java @@ -1,15 +1,15 @@ package dev.openfeature.contrib.tools.junitopenfeature; +import dev.openfeature.contrib.tools.junitopenfeature.annotations.Flag; +import dev.openfeature.contrib.tools.junitopenfeature.annotations.OpenFeature; +import dev.openfeature.contrib.tools.junitopenfeature.annotations.OpenFeatureDefaultDomain; import dev.openfeature.sdk.Client; import dev.openfeature.sdk.OpenFeatureAPI; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.parallel.Execution; -import org.junit.jupiter.api.parallel.ExecutionMode; import static org.assertj.core.api.Assertions.assertThat; -@Execution(ExecutionMode.SAME_THREAD) class DoubleFlagTest { private static final String FLAG = "double-flag"; diff --git a/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/IntegerFlagTest.java b/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/IntegerFlagTest.java index dcd43fef3..e7586919e 100644 --- a/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/IntegerFlagTest.java +++ b/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/IntegerFlagTest.java @@ -1,15 +1,15 @@ package dev.openfeature.contrib.tools.junitopenfeature; +import dev.openfeature.contrib.tools.junitopenfeature.annotations.Flag; +import dev.openfeature.contrib.tools.junitopenfeature.annotations.OpenFeature; +import dev.openfeature.contrib.tools.junitopenfeature.annotations.OpenFeatureDefaultDomain; import dev.openfeature.sdk.Client; import dev.openfeature.sdk.OpenFeatureAPI; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.parallel.Execution; -import org.junit.jupiter.api.parallel.ExecutionMode; import static org.assertj.core.api.Assertions.assertThat; -@Execution(ExecutionMode.SAME_THREAD) class IntegerFlagTest { private static final String FLAG = "integer-flag"; diff --git a/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureExtensionTest.java b/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureExtensionTest.java index 8d72b9af7..a1afc5826 100644 --- a/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureExtensionTest.java +++ b/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureExtensionTest.java @@ -1,17 +1,13 @@ package dev.openfeature.contrib.tools.junitopenfeature; +import dev.openfeature.contrib.tools.junitopenfeature.annotations.OpenFeature; import dev.openfeature.sdk.Client; -import dev.openfeature.sdk.NoOpProvider; import dev.openfeature.sdk.OpenFeatureAPI; -import dev.openfeature.sdk.providers.memory.InMemoryProvider; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.parallel.Execution; -import org.junit.jupiter.api.parallel.ExecutionMode; import static org.assertj.core.api.Assertions.assertThat; -@Execution(ExecutionMode.SAME_THREAD) class OpenFeatureExtensionTest { OpenFeatureAPI api = OpenFeatureAPI.getInstance(); @@ -25,15 +21,15 @@ class OnMethod { @OpenFeature({}) void clientIsSet() { assertThat(api).isNotNull(); - assertThat(api.getProvider()).isInstanceOf(InMemoryProvider.class); + assertThat(api.getProvider()).isInstanceOf(TestProvider.class); } @OpenFeature({}) @OpenFeature(domain = "test", value = {}) void clientIsSetMultipleTimes() { assertThat(api).isNotNull(); - assertThat(api.getProvider()).isInstanceOf(InMemoryProvider.class); - assertThat(api.getProvider("test")).isInstanceOf(InMemoryProvider.class); + assertThat(api.getProvider()).isInstanceOf(TestProvider.class); + assertThat(api.getProvider("test")).isInstanceOf(TestProvider.class); Client client = OpenFeatureAPI.getInstance().getClient(); assertThat(client).isNotNull(); Client clientTest = OpenFeatureAPI.getInstance().getClient("test"); @@ -44,8 +40,7 @@ void clientIsSetMultipleTimes() { @OpenFeature(domain = "domain", value = {}) void clientIsSetWithDomain() { assertThat(api).isNotNull(); - assertThat(api.getProvider("domain")).isInstanceOf(InMemoryProvider.class); - assertThat(api.getProvider()).isInstanceOf(NoOpProvider.class); + assertThat(api.getProvider("domain")).isInstanceOf(TestProvider.class); Client client = OpenFeatureAPI.getInstance().getClient("domain"); assertThat(client).isNotNull(); } @@ -57,7 +52,7 @@ class OnClass { @Test void clientIsSet() { assertThat(api).isNotNull(); - assertThat(api.getProvider()).isInstanceOf(InMemoryProvider.class); + assertThat(api.getProvider()).isInstanceOf(TestProvider.class); Client client = OpenFeatureAPI.getInstance().getClient(); assertThat(client).isNotNull(); } @@ -69,8 +64,7 @@ class OnClassWithDomain { @Test void clientIsSetWithDomain() { assertThat(api).isNotNull(); - assertThat(api.getProvider("domain")).isInstanceOf(InMemoryProvider.class); - assertThat(api.getProvider()).isInstanceOf(NoOpProvider.class); + assertThat(api.getProvider("domain")).isInstanceOf(TestProvider.class); Client client = OpenFeatureAPI.getInstance().getClient("domain"); assertThat(client).isNotNull(); } diff --git a/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/StringFlagTest.java b/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/StringFlagTest.java index 2f7e9abc7..00f02dd76 100644 --- a/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/StringFlagTest.java +++ b/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/StringFlagTest.java @@ -1,15 +1,15 @@ package dev.openfeature.contrib.tools.junitopenfeature; +import dev.openfeature.contrib.tools.junitopenfeature.annotations.Flag; +import dev.openfeature.contrib.tools.junitopenfeature.annotations.OpenFeature; +import dev.openfeature.contrib.tools.junitopenfeature.annotations.OpenFeatureDefaultDomain; import dev.openfeature.sdk.Client; import dev.openfeature.sdk.OpenFeatureAPI; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.parallel.Execution; -import org.junit.jupiter.api.parallel.ExecutionMode; import static org.assertj.core.api.Assertions.assertThat; -@Execution(ExecutionMode.SAME_THREAD) class StringFlagTest { private static final String FLAG = "string-flag"; From 01b9e6fe141c326c84ae0d011b67ac8eac4904c8 Mon Sep 17 00:00:00 2001 From: Simon Schrottner Date: Wed, 24 Jul 2024 21:00:53 +0200 Subject: [PATCH 5/8] chore: changing to inmemory provider wrapper for testprovider and pr fixes Signed-off-by: Simon Schrottner --- .../OpenFeatureExtension.java | 8 +- .../tools/junitopenfeature/TestProvider.java | 121 +++++++++--------- 2 files changed, 65 insertions(+), 64 deletions(-) diff --git a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureExtension.java b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureExtension.java index 3396cef3b..49be77e50 100644 --- a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureExtension.java +++ b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureExtension.java @@ -99,9 +99,9 @@ public void interceptTestMethod( ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext ) throws Throwable { - TestProvider.CURRENT_NAMESPACE.set(getNamespace(extensionContext)); + TestProvider.setCurrentNamespace(getNamespace(extensionContext)); invocation.proceed(); - TestProvider.CURRENT_NAMESPACE.remove(); + TestProvider.clearCurrentNamespace(); } @Override @@ -119,7 +119,7 @@ public void beforeEach(ExtensionContext extensionContext) throws Exception { String domain = stringMapEntry.getKey(); if (api.getProvider(domain) instanceof TestProvider && api.getProvider(domain) != api.getProvider()) { ((TestProvider) api.getProvider(domain)) - .addFlags(getNamespace(extensionContext), stringMapEntry.getValue()); + .addConfigurationForTest(getNamespace(extensionContext), stringMapEntry.getValue()); } else { api.setProvider(domain, new TestProvider( getNamespace(extensionContext), @@ -128,7 +128,7 @@ public void beforeEach(ExtensionContext extensionContext) throws Exception { } else { if (api.getProvider() instanceof TestProvider) { ((TestProvider) api.getProvider()) - .addFlags(getNamespace(extensionContext), stringMapEntry.getValue()); + .addConfigurationForTest(getNamespace(extensionContext), stringMapEntry.getValue()); } else { api.setProvider(new TestProvider( getNamespace(extensionContext), diff --git a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/TestProvider.java b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/TestProvider.java index f00e19820..b169f73ee 100644 --- a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/TestProvider.java +++ b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/TestProvider.java @@ -2,17 +2,13 @@ import dev.openfeature.sdk.EvaluationContext; import dev.openfeature.sdk.EventProvider; +import dev.openfeature.sdk.ImmutableContext; import dev.openfeature.sdk.Metadata; import dev.openfeature.sdk.ProviderEvaluation; import dev.openfeature.sdk.ProviderState; -import dev.openfeature.sdk.Reason; import dev.openfeature.sdk.Value; -import dev.openfeature.sdk.exceptions.FlagNotFoundError; -import dev.openfeature.sdk.exceptions.GeneralError; -import dev.openfeature.sdk.exceptions.OpenFeatureError; -import dev.openfeature.sdk.exceptions.ProviderNotReadyError; -import dev.openfeature.sdk.exceptions.TypeMismatchError; import dev.openfeature.sdk.providers.memory.Flag; +import dev.openfeature.sdk.providers.memory.InMemoryProvider; import lombok.Getter; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; @@ -26,103 +22,108 @@ */ @Slf4j public class TestProvider extends EventProvider { - public static final ThreadLocal CURRENT_NAMESPACE = new ThreadLocal<>(); - public Map>> flagsMap = new HashMap<>(); + private static final InMemoryProvider FALLBACK_PROVIDER = createProvider(new HashMap<>()); + + private static final ThreadLocal CURRENT_NAMESPACE = new ThreadLocal<>(); @Getter private static final String NAME = "TestingProvider"; - @Getter - private ProviderState state = ProviderState.NOT_READY; + private final Map providerMap = new HashMap<>(); - @Override - public Metadata getMetadata() { - return () -> NAME; + public TestProvider(ExtensionContext.Namespace namespace, Map> flags) { + providerMap.put(namespace, createProvider(flags)); } - public TestProvider(ExtensionContext.Namespace namespace, Map> flags) { - flagsMap.put(namespace, flags); + @SneakyThrows + private static InMemoryProvider createProvider(Map> flags) { + InMemoryProvider inMemoryProvider = new InMemoryProvider(flags); + inMemoryProvider.initialize(new ImmutableContext()); + return inMemoryProvider; } - /** - * Initialize the provider. - * @param evaluationContext evaluation context - * @throws Exception on error - */ @Override - public void initialize(EvaluationContext evaluationContext) throws Exception { - super.initialize(evaluationContext); - state = ProviderState.READY; - log.debug("finished initializing provider, state: {}", state); + public Metadata getMetadata() { + return () -> NAME; } /** * Updating provider flags configuration, replacing existing flags. + * * @param flags the flags to use instead of the previous flags. */ - public void addFlags(ExtensionContext.Namespace namespace, Map> flags) { - flagsMap.put(namespace, flags); + public void addConfigurationForTest(ExtensionContext.Namespace namespace, Map> flags) { + InMemoryProvider inMemoryProvider = providerMap.putIfAbsent(namespace, createProvider(flags)); + + if (inMemoryProvider != null) { + inMemoryProvider.updateFlags(flags); + } } @Override public ProviderEvaluation getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext evaluationContext) { - return getEvaluation(key, evaluationContext, Boolean.class); + return providerMap + .getOrDefault(CURRENT_NAMESPACE.get(), FALLBACK_PROVIDER) + .getBooleanEvaluation(key, defaultValue, evaluationContext); } @Override public ProviderEvaluation getStringEvaluation(String key, String defaultValue, EvaluationContext evaluationContext) { - return getEvaluation(key, evaluationContext, String.class); + + return providerMap + .getOrDefault(CURRENT_NAMESPACE.get(), FALLBACK_PROVIDER) + .getStringEvaluation(key, defaultValue, evaluationContext); } @Override public ProviderEvaluation getIntegerEvaluation(String key, Integer defaultValue, EvaluationContext evaluationContext) { - return getEvaluation(key, evaluationContext, Integer.class); + return providerMap + .getOrDefault(CURRENT_NAMESPACE.get(), FALLBACK_PROVIDER) + .getIntegerEvaluation(key, defaultValue, evaluationContext); } @Override public ProviderEvaluation getDoubleEvaluation(String key, Double defaultValue, EvaluationContext evaluationContext) { - return getEvaluation(key, evaluationContext, Double.class); + return providerMap + .getOrDefault(CURRENT_NAMESPACE.get(), FALLBACK_PROVIDER) + .getDoubleEvaluation(key, defaultValue, evaluationContext); } @SneakyThrows @Override public ProviderEvaluation getObjectEvaluation(String key, Value defaultValue, EvaluationContext evaluationContext) { - return getEvaluation(key, evaluationContext, Value.class); + return providerMap + .getOrDefault(CURRENT_NAMESPACE.get(), FALLBACK_PROVIDER) + .getObjectEvaluation(key, defaultValue, evaluationContext); } - @SuppressWarnings("unchecked") - private ProviderEvaluation getEvaluation( - String key, EvaluationContext evaluationContext, Class expectedType - ) throws OpenFeatureError { - if (!ProviderState.READY.equals(state)) { - if (ProviderState.NOT_READY.equals(state)) { - throw new ProviderNotReadyError("provider not yet initialized"); - } - throw new GeneralError("unknown error"); - } - Map>> flagsMap1 = flagsMap; - ExtensionContext.Namespace key1 = CURRENT_NAMESPACE.get(); - Flag flag = flagsMap1.getOrDefault(key1, new HashMap<>()).get(key); - if (flag == null) { - throw new FlagNotFoundError("flag " + key + "not found"); - } - T value; - if (flag.getContextEvaluator() != null) { - value = (T) flag.getContextEvaluator().evaluate(flag, evaluationContext); - } else if (!expectedType.isInstance(flag.getVariants().get(flag.getDefaultVariant()))) { - throw new TypeMismatchError("flag " + key + "is not of expected type"); - } else { - value = (T) flag.getVariants().get(flag.getDefaultVariant()); - } - return ProviderEvaluation.builder() - .value(value) - .variant(flag.getDefaultVariant()) - .reason(Reason.STATIC.toString()) - .build(); + @Override + public ProviderState getState() { + return providerMap + .values() + .stream() + .map(InMemoryProvider::getState) + .reduce( + (providerState, providerState2) -> { + if (providerState.ordinal() < providerState2.ordinal()) { + return providerState2; + } + return providerState; + } + ) + .orElse(ProviderState.READY); + } + + public static void setCurrentNamespace(ExtensionContext.Namespace namespace) { + CURRENT_NAMESPACE.set(namespace); + } + + public static void clearCurrentNamespace() { + CURRENT_NAMESPACE.remove(); } } From 74a9ff87aa36a49be9792d51d98f91aa0b7ac614 Mon Sep 17 00:00:00 2001 From: Simon Schrottner Date: Wed, 24 Jul 2024 21:00:53 +0200 Subject: [PATCH 6/8] chore: changing to inmemory provider wrapper for testprovider and pr fixes Signed-off-by: Simon Schrottner --- providers/flagd/schemas | 2 +- providers/flagd/test-harness | 2 +- tools/junit-openfeature/README.md | 29 +++++-------------- .../{annotations => }/Flag.java | 3 +- .../{annotations => }/Flags.java | 3 +- .../{annotations => }/OpenFeature.java | 3 +- .../OpenFeatureDefaultDomain.java | 3 +- .../OpenFeatureExtension.java | 6 ++-- .../{annotations => }/OpenFeatures.java | 3 +- .../junitopenfeature/BooleanFlagTest.java | 3 -- .../junitopenfeature/DoubleFlagTest.java | 3 -- .../junitopenfeature/IntegerFlagTest.java | 3 -- .../OpenFeatureExtensionTest.java | 1 - .../junitopenfeature/StringFlagTest.java | 3 -- 14 files changed, 16 insertions(+), 51 deletions(-) rename tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/{annotations => }/Flag.java (85%) rename tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/{annotations => }/Flags.java (78%) rename tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/{annotations => }/OpenFeature.java (85%) rename tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/{annotations => }/OpenFeatureDefaultDomain.java (78%) rename tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/{annotations => }/OpenFeatures.java (79%) diff --git a/providers/flagd/schemas b/providers/flagd/schemas index 8c72c14ee..2aa89b314 160000 --- a/providers/flagd/schemas +++ b/providers/flagd/schemas @@ -1 +1 @@ -Subproject commit 8c72c14eebff2a5b20fe2afb90c7ad44c1184ae8 +Subproject commit 2aa89b31432284507af3873de9b0bb7b68dd02c7 diff --git a/providers/flagd/test-harness b/providers/flagd/test-harness index 25544e43d..ed7e0ba66 160000 --- a/providers/flagd/test-harness +++ b/providers/flagd/test-harness @@ -1 +1 @@ -Subproject commit 25544e43d65500ea085d6f0e242b9a616a51f776 +Subproject commit ed7e0ba660b01e1a22849e1b28ec37453921552e diff --git a/tools/junit-openfeature/README.md b/tools/junit-openfeature/README.md index ae0f8f1a5..76feb1ce0 100644 --- a/tools/junit-openfeature/README.md +++ b/tools/junit-openfeature/README.md @@ -73,12 +73,17 @@ Use the extended configuration when your code needs to use multiple domains. ```java @Test @OpenFeature({ - @Flag(name = "BOOLEAN_FLAG", value = "true") + @Flag(name = "BOOLEAN_FLAG", value = "true") }) +@OpenFeature( + domain = "domain", + value = { + @Flag(name = "BOOLEAN_FLAG2", value = "true") + }) void test() { // your test code } -``` +``` #### Multiple flags @@ -134,23 +139,3 @@ void test() { } ``` -#### Multiple Configurations for multiple domains - -Following testcode will generate two providers, with different flag configurations for a test. - -```java -@Test -@OpenFeature({ - @Flag(name = "BOOLEAN_FLAG", value = "true"), - @Flag(name = "BOOLEAN_FLAG2", value = "true") -}) -@OpenFeature( - domain = "domain", - value = { - @Flag(name = "BOOLEAN_FLAG2", value = "true") // will be used - }) -void test() { - // your test code -} -``` - diff --git a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/annotations/Flag.java b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/Flag.java similarity index 85% rename from tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/annotations/Flag.java rename to tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/Flag.java index e2f789df1..5870c60cd 100644 --- a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/annotations/Flag.java +++ b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/Flag.java @@ -1,6 +1,5 @@ -package dev.openfeature.contrib.tools.junitopenfeature.annotations; +package dev.openfeature.contrib.tools.junitopenfeature; -import dev.openfeature.contrib.tools.junitopenfeature.OpenFeatureExtension; import org.junit.jupiter.api.extension.ExtendWith; import java.lang.annotation.ElementType; diff --git a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/annotations/Flags.java b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/Flags.java similarity index 78% rename from tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/annotations/Flags.java rename to tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/Flags.java index b8ef1d7d2..eecdba428 100644 --- a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/annotations/Flags.java +++ b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/Flags.java @@ -1,6 +1,5 @@ -package dev.openfeature.contrib.tools.junitopenfeature.annotations; +package dev.openfeature.contrib.tools.junitopenfeature; -import dev.openfeature.contrib.tools.junitopenfeature.OpenFeatureExtension; import org.junit.jupiter.api.extension.ExtendWith; import java.lang.annotation.ElementType; diff --git a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/annotations/OpenFeature.java b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeature.java similarity index 85% rename from tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/annotations/OpenFeature.java rename to tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeature.java index a60a831f2..aaae83302 100644 --- a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/annotations/OpenFeature.java +++ b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeature.java @@ -1,6 +1,5 @@ -package dev.openfeature.contrib.tools.junitopenfeature.annotations; +package dev.openfeature.contrib.tools.junitopenfeature; -import dev.openfeature.contrib.tools.junitopenfeature.OpenFeatureExtension; import org.junit.jupiter.api.extension.ExtendWith; import java.lang.annotation.ElementType; diff --git a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/annotations/OpenFeatureDefaultDomain.java b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureDefaultDomain.java similarity index 78% rename from tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/annotations/OpenFeatureDefaultDomain.java rename to tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureDefaultDomain.java index b7f9af994..f88fcbbe4 100644 --- a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/annotations/OpenFeatureDefaultDomain.java +++ b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureDefaultDomain.java @@ -1,6 +1,5 @@ -package dev.openfeature.contrib.tools.junitopenfeature.annotations; +package dev.openfeature.contrib.tools.junitopenfeature; -import dev.openfeature.contrib.tools.junitopenfeature.OpenFeatureExtension; import org.junit.jupiter.api.extension.ExtendWith; import java.lang.annotation.ElementType; diff --git a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureExtension.java b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureExtension.java index 49be77e50..9193af898 100644 --- a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureExtension.java +++ b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureExtension.java @@ -1,7 +1,5 @@ package dev.openfeature.contrib.tools.junitopenfeature; -import dev.openfeature.contrib.tools.junitopenfeature.annotations.OpenFeature; -import dev.openfeature.contrib.tools.junitopenfeature.annotations.OpenFeatureDefaultDomain; import dev.openfeature.sdk.OpenFeatureAPI; import dev.openfeature.sdk.providers.memory.Flag; import org.apache.commons.lang3.BooleanUtils; @@ -52,7 +50,7 @@ private static Map>> handleSimpleConfiguration(Exten PioneerAnnotationUtils .findAllEnclosingRepeatableAnnotations( extensionContext, - dev.openfeature.contrib.tools.junitopenfeature.annotations.Flag.class) + dev.openfeature.contrib.tools.junitopenfeature.Flag.class) .forEachOrdered(flag -> { Map> domainFlags = configuration.getOrDefault(defaultDomain, new HashMap<>()); if (!domainFlags.containsKey(flag.name())) { @@ -66,7 +64,7 @@ private static Map>> handleSimpleConfiguration(Exten } private static Flag.FlagBuilder generateFlagBuilder( - dev.openfeature.contrib.tools.junitopenfeature.annotations.Flag flag + dev.openfeature.contrib.tools.junitopenfeature.Flag flag ) { Flag.FlagBuilder builder; switch (flag.valueType().getSimpleName()) { diff --git a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/annotations/OpenFeatures.java b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatures.java similarity index 79% rename from tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/annotations/OpenFeatures.java rename to tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatures.java index 2dcf6bea4..c06002378 100644 --- a/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/annotations/OpenFeatures.java +++ b/tools/junit-openfeature/src/main/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatures.java @@ -1,6 +1,5 @@ -package dev.openfeature.contrib.tools.junitopenfeature.annotations; +package dev.openfeature.contrib.tools.junitopenfeature; -import dev.openfeature.contrib.tools.junitopenfeature.OpenFeatureExtension; import org.junit.jupiter.api.extension.ExtendWith; import java.lang.annotation.ElementType; diff --git a/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/BooleanFlagTest.java b/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/BooleanFlagTest.java index b11788bae..37b6e01ea 100644 --- a/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/BooleanFlagTest.java +++ b/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/BooleanFlagTest.java @@ -1,8 +1,5 @@ package dev.openfeature.contrib.tools.junitopenfeature; -import dev.openfeature.contrib.tools.junitopenfeature.annotations.Flag; -import dev.openfeature.contrib.tools.junitopenfeature.annotations.OpenFeature; -import dev.openfeature.contrib.tools.junitopenfeature.annotations.OpenFeatureDefaultDomain; import dev.openfeature.sdk.Client; import dev.openfeature.sdk.OpenFeatureAPI; import org.junit.jupiter.api.Nested; diff --git a/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/DoubleFlagTest.java b/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/DoubleFlagTest.java index 8f0903939..34a0fc6d5 100644 --- a/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/DoubleFlagTest.java +++ b/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/DoubleFlagTest.java @@ -1,8 +1,5 @@ package dev.openfeature.contrib.tools.junitopenfeature; -import dev.openfeature.contrib.tools.junitopenfeature.annotations.Flag; -import dev.openfeature.contrib.tools.junitopenfeature.annotations.OpenFeature; -import dev.openfeature.contrib.tools.junitopenfeature.annotations.OpenFeatureDefaultDomain; import dev.openfeature.sdk.Client; import dev.openfeature.sdk.OpenFeatureAPI; import org.junit.jupiter.api.Nested; diff --git a/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/IntegerFlagTest.java b/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/IntegerFlagTest.java index e7586919e..b658ff7c1 100644 --- a/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/IntegerFlagTest.java +++ b/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/IntegerFlagTest.java @@ -1,8 +1,5 @@ package dev.openfeature.contrib.tools.junitopenfeature; -import dev.openfeature.contrib.tools.junitopenfeature.annotations.Flag; -import dev.openfeature.contrib.tools.junitopenfeature.annotations.OpenFeature; -import dev.openfeature.contrib.tools.junitopenfeature.annotations.OpenFeatureDefaultDomain; import dev.openfeature.sdk.Client; import dev.openfeature.sdk.OpenFeatureAPI; import org.junit.jupiter.api.Nested; diff --git a/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureExtensionTest.java b/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureExtensionTest.java index a1afc5826..ecd85828d 100644 --- a/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureExtensionTest.java +++ b/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/OpenFeatureExtensionTest.java @@ -1,6 +1,5 @@ package dev.openfeature.contrib.tools.junitopenfeature; -import dev.openfeature.contrib.tools.junitopenfeature.annotations.OpenFeature; import dev.openfeature.sdk.Client; import dev.openfeature.sdk.OpenFeatureAPI; import org.junit.jupiter.api.Nested; diff --git a/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/StringFlagTest.java b/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/StringFlagTest.java index 00f02dd76..1d463426f 100644 --- a/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/StringFlagTest.java +++ b/tools/junit-openfeature/src/test/java/dev/openfeature/contrib/tools/junitopenfeature/StringFlagTest.java @@ -1,8 +1,5 @@ package dev.openfeature.contrib.tools.junitopenfeature; -import dev.openfeature.contrib.tools.junitopenfeature.annotations.Flag; -import dev.openfeature.contrib.tools.junitopenfeature.annotations.OpenFeature; -import dev.openfeature.contrib.tools.junitopenfeature.annotations.OpenFeatureDefaultDomain; import dev.openfeature.sdk.Client; import dev.openfeature.sdk.OpenFeatureAPI; import org.junit.jupiter.api.Nested; From 93f10b9a9091eac29d3b9027fbda7d36d112b904 Mon Sep 17 00:00:00 2001 From: Simon Schrottner Date: Thu, 25 Jul 2024 21:04:11 +0200 Subject: [PATCH 7/8] chore: fix release-please files etc Signed-off-by: Simon Schrottner --- .github/component_owners.yml | 2 ++ .release-please-manifest.json | 5 +++-- release-please-config.json | 11 +++++++++++ tools/junit-openfeature/pom.xml | 2 +- 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/.github/component_owners.yml b/.github/component_owners.yml index cca686378..2b280a851 100644 --- a/.github/component_owners.yml +++ b/.github/component_owners.yml @@ -1,5 +1,7 @@ # Keep all in alphabetical order components: + tools/junit-openfeature: + - aepfli hooks/open-telemetry: - beeme1mr - Kavindu-Dodan diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 4b9bf5674..019ed65f4 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -8,5 +8,6 @@ "providers/unleash": "0.0.4-alpha", "providers/flipt": "0.0.2", "providers/configcat": "0.0.3", - "providers/statsig": "0.0.4" -} \ No newline at end of file + "providers/statsig": "0.0.4", + "tools/junit-openfeature": "0.0.0" +} diff --git a/release-please-config.json b/release-please-config.json index fd41f5d9b..964a4ee8b 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -110,6 +110,17 @@ "pom.xml", "README.md" ] + }, + "tools/junit-openfeature": { + "package-name": "dev.openfeature.contrib.tools.junitopenfeature", + "release-type": "simple", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": true, + "versioning": "default", + "extra-files": [ + "pom.xml", + "README.md" + ] } }, "changelog-sections": [ diff --git a/tools/junit-openfeature/pom.xml b/tools/junit-openfeature/pom.xml index 4d44f95cf..35577002b 100644 --- a/tools/junit-openfeature/pom.xml +++ b/tools/junit-openfeature/pom.xml @@ -10,7 +10,7 @@ dev.openfeature.contrib.tools junitopenfeature - 0.0.1 + 0.0.0 junit-openfeature-extension JUnit5 Extension for OpenFeature From 27dcd9a52ac3ea2445c451e4aac25e7d7f72b944 Mon Sep 17 00:00:00 2001 From: Simon Schrottner Date: Thu, 25 Jul 2024 21:05:16 +0200 Subject: [PATCH 8/8] Apply suggestions from code review Co-authored-by: Michael Beemer Signed-off-by: Simon Schrottner --- tools/junit-openfeature/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/junit-openfeature/README.md b/tools/junit-openfeature/README.md index 76feb1ce0..2e2542a8a 100644 --- a/tools/junit-openfeature/README.md +++ b/tools/junit-openfeature/README.md @@ -36,7 +36,7 @@ void test() { #### Defining Flags for a whole test-class `@Flags` can be defined on the class-level too, but method-level -annotations will superseded class-level annotations. +annotations will supersede class-level annotations. ```java @Flag(name = "BOOLEAN_FLAG", value = "true") @@ -52,7 +52,7 @@ class Test { #### Setting a different domain -You can define an own domain on the test-class-level with `@OpenFeatureDefaultDomain` like: +You can define your own domain on the test-class-level with `@OpenFeatureDefaultDomain` like: ```java @OpenFeatureDefaultDomain("domain") @@ -103,7 +103,7 @@ void test() { #### Defining Flags for a whole test-class -`@Flags` can be defined on the class-level too, but method-level +`@Flag` can be defined on the class-level too, but method-level annotations will superseded class-level annotations. ```java