Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handling Mocking media types #238

Merged
merged 4 commits into from
Feb 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion development/testing/manifests/api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,18 @@ spec:
completed: true
order: 13
url: "http://mockedURL.com"
text/plain:
example: |
title: "Mocked title with text example
completed: true
order: 13
url: "http://mockedURL.com"
application/xml:
example:
title: "Mocked title with XML example"
completed: true
order: 13
url: "http://mockedURL.com"
'400':
description: Client error
content:
Expand Down Expand Up @@ -265,7 +277,19 @@ spec:
- url
# This example is the must for the response body mocking
example:
title: "Mocked title"
title: "Mocked JSON title"
completed: true
order: 13
url: "http://mockedURL.com"
application/xml:
example:
title: "Mocked XML title"
completed: true
order: 13
url: "http://mockedURL.com"
text/plain:
example: |
title: "Mocked Text title"
completed: true
order: 13
url: "http://mockedURL.com"
Expand Down
4 changes: 2 additions & 2 deletions development/testing/postman/api.postman_collection.json
Original file line number Diff line number Diff line change
Expand Up @@ -790,7 +790,7 @@
" pm.expect(jsonData).to.be.an(\"object\");",
" pm.expect(pm.response.headers.get('content-type')).to.eql('application/json');",
" pm.expect(pm.response.headers.get('x-kusk-mocked')).to.eql('true');",
" pm.expect(jsonData.title).to.eql(\"Mocked title\");",
" pm.expect(jsonData.title).to.eql(\"Mocked JSON title\");",
" pm.expect(jsonData.order).to.eql(13);",
" pm.expect(jsonData.completed).to.eql(true);",
"}",
Expand Down Expand Up @@ -1036,4 +1036,4 @@
"type": "string"
}
]
}
}
88 changes: 70 additions & 18 deletions docs/extension.md
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ The validation objects contains the following properties to configure automatic
| validation.request.enabled | boolean flag to enable request validation |

#### strict validation of request bodies

Strict validation means that the request body must conform exactly to the schema specified in your openapi spec.
Any fields not in schema will cause the validation to fail the request/response.
To enable this, please add the following field to your schema block if the request body is of type `object`
Expand Down Expand Up @@ -353,21 +354,33 @@ Note: currently `mocking` is incompatible with the `validation` option, the conf
- url
# Singular example has the priority over other examples.
example:
title: "Mocked title"
title: "Mocked JSON title"
completed: true
order: 13
url: "http://mockedURL.com"
examples:
first:
title: "Mocked title #1"
title: "Mocked JSON title #1"
completed: true
order: 12
url: "http://mockedURL.com"
second:
title: "Mocked title #2"
title: "Mocked JSON title #2"
completed: true
order: 13
url: "http://mockedURL.com"
application/xml:
example:
title: "Mocked XML title"
completed: true
order: 13
url: "http://mockedURL.com"
text/plain:
example: |
title: "Mocked Text title"
completed: true
order: 13
url: "http://mockedURL.com"
patch:
# Disable for patch
x-kusk:
Expand All @@ -376,18 +389,57 @@ Note: currently `mocking` is incompatible with the `validation` option, the conf
...
```

With the example above the response to GET request will be:

```shell
< HTTP/1.1 200 OK
< content-type: application/json
< x-kusk-mocked: true
< date: Mon, 21 Feb 2022 14:36:52 GMT
< content-length: 81
< x-envoy-upstream-service-time: 0
< server: envoy
<
{"completed":true,"order":13,"title":"Mocked title","url":"http://mockedURL.com"}
```

The response includes the `x-kusk-mocked: true` header indicating mocking.
With the example above, the response to GET request will be different depending on the client's preferred media type when using the `Accept` header.

Below we're using the example.com setup from the development/testing directory.

1. Curl call without specifying the Accept header. From the list of specified media types (application/json, plain/text, application/xml) - uses our default Mocking media type - application/json:

```shell
curl -v -H "Host: example.com" http://192.168.49.3/testing/mocked/multiple/1

