Added a new top-level setting: defaults.

Defaults allow you to set default settings for all requests. You can
pass any configuration value and it'll be merged with any values the
request already has allowing you to write even less repetitive code.
This commit is contained in:
David Diaz 2017-12-27 15:08:35 -06:00
parent 78a12e99f2
commit 40abf85a78
16 changed files with 2228 additions and 357 deletions

View File

@ -4,7 +4,11 @@
<h1 align="center">Beau</h1> <h1 align="center">Beau</h1>
<p align="center">Testing JSON APIs made easy.</p> <p align="center">Testing JSON APIs made easy.</p>
<p align="center">
<a href="https://codeclimate.com/github/Seich/Beau/maintainability"><img src="https://api.codeclimate.com/v1/badges/bc2de4d71893d6a2d18b/maintainability" /></a>
<a href="https://codeclimate.com/github/Seich/Beau/test_coverage"><img src="https://api.codeclimate.com/v1/badges/bc2de4d71893d6a2d18b/test_coverage" /></a>
<a href="https://circleci.com/gh/Seich/Beau/tree/master"><img src="https://circleci.com/gh/Seich/Beau/tree/master.svg?style=svg" alt="CircleCI"></a>
</p>
## What is Beau? ## What is Beau?
Beau, is a CLI that executes HTTP requests based on a YAML configuration file. This makes testing easy, it allows you to share test requests with others as part of your repo. Beau, is a CLI that executes HTTP requests based on a YAML configuration file. This makes testing easy, it allows you to share test requests with others as part of your repo.
@ -33,7 +37,7 @@ Beau, is a CLI that executes HTTP requests based on a YAML configuration file. T
## Example Configuration File ## Example Configuration File
version: 1 version: 1
host: https://example.com/api/ endpoint: https://example.com/api/
POST /session: POST /session:
ALIAS: session ALIAS: session

View File

