Reindenting. (#17)

I normally prefer tabs for everything, it's nice to have the ability to
set to whatever your preferred width is. Since this project uses a lot
of yaml stuff I've had to indent those tests with spaces, for
consistency's sake I've decided to use spaces everywhere.
This commit is contained in:
Sergio Díaz 2018-05-04 11:47:28 -06:00 committed by GitHub
parent 64de56d51c
commit 59f85fac8c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 706 additions and 705 deletions

View File

@ -1,6 +1,7 @@
printWidth: 80 printWidth: 80
tabWidth: 4
singleQuote: true singleQuote: true
useTabs: true useTabs: false
trailingComma: none trailingComma: none
bracketSpacing: true bracketSpacing: true
jsxBracketSameLine: true jsxBracketSameLine: true

View File

@ -1,11 +1,11 @@
class DynamicValues { class DynamicValues {
constructor(registry, settings = {}) { constructor(registry, settings = {}) {
registry.defineDynamicValue('add', this.add); registry.defineDynamicValue('add', this.add);
} }
add(x, y) { add(x, y) {
return x + y; return x + y;
} }
} }
module.exports = DynamicValues; module.exports = DynamicValues;

View File

@ -1,20 +1,20 @@
class Modifiers { class Modifiers {
constructor(registry, settings = {}) { constructor(registry, settings = {}) {
registry.addPreRequestModifier(this.preRequest); registry.addPreRequestModifier(this.preRequest);
registry.addPostRequestModifier(this.postRequest); registry.addPostRequestModifier(this.postRequest);
} }
preRequest(request, orig) { preRequest(request, orig) {
request.headers = request.headers || {}; request.headers = request.headers || {};
request.headers.preRequestModifier = true; request.headers.preRequestModifier = true;
return request; return request;
} }
postRequest(response, orig) { postRequest(response, orig) {
response.body = 'Hello World'; response.body = 'Hello World';
response.response.body = 'Hello World'; response.response.body = 'Hello World';
return response; return response;
} }
} }
module.exports = Modifiers; module.exports = Modifiers;

View File

@ -1,20 +1,20 @@
function Request(request) { function Request(request) {
if (Request.fail) { if (Request.fail) {
throw new Error(); throw new Error();
} }
return { return {
request: { request: {
headers: request.headers, headers: request.headers,
body: request.body, body: request.body,
uri: { uri: {
href: `${request.baseUrl}${request.uri}` href: `${request.baseUrl}${request.uri}`
} }
}, },
statusCode: 200, statusCode: 200,
headers: [], headers: [],
body: '{"hello": "world"}' body: '{"hello": "world"}'
}; };
} }
Request.fail = false; Request.fail = false;

View File

@ -1,3 +1,3 @@
module.exports = function(name) { module.exports = function(name) {
return require(name); return require(name);
}; };

View File

@ -2,21 +2,21 @@ const yaml = require('js-yaml');
const Config = require('../config'); const Config = require('../config');
describe('Config', () => { describe('Config', () => {
it('should load valid config keys', () => { it('should load valid config keys', () => {
const doc = yaml.safeLoad(` const doc = yaml.safeLoad(`
version: 1 version: 1
endpoint: http://martianwabbit.com endpoint: http://martianwabbit.com
shouldntBeAdded: true shouldntBeAdded: true
`); `);
const config = new Config(doc); const config = new Config(doc);
expect(config.ENDPOINT).toBe(doc.endpoint); expect(config.ENDPOINT).toBe(doc.endpoint);
expect(config.VERSION).toBe(doc.version); expect(config.VERSION).toBe(doc.version);
expect(config.shouldntBeAdded).toBeUndefined(); expect(config.shouldntBeAdded).toBeUndefined();
}); });
it('should load requests', () => { it('should load requests', () => {
const doc = yaml.safeLoad(` const doc = yaml.safeLoad(`
endpoint: http://example.com endpoint: http://example.com
GET /profile: get-profile GET /profile: get-profile
@ -28,12 +28,12 @@ describe('Config', () => {
hello: world hello: world
`); `);
const config = new Config(doc); const config = new Config(doc);
expect(Object.keys(config.REQUESTS).length).toBe(4); expect(Object.keys(config.REQUESTS).length).toBe(4);
}); });
it('should set up defaults for all requests', () => { it('should set up defaults for all requests', () => {
const doc = yaml.safeLoad(` const doc = yaml.safeLoad(`
version: 1 version: 1
endpoint: 'http://jsonplaceholder.typicode.com' endpoint: 'http://jsonplaceholder.typicode.com'
@ -48,16 +48,16 @@ describe('Config', () => {
hello: world hello: world
`); `);
const config = new Config(doc); const config = new Config(doc);
expect(config).toMatchSnapshot(); expect(config).toMatchSnapshot();
Object.values(config.REQUESTS).forEach(r => { Object.values(config.REQUESTS).forEach(r => {
expect(r.HEADERS.authentication).toMatch('hello'); expect(r.HEADERS.authentication).toMatch('hello');
});
}); });
});
it('should load multiple hosts', () => { it('should load multiple hosts', () => {
const doc = yaml.safeLoad(` const doc = yaml.safeLoad(`
endpoint: http://example.org endpoint: http://example.org
defaults: defaults:
@ -95,13 +95,13 @@ describe('Config', () => {
GET /posts: posts GET /posts: posts
`); `);
let config = new Config(doc); let config = new Config(doc);
expect(config).toMatchSnapshot(); expect(config).toMatchSnapshot();
}); });
it('should namespace all aliases within an host', () => { it('should namespace all aliases within an host', () => {
const doc = yaml.safeLoad(` const doc = yaml.safeLoad(`
hosts: hosts:
- host: test1 - host: test1
endpoint: http://example.com endpoint: http://example.com
@ -111,14 +111,14 @@ describe('Config', () => {
GET /posts: posts GET /posts: posts
`); `);
let config = new Config(doc); let config = new Config(doc);
expect(config.REQUESTS[0].ALIAS).toBe('test1:posts'); expect(config.REQUESTS[0].ALIAS).toBe('test1:posts');
expect(config.REQUESTS[1].ALIAS).toBe('test2:posts'); expect(config.REQUESTS[1].ALIAS).toBe('test2:posts');
}); });
it(`should throw if host doesn't have a host key`, () => { it(`should throw if host doesn't have a host key`, () => {
const doc = yaml.safeLoad(` const doc = yaml.safeLoad(`
hosts: hosts:
- endpoint: http://example.com - endpoint: http://example.com
GET /posts: posts GET /posts: posts
@ -128,11 +128,11 @@ describe('Config', () => {
GET /posts: posts GET /posts: posts
`); `);
expect(() => new Config(doc)).toThrow(); expect(() => new Config(doc)).toThrow();
}); });
it(`should merge host settings with global settings`, () => { it(`should merge host settings with global settings`, () => {
const doc = yaml.safeLoad(` const doc = yaml.safeLoad(`
defaults: defaults:
headers: headers:
hello: 1 hello: 1
@ -150,7 +150,7 @@ describe('Config', () => {
GET /posts: posts GET /posts: posts
`); `);
let config = new Config(doc); let config = new Config(doc);
expect(config.REQUESTS[0].HEADERS.hello).toBe(1); expect(config.REQUESTS[0].HEADERS.hello).toBe(1);
}); });
}); });

View File

@ -4,74 +4,74 @@ const RequestList = require('../requestList');
const requestPromiseNativeMock = require('request-promise-native'); const requestPromiseNativeMock = require('request-promise-native');
describe('Request', () => { describe('Request', () => {
let cache; let cache;
let validRequestConfig; let validRequestConfig;
let invalidRequestConfig; let invalidRequestConfig;
let request; let request;
let requestWithoutDependencies; let requestWithoutDependencies;
beforeEach(() => { beforeEach(() => {
validRequestConfig = { validRequestConfig = {
request: 'POST /user', request: 'POST /user',
endpoint: 'http://martianwabbit.com', endpoint: 'http://martianwabbit.com',
alias: 'update', alias: 'update',
params: { params: {
userId: '$profile.UserId' userId: '$profile.UserId'
}, },
headers: { headers: {
authentication: 'BEARER $session.token' authentication: 'BEARER $session.token'
}, },
payload: { payload: {
username: 'seich' username: 'seich'
} }
}; };
invalidRequestConfig = { invalidRequestConfig = {
request: `POST /session`, request: `POST /session`,
endpoint: 'http://martianwabbit.com' endpoint: 'http://martianwabbit.com'
}; };
cache = new RequestCache(); cache = new RequestCache();
cache.add('session', { token: 'abc123' }); cache.add('session', { token: 'abc123' });
cache.add('profile', { UserId: 14 }); cache.add('profile', { UserId: 14 });
request = new Request(validRequestConfig); request = new Request(validRequestConfig);
requestWithoutDependencies = new Request({ requestWithoutDependencies = new Request({
endpoint: 'http://martianwabbit.com', endpoint: 'http://martianwabbit.com',
request: 'GET /user', request: 'GET /user',
alias: 'show' alias: 'show'
}); });
requestPromiseNativeMock.fail = false; requestPromiseNativeMock.fail = false;
}); });
it('should load up the given request', () => { it('should load up the given request', () => {
expect(request.VERB).toBe('POST'); expect(request.VERB).toBe('POST');
expect(request.ENDPOINT).toBe(validRequestConfig.endpoint); expect(request.ENDPOINT).toBe(validRequestConfig.endpoint);
expect(request.HEADERS).toBeDefined(); expect(request.HEADERS).toBeDefined();
expect(request.PAYLOAD).toBeDefined(); expect(request.PAYLOAD).toBeDefined();
expect(request.PARAMS).toBeDefined(); expect(request.PARAMS).toBeDefined();
}); });
it('should throw if a given request is invalid', () => { it('should throw if a given request is invalid', () => {
expect(() => new Request(invalidRequestConfig)).toThrow(); expect(() => new Request(invalidRequestConfig)).toThrow();
}); });
it('should list all of its dependencies', () => { it('should list all of its dependencies', () => {
expect(request.DEPENDENCIES.size).toBe(2); expect(request.DEPENDENCIES.size).toBe(2);
expect(request.DEPENDENCIES).toContain('session'); expect(request.DEPENDENCIES).toContain('session');
expect(request.DEPENDENCIES).toContain('profile'); expect(request.DEPENDENCIES).toContain('profile');
}); });
it('should execute a request', async () => { it('should execute a request', async () => {
await expect(request.exec(cache)).resolves.toMatchSnapshot(); await expect(request.exec(cache)).resolves.toMatchSnapshot();
await expect( await expect(
requestWithoutDependencies.exec() requestWithoutDependencies.exec()
).resolves.toMatchSnapshot(); ).resolves.toMatchSnapshot();
}); });
it('should throw if the request fails', async () => { it('should throw if the request fails', async () => {
requestPromiseNativeMock.fail = true; requestPromiseNativeMock.fail = true;
await expect(requestWithoutDependencies.exec()).rejects.toThrow(Error); await expect(requestWithoutDependencies.exec()).rejects.toThrow(Error);
}); });
}); });

View File

@ -1,89 +1,89 @@
const RequestCache = require('../requestCache'); const RequestCache = require('../requestCache');
describe('Request Cache', () => { describe('Request Cache', () => {
let cache; let cache;
beforeEach(() => { beforeEach(() => {
cache = new RequestCache(); cache = new RequestCache();
cache.add('session', { cache.add('session', {
hello: 'World' hello: 'World'
}); });
cache.add('array', [ cache.add('array', [
{ {
id: 1, id: 1,
name: 'Sergio' name: 'Sergio'
}, },
{ {
id: 2, id: 2,
name: 'Angela' name: 'Angela'
} }
]); ]);
}); });
it('should add keys to the cache', () => { it('should add keys to the cache', () => {
expect(cache.$cache.session.hello).toBe('World'); expect(cache.$cache.session.hello).toBe('World');
}); });
describe('get', () => { describe('get', () => {
it('should be able to find key values with a given path', () => { it('should be able to find key values with a given path', () => {
expect(cache.get('session.hello')).toBe('World'); expect(cache.get('session.hello')).toBe('World');
}); });
it('should throw when given an invalid path', () => { it('should throw when given an invalid path', () => {
expect(() => cache.get('$session.world')).toThrow(); expect(() => cache.get('$session.world')).toThrow();
}); });
}); });
describe('parse', () => { describe('parse', () => {
it("should transform variables in strings using it's cache", () => { it("should transform variables in strings using it's cache", () => {
expect(cache.parse('Hello $session.hello')).toBe('Hello World'); expect(cache.parse('Hello $session.hello')).toBe('Hello World');
}); });
it('should go transform variables in all values when given an object', () => { it('should go transform variables in all values when given an object', () => {
let parsed = cache.parse({ let parsed = cache.parse({
hello: 'hello $session.hello', hello: 'hello $session.hello',
earth: '$session.hello' earth: '$session.hello'
}); });
expect(parsed.hello).toBe('hello World'); expect(parsed.hello).toBe('hello World');
expect(parsed.earth).toBe('World'); expect(parsed.earth).toBe('World');
}); });
it('should return every non-string value as-is', () => { it('should return every non-string value as-is', () => {
let parsed = cache.parse({ let parsed = cache.parse({
number: 1, number: 1,
nulled: null, nulled: null,
truthy: false, truthy: false,
hello: '$session.hello' hello: '$session.hello'
}); });
expect(parsed.number).toBe(1); expect(parsed.number).toBe(1);
expect(parsed.nulled).toBeNull(); expect(parsed.nulled).toBeNull();
expect(parsed.truthy).toBe(false); expect(parsed.truthy).toBe(false);
expect(parsed.hello).toBe('World'); expect(parsed.hello).toBe('World');
}); });
it('should parse arrays as well', () => { it('should parse arrays as well', () => {
let parsed = cache.parse({ hello: '$array.0.name' }); let parsed = cache.parse({ hello: '$array.0.name' });
expect(parsed.hello).toBe('Sergio'); expect(parsed.hello).toBe('Sergio');
}); });
it('should return an object when given an undefined value', () => { it('should return an object when given an undefined value', () => {
expect(Object.keys(cache.parse(undefined)).length).toBe(0); expect(Object.keys(cache.parse(undefined)).length).toBe(0);
}); });
it('should parse any value other than undefined', () => { it('should parse any value other than undefined', () => {
expect(cache.parse('Hello $session.hello')).toBe('Hello World'); expect(cache.parse('Hello $session.hello')).toBe('Hello World');
}); });
it('should return null when passed null', () => { it('should return null when passed null', () => {
expect(cache.parse(null)).toBe(null); expect(cache.parse(null)).toBe(null);
}); });
it(`shouldn't replace escaped variables`, () => { it(`shouldn't replace escaped variables`, () => {
expect(cache.parse(`\\$session.hello is $session.hello`)).toBe( expect(cache.parse(`\\$session.hello is $session.hello`)).toBe(
`$session.hello is World` `$session.hello is World`
); );
}); });
}); });
}); });

View File

@ -3,76 +3,76 @@ const RequestList = require('../requestList');
const requestPromiseNativeMock = require('request-promise-native'); const requestPromiseNativeMock = require('request-promise-native');
describe('RequestList', () => { describe('RequestList', () => {
const endpoint = 'http://martianwabbit.com'; const endpoint = 'http://martianwabbit.com';
let env = { let env = {
environmental: true environmental: true
}; };
const doc = { const doc = {
ENDPOINT: endpoint, ENDPOINT: endpoint,
ENVIRONMENT: env, ENVIRONMENT: env,
'GET /post': { alias: 'get-posts' }, 'GET /post': { alias: 'get-posts' },
'POST /user': { 'POST /user': {
alias: 'user', alias: 'user',
payload: { payload: {
name: 'Sergio', name: 'Sergio',
lastname: 'Diaz' lastname: 'Diaz'
} }
} }
}; };
let requests; let requests;
beforeEach(() => { beforeEach(() => {
requestPromiseNativeMock.fail = false; requestPromiseNativeMock.fail = false;
let config = new Config(doc); let config = new Config(doc);
requests = new RequestList(config); requests = new RequestList(config);
}); });
it('should allow an empty request list', () => { it('should allow an empty request list', () => {
requests = new RequestList(); requests = new RequestList();
expect(requests.list.length).toBe(0); expect(requests.list.length).toBe(0);
}); });
it('should load valid requests', () => { it('should load valid requests', () => {
expect(requests.list.length).toBe(2); expect(requests.list.length).toBe(2);
}); });
it('should fetch dependencies', async () => { it('should fetch dependencies', async () => {
await expect( await expect(
requests.fetchDependencies(['get-posts']) requests.fetchDependencies(['get-posts'])
).resolves.toMatchSnapshot(); ).resolves.toMatchSnapshot();
}); });
it('should execute requests by alias.', async () => { it('should execute requests by alias.', async () => {
await expect(requests.execByAlias('user')).resolves.toMatchSnapshot(); await expect(requests.execByAlias('user')).resolves.toMatchSnapshot();
}); });
it('should fail if the request fails', async () => { it('should fail if the request fails', async () => {
requestPromiseNativeMock.fail = true; requestPromiseNativeMock.fail = true;
await expect(requests.execByAlias('user')).rejects.toThrow(); await expect(requests.execByAlias('user')).rejects.toThrow();
}); });
it('should return a cached result if available', async () => { it('should return a cached result if available', async () => {
const obj = { test: true }; const obj = { test: true };
requests.cache.add('test', obj); requests.cache.add('test', obj);
await expect(requests.execByAlias('test')).resolves.toBe(obj); await expect(requests.execByAlias('test')).resolves.toBe(obj);
}); });
it('should fail if the alias is not found', async () => { it('should fail if the alias is not found', async () => {
await expect(requests.execByAlias('notAnAlias')).rejects.toThrow(); await expect(requests.execByAlias('notAnAlias')).rejects.toThrow();
}); });
it(`should fail if a given request doesn't have an alias`, () => { it(`should fail if a given request doesn't have an alias`, () => {
let config = new Config({ let config = new Config({
'GET /hello': { 'GET /hello': {
headers: { headers: {
hello: 1 hello: 1
} }
} }
}); });
expect(() => new RequestList(config, config)).toThrow(); expect(() => new RequestList(config, config)).toThrow();
}); });
}); });

View File

@ -2,10 +2,10 @@ const RequestList = require('./requestList');
const Config = require('./config'); const Config = require('./config');
class Beau { class Beau {
constructor(doc, env = {}) { constructor(doc, env = {}) {
this.config = new Config(doc, env); this.config = new Config(doc, env);
this.requests = new RequestList(this.config); this.requests = new RequestList(this.config);
} }
} }
module.exports = Beau; module.exports = Beau;

View File

@ -3,96 +3,96 @@ const { requestRegex, UpperCaseKeys } = require('./shared');
const Plugins = require('./plugins'); const Plugins = require('./plugins');
class Config { class Config {
constructor(doc, env = {}) { constructor(doc, env = {}) {
this.defaultConfigValues = { this.defaultConfigValues = {
VERSION: 1, VERSION: 1,
ENDPOINT: '', ENDPOINT: '',
PLUGINS: [], PLUGINS: [],
DEFAULTS: {}, DEFAULTS: {},
ENVIRONMENT: {}, ENVIRONMENT: {},
HOSTS: [], HOSTS: [],
COOKIEJAR: false COOKIEJAR: false
}; };
this.configKeys = Object.keys(this.defaultConfigValues); this.configKeys = Object.keys(this.defaultConfigValues);
this.doc = doc; this.doc = doc;
let config = this.loadConfig(doc); let config = this.loadConfig(doc);
this.configKeys.forEach(k => { this.configKeys.forEach(k => {
this[k] = config[k] || this.defaultConfigValues[k]; this[k] = config[k] || this.defaultConfigValues[k];
}); });
this.ENVIRONMENT = deepMerge(this.ENVIRONMENT, env); this.ENVIRONMENT = deepMerge(this.ENVIRONMENT, env);
this.REQUESTS = []; this.REQUESTS = [];
this.loadRequests(doc, { this.loadRequests(doc, {
DEFAULTS: this.DEFAULTS, DEFAULTS: this.DEFAULTS,
ENDPOINT: this.ENDPOINT ENDPOINT: this.ENDPOINT
}); });
this.loadHosts(this.HOSTS, config); this.loadHosts(this.HOSTS, config);
this.PLUGINS = new Plugins(this.PLUGINS); this.PLUGINS = new Plugins(this.PLUGINS);
} }
loadHosts(hosts, rootConfig) { loadHosts(hosts, rootConfig) {
hosts.forEach(host => { hosts.forEach(host => {
if (typeof host.host === 'undefined') { if (typeof host.host === 'undefined') {
throw new Error(`Host doesn't indicate it's host name.`); throw new Error(`Host doesn't indicate it's host name.`);
} }
let config = deepMerge( let config = deepMerge(
this.defaultConfigValues, this.defaultConfigValues,
this.loadConfig(host) this.loadConfig(host)
); );
config.DEFAULTS = deepMerge(rootConfig.DEFAULTS, config.DEFAULTS); config.DEFAULTS = deepMerge(rootConfig.DEFAULTS, config.DEFAULTS);
this.loadRequests(host, { this.loadRequests(host, {
DEFAULTS: config.DEFAULTS, DEFAULTS: config.DEFAULTS,
ENDPOINT: config.ENDPOINT, ENDPOINT: config.ENDPOINT,
NAMESPACE: host.host NAMESPACE: host.host
}); });
}); });
} }
loadRequests(host, settings) { loadRequests(host, settings) {
let requests = Object.keys(host) let requests = Object.keys(host)
.filter(key => requestRegex.test(key)) .filter(key => requestRegex.test(key))
.map(key => { .map(key => {
let requestDefinitionIsString = typeof host[key] === 'string'; let requestDefinitionIsString = typeof host[key] === 'string';
let originalRequest = requestDefinitionIsString let originalRequest = requestDefinitionIsString
? { ALIAS: host[key] } ? { ALIAS: host[key] }
: host[key]; : host[key];
let request = UpperCaseKeys(originalRequest); let request = UpperCaseKeys(originalRequest);
if (settings.NAMESPACE) { if (settings.NAMESPACE) {
request.ALIAS = `${settings.NAMESPACE}:${request.ALIAS}`; request.ALIAS = `${settings.NAMESPACE}:${request.ALIAS}`;
} }
request.REQUEST = key; request.REQUEST = key;
request.COOKIEJAR = this.COOKIEJAR; request.COOKIEJAR = this.COOKIEJAR;
request.ENDPOINT = settings.ENDPOINT; request.ENDPOINT = settings.ENDPOINT;
let defaults = UpperCaseKeys(settings.DEFAULTS); let defaults = UpperCaseKeys(settings.DEFAULTS);
return deepMerge(defaults, request); return deepMerge(defaults, request);
}); });
this.REQUESTS = this.REQUESTS.concat(requests); this.REQUESTS = this.REQUESTS.concat(requests);
} }
loadConfig(host) { loadConfig(host) {
let config = {}; let config = {};
Object.keys(host) Object.keys(host)
.filter(k => this.configKeys.includes(k.toUpperCase())) .filter(k => this.configKeys.includes(k.toUpperCase()))
.forEach(k => (config[k.toUpperCase()] = host[k])); .forEach(k => (config[k.toUpperCase()] = host[k]));
return config; return config;
} }
} }
module.exports = Config; module.exports = Config;

View File

@ -5,91 +5,91 @@ const { toKebabCase, dynamicValueRegex, replaceInObject } = require('./shared');
const isPlainObject = require('is-plain-object'); const isPlainObject = require('is-plain-object');
class Plugins { class Plugins {
constructor(plugins = []) { constructor(plugins = []) {
this.registry = { this.registry = {
preRequestModifiers: [], preRequestModifiers: [],
postRequestModifiers: [], postRequestModifiers: [],
dynamicValues: [] dynamicValues: []
}; };
this.context = {}; this.context = {};
plugins.forEach(plugin => this.loadPlugin(plugin)); plugins.forEach(plugin => this.loadPlugin(plugin));
} }
loadPlugin(plugin) { loadPlugin(plugin) {
let name = plugin; let name = plugin;
let settings = {}; let settings = {};
if (typeof plugin === 'object') { if (typeof plugin === 'object') {
let keys = Object.keys(plugin); let keys = Object.keys(plugin);
if (keys.length !== 1) { if (keys.length !== 1) {
throw new Error(`Plugin items should contain only one key.`); throw new Error(`Plugin items should contain only one key.`);
} }
name = keys[0]; name = keys[0];
settings = plugin[name]; settings = plugin[name];
} }
plugin = requireg(`beau-${toKebabCase(name)}`); plugin = requireg(`beau-${toKebabCase(name)}`);
new plugin(this, settings); new plugin(this, settings);
} }
executeModifier(modifier, obj, orig) { executeModifier(modifier, obj, orig) {
let result = deepmerge({}, obj, { isMergeableObject: isPlainObject }); let result = deepmerge({}, obj, { isMergeableObject: isPlainObject });
this.registry[modifier].forEach( this.registry[modifier].forEach(
modifier => (result = modifier(result, orig)) modifier => (result = modifier(result, orig))
); );
return result; return result;
} }
replaceDynamicValues(obj) { replaceDynamicValues(obj) {
return replaceInObject(obj, val => { return replaceInObject(obj, val => {
let valIsEmpty = val.trim().length === 0; let valIsEmpty = val.trim().length === 0;
if (valIsEmpty) { if (valIsEmpty) {
return val; return val;
} }
try { try {
let onlyHasDynamic = let onlyHasDynamic =
val.replace(dynamicValueRegex, '').trim() === ''; val.replace(dynamicValueRegex, '').trim() === '';
if (onlyHasDynamic) { if (onlyHasDynamic) {
let call; let call;
val.replace(dynamicValueRegex, (match, c) => { val.replace(dynamicValueRegex, (match, c) => {
call = c; call = c;
}); });
return vm.runInContext(call, this.context); return vm.runInContext(call, this.context);
} }
return val.replace(dynamicValueRegex, (match, call) => { return val.replace(dynamicValueRegex, (match, call) => {
return vm.runInContext(call, this.context); return vm.runInContext(call, this.context);
}); });
} catch (e) { } catch (e) {
throw new Error(`DynamicValue: ` + e); throw new Error(`DynamicValue: ` + e);
} }
}); });
} }
addPreRequestModifier(modifier) { addPreRequestModifier(modifier) {
this.registry.preRequestModifiers.push(modifier); this.registry.preRequestModifiers.push(modifier);
} }
addPostRequestModifier(modifier) { addPostRequestModifier(modifier) {
this.registry.postRequestModifiers.push(modifier); this.registry.postRequestModifiers.push(modifier);
} }
defineDynamicValue(name, fn) { defineDynamicValue(name, fn) {
this.registry.dynamicValues.push({ name, fn }); this.registry.dynamicValues.push({ name, fn });
this.context[name] = fn; this.context[name] = fn;
vm.createContext(this.context); vm.createContext(this.context);
} }
} }
module.exports = Plugins; module.exports = Plugins;

View File

@ -4,149 +4,149 @@ const RequestCache = require('./requestCache');
const Plugins = require('./plugins'); const Plugins = require('./plugins');
const { const {
httpVerbs, httpVerbs,
requestRegex, requestRegex,
replacementRegex, replacementRegex,
UpperCaseKeys, UpperCaseKeys,
removeOptionalKeys removeOptionalKeys
} = require('./shared'); } = require('./shared');
class Request { class Request {
constructor(req, plugins = new Plugins()) { constructor(req, plugins = new Plugins()) {
this.originalRequest = req; this.originalRequest = req;
this.plugins = plugins; this.plugins = plugins;
this.loadCofiguration( this.loadCofiguration(
[ [
'REQUEST', 'REQUEST',
'ENDPOINT', 'ENDPOINT',
'HEADERS', 'HEADERS',
'PAYLOAD', 'PAYLOAD',
'PARAMS', 'PARAMS',
'FORM', 'FORM',
'ALIAS', 'ALIAS',
'COOKIEJAR', 'COOKIEJAR',
'FORMDATA' 'FORMDATA'
], ],
req req
); );
if (!this.ALIAS) { if (!this.ALIAS) {
throw new Error(`${this.REQUEST} is missing an alias.`); throw new Error(`${this.REQUEST} is missing an alias.`);
} }
const { VERB, PATH } = this.parseRequest(this.REQUEST); const { VERB, PATH } = this.parseRequest(this.REQUEST);
this.VERB = VERB; this.VERB = VERB;
this.PATH = PATH; this.PATH = PATH;
this.DEPENDENCIES = this.findDependencies(req); this.DEPENDENCIES = this.findDependencies(req);
} }
loadCofiguration(keys, obj) { loadCofiguration(keys, obj) {
obj = UpperCaseKeys(obj); obj = UpperCaseKeys(obj);
keys.forEach(k => { keys.forEach(k => {
this[k] = obj[k]; this[k] = obj[k];
}); });
} }
parseRequest(request) { parseRequest(request) {
const parts = request.match(requestRegex); const parts = request.match(requestRegex);
return { return {
VERB: parts[1], VERB: parts[1],
PATH: parts[2] PATH: parts[2]
}; };
} }
findDependencies(request, set = new Set()) { findDependencies(request, set = new Set()) {
let type = typeof request; let type = typeof request;
if (type === 'object') { if (type === 'object') {
Object.keys(request) Object.keys(request)
.filter(key => key !== 'ALIAS') .filter(key => key !== 'ALIAS')
.forEach(key => { .forEach(key => {
set = this.findDependencies(request[key], set); set = this.findDependencies(request[key], set);
}); });
} else if (type === 'string') { } else if (type === 'string') {
const matches = []; const matches = [];
request.replace( request.replace(
replacementRegex, replacementRegex,
(match, g1) => !match.startsWith('\\') && matches.push(g1) (match, g1) => !match.startsWith('\\') && matches.push(g1)
); );
const deps = matches.map(m => m.split('.')[0]); const deps = matches.map(m => m.split('.')[0]);
return new Set([...set, ...deps]); return new Set([...set, ...deps]);
} }
return set; return set;
} }
async exec(cache = new RequestCache()) { async exec(cache = new RequestCache()) {
let settings = cache.parse({ let settings = cache.parse({
baseUrl: this.ENDPOINT, baseUrl: this.ENDPOINT,
uri: this.PATH, uri: this.PATH,
method: this.VERB, method: this.VERB,
jar: this.COOKIEJAR, jar: this.COOKIEJAR,
headers: this.HEADERS, headers: this.HEADERS,
qs: this.PARAMS, qs: this.PARAMS,
body: this.PAYLOAD, body: this.PAYLOAD,
form: this.FORM, form: this.FORM,
formData: this.FORMDATA, formData: this.FORMDATA,
json: true, json: true,
simple: false, simple: false,
resolveWithFullResponse: true resolveWithFullResponse: true
}); });
settings = removeOptionalKeys(settings, [ settings = removeOptionalKeys(settings, [
'headers', 'headers',
'qs', 'qs',
'body', 'body',
'form', 'form',
'formData' 'formData'
]); ]);
settings = this.plugins.replaceDynamicValues(settings); settings = this.plugins.replaceDynamicValues(settings);
settings = this.plugins.executeModifier( settings = this.plugins.executeModifier(
'preRequestModifiers', 'preRequestModifiers',
settings, settings,
this.originalRequest this.originalRequest
); );
try { try {
const response = await request(settings); const response = await request(settings);
let results = { let results = {
request: { request: {
headers: response.request.headers, headers: response.request.headers,
body: response.request.body, body: response.request.body,
endpoint: response.request.uri.href endpoint: response.request.uri.href
}, },
response: { response: {
status: response.statusCode, status: response.statusCode,
headers: response.headers, headers: response.headers,
body: response.body body: response.body
}, },
body: response.body body: response.body
}; };
results = this.plugins.executeModifier( results = this.plugins.executeModifier(
'postRequestModifiers', 'postRequestModifiers',
results, results,
this.originalRequest this.originalRequest
); );
cache.add(this.ALIAS, results); cache.add(this.ALIAS, results);
return results; return results;
} catch ({ error }) { } catch ({ error }) {
throw new Error(`Request Error: ` + error); throw new Error(`Request Error: ` + error);
} }
} }
} }
module.exports = Request; module.exports = Request;

View File

@ -1,46 +1,46 @@
const { replacementRegex, replaceInObject } = require('./shared'); const { replacementRegex, replaceInObject } = require('./shared');
class RequestCache { class RequestCache {
constructor() { constructor() {
this.$cache = {}; this.$cache = {};
} }
exists(key) { exists(key) {
return typeof this.$cache[key] !== 'undefined'; return typeof this.$cache[key] !== 'undefined';
} }
add(key, value) { add(key, value) {
this.$cache[key] = value; this.$cache[key] = value;
} }
get(path) { get(path) {
let result = this.$cache; let result = this.$cache;
path.split('.').forEach(part => { path.split('.').forEach(part => {
if (result[part] === undefined) { if (result[part] === undefined) {
throw new Error(`${path} not found in cache.`); throw new Error(`${path} not found in cache.`);
} }
result = result[part]; result = result[part];
}); });
return result; return result;
} }
parse(item) { parse(item) {
if (item === null) { if (item === null) {
return null; return null;
} }
return replaceInObject(item, item => { return replaceInObject(item, item => {
return item.replace(replacementRegex, (match, key) => { return item.replace(replacementRegex, (match, key) => {
if (match.startsWith('\\')) { if (match.startsWith('\\')) {
return match.replace('\\$', '$'); return match.replace('\\$', '$');
} }
return this.get(key); return this.get(key);
}); });
}); });
} }
} }
module.exports = RequestCache; module.exports = RequestCache;

View File

@ -3,58 +3,58 @@ const RequestCache = require('./requestCache');
const httpVerbs = require('./shared').httpVerbs; const httpVerbs = require('./shared').httpVerbs;
class RequestList { class RequestList {
constructor(config = { REQUESTS: [] }) { constructor(config = { REQUESTS: [] }) {
this.config = config; this.config = config;
this.REQUESTS = config.REQUESTS; this.REQUESTS = config.REQUESTS;
this.list = this.loadRequests(); this.list = this.loadRequests();
this.cache = new RequestCache(); this.cache = new RequestCache();
this.cache.add(`env`, this.config.ENVIRONMENT); this.cache.add(`env`, this.config.ENVIRONMENT);
} }
async execByAlias(alias) { async execByAlias(alias) {
if (this.cache.exists(alias)) { if (this.cache.exists(alias)) {
return this.cache.get(alias); return this.cache.get(alias);
} }
const request = this.list.find(r => r.ALIAS === alias); const request = this.list.find(r => r.ALIAS === alias);
if (typeof request === 'undefined') { if (typeof request === 'undefined') {
throw new Error(`${alias} not found among the requests.`); throw new Error(`${alias} not found among the requests.`);
} }
try { try {
await this.fetchDependencies(Array.from(request.DEPENDENCIES)); await this.fetchDependencies(Array.from(request.DEPENDENCIES));
return await request.exec(this.cache); return await request.exec(this.cache);
} catch (reason) { } catch (reason) {
throw new Error( throw new Error(
`Request: ${request.VERB} ${ `Request: ${request.VERB} ${
request.ENDPOINT request.ENDPOINT
} FAILED. \n${reason}` } FAILED. \n${reason}`
); );
} }
} }
async fetchDependencies(dependencies) { async fetchDependencies(dependencies) {
dependencies = dependencies.map(d => this.execByAlias(d)); dependencies = dependencies.map(d => this.execByAlias(d));
await Promise.all(dependencies); await Promise.all(dependencies);
return this.cache; return this.cache;
} }
loadRequests() { loadRequests() {
let requests = []; let requests = [];
this.REQUESTS.forEach(request => { this.REQUESTS.forEach(request => {
try { try {
requests.push(new Request(request, this.config.PLUGINS)); requests.push(new Request(request, this.config.PLUGINS));
} catch (e) { } catch (e) {
throw new Error(`${request.request} was ignored: ${e}`); throw new Error(`${request.request} was ignored: ${e}`);
} }
}); });
return requests; return requests;
} }
} }
module.exports = RequestList; module.exports = RequestList;

View File

@ -2,78 +2,78 @@ const Joi = require('joi');
const { requestRegex } = require('./shared.js'); const { requestRegex } = require('./shared.js');
const pluginSchema = [ const pluginSchema = [
Joi.string(), Joi.string(),
Joi.object() Joi.object()
.keys(null) .keys(null)
.max(1) .max(1)
]; ];
const requestSchema = [ const requestSchema = [
Joi.object() Joi.object()
.keys({ .keys({
HEADERS: Joi.object().keys(null), HEADERS: Joi.object().keys(null),
PAYLOAD: [Joi.object().keys(null), Joi.string()], PAYLOAD: [Joi.object().keys(null), Joi.string()],
PARAMS: Joi.object().keys(null), PARAMS: Joi.object().keys(null),
FORM: Joi.object().keys(null), FORM: Joi.object().keys(null),
ALIAS: Joi.string().required(), ALIAS: Joi.string().required(),
FORMDATA: Joi.object().keys(null) FORMDATA: Joi.object().keys(null)
}) })
.without('FORM', ['PAYLOAD', 'FORMDATA']) .without('FORM', ['PAYLOAD', 'FORMDATA'])
.without('PAYLOAD', ['FORM', 'FORMDATA']) .without('PAYLOAD', ['FORM', 'FORMDATA'])
.without('FORMDATA', ['FORM', 'PAYLOAD']) .without('FORMDATA', ['FORM', 'PAYLOAD'])
.rename(/headers/i, 'HEADERS', { override: true }) .rename(/headers/i, 'HEADERS', { override: true })
.rename(/payload/i, 'PAYLOAD', { override: true }) .rename(/payload/i, 'PAYLOAD', { override: true })
.rename(/params/i, 'PARAMS', { override: true }) .rename(/params/i, 'PARAMS', { override: true })
.rename(/form/i, 'FORM', { override: true }) .rename(/form/i, 'FORM', { override: true })
.rename(/alias/i, 'ALIAS', { override: true }), .rename(/alias/i, 'ALIAS', { override: true }),
Joi.string() Joi.string()
]; ];
const hostSchema = Joi.object() const hostSchema = Joi.object()
.keys({ .keys({
HOST: Joi.string().required(), HOST: Joi.string().required(),
ENDPOINT: Joi.string(), ENDPOINT: Joi.string(),
DEFAULTS: Joi.object().keys(null) DEFAULTS: Joi.object().keys(null)
}) })
.pattern(requestRegex, requestSchema) .pattern(requestRegex, requestSchema)
.rename(/host/i, 'HOST', { override: true }) .rename(/host/i, 'HOST', { override: true })
.rename(/defaults/i, 'DEFAULTS', { override: true }) .rename(/defaults/i, 'DEFAULTS', { override: true })
.rename(/endpoint/i, 'ENDPOINT', { override: true }); .rename(/endpoint/i, 'ENDPOINT', { override: true });
const schema = Joi.object() const schema = Joi.object()
.keys({ .keys({
VERSION: Joi.number().integer(), VERSION: Joi.number().integer(),
ENDPOINT: Joi.string().uri(), ENDPOINT: Joi.string().uri(),
PLUGINS: Joi.array().items(pluginSchema), PLUGINS: Joi.array().items(pluginSchema),
DEFAULTS: Joi.object(), DEFAULTS: Joi.object(),
ENVIRONMENT: Joi.object(), ENVIRONMENT: Joi.object(),
HOSTS: Joi.array().items(hostSchema), HOSTS: Joi.array().items(hostSchema),
COOKIEJAR: Joi.boolean() COOKIEJAR: Joi.boolean()
}) })
.pattern(requestRegex, requestSchema) .pattern(requestRegex, requestSchema)
.rename(/version/i, 'VERSION', { override: true }) .rename(/version/i, 'VERSION', { override: true })
.rename(/endpoint/i, 'ENDPOINT', { override: true }) .rename(/endpoint/i, 'ENDPOINT', { override: true })
.rename(/hosts/i, 'HOSTS', { override: true }) .rename(/hosts/i, 'HOSTS', { override: true })
.rename(/plugins/i, 'PLUGINS', { override: true }) .rename(/plugins/i, 'PLUGINS', { override: true })
.rename(/defaults/i, 'DEFAULTS', { override: true }) .rename(/defaults/i, 'DEFAULTS', { override: true })
.rename(/environment/i, 'ENVIRONMENT', { override: true }) .rename(/environment/i, 'ENVIRONMENT', { override: true })
.rename(/cookiejar/i, 'COOKIEJAR', { override: true }); .rename(/cookiejar/i, 'COOKIEJAR', { override: true });
const validate = async function(config) { const validate = async function(config) {
try { try {
let results = await Joi.validate(config, schema, { let results = await Joi.validate(config, schema, {
allowUnknown: true allowUnknown: true
}); });
return { valid: true }; return { valid: true };
} catch ({ name, details }) { } catch ({ name, details }) {
return { return {
valid: false, valid: false,
message: `${name}: \n ${details message: `${name}: \n ${details
.map(d => d.message + ' @ ' + d.path) .map(d => d.message + ' @ ' + d.path)
.join(' \n ')}` .join(' \n ')}`
}; };
} }
}; };
module.exports = { schema, validate }; module.exports = { schema, validate };

View File

@ -1,13 +1,13 @@
const httpVerbs = [ const httpVerbs = [
'GET', 'GET',
'HEAD', 'HEAD',
'POST', 'POST',
'PUT', 'PUT',
'DELETE', 'DELETE',
'CONNECT', 'CONNECT',
'OPTIONS', 'OPTIONS',
'TRACE', 'TRACE',
'PATCH' 'PATCH'
]; ];
const requestRegex = new RegExp(`(${httpVerbs.join('|')})\\s(.*)`, 'i'); const requestRegex = new RegExp(`(${httpVerbs.join('|')})\\s(.*)`, 'i');
@ -15,65 +15,65 @@ const replacementRegex = /(?:\\?)\$([a-zA-Z\.\d\-\_\/\\\:]+)/g;
const dynamicValueRegex = /\$\[(\w+\((?:.|[\n\r])+?\))\]/g; const dynamicValueRegex = /\$\[(\w+\((?:.|[\n\r])+?\))\]/g;
const UpperCaseKeys = function(obj) { const UpperCaseKeys = function(obj) {
let result = {}; let result = {};
Object.keys(obj).forEach(k => (result[k.toUpperCase()] = obj[k])); Object.keys(obj).forEach(k => (result[k.toUpperCase()] = obj[k]));
return result; return result;
}; };
const removeOptionalKeys = function(obj, optionalValues) { const removeOptionalKeys = function(obj, optionalValues) {
let result = {}; let result = {};
Object.keys(obj).forEach(key => { Object.keys(obj).forEach(key => {
if ( if (
optionalValues.includes(key) && optionalValues.includes(key) &&
(Object.keys(obj[key]).length === 0 && (Object.keys(obj[key]).length === 0 &&
obj[key].constructor === Object) obj[key].constructor === Object)
) { ) {
return; return;
} }
result[key] = obj[key]; result[key] = obj[key];
}); });
return result; return result;
}; };
const toKebabCase = function(str) { const toKebabCase = function(str) {
return str return str
.trim() .trim()
.replace(/([a-z])([A-Z])/g, '$1-$2') .replace(/([a-z])([A-Z])/g, '$1-$2')
.replace(/\s+/g, '-') .replace(/\s+/g, '-')
.toLowerCase(); .toLowerCase();
}; };
const replaceInObject = function(obj, fn) { const replaceInObject = function(obj, fn) {
if (obj === null) { if (obj === null) {
return null; return null;
} }
let type = typeof obj; let type = typeof obj;
if (type === 'undefined') { if (type === 'undefined') {
return {}; return {};
} }
if (type === 'string') { if (type === 'string') {
return fn(obj); return fn(obj);
} }
if (type === 'object') { if (type === 'object') {
Object.keys(obj).forEach(k => (obj[k] = replaceInObject(obj[k], fn))); Object.keys(obj).forEach(k => (obj[k] = replaceInObject(obj[k], fn)));
} }
return obj; return obj;
}; };
module.exports = { module.exports = {
requestRegex, requestRegex,
replacementRegex, replacementRegex,
dynamicValueRegex, dynamicValueRegex,
UpperCaseKeys, UpperCaseKeys,
removeOptionalKeys, removeOptionalKeys,
toKebabCase, toKebabCase,
replaceInObject replaceInObject
}; };