< HTTP/1.1 200 OK
< content-type: application/json
< x-kusk-mocked: true
< date: Mon, 21 Feb 2022 14:36:52 GMT
< content-length: 81
< x-envoy-upstream-service-time: 0
< server: envoy
<
{"completed":true,"order":13,"title":"Mocked JSON title","url":"http://mockedURL.com"}
```

The response includes the `x-kusk-mocked: true` header indicating mocking.

2. With the Accept header, that has application/xml as the preffered type:

```shell
curl -v -H "Host: example.com" -H "Accept: application/xml" http://192.168.49.3/testing/mocked/1
< HTTP/1.1 200 OK
< content-type: application/xml
< x-kusk-mocked: true
< date: Mon, 28 Feb 2022 08:56:46 GMT
< content-length: 117
< x-envoy-upstream-service-time: 0
< server: envoy

<doc><completed>true</completed><order>13</order><title>Mocked XML title</title><url>http://mockedURL.com</url></doc>
```

3. With the Accept header specifying multiple weighted preffered media types, text/plain with more weight.

```shell
curl -v -H "Host: example.com" -H "Accept: application/json;q=0.8,text/plain;q=0.9" http://192.168.49.3/testing/mocked/1
< content-type: text/plain
< x-kusk-mocked: true
< date: Mon, 28 Feb 2022 08:56:00 GMT
< content-length: 81
< x-envoy-upstream-service-time: 0
< server: envoy
<
title: "Mocked Text title"
completed: true
order: 13
url: "http://mockedURL.com"

```
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/census-instrumentation/opencensus-proto v0.2.1 // indirect
github.com/cespare/xxhash/v2 v2.1.1 // indirect
github.com/clbanning/mxj v1.8.4
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/envoyproxy/protoc-gen-validate v0.1.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I=
github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
Expand Down
37 changes: 31 additions & 6 deletions internal/agent/httpserver/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,41 @@ func (h *mainHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {

// Add Mocked header to show that we mocked the response
w.Header().Set(HeaderMockResponseInsert, "true")
// TODO: detect content type for the user using its request Accept Header
mediaType := "application/json"
data, ok := mockResponse.MediaTypeData[mediaType]
// If no media type data (example) found - this is the simple http code in the response, write it and return
if !ok {
if len(mockResponse.MediaTypeData) == 0 {
w.WriteHeader(mockResponse.StatusCode)
return
}
// otherwise, set Content-Type and write the body
w.Header().Set("Content-Type", mediaType)
mediaTypes := getMediaTypes(mockResponse.MediaTypeData)
defaultMediaType := getDefaultMediaType(mediaTypes)
// Get media type from the request Accept header parsing and matching to the existing media content type.
// If missing or not matched - use the first entry in the media content map.
chosenMediaType := NegotiateContentType(r, mediaTypes, defaultMediaType)
data := mockResponse.MediaTypeData[chosenMediaType]
w.Header().Set("Content-Type", chosenMediaType)
w.WriteHeader(mockResponse.StatusCode)
w.Write(data)
}

func getMediaTypes(mediaTypesData map[string][]byte) []string {
mediaTypes := make([]string, 0, len(mediaTypesData))
for contentType := range mediaTypesData {
mediaTypes = append(mediaTypes, contentType)
}

return mediaTypes
}

func getDefaultMediaType(mediaTypes []string) string {
Tarick marked this conversation as resolved.
Show resolved Hide resolved
if len(mediaTypes) == 1 {
return mediaTypes[0]
}
// Return any json-based mediaType as default
for _, mediaType := range mediaTypes {
if mocking.JsonMediaTypePattern.Match([]byte(mediaType)) {
return mediaType
}
}
// Otherwise return the first found
return mediaTypes[0]
}
Loading