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>
<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?
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
version: 1
host: https://example.com/api/
endpoint: https://example.com/api/
POST /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 {
"alias": "show",
"host": "http://martianwabbit.com",
"endpoint": "http://martianwabbit.com",
"request": "GET /user",
},
],

View File

@ -1,23 +1,44 @@
const Beau = require('../beau');
const yaml = require('js-yaml');
const Beau = require('../beau');
describe(`Beau's config Loader.`, () => {
it('Should only load valid configuration keys', () => {
let host = 'http://martianwabbit.com';
let version = 1;
let cache = false;
let shouldntBeAdded = true;
it('Should only load valid configuration keys', () => {
const doc = yaml.safeLoad(`
version: 1
endpoint: http://martianwabbit.com
cache: false
shouldntBeAdded: true
`);
let beau = new Beau({
version,
host,
cache,
shouldntBeAdded
});
const beau = new Beau(doc);
expect(beau.config.HOST).toBe(host);
expect(beau.config.CACHE).toBe(cache);
expect(beau.config.VERSION).toBe(version);
expect(beau.config.shouldntBeAdded).toBeUndefined();
});
expect(beau.config.ENDPOINT).toBe(doc.endpoint);
expect(beau.config.CACHE).toBe(doc.cache);
expect(beau.config.VERSION).toBe(doc.version);
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(() => {
req = {
request: 'POST /user',
host: 'http://martianwabbit.com',
endpoint: 'http://martianwabbit.com',
alias: 'update',
params: {
userId: '$profile.UserId'
@ -31,7 +31,7 @@ describe('Request', () => {
request = new Request(req);
requestWithoutDependencies = new Request({
host: 'http://martianwabbit.com',
endpoint: 'http://martianwabbit.com',
request: 'GET /user',
alias: 'show'
});
@ -41,7 +41,7 @@ describe('Request', () => {
test('It should load up the given request', () => {
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.PAYLOAD).toBeDefined();
expect(request.PARAMS).toBeDefined();

View File

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

32
beau.js
View File

@ -1,17 +1,43 @@
const deepMerge = require('deepmerge');
const RequestList = require('./requestList');
const requestRegex = require('./shared').requestRegex;
class Beau {
constructor(doc) {
this.defaults = {
VERSION: 1,
CACHE: false,
HOST: '',
PLUGINS: []
ENDPOINT: '',
PLUGINS: [],
DEFAULTS: []
};
this.configKeys = Object.keys(this.defaults);
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) {

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
HOST: https://api.github.com
ENDPOINT: https://api.github.com
auth: &auth
HEADERS:

View File

@ -1,13 +1,16 @@
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/:
alias: posts
documentation:
title: Fetch Posts
description: Fetches all posts available.
headers:
hello: false
POST /posts/:
alias: new-post

View File

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

View File

@ -1,5 +1,5 @@
VERSION: '1'
HOST: https://slack.com/api
ENDPOINT: https://slack.com/api
auth: &auth
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",
"scripts": {
"test": "jest",
"watch": "jest --watch"
"watch": "jest --watch",
"test:coverage": "jest --coverage"
},
"dependencies": {
"cli-color": "^1.1.0",
"clui": "^0.3.1",
"commander": "^2.12.2",
"deepmerge": "^2.0.1",
"js-yaml": "^3.7.0",
"jsome": "^2.3.26",
"request": "^2.83.0",
@ -29,5 +31,8 @@
},
"bin": {
"beau": "./bin/beau"
},
"engines": {
"node": ">=8.9.3"
}
}

View File

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

View File

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