Skip to content
Closed
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
18 changes: 18 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ 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-nestjs - Run NestJS integration tests"
@echo " test-integration-nodejs - Run NodeJS integration tests"
@echo " test-integration-react - Run React integration tests"
@echo " generate - Generate all code (API clients, docs, schema)"
@echo " generate-api - Generate API clients from OpenAPI specs"
@echo " generate-docs - Generate documentation"
Expand Down Expand Up @@ -67,6 +70,21 @@ 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-nestjs
test-integration-nestjs:
@echo "Running NestJS integration test with Dagger..."
@go run ./test/integration/cmd/nestjs/run.go

.PHONY: test-integration-react
test-integration-react:
@echo "Running React integration test with Dagger..."
@go run ./test/integration/cmd/react/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"}).
WithExec([]string{"go", "build", "-o", "cli", "./cmd/openfeature"})
Comment on lines +38 to +45

Choose a reason for hiding this comment

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

medium

There's a significant amount of duplicated code for building the CLI across the new integration test runners (java/run.go, nestjs/run.go, react/run.go). This logic is identical in each file.

To improve maintainability and reduce redundancy, consider refactoring this into a shared helper function within the integration package.

For example, you could create a BuildCLI function in test/integration/integration.go:

// in test/integration/integration.go
func BuildCLI(client *dagger.Client, source *dagger.Directory) *dagger.Container {
    return 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"})
}

Then, you could call it from each test runner like this:

cli := integration.BuildCLI(client, source)


// 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)
}
}
99 changes: 99 additions & 0 deletions test/integration/cmd/nestjs/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 NestJS 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 NestJS 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{"package.json", "tsconfig.json", "src/**/*.ts"},
})

// 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 NestJS client
generated := cli.WithExec([]string{
"./cli", "generate", "nestjs",
"--manifest=/src/sample/sample_manifest.json",
"--output=/tmp/generated",
})

// Get generated files
generatedFiles := generated.Directory("/tmp/generated")

// Test NestJS compilation with the generated files
nestjsContainer := client.Container().
From("node:20-alpine").
WithWorkdir("/app").
WithDirectory("/app", testFiles).
WithDirectory("/app/src/generated", generatedFiles).
WithExec([]string{"npm", "install"}).
WithExec([]string{"npm", "run", "build"}).
WithExec([]string{"node", "dist/main.js"})

return nestjsContainer, nil
}

// Name returns the name of the integration test
func (t *Test) Name() string {
return "nestjs"
}

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/nestjs-integration"))
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get test dir: %v\n", err)
os.Exit(1)
}

// Create and run the NestJS 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)
}
}
99 changes: 99 additions & 0 deletions test/integration/cmd/react/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 React 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 React 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{"package.json", "tsconfig.json", "src/**/*.ts", "src/**/*.tsx"},
})

// 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 React client
generated := cli.WithExec([]string{
"./cli", "generate", "react",
"--manifest=/src/sample/sample_manifest.json",
"--output=/tmp/generated",
})

// Get generated files
generatedFiles := generated.Directory("/tmp/generated")

// Test React compilation with the generated files
reactContainer := client.Container().
From("node:20-alpine").
WithWorkdir("/app").
WithDirectory("/app", testFiles).
WithDirectory("/app/src/generated", generatedFiles).
WithExec([]string{"npm", "install"}).
WithExec([]string{"npm", "run", "build"}).
WithExec([]string{"node", "dist/test.js"})

return reactContainer, nil
}

// Name returns the name of the integration test
func (t *Test) Name() string {
return "react"
}

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/react-integration"))
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get test dir: %v\n", err)
os.Exit(1)
}

// Create and run the React 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)
}
}
27 changes: 27 additions & 0 deletions test/integration/cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,33 @@ 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)
}

// Run the NestJS integration test
nestjsCmd := exec.Command("go", "run", "github.com/open-feature/cli/test/integration/cmd/nestjs")
nestjsCmd.Stdout = os.Stdout
nestjsCmd.Stderr = os.Stderr
if err := nestjsCmd.Run(); err != nil {
fmt.Fprintf(os.Stderr, "Error running NestJS integration test: %v\n", err)
os.Exit(1)
}

// Run the React integration test
reactCmd := exec.Command("go", "run", "github.com/open-feature/cli/test/integration/cmd/react")
reactCmd.Stdout = os.Stdout
reactCmd.Stderr = os.Stderr
if err := reactCmd.Run(); err != nil {
fmt.Fprintf(os.Stderr, "Error running React integration test: %v\n", err)
os.Exit(1)
}
Comment on lines +48 to +73

Choose a reason for hiding this comment

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

medium

The main integration test runner executes each test sequentially. This can be inefficient, especially as more tests are added. The PR description also mentions that tests can run in parallel. To improve performance and align with that benefit, consider refactoring this to run the tests concurrently using goroutines and a sync.WaitGroup. This would also reduce code duplication.

Here's a conceptual example of how you could structure it:

func main() {
    fmt.Println("=== Running all integration tests ===")

    tests := map[string]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",
        "NestJS":  "github.com/open-feature/cli/test/integration/cmd/nestjs",
        "React":   "github.com/open-feature/cli/test/integration/cmd/react",
    }

    var wg sync.WaitGroup
    errChan := make(chan error, len(tests))

    for name, path := range tests {
        wg.Add(1)
        go func(name, path string) {
            defer wg.Done()
            cmd := exec.Command("go", "run", path)
            cmd.Stdout = os.Stdout
            cmd.Stderr = os.Stderr
            if err := cmd.Run(); err != nil {
                errChan <- fmt.Errorf("error running %s integration test: %w", name, err)
            }
        }(name, path)
    }

    wg.Wait()
    close(errChan)

    hasErrors := false
    for err := range errChan {
        fmt.Fprintln(os.Stderr, err)
        hasErrors = true
    }

    if hasErrors {
        os.Exit(1)
    }

    fmt.Println("=== All integration tests passed successfully ===")
}


// Add more tests here as they are available

fmt.Println("=== All integration tests passed successfully ===")
Expand Down
Loading