cli/internal/cmd/generate_test.go

189 lines
5.6 KiB
Go

package cmd
import (
"os"
"path/filepath"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/open-feature/cli/internal/config"
"github.com/open-feature/cli/internal/filesystem"
"github.com/spf13/afero"
)
// generateTestCase holds the configuration for each generate test
type generateTestCase struct {
name string // test case name
command string // generator to run
manifestGolden string // path to the golden manifest file
outputGolden string // path to the golden output file
outputPath string // output directory (optional, defaults to "output")
outputFile string // output file name
packageName string // optional, used for Go (package-name), Java (package-name) and C# (namespace)
}
func TestGenerate(t *testing.T) {
testCases := []generateTestCase{
{
name: "Go generation success",
command: "go",
manifestGolden: "testdata/success_manifest.golden",
outputGolden: "testdata/success_go.golden",
outputFile: "testpackage.go",
packageName: "testpackage",
},
{
name: "React generation success",
command: "react",
manifestGolden: "testdata/success_manifest.golden",
outputGolden: "testdata/success_react.golden",
outputFile: "openfeature.ts",
},
{
name: "NodeJS generation success",
command: "nodejs",
manifestGolden: "testdata/success_manifest.golden",
outputGolden: "testdata/success_nodejs.golden",
outputFile: "openfeature.ts",
},
{
name: "NestJS generation success",
command: "nestjs",
manifestGolden: "testdata/success_manifest.golden",
outputGolden: "testdata/success_nestjs.golden",
outputFile: "openfeature-decorators.ts",
},
{
name: "Python generation success",
command: "python",
manifestGolden: "testdata/success_manifest.golden",
outputGolden: "testdata/success_python.golden",
outputFile: "openfeature.py",
},
{
name: "CSharp generation success",
command: "csharp",
manifestGolden: "testdata/success_manifest.golden",
outputGolden: "testdata/success_csharp.golden",
outputFile: "OpenFeature.g.cs",
packageName: "TestNamespace", // Using packageName field for namespace
},
{
name: "Java generation success",
command: "java",
manifestGolden: "testdata/success_manifest.golden",
outputGolden: "testdata/success_java.golden",
outputFile: "OpenFeature.java",
packageName: "com.example.openfeature",
},
// Add more test cases here as needed
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
cmd := GetGenerateCmd()
// global flag exists on root only.
config.AddRootFlags(cmd)
// Constant paths
const memoryManifestPath = "manifest/path.json"
// Use default output path if not specified
outputPath := tc.outputPath
if outputPath == "" {
outputPath = "output"
}
// Prepare in-memory files
fs := afero.NewMemMapFs()
filesystem.SetFileSystem(fs)
readOsFileAndWriteToMemMap(t, tc.manifestGolden, memoryManifestPath, fs)
// Prepare command arguments
args := []string{
tc.command,
"--manifest", memoryManifestPath,
"--output", outputPath,
}
// Add parameters specific to each generator
if tc.packageName != "" {
if tc.command == "csharp" {
args = append(args, "--namespace", tc.packageName)
} else if tc.command == "go" {
args = append(args, "--package-name", tc.packageName)
} else if tc.command == "java" {
args = append(args, "--package-name", tc.packageName)
}
}
cmd.SetArgs(args)
// Run command
err := cmd.Execute()
if err != nil {
t.Error(err)
}
// Compare result
compareOutput(t, tc.outputGolden, filepath.Join(outputPath, tc.outputFile), fs)
})
}
}
func readOsFileAndWriteToMemMap(t *testing.T, inputPath string, memPath string, memFs afero.Fs) {
data, err := os.ReadFile(inputPath)
if err != nil {
t.Fatalf("error reading file %q: %v", inputPath, err)
}
if err := memFs.MkdirAll(filepath.Dir(memPath), os.ModePerm); err != nil {
t.Fatalf("error creating directory %q: %v", filepath.Dir(memPath), err)
}
f, err := memFs.Create(memPath)
if err != nil {
t.Fatalf("error creating file %q: %v", memPath, err)
}
defer f.Close()
writtenBytes, err := f.Write(data)
if err != nil {
t.Fatalf("error writing contents to file %q: %v", memPath, err)
}
if writtenBytes != len(data) {
t.Fatalf("error writing entire file %v: writtenBytes != expectedWrittenBytes", memPath)
}
}
// normalizeLines trims trailing whitespace and carriage returns from each line.
// This helps ensure consistent comparison by ignoring formatting differences like indentation or line endings.
func normalizeLines(input []string) []string {
normalized := make([]string, len(input))
for i, line := range input {
// Trim right whitespace and convert \r\n or \r to \n
normalized[i] = strings.TrimRight(line, " \t\r")
}
return normalized
}
func compareOutput(t *testing.T, testFile, memoryOutputPath string, fs afero.Fs) {
want, err := os.ReadFile(testFile)
if err != nil {
t.Fatalf("error reading file %q: %v", testFile, err)
}
got, err := afero.ReadFile(fs, memoryOutputPath)
if err != nil {
t.Fatalf("error reading file %q: %v", memoryOutputPath, err)
}
// Convert to string arrays by splitting on newlines
wantLines := normalizeLines(strings.Split(string(want), "\n"))
gotLines := normalizeLines(strings.Split(string(got), "\n"))
if diff := cmp.Diff(wantLines, gotLines); diff != "" {
t.Errorf("output mismatch (-want +got):\n%s", diff)
}
}