From 665d9fa9432dd73f566d060a7d5c3a9d988c064a Mon Sep 17 00:00:00 2001 From: Uncle-Justice <40391338+Uncle-Justice@users.noreply.github.com> Date: Tue, 16 Jan 2024 11:43:30 +0800 Subject: [PATCH] test: add testcases for CompareRequest&Response (#778) --- test/e2e/conformance/utils/http/http.go | 33 +- test/e2e/conformance/utils/http/http_test.go | 1103 ++++++++++++++++++ 2 files changed, 1123 insertions(+), 13 deletions(-) create mode 100644 test/e2e/conformance/utils/http/http_test.go diff --git a/test/e2e/conformance/utils/http/http.go b/test/e2e/conformance/utils/http/http.go index ce3e56647..38e8b9a2f 100644 --- a/test/e2e/conformance/utils/http/http.go +++ b/test/e2e/conformance/utils/http/http.go @@ -318,7 +318,7 @@ func WaitForConsistentResponse(t *testing.T, r roundtripper.RoundTripper, req ro return false } } else { - t.Logf("invalid CompareTarget: %v please set it Request or Response", expected.Meta.CompareTarget, err, elapsed) + t.Logf("invalid CompareTarget: %v please set it CompareTargetRequest or CompareTargetResponse", expected.Meta.CompareTarget) return false } @@ -390,13 +390,15 @@ func CompareRequest(req *roundtripper.Request, cReq *roundtripper.CapturedReques cTyp := cReq.Headers["Content-Type"][0] if eTyp != cTyp { - return fmt.Errorf("expected %s Content-Type to be set, got %s", expected.Request.ExpectedRequest.ContentType, cReq.Headers["Content-Type"][0]) + if !(eTyp == ContentTypeMultipartForm && strings.Contains(cTyp, eTyp)) { + return fmt.Errorf("expected %s Content-Type to be set, got %s", eTyp, cTyp) + } } var ok bool - switch cTyp { + switch eTyp { case ContentTypeTextPlain: if string(expected.Request.ExpectedRequest.Body) != cReq.Body.(string) { - return fmt.Errorf("expected %s body to be %s, got %s##", cTyp, string(expected.Request.ExpectedRequest.Body), cReq.Body.(string)) + return fmt.Errorf("expected %s body to be %s, got %s", eTyp, string(expected.Request.ExpectedRequest.Body), cReq.Body.(string)) } case ContentTypeApplicationJson: var eReqBody map[string]interface{} @@ -408,12 +410,13 @@ func CompareRequest(req *roundtripper.Request, cReq *roundtripper.CapturedReques } if cReqBody, ok = cReq.Body.(map[string]interface{}); !ok { - return fmt.Errorf("failed to parse CapturedRequest body %s, %s", string(cReq.Body.([]byte)), err.Error()) + return fmt.Errorf("failed to parse CapturedRequest body") } if !reflect.DeepEqual(eReqBody, cReqBody) { - b, _ := json.Marshal(cReqBody) - return fmt.Errorf("expected %s body to be %s, got %s", cTyp, string(expected.Request.ExpectedRequest.Body), string(b)) + eRBJson, _ := json.Marshal(eReqBody) + cRBJson, _ := json.Marshal(cReqBody) + return fmt.Errorf("expected %s body to be %s, got result: %s", eTyp, string(eRBJson), string(cRBJson)) } case ContentTypeFormUrlencoded: var eReqBody map[string][]string @@ -424,11 +427,13 @@ func CompareRequest(req *roundtripper.Request, cReq *roundtripper.CapturedReques } if cReqBody, ok = cReq.Body.(map[string][]string); !ok { - return fmt.Errorf("failed to parse CapturedRequest body %s, %s", string(cReq.Body.([]byte)), err.Error()) + return fmt.Errorf("failed to parse CapturedRequest body") } if !reflect.DeepEqual(eReqBody, cReqBody) { - return fmt.Errorf("expected %s body to be %s, got %s", cTyp, string(expected.Request.ExpectedRequest.Body), string(cReq.Body.([]byte))) + eRBJson, _ := json.Marshal(eReqBody) + cRBJson, _ := json.Marshal(cReqBody) + return fmt.Errorf("expected %s body to be %s, got result: %s", eTyp, string(eRBJson), string(cRBJson)) } case ContentTypeMultipartForm: var eReqBody map[string][]string @@ -439,14 +444,16 @@ func CompareRequest(req *roundtripper.Request, cReq *roundtripper.CapturedReques return fmt.Errorf("failed to parse ExpectedRequest body %s, %s", string(expected.Request.ExpectedRequest.Body), err.Error()) } if cReqBody, ok = cReq.Body.(map[string][]string); !ok { - return fmt.Errorf("failed to parse CapturedRequest body %s, %s", string(cReq.Body.([]byte)), err.Error()) + return fmt.Errorf("failed to parse CapturedRequest body") } if !reflect.DeepEqual(eReqBody, cReqBody) { - return fmt.Errorf("expected %s body to be %s, got %s", cTyp, string(expected.Request.ExpectedRequest.Body), string(cReq.Body.([]byte))) + eRBJson, _ := json.Marshal(eReqBody) + cRBJson, _ := json.Marshal(cReqBody) + return fmt.Errorf("expected %s body to be %s, got result: %s", eTyp, string(eRBJson), string(cRBJson)) } default: - return fmt.Errorf("Content-Type: %s invalid or not support.", cTyp) + return fmt.Errorf("Content-Type: %s invalid or not support.", eTyp) } } if expected.Response.ExpectedResponse.Headers != nil { @@ -590,7 +597,7 @@ func CompareResponse(cRes *roundtripper.CapturedResponse, expected Assertion) er switch cTyp { case ContentTypeTextPlain: if !bytes.Equal(expected.Response.ExpectedResponse.Body, cRes.Body) { - return fmt.Errorf("expected %s body to be %s, got %s##", cTyp, string(expected.Response.ExpectedResponse.Body), string(cRes.Body)) + return fmt.Errorf("expected %s body to be %s, got %s", cTyp, string(expected.Response.ExpectedResponse.Body), string(cRes.Body)) } case ContentTypeApplicationJson: eResBody := make(map[string]interface{}) diff --git a/test/e2e/conformance/utils/http/http_test.go b/test/e2e/conformance/utils/http/http_test.go new file mode 100644 index 000000000..e742c08e8 --- /dev/null +++ b/test/e2e/conformance/utils/http/http_test.go @@ -0,0 +1,1103 @@ +// Copyright (c) 2022 Alibaba Group Holding Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package http + +import ( + "testing" + + "github.com/alibaba/higress/test/e2e/conformance/utils/roundtripper" + "github.com/stretchr/testify/require" +) + +func TestCompareRequest(t *testing.T) { + cases := []struct { + caseName string + errMsg string + req *roundtripper.Request + cReq *roundtripper.CapturedRequest + cRes *roundtripper.CapturedResponse + expected Assertion + }{ + { + caseName: "compare request header ok", + req: &roundtripper.Request{}, + cReq: &roundtripper.CapturedRequest{ + Path: "/", + Host: "foo.com", + Method: "GET", + Headers: map[string][]string{ + "X-header-test1": {"h1"}, + "X-header-test2": {"h2", "h22"}, + }, + Body: []byte(``), + Namespace: "", + Pod: "", + }, + cRes: &roundtripper.CapturedResponse{ + StatusCode: 200, + Headers: map[string][]string{ + "X-header-test1": {"h1"}, + "X-header-test2": {"h2", "h22"}, + }, + Body: []byte(``), + }, + expected: Assertion{ + Meta: AssertionMeta{TestCaseName: "", TargetBackend: "", TargetNamespace: "", CompareTarget: CompareTargetRequest}, + Request: AssertionRequest{ + ActualRequest: Request{}, + ExpectedRequest: &ExpectedRequest{ + Request: Request{ + Host: "foo.com", + Method: "GET", + Path: "/", + Headers: map[string]string{ + "X-header-test1": "h1", + "X-header-test2": "h2,h22", + }, + Body: []byte(``), + ContentType: "", + }, + AbsentHeaders: []string{}, + }, + }, + Response: AssertionResponse{ + ExpectedResponse: Response{ + StatusCode: 200, + Headers: map[string]string{}, + AbsentHeaders: []string{}, + }, + AdditionalResponseHeaders: map[string]string{}, + ExpectedResponseNoRequest: false, + }, + }, + }, + { + caseName: "compare request header&body ok", + req: &roundtripper.Request{}, + cReq: &roundtripper.CapturedRequest{ + Path: "/", + Host: "foo.com", + Method: "GET", + Headers: map[string][]string{ + "X-header-test1": {"h1"}, + "X-header-test2": {"h2", "h22"}, + "Content-Type": {"application/json"}, + }, + Body: map[string]interface{}{ + "X-body-test1": []interface{}{"b1"}, + "X-body-test2": []interface{}{"b2", "b22"}, + }, + Namespace: "", + Pod: "", + }, + cRes: &roundtripper.CapturedResponse{ + StatusCode: 200, + Headers: map[string][]string{ + "X-header-test1": {"h1"}, + "X-header-test2": {"h2", "h22"}, + }, + }, + expected: Assertion{ + Meta: AssertionMeta{TestCaseName: "", TargetBackend: "", TargetNamespace: "", CompareTarget: CompareTargetRequest}, + Request: AssertionRequest{ + ActualRequest: Request{}, + ExpectedRequest: &ExpectedRequest{ + Request: Request{ + Host: "foo.com", + Method: "GET", + Path: "/", + Headers: map[string]string{ + "X-header-test1": "h1", + "X-header-test2": "h2,h22", + }, + Body: []byte(` + { + "X-body-test1":["b1"], + "X-body-test2":["b2","b22"] + }`), + ContentType: ContentTypeApplicationJson, + }, + AbsentHeaders: []string{}, + }, + }, + Response: AssertionResponse{ + ExpectedResponse: Response{ + StatusCode: 200, + Headers: map[string]string{}, + AbsentHeaders: []string{}, + }, + AdditionalResponseHeaders: map[string]string{}, + ExpectedResponseNoRequest: false, + }, + }, + }, + { + caseName: "compare request header&body fail, because headers not consistent", + errMsg: "expected X-header-test1 header to be set to h1, got h1,hn", + req: &roundtripper.Request{}, + cReq: &roundtripper.CapturedRequest{ + Path: "/", + Host: "foo.com", + Method: "GET", + Headers: map[string][]string{ + "X-header-test1": {"h1", "hn"}, + "X-header-test2": {"h2", "h22"}, + "Content-Type": {"application/json"}, + }, + Body: map[string]interface{}{ + "X-body-test1": []interface{}{"b1"}, + "X-body-test2": []interface{}{"b2", "b22"}, + }, + Namespace: "", + Pod: "", + }, + cRes: &roundtripper.CapturedResponse{ + StatusCode: 200, + Headers: map[string][]string{ + "X-header-test1": {"h1"}, + "X-header-test2": {"h2", "h22"}, + }, + }, + expected: Assertion{ + Meta: AssertionMeta{TestCaseName: "", TargetBackend: "", TargetNamespace: "", CompareTarget: CompareTargetRequest}, + Request: AssertionRequest{ + ActualRequest: Request{}, + ExpectedRequest: &ExpectedRequest{ + Request: Request{ + Host: "foo.com", + Method: "GET", + Path: "/", + Headers: map[string]string{ + "X-header-test1": "h1", + "X-header-test2": "h2,h22", + }, + Body: []byte(` + { + "X-body-test1":["b1"], + "X-body-test2":["b2","b22"] + }`), + ContentType: ContentTypeApplicationJson, + }, + AbsentHeaders: []string{}, + }, + }, + Response: AssertionResponse{ + ExpectedResponse: Response{ + StatusCode: 200, + Headers: map[string]string{}, + AbsentHeaders: []string{}, + }, + AdditionalResponseHeaders: map[string]string{}, + ExpectedResponseNoRequest: false, + }, + }, + }, + { + caseName: "compare request header&body fail, because body not consistent", + errMsg: "expected application/json body to be", + req: &roundtripper.Request{}, + cReq: &roundtripper.CapturedRequest{ + Path: "/", + Host: "foo.com", + Method: "GET", + Headers: map[string][]string{ + "X-header-test1": {"h1"}, + "X-header-test2": {"h2", "h22"}, + "Content-Type": {"application/json"}, + }, + Body: map[string]interface{}{ + "X-body-test1": []interface{}{"b1", "bn"}, + "X-body-test2": []interface{}{"b2", "b22"}, + }, + Namespace: "", + Pod: "", + }, + cRes: &roundtripper.CapturedResponse{ + StatusCode: 200, + Headers: map[string][]string{ + "X-header-test1": {"h1"}, + "X-header-test2": {"h2", "h22"}, + }, + }, + expected: Assertion{ + Meta: AssertionMeta{TestCaseName: "", TargetBackend: "", TargetNamespace: "", CompareTarget: CompareTargetRequest}, + Request: AssertionRequest{ + ActualRequest: Request{}, + ExpectedRequest: &ExpectedRequest{ + Request: Request{ + Host: "foo.com", + Method: "GET", + Path: "/", + Headers: map[string]string{ + "X-header-test1": "h1", + "X-header-test2": "h2,h22", + }, + Body: []byte(` + { + "X-body-test1":["b1"], + "X-body-test2":["b2","b22"] + }`), + ContentType: ContentTypeApplicationJson, + }, + AbsentHeaders: []string{}, + }, + }, + Response: AssertionResponse{ + ExpectedResponse: Response{ + StatusCode: 200, + Headers: map[string]string{}, + AbsentHeaders: []string{}, + }, + AdditionalResponseHeaders: map[string]string{}, + ExpectedResponseNoRequest: false, + }, + }, + }, + { + caseName: "compare request header&body ok, body type is text/plain", + req: &roundtripper.Request{}, + cReq: &roundtripper.CapturedRequest{ + Path: "/", + Host: "foo.com", + Method: "GET", + Headers: map[string][]string{ + "X-header-test1": {"h1"}, + "X-header-test2": {"h2", "h22"}, + "Content-Type": {"text/plain"}, + }, + Body: "hello higress", + Namespace: "", + Pod: "", + }, + cRes: &roundtripper.CapturedResponse{ + StatusCode: 200, + Headers: map[string][]string{ + "X-header-test1": {"h1"}, + "X-header-test2": {"h2", "h22"}, + }, + }, + expected: Assertion{ + Meta: AssertionMeta{TestCaseName: "", TargetBackend: "", TargetNamespace: "", CompareTarget: CompareTargetRequest}, + Request: AssertionRequest{ + ActualRequest: Request{}, + ExpectedRequest: &ExpectedRequest{ + Request: Request{ + Host: "foo.com", + Method: "GET", + Path: "/", + Headers: map[string]string{ + "X-header-test1": "h1", + "X-header-test2": "h2,h22", + }, + Body: []byte(`hello higress`), + ContentType: ContentTypeTextPlain, + }, + AbsentHeaders: []string{}, + }, + }, + Response: AssertionResponse{ + ExpectedResponse: Response{ + StatusCode: 200, + Headers: map[string]string{}, + AbsentHeaders: []string{}, + }, + AdditionalResponseHeaders: map[string]string{}, + ExpectedResponseNoRequest: false, + }, + }, + }, + { + caseName: "compare request header&body fail, because body not consistent. body type is text/plain", + errMsg: "expected text/plain body to be", + req: &roundtripper.Request{}, + cReq: &roundtripper.CapturedRequest{ + Path: "/", + Host: "foo.com", + Method: "GET", + Headers: map[string][]string{ + "X-header-test1": {"h1"}, + "X-header-test2": {"h2", "h22"}, + "Content-Type": {"text/plain"}, + }, + Body: "Hello Higress", + Namespace: "", + Pod: "", + }, + cRes: &roundtripper.CapturedResponse{ + StatusCode: 200, + Headers: map[string][]string{ + "X-header-test1": {"h1"}, + "X-header-test2": {"h2", "h22"}, + }, + }, + expected: Assertion{ + Meta: AssertionMeta{TestCaseName: "", TargetBackend: "", TargetNamespace: "", CompareTarget: CompareTargetRequest}, + Request: AssertionRequest{ + ActualRequest: Request{}, + ExpectedRequest: &ExpectedRequest{ + Request: Request{ + Host: "foo.com", + Method: "GET", + Path: "/", + Headers: map[string]string{ + "X-header-test1": "h1", + "X-header-test2": "h2,h22", + }, + Body: []byte(`hello higress`), + ContentType: ContentTypeTextPlain, + }, + AbsentHeaders: []string{}, + }, + }, + Response: AssertionResponse{ + ExpectedResponse: Response{ + StatusCode: 200, + Headers: map[string]string{}, + AbsentHeaders: []string{}, + }, + AdditionalResponseHeaders: map[string]string{}, + ExpectedResponseNoRequest: false, + }, + }, + }, + { + caseName: "compare request header&body ok, body type is FormUrlencoded", + req: &roundtripper.Request{}, + cReq: &roundtripper.CapturedRequest{ + Path: "/", + Host: "foo.com", + Method: "GET", + Headers: map[string][]string{ + "X-header-test1": {"h1"}, + "X-header-test2": {"h2", "h22"}, + "Content-Type": {"application/x-www-form-urlencoded"}, + }, + Body: map[string][]string{ + "X-body-test1": {"b1"}, + "X-body-test2": {"b2", "b22"}, + }, + Namespace: "", + Pod: "", + }, + cRes: &roundtripper.CapturedResponse{ + StatusCode: 200, + Headers: map[string][]string{ + "X-header-test1": {"h1"}, + "X-header-test2": {"h2", "h22"}, + }, + }, + expected: Assertion{ + Meta: AssertionMeta{TestCaseName: "", TargetBackend: "", TargetNamespace: "", CompareTarget: CompareTargetRequest}, + Request: AssertionRequest{ + ActualRequest: Request{}, + ExpectedRequest: &ExpectedRequest{ + Request: Request{ + Host: "foo.com", + Method: "GET", + Path: "/", + Headers: map[string]string{ + "X-header-test1": "h1", + "X-header-test2": "h2,h22", + }, + Body: []byte(`X-body-test1=b1&X-body-test2=b2&X-body-test2=b22`), + ContentType: ContentTypeFormUrlencoded, + }, + AbsentHeaders: []string{}, + }, + }, + Response: AssertionResponse{ + ExpectedResponse: Response{ + StatusCode: 200, + Headers: map[string]string{}, + AbsentHeaders: []string{}, + }, + AdditionalResponseHeaders: map[string]string{}, + ExpectedResponseNoRequest: false, + }, + }, + }, + { + caseName: "compare request header&body fail, because body not consistent, body type is FormUrlencoded", + errMsg: "expected application/x-www-form-urlencoded body to be", + req: &roundtripper.Request{}, + cReq: &roundtripper.CapturedRequest{ + Path: "/", + Host: "foo.com", + Method: "GET", + Headers: map[string][]string{ + "X-header-test1": {"h1"}, + "X-header-test2": {"h2", "h22"}, + "Content-Type": {"application/x-www-form-urlencoded"}, + }, + Body: map[string][]string{ + "X-body-test1": {"b1", "bn"}, + "X-body-test2": {"b2", "b22"}, + }, + Namespace: "", + Pod: "", + }, + cRes: &roundtripper.CapturedResponse{ + StatusCode: 200, + Headers: map[string][]string{ + "X-header-test1": {"h1"}, + "X-header-test2": {"h2", "h22"}, + }, + }, + expected: Assertion{ + Meta: AssertionMeta{TestCaseName: "", TargetBackend: "", TargetNamespace: "", CompareTarget: CompareTargetRequest}, + Request: AssertionRequest{ + ActualRequest: Request{}, + ExpectedRequest: &ExpectedRequest{ + Request: Request{ + Host: "foo.com", + Method: "GET", + Path: "/", + Headers: map[string]string{ + "X-header-test1": "h1", + "X-header-test2": "h2,h22", + }, + Body: []byte(`X-body-test1=b1&X-body-test2=b2&X-body-test2=b22`), + ContentType: ContentTypeFormUrlencoded, + }, + AbsentHeaders: []string{}, + }, + }, + Response: AssertionResponse{ + ExpectedResponse: Response{ + StatusCode: 200, + Headers: map[string]string{}, + AbsentHeaders: []string{}, + }, + AdditionalResponseHeaders: map[string]string{}, + ExpectedResponseNoRequest: false, + }, + }, + }, + { + caseName: "compare request header&body ok, body type is MultipartForm", + req: &roundtripper.Request{}, + cReq: &roundtripper.CapturedRequest{ + Path: "/", + Host: "foo.com", + Method: "GET", + Headers: map[string][]string{ + "X-header-test1": {"h1"}, + "X-header-test2": {"h2", "h22"}, + "Content-Type": {"multipart/form-data; boundary=----WebKitFormBoundaryAnydWsQ1ajKuGoCd"}, + }, + Body: map[string][]string{ + "name": {"denzel"}, + "flag": {"test"}, + }, + Namespace: "", + Pod: "", + }, + cRes: &roundtripper.CapturedResponse{ + StatusCode: 200, + Headers: map[string][]string{ + "X-header-test1": {"h1"}, + "X-header-test2": {"h2", "h22"}, + }, + }, + expected: Assertion{ + Meta: AssertionMeta{TestCaseName: "", TargetBackend: "", TargetNamespace: "", CompareTarget: CompareTargetRequest}, + Request: AssertionRequest{ + ActualRequest: Request{}, + ExpectedRequest: &ExpectedRequest{ + Request: Request{ + Host: "foo.com", + Method: "GET", + Path: "/", + Headers: map[string]string{ + "X-header-test1": "h1", + "X-header-test2": "h2,h22", + }, + Body: []byte( + "------WebKitFormBoundaryAnydWsQ1ajKuGoCd\r\n" + + "Content-Disposition: form-data; name=\"file\"; filename=\"Screenshot.png\"\r\n" + + "Content-Type: image/png\r\n\r\n" + + "------WebKitFormBoundaryAnydWsQ1ajKuGoCd\r\n" + + "Content-Disposition: form-data; name=\"name\"\r\n\r\n" + + "denzel\r\n" + + "------WebKitFormBoundaryAnydWsQ1ajKuGoCd\r\n" + + "Content-Disposition: form-data; name=\"flag\"\r\n\r\n" + + "test\r\n" + + "------WebKitFormBoundaryAnydWsQ1ajKuGoCd--\r\n"), + ContentType: ContentTypeMultipartForm + "; boundary=----WebKitFormBoundaryAnydWsQ1ajKuGoCd", + }, + AbsentHeaders: []string{}, + }, + }, + Response: AssertionResponse{ + ExpectedResponse: Response{ + StatusCode: 200, + Headers: map[string]string{}, + AbsentHeaders: []string{}, + }, + AdditionalResponseHeaders: map[string]string{}, + ExpectedResponseNoRequest: false, + }, + }, + }, + { + caseName: "compare request header&body fail, because body not consistent. body type is MultipartForm", + errMsg: "expected multipart/form-data body to be", + req: &roundtripper.Request{}, + cReq: &roundtripper.CapturedRequest{ + Path: "/", + Host: "foo.com", + Method: "GET", + Headers: map[string][]string{ + "X-header-test1": {"h1"}, + "X-header-test2": {"h2", "h22"}, + "Content-Type": {"multipart/form-data; boundary=----WebKitFormBoundaryAnydWsQ1ajKuGoCd"}, + }, + Body: map[string][]string{ + "name": {"higress"}, + "flag": {"test"}, + }, + Namespace: "", + Pod: "", + }, + cRes: &roundtripper.CapturedResponse{ + StatusCode: 200, + Headers: map[string][]string{ + "X-header-test1": {"h1"}, + "X-header-test2": {"h2", "h22"}, + }, + }, + expected: Assertion{ + Meta: AssertionMeta{TestCaseName: "", TargetBackend: "", TargetNamespace: "", CompareTarget: CompareTargetRequest}, + Request: AssertionRequest{ + ActualRequest: Request{}, + ExpectedRequest: &ExpectedRequest{ + Request: Request{ + Host: "foo.com", + Method: "GET", + Path: "/", + Headers: map[string]string{ + "X-header-test1": "h1", + "X-header-test2": "h2,h22", + }, + Body: []byte( + "------WebKitFormBoundaryAnydWsQ1ajKuGoCd\r\n" + + "Content-Disposition: form-data; name=\"file\"; filename=\"Screenshot.png\"\r\n" + + "Content-Type: image/png\r\n\r\n" + + "------WebKitFormBoundaryAnydWsQ1ajKuGoCd\r\n" + + "Content-Disposition: form-data; name=\"name\"\r\n\r\n" + + "denzel\r\n" + + "------WebKitFormBoundaryAnydWsQ1ajKuGoCd\r\n" + + "Content-Disposition: form-data; name=\"flag\"\r\n\r\n" + + "test\r\n" + + "------WebKitFormBoundaryAnydWsQ1ajKuGoCd--\r\n"), + ContentType: ContentTypeMultipartForm + "; boundary=----WebKitFormBoundaryAnydWsQ1ajKuGoCd", + }, + AbsentHeaders: []string{}, + }, + }, + Response: AssertionResponse{ + ExpectedResponse: Response{ + StatusCode: 200, + Headers: map[string]string{}, + AbsentHeaders: []string{}, + }, + AdditionalResponseHeaders: map[string]string{}, + ExpectedResponseNoRequest: false, + }, + }, + }, + } + + for _, c := range cases { + t.Run(c.caseName, func(t *testing.T) { + err := CompareRequest(c.req, c.cReq, c.cRes, c.expected) + if c.errMsg != "" { + require.ErrorContains(t, err, c.errMsg) + return + } + require.NoError(t, err) + }) + } +} + +func TestCompareResponse(t *testing.T) { + cases := []struct { + caseName string + errMsg string + req *roundtripper.Request + cReq *roundtripper.CapturedRequest + cRes *roundtripper.CapturedResponse + expected Assertion + }{ + { + caseName: "compare response header ok", + req: &roundtripper.Request{}, + cReq: &roundtripper.CapturedRequest{}, + cRes: &roundtripper.CapturedResponse{ + StatusCode: 200, + Headers: map[string][]string{ + "X-header-test1": {"h1"}, + "X-header-test2": {"h2", "h22"}, + }, + Body: []byte(``), + }, + expected: Assertion{ + Meta: AssertionMeta{TestCaseName: "", TargetBackend: "", TargetNamespace: "", CompareTarget: CompareTargetResponse}, + Request: AssertionRequest{ + ActualRequest: Request{}, + ExpectedRequest: &ExpectedRequest{ + Request: Request{}, + AbsentHeaders: []string{}, + }, + }, + Response: AssertionResponse{ + ExpectedResponse: Response{ + StatusCode: 200, + Headers: map[string]string{ + "X-header-test1": "h1", + "X-header-test2": "h2,h22", + }, + Body: []byte(``), + AbsentHeaders: []string{}, + }, + AdditionalResponseHeaders: map[string]string{}, + ExpectedResponseNoRequest: false, + }, + }, + }, + { + caseName: "compare response header&body ok", + req: &roundtripper.Request{}, + cReq: &roundtripper.CapturedRequest{}, + cRes: &roundtripper.CapturedResponse{ + StatusCode: 200, + Headers: map[string][]string{ + "X-header-test1": {"h1"}, + "X-header-test2": {"h2", "h22"}, + "Content-Type": {"application/json"}, + }, + Body: []byte(` + { + "X-body-test1":["b1"], + "X-body-test2":["b2", "b22"] + }`), + }, + expected: Assertion{ + Meta: AssertionMeta{TestCaseName: "", TargetBackend: "", TargetNamespace: "", CompareTarget: CompareTargetResponse}, + Request: AssertionRequest{ + ActualRequest: Request{}, + ExpectedRequest: &ExpectedRequest{ + Request: Request{}, + AbsentHeaders: []string{}, + }, + }, + Response: AssertionResponse{ + ExpectedResponse: Response{ + StatusCode: 200, + Headers: map[string]string{ + "X-header-test1": "h1", + "X-header-test2": "h2,h22", + }, + Body: []byte(` + { + "X-body-test1":["b1"], + "X-body-test2":["b2","b22"] + }`), + ContentType: ContentTypeApplicationJson, + AbsentHeaders: []string{}, + }, + AdditionalResponseHeaders: map[string]string{}, + ExpectedResponseNoRequest: false, + }, + }, + }, + { + caseName: "compare response header&body fail, because headers not consistent", + errMsg: "expected X-header-test1 header to be set to h1, got h1,hn", + req: &roundtripper.Request{}, + cReq: &roundtripper.CapturedRequest{}, + cRes: &roundtripper.CapturedResponse{ + StatusCode: 200, + Headers: map[string][]string{ + "X-header-test1": {"h1", "hn"}, + "X-header-test2": {"h2", "h22"}, + "Content-Type": {"application/json"}, + }, + Body: []byte(` + { + "X-body-test1":["b1"], + "X-body-test2":["b2", "b22"] + }`), + }, + expected: Assertion{ + Meta: AssertionMeta{TestCaseName: "", TargetBackend: "", TargetNamespace: "", CompareTarget: CompareTargetResponse}, + Request: AssertionRequest{ + ActualRequest: Request{}, + ExpectedRequest: &ExpectedRequest{ + Request: Request{}, + AbsentHeaders: []string{}, + }, + }, + Response: AssertionResponse{ + ExpectedResponse: Response{ + StatusCode: 200, + Headers: map[string]string{ + "X-header-test1": "h1", + "X-header-test2": "h2,h22", + }, + Body: []byte(` + { + "X-body-test1":["b1"], + "X-body-test2":["b2","b22"] + }`), + ContentType: ContentTypeApplicationJson, + AbsentHeaders: []string{}, + }, + AdditionalResponseHeaders: map[string]string{}, + ExpectedResponseNoRequest: false, + }, + }, + }, + { + caseName: "compare response header&body fail, because body not consistent", + errMsg: "expected application/json body to be", + req: &roundtripper.Request{}, + cReq: &roundtripper.CapturedRequest{}, + cRes: &roundtripper.CapturedResponse{ + StatusCode: 200, + Headers: map[string][]string{ + "X-header-test1": {"h1"}, + "X-header-test2": {"h2", "h22"}, + "Content-Type": {"application/json"}, + }, + Body: []byte(` + { + "X-body-test1":["b1", "bn"], + "X-body-test2":["b2", "b22"] + }`), + }, + expected: Assertion{ + Meta: AssertionMeta{TestCaseName: "", TargetBackend: "", TargetNamespace: "", CompareTarget: CompareTargetResponse}, + Request: AssertionRequest{ + ActualRequest: Request{}, + ExpectedRequest: &ExpectedRequest{ + Request: Request{}, + AbsentHeaders: []string{}, + }, + }, + Response: AssertionResponse{ + ExpectedResponse: Response{ + StatusCode: 200, + Headers: map[string]string{ + "X-header-test1": "h1", + "X-header-test2": "h2,h22", + }, + Body: []byte(` + { + "X-body-test1":["b1"], + "X-body-test2":["b2","b22"] + }`), + ContentType: ContentTypeApplicationJson, + AbsentHeaders: []string{}, + }, + AdditionalResponseHeaders: map[string]string{}, + ExpectedResponseNoRequest: false, + }, + }, + }, + { + caseName: "compare response header&body ok, body type is text/plain", + req: &roundtripper.Request{}, + cReq: &roundtripper.CapturedRequest{}, + cRes: &roundtripper.CapturedResponse{ + StatusCode: 200, + Headers: map[string][]string{ + "X-header-test1": {"h1"}, + "X-header-test2": {"h2", "h22"}, + "Content-Type": {"text/plain"}, + }, + Body: []byte(`hello higress`), + }, + expected: Assertion{ + Meta: AssertionMeta{TestCaseName: "", TargetBackend: "", TargetNamespace: "", CompareTarget: CompareTargetResponse}, + Request: AssertionRequest{ + ActualRequest: Request{}, + ExpectedRequest: &ExpectedRequest{ + Request: Request{}, + AbsentHeaders: []string{}, + }, + }, + Response: AssertionResponse{ + ExpectedResponse: Response{ + StatusCode: 200, + Headers: map[string]string{ + "X-header-test1": "h1", + "X-header-test2": "h2,h22", + }, + Body: []byte(`hello higress`), + ContentType: ContentTypeTextPlain, + AbsentHeaders: []string{}, + }, + AdditionalResponseHeaders: map[string]string{}, + ExpectedResponseNoRequest: false, + }, + }, + }, + { + caseName: "compare response header&body fail, because body not consistent. body type is text/plain", + errMsg: "expected text/plain body to be", + req: &roundtripper.Request{}, + cReq: &roundtripper.CapturedRequest{}, + cRes: &roundtripper.CapturedResponse{ + StatusCode: 200, + Headers: map[string][]string{ + "X-header-test1": {"h1"}, + "X-header-test2": {"h2", "h22"}, + "Content-Type": {"text/plain"}, + }, + Body: []byte(`Hello Higress`), + }, + expected: Assertion{ + Meta: AssertionMeta{TestCaseName: "", TargetBackend: "", TargetNamespace: "", CompareTarget: CompareTargetResponse}, + Request: AssertionRequest{ + ActualRequest: Request{}, + ExpectedRequest: &ExpectedRequest{ + Request: Request{}, + AbsentHeaders: []string{}, + }, + }, + Response: AssertionResponse{ + ExpectedResponse: Response{ + StatusCode: 200, + Headers: map[string]string{ + "X-header-test1": "h1", + "X-header-test2": "h2,h22", + }, + Body: []byte(`hello higress`), + ContentType: ContentTypeTextPlain, + AbsentHeaders: []string{}, + }, + AdditionalResponseHeaders: map[string]string{}, + ExpectedResponseNoRequest: false, + }, + }, + }, + { + caseName: "compare response header&body ok, body type is FormUrlencoded", + req: &roundtripper.Request{}, + cReq: &roundtripper.CapturedRequest{}, + cRes: &roundtripper.CapturedResponse{ + StatusCode: 200, + Headers: map[string][]string{ + "X-header-test1": {"h1"}, + "X-header-test2": {"h2", "h22"}, + "Content-Type": {"application/x-www-form-urlencoded"}, + }, + Body: []byte(`X-body-test1=b1&X-body-test2=b2&X-body-test2=b22`), + }, + expected: Assertion{ + Meta: AssertionMeta{TestCaseName: "", TargetBackend: "", TargetNamespace: "", CompareTarget: CompareTargetResponse}, + Request: AssertionRequest{ + ActualRequest: Request{}, + ExpectedRequest: &ExpectedRequest{ + Request: Request{}, + AbsentHeaders: []string{}, + }, + }, + Response: AssertionResponse{ + ExpectedResponse: Response{ + StatusCode: 200, + Headers: map[string]string{ + "X-header-test1": "h1", + "X-header-test2": "h2,h22", + }, + Body: []byte(`X-body-test1=b1&X-body-test2=b2&X-body-test2=b22`), + ContentType: ContentTypeFormUrlencoded, + AbsentHeaders: []string{}, + }, + AdditionalResponseHeaders: map[string]string{}, + ExpectedResponseNoRequest: false, + }, + }, + }, + { + caseName: "compare response header&body fail, because body not consistent, body type is FormUrlencoded", + errMsg: "expected application/x-www-form-urlencoded body to be", + req: &roundtripper.Request{}, + cReq: &roundtripper.CapturedRequest{}, + cRes: &roundtripper.CapturedResponse{ + StatusCode: 200, + Headers: map[string][]string{ + "X-header-test1": {"h1"}, + "X-header-test2": {"h2", "h22"}, + "Content-Type": {"application/x-www-form-urlencoded"}, + }, + Body: []byte(`X-body-test1=b1&X-body-test1=bn&X-body-test2=b2&X-body-test2=b22`), + }, + expected: Assertion{ + Meta: AssertionMeta{TestCaseName: "", TargetBackend: "", TargetNamespace: "", CompareTarget: CompareTargetResponse}, + Request: AssertionRequest{ + ActualRequest: Request{}, + ExpectedRequest: &ExpectedRequest{ + Request: Request{}, + AbsentHeaders: []string{}, + }, + }, + Response: AssertionResponse{ + ExpectedResponse: Response{ + StatusCode: 200, + Headers: map[string]string{ + "X-header-test1": "h1", + "X-header-test2": "h2,h22", + }, + Body: []byte(`X-body-test1=b1&X-body-test2=b2&X-body-test2=b22`), + ContentType: ContentTypeFormUrlencoded, + AbsentHeaders: []string{}, + }, + AdditionalResponseHeaders: map[string]string{}, + ExpectedResponseNoRequest: false, + }, + }, + }, + { + caseName: "compare response header&body ok, body type is MultipartForm", + req: &roundtripper.Request{}, + cReq: &roundtripper.CapturedRequest{}, + cRes: &roundtripper.CapturedResponse{ + StatusCode: 200, + Headers: map[string][]string{ + "X-header-test1": {"h1"}, + "X-header-test2": {"h2", "h22"}, + "ContentType": {"multipart/form-data; boundary=----WebKitFormBoundaryAnydWsQ1ajKuGoCd"}, + }, + Body: []byte( + "------WebKitFormBoundaryAnydWsQ1ajKuGoCd\r\n" + + "Content-Disposition: form-data; name=\"file\"; filename=\"Screenshot.png\"\r\n" + + "Content-Type: image/png\r\n\r\n" + + "------WebKitFormBoundaryAnydWsQ1ajKuGoCd\r\n" + + "Content-Disposition: form-data; name=\"name\"\r\n\r\n" + + "denzel\r\n" + + "------WebKitFormBoundaryAnydWsQ1ajKuGoCd\r\n" + + "Content-Disposition: form-data; name=\"flag\"\r\n\r\n" + + "test\r\n" + + "------WebKitFormBoundaryAnydWsQ1ajKuGoCd--\r\n"), + }, + expected: Assertion{ + Meta: AssertionMeta{TestCaseName: "", TargetBackend: "", TargetNamespace: "", CompareTarget: CompareTargetResponse}, + Request: AssertionRequest{ + ActualRequest: Request{}, + ExpectedRequest: &ExpectedRequest{ + Request: Request{}, + AbsentHeaders: []string{}, + }, + }, + Response: AssertionResponse{ + ExpectedResponse: Response{ + StatusCode: 200, + Headers: map[string]string{ + "X-header-test1": "h1", + "X-header-test2": "h2,h22", + }, + Body: []byte( + "------WebKitFormBoundaryAnydWsQ1ajKuGoCd\r\n" + + "Content-Disposition: form-data; name=\"file\"; filename=\"Screenshot.png\"\r\n" + + "Content-Type: image/png\r\n\r\n" + + "------WebKitFormBoundaryAnydWsQ1ajKuGoCd\r\n" + + "Content-Disposition: form-data; name=\"name\"\r\n\r\n" + + "denzel\r\n" + + "------WebKitFormBoundaryAnydWsQ1ajKuGoCd\r\n" + + "Content-Disposition: form-data; name=\"flag\"\r\n\r\n" + + "test\r\n" + + "------WebKitFormBoundaryAnydWsQ1ajKuGoCd--\r\n"), + ContentType: ContentTypeMultipartForm + "; boundary=----WebKitFormBoundaryAnydWsQ1ajKuGoCd", + AbsentHeaders: []string{}, + }, + AdditionalResponseHeaders: map[string]string{}, + ExpectedResponseNoRequest: false, + }, + }, + }, + { + caseName: "compare response header&body fail, because body not consistent. body type is MultipartForm", + errMsg: "expected multipart/form-data body to be", + req: &roundtripper.Request{}, + cReq: &roundtripper.CapturedRequest{}, + cRes: &roundtripper.CapturedResponse{ + StatusCode: 200, + Headers: map[string][]string{ + "X-header-test1": {"h1"}, + "X-header-test2": {"h2", "h22"}, + "ContentType": {"multipart/form-data; boundary=----WebKitFormBoundaryAnydWsQ1ajKuGoCd"}, + }, + Body: []byte( + "------WebKitFormBoundaryAnydWsQ1ajKuGoCd\r\n" + + "Content-Disposition: form-data; name=\"file\"; filename=\"Screenshot.png\"\r\n" + + "Content-Type: image/png\r\n\r\n" + + "------WebKitFormBoundaryAnydWsQ1ajKuGoCd\r\n" + + "Content-Disposition: form-data; name=\"name\"\r\n\r\n" + + "higress\r\n" + + "------WebKitFormBoundaryAnydWsQ1ajKuGoCd\r\n" + + "Content-Disposition: form-data; name=\"flag\"\r\n\r\n" + + "test\r\n" + + "------WebKitFormBoundaryAnydWsQ1ajKuGoCd--\r\n"), + }, + expected: Assertion{ + Meta: AssertionMeta{TestCaseName: "", TargetBackend: "", TargetNamespace: "", CompareTarget: CompareTargetResponse}, + Request: AssertionRequest{ + ActualRequest: Request{}, + ExpectedRequest: &ExpectedRequest{ + Request: Request{}, + AbsentHeaders: []string{}, + }, + }, + Response: AssertionResponse{ + ExpectedResponse: Response{ + StatusCode: 200, + Headers: map[string]string{ + "X-header-test1": "h1", + "X-header-test2": "h2,h22", + }, + Body: []byte( + "------WebKitFormBoundaryAnydWsQ1ajKuGoCd\r\n" + + "Content-Disposition: form-data; name=\"file\"; filename=\"Screenshot.png\"\r\n" + + "Content-Type: image/png\r\n\r\n" + + "------WebKitFormBoundaryAnydWsQ1ajKuGoCd\r\n" + + "Content-Disposition: form-data; name=\"name\"\r\n\r\n" + + "denzel\r\n" + + "------WebKitFormBoundaryAnydWsQ1ajKuGoCd\r\n" + + "Content-Disposition: form-data; name=\"flag\"\r\n\r\n" + + "test\r\n" + + "------WebKitFormBoundaryAnydWsQ1ajKuGoCd--\r\n"), + ContentType: ContentTypeMultipartForm + "; boundary=----WebKitFormBoundaryAnydWsQ1ajKuGoCd", + AbsentHeaders: []string{}, + }, + AdditionalResponseHeaders: map[string]string{}, + ExpectedResponseNoRequest: false, + }, + }, + }, + } + + for _, c := range cases { + t.Run(c.caseName, func(t *testing.T) { + err := CompareResponse(c.cRes, c.expected) + if c.errMsg != "" { + require.ErrorContains(t, err, c.errMsg) + return + } + require.NoError(t, err) + }) + } +}