// 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) }) } }