From 8d873b316c758041119df7deda95e07813179e46 Mon Sep 17 00:00:00 2001 From: vikasrao23 Date: Fri, 20 Feb 2026 20:37:59 -0800 Subject: [PATCH 1/2] feat: add integration tests for Java generator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements integration test coverage for the Java generator following the standardized Dagger-based testing pattern. Changes: - test/integration/cmd/java/run.go - Dagger test runner for Java - test/java-integration/pom.xml - Maven configuration with OpenFeature SDK - test/java-integration/src/main/java/dev/openfeature/Main.java - Test program - test/integration/cmd/run.go - Added Java test to main runner - Makefile - Added test-integration-java target The test: 1. Builds the CLI from source 2. Generates Java feature flag client code 3. Compiles and executes the generated code with Maven 4. Validates all flag types (boolean, string, int, double, object) 5. Runs as part of CI suite via test-integration target Acceptance criteria met: ✅ Java generator covered by integration test ✅ Generated Java code compiles and passes execution ✅ Test integrated into CI suite ✅ Follows documented integration testing structure Closes #115 Signed-off-by: vikasrao23 --- Makefile | 6 + test/integration/cmd/java/run.go | 99 ++++++++++++++++ test/integration/cmd/run.go | 9 ++ test/java-integration/pom.xml | 47 ++++++++ .../src/main/java/dev/openfeature/Main.java | 107 ++++++++++++++++++ 5 files changed, 268 insertions(+) create mode 100644 test/integration/cmd/java/run.go create mode 100644 test/java-integration/pom.xml create mode 100644 test/java-integration/src/main/java/dev/openfeature/Main.java diff --git a/Makefile b/Makefile index b889d85..b05df5c 100644 --- a/Makefile +++ b/Makefile @@ -10,6 +10,7 @@ help: @echo " test-integration - Run all integration tests" @echo " test-integration-csharp - Run C# integration tests" @echo " test-integration-go - Run Go integration tests" + @echo " test-integration-java - Run Java integration tests" @echo " test-integration-nodejs - Run NodeJS integration tests" @echo " generate - Generate all code (API clients, docs, schema)" @echo " generate-api - Generate API clients from OpenAPI specs" @@ -67,6 +68,11 @@ test-integration-angular: @echo "Running Angular integration test with Dagger..." @go run ./test/integration/cmd/angular/run.go +.PHONY: test-integration-java +test-integration-java: + @echo "Running Java integration test with Dagger..." + @go run ./test/integration/cmd/java/run.go + .PHONY: test-integration test-integration: @echo "Running all integration tests with Dagger..." diff --git a/test/integration/cmd/java/run.go b/test/integration/cmd/java/run.go new file mode 100644 index 0000000..3caca9f --- /dev/null +++ b/test/integration/cmd/java/run.go @@ -0,0 +1,99 @@ +package main + +import ( + "context" + "fmt" + "os" + "path/filepath" + + "dagger.io/dagger" + "github.com/open-feature/cli/test/integration" +) + +// Test implements the integration test for the Java generator +type Test struct { + // ProjectDir is the absolute path to the root of the project + ProjectDir string + // TestDir is the absolute path to the test directory + TestDir string +} + +// New creates a new Test +func New(projectDir, testDir string) *Test { + return &Test{ + ProjectDir: projectDir, + TestDir: testDir, + } +} + +// Run executes the Java integration test using Dagger +func (t *Test) Run(ctx context.Context, client *dagger.Client) (*dagger.Container, error) { + // Source code container + source := client.Host().Directory(t.ProjectDir) + testFiles := client.Host().Directory(t.TestDir, dagger.HostDirectoryOpts{ + Include: []string{"pom.xml", "src/**/*.java"}, + }) + + // Build the CLI + cli := client.Container(). + From("golang:1.24-alpine"). + WithExec([]string{"apk", "add", "--no-cache", "git"}). + WithDirectory("/src", source). + WithWorkdir("/src"). + WithExec([]string{"go", "mod", "tidy"}). + WithExec([]string{"go", "mod", "download"}). + WithExec([]string{"go", "build", "-o", "cli", "./cmd/openfeature"}) + + // Generate Java client + generated := cli.WithExec([]string{ + "./cli", "generate", "java", + "--manifest=/src/sample/sample_manifest.json", + "--output=/tmp/generated", + "--package-name=dev.openfeature.generated", + }) + + // Get generated files + generatedFiles := generated.Directory("/tmp/generated") + + // Test Java compilation with the generated files + javaContainer := client.Container(). + From("maven:3.9-eclipse-temurin-21-alpine"). + WithWorkdir("/app"). + WithDirectory("/app", testFiles). + WithDirectory("/app/src/main/java/dev/openfeature/generated", generatedFiles). + WithExec([]string{"mvn", "clean", "compile", "-B", "-q"}). + WithExec([]string{"mvn", "exec:java", "-Dexec.mainClass=dev.openfeature.Main", "-q"}) + + return javaContainer, nil +} + +// Name returns the name of the integration test +func (t *Test) Name() string { + return "java" +} + +func main() { + ctx := context.Background() + + // Get project root + projectDir, err := filepath.Abs(os.Getenv("PWD")) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to get project dir: %v\n", err) + os.Exit(1) + } + + // Get test directory + testDir, err := filepath.Abs(filepath.Join(projectDir, "test/java-integration")) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to get test dir: %v\n", err) + os.Exit(1) + } + + // Create and run the Java integration test + test := New(projectDir, testDir) + + if err := integration.RunTest(ctx, test); err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } +} diff --git a/test/integration/cmd/run.go b/test/integration/cmd/run.go index 5923c26..e75acaf 100644 --- a/test/integration/cmd/run.go +++ b/test/integration/cmd/run.go @@ -45,6 +45,15 @@ func main() { os.Exit(1) } + // Run the Java integration test + javaCmd := exec.Command("go", "run", "github.com/open-feature/cli/test/integration/cmd/java") + javaCmd.Stdout = os.Stdout + javaCmd.Stderr = os.Stderr + if err := javaCmd.Run(); err != nil { + fmt.Fprintf(os.Stderr, "Error running Java integration test: %v\n", err) + os.Exit(1) + } + // Add more tests here as they are available fmt.Println("=== All integration tests passed successfully ===") diff --git a/test/java-integration/pom.xml b/test/java-integration/pom.xml new file mode 100644 index 0000000..6525552 --- /dev/null +++ b/test/java-integration/pom.xml @@ -0,0 +1,47 @@ + + + 4.0.0 + + dev.openfeature + cli-java-integration-test + 1.0-SNAPSHOT + + + 11 + 11 + UTF-8 + + + + + + dev.openfeature + sdk + 1.14.0 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 11 + 11 + + + + org.codehaus.mojo + exec-maven-plugin + 3.1.0 + + dev.openfeature.Main + + + + + diff --git a/test/java-integration/src/main/java/dev/openfeature/Main.java b/test/java-integration/src/main/java/dev/openfeature/Main.java new file mode 100644 index 0000000..e994c19 --- /dev/null +++ b/test/java-integration/src/main/java/dev/openfeature/Main.java @@ -0,0 +1,107 @@ +package dev.openfeature; + +import dev.openfeature.generated.*; +import dev.openfeature.sdk.*; +import dev.openfeature.sdk.providers.memory.Flag; +import dev.openfeature.sdk.providers.memory.InMemoryProvider; + +import java.util.HashMap; +import java.util.Map; + +public class Main { + public static void main(String[] args) { + try { + run(); + System.out.println("Generated Java code compiles successfully!"); + } catch (Exception e) { + System.err.println("Error: " + e.getMessage()); + e.printStackTrace(); + System.exit(1); + } + } + + private static void run() throws Exception { + // Set up the in-memory provider with test flags + Map> flags = new HashMap<>(); + + flags.put("discountPercentage", Flag.builder() + .variant("default", 0.15) + .defaultVariant("default") + .build()); + + flags.put("enableFeatureA", Flag.builder() + .variant("default", false) + .defaultVariant("default") + .build()); + + flags.put("greetingMessage", Flag.builder() + .variant("default", "Hello there!") + .defaultVariant("default") + .build()); + + flags.put("usernameMaxLength", Flag.builder() + .variant("default", 50) + .defaultVariant("default") + .build()); + + Map themeConfig = new HashMap<>(); + themeConfig.put("primaryColor", "#007bff"); + themeConfig.put("secondaryColor", "#6c757d"); + + flags.put("themeCustomization", Flag.builder() + .variant("default", new Value(themeConfig)) + .defaultVariant("default") + .build()); + + InMemoryProvider provider = new InMemoryProvider(flags); + + // Set the provider + OpenFeatureAPI.getInstance().setProviderAndWait(provider); + + Client client = OpenFeatureAPI.getInstance().getClient(); + MutableContext evalContext = new MutableContext(); + + // Use the generated code for all flag evaluations + Boolean enableFeatureA = EnableFeatureA.value(client, evalContext); + System.out.println("enableFeatureA: " + enableFeatureA); + FlagEvaluationDetails enableFeatureADetails = EnableFeatureA.valueWithDetails(client, evalContext); + if (enableFeatureADetails.getErrorCode() != null) { + throw new Exception("Error evaluating boolean flag"); + } + + Double discount = DiscountPercentage.value(client, evalContext); + System.out.printf("Discount Percentage: %.2f%n", discount); + FlagEvaluationDetails discountDetails = DiscountPercentage.valueWithDetails(client, evalContext); + if (discountDetails.getErrorCode() != null) { + throw new Exception("Failed to get discount"); + } + + String greetingMessage = GreetingMessage.value(client, evalContext); + System.out.println("greetingMessage: " + greetingMessage); + FlagEvaluationDetails greetingDetails = GreetingMessage.valueWithDetails(client, evalContext); + if (greetingDetails.getErrorCode() != null) { + throw new Exception("Error evaluating string flag"); + } + + Integer usernameMaxLength = UsernameMaxLength.value(client, evalContext); + System.out.println("usernameMaxLength: " + usernameMaxLength); + FlagEvaluationDetails usernameDetails = UsernameMaxLength.valueWithDetails(client, evalContext); + if (usernameDetails.getErrorCode() != null) { + throw new Exception("Error evaluating int flag"); + } + + Value themeCustomization = ThemeCustomization.value(client, evalContext); + FlagEvaluationDetails themeDetails = ThemeCustomization.valueWithDetails(client, evalContext); + if (themeDetails.getErrorCode() != null) { + throw new Exception("Error evaluating object flag"); + } + System.out.println("themeCustomization: " + themeCustomization); + + // Test the getKey() method functionality for all flags + System.out.println("enableFeatureA flag key: " + EnableFeatureA.getKey()); + System.out.println("discountPercentage flag key: " + DiscountPercentage.getKey()); + System.out.println("greetingMessage flag key: " + GreetingMessage.getKey()); + System.out.println("usernameMaxLength flag key: " + UsernameMaxLength.getKey()); + System.out.println("themeCustomization flag key: " + ThemeCustomization.getKey()); + } +} From 3d2cf0411b2139d00f0d9a5639f5d2a87a0cc828 Mon Sep 17 00:00:00 2001 From: Vikas Rao Date: Sun, 22 Feb 2026 08:52:24 -0800 Subject: [PATCH 2/2] Address Gemini code review feedback - Use os.Getwd() for better portability and simplify path construction - Update Java version from 11 to 21 to match Docker image - Use Map.of() for immutable maps (Java 9+ best practice) - Add properties for SDK and plugin versions for centralized management - Remove redundant source/target from maven-compiler-plugin configuration - Include flag keys in error messages for better debugging - Remove unused HashMap import --- test/integration/cmd/java/run.go | 8 +-- test/java-integration/pom.xml | 15 ++-- .../src/main/java/dev/openfeature/Main.java | 68 +++++++++---------- 3 files changed, 42 insertions(+), 49 deletions(-) diff --git a/test/integration/cmd/java/run.go b/test/integration/cmd/java/run.go index 3caca9f..b5c67cb 100644 --- a/test/integration/cmd/java/run.go +++ b/test/integration/cmd/java/run.go @@ -76,18 +76,14 @@ func main() { ctx := context.Background() // Get project root - projectDir, err := filepath.Abs(os.Getenv("PWD")) + projectDir, err := os.Getwd() if err != nil { fmt.Fprintf(os.Stderr, "Failed to get project dir: %v\n", err) os.Exit(1) } // Get test directory - testDir, err := filepath.Abs(filepath.Join(projectDir, "test/java-integration")) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to get test dir: %v\n", err) - os.Exit(1) - } + testDir := filepath.Join(projectDir, "test/java-integration") // Create and run the Java integration test test := New(projectDir, testDir) diff --git a/test/java-integration/pom.xml b/test/java-integration/pom.xml index 6525552..d1bc7c7 100644 --- a/test/java-integration/pom.xml +++ b/test/java-integration/pom.xml @@ -9,9 +9,12 @@ 1.0-SNAPSHOT - 11 - 11 + 21 + 21 UTF-8 + 1.14.0 + 3.11.0 + 3.1.0 @@ -19,7 +22,7 @@ dev.openfeature sdk - 1.14.0 + ${openfeature.sdk.version} @@ -28,16 +31,14 @@ org.apache.maven.plugins maven-compiler-plugin - 3.11.0 + ${maven-compiler-plugin.version} - 11 - 11 org.codehaus.mojo exec-maven-plugin - 3.1.0 + ${exec-maven-plugin.version} dev.openfeature.Main diff --git a/test/java-integration/src/main/java/dev/openfeature/Main.java b/test/java-integration/src/main/java/dev/openfeature/Main.java index e994c19..5d6a2d1 100644 --- a/test/java-integration/src/main/java/dev/openfeature/Main.java +++ b/test/java-integration/src/main/java/dev/openfeature/Main.java @@ -5,7 +5,6 @@ import dev.openfeature.sdk.providers.memory.Flag; import dev.openfeature.sdk.providers.memory.InMemoryProvider; -import java.util.HashMap; import java.util.Map; public class Main { @@ -22,36 +21,33 @@ public static void main(String[] args) { private static void run() throws Exception { // Set up the in-memory provider with test flags - Map> flags = new HashMap<>(); - - flags.put("discountPercentage", Flag.builder() - .variant("default", 0.15) - .defaultVariant("default") - .build()); - - flags.put("enableFeatureA", Flag.builder() - .variant("default", false) - .defaultVariant("default") - .build()); - - flags.put("greetingMessage", Flag.builder() - .variant("default", "Hello there!") - .defaultVariant("default") - .build()); - - flags.put("usernameMaxLength", Flag.builder() - .variant("default", 50) - .defaultVariant("default") - .build()); - - Map themeConfig = new HashMap<>(); - themeConfig.put("primaryColor", "#007bff"); - themeConfig.put("secondaryColor", "#6c757d"); - - flags.put("themeCustomization", Flag.builder() - .variant("default", new Value(themeConfig)) - .defaultVariant("default") - .build()); + Map themeConfig = Map.of( + "primaryColor", "#007bff", + "secondaryColor", "#6c757d" + ); + + Map> flags = Map.of( + "discountPercentage", Flag.builder() + .variant("default", 0.15) + .defaultVariant("default") + .build(), + "enableFeatureA", Flag.builder() + .variant("default", false) + .defaultVariant("default") + .build(), + "greetingMessage", Flag.builder() + .variant("default", "Hello there!") + .defaultVariant("default") + .build(), + "usernameMaxLength", Flag.builder() + .variant("default", 50) + .defaultVariant("default") + .build(), + "themeCustomization", Flag.builder() + .variant("default", new Value(themeConfig)) + .defaultVariant("default") + .build() + ); InMemoryProvider provider = new InMemoryProvider(flags); @@ -66,34 +62,34 @@ private static void run() throws Exception { System.out.println("enableFeatureA: " + enableFeatureA); FlagEvaluationDetails enableFeatureADetails = EnableFeatureA.valueWithDetails(client, evalContext); if (enableFeatureADetails.getErrorCode() != null) { - throw new Exception("Error evaluating boolean flag"); + throw new Exception("Error evaluating boolean flag: " + enableFeatureADetails.getFlagKey()); } Double discount = DiscountPercentage.value(client, evalContext); System.out.printf("Discount Percentage: %.2f%n", discount); FlagEvaluationDetails discountDetails = DiscountPercentage.valueWithDetails(client, evalContext); if (discountDetails.getErrorCode() != null) { - throw new Exception("Failed to get discount"); + throw new Exception("Failed to get discount for flag: " + discountDetails.getFlagKey()); } String greetingMessage = GreetingMessage.value(client, evalContext); System.out.println("greetingMessage: " + greetingMessage); FlagEvaluationDetails greetingDetails = GreetingMessage.valueWithDetails(client, evalContext); if (greetingDetails.getErrorCode() != null) { - throw new Exception("Error evaluating string flag"); + throw new Exception("Error evaluating string flag: " + greetingDetails.getFlagKey()); } Integer usernameMaxLength = UsernameMaxLength.value(client, evalContext); System.out.println("usernameMaxLength: " + usernameMaxLength); FlagEvaluationDetails usernameDetails = UsernameMaxLength.valueWithDetails(client, evalContext); if (usernameDetails.getErrorCode() != null) { - throw new Exception("Error evaluating int flag"); + throw new Exception("Error evaluating int flag: " + usernameDetails.getFlagKey()); } Value themeCustomization = ThemeCustomization.value(client, evalContext); FlagEvaluationDetails themeDetails = ThemeCustomization.valueWithDetails(client, evalContext); if (themeDetails.getErrorCode() != null) { - throw new Exception("Error evaluating object flag"); + throw new Exception("Error evaluating object flag: " + themeDetails.getFlagKey()); } System.out.println("themeCustomization: " + themeCustomization);