VILLASweb-backend-go/helper/test_utilities.go

334 lines
10 KiB
Go
Raw Normal View History

/** Helper package, test utilities.
*
* @author Sonja Happ <sonja.happ@eonerc.rwth-aachen.de>
* @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 <http://www.gnu.org/licenses/>.
*********************************************************************************/
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)
2021-01-18 18:21:34 +01:00
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()
2021-01-18 18:21:34 +01:00
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)
}
2021-01-18 18:21:34 +01:00
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return 0, nil, fmt.Errorf("handle redirect: failed to follow redirect: %v", err)
2021-01-18 18:21:34 +01:00
}
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)
}
2021-01-18 18:21:34 +01:00
return resp.StatusCode, buf, nil
}
2021-01-18 18:21:34 +01:00
// 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
2021-02-05 19:26:15 +01:00
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)
}
2021-02-05 19:26:15 +01:00
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([]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
}