/** Helper package, test utilities. * * @author Sonja Happ * @copyright 2014-2019, Institute for Automation of Complex Power Systems, EONERC * @license GNU General Public License (version 3) * * VILLASweb-backend-go * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . *********************************************************************************/ package helper import ( "bytes" "encoding/json" "fmt" "git.rwth-aachen.de/acs/public/villas/web-backend-go/database" "github.com/gin-gonic/gin" "github.com/nsf/jsondiff" "golang.org/x/crypto/bcrypt" "log" "net/http" "net/http/httptest" ) // data type used in testing type KeyModels map[string]interface{} // ####################################################################### // #################### User data used for testing ####################### // ####################################################################### // Credentials var StrPassword0 = "xyz789" var StrPasswordA = "abc123" var StrPasswordB = "bcd234" var StrPasswordC = "guestpw" // Hash passwords with bcrypt algorithm var bcryptCost = 10 var pw0, _ = bcrypt.GenerateFromPassword([]byte(StrPassword0), bcryptCost) var pwA, _ = bcrypt.GenerateFromPassword([]byte(StrPasswordA), bcryptCost) var pwB, _ = bcrypt.GenerateFromPassword([]byte(StrPasswordB), bcryptCost) var pwC, _ = bcrypt.GenerateFromPassword([]byte(StrPasswordC), bcryptCost) var User0 = database.User{Username: "User_0", Password: string(pw0), Role: "Admin", Mail: "User_0@example.com"} var UserA = database.User{Username: "User_A", Password: string(pwA), Role: "User", Mail: "User_A@example.com", Active: true} var UserB = database.User{Username: "User_B", Password: string(pwB), Role: "User", Mail: "User_B@example.com", Active: true} var UserC = database.User{Username: "User_C", Password: string(pwC), Role: "Guest", Mail: "User_C@example.com", Active: true} type UserRequest struct { Username string `json:"username,omitempty"` Password string `json:"password,omitempty"` OldPassword string `json:"oldPassword,omitempty"` Mail string `json:"mail,omitempty"` Role string `json:"role,omitempty"` Active string `json:"active,omitempty"` } type Credentials struct { Username string `json:"username,required"` Password string `json:"password,required"` } var AdminCredentials = Credentials{ Username: User0.Username, Password: StrPassword0, } var UserACredentials = Credentials{ Username: UserA.Username, Password: StrPasswordA, } var UserBCredentials = Credentials{ Username: UserB.Username, Password: StrPasswordB, } var GuestCredentials = Credentials{ Username: UserC.Username, Password: StrPasswordC, } // ############################################################################ // #################### Functions used for testing ############################ // ############################################################################ // Return the ID of an element contained in a response func GetResponseID(resp *bytes.Buffer) (int, error) { // Transform bytes buffer into byte slice respBytes := []byte(resp.String()) // Map JSON response to a map[string]map[string]interface{} var respRemapped map[string]map[string]interface{} err := json.Unmarshal(respBytes, &respRemapped) if err != nil { return 0, fmt.Errorf("Unmarshal failed for respRemapped %v", err) } // Get an arbitrary key from tha map. The only key (entry) of // course is the model's name. With that trick we do not have to // pass the higher level key as argument. for arbitrary_key := range respRemapped { // The marshaler turns numerical values into float64 types so we // first have to make a type assertion to the interface and then // the conversion to integer before returning id, ok := respRemapped[arbitrary_key]["id"].(float64) if !ok { return 0, fmt.Errorf("Cannot type assert respRemapped") } return int(id), nil } return 0, fmt.Errorf("GetResponse reached exit") } // Return the length of an response in case it is an array func LengthOfResponse(router *gin.Engine, token string, url string, method string, body []byte) (int, error) { w := httptest.NewRecorder() req, err := http.NewRequest(method, url, nil) if err != nil { return 0, fmt.Errorf("Failed to create new request: %v", err) } req.Header.Set("Content-Type", "application/json") req.Header.Add("Authorization", "Bearer "+token) router.ServeHTTP(w, req) // HTTP Code of response must be 200 if w.Code != 200 { return 0, fmt.Errorf("HTTP Code: Expected \"200\". Got \"%v\""+ ".\nResponse message:\n%v", w.Code, w.Body.String()) } // Convert the response in array of bytes responseBytes := []byte(w.Body.String()) // First we are trying to unmarshal the response into an array of // general type variables ([]interface{}). If this fails we will try // to unmarshal into a single general type variable (interface{}). // If that also fails we will return 0. // Response might be array of objects var arrayResponse map[string][]interface{} err = json.Unmarshal(responseBytes, &arrayResponse) if err == nil { // Get an arbitrary key from tha map. The only key (entry) of // course is the model's name. With that trick we do not have to // pass the higher level key as argument. for arbitrary_key := range arrayResponse { return len(arrayResponse[arbitrary_key]), nil } } // Response might be a single object var singleResponse map[string]interface{} err = json.Unmarshal(responseBytes, &singleResponse) if err == nil { return 1, nil } // Failed to identify response. return 0, fmt.Errorf("Length of response cannot be detected") } // Make a request to an endpoint func TestEndpoint(router *gin.Engine, token string, url string, method string, requestBody interface{}) (int, *bytes.Buffer, error) { w := httptest.NewRecorder() // Marshal the HTTP request body body, err := json.Marshal(requestBody) if err != nil { return 0, nil, fmt.Errorf("Failed to marshal request body: %v", err) } // Create the request req, err := http.NewRequest(method, url, bytes.NewBuffer(body)) if err != nil { return 0, nil, fmt.Errorf("Failed to create new request: %v", err) } req.Header.Set("Content-Type", "application/json") req.Header.Add("Authorization", "Bearer "+token) router.ServeHTTP(w, req) return handleRedirect(w, req) } func handleRedirect(w *httptest.ResponseRecorder, req *http.Request) (int, *bytes.Buffer, error) { if w.Code == http.StatusMovedPermanently || w.Code == http.StatusFound || w.Code == http.StatusTemporaryRedirect || w.Code == http.StatusPermanentRedirect { // Follow external redirect redirURL, err := w.Result().Location() if err != nil { return 0, nil, fmt.Errorf("invalid location header") } log.Println("redirecting request to", redirURL.String()) req, err := http.NewRequest(req.Method, redirURL.String(), req.Body) if err != nil { return 0, nil, fmt.Errorf("handle redirect: failed to create new request: %v", err) } client := &http.Client{} resp, err := client.Do(req) if err != nil { return 0, nil, fmt.Errorf("handle redirect: failed to follow redirect: %v", err) } buf := new(bytes.Buffer) _, err = buf.ReadFrom(resp.Body) if err != nil { return 0, nil, fmt.Errorf("handle redirect: failed to follow redirect: %v", err) } return resp.StatusCode, buf, nil } // No redirect return w.Code, w.Body, nil } // Compare the response of a query with a JSON func CompareResponse(resp *bytes.Buffer, expected interface{}) error { // Serialize expected response expectedBytes, err := json.Marshal(expected) if err != nil { return fmt.Errorf("Failed to marshal expected response: %v", err) } // Compare opts := jsondiff.DefaultConsoleOptions() diff, text := jsondiff.Compare(resp.Bytes(), expectedBytes, &opts) if diff.String() != "FullMatch" && diff.String() != "SupersetMatch" { log.Println(text) return fmt.Errorf("Response: Expected \"%v\". Got \"%v\".", "(FullMatch OR SupersetMatch)", diff.String()) } return nil } // Authenticate a user for testing purposes func AuthenticateForTest(router *gin.Engine, url string, method string, credentials interface{}) (string, error) { w := httptest.NewRecorder() // Marshal credentials body, err := json.Marshal(credentials) if err != nil { return "", fmt.Errorf("Failed to marshal credentials: %v", err) } req, err := http.NewRequest(method, url, bytes.NewBuffer(body)) if err != nil { return "", fmt.Errorf("Failed to create new request: %v", err) } req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) // Check that return HTTP Code is 200 (OK) if w.Code != http.StatusOK { return "", fmt.Errorf("HTTP Code: Expected \"%v\". Got \"%v\".", http.StatusOK, w.Code) } // Get the response var body_data map[string]interface{} err = json.Unmarshal([]byte(w.Body.String()), &body_data) if err != nil { return "", err } // Check the response success, ok := body_data["success"].(bool) if !ok { return "", fmt.Errorf("Type asssertion of response[\"success\"] failed") } if !success { return "", fmt.Errorf("Authentication failed: %v", body_data["message"]) } // Extract the token token, ok := body_data["token"].(string) if !ok { return "", fmt.Errorf("Type assertion of response[\"token\"] failed") } // Return the token and nil error return token, nil } // add test users defined above func AddTestUsers() error { testUsers := []database.User{User0, UserA, UserB, UserC} database.DBpool.AutoMigrate(&database.User{}) for _, user := range testUsers { err := database.DBpool.Create(&user).Error if err != nil { return err } } return nil }