split code into multiple files

This commit is contained in:
2024-01-03 09:49:09 +11:00
parent 0f4d4b1801
commit 499a6df594
5 changed files with 513 additions and 525 deletions

6
check_results.go Normal file
View File

@@ -0,0 +1,6 @@
package main
func CheckResults(testCase *TestCase) (bool, error) {
return true, nil
}

256
load_input.go Normal file
View File

@@ -0,0 +1,256 @@
package main
import (
"encoding/json"
"fmt"
"github.com/iancoleman/orderedmap"
)
func ReadInput(data []byte, testDefinitions *TestDefinitions) {
var err error
// Unmarshal the json into an orderedmap to preserve the ordering of json structure
o := orderedmap.New()
err = json.Unmarshal([]byte(data), &o)
if err != nil {
error := fmt.Sprintf("JSON Unmarshal error %s\n", err)
panic(error)
}
topLevel := o.Keys()
//fmt.Printf("Found %d top-level keys in json data\n", len(topLevel))
// TODO : Check required top level keys are present
if len(topLevel) <= 1 {
error := "Missing required keys in input json"
panic(error)
}
// Get the name of the group of tests
if data, ok := o.Get("name"); ok {
testDefinitions.Name = data.(string)
//fmt.Printf("test name: '%s'\n", testDefinitions.Name)
}
// Get the base url if defined
if data, ok := o.Get("url"); ok {
testDefinitions.BaseUrl = data.(string)
}
// Check for any headers to include with all requests
if data, ok := o.Get("header"); ok {
headerMap := data.(orderedmap.OrderedMap)
testDefinitions.Headers = OrderedToStringMap(headerMap)
}
// Check if we should ignore certificate errors
if data, ok := o.Get("insecure"); ok {
testDefinitions.Insecure = data.(bool)
}
// Get a reference to the node containing each of the test cases
testsInterface, ok := o.Get("testCases")
if !ok {
panic("No key defining test cases found\n")
}
/*
// Test Cases is an array, need to go one level down before we start looking at key/values
vs := testsInterface.([]interface{})
fmt.Printf("Listing %d test definitions\n", len(vs))
for i, vInterface := range vs {
v := vInterface.(orderedmap.OrderedMap)
keys := v.Keys()
fmt.Printf("Test %d\n", i)
for j := range keys {
fmt.Printf("[%d] : %s\n", j, keys[j])
}
}
*/
/*
vslice := testsInterface.([]interface{})
vmap := vslice[2].(orderedmap.OrderedMap)
k := vmap.Keys()
for i := range k {
fmt.Printf("[%d] : %s\n", i, k[i])
}
return
*/
// Process the "capture" settings that allow us to use values from one test case in subsequent test cases
// each capture node has a name that links it to the test case that captures that data
if capture, ok := o.Get("capture"); ok {
captureMap := capture.(orderedmap.OrderedMap)
captureKeys := captureMap.Keys()
for _, outerKey := range captureKeys {
// Define a new capture case object to record these details
thisCaptureCase := new(CaptureCase)
// Make sure we can access the capture defintiion
if captureCase, ok := captureMap.Get(outerKey); ok {
// Store the test case name that this capture will come from
thisCaptureCase.TestCaseName = outerKey
//fmt.Printf("[%d] : Capture from %s\n", i, outerKey)
// Get capture data from body
thisCaptureCaseMap := captureCase.(orderedmap.OrderedMap)
if body, ok := thisCaptureCaseMap.Get("body"); ok {
//fmt.Printf("This capture case has body defined\n")
bodyMap := body.(orderedmap.OrderedMap)
bodyMapKeys := bodyMap.Keys()
bodyData := make(map[string]string)
for _, bodyKey := range bodyMapKeys {
if bodyVal, ok := bodyMap.Get(bodyKey); ok {
switch vType := bodyVal.(type) {
case string:
//fmt.Printf("Capturing '%s' from test result and using '%s' for replacement token\n", bodyKey, bodyVal.(string))
// Store capture info
bodyData[bodyKey] = bodyVal.(string)
default:
fmt.Printf("received unexpected value type, %T\n", vType)
}
}
}
captureBody := new(CaptureBody)
captureBody.Data = bodyData
thisCaptureCase.CaptureData.Body = *captureBody
}
// TODO : header
}
testDefinitions.CaptureCases = append(testDefinitions.CaptureCases, *thisCaptureCase)
}
}
// Get the keys for the first test so we know what config options have been specified
testCasesMap := testsInterface.(orderedmap.OrderedMap)
testCasesKeys := testCasesMap.Keys()
// Parse json into our testCaseDefinition
// Parse each key into our config struct
fmt.Printf("Loading %d test cases\n", len(testCasesKeys))
for _, outerKey := range testCasesKeys {
//fmt.Printf("Test %d : %s\n", i, outerKey)
// Get the name of the test case
thisTestCase := new(TestCase)
thisTestCase.Name = outerKey
if testCase, ok := testCasesMap.Get(outerKey); ok {
// Get the details of the test case
thisTestCaseMap := testCase.(orderedmap.OrderedMap)
// Path
if val, ok := thisTestCaseMap.Get("path"); ok {
thisTestCase.Path = val.(string)
}
// Method
if val, ok := thisTestCaseMap.Get("method"); ok {
thisTestCase.Method = val.(string)
}
// Description
if val, ok := thisTestCaseMap.Get("description"); ok {
thisTestCase.Description = val.(string)
}
// Body
if val, ok := thisTestCaseMap.Get("body"); ok {
// Create a normal string map for all the body key-value pairs
// TODO : Support nested json instead of single level flat request
//thisTestCase.Body = OrderedToStringMap(val.(orderedmap.OrderedMap))
bytes, err := json.Marshal(val)
if err != nil {
error := fmt.Sprintf("Error processing request body for test case : '%s'\n", err)
panic(error)
} else {
thisTestCase.Body = bytes
//fmt.Printf("Body marshalled:\n%v\n", string(bytes))
}
}
// Header
if val, ok := thisTestCaseMap.Get("header"); ok {
// Create a normal string map for all the body key-value pairs
thisTestCase.Header = OrderedToStringMap(val.(orderedmap.OrderedMap))
}
// Expect - this is more tricky since it is yet another json fragment
if val, ok := thisTestCaseMap.Get("expect"); ok {
expectOptions := new(ExpectOptions)
// expect can have header and body definitions
expectMap := val.(orderedmap.OrderedMap)
// Handle the possible checks for a header
if expectVal, ok2 := expectMap.Get("header"); ok2 {
ReadHeaderTestCases(expectVal.(orderedmap.OrderedMap), &expectOptions.Header)
}
// Handle the possible checks for a body
if expectVal, ok2 := expectMap.Get("body"); ok2 {
ReadBodyTestCases(expectVal.(orderedmap.OrderedMap), &expectOptions.Body)
}
thisTestCase.Expect = *expectOptions
}
}
testDefinitions.TestCases = append(testDefinitions.TestCases, *thisTestCase)
}
}
func ReadHeaderTestCases(input orderedmap.OrderedMap, result *HeaderTests) {
result.Contains = make(map[string]string)
result.Equals = make(map[string]string)
// Contains check
if val, ok := input.Get("contains"); ok {
containsMap := val.(orderedmap.OrderedMap)
result.Contains = OrderedToStringMap(containsMap)
}
// Equals check
if val, ok := input.Get("equals"); ok {
equalsMap := val.(orderedmap.OrderedMap)
result.Equals = OrderedToStringMap(equalsMap)
}
}
func ReadBodyTestCases(input orderedmap.OrderedMap, result *BodyTests) {
result.Contains = make(map[string]string)
result.Equals = make(map[string]string)
result.HasKeys = make(map[string]string)
// TODO : Use tags in struct rather than hard coding all the different check types
// using https://stackoverflow.com/a/23840419 as an idea
// Contains check
if val, ok := input.Get("contains"); ok {
containsMap := val.(orderedmap.OrderedMap)
result.Contains = OrderedToStringMap(containsMap)
}
// Equals check
if val, ok := input.Get("equals"); ok {
equalsMap := val.(orderedmap.OrderedMap)
result.Equals = OrderedToStringMap(equalsMap)
}
// Has Keys check
if val, ok := input.Get("haskeys"); ok {
equalsMap := val.(orderedmap.OrderedMap)
result.HasKeys = OrderedToStringMap(equalsMap)
}
// TODO : remaining tests
}

