Skip to content

Commit

Permalink
feat: add prefer example implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
tabrindle committed Feb 10, 2021
1 parent 7faf910 commit 02ecd77
Show file tree
Hide file tree
Showing 7 changed files with 185 additions and 8 deletions.
9 changes: 5 additions & 4 deletions lib/mocker/express/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const cors = require('cors');
const cookieParser = require('cookie-parser');
const logger = require('lllog')();
const colors = require('colors');
const parsePreferHeader = require('parse-prefer-header');

const openApiMockSymbol = Symbol('openApiMock');

Expand Down Expand Up @@ -95,14 +96,14 @@ class Server {
return this.sendResponse(res, { errors: failedValidations }, 400);

const preferHeader = req.header('prefer') || '';
const [, preferStatusCode] = preferHeader.match(/statusCode=(\d{3})/) || [];
const { example: preferredExampleName, statusCode: preferredStatusCode } = parsePreferHeader(preferHeader) || {};

if(preferStatusCode)
logger.debug(`Searching requested response with status code ${preferStatusCode}`);
if(preferredStatusCode)
logger.debug(`Searching requested response with status code ${preferredStatusCode}`);
else
logger.debug('Searching first response');

const { statusCode, headers: responseHeaders, body } = path.getResponse(preferStatusCode);
const { statusCode, headers: responseHeaders, body } = path.getResponse(preferredStatusCode, preferredExampleName);

return this.sendResponse(res, body, statusCode, responseHeaders);
});
Expand Down
4 changes: 2 additions & 2 deletions lib/paths/path.js
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ class Path {
return !possibleValues || !possibleValues.length || possibleValues.includes(value);
}

getResponse(preferredStatusCode) {
getResponse(preferredStatusCode, preferredExampleName) {

const {
statusCode,
Expand All @@ -220,7 +220,7 @@ class Path {
return {
statusCode: Number(statusCode),
headers: headers && this.generateResponseHeaders(headers),
body: schema ? ResponseGenerator.generate(schema) : null
body: schema ? ResponseGenerator.generate(schema, preferredExampleName) : null
};
}

Expand Down
7 changes: 5 additions & 2 deletions lib/response-generator/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@

class ResponseGenerator {

static generate(schemaResponse) {
static generate(schemaResponse, preferredExampleName) {

if(schemaResponse.example)
return schemaResponse.example;

if(schemaResponse.examples && schemaResponse.examples.length)
if(schemaResponse.examples && Object.values(schemaResponse.examples).length) {
if(preferredExampleName)
return schemaResponse.examples[preferredExampleName] || Object.values(schemaResponse.examples)[0];
return schemaResponse.examples[0];
}

if(schemaResponse.enum && schemaResponse.enum.length)
return this.generateByEnum(schemaResponse.enum);
Expand Down
13 changes: 13 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"js-yaml": "^3.14.0",
"json-refs": "^3.0.13",
"lllog": "^1.1.2",
"parse-prefer-header": "^1.0.0",
"superstruct": "^0.6.1",
"yargs": "^13.3.0"
},
Expand Down
118 changes: 118 additions & 0 deletions tests/paths/path.js
Original file line number Diff line number Diff line change
Expand Up @@ -1803,6 +1803,78 @@ describe('Paths', () => {
});
});

it('Should call the response generator with the first available response with given example if no preferred statusCode is passed', () => {

const path = new Path({
uri: '/hello',
httpMethod: 'get',
parameters: undefined,
responses: {
200: {
description: 'OK',
content: {
'application/json': {
examples: {
hello: {
message: 'world'
},
goodbye: {
message: 'yellow brick road'
}
}
}
}
}
}
});

const response = path.getResponse(undefined, 'goodbye');

assert.deepStrictEqual(response, {
statusCode: 200,
headers: undefined,
body: {
message: 'yellow brick road'
}
});
});

it('Should call the response generator with the first available response with prefer example & no prefered statusCode, but no match', () => {

const path = new Path({
uri: '/hello',
httpMethod: 'get',
parameters: undefined,
responses: {
200: {
description: 'OK',
content: {
'application/json': {
examples: {
hello: {
message: 'world'
},
goodbye: {
message: 'yellow brick road'
}
}
}
}
}
}
});

const response = path.getResponse(undefined, 'sup');

assert.deepStrictEqual(response, {
statusCode: 200,
headers: undefined,
body: {
message: 'world'
}
});
});

it('Should call the response generator with the preferred response based on the passed statusCode', () => {

const path = new Path({
Expand Down Expand Up @@ -1844,6 +1916,52 @@ describe('Paths', () => {
});
});

it('Should call the response generator with the preferred response based on the passed statusCode and passed example', () => {

const path = new Path({
uri: '/hello',
httpMethod: 'get',
parameters: undefined,
responses: {
200: {
description: 'OK',
content: {
'application/json': {
example: {
hello: 'world'
}
}
}
},
401: {
description: 'Unauthorized',
content: {
'application/json': {
examples: {
invalid: {
message: 'Unauthorized - token invalid'
},
expired: {
message: 'Unauthorized - token expired'
}
}
}
}
}
}
});

const response = path.getResponse('401', 'expired');

assert.deepStrictEqual(response, {
statusCode: 401,
headers: undefined,
body: {
message: 'Unauthorized - token expired'
}
});
});

it('Should call the response generator with the first response if preferred response is not available', () => {

const path = new Path({
Expand Down
41 changes: 41 additions & 0 deletions tests/response-generator/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,47 @@ describe('Response Generator', () => {
});
});

it('Should return the preferred example if prefer header is set', () => {

const responseSchema = {
examples: {
cat: {
summary: 'An example of a cat',
value: {
name: 'Fluffy',
petType: 'Cat',
color: 'White',
gender: 'male',
breed: 'Persian'
}
},
dog: {
summary: 'An example of a dog with a cat\'s name',
value: {
name: 'Puma',
petType: 'Dog',
color: 'Black',
gender: 'Female',
breed: 'Mixed'
}
}
}
};

const response = ResponseGenerator.generate(responseSchema, 'cat');

assert.deepStrictEqual(response, {
summary: 'An example of a cat',
value: {
name: 'Fluffy',
petType: 'Cat',
color: 'White',
gender: 'male',
breed: 'Persian'
}
});
});

it('Should return the schema\'s example if it\'s defined', () => {

const responseSchema = {
Expand Down

0 comments on commit 02ecd77

Please sign in to comment.