From 2c5e7b53fc3f3b994f381d6e90e5ee6a514106e7 Mon Sep 17 00:00:00 2001 From: Kugamoorthy Gajananan Date: Wed, 12 Feb 2025 23:25:22 +1100 Subject: [PATCH 1/2] Add unit tests for Swagger datasource definition generation (#5283) - Implement unit tests to validate datasource definition generation from Swagger files. - Ensure hardcoded OpenAPI v2 docs produce expected datasource definitions. - Maintain robustness of generation command to prevent accidental breaks. Signed-off-by: Kugamoorthy Gajananan --- cmd/dev/app/datasource/generate_test.go | 157 ++++++++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 cmd/dev/app/datasource/generate_test.go diff --git a/cmd/dev/app/datasource/generate_test.go b/cmd/dev/app/datasource/generate_test.go new file mode 100644 index 0000000000..d38742e58e --- /dev/null +++ b/cmd/dev/app/datasource/generate_test.go @@ -0,0 +1,157 @@ +// SPDX-FileCopyrightText: Copyright 2025 The Minder Authors +// SPDX-License-Identifier: Apache-2.0 + +package datasource_test + +import ( + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + "google.golang.org/protobuf/encoding/protojson" + + "github.com/mindersec/minder/cmd/dev/app/datasource" + minderv1 "github.com/mindersec/minder/pkg/api/protobuf/go/minder/v1" +) + +type testWriter struct { + output string +} + +func (tw *testWriter) Write(p []byte) (n int, err error) { + tw.output += string(p) + return len(p), nil +} + +func TestCmdGenerate(t *testing.T) { + t.Parallel() + + // Create test files + testDir := t.TempDir() + + // Create a simple OpenAPI v2 document with basePath + simpleOpenAPI := `{ + "swagger": "2.0", + "info": { + "title": "Test API", + "version": "1.0.0" + }, + "basePath": "/api", + "paths": { + "/test": { + "get": { + "summary": "Test endpoint", + "operationId": "getTest", + "parameters": [], + "responses": { + "200": { + "description": "Successful response" + } + } + } + } + } +} +` + simpleOpenAPIPath := filepath.Join(testDir, "simple_openapi.json") + err := os.WriteFile(simpleOpenAPIPath, []byte(simpleOpenAPI), 0600) + assert.NoError(t, err, "Failed to create simple OpenAPI file") + + // Create the expected datasource definition for the simple API + simpleDatasource := &minderv1.DataSource{ + Id: uuid.New().String(), + Name: "updated_ds", + Context: &minderv1.ContextV2{ + ProjectId: uuid.New().String(), + }, + Driver: &minderv1.DataSource_Rest{ + Rest: &minderv1.RestDataSource{ + Def: map[string]*minderv1.RestDataSource_Def{ + "GET_/api/test": { + Method: "GET", + Endpoint: "/api/test", + Parse: "json", + }, + }, + }, + }, + } + + // Marshal using protojson + simpleDatasourceData, err := protojson.Marshal(simpleDatasource) + assert.NoError(t, err, "Failed to marshal simple datasource data") + simpleDatasourcePath := filepath.Join(testDir, "simple_datasource.json") + err = os.WriteFile(simpleDatasourcePath, simpleDatasourceData, 0600) + assert.NoError(t, err, "Failed to create simple datasource file") + + tests := []struct { + name string + openAPIFile string + expectedDataFile string + expectedError bool + }{ + { + name: "simple API", + openAPIFile: simpleOpenAPIPath, + expectedDataFile: simpleDatasourcePath, + expectedError: false, + }, + { + name: "missing OpenAPI file", + openAPIFile: filepath.Join(testDir, "missing_openapi.json"), + expectedDataFile: "", + expectedError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + cmd := datasource.CmdGenerate() + tw := &testWriter{} + cmd.SetOut(tw) + cmd.SetErr(tw) + cmd.SetArgs([]string{tt.openAPIFile}) + + // Execute command and capture any errors + err := cmd.Execute() + if tt.expectedError { + assert.Error(t, err, "Expected an error but got none") + return + } + assert.NoError(t, err, "Command execution should not produce an error") + + // Handle the case for missing OpenAPI file + if tt.name == "missing OpenAPI file" { + assert.FileExists(t, tt.openAPIFile, "OpenAPI file should not exist") + return + } + // Print captured output for debugging + fmt.Println("Captured Output:", tw.output) + // Load expected datasource definition + expectedData, err := os.ReadFile(tt.expectedDataFile) + assert.NoError(t, err, "Failed to read expected data file") + + var expectedDS minderv1.DataSource + err = protojson.Unmarshal(expectedData, &expectedDS) + assert.NoError(t, err, "Failed to unmarshal expected data") + + // // Load generated datasource definition + // var generatedDS minderv1.DataSource + // err = protojson.Unmarshal([]byte(tw.output), &generatedDS) + // assert.NoError(t, err, "Failed to unmarshal generated data") + + // // Compare the generated and expected datasource definitions + // if !assert.Equal(t, &expectedDS, &generatedDS, "Generated datasource definition should match expected") { + // expectedStr := protojson.Format(&expectedDS) + // generatedStr := protojson.Format(&generatedDS) + // fmt.Printf("Mismatch between expected and generated datasource definitions:\nExpected:\n%s\nGenerated:\n%s", expectedStr, generatedStr) + // t.Errorf("Buffer content: %s", tw.output) + // } + }) + } +} From e049ff7b0c9792bf95cbb21597b73d7ba4a91466 Mon Sep 17 00:00:00 2001 From: Kugamoorthy Gajananan Date: Sat, 15 Feb 2025 09:28:37 +1100 Subject: [PATCH 2/2] Add unit tests for Swagger datasource definition generation (#5283) - Implement unit tests to validate datasource definition generation from Swagger files. - Ensure hardcoded OpenAPI v2 docs produce expected datasource definitions. - Maintain robustness of generation command to prevent accidental breaks. - Renamed generate_test to main_test.go and moved it to cmd/dev/ Signed-off-by: Kugamoorthy Gajananan --- cmd/dev/app/datasource/generate_test.go | 157 ------------------------ cmd/dev/main_test.go | 141 +++++++++++++++++++++ 2 files changed, 141 insertions(+), 157 deletions(-) delete mode 100644 cmd/dev/app/datasource/generate_test.go create mode 100644 cmd/dev/main_test.go diff --git a/cmd/dev/app/datasource/generate_test.go b/cmd/dev/app/datasource/generate_test.go deleted file mode 100644 index d38742e58e..0000000000 --- a/cmd/dev/app/datasource/generate_test.go +++ /dev/null @@ -1,157 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2025 The Minder Authors -// SPDX-License-Identifier: Apache-2.0 - -package datasource_test - -import ( - "fmt" - "os" - "path/filepath" - "testing" - - "github.com/google/uuid" - "github.com/stretchr/testify/assert" - "google.golang.org/protobuf/encoding/protojson" - - "github.com/mindersec/minder/cmd/dev/app/datasource" - minderv1 "github.com/mindersec/minder/pkg/api/protobuf/go/minder/v1" -) - -type testWriter struct { - output string -} - -func (tw *testWriter) Write(p []byte) (n int, err error) { - tw.output += string(p) - return len(p), nil -} - -func TestCmdGenerate(t *testing.T) { - t.Parallel() - - // Create test files - testDir := t.TempDir() - - // Create a simple OpenAPI v2 document with basePath - simpleOpenAPI := `{ - "swagger": "2.0", - "info": { - "title": "Test API", - "version": "1.0.0" - }, - "basePath": "/api", - "paths": { - "/test": { - "get": { - "summary": "Test endpoint", - "operationId": "getTest", - "parameters": [], - "responses": { - "200": { - "description": "Successful response" - } - } - } - } - } -} -` - simpleOpenAPIPath := filepath.Join(testDir, "simple_openapi.json") - err := os.WriteFile(simpleOpenAPIPath, []byte(simpleOpenAPI), 0600) - assert.NoError(t, err, "Failed to create simple OpenAPI file") - - // Create the expected datasource definition for the simple API - simpleDatasource := &minderv1.DataSource{ - Id: uuid.New().String(), - Name: "updated_ds", - Context: &minderv1.ContextV2{ - ProjectId: uuid.New().String(), - }, - Driver: &minderv1.DataSource_Rest{ - Rest: &minderv1.RestDataSource{ - Def: map[string]*minderv1.RestDataSource_Def{ - "GET_/api/test": { - Method: "GET", - Endpoint: "/api/test", - Parse: "json", - }, - }, - }, - }, - } - - // Marshal using protojson - simpleDatasourceData, err := protojson.Marshal(simpleDatasource) - assert.NoError(t, err, "Failed to marshal simple datasource data") - simpleDatasourcePath := filepath.Join(testDir, "simple_datasource.json") - err = os.WriteFile(simpleDatasourcePath, simpleDatasourceData, 0600) - assert.NoError(t, err, "Failed to create simple datasource file") - - tests := []struct { - name string - openAPIFile string - expectedDataFile string - expectedError bool - }{ - { - name: "simple API", - openAPIFile: simpleOpenAPIPath, - expectedDataFile: simpleDatasourcePath, - expectedError: false, - }, - { - name: "missing OpenAPI file", - openAPIFile: filepath.Join(testDir, "missing_openapi.json"), - expectedDataFile: "", - expectedError: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - cmd := datasource.CmdGenerate() - tw := &testWriter{} - cmd.SetOut(tw) - cmd.SetErr(tw) - cmd.SetArgs([]string{tt.openAPIFile}) - - // Execute command and capture any errors - err := cmd.Execute() - if tt.expectedError { - assert.Error(t, err, "Expected an error but got none") - return - } - assert.NoError(t, err, "Command execution should not produce an error") - - // Handle the case for missing OpenAPI file - if tt.name == "missing OpenAPI file" { - assert.FileExists(t, tt.openAPIFile, "OpenAPI file should not exist") - return - } - // Print captured output for debugging - fmt.Println("Captured Output:", tw.output) - // Load expected datasource definition - expectedData, err := os.ReadFile(tt.expectedDataFile) - assert.NoError(t, err, "Failed to read expected data file") - - var expectedDS minderv1.DataSource - err = protojson.Unmarshal(expectedData, &expectedDS) - assert.NoError(t, err, "Failed to unmarshal expected data") - - // // Load generated datasource definition - // var generatedDS minderv1.DataSource - // err = protojson.Unmarshal([]byte(tw.output), &generatedDS) - // assert.NoError(t, err, "Failed to unmarshal generated data") - - // // Compare the generated and expected datasource definitions - // if !assert.Equal(t, &expectedDS, &generatedDS, "Generated datasource definition should match expected") { - // expectedStr := protojson.Format(&expectedDS) - // generatedStr := protojson.Format(&generatedDS) - // fmt.Printf("Mismatch between expected and generated datasource definitions:\nExpected:\n%s\nGenerated:\n%s", expectedStr, generatedStr) - // t.Errorf("Buffer content: %s", tw.output) - // } - }) - } -} diff --git a/cmd/dev/main_test.go b/cmd/dev/main_test.go new file mode 100644 index 0000000000..4241cbb1af --- /dev/null +++ b/cmd/dev/main_test.go @@ -0,0 +1,141 @@ +// SPDX-FileCopyrightText: Copyright 2023 The Minder Authors +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "bytes" + "io" + "os" + "path/filepath" + "strings" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/mindersec/minder/cmd/dev/app" +) + +func TestCobraMain(t *testing.T) { + + t.Parallel() + + // Create test files + testDir := t.TempDir() + + // Create a simple OpenAPI v2 document with basePath + simpleOpenAPI := `swagger: "2.0" +info: + title: "Test API" + version: "1.0.0" +basePath: "/api" +paths: + /test: + get: + summary: "Test endpoint" + operationId: "getTest" + parameters: [] + responses: + "200": + description: "Successful response" + content: + application/json: {}` + + simpleOpenAPIPath := filepath.Join(testDir, "simple_openapi.json") + err := os.WriteFile(simpleOpenAPIPath, []byte(simpleOpenAPI), 0600) + assert.NoError(t, err, "Failed to create simple OpenAPI file") + + tests := []struct { + name string + openAPIFile string + expectedData string + expectedError bool + }{ + { + name: "simple API", + openAPIFile: simpleOpenAPIPath, + expectedData: `version: v1 + type: data-source + context: {} + name: Test API + rest: + def: + get_test: + endpoint: /api/test + method: GET + parse: json + inputSchema: {}`, + expectedError: false, + }, + { + name: "missing OpenAPI file", + openAPIFile: filepath.Join(testDir, "missing_openapi.json"), + expectedData: "", + expectedError: true, + }, + } + var mu sync.Mutex + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + cmd := app.CmdRoot() + + cmd.SetArgs([]string{"datasource", "generate", tt.openAPIFile}) + + // Save the original os.Stdout + originalStdout := os.Stdout + + // Create a pipe to capture the output, + r, w, _ := os.Pipe() + os.Stdout = w + + // Redirect the output to the buffer in a separate goroutine + outC := make(chan string) + go func() { + var buf bytes.Buffer + _, err = io.Copy(&buf, r) + assert.NoError(t, err, "Buffer copy should not produce an error") + + outC <- buf.String() + }() + + // Execute command and capture any errors + err = cmd.Execute() + if tt.expectedError { + assert.Error(t, err, "Expected an error but got none") + return + } + // Close the writer and restore original os.Stdout + err = w.Close() + assert.NoError(t, err, "File close should not produce an error") + os.Stdout = originalStdout + + // Read the captured output + output := <-outC + + assert.NoError(t, err, "Command execution should not produce an error") + + // Handle the case for missing OpenAPI file + if tt.name == "missing OpenAPI file" { + mu.Lock() + assert.FileExists(t, tt.openAPIFile, "OpenAPI file should not exist") + mu.Unlock() + return + } + + assert.NoError(t, err, "Command execution should not produce an error") + + // Normalize and compare the YAML strings + expectedYAML := strings.Join(strings.Fields(string(tt.expectedData)), "") + generatedYAML := strings.Join(strings.Fields(string(output)), "") + // Compare the YAML strings directly + assert.Equal(t, expectedYAML, generatedYAML, "Generated datasource definition should match expected") + + // Add a slight delay to ensure the output is captured correctly + time.Sleep(100 * time.Millisecond) + }) + } +}