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

[DX-296] Mocks can have the same signature as real plugins #794

Merged
merged 2 commits into from
Dec 20, 2017
Merged
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
82 changes: 43 additions & 39 deletions src/cli/domain/get-mocked-plugins.js
Original file line number Diff line number Diff line change
@@ -1,73 +1,77 @@
'use strict';

const colors = require('colors/safe');
const fs = require('fs-extra');
const path = require('path');
const _ = require('lodash');

const settings = require('../../resources/settings');
const strings = require('../../resources/');

const registerStaticMocks = function(mocks, logger) {
return _.map(mocks, (mockedValue, pluginName) => {
logger.log(colors.green('├── ' + pluginName + ' () => ' + mockedValue));
const isMockValid = plugin => {
const isFunction = _.isFunction(plugin);
const isValidObject =
_.isObject(plugin) &&
_.isFunction(plugin.register) &&
_.isFunction(plugin.execute);
return isFunction || isValidObject;
};

const defaultRegister = (options, dependencies, next) => next();

const registerStaticMocks = (mocks, logger) =>
_.map(mocks, (mockedValue, pluginName) => {
logger.ok(`├── ${pluginName} () => ${mockedValue}`);
return {
name: pluginName,
register: {
register: function(options, dependencies, next) {
return next();
},
execute: function() {
return mockedValue;
}
register: defaultRegister,
execute: () => mockedValue
}
};
});
};

const registerDynamicMocks = function(ocJsonLocation, mocks, logger) {
return _.map(mocks, (source, pluginName) => {
let p;
const registerDynamicMocks = (ocJsonLocation, mocks, logger) =>
_.map(mocks, (source, pluginName) => {
let pluginMock;
try {
p = require(path.resolve(ocJsonLocation, source));
pluginMock = require(path.resolve(ocJsonLocation, source));
} catch (er) {
logger.err(er.toString());
return;
}

if (!_.isFunction(p)) {
logger.err(strings.errors.cli.MOCK_PLUGIN_IS_NOT_A_FUNCTION);
if (!isMockValid(pluginMock)) {
logger.err(`├── ${pluginName} () => Error (skipping)`);
logger.err(strings.errors.cli.MOCK_PLUGIN_IS_NOT_VALID);
return;
}

logger.log(colors.green('├── ' + pluginName + ' () => [Function]'));
const register = pluginMock.register || defaultRegister;
const execute = pluginMock.execute || pluginMock;

logger.ok(`├── ${pluginName} () => [Function]`);

return {
name: pluginName,
register: {
register: function(options, dependencies, next) {
return next();
},
execute: p
}
register: { execute, register }
};
}).filter(p => p);
};
}).filter(pluginMock => pluginMock);

const findPath = function(pathToResolve, fileName) {
const rootDir = fs.realpathSync('.'),
fileToResolve = path.join(pathToResolve, fileName);
const rootDir = fs.realpathSync('.');
const fileToResolve = path.join(pathToResolve, fileName);

if (!fs.existsSync(fileToResolve)) {
if (pathToResolve === rootDir) {
return undefined;
} else {
const getParent = function(x) {
return x
.split('/')
.slice(0, -1)
.join('/');
},
parentDir = pathToResolve ? getParent(pathToResolve) : rootDir;
const getParent = pathToResolve =>
pathToResolve
.split('/')
.slice(0, -1)
.join('/');

const parentDir = pathToResolve ? getParent(pathToResolve) : rootDir;

return findPath(parentDir, fileName);
}
Expand All @@ -80,15 +84,15 @@ module.exports = function(logger, componentsDir) {
componentsDir = path.resolve(componentsDir || '.');

let plugins = [];
const ocJsonFileName = settings.configFile.src.replace('./', ''),
ocJsonPath = findPath(componentsDir, ocJsonFileName);
const ocJsonFileName = settings.configFile.src.replace('./', '');
const ocJsonPath = findPath(componentsDir, ocJsonFileName);

if (!ocJsonPath) {
return plugins;
}

const content = fs.readJsonSync(ocJsonPath),
ocJsonLocation = ocJsonPath.slice(0, -ocJsonFileName.length);
const content = fs.readJsonSync(ocJsonPath);
const ocJsonLocation = ocJsonPath.slice(0, -ocJsonFileName.length);

if (!content.mocks || !content.mocks.plugins) {
return plugins;
Expand Down
36 changes: 32 additions & 4 deletions src/resources/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,25 @@
'use strict';
const colors = require('colors/safe');

const validFunctionMock = `// simplified mock signature
module.exports = (a, b) => a+b;`;

const validMockObject = `// standard plugin-like signature

const dbClient = require('my-db-client');
let cache;

module.exports.register = (options, dependencies, next) => {
let client = dbClient(options);
client.init((err, response) => {
cache = response;
next(err);
});
};

module.exports.execute = key => cache[key];
`;

module.exports = {
commands: {
cli: {
Expand Down Expand Up @@ -91,7 +110,9 @@ module.exports = {
},
cli: {
scaffoldError: (url, error) =>
`Scaffolding failed. Please open an issue on ${url} with the following information: ${error}`,
`Scaffolding failed. Please open an issue on ${
url
} with the following information: ${error}`,
COMPONENT_HREF_NOT_FOUND:
"The specified path is not a valid component's url",
COMPONENTS_NOT_FOUND: 'no components found in specified path',
Expand All @@ -102,8 +123,13 @@ module.exports = {
DEV_FAIL: 'An error happened when initialising the dev runner: {0}',
INIT_FAIL: 'An error happened when initialising the component: {0}',
INVALID_CREDENTIALS: 'Invalid credentials',
MOCK_PLUGIN_IS_NOT_A_FUNCTION:
'Looks like you are trying to register a dynamic mock plugin but the file you specified is not a function',
MOCK_PLUGIN_IS_NOT_VALID: `Looks like you are trying to register a dynamic mock plugin but the file you specified is not a valid mock.
The entry point should be a synchronous function or an object containing an asynchronous register() function and a synchronous execute() function.
Example:

${colors.yellow(validFunctionMock)}

${colors.yellow(validMockObject)}`,
NAME_NOT_VALID:
'the name is not valid. Allowed characters are alphanumeric, _, -',
NODE_CLI_VERSION_NEEDS_UPGRADE:
Expand Down Expand Up @@ -164,7 +190,9 @@ Happy coding
installCompilerSuccess: (template, compiler, version) =>
`${colors.green('✔')} Installed ${compiler} [${template} v${version}]`,
legacyTemplateDeprecationWarning: (legacyType, newType) =>
`Template-type "${legacyType}" has been deprecated and is now replaced by "${newType}"`,
`Template-type "${
legacyType
}" has been deprecated and is now replaced by "${newType}"`,
CHANGES_DETECTED: 'Changes detected on file: {0}',
CHECKING_DEPENDENCIES: 'Ensuring dependencies are loaded...',
COMPONENT_INITED: 'Success! Created "{0}"',
Expand Down
78 changes: 51 additions & 27 deletions test/unit/cli-domain-get-mocked-plugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ const sinon = require('sinon');
const _ = require('lodash');

describe('cli : domain : get-mocked-plugins', () => {
const dynamicPluginModule = function(a) {
return a ? 'blarg' : 'flarg';
},
notAFunctionModule = { foo: 'bar' };
const dynamicPluginModule = a => (a ? 'blarg' : 'flarg');
const notAFunctionModule = { foo: 'bar' };
const dynamicObjectPluginModule = {
register: (opts, deps, next) => next(),
execute: () => 'result'
};

const logMock = { err: _.noop, log: _.noop, ok: _.noop, warn: _.noop };
let fsMock, getMockedPlugins;

const initialise = function(fs, pathJoinStub) {
Expand All @@ -37,6 +40,7 @@ describe('cli : domain : get-mocked-plugins', () => {
join: pathJoinStub || fakePathFunc,
resolve: fakePathFunc
},
'/root/components/dynamic-object-plugin.js': dynamicObjectPluginModule,
'/root/components/dynamic-plugin.js': dynamicPluginModule,
'/root/components/not-a-function.js': notAFunctionModule
});
Expand All @@ -48,7 +52,7 @@ describe('cli : domain : get-mocked-plugins', () => {

beforeEach(() => {
initialise({}, joinStub);
getMockedPlugins({ log: _.noop }, undefined);
getMockedPlugins(logMock, undefined);
});

it('should use . as default', () => {
Expand All @@ -61,7 +65,7 @@ describe('cli : domain : get-mocked-plugins', () => {

beforeEach(() => {
initialise({}, joinStub);
getMockedPlugins({ log: _.noop });
getMockedPlugins(logMock);
});

it('should use . as default', () => {
Expand All @@ -87,10 +91,7 @@ describe('cli : domain : get-mocked-plugins', () => {
existsSync: sinon.stub().returns(true),
readJsonSync: readMock
});
result = getMockedPlugins(
{ log: () => {}, warn: () => {} },
'/root/components/'
);
result = getMockedPlugins(logMock, '/root/components/');
});

it('should use components folder oc.json as default', () => {
Expand Down Expand Up @@ -133,10 +134,7 @@ describe('cli : domain : get-mocked-plugins', () => {
existsSync: existsMock,
readJsonSync: readMock
});
result = getMockedPlugins(
{ log: () => {}, warn: () => {} },
'/root/components/'
);
result = getMockedPlugins(logMock, '/root/components/');
});

it('should use root oc.json', () => {
Expand All @@ -148,7 +146,7 @@ describe('cli : domain : get-mocked-plugins', () => {
let result;
beforeEach(() => {
initialise({ existsSync: sinon.stub().returns(false) });
result = getMockedPlugins(console, '/root/components/');
result = getMockedPlugins(logMock, '/root/components/');
});

it('should return an empty array', () => {
Expand All @@ -170,7 +168,7 @@ describe('cli : domain : get-mocked-plugins', () => {
existsSync: sinon.stub().returns(true),
readJsonSync: sinon.stub().returns(ocJson)
});
result = getMockedPlugins({ warn: sinon.stub() }, '/root/components/');
result = getMockedPlugins(logMock, '/root/components/');
});

it('should return an empty array', () => {
Expand All @@ -196,10 +194,7 @@ describe('cli : domain : get-mocked-plugins', () => {
existsSync: sinon.stub().returns(true),
readJsonSync: sinon.stub().returns(ocJson)
});
result = getMockedPlugins(
{ log: () => {}, warn: () => {} },
'/root/components/'
);
result = getMockedPlugins(logMock, '/root/components/');
});

it('should return the static plugin', () => {
Expand All @@ -212,7 +207,7 @@ describe('cli : domain : get-mocked-plugins', () => {
});
});

describe('when a dynamic plugin is specified', () => {
describe('when a dynamic plugin with a function signature is specified', () => {
let result;
const ocJson = {
registries: [],
Expand All @@ -230,10 +225,7 @@ describe('cli : domain : get-mocked-plugins', () => {
existsSync: sinon.stub().returns(true),
readJsonSync: sinon.stub().returns(ocJson)
});
result = getMockedPlugins(
{ log: () => {}, warn: () => {} },
'/root/components/'
);
result = getMockedPlugins(logMock, '/root/components/');
});

it('should return the dynamic plugin', () => {
Expand All @@ -247,6 +239,37 @@ describe('cli : domain : get-mocked-plugins', () => {
});
});

describe('when a dynamic plugin with an object signature is specified', () => {
let result;
const ocJson = {
registries: [],
mocks: {
plugins: {
dynamic: {
myPlugin: './dynamic-object-plugin.js'
}
}
}
};

beforeEach(() => {
initialise({
existsSync: sinon.stub().returns(true),
readJsonSync: sinon.stub().returns(ocJson)
});
result = getMockedPlugins(logMock, '/root/components/');
});

it('should return the dynamic plugin', () => {
expect(result.length).to.equal(1);
expect(result[0].name).to.equal('myPlugin');
});

it('should set up the execute method to run the module', () => {
expect(result[0].register.execute()).to.equal('result');
});
});

describe('when a dynamic plugin is specified and the referenced file is missing', () => {
let result;
const ocJson = {
Expand Down Expand Up @@ -303,8 +326,9 @@ describe('cli : domain : get-mocked-plugins', () => {
});

it('should log an error', () => {
expect(logger.err.args[0][0]).to.contain(
'Looks like you are trying to register a dynamic mock plugin but the file you specified is not a function'
expect(logger.err.args[0][0]).to.contain('foo () => Error (skipping)');
expect(logger.err.args[1][0]).to.contain(
'Looks like you are trying to register a dynamic mock plugin but the file you specified is not a valid mock'
);
});

Expand Down