Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[INTERNAL] test: improved test coverage #91

Merged
merged 49 commits into from
Jan 9, 2019
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
ff42d8e
[INTERNAL] build: added tests. Slightly refactored build handler method.
romaniam Dec 13, 2018
4d10beb
[FIX] dependencies: Added sinon as dependency
romaniam Dec 13, 2018
f96d4f9
[INTERNAL] serve: added some tests. Slightly refactored the serve com…
romaniam Dec 13, 2018
9849ca1
[INTERNAL] serve: moved the opn module initialisation into a function…
romaniam Dec 13, 2018
38c2333
[INTERNAL] serve: Added additional options tests e.g. open
romaniam Dec 14, 2018
f32bd87
[FIX] serve: key and cert are now passed to getSsslCert func to retri…
romaniam Dec 14, 2018
3105a90
[FIX] serve: fixed failing tests because of added cert and key defaul…
romaniam Dec 14, 2018
32d2a0d
[INTERNAL] serve: added tests for cert and key options
romaniam Dec 14, 2018
f82fd31
[INTERNAL] serve: added tests for config and translate options
romaniam Dec 14, 2018
b266457
[INTERNAL] init: added tests
romaniam Dec 18, 2018
902af47
[INTERNAL] increased code coverage and refactored some modules
romaniam Dec 19, 2018
a508006
[INTERNAL] test: added tests for tree command
romaniam Dec 19, 2018
a2785bf
[INTERNAL] test: added tests for base middleware
romaniam Dec 19, 2018
4fc780e
[INTERNAL] clean + test: refactored middleware, implemented tests for…
romaniam Dec 19, 2018
b4fef79
[INTERNAL] clean + test: refactored versions. added versions tests
romaniam Dec 19, 2018
29fc902
[INTERNAL] Added module lazy loading for init command
romaniam Dec 21, 2018
c2b125f
[INTERNAL] Refactored a few modules
romaniam Jan 3, 2019
30dd0b7
Fix cli/commands/build.js tests
matz3 Jan 3, 2019
e129981
Removed default params
romaniam Jan 3, 2019
bfca920
Merge branch 'increasing-test-coverage' of https://github.com/SAP/ui5…
romaniam Jan 3, 2019
bff7df0
Added mock require. Added mock for opn module in serve tests
romaniam Jan 3, 2019
9a92681
[INTERNAL] Integrated mock-require for commands tests
romaniam Jan 7, 2019
4e515b6
[INTERNAL] build: added tests. Slightly refactored build handler method.
romaniam Dec 13, 2018
10012e8
[FIX] dependencies: Added sinon as dependency
romaniam Dec 13, 2018
9e8beb3
[INTERNAL] serve: added some tests. Slightly refactored the serve com…
romaniam Dec 13, 2018
54ed49e
[INTERNAL] serve: moved the opn module initialisation into a function…
romaniam Dec 13, 2018
2ce5ac2
[INTERNAL] serve: Added additional options tests e.g. open
romaniam Dec 14, 2018
ea8f4de
[FIX] serve: key and cert are now passed to getSsslCert func to retri…
romaniam Dec 14, 2018
6171838
[FIX] serve: fixed failing tests because of added cert and key defaul…
romaniam Dec 14, 2018
a95d063
[INTERNAL] serve: added tests for cert and key options
romaniam Dec 14, 2018
6963558
[INTERNAL] serve: added tests for config and translate options
romaniam Dec 14, 2018
0832adc
[INTERNAL] init: added tests
romaniam Dec 18, 2018
93db6fe
[INTERNAL] increased code coverage and refactored some modules
romaniam Dec 19, 2018
2cf9b15
[INTERNAL] test: added tests for tree command
romaniam Dec 19, 2018
b4c6ddd
[INTERNAL] test: added tests for base middleware
romaniam Dec 19, 2018
43055dc
[INTERNAL] clean + test: refactored middleware, implemented tests for…
romaniam Dec 19, 2018
95accb3
[INTERNAL] clean + test: refactored versions. added versions tests
romaniam Dec 19, 2018
5072c11
[INTERNAL] Added module lazy loading for init command
romaniam Dec 21, 2018
41abd8c
[INTERNAL] Refactored a few modules
romaniam Jan 3, 2019
feafead
Fix cli/commands/build.js tests
matz3 Jan 3, 2019
b3eec89
Removed default params
romaniam Jan 3, 2019
10b711a
Added mock require. Added mock for opn module in serve tests
romaniam Jan 3, 2019
a3b97cc
[INTERNAL] Integrated mock-require for commands tests
romaniam Jan 7, 2019
014d995
Merge branch 'increasing-test-coverage' of https://github.com/SAP/ui5…
romaniam Jan 7, 2019
bdc66e1
[INTERNAL] Replaced mock require with sinon stubs
romaniam Jan 8, 2019
d0a4b38
[INTERNAL] Removed unused function
romaniam Jan 8, 2019
bb3781b
[INTERNAL] All stubs are now restored using sinon.restore()
romaniam Jan 8, 2019
0824ad1
[INTERNAL] Tests: Restore sutubs in test.afterEach.always hook
RandomByte Jan 8, 2019
1a78601
Merge branch 'master' into increasing-test-coverage
romaniam Jan 9, 2019
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
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const ui5Cli = {
init: require("./lib/init/init")
init: require("./lib/init/init").init
};

