Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

smoke

Package Overview
Dependencies
Maintainers
2
Versions
17
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

smoke - npm Package Compare versions

Comparing version 1.4.0 to 2.0.0

bin/smoke-conv

7

CHANGELOG.md

@@ -0,1 +1,8 @@

# 2.0.0
- Add support for single file mock collections
- Add mock collection conversion utility
## Breaking changes
- Change mock set prefix to `__`
# 1.4.0

@@ -2,0 +9,0 @@ - Add support for query params matching

111

lib/mock.js
const path = require('path');
const fs = require('fs-extra');
const globby = require('globby');
const pathToRegexp = require('path-to-regexp');
const importFresh = require('import-fresh');
const methodRegExp = /([a-zA-Z+]+?){1}_.+/i;
const paramsRegExp = /\$([^.\s]+)/;
const paramsRegExp = /[$?]([^.\s]+)/;
const setRegExp = /__([\w-]+?)$/;
const defaultType = 'application/octet-stream';
function getMock(basePath, file) {
class MockContentError extends Error {
constructor(message, error) {
super(message);
this.innerError = error;
}
toString() {
return `${this.message}: ${this.innerError.message}`;
}
}
function getMock(basePath, file, data = undefined) {
let ext = path.extname(file);
const isTemplate = ext.endsWith('_');
let basename = path.basename(file, ext);
const set = path.extname(basename);
ext = ext ? ext.substring(1, ext.length - (isTemplate ? 1 : 0)).toLowerCase() : ext;
if (set) {
basename = path.basename(basename, set);
if (data !== undefined) {
ext = typeof data === 'function' ? 'js' : ext !== null && typeof data === 'object' ? 'json' : ext;
}
let set = null;
const matchSet = basename.match(setRegExp);
if (matchSet) {
set = matchSet[1];
basename = path.basename(basename, matchSet[0]);
}
let params = null;

@@ -60,6 +81,7 @@ const matchParams = basename.match(paramsRegExp);

return {
originalFile: file,
file: path.join(basePath, file),
ext,
type: ext || defaultType,
set: set ? set.substring(1) : set,
set,
isTemplate,

@@ -70,3 +92,4 @@ methods,

keys,
params
params,
data
};

@@ -76,23 +99,77 @@ }

async function getMocks(basePath, ignoreGlobs, globs = ['**/*']) {
if (!basePath) {
globs.push('!node_modules');
}
// Ensure relative paths for ignore globs
ignoreGlobs = ignoreGlobs.map(glob => `!${path.isAbsolute(glob) ? path.relative(basePath, glob) : glob}`);
let mockFiles = await globby(globs.concat(ignoreGlobs), {cwd: basePath});
const singleFileMocks = [];
let mockFiles = await getMockFiles(basePath, ignoreGlobs, globs);
const mockCollectionFiles = [];
mockFiles = mockFiles.filter(mock => {
if (mock.endsWith('.mocks.js')) {
singleFileMocks.push(mock);
mockCollectionFiles.push(mock);
return false;
}
return true;
});
return mockFiles.map(file => getMock(basePath, file));
return mockFiles.map(file => getMock(basePath, file)).concat(getMocksFromCollections(basePath, mockCollectionFiles));
}
function getMockFiles(basePath, ignoreGlobs, globs) {
if (!basePath) {
globs.push('!node_modules');
}
// Ensure relative paths for ignore globs
ignoreGlobs = ignoreGlobs.map(glob => `!${path.isAbsolute(glob) ? path.relative(basePath, glob) : glob}`);
return globby(globs.concat(ignoreGlobs), {cwd: basePath});
}
function getMocksFromCollections(basePath, mockCollectionFiles) {
return mockCollectionFiles.reduce((mocks, file) => {
try {
basePath = path.isAbsolute(basePath) ? basePath : path.join(process.cwd(), basePath);
const collection = importFresh(path.join(basePath, file));
const newMocks = Object.entries(collection).map(([route, data]) => getMock(basePath, route, data));
return mocks.concat(newMocks);
} catch (error) {
console.error(`Error while loading collection "${file}"`, error);
return mocks;
}
}, []);
}
async function getMockContent(mock) {
let content;
if (mock.data !== undefined) {
content = mock.data;
} else if (mock.isTemplate || mock.ext === 'json') {
try {
content = await fs.readFile(mock.file, 'utf-8');
} catch (error) {
throw new MockContentError(`Error while reading mock file "${mock.file}"`, error);
}
} else if (mock.ext === 'js') {
try {
const filePath = path.isAbsolute(mock.file) ? mock.file : path.join(process.cwd(), mock.file);
content = importFresh(filePath);
} catch (error) {
throw new MockContentError(`Error while evaluating JS for mock "${mock.file}"`);
}
} else {
try {
// Read file as buffer
content = await fs.readFile(mock.file);
} catch (error) {
throw new MockContentError(`Error while reading mock file "${mock.file}"`);
}
}
return content;
}
module.exports = {
MockContentError,
getMock,
getMocks,
getMock
getMockFiles,
getMocksFromCollections,
getMockContent
};

3

lib/recorder.js

@@ -31,3 +31,3 @@ const path = require('path');

if (options.set) {
file += '.' + options.set;
file += '__' + options.set;
}

@@ -73,3 +73,4 @@

module.exports = {
isStringContent,
record
};

@@ -1,6 +0,3 @@

const path = require('path');
const fs = require('fs-extra');
const importFresh = require('import-fresh');
const {render} = require('./template');
const {MockContentError, getMockContent} = require('./mock');

@@ -15,3 +12,3 @@ function getResponseDetails(response, statusCode) {

if (typeof response === 'object' && hasProperty('statusCode') && hasProperty('body')) {
if (response !== null && typeof response === 'object' && hasProperty('statusCode') && hasProperty('body')) {
details.statusCode = response.statusCode || details.statusCode;

@@ -36,4 +33,4 @@ details.headers = response.headers || details.headers;

async function respondMock(res, mock, data, statusCode = null) {
let result;
async function getMockResponse(mock, data) {
let response = await getMockContent(mock);

@@ -46,41 +43,36 @@ // Response depends of input file type:

if (mock.isTemplate || mock.ext === 'json') {
if (mock.isTemplate) {
try {
result = await fs.readFile(mock.file, 'utf-8');
response = render(response, data);
} catch (error) {
return internalError(res, `Error while reading mock file "${mock.file}"`, error);
throw new MockContentError(`Error while processing template for mock file "${mock.file}"`, error);
}
}
if (mock.isTemplate) {
try {
result = render(result, data);
} catch (error) {
return internalError(res, `Error while processing template for mock file "${mock.file}"`, error);
}
}
if (mock.ext === 'json') {
try {
result = result ? JSON.parse(result) : undefined;
} catch (error) {
return internalError(res, `Error while parsing JSON for mock "${mock.file}"`, error);
}
}
} else if (mock.ext === 'js') {
if (mock.ext === 'json' && typeof response === 'string') {
try {
const filePath = path.isAbsolute(mock.file) ? mock.file : path.join(process.cwd(), mock.file);
result = importFresh(filePath)(data);
response = response ? JSON.parse(response) : undefined;
} catch (error) {
return internalError(res, `Error while evaluating JS for mock "${mock.file}"`, error);
throw new MockContentError(`Error while parsing JSON for mock "${mock.file}"`, error);
}
} else {
} else if (mock.ext === 'js') {
try {
// Read file as buffer
result = await fs.readFile(mock.file);
response = response(data);
} catch (error) {
return internalError(res, `Error while reading mock file "${mock.file}"`, error);
throw new MockContentError(`Error while evaluating JS for mock "${mock.file}"`);
}
}
const details = getResponseDetails(result, statusCode);
return response;
}
async function respondMock(res, mock, data, statusCode = null) {
let response;
try {
response = await getMockResponse(mock, data);
} catch (error) {
return internalError(res, error.message, error.innerError);
}
const details = getResponseDetails(response, statusCode);
const needType =

@@ -100,3 +92,4 @@ Object.getOwnPropertyNames(details.headers)

module.exports = {
getMockResponse,
respondMock
};
{
"name": "smoke",
"version": "1.4.0",
"version": "2.0.0",
"description": "Simple yet powerful file-based mock server with recording abilities",
"main": "smoke.js",
"bin": {
"smoke": "./bin/smoke"
"smoke": "./bin/smoke",
"smoke-conv": "./bin/smoke-conv"
},

@@ -31,5 +32,5 @@ "scripts": {

"express": "^4.16.4",
"express-http-proxy": "^1.5.0",
"express-http-proxy": "^1.5.1",
"fs-extra": "^7.0.1",
"globby": "^8.0.2",
"globby": "^9.0.0",
"import-fresh": "^3.0.0",

@@ -41,8 +42,8 @@ "lodash.template": "^4.4.0",

"multer": "^1.4.1",
"path-to-regexp": "^2.4.0"
"path-to-regexp": "^3.0.0"
},
"devDependencies": {
"jest": "^23.6.0",
"supertest": "^3.3.0",
"xo": "^0.23.0"
"supertest": "^3.4.1",
"xo": "^0.24.0"
},

@@ -60,2 +61,10 @@ "xo": {

},
"jest": {
"collectCoverageFrom": [
"*.js",
"lib/**/*.js"
],
"silent": true,
"verbose": true
},
"engines": {

@@ -62,0 +71,0 @@ "node": ">=8.0.0"

@@ -74,3 +74,3 @@ # :dash: smoke

**General format:** `methods_api#route#:routeParam$queryParam=value=.set.extension`
**General format:** `methods_api#route#:routeParam$queryParam=value.__et.extension`

@@ -120,5 +120,5 @@ The path and file name of the mock is used to determinate:

#### Mock set
You can optionally specify a mock set before the file extension by using a `.set-name` suffix after the file name.
You can optionally specify a mock set before the file extension by using a `__set-name` suffix after the file name.
For example `get_api#hello.error.json` will only be used if you start the server with the `error` set enabled:
For example `get_api#hello__error.json` will only be used if you start the server with the `error` set enabled:
`smoke --set error`.

@@ -301,2 +301,34 @@

### Single file mock collection
You can regroup multiple mocks in a special single file with the extension `.mocks.js`, using this format:
```js
module.exports = {
'<file_name>': '<file_content>' // can be a string, an object (custom response) or a function (JavaScript mock)
};
```
See this [example mock collection](test/mocks/collection.mocks.js) to get an idea of all possibilities.
The format of file name is the same as for individual mock files, and will be used to match the request using the same
rules. As for the mock content, the format is also the same as what you would put in single file mock. If a request
matches both a mock file and a mock within a collection with the same specificity, the mock file will always be used
over the collection.
As the format is the same, you can convert a bunch of files to a single file mock collection and conversely.
To convert separate mock files to a collection:
```sh
smoke-conv <glob> <output_file> // Will create <output_file>.mocks.js from all mocks found
```
To convert a mock collection to separate files:
```sh
smoke-conv <file> <output_folder> // Will extract separate mocks into <output_folder>
```
Note that only text-based file content will be inserted directly, other file content will be converted to a base64
string.
:warning: There is a limitation regarding JavaScript mocks: only the exported function will be converted for a given
mock, meaning that if you have non-exported functions, variables or imports they will be lost during the conversion.
## Other mock servers

@@ -303,0 +335,0 @@

@@ -93,3 +93,4 @@ const path = require('path');

if (accept && matchMock(mock, method, options.set, query)) {
const score = (mock.methods ? 1 : 0) + (mock.set ? 2 : 0) + (mock.params ? 4 : 0);
const score =
(mock.methods ? 1 : 0) + (mock.set ? 2 : 0) + (mock.params ? 4 : 0) + (mock.data === undefined ? 0.5 : 0);
allMatches.push({match, mock, score});

@@ -111,2 +112,3 @@ }

}
return proxyResData;

@@ -118,3 +120,3 @@ }

// Search for 404 mocks, matching accept header
const notFoundMocks = await getMocks(options.basePath, ignore, options.notFound);
const notFoundMocks = await getMocks(options.basePath, ignore, [options.notFound]);
const types = notFoundMocks.length > 0 ? notFoundMocks.map(mock => mock.type) : null;

@@ -131,4 +133,5 @@ const accept = types && req.accepts(types);

} else {
const accept = req.accepts(matches.map(match => match.mock.type));
const {match, mock} = matches.filter(match => accept === match.mock.type).sort((a, b) => b.score - a.score)[0];
const sortedMatches = matches.sort((a, b) => b.score - a.score);
const accept = req.accepts(sortedMatches.map(match => match.mock.type));
const {match, mock} = sortedMatches.filter(match => accept === match.mock.type)[0];

@@ -142,2 +145,3 @@ // Fill in route params

}
next();

@@ -144,0 +148,0 @@ };

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc