From b2519287effe3c09767e192f649d74ac017ed75b Mon Sep 17 00:00:00 2001 From: Nathan Coad Date: Wed, 3 Jan 2024 15:42:25 +1100 Subject: [PATCH] improve body contains check --- .drone.yml | 1 + check_results.go | 61 +++++++++++++++++++++++++++----------- go.mod | 7 +++++ go.sum | 11 +++++++ load_input.go | 28 ------------------ main.go | 17 +++-------- run_tests.go | 8 +++-- tests.json | 77 +++++++++++++++++++++++++++++++++++++++++++++++- 8 files changed, 148 insertions(+), 62 deletions(-) diff --git a/.drone.yml b/.drone.yml index 3ca26a9..7ab1cf1 100644 --- a/.drone.yml +++ b/.drone.yml @@ -11,6 +11,7 @@ steps: commands: - sh ./.drone.sh +# See https://plugins.drone.io/plugins/gitea-release - name: gitea_release image: plugins/gitea-release when: diff --git a/check_results.go b/check_results.go index 8e624d6..0c99f27 100644 --- a/check_results.go +++ b/check_results.go @@ -56,11 +56,11 @@ func HeaderContains(testCase *TestCase) (bool, error) { checkFound := false for hKey, hVal := range testCase.ResultHeaders { if k == hKey { - fmt.Printf("Found header key '%s', checking value '%s' matches test definition '%s'\n", hKey, hVal, v) + //fmt.Printf("Found header key '%s', checking value '%s' matches test definition '%s'\n", hKey, hVal, v) for i := range hVal { if strings.Contains(hVal[i], v) { - fmt.Printf("Found match '%s' = '%s'\n", hVal[i], v) + //fmt.Printf("Found match '%s' = '%s'\n", hVal[i], v) checkFound = true } } @@ -68,7 +68,7 @@ func HeaderContains(testCase *TestCase) (bool, error) { } if !checkFound { - fmt.Printf("Expected to find header key-value of '%s:%s' but no matching value was found\n", k, v) + fmt.Printf("Header Contains Check failed to find expected header key-value of '%s:%s'\n", k, v) return false, nil } } @@ -90,27 +90,32 @@ func HeaderEquals(testCase *TestCase) (bool, error) { func BodyContains(testCase *TestCase) (bool, error) { for k, v := range testCase.Expect.Body.Contains { - //fmt.Printf("Body contains check '%s'='%s'\n", k, v) + fmt.Printf("Body contains check '%s'='%s'\n", k, v) + fmt.Printf("%+v\n", testCase.ResultBodyMap) - // If the body response was json then we stored it already when running the test - if val, ok := testCase.ResultBodyMap[k]; ok { - // TODO handle values other than string using a switch - //fmt.Printf("Found key in body with response '%s'\n", val) + results := findKey(k, testCase.ResultBodyMap) - // Check that the value matched - if strings.Contains(val.(string), v) { - fmt.Printf("Key matches, success\n") - } else { - fmt.Printf("Body Contains check failed on key '%s'. Expected value '%s' but found '%s'\n", k, v, val) + if len(results) > 0 { + fmt.Printf("Found key '%s': %v\n", k, results) + + checkFound := false + // search through all the results to see if the expected value is there + for i := range results { + if strings.Contains(results[i].(string), v) { + fmt.Printf("Expected value of '%s' matches, success\n", v) + checkFound = true + break + } + } + + if !checkFound { + fmt.Printf("Body Contains check failed on key '%s', did not find expected value '%s'\n", k, v) return false, nil } } else { - // body did not contain the key we were looking for so return false - return false, nil + fmt.Printf("Key '%s' not found\n", k) } - - // TODO } return true, nil @@ -120,7 +125,7 @@ func BodyHasKeys(testCase *TestCase) (bool, error) { for _, key := range testCase.Expect.Body.HasKeys { // If the body response was json then we stored it already when running the test if _, ok := testCase.ResultBodyMap[key]; ok { - fmt.Printf("Confirmed body has key '%s'\n", key) + //fmt.Printf("Confirmed body has key '%s'\n", key) } else { fmt.Printf("Body HasKeys test failed, expected key '%s' not found\n", key) return false, nil @@ -129,3 +134,23 @@ func BodyHasKeys(testCase *TestCase) (bool, error) { return true, nil } + +// findKey recursively searches map for specified key +func findKey(key string, data interface{}) []interface{} { + var results []interface{} + + switch v := data.(type) { + case map[string]interface{}: + if val, ok := v[key]; ok { + results = append(results, val) + } + for _, value := range v { + results = append(results, findKey(key, value)...) + } + case []interface{}: + for _, value := range v { + results = append(results, findKey(key, value)...) + } + } + return results +} diff --git a/go.mod b/go.mod index a5ab58f..6006c47 100644 --- a/go.mod +++ b/go.mod @@ -3,3 +3,10 @@ module apitester go 1.21.0 require github.com/iancoleman/orderedmap v0.3.0 + +require ( + github.com/fatih/color v1.16.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + golang.org/x/sys v0.14.0 // indirect +) diff --git a/go.sum b/go.sum index 3300bf6..b26d3eb 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,13 @@ +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc= github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/load_input.go b/load_input.go index 52a3b42..f9193dd 100644 --- a/load_input.go +++ b/load_input.go @@ -56,30 +56,6 @@ func ReadInput(data []byte, testDefinitions *TestDefinitions) { 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 { @@ -255,12 +231,8 @@ func ReadBodyTestCases(input orderedmap.OrderedMap, result *BodyTests) { // Has Keys check if val, ok := input.Get("hasKeys"); ok { // This is an array not a map - hasKeysArray := val.([]interface{}) result.HasKeys = OrderedToStringSlice(hasKeysArray) - - //equalsMap := val.(orderedmap.OrderedMap) - //result.HasKeys = OrderedToStringMap(equalsMap) } // TODO : remaining tests diff --git a/main.go b/main.go index c043213..27e64a6 100644 --- a/main.go +++ b/main.go @@ -53,12 +53,14 @@ func main() { if !success { fmt.Printf("Test result failed\n") os.Exit(1) + } else { + fmt.Printf("Tests successful\n") } } // For debugging, just dump the output - fmt.Printf("%+v\n", testDefinitions) + //fmt.Printf("%+v\n", testDefinitions) } // fileExists returns true if the specified file exists and is not a directory @@ -92,27 +94,16 @@ func OrderedToStringMap(input orderedmap.OrderedMap) map[string]string { func OrderedToStringSlice(input []interface{}) []string { result := []string{} - //vs := testsInterface.([]interface{}) - //fmt.Printf("Listing %d test definitions\n", len(vs)) for i := range input { - //fmt.Printf("Reading '%s'\n", input[i]) switch vType := input[i].(type) { + // TODO : more types case string: result = append(result, input[i].(string)) default: fmt.Printf("OrderedToStringSlice received unexpected value type, %T\n", vType) } - - /* - 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]) - } - */ } return result diff --git a/run_tests.go b/run_tests.go index 5d1a0b1..b24b602 100644 --- a/run_tests.go +++ b/run_tests.go @@ -25,7 +25,7 @@ func RunTest(testCase *TestCase) error { } client := &http.Client{Transport: tr} - fmt.Printf("Running test '%s'\n", testCase.Name) + fmt.Printf("\nRunning %s : %s\n", testCase.Name, testCase.Description) // Determine URL if len(testDefinitions.BaseUrl) > 0 { @@ -50,8 +50,11 @@ func RunTest(testCase *TestCase) error { requestType = testCase.Method } + fmt.Printf("Preparing %s for endpoint %s\n", requestType, requestUrl) + // Create request if len(testCase.Body) > 0 { + fmt.Printf("Adding body to request : %s\n", testCase.Body) req, err = http.NewRequest(requestType, requestUrl, bytes.NewBuffer(testCase.Body)) } else { req, err = http.NewRequest(requestType, requestUrl, nil) @@ -87,6 +90,7 @@ func RunTest(testCase *TestCase) error { } // Perform request + fmt.Printf("Sending request\n") resp, err := client.Do(req) if err != nil { fmt.Println(err) @@ -98,7 +102,7 @@ func RunTest(testCase *TestCase) error { testCase.ResultStatusCode = resp.StatusCode testCase.ResultHeaders = resp.Header - fmt.Printf("http_status:\n'%+v'\n", testCase.ResultStatusCode) + fmt.Printf("Status Code: %s\n", resp.Status) fmt.Printf("Header response:\n%+v\n", testCase.ResultHeaders) body, err := io.ReadAll(resp.Body) diff --git a/tests.json b/tests.json index 44b7a69..77d3c68 100644 --- a/tests.json +++ b/tests.json @@ -22,6 +22,80 @@ } }, "test2": { + "path": "/api/admin/user/register", + "method": "POST", + "description": "register user account", + "header": { + "Authorization": "Bearer %ACCESS_TOKEN%" + }, + "body": { + "username": "test", + "password": "test" + }, + "expect": { + "header": { + "contains": { + "http_status": "200" + } + }, + "body": { + "contains": { + "message": "registration success" + } + } + } + }, + "test3": { + "path": "/api/admin/users", + "method": "GET", + "description": "get user account listing", + "expect": { + "body": { + "contains": { + "userName": "test" + } + } + } + }, + "test4": { + "path": "/api/admin/user/delete", + "method": "POST", + "description": "delete user account", + "body": { + "userName": "test" + }, + "expect": { + "header": { + "contains": { + "http_status": "200" + } + }, + "body": { + "contains": { + "message": "user deletion success" + } + } + } + }, + "test10": { + "path": "/api/secret/store", + "method": "POST", + "description": "store new secret", + "body": { + "deviceName": "adcp01.cdc.home", + "deviceCategory": "windows-server", + "userName": "dummy@cdc.home", + "secretValue": "TheAlphabet" + }, + "expect": { + "header": { + "contains": { + "http_status": "200" + } + } + } + }, + "test99": { "path": "/api/secret/list", "method": "GET", "description": "List secrets", @@ -47,7 +121,8 @@ "url": "https://10.63.39.130:8443", "insecure": true, "header": { - "Content-Type": "application/json" + "Content-Type": "application/json", + "Authorization": "Bearer %ACCESS_TOKEN%" }, "capture": { "test1": {