module.exports = ui5Cli;
25 changes: 3 additions & 22 deletions lib/cli/commands/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,37 +64,18 @@ function handleBuild(argv) {
const logger = require("@ui5/logger");

const command = argv._[argv._.length - 1];
let dev = false;
let selfContained = false;

switch (command) {
case "build":
case "preload":
// Default
// nothing to do (yet)
break;
case "dev":
dev = true;
break;
case "self-contained":
selfContained = true;
break;
default:
throw new Error(`Unhandled command "${command}`);
}

logger.setShowProgress(true);

normalizer.generateProjectTree({
translator: argv.translator,
configPath: argv.config
}).then(function(tree) {
return builder.build({
tree,
tree: tree,
destPath: argv.dest,
buildDependencies: argv.all,
dev,
selfContained,
dev: command === "dev",
selfContained: command === "self-contained",
devExcludeProject: argv["dev-exclude-project"],
includedTasks: argv["include-task"],
excludedTasks: argv["exclude-task"]
Expand Down
41 changes: 13 additions & 28 deletions lib/cli/commands/init.js
Original file line number Diff line number Diff line change
@@ -1,44 +1,29 @@
const validate = require("../../utils/validate");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We move the require statements into the handler function to not load the dependencies all the time.
This had a significant impact e.g. when just calling ui5 --version as all command files need to be required, but not all code in there needs to be executed.

A better approach might be to move the handler functions in a separate module that is then required from the yargs handler function once it's called.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good points, thanks. Personally I would prefer not to move the handler to a separate module. How about this approach: 29fc902 ? (just saw the async flag which is not required, will remove it after your next review)

const init = require("../../init/init");
const {promisify} = require("util");
const path = require("path");
const fs = require("fs");
const jsYaml = require("js-yaml");

// Init
const init = {
const initCommand = {
command: "init",
describe: "Initialize the UI5 Build and Development Tooling configuration for an application or library project.",
middlewares: [require("../middlewares/base.js")]
};

init.handler = async function(argv) {
const init = require("../../init/init");
const {promisify} = require("util");
const path = require("path");
const fs = require("fs");
const stat = promisify(fs.stat);
initCommand.handler = async function() {
const writeFile = promisify(fs.writeFile);
const safeDumpYaml = require("js-yaml").safeDump;

async function exists(filePath) {
try {
await stat(filePath);
return true;
} catch (err) {
// "File or directory does not exist"
if (err.code === "ENOENT") {
return false;
} else {
throw err;
}
}
}

try {
const yamlPath = path.resolve("./ui5.yaml");
if (await exists(yamlPath)) {
if (await validate.exists(yamlPath)) {
throw new Error("Initialization not possible: ui5.yaml already exists");
}

const projectConfig = await init();
const yaml = safeDumpYaml(projectConfig);
const projectConfig = await init.init();
const yaml = jsYaml.safeDump(projectConfig);

await writeFile(yamlPath, yaml);

console.log(`Wrote ui5.yaml to ${yamlPath}:\n`);
console.log(yaml);
} catch (err) {
Expand All @@ -47,4 +32,4 @@ init.handler = async function(argv) {
}
};

module.exports = init;
module.exports = initCommand;
48 changes: 27 additions & 21 deletions lib/cli/commands/serve.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,12 @@ serve.builder = function(cli) {
"Listen to port 1337 and launch default browser with http://localhost:1337/test/QUnit.html");
};

serve.handler = function(argv) {
serve.openUrl = async function(browserUrl) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, adding a function just to make testing easier should be avoided if possible. What about mock-require?

The function is called just one time, so no real need to have it in a separate function.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How should we test the functionality of the url opening then? e.g. spy on opn call will not work.

  • the method adds more flexibility in my opinion e.g. adding validation if browser, easier to test, easier to maintain (e.g. dependency replacement)

const opn = require("opn");
return opn(browserUrl);
};

serve.handler = function(argv = {}) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the defaulting to empty object? argv should always be supplied shouldn't it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default param is there to avoid errors when properties are accessed e.g.

normalizer.generateProjectTree({
		translator: argv.translator,
		configPath: argv.config
})

const normalizer = require("@ui5/project").normalizer;
const ui5Server = require("@ui5/server");
const server = ui5Server.server;
Expand All @@ -60,34 +64,36 @@ serve.handler = function(argv) {
port: argv.port === undefined ? argv.h2 ? 8443 : 8080 : argv.port,
changePortIfInUse: argv.port === undefined, // only change if port isn't explicitly set
h2: argv.h2,
acceptRemoteConnections: argv["accept-remote-connections"]
acceptRemoteConnections: !!argv.acceptRemoteConnections,
cert: argv.h2 ? argv.cert : undefined,
key: argv.h2 ? argv.key : undefined
};
return !serverConfig.h2
? {serverConfig, tree}
: ui5Server.sslUtil.getSslCertificate(serverConfig.key, serverConfig.cert).then(({key, cert}) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really hard to read. Please use if/else

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah sorry about that. Is fixed with in c2b125f

serverConfig.key = key;
serverConfig.cert = cert;
return {serverConfig, tree};
});
}).then(({serverConfig, tree}) => {
return server.serve(tree, serverConfig).then(function({h2, port}) {
const protocol = h2 ? "https" : "http";
let browserUrl = protocol + "://localhost:" + port;
console.log("Server started");
console.log("URL: " + browserUrl);

return Promise.resolve().then(function() {
if (serverConfig.h2) {
return ui5Server.sslUtil.getSslCertificate().then(({key, cert}) => {
serverConfig.key = key;
serverConfig.cert = cert;
return serverConfig;
});
} else {
return serverConfig;
}
}).then((serverConfig) => {
return server.serve(tree, serverConfig).then(function({h2, port}) {
const protocol = h2 ? "https" : "http";
let browserUrl = protocol + "://localhost:" + port;
if (argv.open !== undefined) {
if (argv.open !== undefined) {
if (typeof argv.open === "string") {
let relPath = argv.open || "/";
if (!relPath.startsWith("/")) {
relPath = "/" + relPath;
}
browserUrl += relPath;
opn(browserUrl);
}
console.log("Server started");
console.log("URL: " + browserUrl);
});
serve.openUrl(browserUrl);
}

return browserUrl;
});
}).catch(function(err) {
console.error(err);
Expand Down
12 changes: 4 additions & 8 deletions lib/cli/commands/tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ tree.builder = function(cli) {
.example("ui5 tree --json > tree.json", "Pipes the dependency tree into a new file \"tree.json\"");
};

tree.handler = function(argv) {
tree.handler = function(argv = {}) {
const normalizer = require("@ui5/project").normalizer;
const treeify = require("treeify");

Expand All @@ -37,13 +37,9 @@ tree.handler = function(argv) {
}

p.then(function(tree) {
if (argv.json) {
// Formatted JSON
console.log(JSON.stringify(tree, null, 4));
} else {
// Formatted tree
console.log(treeify.asTree(tree, true));
}
const output = argv.json ? JSON.stringify(tree, null, 4) : treeify.asTree(tree, true);
console.log(output);
return output;
}).catch(function(err) {
console.error(err);
process.exit(1);
Expand Down
17 changes: 8 additions & 9 deletions lib/cli/commands/versions.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const versions = {
};

const NOT_FOUND = "===(not installed)";
const getVersion = (pkg) => {
versions.getVersion = (pkg) => {
try {
const packageInfo = require(`${pkg}/package.json`);
return packageInfo.version || NOT_FOUND;
Expand All @@ -17,15 +17,14 @@ const getVersion = (pkg) => {
}
};

versions.handler = (argv) => {
versions.handler = () => {
try {
const cliVersion =
require("../../../package.json").version || NOT_FOUND;
const builderVersion = getVersion("@ui5/builder");
const serverVersion = getVersion("@ui5/server");
const fsVersion = getVersion("@ui5/fs");
const projectVersion = getVersion("@ui5/project");
const loggerVersion = getVersion("@ui5/logger");
const cliVersion = versions.getVersion("../../..");
const builderVersion = versions.getVersion("@ui5/builder");
const serverVersion = versions.getVersion("@ui5/server");
const fsVersion = versions.getVersion("@ui5/fs");
const projectVersion = versions.getVersion("@ui5/project");
const loggerVersion = versions.getVersion("@ui5/logger");
console.log(`
@ui5/cli: ${cliVersion}
@ui5/builder: ${builderVersion}
Expand Down
16 changes: 4 additions & 12 deletions lib/cli/middlewares/base.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,13 @@
const logger = require("./logger");
/**
* Base middleware for CLI commands.
*
* This middleware should be executed for every CLI command to enable basic features (e.g. logging).
* @param {Object} argv The CLI arguments
* @returns {Object} Data to be appended to argv
*/
module.exports = function(argv) {
if (argv.verbose || argv.loglevel) {
const logger = require("@ui5/logger");
if (argv.loglevel) {
logger.setLevel(argv.loglevel);
}
if (argv.verbose) {
logger.setLevel("verbose");
logger.getLogger("cli:middlewares:base").verbose(`using node version ${process.version}`);
}
}
module.exports = function(argv = {}) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why default argv to an empty object just to return null if argv is an empty object?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default param is there to avoid errors when properties are accessed...

if (Object.keys(argv).length === 0) return null;

return {};
logger.init(argv);
};
22 changes: 22 additions & 0 deletions lib/cli/middlewares/logger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Logger middleware
*
* Logger middleware used as one of default middlewares by tooling
* @param {Object} argv logger arguments
* @returns {Object} logger instance or null
*/
function init(argv = {}) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

argv is not optional, why defaulting it then?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default param is there to avoid errors when properties are accessed...

if (!argv.verbose && !argv.loglevel) return null;

const logger = require("@ui5/logger");
if (argv.loglevel) {
logger.setLevel(argv.loglevel);
}
if (argv.verbose) {
logger.setLevel("verbose");
logger.getLogger("cli:middlewares:base").verbose(`using node version ${process.version}`);
}
return logger;
}

module.exports = {init};
39 changes: 5 additions & 34 deletions lib/init/init.js
Original file line number Diff line number Diff line change
@@ -1,39 +1,8 @@
const {promisify} = require("util");
const path = require("path");
const fs = require("fs");
const stat = promisify(fs.stat);
const readFile = promisify(fs.readFile);

/**
* Checks if a file or path exists
*
* @private
* @param {string} filePath Path to check
* @returns {Promise} Promise resolving with true if the file or path exists
*/
async function exists(filePath) {
try {
await stat(filePath);
return true;
} catch (err) {
// "File or directory does not exist"
if (err.code === "ENOENT") {
return false;
} else {
throw err;
}
}
}

/**
* Checks if a list of paths exists
*
* @private
* @param {Array} paths List of paths to check
* @param {string} cwd Current working directory
* @returns {Promise} Resolving with an array of booleans for each path
*/
const pathsExist = async (paths, cwd) => await Promise.all(paths.map((p) => exists(path.join(cwd, p))));
const validate = require("../utils/validate");

/**
* Reads the package.json file and returns its content
Expand Down Expand Up @@ -118,10 +87,12 @@ async function init({cwd = "./"} = {}) {
throw new Error("Initialization not possible: Missing 'name' in package.json");
}

const [hasWebapp, hasSrc, hasTest] = await pathsExist(["webapp", "src", "test"], cwd);
const [hasWebapp, hasSrc, hasTest] = await validate.pathsExist(["webapp", "src", "test"], cwd);
projectConfig.type = getProjectType(hasWebapp, hasSrc, hasTest);

return projectConfig;
}

module.exports = init;
module.exports = {
init: init
};
43 changes: 43 additions & 0 deletions lib/utils/validate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const {promisify} = require("util");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we rename this file to fsHelper or something alike?

In my opinion it doesn't really "validate" anything or throws errors if some validation failed. It currently just offers convenience wrappers for some fs APIs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. renamed as requested c2b125f

const fs = require("fs");
const stat = promisify(fs.stat);
const path = require("path");

/**
* Checks if a file or path exists
*
* @private
* @param {string} filePath Path to check
* @returns {Promise} Promise resolving with true if the file or path exists
*/
async function exists(filePath) {
try {
await stat(filePath);
return true;
} catch (err) {
// "File or directory does not exist"
if (err.code === "ENOENT") {
return false;
} else {
throw err;
}
}
}

/**
* Checks if a list of paths exists
*
* @private
* @param {Array} paths List of paths to check
* @param {string} cwd Current working directory
* @returns {Promise} Resolving with an array of booleans for each path
*/
async function pathsExist(paths, cwd) {
return await Promise.all(paths.map((p) => exists(path.join(cwd, p))));
}

module.exports = {
exists,
pathsExist
};

Loading