All checks were successful
continuous-integration/drone/push Build is passing
274 lines
7.4 KiB
Go
274 lines
7.4 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/tls"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// RunTest Executes each individual test case
|
|
func RunTest(testCase *TestCase) error {
|
|
var err error
|
|
var requestUrl string
|
|
var requestType string
|
|
var req *http.Request
|
|
//var bodyMap map[string]interface{}
|
|
|
|
tr := &http.Transport{
|
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: testDefinitions.Insecure},
|
|
}
|
|
client := &http.Client{Transport: tr}
|
|
|
|
fmt.Printf("\nRunning %s : %s\n\n", testCase.Name, testCase.Description)
|
|
|
|
// Determine URL
|
|
if len(testDefinitions.BaseUrl) > 0 {
|
|
//fmt.Printf("Joining '%s' with '%s'\n", testDefinitions.BaseUrl, testCase.Path)
|
|
|
|
requestUrl, err = url.JoinPath(testDefinitions.BaseUrl, testCase.Path)
|
|
if err != nil {
|
|
errMessage := fmt.Sprintf("error combining request URL : '%s'\n", err)
|
|
return errors.New(errMessage)
|
|
}
|
|
|
|
decoded, err := url.QueryUnescape(requestUrl)
|
|
if err != nil {
|
|
errMessage := fmt.Sprintf("error unescaping request URL : '%s'\n", err)
|
|
return errors.New(errMessage)
|
|
}
|
|
requestUrl = decoded
|
|
//fmt.Printf("url path decoded '%s'\n", decoded)
|
|
|
|
} else {
|
|
requestUrl = testCase.Path
|
|
}
|
|
|
|
// Populate request body
|
|
|
|
// Get request type, default to GET
|
|
if len(testCase.Method) == 0 {
|
|
//return errors.New("no test method specifed, must be either GET or POST")
|
|
|
|
// Assume GET if no method specified
|
|
requestType = "GET"
|
|
} else {
|
|
requestType = testCase.Method
|
|
}
|
|
|
|
fmt.Printf("Preparing %s for endpoint %s\n", requestType, requestUrl)
|
|
|
|
// Create request
|
|
if len(testCase.Body) > 0 {
|
|
|
|
// unmarshal testCase.Body so we can search for replacements
|
|
var jsonBody map[string]interface{}
|
|
err = json.Unmarshal(testCase.Body, &jsonBody)
|
|
if err != nil {
|
|
error := fmt.Sprintf("Error processing request body for test case : '%s'\n", err)
|
|
panic(error)
|
|
}
|
|
|
|
// iterate through the key-value pairs and check for replacements
|
|
//fmt.Printf("Capture data : '%+v'\n", captureValues.Data)
|
|
BodyReplaceCapture(jsonBody, captureValues.Data)
|
|
|
|
// turn the unmarshalled object back into a []byte so that we can send it
|
|
modifiedJsonData, err := json.Marshal(jsonBody)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// Check for capture tokens that need to be replaced before adding the value
|
|
//newBody := ReplaceCaptures(string(testCase.Body[:]))
|
|
|
|
fmt.Printf("Adding body to request : %s\n", modifiedJsonData)
|
|
req, err = http.NewRequest(requestType, requestUrl, bytes.NewBuffer(modifiedJsonData))
|
|
|
|
if err != nil {
|
|
errMessage := fmt.Sprintf("error submitting request : '%s'\n", err)
|
|
return errors.New(errMessage)
|
|
}
|
|
|
|
} else {
|
|
req, err = http.NewRequest(requestType, requestUrl, nil)
|
|
}
|
|
|
|
if err != nil {
|
|
errMessage := fmt.Sprintf("error submitting request : '%s'\n", err)
|
|
return errors.New(errMessage)
|
|
}
|
|
|
|
// Assemble headers
|
|
// Global headers first
|
|
if len(testDefinitions.Headers) > 0 {
|
|
//fmt.Printf("Adding global headers to request\n")
|
|
for k, v := range testDefinitions.Headers {
|
|
// Check for capture tokens that need to be replaced before adding the value
|
|
key := HeaderReplaceCaptures(k)
|
|
val := HeaderReplaceCaptures(v)
|
|
|
|
//fmt.Printf("Add global header %s = %s\n", key, val)
|
|
req.Header.Add(key, val)
|
|
}
|
|
}
|
|
|
|
// Then any test case specific headers
|
|
for k, v := range testCase.Header {
|
|
// Check for capture tokens that need to be replaced before adding the value
|
|
key := HeaderReplaceCaptures(k)
|
|
val := HeaderReplaceCaptures(v)
|
|
|
|
//fmt.Printf("Add header %s = %s\n", key, val)
|
|
req.Header.Add(key, val)
|
|
}
|
|
|
|
// Perform request
|
|
fmt.Printf("Sending request\n")
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
// Store response
|
|
testCase.ResultStatusCode = resp.StatusCode
|
|
testCase.ResultHeaders = resp.Header
|
|
|
|
fmt.Printf("Status Code: %s\n", resp.Status)
|
|
fmt.Printf("Header response:\n%+v\n", testCase.ResultHeaders)
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
return err
|
|
}
|
|
|
|
testCase.ResultBody = string(body)
|
|
fmt.Printf("Body response:\n%s\n", testCase.ResultBody)
|
|
|
|
// We have no idea what the response will look like so use an interface and unmarshal manually as needed
|
|
if len(body) > 0 {
|
|
var b interface{}
|
|
json.Unmarshal(body, &b)
|
|
if b != nil {
|
|
testCase.ResultBodyMap = b.(map[string]interface{})
|
|
//fmt.Printf("Body map: '%v'\n", testCase.ResultBodyMap)
|
|
//bodyMap = b.(map[string]interface{})
|
|
}
|
|
}
|
|
|
|
// Iterate through testDefinitions.CaptureCases and see if there are any that match this test
|
|
// If we found one, add the appropriate replacement to captureValues
|
|
for i := range testDefinitions.CaptureCases {
|
|
if testDefinitions.CaptureCases[i].TestCaseName == testCase.Name {
|
|
fmt.Printf("\nProcessing capture(s) for test case '%s'\n", testCase.Name)
|
|
captureData := testDefinitions.CaptureCases[i].CaptureData
|
|
|
|
for k, v := range captureData.Body.Data {
|
|
fmt.Printf("Searching body response for match on capture string '%s'\n", k)
|
|
|
|
results := findKey(k, testCase.ResultBodyMap)
|
|
//fmt.Printf("Results : '%v'\n", results)
|
|
|
|
if len(results) > 0 {
|
|
//fmt.Printf("Found %d results but only storing the first one\n", len(results))
|
|
|
|
// Get the type of the first element
|
|
valueType := reflect.TypeOf(results[0])
|
|
|
|
// Check if the type is not string
|
|
if valueType.Kind() != reflect.String {
|
|
// Convert the value to string
|
|
results[0] = fmt.Sprintf("%v", results[0])
|
|
}
|
|
|
|
fmt.Printf("Storing capture '%s' = '%s'\n", k, results[0].(string))
|
|
captureValues.Data[v] = results[0].(string)
|
|
}
|
|
}
|
|
|
|
// Capture anything needed
|
|
break
|
|
}
|
|
}
|
|
|
|
// No errors if we reached this point
|
|
return nil
|
|
}
|
|
|
|
// HeaderReplaceCaptures iterates through captureValues to apply any text replacements necessary for input string
|
|
func HeaderReplaceCaptures(input string) string {
|
|
|
|
for k, v := range captureValues.Data {
|
|
input = strings.Replace(input, k, v, -1)
|
|
/*
|
|
if strings.Contains(input, k) {
|
|
//fmt.Printf("Found key '%s' in input string '%s', replacing with '%s'\n", k, input, v)
|
|
|
|
}
|
|
*/
|
|
}
|
|
|
|
return input
|
|
}
|
|
|
|
// Search unmarshalled json data for any replacements to make from data already captured by previous tests
|
|
func BodyReplaceCapture(data interface{}, captureValues map[string]string) {
|
|
switch v := data.(type) {
|
|
case map[string]interface{}:
|
|
for key, val := range v {
|
|
//fmt.Printf("NewReplaceCapture checking '%v' = '%v'\n", key, val)
|
|
|
|
// Check if key needs to be replaced
|
|
if replaceKey, ok := captureValues[key]; ok {
|
|
//fmt.Printf("Replacing key '%s'\n", key)
|
|
delete(v, key)
|
|
v[replaceKey] = val
|
|
key = replaceKey
|
|
}
|
|
|
|
// Check if value needs to be replaced
|
|
if strVal, ok := val.(string); ok {
|
|
//fmt.Printf("Checking value '%s'\n", strVal)
|
|
if replaceVal, ok := captureValues[strVal]; ok {
|
|
|
|
if strings.HasPrefix(strVal, "int:") {
|
|
fmt.Printf("Replacing capture token '%s' with int version of '%s'\n", strVal, replaceVal)
|
|
i, err := strconv.Atoi(replaceVal)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
v[key] = i
|
|
} else {
|
|
fmt.Printf("Replacing capture token '%s' with '%s'\n", strVal, replaceVal)
|
|
v[key] = replaceVal
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
if replaceVal, ok := captureValues[key]; ok {
|
|
//fmt.Printf("Replacing value '%s'\n", captureValues[key])
|
|
v[key] = replaceVal
|
|
}
|
|
|
|
BodyReplaceCapture(val, captureValues)
|
|
}
|
|
case []interface{}:
|
|
for i, val := range v {
|
|
fmt.Printf("i val : '%v' = '%v'\n", i, val)
|
|
BodyReplaceCapture(val, captureValues)
|
|
v[i] = val
|
|
}
|
|
}
|
|
}
|