Skip to content
This repository has been archived by the owner on Jan 21, 2024. It is now read-only.

Added support for preferred responses #85

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
example
npm-debug.log
6 changes: 6 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
FROM node:11

WORKDIR /usr/src/app

COPY . .
RUN npm install -g
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,45 @@ main()
* `createServerFromBaseUri` Creates a mock service with Osprey and uses the base URI path
* `loadFile` Creates a mock service with Osprey and the base URI path from a RAML file

### Preferred responses

Clients of mock service may provive HTTP header called `Mock-Preferred-Responses` to choose which of example responses should be returned.

For example, in case of API definition such as:

```
#%RAML 1.0
title: Example API
baseUri: http://localhost:3000/api/{version}
version: v1

/example:
get:
responses:
200:
body:
application/json:
example: |
{
"message": "success"
}
400:
body:
application/json:
example: |
{
"message": "failure"
}
```

Client could choose to receive 400 response, by providing `Mock-Preferred-Responses` HTTP header with value:

```
{
"GET /example": 400
}
```

## License

Apache License 2.0
Expand Down
6 changes: 6 additions & 0 deletions example/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
FROM osprey-mock-service:latest

COPY api.raml .

EXPOSE 8080
CMD [ "osprey-mock-service", "-f", "api.raml", "-p", "8080" ]
27 changes: 24 additions & 3 deletions example/api.raml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,27 @@ baseUri: http://127.0.0.1
200:
body:
application/json:
example:
firstname: john
lastname: doe
example: |
{
"users": [
{
"firstname": "john",
"lastname": "doe"
}
],
"message": "ok"
}
400:
body:
application/json:
example: |
{
"users": []
"message": "failure"
}
/groups:
get:
headers:
Authorized:
type: string
enum: [yes]
101 changes: 74 additions & 27 deletions osprey-mock-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,23 @@ function getSchemaExample (schema) {
return extractDataNodeValue(exNode.structuredValue)
}

/**
* Returns value of raw header, if included in request.
*
* @param {HTTP.Request} req
* @param {String} header
* @return {String}
*/
function getRawHeaderValue (req, header) {
for (var i = 0; i < req.rawHeaders.length; i += 2) {
if (req.rawHeaders[i] === header) {
if (i + 1 < req.rawHeaders.length) {
return req.rawHeaders[i + 1]
}
}
}
}

/**
* Extracts data from DataNode subclass instance.
*
Expand Down Expand Up @@ -145,36 +162,66 @@ function getHeaderExample (header) {
* @return {Function}
*/
function mockHandler (method) {
const response = method.responses && method.responses[0]
const statusCode = response
? parseInt(response.statusCode.value())
: 200

// Set up the default response headers.
const headers = {}
if (response && response.headers) {
response.headers.forEach(header => {
const defaultVal = (
header.schema.defaultValueStr &&
header.schema.defaultValueStr.option)
const example = getHeaderExample(header)
if (defaultVal) {
headers[header.name.value()] = defaultVal
} else if (example) {
headers[header.name.value()] = example
const mockMethod = method

return function (req, res) {
const resourceMethod = req.method + ' ' + req.resourcePath
const preferredResponses = getRawHeaderValue(req, 'Mock-Preferred-Responses')
const preferredResponse = preferredResponses
? JSON.parse(preferredResponses)[resourceMethod]
: null

var response = null
var statusCode = null
if (mockMethod.responses && mockMethod.responses.length > 0) {
if (preferredResponse) {
mockMethod.responses.forEach(mockResponse => {
const mockStatusCode = parseInt(mockResponse.statusCode)
if (mockStatusCode === preferredResponse) {
response = mockResponse
statusCode = mockStatusCode
}
})
if (!response) {
response = mockMethod.responses[0]
statusCode = preferredResponse
}
}
})
}
if (!response) {
response = mockMethod.responses[0]
statusCode = mockMethod.responses[0].statusCode
}
}
if (!response && preferredResponse) {
statusCode = preferredResponse
} else if (!response) {
statusCode = 200
}

const bodies = {}
if (response) {
response.payloads.forEach(pl => {
bodies[pl.mediaType.value()] = pl
})
}
const types = Object.keys(bodies)
// Set up the default response headers.
const headers = {}
if (response && response.headers) {
response.headers.forEach(header => {
const defaultVal = (
header.schema.defaultValueStr &&
header.schema.defaultValueStr.option)
const example = getHeaderExample(header)
if (defaultVal) {
headers[header.name.value()] = defaultVal
} else if (example) {
headers[header.name.value()] = example
}
})
}

const bodies = {}
if (response) {
response.payloads.forEach(pl => {
bodies[pl.mediaType.value()] = pl
})
}
const types = Object.keys(bodies)

return function (req, res) {
const negotiator = new Negotiator(req)
let type = negotiator.mediaType(types)
if (req.params && (req.params.mediaTypeExtension || req.params.ext)) {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "osprey-mock-service",
"version": "1.0.0",
"version": "1.0.1",
"description": "Generate an API mock service from a RAML definition using Osprey",
"main": "osprey-mock-service.js",
"files": [
Expand Down