Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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..."
Expand Down
99 changes: 99 additions & 0 deletions test/integration/cmd/java/run.go
Original file line number Diff line number Diff line change
@@ -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"}).
Comment on lines +43 to +44

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Running go mod tidy during the integration test is unnecessary and can lead to non-reproducible builds if the environment differs from the developer's. It's better to rely on go mod download to ensure the dependencies defined in go.sum are used.

Suggested change
WithExec([]string{"go", "mod", "tidy"}).
WithExec([]string{"go", "mod", "download"}).
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 := 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)
}

// 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)
}
}
52 changes: 18 additions & 34 deletions test/integration/cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,45 +7,29 @@ import (
)

func main() {
// Run the language-specific tests
fmt.Println("=== Running all integration tests ===")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The comment // Run the language-specific tests is no longer accurate as the code now iterates through a list of tests. It can be removed for clarity.

Suggested change
fmt.Println("=== Running all integration 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)
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)
}
}
// 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)
}

// Add more tests here as they are available

fmt.Println("=== All integration tests passed successfully ===")
}
47 changes: 47 additions & 0 deletions test/java-integration/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>dev.openfeature</groupId>
<artifactId>cli-java-integration-test</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<!-- OpenFeature Java SDK -->
<dependency>
<groupId>dev.openfeature</groupId>
<artifactId>sdk</artifactId>
<version>1.14.0</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>21</source>
<target>21</target>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<mainClass>dev.openfeature.Main</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
103 changes: 103 additions & 0 deletions test/java-integration/src/main/java/dev/openfeature/Main.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
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.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<String, Object> themeConfig = Map.of(
"primaryColor", "#007bff",
"secondaryColor", "#6c757d"
);

Map<String, Flag<?>> 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);

// 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<Boolean> enableFeatureADetails = EnableFeatureA.valueWithDetails(client, evalContext);
if (enableFeatureADetails.getErrorCode() != null) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The test verifies that no error occurred during evaluation but does not assert that the returned value is correct. It is recommended to add assertions to verify that the values match the defaults configured in the InMemoryProvider (e.g., assert enableFeatureA == false).

throw new Exception("Error evaluating boolean flag");
}

Double discount = DiscountPercentage.value(client, evalContext);
System.out.printf("Discount Percentage: %.2f%n", discount);
FlagEvaluationDetails<Double> 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<String> 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<Integer> usernameDetails = UsernameMaxLength.valueWithDetails(client, evalContext);
if (usernameDetails.getErrorCode() != null) {
throw new Exception("Error evaluating int flag");
}

Value themeCustomization = ThemeCustomization.value(client, evalContext);
FlagEvaluationDetails<Value> 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());
Comment on lines +61 to +101

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The test code is incompatible with the current Java generator implementation. The generator (defined in internal/generators/java/java.tmpl) produces a single OpenFeature class with a GeneratedClient interface and camelCase methods. However, this test attempts to use classes like EnableFeatureA, DiscountPercentage, etc., which are not generated. This will cause compilation failures during the integration test run.

To fix this, the test should be updated to use the generated OpenFeature class:

OpenFeature.GeneratedClient generatedClient = OpenFeature.getClient();
Boolean value = generatedClient.enableFeatureA(evalContext);

}
}
Loading