From 499a6df594dceca78057723b673eb782a718d503 Mon Sep 17 00:00:00 2001 From: Nathan Coad Date: Wed, 3 Jan 2024 09:49:09 +1100 Subject: [PATCH] split code into multiple files --- check_results.go | 6 + load_input.go | 256 ++++++++++++++++++++++ main.go | 539 ++--------------------------------------------- run_tests.go | 169 +++++++++++++++ structs.go | 68 ++++++ 5 files changed, 513 insertions(+), 525 deletions(-) create mode 100644 check_results.go create mode 100644 load_input.go create mode 100644 run_tests.go create mode 100644 structs.go diff --git a/check_results.go b/check_results.go new file mode 100644 index 0000000..678930e --- /dev/null +++ b/check_results.go @@ -0,0 +1,6 @@ +package main + +func CheckResults(testCase *TestCase) (bool, error) { + + return true, nil +} diff --git a/load_input.go b/load_input.go new file mode 100644 index 0000000..97e247b --- /dev/null +++ b/load_input.go @@ -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 +} diff --git a/main.go b/main.go index b0ecd93..c8dcd7e 100644 --- a/main.go +++ b/main.go @@ -1,287 +1,17 @@ package main import ( - "bytes" - "crypto/tls" - "encoding/json" - "errors" "flag" "fmt" - "io" - "net/http" - "net/url" "os" "strconv" - "strings" "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 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() { var s []byte var err error @@ -312,222 +42,27 @@ func main() { // Process the input json ReadInput(s, testDefinitions) - // For debugging, just dump the output - fmt.Printf("%+v\n", testDefinitions) - // Perform specified test cases for i := range testDefinitions.TestCases { 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) { - 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 { - 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) +// 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 OrderedToStringMap(input orderedmap.OrderedMap) map[string]string { @@ -549,49 +84,3 @@ func OrderedToStringMap(input orderedmap.OrderedMap) map[string]string { 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 -} diff --git a/run_tests.go b/run_tests.go new file mode 100644 index 0000000..a651b50 --- /dev/null +++ b/run_tests.go @@ -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 +} diff --git a/structs.go b/structs.go new file mode 100644 index 0000000..b2897de --- /dev/null +++ b/structs.go @@ -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 +}