/** 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" "log" "net/http" "net/http/httptest" "github.com/gin-gonic/gin" "github.com/nsf/jsondiff" ) // data type used in testing type KeyModels map[string]interface{} // ####################################################################### // #################### User data used for testing ####################### // ####################################################################### 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"` } // ############################################################################ // #################### 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 := resp.Bytes() // 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("failed to unmarshal 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 := w.Body.Bytes() // 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, 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("POST", "/api/v2/authenticate/internal", 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(w.Body.Bytes(), &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 }