Skip to content

Commit

Permalink
Merge pull request #794 from opentable/mocks
Browse files Browse the repository at this point in the history
[DX-296] Mocks can have the same signature as real plugins
  • Loading branch information
matteofigus committed Dec 20, 2017
2 parents 0068f35 + 0d48ade commit 9362e7e
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 70 deletions.
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

0 comments on commit 9362e7e

Please sign in to comment.