From 28d81c4b659b6a12e79f62c44c8c9e365e422d30 Mon Sep 17 00:00:00 2001 From: vikasrao23 Date: Fri, 20 Feb 2026 20:46:42 -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, 267 insertions(+), 1 deletion(-) 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..d08a55e 100644 --- a/test/integration/cmd/run.go +++ b/test/integration/cmd/run.go @@ -45,7 +45,14 @@ func main() { os.Exit(1) } - // Add more tests here as they are available + // 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) + } 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 6e1886a479c668efb4a5ade255e7f3d8b6dff1fe Mon Sep 17 00:00:00 2001 From: Vikas Rao Date: Sun, 22 Feb 2026 08:50:47 -0800 Subject: [PATCH 2/2] Address Gemini code review feedback - Use os.Getwd() instead of filepath.Abs(os.Getenv("PWD")) for better portability - Update Java version from 11 to 21 to match test Docker image - Use Map.of() for immutable maps (Java 9+ best practice) - Remove unused HashMap import - Refactor integration test runner to use loop pattern for better maintainability --- test/integration/cmd/java/run.go | 2 +- test/integration/cmd/run.go | 59 ++++++------------- test/java-integration/pom.xml | 8 +-- .../src/main/java/dev/openfeature/Main.java | 58 +++++++++--------- 4 files changed, 50 insertions(+), 77 deletions(-) diff --git a/test/integration/cmd/java/run.go b/test/integration/cmd/java/run.go index 3caca9f..c80eaf9 100644 --- a/test/integration/cmd/java/run.go +++ b/test/integration/cmd/java/run.go @@ -76,7 +76,7 @@ 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) diff --git a/test/integration/cmd/run.go b/test/integration/cmd/run.go index d08a55e..3c8ac8e 100644 --- a/test/integration/cmd/run.go +++ b/test/integration/cmd/run.go @@ -7,51 +7,28 @@ import ( ) func main() { - // Run the language-specific tests fmt.Println("=== Running all integration tests ===") - // Run the C# integration test - csharpCmd := exec.Command("go", "run", "github.com/open-feature/cli/test/integration/cmd/csharp") - csharpCmd.Stdout = os.Stdout - csharpCmd.Stderr = os.Stderr - if err := csharpCmd.Run(); err != nil { - fmt.Fprintf(os.Stderr, "Error running C# integration test: %v\n", err) - os.Exit(1) + tests := []struct { + name string + path string + }{ + {"C#", "github.com/open-feature/cli/test/integration/cmd/csharp"}, + {"Go", "github.com/open-feature/cli/test/integration/cmd/go"}, + {"NodeJS", "github.com/open-feature/cli/test/integration/cmd/nodejs"}, + {"Angular", "github.com/open-feature/cli/test/integration/cmd/angular"}, + {"Java", "github.com/open-feature/cli/test/integration/cmd/java"}, } - // Run the Go integration test - goCmd := exec.Command("go", "run", "github.com/open-feature/cli/test/integration/cmd/go") - goCmd.Stdout = os.Stdout - goCmd.Stderr = os.Stderr - if err := goCmd.Run(); err != nil { - fmt.Fprintf(os.Stderr, "Error running Go integration test: %v\n", err) - os.Exit(1) - } - // Run the nodejs test - nodeCmd := exec.Command("go", "run", "github.com/open-feature/cli/test/integration/cmd/nodejs") - nodeCmd.Stdout = os.Stdout - nodeCmd.Stderr = os.Stderr - if err := nodeCmd.Run(); err != nil { - fmt.Fprintf(os.Stderr, "Error running nodejs integration test: %v\n", err) - os.Exit(1) - } - - // Run the Angular integration test - angularCmd := exec.Command("go", "run", "github.com/open-feature/cli/test/integration/cmd/angular") - angularCmd.Stdout = os.Stdout - angularCmd.Stderr = os.Stderr - if err := angularCmd.Run(); err != nil { - fmt.Fprintf(os.Stderr, "Error running Angular integration test: %v\n", err) - 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) + for _, test := range tests { + fmt.Printf("--- Running %s integration test ---\n", test.name) + cmd := exec.Command("go", "run", test.path) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + fmt.Fprintf(os.Stderr, "Error running %s integration test: %v\n", test.name, err) + os.Exit(1) + } } fmt.Println("=== All integration tests passed successfully ===") diff --git a/test/java-integration/pom.xml b/test/java-integration/pom.xml index 6525552..0bb7be4 100644 --- a/test/java-integration/pom.xml +++ b/test/java-integration/pom.xml @@ -9,8 +9,8 @@ 1.0-SNAPSHOT - 11 - 11 + 21 + 21 UTF-8 @@ -30,8 +30,8 @@ maven-compiler-plugin 3.11.0 - 11 - 11 + 21 + 21 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..7fb10a2 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);