Skip to content

Commit

Permalink
response-header-control-65689 (#76)
Browse files Browse the repository at this point in the history
* http_server: do not try to unmarshal empty body for bounce-json

* control can be used for response headers

* finished implementing control structures for response headers; completed apitest; see #65689

* fixed control for header top level key; small fixes after change request

Co-authored-by: Philipp Hempel <philipp.hempel@programmfabrik.de>
  • Loading branch information
phempel and Philipp Hempel authored Oct 18, 2022
1 parent e3b9f17 commit 7c088c0
Show file tree
Hide file tree
Showing 12 changed files with 180 additions and 54 deletions.
2 changes: 1 addition & 1 deletion api_testcase.go
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,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, res.Format)
return api.NewResponse(http.StatusOK, nil, nil, nil, nil, nil, res.Format)
}
spec, err := testCase.loadResponseSerialization(testCase.ResponseData)
if err != nil {
Expand Down
15 changes: 8 additions & 7 deletions http_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,16 +162,17 @@ func bounceJSON(w http.ResponseWriter, r *http.Request) {
return
}

err = json.Unmarshal(bodyBytes, &bodyJSON)
if err != nil {
errorResponse(w, 500, err, errorBody)
return
}

response := BounceResponse{
Header: r.Header,
QueryParams: r.URL.Query(),
Body: bodyJSON,
}
if len(bodyBytes) > 0 {
err = json.Unmarshal(bodyBytes, &bodyJSON)
if err != nil {
errorResponse(w, 500, err, errorBody)
return
}
response.Body = bodyJSON
}

responseData, err := json.MarshalIndent(response, "", " ")
Expand Down
6 changes: 5 additions & 1 deletion pkg/lib/api/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,11 @@ func (request Request) Send() (response Response, err error) {

defer httpResponse.Body.Close()

response, err = NewResponse(httpResponse.StatusCode, httpResponse.Header, httpResponse.Cookies(), httpResponse.Body, nil, ResponseFormat{})
header, err := HttpHeaderToMap(httpResponse.Header)
if err != nil {
return response, err
}
response, err = NewResponse(httpResponse.StatusCode, header, nil, httpResponse.Cookies(), httpResponse.Body, nil, ResponseFormat{})
if err != nil {
return response, fmt.Errorf("error constructing response from http response")
}
Expand Down
95 changes: 61 additions & 34 deletions pkg/lib/api/response.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@ import (
)

type Response struct {
StatusCode int
Headers map[string][]string
Cookies []*http.Cookie
Body []byte
bodyControl util.JsonObject
Format ResponseFormat
StatusCode int
Headers map[string]any
headerControl util.JsonObject
Cookies []*http.Cookie
Body []byte
bodyControl util.JsonObject
Format ResponseFormat

ReqDur time.Duration
BodyLoadDur time.Duration
Expand All @@ -41,6 +42,22 @@ func (res Response) NeedsCheck() bool {
return false
}

func (res Response) SerializeHeaders() (headers map[string]any, err error) {
headers = map[string]any{}
for k, h := range res.Headers {
headers[k] = h
}
return headers, nil
}

func HttpHeaderToMap(header http.Header) (headers map[string]any, err error) {
headers = map[string]any{}
for k, h := range header {
headers[k] = h
}
return headers, nil
}

// Cookie definition
type Cookie struct {
Name string `json:"name"`
Expand All @@ -55,12 +72,13 @@ type Cookie struct {
}

type ResponseSerialization struct {
StatusCode int `yaml:"statuscode" json:"statuscode"`
Headers map[string][]string `yaml:"header" json:"header,omitempty"`
Cookies map[string]Cookie `yaml:"cookie" json:"cookie,omitempty"`
Body interface{} `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"`
HeaderControl util.JsonObject `yaml:"header:control" json:"header:control,omitempty"`
Cookies map[string]Cookie `yaml:"cookie" json:"cookie,omitempty"`
Body interface{} `yaml:"body" json:"body,omitempty"`
BodyControl util.JsonObject `yaml:"body:control" json:"body:control,omitempty"`
Format ResponseFormat `yaml:"format" json:"format,omitempty"`
}

type ResponseFormat struct {
Expand All @@ -72,13 +90,14 @@ type ResponseFormat struct {
PreProcess *PreProcess `json:"pre_process,omitempty"`
}

func NewResponse(statusCode int, headers map[string][]string, cookies []*http.Cookie, body io.Reader, bodyControl util.JsonObject, bodyFormat ResponseFormat) (res Response, err error) {
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) {
res = Response{
StatusCode: statusCode,
Headers: headers,
Cookies: cookies,
bodyControl: bodyControl,
Format: bodyFormat,
StatusCode: statusCode,
Headers: headers,
Cookies: cookies,
bodyControl: bodyControl,
headerControl: headerControl,
Format: bodyFormat,
}
if body != nil {
start := time.Now()
Expand Down Expand Up @@ -124,7 +143,7 @@ func NewResponseFromSpec(spec ResponseSerialization) (res Response, err error) {
}
}

return NewResponse(spec.StatusCode, spec.Headers, cookies, body, spec.BodyControl, spec.Format)
return NewResponse(spec.StatusCode, spec.Headers, spec.HeaderControl, cookies, body, spec.BodyControl, spec.Format)
}

// ServerResponseToGenericJSON parse response from server. convert xml, csv, binary to json if necessary
Expand Down Expand Up @@ -184,11 +203,13 @@ func (response Response) ServerResponseToGenericJSON(responseFormat ResponseForm
return res, fmt.Errorf("Invalid response format '%s'", responseFormat.Type)
}

headers, err := resp.SerializeHeaders()
if err != nil {
return res, err
}
responseJSON := ResponseSerialization{
StatusCode: resp.StatusCode,
}
if len(resp.Headers) > 0 {
responseJSON.Headers = resp.Headers
Headers: headers,
}
// Build cookies map from standard bag
if len(resp.Cookies) > 0 {
Expand Down Expand Up @@ -252,12 +273,15 @@ func (response Response) ToGenericJSON() (interface{}, error) {
}
}

responseJSON := ResponseSerialization{
StatusCode: response.StatusCode,
BodyControl: response.bodyControl,
headers, err := response.SerializeHeaders()
if err != nil {
return res, err
}
if len(response.Headers) > 0 {
responseJSON.Headers = response.Headers
responseJSON := ResponseSerialization{
StatusCode: response.StatusCode,
BodyControl: response.bodyControl,
Headers: headers,
HeaderControl: response.headerControl,
}

// Build cookies map from standard bag
Expand Down Expand Up @@ -325,14 +349,17 @@ func (response Response) ToString() string {
)

for k, v := range response.Headers {
value := ""
for _, iv := range v {
value = fmt.Sprintf("%s %s", value, iv)
}
if strings.TrimSpace(value) == "" {
continue
switch v2 := v.(type) {
case []string:
value := ""
for _, iv := range v2 {
value = fmt.Sprintf("%s %s", value, iv)
}
if strings.TrimSpace(value) == "" {
continue
}
headersString = fmt.Sprintf("%s\n%s:%s", headersString, k, value)
}
headersString = fmt.Sprintf("%s\n%s:%s", headersString, k, value)
}

if response.Format.PreProcess != nil {
Expand Down
29 changes: 20 additions & 9 deletions pkg/lib/api/response_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,16 @@ import (
"strings"
"testing"

"github.com/programmfabrik/apitest/pkg/lib/util"
go_test_utils "github.com/programmfabrik/go-test-utils"
"github.com/tidwall/gjson"
)

func TestResponse_ToGenericJson(t *testing.T) {
response := Response{
StatusCode: 200,
Headers: map[string][]string{
"foo": {"bar"},
Headers: map[string]any{
"foo": []string{"bar"},
},
}
genericJson, err := response.ToGenericJSON()
Expand Down Expand Up @@ -48,15 +49,18 @@ func TestResponse_ToGenericJson(t *testing.T) {
func TestResponse_NewResponseFromSpec(t *testing.T) {
responseSpec := ResponseSerialization{
StatusCode: 200,
Headers: map[string][]string{
"foo": {"bar"},
Headers: map[string]any{
"foo": []string{"bar"},
"foo2:control": util.JsonObject{
"must_not_exist": true,
},
},
Body: nil,
}
response, err := NewResponseFromSpec(responseSpec)
go_test_utils.ExpectNoError(t, err, "unexpected error")
go_test_utils.AssertIntEquals(t, response.StatusCode, responseSpec.StatusCode)
go_test_utils.AssertStringEquals(t, response.Headers["foo"][0], "bar")
go_test_utils.AssertStringEquals(t, response.Headers["foo"].([]string)[0], "bar")
}

func TestResponse_NewResponseFromSpec_StatusCode_not_set(t *testing.T) {
Expand All @@ -69,17 +73,20 @@ func TestResponse_NewResponseFromSpec_StatusCode_not_set(t *testing.T) {
}

func TestResponse_NewResponse(t *testing.T) {
response, err := NewResponse(200, nil, nil, strings.NewReader("foo"), nil, ResponseFormat{})
response, err := NewResponse(200, nil, nil, nil, strings.NewReader("foo"), nil, ResponseFormat{})
go_test_utils.ExpectNoError(t, err, "unexpected error")
go_test_utils.AssertIntEquals(t, response.StatusCode, 200)
}

func TestResponse_String(t *testing.T) {
requestString := `{
"foo": "bar"
"foo": "bar",
"foo2:control": {
"must_not_exist": true
}
}`

response, err := NewResponse(200, nil, nil, strings.NewReader(requestString), nil, ResponseFormat{})
response, err := NewResponse(200, nil, nil, nil, strings.NewReader(requestString), nil, ResponseFormat{})
go_test_utils.ExpectNoError(t, err, "error constructing response")

assertString := "200\n\n\n" + requestString
Expand Down Expand Up @@ -108,7 +115,11 @@ func TestResponse_Cookies(t *testing.T) {
}
defer res.Body.Close()

response, err := NewResponse(res.StatusCode, res.Header, res.Cookies(), res.Body, nil, ResponseFormat{})
header, err := HttpHeaderToMap(res.Header)
if err != nil {
t.Fatal(err)
}
response, err := NewResponse(res.StatusCode, header, nil, res.Cookies(), res.Body, nil, ResponseFormat{})
if err != nil {
t.Fatal(err)
}
Expand Down
5 changes: 3 additions & 2 deletions pkg/lib/template/template_loader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func TestBigIntRender(t *testing.T) {

inputNumber := "132132132182323"

resp, _ := api.NewResponse(200, 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)), nil, api.ResponseFormat{})

respJson, _ := resp.ServerResponseToJsonString(false)
store.SetWithQjson(respJson, map[string]string{"testINT": "body.bigINT"})
Expand Down Expand Up @@ -406,7 +406,8 @@ func TestRender_LoadFile_QJson(t *testing.T) {
func Test_DataStore_QJson(t *testing.T) {
response, _ := api.NewResponse(
200,
map[string][]string{"x-header": {"foo", "bar"}},
map[string]any{"x-header": []string{"foo", "bar"}},
nil,
nil,
strings.NewReader(`{
"flib": [
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
82 changes: 82 additions & 0 deletions test/control/header/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
{
"http_server": {
"addr": ":9999",
"dir": "../_res/assets/",
"testmode": false
},
"name": "check control structures in header",
"tests": [
{
"name": "check HTTP header and bounced header using control",
"request": {
"server_url": "http://localhost:9999",
"endpoint": "bounce-json",
"method": "POST",
"header": {
"header1": "ABC",
"header2": "123"
}
},
"response": {
// check actual HTTP headers
"header": {
"Content-Type:control": {
"element_count": 1
},
"xxx:control": {
"must_not_exist": true
}
},
"body": {
// check bounced headers as part of the response body
"header": {
"Header1": [
"ABC"
],
"Header2:control": {
"element_count": 1
},
"Header3:control": {
"must_not_exist": true
}
}
}
}
},
{
"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": {
"server_url": "http://localhost:9999",
"endpoint": "bounce-json",
"method": "POST",
"header": {
"header1": "ABC"
}
},
"response": {
"header": {
// HTTP header should never exist twice
"Content-Type:control": {
"element_count": 2
}
}
},
"reverse_test_result": true
}
]
}

0 comments on commit 7c088c0

Please sign in to comment.