539
main.go
View File

@@ -1,287 +1,17 @@
package main package main
import ( import (
"bytes"
"crypto/tls"
"encoding/json"
"errors"
"flag" "flag"
"fmt" "fmt"
"io"
"net/http"
"net/url"
"os" "os"
"strconv" "strconv"
"strings"
"github.com/iancoleman/orderedmap" "github.com/iancoleman/orderedmap"
) )
type TestDefinitions struct {
Name string `json:"name"`
TestCases []TestCase `json:"testCases"`
BaseUrl string `json:"url"`
CaptureCases []CaptureCase `json:"capture"`
Headers map[string]string
Insecure bool `json:"insecure"`
}
type TestCase struct {
Name string
Path string `json:"path"`
Method string `json:"method"`
Description string `json:"description"`
Expect ExpectOptions `json:"expect"`
Header map[string]string
//Body map[string]string
Body []byte
// Something to store results in
ResultStatusCode int
ResultHeaders map[string]string
ResultBody string
}
type CaptureCase struct {
TestCaseName string
CaptureData CaptureCaseData
}
type CaptureCaseData struct {
Header CaptureHeader `json:"header"`
Body CaptureBody `json:"body"`
}
type CaptureHeader struct {
Data map[string]string
}
type CaptureBody struct {
Data map[string]string
}
type CaptureValues struct {
Data map[string]string
}
type ExpectOptions struct {
Header HeaderTests `json:"header"`
Body BodyTests `json:"body"`
}
// Test types
type HeaderTests struct {
Contains map[string]string
Equals map[string]string
}
type BodyTests struct {
Contains map[string]string
Equals map[string]string
HasKeys map[string]string
PathEquals string
PathContains map[string]string
}
var testDefinitions *TestDefinitions var testDefinitions *TestDefinitions
var captureValues *CaptureValues var captureValues *CaptureValues
// fileExists returns true if the specified file exists and is not a directory
func fileExists(filename string) bool {
info, err := os.Stat(filename)
if os.IsNotExist(err) {
return false
}
return !info.IsDir()
}
func PerformGet() {
// Sample code from https://golangnote.com/request/sending-post-request-in-golang-with-header
url := "https://www.example.com/api/v1/create"
contentType := "application/json"
data := []byte(`{"name": "Test User", "email": "test@example.com"}`)
client := &http.Client{}
req, err := http.NewRequest("GET", url, bytes.NewBuffer(data))
if err != nil {
fmt.Println(err)
return
}
req.Header.Add("Content-Type", contentType)
req.Header.Add("Authorization", "Bearer YOUR_ACCESS_TOKEN")
resp, err := client.Do(req)
if err != nil {
fmt.Println(err)
return
}
defer resp.Body.Close()
fmt.Printf("%+v\n", resp.Header)
fmt.Printf("%+v\n", resp.StatusCode)
body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(body))
}
func PerformPost() {
}
func TestOutput() {
}
// 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("Running test '%s'\n", testCase.Name)
// Determine URL
if len(testDefinitions.BaseUrl) > 0 {
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)
}
} 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
}
// Create request
if len(testCase.Body) > 0 {
req, err = http.NewRequest(requestType, requestUrl, bytes.NewBuffer(testCase.Body))
} 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
resp, err := client.Do(req)
if err != nil {
fmt.Println(err)
return err
}
defer resp.Body.Close()
// Store response
fmt.Printf("Header response:\n%+v\n", resp.Header)
fmt.Printf("http_status:\n'%+v'\n", resp.StatusCode)
body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
return err
}
fmt.Printf("Body response:\n%s\n", string(body))
// 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 {
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("Need to capture value from this test case\n")
captureData := testDefinitions.CaptureCases[i].CaptureData
for k, v := range captureData.Body.Data {
fmt.Printf("Body capture %s = %s\n", k, v)
if val, ok := bodyMap[k]; ok {
// TODO handle values other than string using a switch
fmt.Printf("Found matching capture in body with value : '%v', storing replacement with key '%s'\n", val, v)
captureValues.Data[v] = val.(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 {
if strings.Contains(input, k) {
fmt.Printf("Found key '%s' in input string '%s', replacing with '%s'\n", k, input, v)
input = strings.Replace(input, k, v, -1)
}
}
return input
}
func main() { func main() {
var s []byte var s []byte
var err error var err error
@@ -312,222 +42,27 @@ func main() {
// Process the input json // Process the input json
ReadInput(s, testDefinitions) ReadInput(s, testDefinitions)
// For debugging, just dump the output
fmt.Printf("%+v\n", testDefinitions)
// Perform specified test cases // Perform specified test cases
for i := range testDefinitions.TestCases { for i := range testDefinitions.TestCases {
RunTest(&testDefinitions.TestCases[i]) RunTest(&testDefinitions.TestCases[i])
success, _ := CheckResults(&testDefinitions.TestCases[i])
if !success {
fmt.Printf("Test result failed\n")
os.Exit(1)
}
} }
// For debugging, just dump the output
fmt.Printf("%+v\n", testDefinitions)
} }
func ReadInput(data []byte, testDefinitions *TestDefinitions) { // fileExists returns true if the specified file exists and is not a directory
var err error func fileExists(filename string) bool {
info, err := os.Stat(filename)
// Unmarshal the json into an orderedmap to preserve the ordering of json structure if os.IsNotExist(err) {
o := orderedmap.New() return false
err = json.Unmarshal([]byte(data), &o)
if err != nil {
error := fmt.Sprintf("JSON Unmarshal error %s\n", err)
panic(error)
}
topLevel := o.Keys()
fmt.Printf("Found %d top-level keys in json data\n", len(topLevel))
// TODO : Check required top level keys are present
if len(topLevel) <= 1 {
error := "Missing required keys in input json"
panic(error)
}
// Get the name of the group of tests
if data, ok := o.Get("name"); ok {
testDefinitions.Name = data.(string)
fmt.Printf("test name: '%s'\n", testDefinitions.Name)
}
// Get the base url if defined
if data, ok := o.Get("url"); ok {
testDefinitions.BaseUrl = data.(string)
}
// Check for any headers to include with all requests
if data, ok := o.Get("header"); ok {
headerMap := data.(orderedmap.OrderedMap)
testDefinitions.Headers = OrderedToStringMap(headerMap)
}
// Check if we should ignore certificate errors
if data, ok := o.Get("insecure"); ok {
testDefinitions.Insecure = data.(bool)
}
// Get a reference to the node containing each of the test cases
testsInterface, ok := o.Get("testCases")
if !ok {
fmt.Printf("No key defining test cases found\n")
}
/*
// Test Cases is an array, need to go one level down before we start looking at key/values
vs := testsInterface.([]interface{})
fmt.Printf("Listing %d test definitions\n", len(vs))
for i, vInterface := range vs {
v := vInterface.(orderedmap.OrderedMap)
keys := v.Keys()
fmt.Printf("Test %d\n", i)
for j := range keys {
fmt.Printf("[%d] : %s\n", j, keys[j])
}
}
*/
/*
vslice := testsInterface.([]interface{})
vmap := vslice[2].(orderedmap.OrderedMap)
k := vmap.Keys()
for i := range k {
fmt.Printf("[%d] : %s\n", i, k[i])
}
return
*/
// Process the "capture" settings that allow us to use values from one test case in subsequent test cases
// each capture node has a name that links it to the test case that captures that data
if capture, ok := o.Get("capture"); ok {
captureMap := capture.(orderedmap.OrderedMap)
captureKeys := captureMap.Keys()
for i, outerKey := range captureKeys {
// Define a new capture case object to record these details
thisCaptureCase := new(CaptureCase)
// Make sure we can access the capture defintiion
if captureCase, ok := captureMap.Get(outerKey); ok {
// Store the test case name that this capture will come from
thisCaptureCase.TestCaseName = outerKey
fmt.Printf("[%d] : Capture from %s\n", i, outerKey)
// Get capture data from body
thisCaptureCaseMap := captureCase.(orderedmap.OrderedMap)
if body, ok := thisCaptureCaseMap.Get("body"); ok {
//fmt.Printf("This capture case has body defined\n")
bodyMap := body.(orderedmap.OrderedMap)
bodyMapKeys := bodyMap.Keys()
bodyData := make(map[string]string)
for _, bodyKey := range bodyMapKeys {
if bodyVal, ok := bodyMap.Get(bodyKey); ok {
switch vType := bodyVal.(type) {
case string:
fmt.Printf("Capturing '%s' from test result and using '%s' for replacement token\n", bodyKey, bodyVal.(string))
// Store capture info
bodyData[bodyKey] = bodyVal.(string)
default:
fmt.Printf("received unexpected value type, %T\n", vType)
}
}
}
captureBody := new(CaptureBody)
captureBody.Data = bodyData
thisCaptureCase.CaptureData.Body = *captureBody
}
// TODO : header
}
testDefinitions.CaptureCases = append(testDefinitions.CaptureCases, *thisCaptureCase)
}
}
// Get the keys for the first test so we know what config options have been specified
testCasesMap := testsInterface.(orderedmap.OrderedMap)
testCasesKeys := testCasesMap.Keys()
// Parse json into our testCaseDefinition
// Parse each key into our config struct
fmt.Printf("Listing %d test cases\n", len(testCasesKeys))
for i, outerKey := range testCasesKeys {
fmt.Printf("Test %d : %s\n", i, outerKey)
// Get the name of the test case
thisTestCase := new(TestCase)
thisTestCase.Name = outerKey
if testCase, ok := testCasesMap.Get(outerKey); ok {
// Get the details of the test case
thisTestCaseMap := testCase.(orderedmap.OrderedMap)
// Path
if val, ok := thisTestCaseMap.Get("path"); ok {
thisTestCase.Path = val.(string)
}
// Method
if val, ok := thisTestCaseMap.Get("method"); ok {
thisTestCase.Method = val.(string)
}
// Description
if val, ok := thisTestCaseMap.Get("description"); ok {
thisTestCase.Description = val.(string)
}
// Body
if val, ok := thisTestCaseMap.Get("body"); ok {
// Create a normal string map for all the body key-value pairs
// TODO : Support nested json instead of single level flat request
//thisTestCase.Body = OrderedToStringMap(val.(orderedmap.OrderedMap))
bytes, err := json.Marshal(val)
if err != nil {
fmt.Printf("Error processing request body for test case : '%s'\n", err)
} else {
thisTestCase.Body = bytes
//fmt.Printf("Body marshalled:\n%v\n", string(bytes))
}
}
// Header
if val, ok := thisTestCaseMap.Get("header"); ok {
// Create a normal string map for all the body key-value pairs
thisTestCase.Header = OrderedToStringMap(val.(orderedmap.OrderedMap))
}
// Expect - this is more tricky since it is yet another json fragment
if val, ok := thisTestCaseMap.Get("expect"); ok {
expectOptions := new(ExpectOptions)
// expect can have header and body definitions
expectMap := val.(orderedmap.OrderedMap)
// Handle the possible checks for a header
if expectVal, ok2 := expectMap.Get("header"); ok2 {
ReadHeaderTestCases(expectVal.(orderedmap.OrderedMap), &expectOptions.Header)
}
// Handle the possible checks for a body
if expectVal, ok2 := expectMap.Get("body"); ok2 {
ReadBodyTestCases(expectVal.(orderedmap.OrderedMap), &expectOptions.Body)
}
thisTestCase.Expect = *expectOptions
}
/*
for j, key := range thisTestCaseMap.Keys() {
val, _ := thisTestCaseMap.Get(key)
fmt.Printf("[%d] %s : %s\n", j, key, val)
}
*/
}
testDefinitions.TestCases = append(testDefinitions.TestCases, *thisTestCase)
} }
return !info.IsDir()
} }
func OrderedToStringMap(input orderedmap.OrderedMap) map[string]string { func OrderedToStringMap(input orderedmap.OrderedMap) map[string]string {
@@ -549,49 +84,3 @@ func OrderedToStringMap(input orderedmap.OrderedMap) map[string]string {
return result return result
} }
func ReadHeaderTestCases(input orderedmap.OrderedMap, result *HeaderTests) {
result.Contains = make(map[string]string)
result.Equals = make(map[string]string)
// Contains check
if val, ok := input.Get("contains"); ok {
containsMap := val.(orderedmap.OrderedMap)
result.Contains = OrderedToStringMap(containsMap)
}
// Equals check
if val, ok := input.Get("equals"); ok {
equalsMap := val.(orderedmap.OrderedMap)
result.Equals = OrderedToStringMap(equalsMap)
}
}
func ReadBodyTestCases(input orderedmap.OrderedMap, result *BodyTests) {
result.Contains = make(map[string]string)
result.Equals = make(map[string]string)
result.HasKeys = make(map[string]string)
// TODO : Use tags in struct rather than hard coding all the different check types
// using https://stackoverflow.com/a/23840419 as an idea
// Contains check
if val, ok := input.Get("contains"); ok {
containsMap := val.(orderedmap.OrderedMap)
result.Contains = OrderedToStringMap(containsMap)
}
// Equals check
if val, ok := input.Get("equals"); ok {
equalsMap := val.(orderedmap.OrderedMap)
result.Equals = OrderedToStringMap(equalsMap)
}
// Has Keys check
if val, ok := input.Get("haskeys"); ok {
equalsMap := val.(orderedmap.OrderedMap)
result.HasKeys = OrderedToStringMap(equalsMap)
}
// TODO : remaining tests
}

169
run_tests.go Normal file
View File

@@ -0,0 +1,169 @@
package main
import (
"bytes"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"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("Running test '%s'\n", testCase.Name)
// Determine URL
if len(testDefinitions.BaseUrl) > 0 {
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)
}
} 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
}
// Create request
if len(testCase.Body) > 0 {
req, err = http.NewRequest(requestType, requestUrl, bytes.NewBuffer(testCase.Body))
} 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
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("http_status:\n'%+v'\n", testCase.ResultStatusCode)
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)
/*
// Something to store results in
ResultStatusCode int
ResultHeaders map[string]string
ResultBody string
*/
// 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 {
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 {
captureData := testDefinitions.CaptureCases[i].CaptureData
for k, v := range captureData.Body.Data {
//fmt.Printf("Body capture %s = %s\n", k, v)
if val, ok := bodyMap[k]; ok {
// TODO handle values other than string using a switch
//fmt.Printf("Found matching capture in body with value : '%v', storing replacement with key '%s'\n", val, v)
captureValues.Data[v] = val.(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
}

68
structs.go Normal file
View File

@@ -0,0 +1,68 @@
package main
type TestDefinitions struct {
Name string `json:"name"`
TestCases []TestCase `json:"testCases"`
BaseUrl string `json:"url"`
CaptureCases []CaptureCase `json:"capture"`
Headers map[string]string
Insecure bool `json:"insecure"`
}
type TestCase struct {
Name string
Path string `json:"path"`
Method string `json:"method"`
Description string `json:"description"`
Expect ExpectOptions `json:"expect"`
Header map[string]string
//Body map[string]string
Body []byte
// Something to store results in
ResultStatusCode int
ResultHeaders map[string][]string
ResultBody string
}
type CaptureCase struct {
TestCaseName string
CaptureData CaptureCaseData
}
type CaptureCaseData struct {
Header CaptureHeader `json:"header"`
Body CaptureBody `json:"body"`
}
type CaptureHeader struct {
Data map[string]string
}
type CaptureBody struct {
Data map[string]string
}
type CaptureValues struct {
Data map[string]string
}
type ExpectOptions struct {
Header HeaderTests `json:"header"`
Body BodyTests `json:"body"`
}
// Test types
type HeaderTests struct {
Contains map[string]string
Equals map[string]string
}
type BodyTests struct {
Contains map[string]string
Equals map[string]string
HasKeys map[string]string
PathEquals string
PathContains map[string]string
}