From d341bfd7214b6fc0e381d650dc3fc4fe0bf5c91c Mon Sep 17 00:00:00 2001 From: Philipp Hempel Date: Mon, 28 Oct 2024 12:17:10 +0100 Subject: [PATCH] - new response field header_flat: - map of strings with (concatenated) header values - contains the same values as the header, but not in arrays - :control can be used on different header values - removed unused body:control and header:control from code, check was never implemented see #73980 --- api_testcase.go | 5 +- api_testsuite.go | 2 + pkg/lib/api/request.go | 2 +- pkg/lib/api/response.go | 84 +++++++++++------------- pkg/lib/api/response_test.go | 6 +- pkg/lib/template/template_loader_test.go | 3 +- test/control/header/manifest.json | 15 ----- test/datastore/check.json | 6 +- test/response/header/manifest.json | 84 ++++++++++++++++++++++++ 9 files changed, 135 insertions(+), 72 deletions(-) create mode 100644 test/response/header/manifest.json diff --git a/api_testcase.go b/api_testcase.go index 4532e22d..42bdd6ce 100644 --- a/api_testcase.go +++ b/api_testcase.go @@ -50,6 +50,7 @@ type Case struct { dataStore *datastore.Datastore standardHeader map[string]*string + headerFlat map[string]*string standardHeaderFromStore map[string]string ServerURL string `json:"server_url"` @@ -471,7 +472,7 @@ func (testCase Case) loadRequest() (api.Request, error) { func (testCase Case) loadExpectedResponse() (res api.Response, err error) { // unspecified response is interpreted as status_code 200 if testCase.ResponseData == nil { - return api.NewResponse(http.StatusOK, nil, nil, nil, nil, nil, res.Format) + return api.NewResponse(http.StatusOK, nil, nil, nil, nil, res.Format) } spec, err := testCase.loadResponseSerialization(testCase.ResponseData) if err != nil { @@ -489,7 +490,7 @@ func (testCase Case) responsesEqual(expected, got api.Response) (compare.Compare if err != nil { return compare.CompareResult{}, fmt.Errorf("error loading expected generic json: %s", err) } - if len(expected.Body) == 0 && len(expected.BodyControl) == 0 { + if len(expected.Body) == 0 { expected.Format.IgnoreBody = true } else { expected.Format.IgnoreBody = false diff --git a/api_testsuite.go b/api_testsuite.go index e3c2582d..fc4b5f3c 100644 --- a/api_testsuite.go +++ b/api_testsuite.go @@ -44,6 +44,7 @@ type Suite struct { Store map[string]any `json:"store"` StandardHeader map[string]*string `yaml:"header" json:"header"` + HeaderFlat map[string]*string `yaml:"header_flat" json:"header_flat"` StandardHeaderFromStore map[string]string `yaml:"header_from_store" json:"header_from_store"` Config TestToolConfig @@ -399,6 +400,7 @@ func (ats *Suite) runLiteralTest( test.index = index test.dataStore = ats.datastore test.standardHeader = ats.StandardHeader + test.headerFlat = ats.HeaderFlat test.standardHeaderFromStore = ats.StandardHeaderFromStore if test.LogNetwork == nil { test.LogNetwork = &ats.Config.LogNetwork diff --git a/pkg/lib/api/request.go b/pkg/lib/api/request.go index 5d3e1d8a..a3a4619c 100755 --- a/pkg/lib/api/request.go +++ b/pkg/lib/api/request.go @@ -347,7 +347,7 @@ func (request Request) Send() (response Response, err error) { if err != nil { return response, err } - response, err = NewResponse(httpResponse.StatusCode, header, nil, httpResponse.Cookies(), httpResponse.Body, nil, ResponseFormat{}) + response, err = NewResponse(httpResponse.StatusCode, header, nil, httpResponse.Cookies(), httpResponse.Body, ResponseFormat{}) if err != nil { return response, fmt.Errorf("error constructing response from http response") } diff --git a/pkg/lib/api/response.go b/pkg/lib/api/response.go index 3f69a960..1d095c26 100755 --- a/pkg/lib/api/response.go +++ b/pkg/lib/api/response.go @@ -21,32 +21,28 @@ import ( ) type Response struct { - StatusCode int - Headers map[string]any - headerControl util.JsonObject - Cookies []*http.Cookie - Body []byte - BodyControl util.JsonObject - Format ResponseFormat + StatusCode int + Headers map[string]any + HeaderFlat map[string]any + Cookies []*http.Cookie + Body []byte + Format ResponseFormat ReqDur time.Duration BodyLoadDur time.Duration } -func (res Response) NeedsCheck() bool { - if res.StatusCode != http.StatusOK { - return true - } - if len(res.Headers) > 0 || len(res.Cookies) > 0 || len(res.Body) > 0 || len(res.BodyControl) > 0 { - return true - } - return false -} - -func (res Response) SerializeHeaders() (headers map[string]any, err error) { +func (res Response) SerializeHeaderFlat() (headers map[string]any, err error) { headers = map[string]any{} for k, h := range res.Headers { - headers[k] = h + switch v := h.(type) { + case string: + headers[k] = h + case nil: + headers[k] = "" + case []string: + headers[k] = strings.Join(v, "; ") + } } return headers, nil } @@ -73,13 +69,12 @@ type Cookie struct { } type ResponseSerialization struct { - StatusCode int `yaml:"statuscode" json:"statuscode"` - Headers map[string]any `yaml:"header" json:"header,omitempty"` - HeaderControl util.JsonObject `yaml:"header:control" json:"header:control,omitempty"` - Cookies map[string]Cookie `yaml:"cookie" json:"cookie,omitempty"` - Body any `yaml:"body" json:"body,omitempty"` - BodyControl util.JsonObject `yaml:"body:control" json:"body:control,omitempty"` - Format ResponseFormat `yaml:"format" json:"format,omitempty"` + StatusCode int `yaml:"statuscode" json:"statuscode"` + Headers map[string]any `yaml:"header" json:"header,omitempty"` + HeaderFlat map[string]any `yaml:"header_flat" json:"header_flat,omitempty"` + Cookies map[string]Cookie `yaml:"cookie" json:"cookie,omitempty"` + Body any `yaml:"body" json:"body,omitempty"` + Format ResponseFormat `yaml:"format" json:"format,omitempty"` } type ResponseFormat struct { @@ -91,14 +86,19 @@ type ResponseFormat struct { PreProcess *PreProcess `json:"pre_process,omitempty"` } -func NewResponse(statusCode int, headers map[string]any, headerControl util.JsonObject, cookies []*http.Cookie, body io.Reader, bodyControl util.JsonObject, bodyFormat ResponseFormat) (res Response, err error) { +func NewResponse(statusCode int, + headers map[string]any, + headerFlat map[string]any, + cookies []*http.Cookie, + body io.Reader, + bodyFormat ResponseFormat, +) (res Response, err error) { res = Response{ - StatusCode: statusCode, - Headers: headers, - Cookies: cookies, - BodyControl: bodyControl, - headerControl: headerControl, - Format: bodyFormat, + StatusCode: statusCode, + Headers: headers, + HeaderFlat: headerFlat, + Cookies: cookies, + Format: bodyFormat, } if body != nil { start := time.Now() @@ -144,7 +144,7 @@ func NewResponseFromSpec(spec ResponseSerialization) (res Response, err error) { } } - return NewResponse(spec.StatusCode, spec.Headers, spec.HeaderControl, cookies, body, spec.BodyControl, spec.Format) + return NewResponse(spec.StatusCode, spec.Headers, spec.HeaderFlat, cookies, body, spec.Format) } // ServerResponseToGenericJSON parse response from server. convert xml, csv, binary to json if necessary @@ -214,13 +214,14 @@ func (response Response) ServerResponseToGenericJSON(responseFormat ResponseForm return res, fmt.Errorf("Invalid response format '%s'", responseFormat.Type) } - headers, err := resp.SerializeHeaders() + headerFlat, err := resp.SerializeHeaderFlat() if err != nil { return res, err } responseJSON := ResponseSerialization{ StatusCode: resp.StatusCode, - Headers: headers, + Headers: resp.Headers, + HeaderFlat: headerFlat, } // Build cookies map from standard bag if len(resp.Cookies) > 0 { @@ -284,15 +285,10 @@ func (response Response) ToGenericJSON() (any, error) { } } - headers, err := response.SerializeHeaders() - if err != nil { - return res, err - } responseJSON := ResponseSerialization{ - StatusCode: response.StatusCode, - BodyControl: response.BodyControl, - Headers: headers, - HeaderControl: response.headerControl, + StatusCode: response.StatusCode, + Headers: response.Headers, + HeaderFlat: response.HeaderFlat, } // Build cookies map from standard bag diff --git a/pkg/lib/api/response_test.go b/pkg/lib/api/response_test.go index 4c0fd074..8767e674 100644 --- a/pkg/lib/api/response_test.go +++ b/pkg/lib/api/response_test.go @@ -73,7 +73,7 @@ func TestResponse_NewResponseFromSpec_StatusCode_not_set(t *testing.T) { } func TestResponse_NewResponse(t *testing.T) { - response, err := NewResponse(200, nil, nil, nil, strings.NewReader("foo"), nil, ResponseFormat{}) + response, err := NewResponse(200, nil, nil, nil, strings.NewReader("foo"), ResponseFormat{}) go_test_utils.ExpectNoError(t, err, "unexpected error") go_test_utils.AssertIntEquals(t, response.StatusCode, 200) } @@ -86,7 +86,7 @@ func TestResponse_String(t *testing.T) { } }` - response, err := NewResponse(200, nil, nil, nil, strings.NewReader(requestString), nil, ResponseFormat{}) + response, err := NewResponse(200, nil, nil, nil, strings.NewReader(requestString), ResponseFormat{}) go_test_utils.ExpectNoError(t, err, "error constructing response") assertString := "200\n\n\n" + requestString @@ -119,7 +119,7 @@ func TestResponse_Cookies(t *testing.T) { if err != nil { t.Fatal(err) } - response, err := NewResponse(res.StatusCode, header, nil, res.Cookies(), res.Body, nil, ResponseFormat{}) + response, err := NewResponse(res.StatusCode, header, nil, res.Cookies(), res.Body, ResponseFormat{}) if err != nil { t.Fatal(err) } diff --git a/pkg/lib/template/template_loader_test.go b/pkg/lib/template/template_loader_test.go index c3b839f7..485cb9b6 100644 --- a/pkg/lib/template/template_loader_test.go +++ b/pkg/lib/template/template_loader_test.go @@ -82,7 +82,7 @@ func TestBigIntRender(t *testing.T) { inputNumber := "132132132182323" - resp, _ := api.NewResponse(200, nil, nil, nil, strings.NewReader(fmt.Sprintf(`{"bigINT":%s}`, inputNumber)), nil, api.ResponseFormat{}) + resp, _ := api.NewResponse(200, nil, nil, nil, strings.NewReader(fmt.Sprintf(`{"bigINT":%s}`, inputNumber)), api.ResponseFormat{}) respJson, _ := resp.ServerResponseToJsonString(false) store.SetWithQjson(respJson, map[string]string{"testINT": "body.bigINT"}) @@ -415,7 +415,6 @@ func Test_DataStore_QJson(t *testing.T) { "flob" ] }`), - nil, api.ResponseFormat{}, ) store := datastore.NewStore(false) diff --git a/test/control/header/manifest.json b/test/control/header/manifest.json index 644b1ef7..a612b377 100644 --- a/test/control/header/manifest.json +++ b/test/control/header/manifest.json @@ -43,21 +43,6 @@ } } }, - { - "name": "check HTTP header using control, use reverse_test_result", - "request": { - "server_url": "http://localhost:9999", - "endpoint": "bounce-json", - "method": "POST" - }, - "response": { - // check number of HTTP headers, should always be > 0 - "header:control": { - "element_count": 0 - } - }, - "reverse_test_result": true - }, { "name": "check value in HTTP header using control, use reverse_test_result", "request": { diff --git a/test/datastore/check.json b/test/datastore/check.json index 40de596c..4cca4797 100644 --- a/test/datastore/check.json +++ b/test/datastore/check.json @@ -18,11 +18,7 @@ {"some": "data"}, {"some": ["more", "data"]}, {"some": "data"} - ], - "body:control": { - "order_matters": true, - "no_extra": true - } + ] } } } diff --git a/test/response/header/manifest.json b/test/response/header/manifest.json new file mode 100644 index 00000000..4771746f --- /dev/null +++ b/test/response/header/manifest.json @@ -0,0 +1,84 @@ +{ + "http_server": { + "addr": ":9999", + "dir": "../_res", + "testmode": false + }, + "name": "response header: format header_flat", + "tests": [ + { + "name": "test response header_flat", + "request": { + "server_url": "http://localhost:9999", + "endpoint": "bounce-json", + "method": "POST" + }, + "response": { + "statuscode": 200, + "header": { + "Content-Type": [ + "text/plain; charset=utf-8" + ] + }, + "header_flat": { + "Content-Type": "text/plain; charset=utf-8" + } + } + }, + { + "name": "test response header_flat (check for a failing test with a reverse result)", + "reverse_test_result": true, + "request": { + "server_url": "http://localhost:9999", + "endpoint": "bounce-json", + "method": "POST" + }, + "response": { + "statuscode": 200, + "header_flat": { + "Content-Length": "foo", + "Content-Type": "bar" + } + } + }, + { + "name": "test response header_flat: control for header values", + "request": { + "server_url": "http://localhost:9999", + "endpoint": "bounce-json", + "method": "POST" + }, + "response": { + "statuscode": 200, + "header_flat": { + "Content-Length:control": { + "match": "^\\d+$" + }, + "Content-Type:control": { + "match": "^text/plain;.*" + } + } + } + }, + { + "name": "test response header_flat: control for header values (check for a failing test with a reverse result)", + "reverse_test_result": true, + "request": { + "server_url": "http://localhost:9999", + "endpoint": "bounce-json", + "method": "POST" + }, + "response": { + "statuscode": 200, + "header_flat": { + "Content-Length:control": { + "match": "foo" + }, + "Content-Type:control": { + "match": "bar" + } + } + } + } + ] +} \ No newline at end of file