Skip to content
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
20 changes: 20 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch via NPM",
"type": "node",
"request": "launch",
"cwd": "${workspaceRoot}",
"runtimeExecutable": "npm",
"runtimeArgs": [
"run-script",
"debug"
],
"port": 5000
}
]
}
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
Chaos experiments added. You can add a statuscode and latency delay.

## [1.11.1] - 2022-02-20
### Fixed
- Responses that are just a number are now properly generated (#56)
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ docker run -v "$PWD/myschema.json:/app/schema.json" -p "3000:3000" jormaechea/op
- [x] Request and response logging
- [x] Servers basepath support
- [x] Support x-faker and x-count extension methods to customise generated responses
- [x] Support for `latency` and `latencyRate` to introduce random latency to the response. Latency is number of milliseconds to wait before returning the response, rate is a number between 0-100 to specify when to add this behaviour.
- [x] Support for `statusCode` and `statuscodeRate` to introduce a different statuscode to the response. Specify the statusCode to return and use the rate (a number between 0-100) to specify when to add this behaviour.
- [ ] API Authentication

## Customizing Generated Responses
Expand Down Expand Up @@ -152,6 +154,10 @@ Will produce the following response:
]
```

## Chaos Engineering

Besides generating fake data, you can also specify with the `prefer` header that you would like the mock API to respond in unpredictable ways. You can send different status code or add latency to the response by adding the `statusCode` and/or `latency` parameters inside the `prefer` header. Using the `statusCodeRate` and `latencyRate` parameters you can specify when to add these behaviours. Use a value between 0 and 100 (%) to add less or more randomness.

## Advanced usage

See the [advanced usage docs](docs/README.md) to extend or build your own app upon OpenAPI Mocker.
Expand Down
23 changes: 17 additions & 6 deletions lib/mocker/express/request-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,20 @@ const colors = require('colors');

// Create a function that is memoized using the URL, query, the Prefer header and the body.
// eslint-disable-next-line no-unused-vars
const getResponse = (path, url, query, preferHeader, body) => {
const { example: preferredExampleName, statuscode: preferredStatusCode } = parsePreferHeader(preferHeader) || {};
const getResponse = async (path, url, query, preferHeader, body) => {
const {
example: preferredExampleName, statuscode: preferredStatusCode,
statuscoderate: preferredStatusCodeRate, latency: preferredLatency,
latencyrate: preferredLatencyRate
} = parsePreferHeader(preferHeader) || {};

if(preferredStatusCode)
logger.debug(`Searching requested response with status code ${preferredStatusCode}`);
else
logger.debug('Searching first response');
return path.getResponse(preferredStatusCode, preferredExampleName);

const response = await path.getResponse(preferredStatusCode, preferredStatusCodeRate, preferredExampleName, preferredLatency, preferredLatencyRate);
return new Promise(resolve => resolve(response));
};

const getResponseMemo = memoize(getResponse, {
Expand All @@ -29,7 +35,7 @@ const checkContentType = req => {
logger.warn(`${colors.yellow('*')} Missing content-type header`);
};

const handleRequest = (path, responseHandler) => (req, res) => {
const handleRequest = (path, responseHandler) => async (req, res) => {

checkContentType(req);

Expand All @@ -54,8 +60,13 @@ const handleRequest = (path, responseHandler) => (req, res) => {

const preferHeader = req.header('prefer') || '';

const { statusCode, headers: responseHeaders, body, responseMimeType } =
getResponseMemo(path, req.path, JSON.stringify(req.query), preferHeader, JSON.stringify(requestBody));
let response;
if(preferHeader)
response = await getResponse(path, req.path, JSON.stringify(req.query), preferHeader, JSON.stringify(requestBody));
else
response = await getResponseMemo(path, req.path, JSON.stringify(req.query), preferHeader, JSON.stringify(requestBody));

const { statusCode, headers: responseHeaders, body, responseMimeType } = response;

return responseHandler(req, res, body, statusCode, responseHeaders, responseMimeType);
};
Expand Down
27 changes: 22 additions & 5 deletions lib/paths/path.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,31 +197,48 @@ class Path {
return !possibleValues || !possibleValues.length || possibleValues.includes(value);
}

getResponse(preferredStatusCode, preferredExampleName) {
delay(time) {
return new Promise(resolve => setTimeout(resolve, time));
}

async getResponse(preferredStatusCode, preferredStatusCodeRate, preferredExampleName, preferredLatency, preferredLatencyRate) {

const applyLatencyExperiment = preferredLatencyRate ? this.getRandomInt(100) < (preferredLatencyRate) : true;
const applyStatusCodeExperiment = preferredStatusCodeRate ? this.getRandomInt(100) < (preferredStatusCodeRate) : true;

if(applyLatencyExperiment) {
if(preferredLatency)
await this.delay(preferredLatency);
}
const {
statusCode,
headers,
schema,
responseMimeType
} = preferredStatusCode ? this.getResponseByStatusCode(preferredStatusCode) : this.getFirstResponse();
} = preferredStatusCode && applyStatusCodeExperiment ? this.getResponseByStatusCode(preferredStatusCode) : this.getFirstResponse();

return {
const response = {
statusCode: Number(statusCode),
headers: headers && this.generateResponseHeaders(headers),
body: schema ? ResponseGenerator.generate(schema, preferredExampleName) : null,
responseMimeType
};

return new Promise(resolve => resolve(response));
}

getRandomInt(max) {
return Math.floor(Math.random() * max) + 1;
}

getResponseByStatusCode(statusCode) {

if(!this.responses[statusCode]) {
if(!this.responses[statusCode] && !this.responses.default) {
logger.warn(`Could not find a response for status code ${statusCode}. Responding with first response`);
return this.getFirstResponse();
}

const preferredResponse = this.responses[statusCode];
const preferredResponse = this.responses[statusCode] || this.responses.default;

const [[responseMimeType, responseContent] = []] = Object.entries(preferredResponse.content || {});

Expand Down
Loading