diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..f8b15c7 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +node_modules +example +npm-debug.log \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1571863 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,6 @@ +FROM node:11 + +WORKDIR /usr/src/app + +COPY . . +RUN npm install -g diff --git a/README.md b/README.md index d57d53f..8718e36 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/example/Dockerfile b/example/Dockerfile new file mode 100644 index 0000000..c369810 --- /dev/null +++ b/example/Dockerfile @@ -0,0 +1,6 @@ +FROM osprey-mock-service:latest + +COPY api.raml . + +EXPOSE 8080 +CMD [ "osprey-mock-service", "-f", "api.raml", "-p", "8080" ] diff --git a/example/api.raml b/example/api.raml index 92e801e..74aabd4 100644 --- a/example/api.raml +++ b/example/api.raml @@ -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] diff --git a/osprey-mock-service.js b/osprey-mock-service.js index 8422e69..9af2ec5 100644 --- a/osprey-mock-service.js +++ b/osprey-mock-service.js @@ -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. * @@ -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)) { diff --git a/package.json b/package.json index 2eeab89..265f62d 100644 --- a/package.json +++ b/package.json @@ -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": [