@ -0,0 +1,96 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Beau's config Loader. should set up defaults for all requests 1`] = `
Beau {
"config": Object {
"CACHE": false,
"DEFAULTS": Object {
"headers": Object {
"authentication": "hello",
},
},
"ENDPOINT": "http://jsonplaceholder.typicode.com",
"PLUGINS": Array [],
"VERSION": 1,
},
"configKeys": Array [
"VERSION",
"CACHE",
"ENDPOINT",
"PLUGINS",
"DEFAULTS",
],
"defaults": Object {
"CACHE": false,
"DEFAULTS": Object {
"headers": Object {
"authentication": "hello",
},
},
"ENDPOINT": "http://jsonplaceholder.typicode.com",
"PLUGINS": Array [],
"VERSION": 1,
},
"requests": RequestList {
"cache": RequestCache {
"$cache": Object {},
},
"config": Object {
"CACHE": false,
"DEFAULTS": Object {
"headers": Object {
"authentication": "hello",
},
},
"ENDPOINT": "http://jsonplaceholder.typicode.com",
"PLUGINS": Array [],
"VERSION": 1,
},
"list": Array [
Request {
"ALIAS": "get-post",
"DEPENDENCIES": Set {},
"DOCUMENTATION": undefined,
"ENDPOINT": "http://jsonplaceholder.typicode.com/posts/1",
"HEADERS": Object {
"authentication": "hello",
},
"PARAMS": undefined,
"PAYLOAD": undefined,
"VERB": "GET",
"originalRequest": Object {
"ALIAS": "get-post",
"ENDPOINT": "http://jsonplaceholder.typicode.com",
"headers": Object {
"authentication": "hello",
},
"request": "GET /posts/1",
},
},
Request {
"ALIAS": "user",
"DEPENDENCIES": Set {},
"DOCUMENTATION": undefined,
"ENDPOINT": "http://jsonplaceholder.typicode.com/user",
"HEADERS": Object {
"authentication": "hello",
"hello": "world",
},
"PARAMS": undefined,
"PAYLOAD": undefined,
"VERB": "GET",
"originalRequest": Object {
"ENDPOINT": "http://jsonplaceholder.typicode.com",
"alias": "user",
"headers": Object {
"authentication": "hello",
"hello": "world",
},
"request": "GET /user",
},
},
],
"modifiers": Array [],
},
}
`;

View File

@ -48,7 +48,7 @@ Array [
}, },
Object { Object {
"alias": "show", "alias": "show",
"host": "http://martianwabbit.com", "endpoint": "http://martianwabbit.com",
"request": "GET /user", "request": "GET /user",
}, },
], ],

View File

@ -1,23 +1,44 @@
const Beau = require('../beau');
const yaml = require('js-yaml'); const yaml = require('js-yaml');
const Beau = require('../beau');
describe(`Beau's config Loader.`, () => { describe(`Beau's config Loader.`, () => {
it('Should only load valid configuration keys', () => { it('Should only load valid configuration keys', () => {
let host = 'http://martianwabbit.com'; const doc = yaml.safeLoad(`
let version = 1; version: 1
let cache = false; endpoint: http://martianwabbit.com
let shouldntBeAdded = true; cache: false
shouldntBeAdded: true
`);
let beau = new Beau({ const beau = new Beau(doc);
version,
host,
cache,
shouldntBeAdded
});
expect(beau.config.HOST).toBe(host); expect(beau.config.ENDPOINT).toBe(doc.endpoint);
expect(beau.config.CACHE).toBe(cache); expect(beau.config.CACHE).toBe(doc.cache);
expect(beau.config.VERSION).toBe(version); expect(beau.config.VERSION).toBe(doc.version);
expect(beau.config.shouldntBeAdded).toBeUndefined(); expect(beau.config.shouldntBeAdded).toBeUndefined();
}); });
it('should set up defaults for all requests', () => {
const doc = yaml.safeLoad(`
version: 1
endpoint: 'http://jsonplaceholder.typicode.com'
defaults:
headers:
authentication: hello
GET /posts/1: get-post
GET /user:
alias: user
headers:
hello: world
`);
const beau = new Beau(doc);
expect(beau).toMatchSnapshot();
beau.requests.list.forEach(r => {
expect(r.HEADERS.authentication).toMatch('hello');
});
});
}); });

View File

@ -12,7 +12,7 @@ describe('Request', () => {
beforeEach(() => { beforeEach(() => {
req = { req = {
request: 'POST /user', request: 'POST /user',
host: 'http://martianwabbit.com', endpoint: 'http://martianwabbit.com',
alias: 'update', alias: 'update',
params: { params: {
userId: '$profile.UserId' userId: '$profile.UserId'
@ -31,7 +31,7 @@ describe('Request', () => {
request = new Request(req); request = new Request(req);
requestWithoutDependencies = new Request({ requestWithoutDependencies = new Request({
host: 'http://martianwabbit.com', endpoint: 'http://martianwabbit.com',
request: 'GET /user', request: 'GET /user',
alias: 'show' alias: 'show'
}); });
@ -41,7 +41,7 @@ describe('Request', () => {
test('It should load up the given request', () => { test('It should load up the given request', () => {
expect(request.VERB).toBe('POST'); expect(request.VERB).toBe('POST');
expect(request.ENDPOINT).toBe(req.host + '/user'); expect(request.ENDPOINT).toBe(req.endpoint + '/user');
expect(request.HEADERS).toBeDefined(); expect(request.HEADERS).toBeDefined();
expect(request.PAYLOAD).toBeDefined(); expect(request.PAYLOAD).toBeDefined();
expect(request.PARAMS).toBeDefined(); expect(request.PARAMS).toBeDefined();

View File

@ -2,11 +2,11 @@ const RequestList = require('../requestList');
const requestPromiseNativeMock = require('request-promise-native'); const requestPromiseNativeMock = require('request-promise-native');
describe('RequestList', () => { describe('RequestList', () => {
const host = 'http://martianwabbit.com'; const endpoint = 'http://martianwabbit.com';
const doc = { const doc = {
'POST /session': null, 'POST /session': null,
'Not a Request': null, 'Not a Request': null,
'GET /post': 'get-posts', 'GET /post': { alias: 'get-posts' },
'POST /user': { 'POST /user': {
alias: 'user', alias: 'user',
payload: { payload: {
@ -20,7 +20,7 @@ describe('RequestList', () => {
beforeEach(() => { beforeEach(() => {
requestPromiseNativeMock.fail = false; requestPromiseNativeMock.fail = false;
requests = new RequestList(doc, { requests = new RequestList(doc, {
HOST: host, ENDPOINT: endpoint,
PLUGINS: [ PLUGINS: [
{ {
'beau-jwt': { 'beau-jwt': {
@ -40,7 +40,7 @@ describe('RequestList', () => {
expect(requests.list.length).toBe(3); expect(requests.list.length).toBe(3);
expect(request.VERB).toBe('POST'); expect(request.VERB).toBe('POST');
expect(request.ENDPOINT).toBe(host + '/session'); expect(request.ENDPOINT).toBe(endpoint + '/session');
}); });
it('should fetch dependencies', () => { it('should fetch dependencies', () => {

32
beau.js
View File

@ -1,17 +1,43 @@
const deepMerge = require('deepmerge');
const RequestList = require('./requestList'); const RequestList = require('./requestList');
const requestRegex = require('./shared').requestRegex;
class Beau { class Beau {
constructor(doc) { constructor(doc) {
this.defaults = { this.defaults = {
VERSION: 1, VERSION: 1,
CACHE: false, CACHE: false,
HOST: '', ENDPOINT: '',
PLUGINS: [] PLUGINS: [],
DEFAULTS: []
}; };
this.configKeys = Object.keys(this.defaults); this.configKeys = Object.keys(this.defaults);
this.config = this.loadConfig(doc); this.config = this.loadConfig(doc);
this.requests = new RequestList(doc, this.config); this.requests = this.getRequests(doc);
this.requests = new RequestList(this.requests, this.config);
}
getRequests(doc) {
let requests = Object.keys(doc).filter(key => {
return requestRegex.test(key);
});
let results = {};
requests.forEach(r => {
if (typeof doc[r] === 'string') {
results[r] = {
ALIAS: doc[r]
};
} else {
results[r] = doc[r];
}
results[r] = deepMerge(this.config.DEFAULTS, results[r]);
});
return results;
} }
loadConfig(doc) { loadConfig(doc) {

12
circle.yml Normal file
View File

@ -0,0 +1,12 @@
machine:
node:
version: 8.9.3
dependencies:
post:
- npm install -g codeclimate-test-reporter
test:
pre:
- npm run test:coverage
- codeclimate-test-reporter < ./coverage/lcov.info

View File

@ -1,5 +1,5 @@
VERSION: 1 VERSION: 1
HOST: https://api.github.com ENDPOINT: https://api.github.com
auth: &auth auth: &auth
HEADERS: HEADERS:

View File

@ -1,13 +1,16 @@
version: 1 version: 1
host: 'http://jsonplaceholder.typicode.com' endpoint: 'http://jsonplaceholder.typicode.com'
defaults:
headers:
hello: $posts.body.0.userId
GET /posts/1: get-post GET /posts/1: get-post
GET /posts/: GET /posts/:
alias: posts alias: posts
documentation: headers:
title: Fetch Posts hello: false
description: Fetches all posts available.
POST /posts/: POST /posts/:
alias: new-post alias: new-post

View File

@ -1,4 +1,4 @@
host: http://localhost:10080 endpoint: http://localhost:10080
plugins: plugins:
- beau-jwt: - beau-jwt:

View File

@ -1,5 +1,5 @@
VERSION: '1' VERSION: '1'
HOST: https://slack.com/api ENDPOINT: https://slack.com/api
auth: &auth auth: &auth
token: xoxp-139455775026-139455775090-147751461120-f224ed6ffee029869a0f138d0859e7d6 token: xoxp-139455775026-139455775090-147751461120-f224ed6ffee029869a0f138d0859e7d6

2321
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -7,12 +7,14 @@
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"test": "jest", "test": "jest",
"watch": "jest --watch" "watch": "jest --watch",
"test:coverage": "jest --coverage"
}, },
"dependencies": { "dependencies": {
"cli-color": "^1.1.0", "cli-color": "^1.1.0",
"clui": "^0.3.1", "clui": "^0.3.1",
"commander": "^2.12.2", "commander": "^2.12.2",
"deepmerge": "^2.0.1",
"js-yaml": "^3.7.0", "js-yaml": "^3.7.0",
"jsome": "^2.3.26", "jsome": "^2.3.26",
"request": "^2.83.0", "request": "^2.83.0",
@ -29,5 +31,8 @@
}, },
"bin": { "bin": {
"beau": "./bin/beau" "beau": "./bin/beau"
},
"engines": {
"node": ">=8.9.3"
} }
} }

View File

@ -14,15 +14,15 @@ class Request {
REQUEST, REQUEST,
ALIAS, ALIAS,
PAYLOAD, PAYLOAD,
HOST, ENDPOINT,
PARAMS, PARAMS,
HEADERS, HEADERS,
DOCUMENTATION DOCUMENTATION
} = config; } = config;
const { verb, endpoint } = this.parseRequest(REQUEST); const { verb, path } = this.parseRequest(REQUEST);
this.VERB = verb; this.VERB = verb;
this.ENDPOINT = HOST + endpoint; this.ENDPOINT = ENDPOINT + path;
this.HEADERS = HEADERS; this.HEADERS = HEADERS;
this.PAYLOAD = PAYLOAD; this.PAYLOAD = PAYLOAD;
@ -39,11 +39,11 @@ class Request {
} }
parseRequest(request) { parseRequest(request) {
let parts = request.match(requestRegex); const parts = request.match(requestRegex);
return { return {
verb: parts[1], verb: parts[1],
endpoint: parts[2] path: parts[2]
}; };
} }

View File

@ -51,17 +51,8 @@ class RequestList {
}); });
return requests.map(request => { return requests.map(request => {
const type = typeof doc[request];
if (type === 'string') {
doc[request] = {
ALIAS: doc[request]
};
}
doc[request] = doc[request] || {}; doc[request] = doc[request] || {};
doc[request].ENDPOINT = this.config.ENDPOINT;
doc[request].HOST = this.config.HOST;
doc[request].request = request; doc[request].request = request;
return new Request(doc[request]); return new Request(doc[request]);