mirror of https://github.com/Seich/Beau.git
Initial Commit.
This commit is contained in:
commit
40f17dccaf
|
|
@ -0,0 +1 @@
|
|||
node_modules/
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
const fs = require('fs');
|
||||
const Beau = require('../beau');
|
||||
const yaml = require('js-yaml');
|
||||
|
||||
describe(`Beau's config Loader.`, () => {
|
||||
it ('Should only load valid configuration keys', () => {
|
||||
let HOST = 'http:/martianwabbit.com';
|
||||
let VERSION = 2;
|
||||
let CACHE = false;
|
||||
let shouldntBeAdded = true;
|
||||
|
||||
let beau = new Beau({
|
||||
VERSION,
|
||||
HOST,
|
||||
CACHE,
|
||||
shouldntBeAdded
|
||||
});
|
||||
|
||||
expect(beau.config.HOST).toBe(HOST);
|
||||
expect(beau.config.CACHE).toBe(CACHE);
|
||||
expect(beau.config.VERSION).toBe(VERSION);
|
||||
expect(beau.config.shouldntBeAdded).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
const Request = require('../request');
|
||||
const RequestCache = require('../requestCache');
|
||||
const RequestList = require('../requestList');
|
||||
|
||||
describe('Request', () => {
|
||||
let req;
|
||||
let cache;
|
||||
let request;
|
||||
|
||||
beforeEach(() => {
|
||||
req = {
|
||||
request: 'POST /user',
|
||||
HOST: 'http://martianwabbit.com',
|
||||
PARAMS: {
|
||||
userId: '$profile.UserId'
|
||||
},
|
||||
HEADERS: {
|
||||
authentication: 'BEARER $session.token'
|
||||
},
|
||||
PAYLOAD: {
|
||||
username: 'seich'
|
||||
}
|
||||
};
|
||||
|
||||
cache = new RequestCache();
|
||||
cache.add('$session', { token: 'abc123' });
|
||||
cache.add('$profile', { UserId: 14 });
|
||||
|
||||
request = new Request(req);
|
||||
});
|
||||
|
||||
test('It should load up the given request', () => {
|
||||
expect(request.$verb).toBe('POST');
|
||||
expect(request.$endpoint).toBe(req.HOST + '/user');
|
||||
expect(request.$headers).toBeDefined();
|
||||
expect(request.$payload).toBeDefined();
|
||||
expect(request.$params).toBeDefined();
|
||||
});
|
||||
|
||||
test('It should list all of its dependencies', () => {
|
||||
expect(request.$dependencies.size).toBe(2);
|
||||
expect(request.$dependencies).toContain('$session');
|
||||
expect(request.$dependencies).toContain('$profile');
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
const RequestCache = require('../requestCache');
|
||||
|
||||
describe('Request Cache', () => {
|
||||
let cache;
|
||||
|
||||
beforeEach(() => {
|
||||
cache = new RequestCache();
|
||||
|
||||
cache.add('$session', {
|
||||
hello: 'World'
|
||||
});
|
||||
|
||||
cache.add('$array', [{
|
||||
id: 1,
|
||||
name: 'Sergio'
|
||||
}, {
|
||||
id: 2,
|
||||
name: 'Angela'
|
||||
}]);
|
||||
});
|
||||
|
||||
it('should add keys to the cache', () => {
|
||||
expect(cache.$cache.$session.hello).toBe('World');
|
||||
});
|
||||
|
||||
describe('get', () => {
|
||||
it('should be able to find key values with a given path', () => {
|
||||
expect(cache.get('$session.hello')).toBe('World');
|
||||
});
|
||||
|
||||
it('should throw when given an invalid path', () => {
|
||||
expect(cache.get('$session.hello')).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('parse', () => {
|
||||
it('should transform variables in strings using it\'s cache', () => {
|
||||
expect(cache.parse('Hello $session.hello')).toBe('Hello World');
|
||||
});
|
||||
|
||||
it('should go transform variables in all values when given an object', () => {
|
||||
let parsed = cache.parse({ hello: 'hello $session.hello', earth: '$session.hello' });
|
||||
expect(parsed.hello).toBe('hello World');
|
||||
expect(parsed.earth).toBe('World');
|
||||
});
|
||||
|
||||
it('should return every non-string value as-is', () => {
|
||||
let parsed = cache.parse({ number: 1, nulled: null, truthy: false, hello: '$session.hello' });
|
||||
expect(parsed.number).toBe(1);
|
||||
expect(parsed.nulled).toBeNull();
|
||||
expect(parsed.truthy).toBe(false);
|
||||
expect(parsed.hello).toBe('World');
|
||||
});
|
||||
|
||||
it('should parse arrays as well', () => {
|
||||
let parsed = cache.parse({ hello: '$array.0.name' })
|
||||
expect(parsed.hello).toBe('Sergio');
|
||||
console.log([parsed]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('safely', () => {
|
||||
it('should return an object when given an undefined value', () => {
|
||||
expect(Object.keys(cache.safely(undefined)).length).toBe(0)
|
||||
});
|
||||
|
||||
it('should parse any value other than undefined', () => {
|
||||
expect(cache.safely('Hello $session.hello')).toBe('Hello World');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
const RequestList = require('../requestList');
|
||||
|
||||
describe('RequestList', () => {
|
||||
let host = 'http://martianwabbit.com';
|
||||
let doc = {
|
||||
'POST /session': null,
|
||||
'Not a Request': null,
|
||||
'POST /user': {
|
||||
ALIAS: '$user',
|
||||
PAYLOAD: {
|
||||
name: 'Sergio',
|
||||
lastname: 'Diaz'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
it('should load valid requests', () => {
|
||||
let requests = new RequestList(doc, { HOST: host });
|
||||
let request = requests.list[0];
|
||||
|
||||
expect(requests.list.length).toBe(2);
|
||||
expect(request.$verb).toBe('POST');
|
||||
expect(request.$endpoint).toBe(host + '/session');
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
const RequestList = require('./requestList');
|
||||
|
||||
class Beau {
|
||||
constructor(doc) {
|
||||
this.defaults = {
|
||||
VERSION: 1,
|
||||
CACHE: false,
|
||||
HOST: ''
|
||||
};
|
||||
|
||||
this.configKeys = Object.keys(this.defaults);
|
||||
this.config = this.loadConfig(doc, this.defaults);
|
||||
this.requests = new RequestList(doc, this.config);
|
||||
}
|
||||
|
||||
loadConfig(doc, defaults = {}) {
|
||||
var result = defaults;
|
||||
|
||||
Object.keys(doc)
|
||||
.filter(k => this.configKeys.indexOf(k.toUpperCase()) > -1)
|
||||
.forEach(k => result[k.toUpperCase()] = doc[k]);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = Beau;
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const program = require('commander');
|
||||
const Beau = require('../beau');
|
||||
const yaml = require('js-yaml');
|
||||
const fs = require('fs');
|
||||
const { Line, Spinner } = require('clui');
|
||||
const clc = require('cli-color');
|
||||
const eyes = require('eyes');
|
||||
|
||||
program
|
||||
.version('0.0.1')
|
||||
.usage(`[options] -r <Request Alias>`)
|
||||
.option('-r, --request [request]', `The alias for the request you'd like to trigger.`)
|
||||
.option('-v, --verbose', `Show all the information related to the current request and it's response.`)
|
||||
.option('-c, --config [file]', 'Specify your request config file. Defaults to beau.yml in the current directory.', 'beau.yml')
|
||||
.option('-l, --list', `List all requests in the config file.`)
|
||||
.option('-t, --truncate [length]', `Truncate the content to the given length`)
|
||||
.parse(process.argv);
|
||||
|
||||
if (!fs.existsSync(program.config)) {
|
||||
console.error(`The config file, ${program.config} was not found.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const config = yaml.safeLoad(fs.readFileSync(program.config, 'utf-8'));
|
||||
const beau = new Beau(config);
|
||||
|
||||
if (typeof program.list === 'undefined' &&
|
||||
typeof program.request === 'undefined') {
|
||||
|
||||
}
|
||||
|
||||
if (program.list) {
|
||||
new Line().padding(2)
|
||||
.column('HTTP Verb', 20, [clc.cyan])
|
||||
.column('Alias', 30, [clc.cyan])
|
||||
.column('Endpoint', 20, [clc.cyan])
|
||||
.output();
|
||||
|
||||
beau.requests.list.forEach(({$verb, $alias, $endpoint}) =>
|
||||
new Line().padding(2)
|
||||
.column($verb, 20, [clc.yellow])
|
||||
.column($alias, 30, [clc.yellow])
|
||||
.column($endpoint)
|
||||
.output()
|
||||
);
|
||||
|
||||
new Line().output();
|
||||
}
|
||||
|
||||
if (program.request) {
|
||||
const request = `$${program.request}`;
|
||||
const spinner = new Spinner(clc.yellow(`Requesting: ${request}`));
|
||||
|
||||
spinner.start();
|
||||
|
||||
beau.requests
|
||||
.execByAlias(request)
|
||||
.then(res => {
|
||||
spinner.stop();
|
||||
|
||||
let { status, body } = res.response;
|
||||
let { endpoint } = res.request;
|
||||
|
||||
status = status.toString().startsWith(2) ? clc.green(status) : clc.red(status);
|
||||
|
||||
new Line().padding(2)
|
||||
.column('Status', 20, [clc.cyan])
|
||||
.column('Endpoint', 20, [clc.cyan])
|
||||
.output();
|
||||
|
||||
new Line().padding(2)
|
||||
.column(status, 20)
|
||||
.column(endpoint)
|
||||
.output();
|
||||
|
||||
new Line().output();
|
||||
|
||||
let maxLength = +(program.truncate || res.length);
|
||||
let inspect = eyes.inspector({ maxLength });
|
||||
|
||||
if (program.verbose) {
|
||||
inspect(res);
|
||||
} else {
|
||||
inspect(body);
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
}).catch(function(err) {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
VERSION: 1
|
||||
HOST: https://api.github.com
|
||||
|
||||
auth: &auth
|
||||
HEADERS:
|
||||
Authorization: token asfdasf123423sd1fgnh7d83n478
|
||||
User-Agent: Beau
|
||||
|
||||
GET /user:
|
||||
ALIAS: $user
|
||||
<<: *auth
|
||||
|
||||
GET /user/repos:
|
||||
ALIAS: $repos
|
||||
<<: *auth
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
VERSION: '1'
|
||||
HOST: https://slack.com/api
|
||||
|
||||
auth: &auth
|
||||
token: xoxp-139455775026-139455775090-140860933030-239230833dba65fc90078876ae85d9fb
|
||||
|
||||
GET /users.getPresence:
|
||||
ALIAS: $presence
|
||||
PARAMS:
|
||||
<<: *auth
|
||||
|
||||
GET /channels.list:
|
||||
ALIAS: $channel-list
|
||||
PARAMS:
|
||||
<<: *auth
|
||||
exclude_archived: true
|
||||
|
||||
GET /channels.info:
|
||||
ALIAS: $channel-info
|
||||
PARAMS:
|
||||
<<: *auth
|
||||
channel: $channel-list.response.body.channels.0.id
|
||||
|
||||
POST /chat.postMessage:
|
||||
ALIAS: $new-message
|
||||
PARAMS:
|
||||
<<: *auth
|
||||
channel: $channel-info.response.body.channel.id
|
||||
text: 'Hey Seich!'
|
||||
parse: full
|
||||
link_names: true
|
||||
username: Beau
|
||||
|
||||
GET /users.list:
|
||||
ALIAS: $user-list
|
||||
PARAMS:
|
||||
<<: *auth
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"name": "beau",
|
||||
"version": "0.0.1",
|
||||
"description": "A tool for testing RESTful APIs",
|
||||
"main": "beau.js",
|
||||
"author": "Sergio Diaz <seich@martianwabbit.com>",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"cli-color": "^1.1.0",
|
||||
"clui": "^0.3.1",
|
||||
"commander": "^2.9.0",
|
||||
"eyes": "^0.1.8",
|
||||
"js-yaml": "^3.7.0",
|
||||
"unirest": "^0.5.1"
|
||||
},
|
||||
"repository": "git@github.com:Seich/Beau.git",
|
||||
"devDependencies": {
|
||||
"jest": "^17.0.3"
|
||||
},
|
||||
"jest": {
|
||||
"testEnvironment": "node",
|
||||
"notify": false
|
||||
},
|
||||
"bin": {
|
||||
"beau": "./bin/beau"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
const unirest = require('unirest');
|
||||
const {httpVerbs, requestRegex, replacementRegex} = require('./shared');
|
||||
const RequestList = require('./requestList');
|
||||
const RequestCache = require('./requestCache');
|
||||
|
||||
class Request {
|
||||
constructor(req) {
|
||||
let { request, ALIAS, PAYLOAD, HOST, PARAMS, HEADERS } = req;
|
||||
let { verb, endpoint } = this.parseRequest(request);
|
||||
|
||||
this.$verb = verb;
|
||||
this.$endpoint = HOST + endpoint;
|
||||
|
||||
this.$headers = HEADERS;
|
||||
this.$payload = PAYLOAD;
|
||||
this.$params = PARAMS;
|
||||
|
||||
this.$alias = ALIAS;
|
||||
this.$dependencies = this.findDependencies(req);
|
||||
}
|
||||
|
||||
parseRequest(request) {
|
||||
let parts = request.match(requestRegex);
|
||||
|
||||
return {
|
||||
verb: parts[1],
|
||||
endpoint: parts[2]
|
||||
};
|
||||
}
|
||||
|
||||
findDependencies(request, set = new Set()) {
|
||||
if (typeof request === 'object') {
|
||||
Object.keys(request).forEach(key => {
|
||||
if (key === 'ALIAS' || key.startsWith('$'))
|
||||
return;
|
||||
|
||||
set = this.findDependencies(request[key], set);
|
||||
});
|
||||
} else if (typeof request === 'string') {
|
||||
let matches = request.match(replacementRegex) || [];
|
||||
let deps = matches.map(m => m.split('.')[0]);
|
||||
|
||||
return new Set([...set, ...deps]);
|
||||
}
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
exec(list = new RequestList(), cache = new RequestCache()) {
|
||||
let dependencies = [];
|
||||
|
||||
if (this.$dependencies.size > 0) {
|
||||
dependencies = Array.from(this.$dependencies).map(dep => {
|
||||
return list.execByAlias(dep);
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.all(dependencies).then(() => {
|
||||
let endpoint = cache.parse(this.$endpoint);
|
||||
let request = unirest(this.$verb, endpoint);
|
||||
|
||||
request.headers(cache.safely(this.$headers));
|
||||
request.query(cache.safely(this.$params));
|
||||
request.send(cache.safely(this.$payload));
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
request.end(res => {
|
||||
let results = {
|
||||
request: {
|
||||
headers: res.request.headers,
|
||||
body: res.request.body,
|
||||
endpoint: endpoint
|
||||
},
|
||||
response: {
|
||||
status: res.status,
|
||||
headers: res.headers,
|
||||
body: res.body,
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
if (typeof this.$alias !== 'undefined') {
|
||||
cache.add(this.$alias, results);
|
||||
}
|
||||
|
||||
resolve(results);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Request;
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
const { replacementRegex } = require('./shared');
|
||||
|
||||
class RequestCache {
|
||||
constructor() {
|
||||
this.$cache = {};
|
||||
}
|
||||
|
||||
add(key, value) {
|
||||
this.$cache[key] = value;
|
||||
}
|
||||
|
||||
get(path) {
|
||||
let result = this.$cache;
|
||||
|
||||
path.split('.').forEach(part => {
|
||||
if (result === undefined) return;
|
||||
result = result[part];
|
||||
});
|
||||
|
||||
if (typeof result === 'undefined') {
|
||||
throw new Error('Key not found in cache: ', path);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
safely(val) {
|
||||
if (typeof val === 'undefined')
|
||||
return {};
|
||||
|
||||
return this.parse(val);
|
||||
}
|
||||
|
||||
parse(item) {
|
||||
if (typeof item === 'string') {
|
||||
return item.replace(replacementRegex, key => this.get(key));
|
||||
} else if(typeof item === 'object' && item !== null) {
|
||||
Object.keys(item).forEach(k => item[k] = this.parse(item[k]));
|
||||
return item;
|
||||
} else {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RequestCache;
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
const Request = require('./request');
|
||||
const RequestCache = require('./requestCache');
|
||||
const httpVerbs = require('./shared').httpVerbs;
|
||||
class RequestList {
|
||||
constructor(doc = {}, config = {}) {
|
||||
this.config = config;
|
||||
this.list = this.loadRequests(doc);
|
||||
this.cache = new RequestCache();
|
||||
}
|
||||
|
||||
exec(request) {
|
||||
return request.exec(this, this.cache);
|
||||
}
|
||||
|
||||
execByAlias(alias) {
|
||||
let request = this.list.find(r => r.$alias === alias);
|
||||
|
||||
if (typeof request === 'undefined') {
|
||||
return Promise.reject(`${alias} not found among the requests.`);
|
||||
}
|
||||
|
||||
return this.exec(request);
|
||||
}
|
||||
|
||||
loadRequests(doc) {
|
||||
let requestKeys = Object.keys(doc)
|
||||
.filter(key => {
|
||||
let verb = key.split(' ')[0].toUpperCase();
|
||||
return httpVerbs.indexOf(verb) > -1;
|
||||
});
|
||||
|
||||
return requestKeys.map(key => {
|
||||
doc[key] = doc[key] || {};
|
||||
|
||||
doc[key].HOST = this.config.HOST;
|
||||
doc[key].request = key;
|
||||
|
||||
return new Request(doc[key]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RequestList;
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
const httpVerbs = ['POST', 'GET', 'PUT', 'PATCH', 'DELETE'];
|
||||
const requestRegex = new RegExp(`(${httpVerbs.join('|')})\\s(.*)`, 'i');
|
||||
const replacementRegex = /(\$[a-zA-Z\.\d\-\_]*)/g;
|
||||
|
||||
module.exports = {
|
||||
httpVerbs,
|
||||
requestRegex,
|
||||
replacementRegex
|
||||
};
|
||||
Loading…
Reference in New Issue