From 7a3c484b213ce78b15526414cb9f4f4c545df342 Mon Sep 17 00:00:00 2001 From: Sivaanand Murugesan Date: Mon, 19 Aug 2024 19:49:13 +0530 Subject: [PATCH 1/8] initial commit --- .gitignore | 6 + spectrocloud/common_test.go | 102 +++++++++++------ spectrocloud/resource_project_test.go | 14 +++ tests/mockApiServer/main.go | 114 +++++++++++++++++++ tests/mockApiServer/routes.json | 20 ++++ tests/mockApiServer/start_mock_api_server.sh | 17 +++ tests/mockApiServer/stop_mock_api_server.sh | 12 ++ 7 files changed, 248 insertions(+), 37 deletions(-) create mode 100644 tests/mockApiServer/main.go create mode 100644 tests/mockApiServer/routes.json create mode 100755 tests/mockApiServer/start_mock_api_server.sh create mode 100755 tests/mockApiServer/stop_mock_api_server.sh diff --git a/.gitignore b/.gitignore index b5c22c07..0028ba47 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,9 @@ kubeconfig_* .local dist providers + +# ignore for mock api server +server.crt +server.key +MockAPIServer +mock_api_server.log \ No newline at end of file diff --git a/spectrocloud/common_test.go b/spectrocloud/common_test.go index a1ed154e..2c0baa47 100644 --- a/spectrocloud/common_test.go +++ b/spectrocloud/common_test.go @@ -1,20 +1,37 @@ package spectrocloud import ( + "context" "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/spectrocloud/palette-sdk-go/client" "os" + "os/exec" "testing" ) -type Cred struct { - hubbleHost string - project string - apikey string - component string - AlertUid string -} +//type Cred struct { +// hubbleHost string +// project string +// apikey string +// component string +// AlertUid string +//} + +const ( + host = "127.0.0.1:8080" + trace = false + retryAttempts = 10 + apiKey = "12345" + projectName = "unittest" + projectUID = "testprojectuid" +) -var baseConfig Cred +// var baseConfig Cred +var unitTestMockAPIClient interface{} +var basePath = os.Getenv("TF_SRC") +var startMockApiServerScript = basePath + "/tests/mockApiServer/start_mock_api_server.sh" +var stopMockApiServerScript = basePath + "/tests/mockApiServer/stop_mock_api_server.sh" func TestMain(m *testing.M) { setup() @@ -23,39 +40,50 @@ func TestMain(m *testing.M) { os.Exit(code) } +func unitTestProviderConfigure(ctx context.Context) (interface{}, diag.Diagnostics) { + host := host + apiKey := apiKey + retryAttempts := retryAttempts + + // Warning or errors can be collected in a slice type + var diags diag.Diagnostics + + c := client.New( + client.WithPaletteURI(host), + client.WithAPIKey(apiKey), + client.WithRetries(retryAttempts), + client.WithInsecureSkipVerify(true)) + //// comment to trace flag + //client.WithTransportDebug()(c) + uid := projectUID + ProviderInitProjectUid = uid + client.WithScopeProject(uid)(c) + + return c, diags + +} + func setup() { - // Setting up test credentials & base config from env variables - baseConfig.hubbleHost = getEnvWithFallBack("TEST_HOST") - baseConfig.project = getEnvWithFallBack("TEST_PROJECT") - baseConfig.apikey = getEnvWithFallBack("TEST_API_KEY") - baseConfig.component = "ClusterHealth" - baseConfig.AlertUid = "" - if IsIntegrationTestEnvSet(baseConfig) { - fmt.Printf("\033[1;36m%s\033[0m", "> Credentials & Base config setup completed\n") - fmt.Printf("\033[1;36m%s\033[0m", "-- Test Runnig with below crdentials & base config\n") - fmt.Printf("* Test host - %s \n", baseConfig.hubbleHost) - fmt.Printf("* Test project - %s \n", baseConfig.project) - fmt.Printf("* Test key - %s \n", "***********************") - fmt.Printf("\033[1;36m%s\033[0m", "-------------------------------\n") - } else { - fmt.Printf("\033[1;36m%s\033[0m", "> Since env variable not sipping integration test\n") + fmt.Printf("\033[1;36m%s\033[0m", "> Starting Mock API Server \n") + var ctx context.Context + + cmd := exec.Command("sh", startMockApiServerScript) + output, err := cmd.CombinedOutput() + if err != nil { + fmt.Printf("Failed to run start api server script: %s\nError: %s", output, err) } + fmt.Printf("\033[1;36m%s\033[0m", "> Started Mock Api Server at https://127.0.0.1:8080 \n") + unitTestMockAPIClient, _ = unitTestProviderConfigure(ctx) + fmt.Printf("\033[1;36m%s\033[0m", "> Setup completed \n") } -func IsIntegrationTestEnvSet(config Cred) (envSet bool) { - if config.hubbleHost != "" && config.project != "" && config.apikey != "" { - return true - } else { - return false - } -} -func getEnvWithFallBack(key string) (response string) { - value := os.Getenv(key) - if len(value) == 0 { - return "" - } - return value -} + func teardown() { + cmd := exec.Command("bash", stopMockApiServerScript) + output, err := cmd.CombinedOutput() + if err != nil { + fmt.Printf("Failed to run stop api server script: %s\nError: %s", output, err) + } + fmt.Printf("\033[1;36m%s\033[0m", "> Stopped Mock Api Server \n") fmt.Printf("\033[1;36m%s\033[0m", "> Teardown completed \n") } diff --git a/spectrocloud/resource_project_test.go b/spectrocloud/resource_project_test.go index 06839b1d..4dc96d5c 100644 --- a/spectrocloud/resource_project_test.go +++ b/spectrocloud/resource_project_test.go @@ -1,6 +1,8 @@ package spectrocloud import ( + "context" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -81,3 +83,15 @@ func TestToProject(t *testing.T) { }) } } + +func TestCreateFunc(t *testing.T) { + d := resourceProject().TestResourceData() + var diags diag.Diagnostics + err := d.Set("name", "dev") + if err != nil { + return + } + var ctx context.Context + diags = resourceProjectCreate(ctx, d, unitTestMockAPIClient) + assert.Equal(t, 0, len(diags)) +} diff --git a/tests/mockApiServer/main.go b/tests/mockApiServer/main.go new file mode 100644 index 00000000..a1dff6b6 --- /dev/null +++ b/tests/mockApiServer/main.go @@ -0,0 +1,114 @@ +package main + +import ( + "encoding/base64" + "encoding/json" + "io/ioutil" + "log" + "net/http" + "strings" +) + +type Route struct { + Path string `json:"path"` + Method string `json:"method"` + Headers map[string]string `json:"headers,omitempty"` + Auth *AuthConfig `json:"auth,omitempty"` + Response ResponseConfig `json:"response"` +} + +type AuthConfig struct { + Type string `json:"type"` + Header string `json:"header,omitempty"` + Key string `json:"key,omitempty"` + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` +} + +type ResponseConfig struct { + Status int `json:"status"` + Body json.RawMessage `json:"body"` +} + +func main() { + // Load routes from JSON file + routes := loadRoutes("./routes.json") + + // Setup handlers based on routes + for _, route := range routes { + http.HandleFunc(route.Path, createHandler(route)) + } + + // Start server + log.Println("Starting server on :8080") + err := http.ListenAndServeTLS(":8080", "./server.crt", "./server.key", nil) + if err != nil { + log.Fatal("ListenAndServeTLS: ", err) + } + log.Fatal(http.ListenAndServe(":8080", nil)) +} + +func loadRoutes(file string) []Route { + data, err := ioutil.ReadFile(file) + if err != nil { + log.Fatalf("Error reading routes file: %v", err) + } + + var routes []Route + if err := json.Unmarshal(data, &routes); err != nil { + log.Fatalf("Error unmarshaling routes: %v", err) + } + return routes +} + +func createHandler(route Route) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if r.Method != route.Method { + http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) + return + } + + // Check authentication if required + if route.Auth != nil && !checkAuth(route.Auth, r) { + http.Error(w, "Unauthorized", http.StatusUnauthorized) + return + } + + // Set headers + for key, value := range route.Headers { + w.Header().Set(key, value) + } + + // Handle dynamic project_uid replacement + responseBody := string(route.Response.Body) + if strings.Contains(responseBody, "{{project_uid}}") { + projectUID := r.Header.Get("project_uid") + responseBody = strings.ReplaceAll(responseBody, "{{project_uid}}", projectUID) + } + + w.WriteHeader(route.Response.Status) + _, err := w.Write([]byte(responseBody)) + if err != nil { + return + } + } +} + +func checkAuth(auth *AuthConfig, r *http.Request) bool { + switch auth.Type { + case "apikey": + return r.Header.Get(auth.Header) == auth.Key + case "basic": + authHeader := r.Header.Get("Authorization") + if strings.HasPrefix(authHeader, "Basic ") { + payload, err := base64.StdEncoding.DecodeString(authHeader[len("Basic "):]) + if err == nil { + pair := strings.SplitN(string(payload), ":", 2) + if len(pair) == 2 && pair[0] == auth.Username && pair[1] == auth.Password { + return true + } + } + } + } + return false +} diff --git a/tests/mockApiServer/routes.json b/tests/mockApiServer/routes.json new file mode 100644 index 00000000..21499319 --- /dev/null +++ b/tests/mockApiServer/routes.json @@ -0,0 +1,20 @@ +[ + { + "path": "/v1/projects", + "method": "POST", + "headers": { + "Content-Type": "application/json" + }, + "auth": { + "type": "apikey", + "header": "ApiKey", + "key": "12345" + }, + "response": { + "status": 201, + "body": { + "UID": "test_project_uid" + } + } + } +] \ No newline at end of file diff --git a/tests/mockApiServer/start_mock_api_server.sh b/tests/mockApiServer/start_mock_api_server.sh new file mode 100755 index 00000000..2cc26002 --- /dev/null +++ b/tests/mockApiServer/start_mock_api_server.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +export MOCK_SERVER_PATH="$TF_SRC/tests/mockApiServer" +# Navigate to the mock API server directory +cd $MOCK_SERVER_PATH || exit + +# Generate the private key +openssl genpkey -algorithm RSA -out server.key -pkeyopt rsa_keygen_bits:2048 + +# Generate the self-signed certificate with default input +openssl req -new -x509 -key server.key -out server.crt -days 365 -subj "/C=US/ST=CA/L=City/O=Organization/OU=Department/CN=localhost" + +# Build the Go project +go build -o MockAPIServer main.go + +# Run the server in the background and redirect output to server.log +nohup ./MockAPIServer > mock_api_server.log 2>&1 & \ No newline at end of file diff --git a/tests/mockApiServer/stop_mock_api_server.sh b/tests/mockApiServer/stop_mock_api_server.sh new file mode 100755 index 00000000..d96650d7 --- /dev/null +++ b/tests/mockApiServer/stop_mock_api_server.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Find the process ID of the running mockApiserver +PID=$(pgrep -f MockAPIServer) + +if [ -z "$PID" ]; then + echo "MockAPIServer is not running." +else + # Kill the process + kill $PID + echo "MockAPIServer (PID: $PID) has been stopped." +fi \ No newline at end of file From 661be4062e216b2e441372abc105b0a9daf1a68d Mon Sep 17 00:00:00 2001 From: Sivaanand Murugesan Date: Fri, 23 Aug 2024 10:07:30 +0530 Subject: [PATCH 2/8] draft --- tests/mockApiServer/main.go | 184 ++++++++++++++++---------------- tests/mockApiServer/routes.json | 20 ---- 2 files changed, 94 insertions(+), 110 deletions(-) delete mode 100644 tests/mockApiServer/routes.json diff --git a/tests/mockApiServer/main.go b/tests/mockApiServer/main.go index a1dff6b6..f3caeaf9 100644 --- a/tests/mockApiServer/main.go +++ b/tests/mockApiServer/main.go @@ -1,114 +1,118 @@ -package main +package mockApiServer import ( - "encoding/base64" "encoding/json" - "io/ioutil" + "github.com/gorilla/mux" "log" "net/http" - "strings" ) -type Route struct { - Path string `json:"path"` - Method string `json:"method"` - Headers map[string]string `json:"headers,omitempty"` - Auth *AuthConfig `json:"auth,omitempty"` - Response ResponseConfig `json:"response"` -} - -type AuthConfig struct { - Type string `json:"type"` - Header string `json:"header,omitempty"` - Key string `json:"key,omitempty"` - Username string `json:"username,omitempty"` - Password string `json:"password,omitempty"` +// ResponseData defines the structure of mock responses +type ResponseData struct { + StatusCode int + Payload interface{} } -type ResponseConfig struct { - Status int `json:"status"` - Body json.RawMessage `json:"body"` +// Route defines a mock route with method, path, and response +type Route struct { + Method string + Path string + Response ResponseData } -func main() { - // Load routes from JSON file - routes := loadRoutes("./routes.json") - - // Setup handlers based on routes - for _, route := range routes { - http.HandleFunc(route.Path, createHandler(route)) - } +// API key for authentication +const apiKey = "12345" - // Start server - log.Println("Starting server on :8080") - err := http.ListenAndServeTLS(":8080", "./server.crt", "./server.key", nil) - if err != nil { - log.Fatal("ListenAndServeTLS: ", err) - } - log.Fatal(http.ListenAndServe(":8080", nil)) +// Define userRoutes as a separate slice +var userRoutes = []Route{ + { + Method: "GET", + Path: "/api/v1/users", + Response: ResponseData{ + StatusCode: http.StatusOK, + Payload: []map[string]interface{}{ + {"id": 1, "name": "John Doe"}, + {"id": 2, "name": "Jane Doe"}, + }, + }, + }, + { + Method: "POST", + Path: "/api/v1/users", + Response: ResponseData{ + StatusCode: http.StatusCreated, + Payload: map[string]interface{}{ + "id": 3, + "name": "New User", + }, + }, + }, + { + Method: "GET", + Path: "/api/v1/users/{userId}", + Response: ResponseData{ + StatusCode: http.StatusOK, + Payload: map[string]interface{}{ + "id": 1, + "name": "John Doe", + }, + }, + }, + { + Method: "PUT", + Path: "/api/v1/users/{userId}", + Response: ResponseData{ + StatusCode: http.StatusOK, + Payload: map[string]interface{}{ + "id": 1, + "name": "Updated User", + }, + }, + }, + { + Method: "DELETE", + Path: "/api/v1/users/{userId}", + Response: ResponseData{ + StatusCode: http.StatusNoContent, + Payload: nil, + }, + }, } -func loadRoutes(file string) []Route { - data, err := ioutil.ReadFile(file) - if err != nil { - log.Fatalf("Error reading routes file: %v", err) - } - - var routes []Route - if err := json.Unmarshal(data, &routes); err != nil { - log.Fatalf("Error unmarshaling routes: %v", err) - } - return routes -} +// Aggregate all routes into a single slice +var allRoutes = append(userRoutes) -func createHandler(route Route) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - if r.Method != route.Method { - http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) - return - } - - // Check authentication if required - if route.Auth != nil && !checkAuth(route.Auth, r) { - http.Error(w, "Unauthorized", http.StatusUnauthorized) +// Middleware to check for the API key in the header +func apiKeyAuthMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Header.Get("ApiKey") != apiKey { + http.Error(w, "Forbidden", http.StatusForbidden) return } + next.ServeHTTP(w, r) + }) +} - // Set headers - for key, value := range route.Headers { - w.Header().Set(key, value) - } +func main() { + router := mux.NewRouter() - // Handle dynamic project_uid replacement - responseBody := string(route.Response.Body) - if strings.Contains(responseBody, "{{project_uid}}") { - projectUID := r.Header.Get("project_uid") - responseBody = strings.ReplaceAll(responseBody, "{{project_uid}}", projectUID) - } + // Apply API key middleware to all routes + router.Use(apiKeyAuthMiddleware) - w.WriteHeader(route.Response.Status) - _, err := w.Write([]byte(responseBody)) - if err != nil { - return - } - } -} + // Register all routes + for _, route := range allRoutes { + route := route // capture the range variable -func checkAuth(auth *AuthConfig, r *http.Request) bool { - switch auth.Type { - case "apikey": - return r.Header.Get(auth.Header) == auth.Key - case "basic": - authHeader := r.Header.Get("Authorization") - if strings.HasPrefix(authHeader, "Basic ") { - payload, err := base64.StdEncoding.DecodeString(authHeader[len("Basic "):]) - if err == nil { - pair := strings.SplitN(string(payload), ":", 2) - if len(pair) == 2 && pair[0] == auth.Username && pair[1] == auth.Password { - return true - } + router.HandleFunc(route.Path, func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(route.Response.StatusCode) + if route.Response.Payload != nil { + json.NewEncoder(w).Encode(route.Response.Payload) } - } + }).Methods(route.Method) } - return false + + // Start the server + log.Println("Starting server on :8080...") + log.Fatal(http.ListenAndServe(":8080", router)) } diff --git a/tests/mockApiServer/routes.json b/tests/mockApiServer/routes.json deleted file mode 100644 index 21499319..00000000 --- a/tests/mockApiServer/routes.json +++ /dev/null @@ -1,20 +0,0 @@ -[ - { - "path": "/v1/projects", - "method": "POST", - "headers": { - "Content-Type": "application/json" - }, - "auth": { - "type": "apikey", - "header": "ApiKey", - "key": "12345" - }, - "response": { - "status": 201, - "body": { - "UID": "test_project_uid" - } - } - } -] \ No newline at end of file From d3b4da5cf792ece333d74019cfdbf8e234059034 Mon Sep 17 00:00:00 2001 From: Sivaanand Murugesan Date: Mon, 26 Aug 2024 10:39:35 +0530 Subject: [PATCH 3/8] draft 2 --- spectrocloud/common_test.go | 21 ++++-- spectrocloud/resource_project_test.go | 12 +++- tests/mockApiServer/main.go | 75 ++++---------------- tests/mockApiServer/start_mock_api_server.sh | 4 +- 4 files changed, 42 insertions(+), 70 deletions(-) diff --git a/spectrocloud/common_test.go b/spectrocloud/common_test.go index 2c0baa47..80b4c4a8 100644 --- a/spectrocloud/common_test.go +++ b/spectrocloud/common_test.go @@ -7,6 +7,7 @@ import ( "github.com/spectrocloud/palette-sdk-go/client" "os" "os/exec" + "path/filepath" "testing" ) @@ -29,11 +30,18 @@ const ( // var baseConfig Cred var unitTestMockAPIClient interface{} -var basePath = os.Getenv("TF_SRC") -var startMockApiServerScript = basePath + "/tests/mockApiServer/start_mock_api_server.sh" -var stopMockApiServerScript = basePath + "/tests/mockApiServer/stop_mock_api_server.sh" + +var basePath = "" +var startMockApiServerScript = "" +var stopMockApiServerScript = "" func TestMain(m *testing.M) { + cwd, _ := os.Getwd() + _ = os.Setenv("TF_SRC", filepath.Dir(cwd)) + basePath = os.Getenv("TF_SRC") + startMockApiServerScript = basePath + "/tests/mockApiServer/start_mock_api_server.sh" + stopMockApiServerScript = basePath + "/tests/mockApiServer/stop_mock_api_server.sh" + setup() code := m.Run() teardown() @@ -52,15 +60,16 @@ func unitTestProviderConfigure(ctx context.Context) (interface{}, diag.Diagnosti client.WithPaletteURI(host), client.WithAPIKey(apiKey), client.WithRetries(retryAttempts), - client.WithInsecureSkipVerify(true)) + client.WithInsecureSkipVerify(true), + client.WithRetries(1)) + //// comment to trace flag //client.WithTransportDebug()(c) + uid := projectUID ProviderInitProjectUid = uid client.WithScopeProject(uid)(c) - return c, diags - } func setup() { diff --git a/spectrocloud/resource_project_test.go b/spectrocloud/resource_project_test.go index 4dc96d5c..154cb245 100644 --- a/spectrocloud/resource_project_test.go +++ b/spectrocloud/resource_project_test.go @@ -84,7 +84,7 @@ func TestToProject(t *testing.T) { } } -func TestCreateFunc(t *testing.T) { +func TestCreateProjectFunc(t *testing.T) { d := resourceProject().TestResourceData() var diags diag.Diagnostics err := d.Set("name", "dev") @@ -95,3 +95,13 @@ func TestCreateFunc(t *testing.T) { diags = resourceProjectCreate(ctx, d, unitTestMockAPIClient) assert.Equal(t, 0, len(diags)) } + +func TestReadProjectFunc(t *testing.T) { + d := resourceProject().TestResourceData() + var diags diag.Diagnostics + d.SetId("test123") + + var ctx context.Context + diags = resourceProjectRead(ctx, d, unitTestMockAPIClient) + assert.Equal(t, 0, len(diags)) +} diff --git a/tests/mockApiServer/main.go b/tests/mockApiServer/main.go index f3caeaf9..6fd815b5 100644 --- a/tests/mockApiServer/main.go +++ b/tests/mockApiServer/main.go @@ -1,9 +1,10 @@ -package mockApiServer +package main import ( "encoding/json" "github.com/gorilla/mux" "log" + "mockApiServer/routes" "net/http" ) @@ -23,72 +24,20 @@ type Route struct { // API key for authentication const apiKey = "12345" -// Define userRoutes as a separate slice -var userRoutes = []Route{ - { - Method: "GET", - Path: "/api/v1/users", - Response: ResponseData{ - StatusCode: http.StatusOK, - Payload: []map[string]interface{}{ - {"id": 1, "name": "John Doe"}, - {"id": 2, "name": "Jane Doe"}, - }, - }, - }, - { - Method: "POST", - Path: "/api/v1/users", - Response: ResponseData{ - StatusCode: http.StatusCreated, - Payload: map[string]interface{}{ - "id": 3, - "name": "New User", - }, - }, - }, - { - Method: "GET", - Path: "/api/v1/users/{userId}", - Response: ResponseData{ - StatusCode: http.StatusOK, - Payload: map[string]interface{}{ - "id": 1, - "name": "John Doe", - }, - }, - }, - { - Method: "PUT", - Path: "/api/v1/users/{userId}", - Response: ResponseData{ - StatusCode: http.StatusOK, - Payload: map[string]interface{}{ - "id": 1, - "name": "Updated User", - }, - }, - }, - { - Method: "DELETE", - Path: "/api/v1/users/{userId}", - Response: ResponseData{ - StatusCode: http.StatusNoContent, - Payload: nil, - }, - }, -} - // Aggregate all routes into a single slice -var allRoutes = append(userRoutes) +var allRoutes []routes.Route -// Middleware to check for the API key in the header +// Middleware to check for the API key and log the Project-ID if present func apiKeyAuthMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Header.Get("ApiKey") != apiKey { http.Error(w, "Forbidden", http.StatusForbidden) return } + // Log the Project-ID if it is present + if projectID := r.Header.Get("Project-ID"); projectID != "" { + log.Printf("Project-ID: %s", projectID) + } next.ServeHTTP(w, r) }) } @@ -98,7 +47,7 @@ func main() { // Apply API key middleware to all routes router.Use(apiKeyAuthMiddleware) - + setAllRoutes() // Register all routes for _, route := range allRoutes { route := route // capture the range variable @@ -114,5 +63,9 @@ func main() { // Start the server log.Println("Starting server on :8080...") - log.Fatal(http.ListenAndServe(":8080", router)) + log.Fatal(http.ListenAndServeTLS(":8080", "mock_server.crt", "mock_server.key", router)) +} + +func setAllRoutes() { + allRoutes = append(allRoutes, routes.ProjectRoutes()...) } diff --git a/tests/mockApiServer/start_mock_api_server.sh b/tests/mockApiServer/start_mock_api_server.sh index 2cc26002..723c1167 100755 --- a/tests/mockApiServer/start_mock_api_server.sh +++ b/tests/mockApiServer/start_mock_api_server.sh @@ -5,10 +5,10 @@ export MOCK_SERVER_PATH="$TF_SRC/tests/mockApiServer" cd $MOCK_SERVER_PATH || exit # Generate the private key -openssl genpkey -algorithm RSA -out server.key -pkeyopt rsa_keygen_bits:2048 +openssl genpkey -algorithm RSA -out mock_server.key -pkeyopt rsa_keygen_bits:2048 # Generate the self-signed certificate with default input -openssl req -new -x509 -key server.key -out server.crt -days 365 -subj "/C=US/ST=CA/L=City/O=Organization/OU=Department/CN=localhost" +openssl req -new -x509 -key mock_server.key -out mock_server.crt -days 365 -subj "/C=US/ST=CA/L=City/O=Organization/OU=Department/CN=localhost" # Build the Go project go build -o MockAPIServer main.go From 5708e974a9a95bbe2c63170b81c2523dd231d103 Mon Sep 17 00:00:00 2001 From: Sivaanand Murugesan Date: Mon, 26 Aug 2024 17:17:26 +0530 Subject: [PATCH 4/8] completed project unitest with mock --- spectrocloud/common_test.go | 105 +++++++++++++++++-- spectrocloud/resource_project_test.go | 85 ++++++++++++++- tests/mockApiServer/main.go | 81 +++++++++----- tests/mockApiServer/start_mock_api_server.sh | 1 + tests/mockApiServer/stop_mock_api_server.sh | 1 + 5 files changed, 241 insertions(+), 32 deletions(-) diff --git a/spectrocloud/common_test.go b/spectrocloud/common_test.go index 80b4c4a8..7cbf8912 100644 --- a/spectrocloud/common_test.go +++ b/spectrocloud/common_test.go @@ -2,13 +2,17 @@ package spectrocloud import ( "context" + "crypto/tls" + "errors" "fmt" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/spectrocloud/palette-sdk-go/client" + "net/http" "os" "os/exec" "path/filepath" "testing" + "time" ) //type Cred struct { @@ -20,6 +24,7 @@ import ( //} const ( + negativeHost = "127.0.0.1:8888" host = "127.0.0.1:8080" trace = false retryAttempts = 10 @@ -30,6 +35,7 @@ const ( // var baseConfig Cred var unitTestMockAPIClient interface{} +var unitTestMockAPINegativeClient interface{} var basePath = "" var startMockApiServerScript = "" @@ -42,7 +48,11 @@ func TestMain(m *testing.M) { startMockApiServerScript = basePath + "/tests/mockApiServer/start_mock_api_server.sh" stopMockApiServerScript = basePath + "/tests/mockApiServer/stop_mock_api_server.sh" - setup() + err := setup() + if err != nil { + fmt.Printf("Error during setup: %v\n", err) + os.Exit(1) + } code := m.Run() teardown() os.Exit(code) @@ -72,27 +82,108 @@ func unitTestProviderConfigure(ctx context.Context) (interface{}, diag.Diagnosti return c, diags } -func setup() { +func unitTestNegativeCaseProviderConfigure(ctx context.Context) (interface{}, diag.Diagnostics) { + apiKey := apiKey + retryAttempts := retryAttempts + + // Warning or errors can be collected in a slice type + var diags diag.Diagnostics + + c := client.New( + client.WithPaletteURI(negativeHost), + client.WithAPIKey(apiKey), + client.WithRetries(retryAttempts), + client.WithInsecureSkipVerify(true), + client.WithRetries(1)) + + //// comment to trace flag + //client.WithTransportDebug()(c) + + uid := projectUID + ProviderInitProjectUid = uid + client.WithScopeProject(uid)(c) + return c, diags +} + +func checkMockServerHealth() error { + maxRetries := 5 + delay := 2 * time.Second + + // Skip TLS verification (use with caution; not recommended for production) + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + c := &http.Client{Transport: tr} + + for i := 0; i < maxRetries; i++ { + // Create a new HTTP request + req, err := http.NewRequest("GET", "https://127.0.0.1:8080/v1/health", nil) + if err != nil { + return err + } + + // Add the API key as a header + req.Header.Set("ApiKey", "12345") + + // Send the request + resp, err := c.Do(req) + if err == nil && resp.StatusCode == http.StatusOK { + // Server is up and running + err := resp.Body.Close() + if err != nil { + return err + } + return nil + } + + if resp != nil { + err := resp.Body.Close() + if err != nil { + return err + } + } + + // Wait before retrying + time.Sleep(delay) + } + + return errors.New("server is not responding after multiple attempts") +} + +func setup() error { fmt.Printf("\033[1;36m%s\033[0m", "> Starting Mock API Server \n") var ctx context.Context cmd := exec.Command("sh", startMockApiServerScript) output, err := cmd.CombinedOutput() + err = checkMockServerHealth() if err != nil { fmt.Printf("Failed to run start api server script: %s\nError: %s", output, err) + return err } + fmt.Printf("\033[1;36m%s\033[0m", "> Started Mock Api Server at https://127.0.0.1:8080 \n") unitTestMockAPIClient, _ = unitTestProviderConfigure(ctx) - + unitTestMockAPINegativeClient, _ = unitTestNegativeCaseProviderConfigure(ctx) fmt.Printf("\033[1;36m%s\033[0m", "> Setup completed \n") + return nil } func teardown() { cmd := exec.Command("bash", stopMockApiServerScript) - output, err := cmd.CombinedOutput() - if err != nil { - fmt.Printf("Failed to run stop api server script: %s\nError: %s", output, err) - } + _, _ = cmd.CombinedOutput() fmt.Printf("\033[1;36m%s\033[0m", "> Stopped Mock Api Server \n") fmt.Printf("\033[1;36m%s\033[0m", "> Teardown completed \n") + err := deleteBuild() + if err != nil { + fmt.Printf("Test Clean up is incomplete: %v\n", err) + } +} + +func deleteBuild() error { + err := os.Remove(basePath + "/tests/mockApiServer/MockAPIServer") + if err != nil { + return err + } + return nil } diff --git a/spectrocloud/resource_project_test.go b/spectrocloud/resource_project_test.go index 154cb245..1e4b28a4 100644 --- a/spectrocloud/resource_project_test.go +++ b/spectrocloud/resource_project_test.go @@ -10,6 +10,16 @@ import ( "github.com/stretchr/testify/assert" ) +func prepareBaseProjectSchema() *schema.ResourceData { + d := resourceProject().TestResourceData() + d.SetId("test123") + err := d.Set("name", "Default") + if err != nil { + return nil + } + return d +} + // TestToProject tests the toProject function func TestToProject(t *testing.T) { tests := []struct { @@ -85,7 +95,7 @@ func TestToProject(t *testing.T) { } func TestCreateProjectFunc(t *testing.T) { - d := resourceProject().TestResourceData() + d := prepareBaseProjectSchema() var diags diag.Diagnostics err := d.Set("name", "dev") if err != nil { @@ -105,3 +115,76 @@ func TestReadProjectFunc(t *testing.T) { diags = resourceProjectRead(ctx, d, unitTestMockAPIClient) assert.Equal(t, 0, len(diags)) } + +func TestResourceProjectUpdate(t *testing.T) { + // Prepare the schema data for the test. + d := prepareBaseProjectSchema() + // Call the function you want to test. + ctx := context.Background() + diags := resourceProjectUpdate(ctx, d, unitTestMockAPIClient) + // Assert that no diagnostics were returned (i.e., no errors). + assert.Empty(t, diags) +} + +func TestResourceProjectDelete(t *testing.T) { + // Prepare the schema data for the test. + d := prepareBaseProjectSchema() + // Call the function you want to test. + ctx := context.Background() + diags := resourceProjectDelete(ctx, d, unitTestMockAPIClient) + // Assert that no diagnostics were returned (i.e., no errors). + assert.Empty(t, diags) +} + +// Negative case's + +func TestCreateProjectNegativeFunc(t *testing.T) { + d := prepareBaseProjectSchema() + var diags diag.Diagnostics + err := d.Set("name", "dev") + if err != nil { + return + } + var ctx context.Context + diags = resourceProjectCreate(ctx, d, unitTestMockAPINegativeClient) + if assert.NotEmpty(t, diags, "Expected diags to contain at least one element") { + assert.Contains(t, diags[0].Summary, "Project already exist", "The first diagnostic message does not contain the expected error message") + } +} + +func TestReadProjectNegativeFunc(t *testing.T) { + d := resourceProject().TestResourceData() + var diags diag.Diagnostics + d.SetId("test123") + + var ctx context.Context + diags = resourceProjectRead(ctx, d, unitTestMockAPINegativeClient) + if assert.NotEmpty(t, diags, "Expected diags to contain at least one element") { + assert.Contains(t, diags[0].Summary, "Project not found", "The first diagnostic message does not contain the expected error message") + } +} + +func TestUpdateProjectNegativeFunc(t *testing.T) { + d := prepareBaseProjectSchema() + var diags diag.Diagnostics + err := d.Set("name", "dev") + if err != nil { + return + } + var ctx context.Context + diags = resourceProjectUpdate(ctx, d, unitTestMockAPINegativeClient) + if assert.NotEmpty(t, diags, "Expected diags to contain at least one element") { + assert.Contains(t, diags[0].Summary, "Operation not allowed", "The first diagnostic message does not contain the expected error message") + } +} + +func TestResourceProjectInvalidDelete(t *testing.T) { + // Prepare the schema data for the test. + d := prepareBaseProjectSchema() + ctx := context.Background() + // Call the function you want to test. + diags := resourceProjectDelete(ctx, d, unitTestMockAPINegativeClient) + if assert.NotEmpty(t, diags, "Expected diags to contain at least one element") { + assert.Contains(t, diags[0].Summary, "Project not found", "The first diagnostic message does not contain the expected error message") + } +} diff --git a/tests/mockApiServer/main.go b/tests/mockApiServer/main.go index 6fd815b5..70f3df20 100644 --- a/tests/mockApiServer/main.go +++ b/tests/mockApiServer/main.go @@ -8,24 +8,25 @@ import ( "net/http" ) -// ResponseData defines the structure of mock responses -type ResponseData struct { - StatusCode int - Payload interface{} -} - -// Route defines a mock route with method, path, and response -type Route struct { - Method string - Path string - Response ResponseData -} +//// ResponseData defines the structure of mock responses +//type ResponseData struct { +// StatusCode int +// Payload interface{} +//} +// +//// Route defines a mock route with method, path, and response +//type Route struct { +// Method string +// Path string +// Response ResponseData +//} // API key for authentication const apiKey = "12345" -// Aggregate all routes into a single slice -var allRoutes []routes.Route +// Aggregate all routes into slices for different servers +var allRoutesPositive []routes.Route +var allRoutesNegative []routes.Route // Middleware to check for the API key and log the Project-ID if present func apiKeyAuthMiddleware(next http.Handler) http.Handler { @@ -43,29 +44,61 @@ func apiKeyAuthMiddleware(next http.Handler) http.Handler { } func main() { - router := mux.NewRouter() + // Create routers for different ports + router8080 := mux.NewRouter() + router8888 := mux.NewRouter() + + // Set up routes for port 8080 + setupRoutes(router8080, allRoutesPositive) + + // Set up routes for port 8888 + setupRoutes(router8888, allRoutesNegative) + + // Start servers on different ports + go func() { + log.Println("Starting server on :8080...") + if err := http.ListenAndServeTLS(":8080", "mock_server.crt", "mock_server.key", router8080); err != nil { + log.Fatalf("Server failed to start on port 8080: %v", err) + } + }() + log.Println("Starting server on :8888...") + //if err := http.ListenAndServe(":8888", router8888); err != nil { + // log.Fatalf("Server failed to start on port 8888: %v", err) + //} + if err := http.ListenAndServeTLS(":8888", "mock_server.crt", "mock_server.key", router8888); err != nil { + log.Fatalf("Server failed to start on port 8080: %v", err) + } +} + +// setupRoutes configures the given router with the provided routes +func setupRoutes(router *mux.Router, routes []routes.Route) { // Apply API key middleware to all routes router.Use(apiKeyAuthMiddleware) - setAllRoutes() + // Register all routes - for _, route := range allRoutes { + for _, route := range routes { route := route // capture the range variable router.HandleFunc(route.Path, func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(route.Response.StatusCode) if route.Response.Payload != nil { - json.NewEncoder(w).Encode(route.Response.Payload) + err := json.NewEncoder(w).Encode(route.Response.Payload) + if err != nil { + return + } } }).Methods(route.Method) } - - // Start the server - log.Println("Starting server on :8080...") - log.Fatal(http.ListenAndServeTLS(":8080", "mock_server.crt", "mock_server.key", router)) } -func setAllRoutes() { - allRoutes = append(allRoutes, routes.ProjectRoutes()...) +func init() { + // Initialize routes for port 8080 + allRoutesPositive = append(allRoutesPositive, routes.ProjectRoutes()...) + allRoutesPositive = append(allRoutesPositive, routes.CommonProjectRoutes()...) + + // Initialize routes for port 8888 + allRoutesNegative = append(allRoutesNegative, routes.ProjectNegativeRoutes()...) + allRoutesNegative = append(allRoutesNegative, routes.CommonProjectRoutes()...) } diff --git a/tests/mockApiServer/start_mock_api_server.sh b/tests/mockApiServer/start_mock_api_server.sh index 723c1167..528a4ec0 100755 --- a/tests/mockApiServer/start_mock_api_server.sh +++ b/tests/mockApiServer/start_mock_api_server.sh @@ -1,6 +1,7 @@ #!/bin/bash export MOCK_SERVER_PATH="$TF_SRC/tests/mockApiServer" + # Navigate to the mock API server directory cd $MOCK_SERVER_PATH || exit diff --git a/tests/mockApiServer/stop_mock_api_server.sh b/tests/mockApiServer/stop_mock_api_server.sh index d96650d7..4d3978e4 100755 --- a/tests/mockApiServer/stop_mock_api_server.sh +++ b/tests/mockApiServer/stop_mock_api_server.sh @@ -8,5 +8,6 @@ if [ -z "$PID" ]; then else # Kill the process kill $PID +# [ -f ./MockAPIServer ] && rm -f ./MockAPIServer echo "MockAPIServer (PID: $PID) has been stopped." fi \ No newline at end of file From 6bf4300e78af1750d86cef991df2b439dc32cb8a Mon Sep 17 00:00:00 2001 From: Sivaanand Murugesan Date: Mon, 26 Aug 2024 17:32:28 +0530 Subject: [PATCH 5/8] refreshed so mod --- go.mod | 1 + go.sum | 1 + 2 files changed, 2 insertions(+) diff --git a/go.mod b/go.mod index 8a1ef173..c3766d77 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.22.5 require ( github.com/go-openapi/strfmt v0.23.0 github.com/google/go-cmp v0.6.0 + github.com/gorilla/mux v1.8.0 github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 github.com/hashicorp/terraform-plugin-docs v0.16.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.30.0 diff --git a/go.sum b/go.sum index 977c1f3c..56836701 100644 --- a/go.sum +++ b/go.sum @@ -319,6 +319,7 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= From 51d2cf521bf181310324204e98bcdae40f05e6b1 Mon Sep 17 00:00:00 2001 From: Sivaanand Murugesan Date: Tue, 27 Aug 2024 13:07:45 +0530 Subject: [PATCH 6/8] mockserver fix --- tests/mockApiServer/main.go | 104 ------------------- tests/mockApiServer/start_mock_api_server.sh | 2 +- 2 files changed, 1 insertion(+), 105 deletions(-) delete mode 100644 tests/mockApiServer/main.go diff --git a/tests/mockApiServer/main.go b/tests/mockApiServer/main.go deleted file mode 100644 index 70f3df20..00000000 --- a/tests/mockApiServer/main.go +++ /dev/null @@ -1,104 +0,0 @@ -package main - -import ( - "encoding/json" - "github.com/gorilla/mux" - "log" - "mockApiServer/routes" - "net/http" -) - -//// ResponseData defines the structure of mock responses -//type ResponseData struct { -// StatusCode int -// Payload interface{} -//} -// -//// Route defines a mock route with method, path, and response -//type Route struct { -// Method string -// Path string -// Response ResponseData -//} - -// API key for authentication -const apiKey = "12345" - -// Aggregate all routes into slices for different servers -var allRoutesPositive []routes.Route -var allRoutesNegative []routes.Route - -// Middleware to check for the API key and log the Project-ID if present -func apiKeyAuthMiddleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Header.Get("ApiKey") != apiKey { - http.Error(w, "Forbidden", http.StatusForbidden) - return - } - // Log the Project-ID if it is present - if projectID := r.Header.Get("Project-ID"); projectID != "" { - log.Printf("Project-ID: %s", projectID) - } - next.ServeHTTP(w, r) - }) -} - -func main() { - // Create routers for different ports - router8080 := mux.NewRouter() - router8888 := mux.NewRouter() - - // Set up routes for port 8080 - setupRoutes(router8080, allRoutesPositive) - - // Set up routes for port 8888 - setupRoutes(router8888, allRoutesNegative) - - // Start servers on different ports - go func() { - log.Println("Starting server on :8080...") - if err := http.ListenAndServeTLS(":8080", "mock_server.crt", "mock_server.key", router8080); err != nil { - log.Fatalf("Server failed to start on port 8080: %v", err) - } - }() - - log.Println("Starting server on :8888...") - //if err := http.ListenAndServe(":8888", router8888); err != nil { - // log.Fatalf("Server failed to start on port 8888: %v", err) - //} - if err := http.ListenAndServeTLS(":8888", "mock_server.crt", "mock_server.key", router8888); err != nil { - log.Fatalf("Server failed to start on port 8080: %v", err) - } -} - -// setupRoutes configures the given router with the provided routes -func setupRoutes(router *mux.Router, routes []routes.Route) { - // Apply API key middleware to all routes - router.Use(apiKeyAuthMiddleware) - - // Register all routes - for _, route := range routes { - route := route // capture the range variable - - router.HandleFunc(route.Path, func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(route.Response.StatusCode) - if route.Response.Payload != nil { - err := json.NewEncoder(w).Encode(route.Response.Payload) - if err != nil { - return - } - } - }).Methods(route.Method) - } -} - -func init() { - // Initialize routes for port 8080 - allRoutesPositive = append(allRoutesPositive, routes.ProjectRoutes()...) - allRoutesPositive = append(allRoutesPositive, routes.CommonProjectRoutes()...) - - // Initialize routes for port 8888 - allRoutesNegative = append(allRoutesNegative, routes.ProjectNegativeRoutes()...) - allRoutesNegative = append(allRoutesNegative, routes.CommonProjectRoutes()...) -} diff --git a/tests/mockApiServer/start_mock_api_server.sh b/tests/mockApiServer/start_mock_api_server.sh index 528a4ec0..c9ff2b23 100755 --- a/tests/mockApiServer/start_mock_api_server.sh +++ b/tests/mockApiServer/start_mock_api_server.sh @@ -12,7 +12,7 @@ openssl genpkey -algorithm RSA -out mock_server.key -pkeyopt rsa_keygen_bits:204 openssl req -new -x509 -key mock_server.key -out mock_server.crt -days 365 -subj "/C=US/ST=CA/L=City/O=Organization/OU=Department/CN=localhost" # Build the Go project -go build -o MockAPIServer main.go +go build -o MockAPIServer mockApiServer.go # Run the server in the background and redirect output to server.log nohup ./MockAPIServer > mock_api_server.log 2>&1 & \ No newline at end of file From 9cb4888738fae88fedcf71eefc587a92672239f9 Mon Sep 17 00:00:00 2001 From: Sivaanand Murugesan Date: Tue, 27 Aug 2024 13:14:21 +0530 Subject: [PATCH 7/8] Fixed mack api server --- .gitignore | 6 +- tests/mockApiServer/apiServerMock.go | 89 +++++++++++++++ tests/mockApiServer/routes/common.go | 54 ++++++++++ tests/mockApiServer/routes/mockProjects.go | 108 +++++++++++++++++++ tests/mockApiServer/start_mock_api_server.sh | 4 +- 5 files changed, 256 insertions(+), 5 deletions(-) create mode 100644 tests/mockApiServer/apiServerMock.go create mode 100644 tests/mockApiServer/routes/common.go create mode 100644 tests/mockApiServer/routes/mockProjects.go diff --git a/.gitignore b/.gitignore index a5256285..64cc9d45 100644 --- a/.gitignore +++ b/.gitignore @@ -21,7 +21,7 @@ dist providers # ignore for mock api server -server.crt -server.key -MockAPIServer +mock_server.crt +mock_server.key +MockBuild mock_api_server.log \ No newline at end of file diff --git a/tests/mockApiServer/apiServerMock.go b/tests/mockApiServer/apiServerMock.go new file mode 100644 index 00000000..145905f1 --- /dev/null +++ b/tests/mockApiServer/apiServerMock.go @@ -0,0 +1,89 @@ +package main + +import ( + "encoding/json" + "github.com/gorilla/mux" + "github.com/spectrocloud/terraform-provider-spectrocloud/tests/mockApiServer/routes" + "log" + "net/http" +) + +// API key for authentication +const apiKey = "12345" + +// Aggregate all routes into slices for different servers +var allRoutesPositive []routes.Route +var allRoutesNegative []routes.Route + +// Middleware to check for the API key and log the Project-ID if present +func apiKeyAuthMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Header.Get("ApiKey") != apiKey { + http.Error(w, "Forbidden", http.StatusForbidden) + return + } + // Log the Project-ID if it is present + if projectID := r.Header.Get("Project-ID"); projectID != "" { + log.Printf("Project-ID: %s", projectID) + } + next.ServeHTTP(w, r) + }) +} + +func main() { + // Create routers for different ports + router8080 := mux.NewRouter() + router8888 := mux.NewRouter() + + // Set up routes for port 8080 + setupRoutes(router8080, allRoutesPositive) + + // Set up routes for port 8888 + setupRoutes(router8888, allRoutesNegative) + + // Start servers on different ports + go func() { + log.Println("Starting server on :8080...") + if err := http.ListenAndServeTLS(":8080", "mock_server.crt", "mock_server.key", router8080); err != nil { + log.Fatalf("Server failed to start on port 8080: %v", err) + } + }() + + log.Println("Starting server on :8888...") + + if err := http.ListenAndServeTLS(":8888", "mock_server.crt", "mock_server.key", router8888); err != nil { + log.Fatalf("Server failed to start on port 8080: %v", err) + } +} + +// setupRoutes configures the given router with the provided routes +func setupRoutes(router *mux.Router, routes []routes.Route) { + // Apply API key middleware to all routes + router.Use(apiKeyAuthMiddleware) + + // Register all routes + for _, route := range routes { + route := route // capture the range variable + + router.HandleFunc(route.Path, func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(route.Response.StatusCode) + if route.Response.Payload != nil { + err := json.NewEncoder(w).Encode(route.Response.Payload) + if err != nil { + return + } + } + }).Methods(route.Method) + } +} + +func init() { + // Initialize routes for port 8080 + allRoutesPositive = append(allRoutesPositive, routes.ProjectRoutes()...) + allRoutesPositive = append(allRoutesPositive, routes.CommonProjectRoutes()...) + + // Initialize routes for port 8888 + allRoutesNegative = append(allRoutesNegative, routes.ProjectNegativeRoutes()...) + allRoutesNegative = append(allRoutesNegative, routes.CommonProjectRoutes()...) +} diff --git a/tests/mockApiServer/routes/common.go b/tests/mockApiServer/routes/common.go new file mode 100644 index 00000000..a824a568 --- /dev/null +++ b/tests/mockApiServer/routes/common.go @@ -0,0 +1,54 @@ +package routes + +import ( + "crypto/rand" + "encoding/hex" + "github.com/spectrocloud/palette-sdk-go/api/models" + "net/http" +) + +// ResponseData defines the structure of mock responses +type ResponseData struct { + StatusCode int + Payload interface{} +} + +// Route defines a mock route with method, path, and response +type Route struct { + Method string + Path string + Response ResponseData +} + +func generateRandomStringUID() string { + bytes := make([]byte, 24/2) + _, err := rand.Read(bytes) + if err != nil { + return "test" + } + return hex.EncodeToString(bytes) +} + +func CommonProjectRoutes() []Route { + return []Route{ + { + Method: "GET", + Path: "/v1/health", + Response: ResponseData{ + StatusCode: http.StatusOK, + Payload: map[string]interface{}{ + "healthy": true, + }, + }, + }, + } +} + +func getError(code string, msg string) models.V1Error { + return models.V1Error{ + Code: code, + Details: nil, + Message: msg, + Ref: "ref-" + generateRandomStringUID(), + } +} diff --git a/tests/mockApiServer/routes/mockProjects.go b/tests/mockApiServer/routes/mockProjects.go new file mode 100644 index 00000000..40ab5fdc --- /dev/null +++ b/tests/mockApiServer/routes/mockProjects.go @@ -0,0 +1,108 @@ +package routes + +import ( + "github.com/spectrocloud/palette-sdk-go/api/models" + "net/http" + "strconv" +) + +func getMockProjectPayload() models.V1Project { + return models.V1Project{ + Metadata: &models.V1ObjectMeta{ + Annotations: nil, + CreationTimestamp: models.V1Time{}, + DeletionTimestamp: models.V1Time{}, + Labels: map[string]string{ + "description": "default project", + }, + LastModifiedTimestamp: models.V1Time{}, + Name: "Default", + UID: generateRandomStringUID(), + }, + Spec: &models.V1ProjectSpec{ + Alerts: nil, + LogoURL: "", + Teams: nil, + Users: nil, + }, + Status: &models.V1ProjectStatus{ + CleanUpStatus: nil, + IsDisabled: false, + }, + } + +} + +func ProjectRoutes() []Route { + return []Route{ + { + Method: "POST", + Path: "/v1/projects", + Response: ResponseData{ + StatusCode: http.StatusCreated, + Payload: map[string]interface{}{"UID": generateRandomStringUID()}, + }, + }, + { + Method: "GET", + Path: "/v1/projects/{uid}", + Response: ResponseData{ + StatusCode: http.StatusOK, + Payload: getMockProjectPayload(), + }, + }, + { + Method: "PUT", + Path: "/v1/projects/{uid}", + Response: ResponseData{ + StatusCode: http.StatusNoContent, + Payload: map[string]interface{}{"UID": generateRandomStringUID()}, + }, + }, + { + Method: "DELETE", + Path: "/v1/projects/{uid}", + Response: ResponseData{ + StatusCode: http.StatusNoContent, + Payload: nil, + }, + }, + } +} + +func ProjectNegativeRoutes() []Route { + return []Route{ + { + Method: "POST", + Path: "/v1/projects", + Response: ResponseData{ + StatusCode: http.StatusConflict, + Payload: getError(strconv.Itoa(http.StatusConflict), "Project already exist"), + }, + }, + { + Method: "GET", + Path: "/v1/projects/{uid}", + Response: ResponseData{ + StatusCode: http.StatusNotFound, + Payload: getError(strconv.Itoa(http.StatusOK), "Project not found"), + }, + }, + { + Method: "PUT", + Path: "/v1/projects/{uid}", + Response: ResponseData{ + StatusCode: http.StatusMethodNotAllowed, + Payload: getError(strconv.Itoa(http.StatusNoContent), "Operation not allowed"), + }, + }, + { + Method: "DELETE", + Path: "/v1/projects/{uid}", + Response: ResponseData{ + StatusCode: http.StatusNotFound, + Payload: getError(strconv.Itoa(http.StatusOK), "Project not found"), + }, + }, + } +} diff --git a/tests/mockApiServer/start_mock_api_server.sh b/tests/mockApiServer/start_mock_api_server.sh index c9ff2b23..dc068d17 100755 --- a/tests/mockApiServer/start_mock_api_server.sh +++ b/tests/mockApiServer/start_mock_api_server.sh @@ -12,7 +12,7 @@ openssl genpkey -algorithm RSA -out mock_server.key -pkeyopt rsa_keygen_bits:204 openssl req -new -x509 -key mock_server.key -out mock_server.crt -days 365 -subj "/C=US/ST=CA/L=City/O=Organization/OU=Department/CN=localhost" # Build the Go project -go build -o MockAPIServer mockApiServer.go +go build -o MockBuild apiServerMock.go # Run the server in the background and redirect output to server.log -nohup ./MockAPIServer > mock_api_server.log 2>&1 & \ No newline at end of file +nohup ./MockBuild > mock_api_server.log 2>&1 & \ No newline at end of file From ea30cd334153d10234d9760b9fce737c323e3911 Mon Sep 17 00:00:00 2001 From: Sivaanand Murugesan Date: Tue, 27 Aug 2024 15:53:06 +0530 Subject: [PATCH 8/8] completed unitest for data source appliance --- spectrocloud/common_test.go | 2 +- spectrocloud/data_source_appliance_test.go | 55 ++++++ spectrocloud/data_source_appliances_test.go | 55 ++++++ tests/mockApiServer/apiServerMock.go | 2 + tests/mockApiServer/routes/mockAppliances.go | 185 +++++++++++++++++++ tests/mockApiServer/stop_mock_api_server.sh | 4 +- 6 files changed, 300 insertions(+), 3 deletions(-) create mode 100644 spectrocloud/data_source_appliance_test.go create mode 100644 spectrocloud/data_source_appliances_test.go create mode 100644 tests/mockApiServer/routes/mockAppliances.go diff --git a/spectrocloud/common_test.go b/spectrocloud/common_test.go index 7cbf8912..3a89f1f6 100644 --- a/spectrocloud/common_test.go +++ b/spectrocloud/common_test.go @@ -181,7 +181,7 @@ func teardown() { } func deleteBuild() error { - err := os.Remove(basePath + "/tests/mockApiServer/MockAPIServer") + err := os.Remove(basePath + "/tests/mockApiServer/MockBuild") if err != nil { return err } diff --git a/spectrocloud/data_source_appliance_test.go b/spectrocloud/data_source_appliance_test.go new file mode 100644 index 00000000..56553b5a --- /dev/null +++ b/spectrocloud/data_source_appliance_test.go @@ -0,0 +1,55 @@ +package spectrocloud + +import ( + "context" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/stretchr/testify/assert" + "testing" +) + +func prepareBaseDataSourceApplianceSchema() *schema.ResourceData { + d := dataSourceAppliance().TestResourceData() + d.SetId("test123") + err := d.Set("name", "test-edge-01") + if err != nil { + return nil + } + err = d.Set("tags", map[string]string{"test": "true"}) + if err != nil { + return nil + } + err = d.Set("status", "ready") + if err != nil { + return nil + } + err = d.Set("health", "healthy") + if err != nil { + return nil + } + err = d.Set("architecture", "amd") + if err != nil { + return nil + } + return d +} + +func TestDataSourceApplianceReadFunc(t *testing.T) { + d := prepareBaseDataSourceApplianceSchema() + var diags diag.Diagnostics + + var ctx context.Context + diags = dataSourceApplianceRead(ctx, d, unitTestMockAPIClient) + assert.Equal(t, 0, len(diags)) +} + +func TestDataSourceApplianceReadNegativeFunc(t *testing.T) { + d := prepareBaseDataSourceApplianceSchema() + var diags diag.Diagnostics + + var ctx context.Context + diags = dataSourceApplianceRead(ctx, d, unitTestMockAPINegativeClient) + if assert.NotEmpty(t, diags, "Expected diags to contain at least one element") { + assert.Contains(t, diags[0].Summary, "No edge host found", "The first diagnostic message does not contain the expected error message") + } +} diff --git a/spectrocloud/data_source_appliances_test.go b/spectrocloud/data_source_appliances_test.go new file mode 100644 index 00000000..351a8289 --- /dev/null +++ b/spectrocloud/data_source_appliances_test.go @@ -0,0 +1,55 @@ +package spectrocloud + +import ( + "context" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/stretchr/testify/assert" + "testing" +) + +func prepareBaseDataSourceAppliancesSchema() *schema.ResourceData { + d := dataSourceAppliances().TestResourceData() + d.SetId("test123") + err := d.Set("context", "project") + if err != nil { + return nil + } + err = d.Set("tags", map[string]string{"test": "true"}) + if err != nil { + return nil + } + err = d.Set("status", "ready") + if err != nil { + return nil + } + err = d.Set("health", "healthy") + if err != nil { + return nil + } + err = d.Set("architecture", "amd") + if err != nil { + return nil + } + return d +} + +func TestDataSourceAppliancesReadFunc(t *testing.T) { + d := prepareBaseDataSourceAppliancesSchema() + var diags diag.Diagnostics + + var ctx context.Context + diags = dataSourcesApplianceRead(ctx, d, unitTestMockAPIClient) + assert.Equal(t, 0, len(diags)) +} + +func TestDataSourceAppliancesReadNegativeFunc(t *testing.T) { + d := prepareBaseDataSourceAppliancesSchema() + var diags diag.Diagnostics + + var ctx context.Context + diags = dataSourcesApplianceRead(ctx, d, unitTestMockAPINegativeClient) + if assert.NotEmpty(t, diags, "Expected diags to contain at least one element") { + assert.Contains(t, diags[0].Summary, "No edge host found", "The first diagnostic message does not contain the expected error message") + } +} diff --git a/tests/mockApiServer/apiServerMock.go b/tests/mockApiServer/apiServerMock.go index 145905f1..bfb545f6 100644 --- a/tests/mockApiServer/apiServerMock.go +++ b/tests/mockApiServer/apiServerMock.go @@ -81,9 +81,11 @@ func setupRoutes(router *mux.Router, routes []routes.Route) { func init() { // Initialize routes for port 8080 allRoutesPositive = append(allRoutesPositive, routes.ProjectRoutes()...) + allRoutesPositive = append(allRoutesPositive, routes.AppliancesRoutes()...) allRoutesPositive = append(allRoutesPositive, routes.CommonProjectRoutes()...) // Initialize routes for port 8888 allRoutesNegative = append(allRoutesNegative, routes.ProjectNegativeRoutes()...) + allRoutesNegative = append(allRoutesNegative, routes.AppliancesNegativeRoutes()...) allRoutesNegative = append(allRoutesNegative, routes.CommonProjectRoutes()...) } diff --git a/tests/mockApiServer/routes/mockAppliances.go b/tests/mockApiServer/routes/mockAppliances.go new file mode 100644 index 00000000..6bedc902 --- /dev/null +++ b/tests/mockApiServer/routes/mockAppliances.go @@ -0,0 +1,185 @@ +package routes + +import ( + "github.com/spectrocloud/gomi/pkg/ptr" + "github.com/spectrocloud/palette-sdk-go/api/models" + "net/http" + "strconv" +) + +func getEdgeHostSearchSummary() models.V1EdgeHostsSearchSummary { + var items []*models.V1EdgeHostsMetadata + var profileSummary []*models.V1ProfileTemplateSummary + profileSummary = append(profileSummary, &models.V1ProfileTemplateSummary{ + CloudType: "aws", + Name: "test-profile-1", + Packs: []*models.V1PackRefSummary{{ + AddonType: "", + Annotations: nil, + DisplayName: "k8", + Layer: "infra", + LogoURL: "", + Name: "kubernetes_pack", + PackUID: generateRandomStringUID(), + Tag: "", + Type: "", + Version: "1.28.0", + }}, + Type: "cluster", + UID: generateRandomStringUID(), + Version: "1.0", + }) + items = append(items, &models.V1EdgeHostsMetadata{ + Metadata: &models.V1ObjectMeta{ + Annotations: nil, + CreationTimestamp: models.V1Time{}, + DeletionTimestamp: models.V1Time{}, + Labels: nil, + LastModifiedTimestamp: models.V1Time{}, + Name: "test-edge-01", + UID: generateRandomStringUID(), + }, + Spec: &models.V1EdgeHostsMetadataSpec{ + ClusterProfileTemplates: profileSummary, + Device: &models.V1DeviceSpec{ + ArchType: ptr.StringPtr("AMD"), + CPU: &models.V1CPU{ + Cores: 2, + }, + Disks: []*models.V1Disk{{ + Controller: "", + Partitions: nil, + Size: 50, + Vendor: "", + }}, + Gpus: []*models.V1GPUDeviceSpec{ + { + Addresses: map[string]string{ + "test": "121.0.0.1", + }, + Model: "xyz", + Vendor: "abc", + }, + }, + Memory: nil, + Nics: nil, + Os: nil, + }, + Host: &models.V1EdgeHostSpecHost{ + HostAddress: "192.168.1.100", + MacAddress: "2001:0db8:85a3:0000:0000:8a2e:0370:7334", + }, + ProjectMeta: nil, + Type: "", + }, + Status: &models.V1EdgeHostsMetadataStatus{ + Health: &models.V1EdgeHostHealth{ + AgentVersion: "", + Message: "", + State: "healthy", + }, + InUseClusters: nil, + State: "", + }, + }) + return models.V1EdgeHostsSearchSummary{ + Items: items, + Listmeta: &models.V1ListMetaData{ + Continue: "", + Count: 1, + Limit: 50, + Offset: 0, + }, + } +} + +func getEdgeHostPayload() models.V1EdgeHostDevice { + return models.V1EdgeHostDevice{ + Aclmeta: &models.V1ACLMeta{ + OwnerUID: generateRandomStringUID(), + ProjectUID: generateRandomStringUID(), + TenantUID: generateRandomStringUID(), + }, + Metadata: &models.V1ObjectMeta{ + Annotations: nil, + CreationTimestamp: models.V1Time{}, + DeletionTimestamp: models.V1Time{}, + Labels: map[string]string{"type": "test"}, + LastModifiedTimestamp: models.V1Time{}, + Name: "test-edge-01", + UID: generateRandomStringUID(), + }, + Spec: &models.V1EdgeHostDeviceSpec{ + CloudProperties: nil, + ClusterProfileTemplates: nil, + Device: &models.V1DeviceSpec{ + ArchType: ptr.StringPtr("amd64"), + CPU: nil, + Disks: nil, + Gpus: nil, + Memory: nil, + Nics: nil, + Os: nil, + }, + Host: nil, + Properties: nil, + Service: nil, + Type: "", + Version: "1.0", + }, + Status: &models.V1EdgeHostDeviceStatus{ + Health: &models.V1EdgeHostHealth{ + AgentVersion: "", + Message: "", + State: "healthy", + }, + InUseClusters: nil, + Packs: nil, + ProfileStatus: nil, + ServiceAuthToken: "", + State: "ready", + }, + } +} + +func AppliancesRoutes() []Route { + return []Route{ + { + Method: "POST", + Path: "/v1/dashboard/edgehosts/search", + Response: ResponseData{ + StatusCode: http.StatusOK, + Payload: getEdgeHostSearchSummary(), + }, + }, + { + Method: "GET", + Path: "/v1/edgehosts/{uid}", + Response: ResponseData{ + StatusCode: http.StatusOK, + Payload: getEdgeHostPayload(), + }, + }, + } +} + +func AppliancesNegativeRoutes() []Route { + return []Route{ + { + Method: "POST", + Path: "/v1/dashboard/edgehosts/search", + Response: ResponseData{ + StatusCode: http.StatusNotFound, + Payload: getError(strconv.Itoa(http.StatusNotFound), "No edge host found"), + }, + }, + { + Method: "GET", + Path: "/v1/edgehosts/{uid}", + Response: ResponseData{ + StatusCode: http.StatusNotFound, + Payload: getError(strconv.Itoa(http.StatusNotFound), "No edge host found"), + }, + }, + } +} diff --git a/tests/mockApiServer/stop_mock_api_server.sh b/tests/mockApiServer/stop_mock_api_server.sh index 4d3978e4..5b376f15 100755 --- a/tests/mockApiServer/stop_mock_api_server.sh +++ b/tests/mockApiServer/stop_mock_api_server.sh @@ -1,13 +1,13 @@ #!/bin/bash # Find the process ID of the running mockApiserver -PID=$(pgrep -f MockAPIServer) +PID=$(pgrep -f MockBuild) if [ -z "$PID" ]; then echo "MockAPIServer is not running." else # Kill the process kill $PID -# [ -f ./MockAPIServer ] && rm -f ./MockAPIServer + kill -9 $(lsof -t -i :8080) $(lsof -t -i :8888) echo "MockAPIServer (PID: $PID) has been stopped." fi \ No newline at end of file