-
Notifications
You must be signed in to change notification settings - Fork 19
feat: add integration tests for React generator #224
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,98 @@ | ||||||||||||||||||||||||||||||
| 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 in a Go container | ||||||||||||||||||||||||||||||
| cli := client.Container(). | ||||||||||||||||||||||||||||||
| From("golang:1.24.3-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
+40
to
+45
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's good practice to specify the exact version of
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point! I've pinned the Go version to |
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // 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") | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // Create the React test container | ||||||||||||||||||||||||||||||
| reactContainer := client.Container(). | ||||||||||||||||||||||||||||||
| From("node:20.18.1-alpine3.19"). | ||||||||||||||||||||||||||||||
| WithDirectory("/app", testFiles). | ||||||||||||||||||||||||||||||
| WithDirectory("/app/src/generated", generatedFiles). | ||||||||||||||||||||||||||||||
| WithExec([]string{"npm", "install"}). | ||||||||||||||||||||||||||||||
| WithExec([]string{"npm", "run", "build"}). | ||||||||||||||||||||||||||||||
| WithExec([]string{"node", "dist/test.js"}) | ||||||||||||||||||||||||||||||
|
Comment on lines
+58
to
+64
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The indentation for this Dagger pipeline definition is inconsistent, which affects readability. The
Suggested change
|
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| 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 := 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/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) | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| { | ||
| "name": "cli-react-integration-test", | ||
| "version": "1.0.0", | ||
| "description": "Integration test for OpenFeature CLI React generator", | ||
| "scripts": { | ||
| "build": "tsc", | ||
| "test": "node dist/test.js" | ||
| }, | ||
| "dependencies": { | ||
| "@openfeature/react-sdk": "1.0.2", | ||
| "@openfeature/server-sdk": "1.34.0", | ||
| "react": "18.2.0" | ||
| }, | ||
| "devDependencies": { | ||
| "@types/node": "20.17.10", | ||
| "@types/react": "18.2.79", | ||
| "typescript": "5.3.3" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| import * as generated from './generated'; | ||
|
|
||
| async function main() { | ||
| try { | ||
| // Validate that all generated exports exist and have the expected structure | ||
| const flags = [ | ||
| { name: 'EnableFeatureA', flag: generated.EnableFeatureA, expectedKey: 'enableFeatureA' }, | ||
| { name: 'DiscountPercentage', flag: generated.DiscountPercentage, expectedKey: 'discountPercentage' }, | ||
| { name: 'GreetingMessage', flag: generated.GreetingMessage, expectedKey: 'greetingMessage' }, | ||
| { name: 'UsernameMaxLength', flag: generated.UsernameMaxLength, expectedKey: 'usernameMaxLength' }, | ||
| { name: 'ThemeCustomization', flag: generated.ThemeCustomization, expectedKey: 'themeCustomization' }, | ||
| ]; | ||
|
|
||
| for (const { name, flag, expectedKey } of flags) { | ||
| // Validate the flag object has the expected properties | ||
| if (typeof flag !== 'object' || flag === null) { | ||
| throw new Error(`${name} is not an object`); | ||
| } | ||
|
|
||
| // Check for getKey method | ||
| if (typeof flag.getKey !== 'function') { | ||
| throw new Error(`${name}.getKey is not a function`); | ||
| } | ||
|
|
||
| const key = flag.getKey(); | ||
| console.log(`${name} flag key:`, key); | ||
|
|
||
| if (key !== expectedKey) { | ||
| throw new Error(`${name} has incorrect key. Expected '${expectedKey}', but got '${key}'.`); | ||
| } | ||
|
|
||
| // Check for useFlag hook | ||
| if (typeof flag.useFlag !== 'function') { | ||
| throw new Error(`${name}.useFlag is not a function`); | ||
| } | ||
|
|
||
| // Check for useFlagWithDetails hook | ||
| if (typeof flag.useFlagWithDetails !== 'function') { | ||
| throw new Error(`${name}.useFlagWithDetails is not a function`); | ||
| } | ||
| } | ||
|
|
||
| console.log('All generated React hooks are properly structured!'); | ||
| console.log('Generated React code compiles successfully!'); | ||
| process.exit(0); | ||
| } catch (error: unknown) { | ||
| const message = error instanceof Error ? error.message : String(error); | ||
| console.error('Error:', message); | ||
| process.exit(1); | ||
| } | ||
| } | ||
|
|
||
| main(); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| { | ||
| "compilerOptions": { | ||
| "target": "ES2021", | ||
| "module": "commonjs", | ||
| "lib": ["ES2021"], | ||
| "jsx": "react", | ||
| "outDir": "./dist", | ||
| "rootDir": "./src", | ||
| "strict": true, | ||
| "esModuleInterop": true, | ||
| "skipLibCheck": true, | ||
| "forceConsistentCasingInFileNames": true, | ||
| "resolveJsonModule": true, | ||
| "moduleResolution": "node" | ||
| }, | ||
| "include": ["src/**/*"], | ||
| "exclude": ["node_modules"] | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's good practice to specify the exact version of
golangandalpineto ensure consistent builds across different environments and over time. This prevents unexpected build failures if a new version introduces breaking changes.