serverless-mocha-plugin
Advanced tools
Comparing version 1.0.3 to 1.1.0
348
index.js
@@ -8,14 +8,23 @@ 'use strict'; | ||
const path = require('path'), | ||
fs = require('fs'), | ||
lambdaWrapper = require('lambda-wrapper'), | ||
Mocha = require('mocha'), | ||
chai = require('chai'), | ||
ejs = require('ejs'), | ||
utils = require('./utils.js'), | ||
BbPromise = require('bluebird'); // Serverless uses Bluebird Promises and we recommend you do to because they provide more than your average Promise :) | ||
const path = require('path'); | ||
const fs = require('fs'); | ||
const lambdaWrapper = require('lambda-wrapper'); | ||
const Mocha = require('mocha'); | ||
const chai = require('chai'); | ||
const ejs = require('ejs'); | ||
const fse = require('fs-extra'); | ||
const utils = require('./utils'); | ||
const BbPromise = require('bluebird'); | ||
const yamlEdit = require('yaml-edit'); | ||
const testFolder = 'test'; // Folder used my mocha for tests | ||
const templateFilename = 'sls-mocha-plugin-template.ejs'; | ||
const testTemplateFile = path.join('templates', 'test-template.ejs'); | ||
const functionTemplateFile = path.join('templates', 'function-template.ejs'); | ||
const validFunctionRuntimes = [ | ||
'aws-nodejs4.3', | ||
]; | ||
const humanReadableFunctionRuntimes = `${validFunctionRuntimes | ||
.map((template) => `"${template}"`).join(', ')}`; | ||
class mochaPlugin { | ||
@@ -33,3 +42,3 @@ constructor(serverless, options) { | ||
lifecycleEvents: [ | ||
'test' | ||
'test', | ||
], | ||
@@ -41,6 +50,23 @@ options: { | ||
required: true, | ||
} | ||
} | ||
} | ||
} | ||
}, | ||
}, | ||
}, | ||
function: { | ||
usage: 'Create a function into the service', | ||
lifecycleEvents: [ | ||
'create', | ||
], | ||
options: { | ||
function: { | ||
usage: 'Name of the function', | ||
shortcut: 'f', | ||
required: true, | ||
}, | ||
handler: { | ||
usage: 'Handler for the function (e.g. --handler my-function/index.handler)', | ||
required: true, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
@@ -53,3 +79,3 @@ invoke: { | ||
lifecycleEvents: [ | ||
'test' | ||
'test', | ||
], | ||
@@ -65,13 +91,13 @@ options: { | ||
shortcut: 'R', | ||
required: false | ||
required: false, | ||
}, | ||
"reporter-options": { | ||
'reporter-options': { | ||
usage: 'Options for mocha reporter', | ||
shortcut: 'O', | ||
required: false | ||
} | ||
} | ||
} | ||
} | ||
} | ||
required: false, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}; | ||
@@ -81,54 +107,62 @@ | ||
'create:test:test': () => { | ||
BbPromise.bind(this) | ||
.then(this.createTest); | ||
BbPromise.bind(this) | ||
.then(this.createTest); | ||
}, | ||
'invoke:test:test': () => { | ||
BbPromise.bind(this) | ||
.then(this.runTests); | ||
} | ||
} | ||
BbPromise.bind(this) | ||
.then(this.runTests); | ||
}, | ||
'create:function:create': () => { | ||
BbPromise.bind(this) | ||
.then(this.createFunction) | ||
.then(this.createTest); | ||
}, | ||
}; | ||
} | ||
runTests() { | ||
let _this = this; | ||
let funcName = this.options.f || this.options.function || []; | ||
let testFileMap = {}; | ||
let mocha = new Mocha({timeout: 6000}); | ||
const myModule = this; | ||
const funcName = this.options.f || this.options.function || []; | ||
const testFileMap = {}; | ||
const mocha = new Mocha({ | ||
timeout: 6000, | ||
}); | ||
let stage = this.options.stage; | ||
let region = this.options.region; | ||
const stage = this.options.stage; | ||
const region = this.options.region; | ||
this.serverless.service.load({ | ||
stage: stage, | ||
region: region | ||
stage, | ||
region, | ||
}) | ||
.then( (inited) => { | ||
_this.serverless.environment = inited.environment; | ||
.then((inited) => { | ||
myModule.serverless.environment = inited.environment; | ||
_this.getFunctions(funcName) | ||
myModule.getFunctions(funcName) | ||
.then(utils.getTestFiles) | ||
.then((funcs) => { | ||
let funcNames = Object.keys(funcs); | ||
const funcNames = Object.keys(funcs); | ||
if (funcNames.length === 0) { | ||
return _this.serverless.cli.log("No tests to run"); | ||
return myModule.serverless.cli.log('No tests to run'); | ||
} | ||
funcNames.forEach(function(func) { | ||
_this.setEnvVars(func, { | ||
stage: stage, | ||
region: region | ||
funcNames.forEach((func) => { | ||
myModule.setEnvVars(func, { | ||
stage, | ||
region, | ||
}); | ||
testFileMap[func] = funcs[func]; | ||
mocha.addFile(funcs[func].mochaPlugin.testPath); | ||
}) | ||
var reporter = _this.options.reporter; | ||
if ( reporter !== undefined) { | ||
var reporterOptions = {}; | ||
if (_this.options["reporter-options"] !== undefined) { | ||
_this.options["reporter-options"].split(",").forEach(function(opt) { | ||
var L = opt.split("="); | ||
}); | ||
const reporter = myModule.options.reporter; | ||
if (reporter !== undefined) { | ||
const reporterOptions = {}; | ||
if (myModule.options['reporter-options'] !== undefined) { | ||
myModule.options['reporter-options'].split(',').forEach((opt) => { | ||
const L = opt.split('='); | ||
if (L.length > 2 || L.length === 0) { | ||
throw new Error("invalid reporter option '" + opt + "'"); | ||
throw new Error(`invalid reporter option "${opt}"`); | ||
} else if (L.length === 2) { | ||
@@ -141,69 +175,66 @@ reporterOptions[L[0]] = L[1]; | ||
} | ||
mocha.reporter(reporter, reporterOptions) | ||
mocha.reporter(reporter, reporterOptions); | ||
} | ||
mocha.run(function(failures){ | ||
process.on('exit', function () { | ||
mocha.run((failures) => { | ||
process.on('exit', () => { | ||
process.exit(failures); // exit with non-zero status if there were failures | ||
}); | ||
}) | ||
.on('suite', function(suite) { | ||
let funcName = utils.funcNameFromPath(suite.file); | ||
let func = testFileMap[funcName]; | ||
.on('suite', (suite) => { | ||
const testFuncName = utils.funcNameFromPath(suite.file); | ||
const func = testFileMap[testFuncName]; | ||
if (func) { | ||
_this.setEnvVars(func, { | ||
stage: stage, | ||
region: region | ||
}); | ||
} | ||
}) | ||
.on('end', (e) => { | ||
}); | ||
}, function(error) { | ||
return _this.serverless.cli.log(error); | ||
}); | ||
}); | ||
if (func) { | ||
myModule.setEnvVars(func, { | ||
stage, | ||
region, | ||
}); | ||
} | ||
}); | ||
return null; | ||
}, (error) => myModule.serverless.cli.log(error) | ||
); | ||
}); | ||
} | ||
createTest() { | ||
let funcName = this.options.f || this.options.function; | ||
let _this = this; | ||
const funcName = this.options.f || this.options.function; | ||
const myModule = this; | ||
utils.createTestFolder().then(function(testFolder) { | ||
let testFilePath = utils.getTestFilePath(funcName); | ||
let servicePath = _this.serverless.config.servicePath; | ||
let func = _this.serverless.service.functions[funcName]; | ||
let handlerParts = func.handler.split('.'); | ||
let funcPath = (handlerParts[0] + '.js').replace(/\\/g, "/"); | ||
let funcCall = handlerParts[1]; | ||
utils.createTestFolder().then((testFolder) => { | ||
const testFilePath = utils.getTestFilePath(funcName); | ||
const func = myModule.serverless.service.functions[funcName]; | ||
const handlerParts = func.handler.split('.'); | ||
const funcPath = (`${handlerParts[0]}.js`).replace(/\\/g, '/'); | ||
const funcCall = handlerParts[1]; | ||
fs.exists(testFilePath, function (exists) { | ||
fs.exists(testFilePath, (exists) => { | ||
if (exists) { | ||
_this.serverless.cli.log(`Test file ${testFilePath} already exists`) | ||
myModule.serverless.cli.log(`Test file ${testFilePath} already exists`); | ||
return (new Error(`File ${testFilePath} already exists`)); | ||
} | ||
let templateFilenamePath = path.join(testFolder, templateFilename); | ||
fs.exists(templateFilenamePath, function (exists) { | ||
if (! exists) { | ||
templateFilenamePath = path.join(__dirname, templateFilename); | ||
let templateFilenamePath = path.join(testFolder, testTemplateFile); | ||
fs.exists(templateFilenamePath, (exists2) => { | ||
if (!exists2) { | ||
templateFilenamePath = path.join(__dirname, testTemplateFile); | ||
} | ||
let templateString = utils.getTemplateFromFile(templateFilenamePath); | ||
const templateString = utils.getTemplateFromFile(templateFilenamePath); | ||
let content = ejs.render(templateString, { | ||
'functionName': funcName, | ||
'functionPath': funcPath, | ||
'handlerName': funcCall | ||
const content = ejs.render(templateString, { | ||
functionName: funcName, | ||
functionPath: funcPath, | ||
handlerName: funcCall, | ||
}); | ||
fs.writeFile(testFilePath, content, function(err) { | ||
fs.writeFile(testFilePath, content, (err) => { | ||
if (err) { | ||
_this.serverless.cli.log(`Creating file ${testFilePath} failed: ${err}`); | ||
myModule.serverless.cli.log(`Creating file ${testFilePath} failed: ${err}`); | ||
return new Error(`Creating file ${testFilePath} failed: ${err}`); | ||
} | ||
return _this.serverless.cli.log(`serverless-mocha-plugin: created ${testFilePath}`); | ||
}) | ||
return myModule.serverless.cli.log(`serverless-mocha-plugin: created ${testFilePath}`); | ||
}); | ||
}); | ||
return null; | ||
}); | ||
@@ -214,21 +245,20 @@ }); | ||
// Helper functions | ||
getFunctions(funcNames) { | ||
let _this = this; | ||
return new BbPromise(function(resolve, reject) { | ||
let funcObjs = {}; | ||
let allFuncs = _this.serverless.service.functions; | ||
if (typeof(funcNames) === 'string') { | ||
funcNames = [ funcNames ]; | ||
const myModule = this; | ||
let funcList = funcNames; | ||
return new BbPromise((resolve) => { | ||
const funcObjs = {}; | ||
const allFuncs = myModule.serverless.service.functions; | ||
if (typeof funcNames === 'string') { | ||
funcList = [funcNames]; | ||
} | ||
if (funcNames.length === 0) { | ||
let sFuncs = allFuncs; | ||
return resolve(sFuncs); | ||
return resolve(allFuncs); | ||
} | ||
let func; | ||
funcNames.forEach(function(funcName, idx) { | ||
funcList.forEach((funcName) => { | ||
func = allFuncs[funcName]; | ||
@@ -238,6 +268,8 @@ if (func) { | ||
} else { | ||
_this.serverless.cli.log(`Warning: Could not find function '${funcName}'.`); | ||
myModule.serverless.cli.log(`Warning: Could not find function '${funcName}'.`); | ||
} | ||
}); | ||
resolve(funcObjs); | ||
return null; | ||
}); | ||
@@ -253,3 +285,4 @@ } | ||
if (options.region) { | ||
utils.setEnv(this.serverless.environment.stages[options.stage].regions[options.region].vars); | ||
utils.setEnv(this.serverless.environment.stages[options.stage] | ||
.regions[options.region].vars); | ||
} | ||
@@ -259,7 +292,88 @@ } | ||
} | ||
createAWSNodeJSFuncFile(handlerPath) { | ||
const handlerInfo = path.parse(handlerPath); | ||
const handlerDir = path.join(this.serverless.config.servicePath, handlerInfo.dir); | ||
const handlerFile = `${handlerInfo.name}.js`; | ||
const handlerFunction = handlerInfo.ext.replace(/^\./, ''); | ||
const templateText = fse.readFileSync(path.join(__dirname, functionTemplateFile)).toString(); | ||
const jsFile = ejs.render(templateText, { | ||
handlerFunction, | ||
}); | ||
const filePath = path.join(handlerDir, handlerFile); | ||
this.serverless.utils.writeFileDir(filePath); | ||
if (this.serverless.utils.fileExistsSync(filePath)) { | ||
const errorMessage = [ | ||
`File "${filePath}" already exists. Cannot create function.`, | ||
].join(''); | ||
throw new this.serverless.classes.Error(errorMessage); | ||
} | ||
fse.writeFileSync(path.join(handlerDir, handlerFile), jsFile); | ||
this.serverless.cli.log(`Created function file "${path.join(handlerDir, handlerFile)}"`); | ||
return BbPromise.resolve(); | ||
} | ||
createFunction() { | ||
this.serverless.cli.log('Generating function…'); | ||
const functionName = this.options.function; | ||
const handler = this.options.handler; | ||
const serverlessYmlFilePath = path | ||
.join(this.serverless.config.servicePath, 'serverless.yml'); | ||
const serverlessYmlFileContent = fse | ||
.readFileSync(serverlessYmlFilePath).toString(); | ||
return this.serverless.yamlParser.parse(serverlessYmlFilePath) | ||
.then((config) => { | ||
const runtime = [config.provider.name, config.provider.runtime].join('-'); | ||
if (validFunctionRuntimes.indexOf(runtime) < 0) { | ||
const errorMessage = [ | ||
`Provider / Runtime "${runtime}" is not supported.`, | ||
` Supported runtimes are: ${humanReadableFunctionRuntimes}.`, | ||
].join(''); | ||
throw new this.serverless.classes.Error(errorMessage); | ||
} | ||
const ymlEditor = yamlEdit(serverlessYmlFileContent); | ||
if (ymlEditor.hasKey(`functions.${functionName}`)) { | ||
const errorMessage = [ | ||
`Function "${functionName}" already exists. Cannot create function.`, | ||
].join(''); | ||
throw new this.serverless.classes.Error(errorMessage); | ||
} | ||
const funcDoc = {}; | ||
funcDoc[functionName] = this.serverless.service.functions[functionName] = { | ||
handler, | ||
}; | ||
if (ymlEditor.insertChild('functions', funcDoc)) { | ||
const errorMessage = [ | ||
`Could not find functions in ${serverlessYmlFilePath}`, | ||
].join(''); | ||
throw new this.serverless.classes.Error(errorMessage); | ||
} | ||
fse.writeFileSync(serverlessYmlFilePath, ymlEditor.dump()); | ||
if (runtime === 'aws-nodejs4.3') { | ||
return this.createAWSNodeJSFuncFile(handler); | ||
} | ||
return BbPromise.resolve(); | ||
}); | ||
} | ||
} | ||
module.exports = mochaPlugin; | ||
module.exports.lambdaWrapper = lambdaWrapper; | ||
module.exports.chai = chai; |
{ | ||
"name": "serverless-mocha-plugin", | ||
"version": "1.0.3", | ||
"version": "1.1.0", | ||
"engines": { | ||
@@ -35,4 +35,16 @@ "node": ">=4.0" | ||
"devDependencies": { | ||
"chai": "^3.2.0", | ||
"eslint": "^3.3.1", | ||
"eslint-config-airbnb": "^10.0.1", | ||
"eslint-config-airbnb-base": "^5.0.2", | ||
"eslint-plugin-import": "^1.13.0", | ||
"eslint-plugin-jsx-a11y": "^2.1.0", | ||
"eslint-plugin-react": "^6.1.1", | ||
"mocha": "^2.2.5", | ||
"serverless": "^1.1.0", | ||
"sinon": "^1.17.6" | ||
}, | ||
"dependencies": { | ||
"aws-sdk": "^2.4.0", | ||
"bluebird": "3.0.6", | ||
@@ -43,4 +55,5 @@ "chai": "3.2.0", | ||
"mocha": "2.2.5", | ||
"aws-sdk": "^2.4.0" | ||
"fs-extra": "^1.0.0", | ||
"yaml-edit": "^0.1.3" | ||
} | ||
} |
@@ -13,2 +13,3 @@ # Serverless Mocha Plugin | ||
* It provides commands to create and run tests manually | ||
* It provides a command to create a function, which automatically also creates a test | ||
@@ -20,3 +21,3 @@ ## Installation | ||
```bash | ||
npm install --save-dev serverless-mocha-plugin@1.0 | ||
npm install --save-dev serverless-mocha-plugin | ||
``` | ||
@@ -33,7 +34,22 @@ | ||
### Creating functions | ||
Functions (and associated tests) can be created using the command | ||
``` | ||
sls create function -f functionName --handler handler | ||
``` | ||
e.g. | ||
``` | ||
sls create function -f myFunction --handler functions/myFunction/index.handler | ||
``` | ||
creates a new function `myFunction` into `serverless.yml` with a code template for | ||
the handler in `functions/myFunction/index.js` and a Javascript function `module.exports.handler` | ||
as the entrypoint for the Lambda function. A test template is also created into `test/myFunction.js`. | ||
### Creating tests | ||
When the plug-in is installed, tests are automatically created to the test/ directory | ||
when creating new functions (only when using node 4.3 runtime). | ||
Functions can also be added manually using the mocha-create command | ||
@@ -81,17 +97,7 @@ | ||
## Release History | ||
## Release History (1.x) | ||
* 2016/11/09 - v1.1.0 - Added function create command. | ||
* 2016/09/23 - v1.0.2 - Bugfixes, configurable test timeouts | ||
* 2016/08/15 - v1.0.0 - Preliminary version for Serverless 1.0 | ||
* 2016/06/28 - v0.5.13 - Increase default test timeout to 6000ms | ||
* 2016/06/27 - v0.5.12 - Add support for using template test files | ||
* 2016/06/22 - v0.5.11 - Add support for running tests from live environment | ||
* 2016/06/21 - v0.5.9 - Prompt for region / stage when running tests. Set environment separately for each test | ||
* 2016/06/03 - v0.5.7 - Fix entangled function tests, Move wrapper.init into 'it' scope in generated mocha test code. | ||
- Fix non-posix path separator in Windows. | ||
- set environment variables correctly also when running all tests | ||
* 2016/05/10 - v0.5.5 - Fix error message for mocha-create. | ||
- Create tests with mocha-create without path in test name (as function create does) | ||
* 2016/05/09 - v0.5.3 - Set environment variables during mocha-run (by AniKo) | ||
- Add reporter options, return non-zero status for failures (by chouandy) | ||
* 2016/04/09 - v0.5.0 - Initial version of module for serverless 0.5.* | ||
@@ -98,0 +104,0 @@ ## License |
71
utils.js
'use strict'; | ||
const BbPromise = require('bluebird'), | ||
path = require('path'), | ||
fs = require('fs'); | ||
const BbPromise = require('bluebird'); | ||
const path = require('path'); | ||
const fs = require('fs'); | ||
const testFolder = 'test'; // Folder used my mocha for tests | ||
const templateFilename = 'sls-mocha-plugin-template.ejs'; | ||
function getTestFilePath(funcName) { | ||
return path.join(testFolder, `${funcName.replace(/.*\//g, '')}.js`); | ||
} | ||
// getTestFiles. If no functions provided, returns all files | ||
function getTestFiles(funcs) { | ||
return new BbPromise(function(resolve, reject) { | ||
var funcNames = Object.keys(funcs); | ||
return new BbPromise((resolve) => { | ||
const funcNames = Object.keys(funcs); | ||
const resFuncs = funcs; | ||
if (funcNames && (funcNames.length > 0)) { | ||
funcNames.forEach(function(val, idx) { | ||
funcs[val].mochaPlugin = { | ||
testPath: getTestFilePath(val) | ||
funcNames.forEach((val) => { | ||
resFuncs[val].mochaPlugin = { | ||
testPath: getTestFilePath(val), | ||
}; | ||
}); | ||
return resolve(funcs); | ||
return resolve(resFuncs); | ||
} | ||
@@ -27,8 +32,8 @@ return resolve({}); | ||
function createTestFolder() { | ||
return new BbPromise(function(resolve, reject) { | ||
fs.exists(testFolder, function(exists) { | ||
return new BbPromise((resolve, reject) => { | ||
fs.exists(testFolder, (exists) => { | ||
if (exists) { | ||
return resolve(testFolder); | ||
return resolve(testFolder); | ||
} | ||
fs.mkdir(testFolder, function(err) { | ||
fs.mkdir(testFolder, (err) => { | ||
if (err) { | ||
@@ -38,4 +43,5 @@ return reject(err); | ||
return resolve(testFolder); | ||
}) | ||
}) | ||
}); | ||
return null; | ||
}); | ||
}); | ||
@@ -48,8 +54,4 @@ } | ||
function getTestFilePath(funcName) { | ||
return path.join(testFolder, `${funcName.replace(/.*\//g, '')}.js`); | ||
} | ||
function funcNameFromPath(filePath) { | ||
let data = path.parse(filePath); | ||
const data = path.parse(filePath); | ||
@@ -60,18 +62,21 @@ return data.name; | ||
function setEnv(params) { | ||
const myParams = params; | ||
if (myParams) { | ||
// Do the magic here | ||
} | ||
return null; | ||
// Serverless does not seem to have any logic with regards to variables yet. | ||
//let vars = Object.keys(params); | ||
//vars.forEach((val, idx) => { | ||
// process.env[val] = params[val]; | ||
//}); | ||
// let vars = Object.keys(params); | ||
// vars.forEach((val, idx) => { | ||
// process.env[val] = params[val]; | ||
// }); | ||
} | ||
module.exports = { | ||
getTestFilePath: getTestFilePath, | ||
getTestFiles: getTestFiles, | ||
createTestFolder: createTestFolder, | ||
getTemplateFromFile: getTemplateFromFile, | ||
funcNameFromPath: funcNameFromPath, | ||
setEnv: setEnv | ||
} | ||
getTestFilePath, | ||
getTestFiles, | ||
createTestFolder, | ||
getTemplateFromFile, | ||
funcNameFromPath, | ||
setEnv, | ||
}; |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
26970
19
484
107
8
10
3
1
+ Addedfs-extra@^1.0.0
+ Addedyaml-edit@^0.1.3
+ Addedfs-extra@1.0.0(transitive)
+ Addedgraceful-fs@4.2.11(transitive)
+ Addedjsonfile@2.4.0(transitive)
+ Addedklaw@1.3.1(transitive)
+ Addedyaml-edit@0.1.3(transitive)