mirror of https://github.com/Seich/Beau.git
Compare commits
No commits in common. "master" and "v0.9.5" have entirely different histories.
|
|
@ -1,37 +0,0 @@
|
|||
name: Tests
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2-beta
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
npm install
|
||||
wget https://codeclimate.com/downloads/test-reporter/test-reporter-0.6.3-linux-amd64 -O cc-test-reporter
|
||||
chmod +x ./cc-test-reporter
|
||||
|
||||
- name: Validate Schema
|
||||
run: |
|
||||
npx ajv-cli compile -s schema.json
|
||||
npx ajv-cli validate -s schema.json -d examples/beau.yml
|
||||
- name: Run CLI Tests
|
||||
run: npm test -- ./bin
|
||||
- name: Run Lib Tests
|
||||
run: npm run test:coverage
|
||||
- name: Report Results
|
||||
if: success()
|
||||
run: |
|
||||
./cc-test-reporter format-coverage
|
||||
./cc-test-reporter upload-coverage
|
||||
env:
|
||||
GIT_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
GIT_BRANCH: ${{ github.head_ref }}
|
||||
CC_TEST_REPORTER_ID: ${{ secrets.CODECLIMATE_REPO_TOKEN }}
|
||||
|
|
@ -5,6 +5,7 @@ useTabs: false
|
|||
trailingComma: none
|
||||
bracketSpacing: true
|
||||
jsxBracketSameLine: true
|
||||
semi: false
|
||||
parser: babylon
|
||||
semi: true
|
||||
requirePragma: false
|
||||
proseWrap: always
|
||||
|
|
|
|||
42
LICENSE
42
LICENSE
|
|
@ -1,41 +1,7 @@
|
|||
“Commons Clause” License Condition v1.0
|
||||
Copyright 2018 David Sergio Díaz
|
||||
|
||||
The Software is provided to you by the Licensor under the License, as defined
|
||||
below, subject to the following condition.
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
Without limiting other conditions in the License, the grant of rights under the
|
||||
License will not include, and the License does not grant to you, the right to
|
||||
Sell the Software.
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
For purposes of the foregoing, “Sell” means practicing any or all of the rights
|
||||
granted to you under the License to provide to third parties, for a fee or other
|
||||
consideration (including without limitation fees for hosting or consulting/
|
||||
support services related to the Software), a product or service whose value
|
||||
derives, entirely or substantially, from the functionality of the Software. Any
|
||||
license notice or attribution required by the License must also include this
|
||||
Commons Clause License Condition notice.
|
||||
|
||||
Software: Beau
|
||||
License: MIT
|
||||
Licensor: David Díaz
|
||||
|
||||
---
|
||||
|
||||
Copyright 2020 David Díaz
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
|
|
|||
14
README.md
14
README.md
|
|
@ -1,5 +1,5 @@
|
|||
<div align="center">
|
||||
<img src="media/beau.png" height="144" alt="Beau's Logo is a Seahorse" />
|
||||
<img src="http://files.martianwabbit.com/beau.png?1" height="144"/>
|
||||
</div>
|
||||
|
||||
<h1 align="center">Beau</h1>
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
<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>
|
||||
<img src="https://github.com/Seich/Beau/workflows/Tests/badge.svg"/>
|
||||
<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?
|
||||
|
|
@ -15,9 +15,7 @@
|
|||
Beau is a modern http client. It uses a YAML file as configuration allowing you
|
||||
to test APIs without having to write lengthy commands.
|
||||
|
||||
<div align="center">
|
||||
<img src="media/usage.gif" alt="A gif showing how beau works." />
|
||||
</div>
|
||||

|
||||
|
||||
## Installation
|
||||
|
||||
|
|
@ -41,7 +39,8 @@ endpoint: https://httpbin.org/
|
|||
POST /anything:
|
||||
alias: anything
|
||||
payload:
|
||||
hello: world
|
||||
username: sergio
|
||||
password: password1
|
||||
```
|
||||
|
||||
```
|
||||
|
|
@ -53,7 +52,8 @@ Status Endpoint
|
|||
{
|
||||
...
|
||||
json: {
|
||||
hello: "world"
|
||||
password: "password1",
|
||||
username: "sergio"
|
||||
},
|
||||
method: "POST",
|
||||
url: "https://httpbin.org/anything"
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
const Beau = require('../../../src/beau')
|
||||
|
||||
const original = jest.requireActual('../base')
|
||||
|
||||
const config = {
|
||||
environment: {
|
||||
params: {
|
||||
name: 'David'
|
||||
}
|
||||
},
|
||||
endpoint: 'https://example.org',
|
||||
version: 1,
|
||||
'GET /anything': {
|
||||
alias: 'alias',
|
||||
payload: {
|
||||
name: '$env.params.name'
|
||||
}
|
||||
},
|
||||
'GET /status/418': {
|
||||
alias: 'teapot'
|
||||
}
|
||||
}
|
||||
|
||||
class Base extends original {
|
||||
loadConfig(configFile, params = []) {
|
||||
return new Beau(config, {})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Base
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
const fs = jest.genMockFromModule('fs')
|
||||
const fs = jest.genMockFromModule('fs');
|
||||
|
||||
fs.existsSync = (filename) => filename === 'beau.yml'
|
||||
fs.existsSync = filename => filename === 'beau.yml';
|
||||
fs.readFileSync = () => `
|
||||
version: 1
|
||||
endpoint: https://example.org/
|
||||
|
|
@ -9,6 +9,6 @@ GET /anything:
|
|||
alias: anything
|
||||
payload:
|
||||
name: $env.params.name
|
||||
`
|
||||
`;
|
||||
|
||||
module.exports = fs
|
||||
module.exports = fs;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
const Beau = require('../../../src/beau');
|
||||
const original = require.requireActual('../utils');
|
||||
|
||||
const utils = {};
|
||||
|
||||
const config = {
|
||||
environment: {
|
||||
params: {
|
||||
name: 'David'
|
||||
}
|
||||
},
|
||||
endpoint: 'https://example.org',
|
||||
version: 1,
|
||||
'GET /anything': {
|
||||
alias: 'alias',
|
||||
payload: {
|
||||
name: '$env.params.name'
|
||||
}
|
||||
},
|
||||
'GET /status/418': {
|
||||
alias: 'teapot'
|
||||
}
|
||||
};
|
||||
|
||||
utils.loadConfig = function() {
|
||||
return new Beau(config, {});
|
||||
};
|
||||
|
||||
utils.openConfigFile = function(filename) {
|
||||
if (filename === 'beau.yml') {
|
||||
return config;
|
||||
}
|
||||
|
||||
if (filename === 'invalid-conf.yml') {
|
||||
return { plugins: [{ hello: 1, world: 2 }] };
|
||||
}
|
||||
};
|
||||
|
||||
utils.baseFlags = original.baseFlags;
|
||||
|
||||
module.exports = utils;
|
||||
|
|
@ -9,7 +9,7 @@ Array [
|
|||
",
|
||||
"
|
||||
",
|
||||
"{\\"hello\\": \\"world\\"}
|
||||
"\\"{\\"hello\\": \\"world\\"}\\"
|
||||
",
|
||||
]
|
||||
`;
|
||||
|
|
@ -53,18 +53,18 @@ Array [
|
|||
"
|
||||
",
|
||||
"{
|
||||
\\"request\\": {
|
||||
\\"body\\": {
|
||||
\\"name\\": \\"David\\"
|
||||
request: {
|
||||
body: {
|
||||
name: \\"David\\"
|
||||
},
|
||||
\\"endpoint\\": \\"https://example.org/anything\\"
|
||||
endpoint: \\"https://example.org/anything\\"
|
||||
},
|
||||
\\"response\\": {
|
||||
\\"status\\": 200,
|
||||
\\"headers\\": [],
|
||||
\\"body\\": \\"{\\\\\\"hello\\\\\\": \\\\\\"world\\\\\\"}\\"
|
||||
response: {
|
||||
status: 200,
|
||||
headers: [],
|
||||
body: \\"{\\"hello\\": \\"world\\"}\\"
|
||||
},
|
||||
\\"body\\": \\"{\\\\\\"hello\\\\\\": \\\\\\"world\\\\\\"}\\"
|
||||
body: \\"{\\"hello\\": \\"world\\"}\\"
|
||||
}
|
||||
",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`utils loadConfig should load load the config onto Beau 1`] = `
|
||||
Config {
|
||||
"COOKIEJAR": false,
|
||||
"DEFAULTS": Object {},
|
||||
"ENDPOINT": "https://example.org/",
|
||||
"ENVIRONMENT": Object {
|
||||
"_": Object {},
|
||||
},
|
||||
"HOSTS": Array [],
|
||||
"PLUGINS": Plugins {
|
||||
"autoload": Array [
|
||||
"std",
|
||||
],
|
||||
"context": Object {
|
||||
"createReadStream": [Function],
|
||||
},
|
||||
"registry": Object {
|
||||
"dynamicValues": Array [
|
||||
Object {
|
||||
"fn": [Function],
|
||||
"name": "createReadStream",
|
||||
},
|
||||
],
|
||||
"postRequestModifiers": Array [],
|
||||
"preRequestModifiers": Array [],
|
||||
},
|
||||
},
|
||||
"REQUESTS": Array [
|
||||
Object {
|
||||
"ALIAS": "anything",
|
||||
"COOKIEJAR": false,
|
||||
"ENDPOINT": "https://example.org/",
|
||||
"PAYLOAD": Object {
|
||||
"name": "$env.params.name",
|
||||
},
|
||||
"REQUEST": "GET /anything",
|
||||
},
|
||||
],
|
||||
"VERSION": 1,
|
||||
"configKeys": Array [
|
||||
"VERSION",
|
||||
"ENDPOINT",
|
||||
"PLUGINS",
|
||||
"DEFAULTS",
|
||||
"ENVIRONMENT",
|
||||
"HOSTS",
|
||||
"COOKIEJAR",
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`utils loadConfig should load params onto the environment 1`] = `
|
||||
Object {
|
||||
"_": Object {
|
||||
"BYE": "MARS",
|
||||
"HELLO": "WORLD",
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`utils openConfigFile should read and parse the given configuration file. 1`] = `
|
||||
Object {
|
||||
"GET /anything": Object {
|
||||
"alias": "anything",
|
||||
"payload": Object {
|
||||
"name": "$env.params.name",
|
||||
},
|
||||
},
|
||||
"endpoint": "https://example.org/",
|
||||
"version": 1,
|
||||
}
|
||||
`;
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Validate Command should validate the configuration file 1`] = `
|
||||
Array [
|
||||
"beau.yml is valid.
|
||||
",
|
||||
]
|
||||
`;
|
||||
|
|
@ -1,23 +1,23 @@
|
|||
const ListCommand = require('../commands/list')
|
||||
const ListCommand = require('../commands/list');
|
||||
|
||||
jest.mock('../../../src/shared')
|
||||
jest.mock('../../../src/shared');
|
||||
|
||||
jest.mock('../base')
|
||||
jest.mock('../utils');
|
||||
|
||||
describe('List Command', () => {
|
||||
let result
|
||||
let result;
|
||||
|
||||
beforeEach(() => {
|
||||
result = []
|
||||
jest.spyOn(process.stdout, 'write').mockImplementation((val) =>
|
||||
result = [];
|
||||
jest.spyOn(process.stdout, 'write').mockImplementation(val =>
|
||||
result.push(require('strip-ansi')(val.toString('utf8')))
|
||||
)
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => jest.restoreAllMocks())
|
||||
afterEach(() => jest.restoreAllMocks());
|
||||
|
||||
test.each([[], ['--no-format']])('with flags:', async (...args) => {
|
||||
await ListCommand.run(args)
|
||||
expect(result).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
await ListCommand.run(args);
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,22 +1,22 @@
|
|||
const RequestCommand = require('../commands/request')
|
||||
const requestPromiseNativeMock = require('request-promise-native')
|
||||
const RequestCommand = require('../commands/request');
|
||||
const requestPromiseNativeMock = require('request-promise-native');
|
||||
|
||||
jest.mock('../../../src/shared')
|
||||
jest.mock('../../../src/shared');
|
||||
|
||||
jest.mock('../base')
|
||||
jest.mock('../utils');
|
||||
|
||||
describe('Request Command', () => {
|
||||
let result
|
||||
let result;
|
||||
|
||||
beforeEach(() => {
|
||||
requestPromiseNativeMock.fail = false
|
||||
result = []
|
||||
jest.spyOn(process.stdout, 'write').mockImplementation((val) =>
|
||||
requestPromiseNativeMock.fail = false;
|
||||
result = [];
|
||||
jest.spyOn(process.stdout, 'write').mockImplementation(val =>
|
||||
result.push(require('strip-ansi')(val.toString('utf8')))
|
||||
)
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => jest.restoreAllMocks())
|
||||
afterEach(() => jest.restoreAllMocks());
|
||||
|
||||
test.each([
|
||||
['alias'],
|
||||
|
|
@ -26,12 +26,12 @@ describe('Request Command', () => {
|
|||
['alias', '--no-format'],
|
||||
['alias', '--quiet']
|
||||
])('with flags: %s %s %s', async (...args) => {
|
||||
await RequestCommand.run(args)
|
||||
expect(result).toMatchSnapshot()
|
||||
})
|
||||
await RequestCommand.run(args);
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should throw an error when the request fails', async () => {
|
||||
requestPromiseNativeMock.fail = true
|
||||
await expect(RequestCommand.run(['anything'])).rejects.toThrow(Error)
|
||||
})
|
||||
})
|
||||
requestPromiseNativeMock.fail = true;
|
||||
await expect(RequestCommand.run(['anything'])).rejects.toThrow(Error);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
const utils = require('../utils.js');
|
||||
|
||||
jest.mock('../../../src/shared');
|
||||
|
||||
jest.mock('fs');
|
||||
|
||||
describe('utils', () => {
|
||||
describe('openConfigFile', () => {
|
||||
it('should read and parse the given configuration file.', () => {
|
||||
expect(utils.openConfigFile('beau.yml')).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should throw if given not given a file', () => {
|
||||
expect(() => utils.openConfigFile('not-a-file.yml')).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('loadConfig', () => {
|
||||
it('should load load the config onto Beau', () => {
|
||||
let beau = utils.loadConfig('beau.yml');
|
||||
expect(beau.config).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should load params onto the environment', () => {
|
||||
let beau = utils.loadConfig('beau.yml', [
|
||||
'HELLO=WORLD',
|
||||
'BYE=MARS'
|
||||
]);
|
||||
expect(beau.config.ENVIRONMENT).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
const ValidateCommand = require('../commands/validate');
|
||||
|
||||
jest.mock('../utils');
|
||||
|
||||
describe('Validate Command', () => {
|
||||
let result;
|
||||
|
||||
beforeEach(() => {
|
||||
result = [];
|
||||
jest.spyOn(process.stdout, 'write').mockImplementation(val =>
|
||||
result.push(require('strip-ansi')(val.toString('utf8')))
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => jest.restoreAllMocks());
|
||||
|
||||
it('should validate the configuration file', async () => {
|
||||
await ValidateCommand.run([]);
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should show schema errors', async () => {
|
||||
await expect(
|
||||
ValidateCommand.run(['invalid-conf.yml'])
|
||||
).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
const { Command, flags } = require('@oclif/command')
|
||||
const yaml = require('js-yaml')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const dotenv = require('dotenv')
|
||||
const Beau = require('../../src/beau')
|
||||
const Ajv = require('ajv').default
|
||||
const betterAjvErrors = require('better-ajv-errors')
|
||||
|
||||
const schema = require('../../schema.json')
|
||||
const ajv = new Ajv()
|
||||
const validate = ajv.compile(schema)
|
||||
|
||||
class Base extends Command {
|
||||
openConfigFile(configFile) {
|
||||
if (!fs.existsSync(configFile)) {
|
||||
throw new Error(`The config file, ${configFile} was not found.`)
|
||||
}
|
||||
|
||||
let config
|
||||
yaml.loadAll(fs.readFileSync(configFile, 'utf-8'), (doc) => {
|
||||
const valid = validate(doc)
|
||||
|
||||
if (!valid) {
|
||||
this.log(`The configuration file is not valid.`)
|
||||
this.error(
|
||||
betterAjvErrors(schema, doc, validate.errors, { indent: 2 })
|
||||
)
|
||||
}
|
||||
|
||||
if (typeof config === 'undefined') {
|
||||
config = doc
|
||||
} else {
|
||||
if (typeof config.hosts === 'undefined') {
|
||||
config.hosts = []
|
||||
}
|
||||
|
||||
config.hosts.push(doc)
|
||||
}
|
||||
})
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
loadConfig(configFile, params = []) {
|
||||
const config = this.openConfigFile(configFile)
|
||||
const env = dotenv.config().parsed || {}
|
||||
params = dotenv.parse(params.join('\n'))
|
||||
|
||||
const envParams = { _: Object.assign(env, params) }
|
||||
|
||||
const configFileDir = path.dirname(
|
||||
path.resolve(process.cwd(), configFile)
|
||||
)
|
||||
|
||||
process.chdir(configFileDir)
|
||||
|
||||
return new Beau(config, envParams)
|
||||
}
|
||||
}
|
||||
|
||||
Base.flags = {
|
||||
config: flags.string({
|
||||
char: 'c',
|
||||
description: 'The configuration file to be used.',
|
||||
default: 'beau.yml'
|
||||
}),
|
||||
verbose: flags.boolean({
|
||||
char: 'V',
|
||||
description: `Show all additional information available for a command.`
|
||||
}),
|
||||
'no-format': flags.boolean({
|
||||
description: `Disables color formatting for usage on external tools.`
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = Base
|
||||
|
|
@ -1,23 +1,26 @@
|
|||
const clc = require('cli-color')
|
||||
const { Line } = require('clui')
|
||||
const { expandPath } = require('../../../src/shared')
|
||||
const Base = require('../base')
|
||||
const clc = require('cli-color');
|
||||
const { Line } = require('clui');
|
||||
const { flags, Command } = require('@oclif/command');
|
||||
const { baseFlags, loadConfig } = require('../utils');
|
||||
|
||||
class ListCommand extends Base {
|
||||
class ListCommand extends Command {
|
||||
async run() {
|
||||
const { flags } = this.parse(ListCommand)
|
||||
const Beau = this.loadConfig(flags.config)
|
||||
const { flags } = this.parse(ListCommand);
|
||||
const Beau = loadConfig(flags.config);
|
||||
|
||||
if (flags['no-format']) {
|
||||
return Beau.requests.list.forEach(
|
||||
({ VERB, ALIAS, ENDPOINT, PATH }) =>
|
||||
this.log(
|
||||
`${VERB}\t${ALIAS}\t${ENDPOINT.replace(
|
||||
/\/$/,
|
||||
''
|
||||
)}/${PATH.replace(/^\//, '')}`
|
||||
)
|
||||
VERB +
|
||||
`\t` +
|
||||
ALIAS +
|
||||
`\t` +
|
||||
ENDPOINT.replace(/\/$/, '') +
|
||||
`/` +
|
||||
PATH.replace(/^\//, '')
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
new Line()
|
||||
|
|
@ -25,22 +28,24 @@ class ListCommand extends Base {
|
|||
.column('HTTP Verb', 20, [clc.cyan])
|
||||
.column('Alias', 30, [clc.cyan])
|
||||
.column('Endpoint', 20, [clc.cyan])
|
||||
.output()
|
||||
.output();
|
||||
|
||||
Beau.requests.list.forEach(({ VERB, ALIAS, ENDPOINT, PATH }) =>
|
||||
new Line()
|
||||
.padding(2)
|
||||
.column(VERB, 20, [clc.yellow])
|
||||
.column(ALIAS, 30, [clc.yellow])
|
||||
.column(expandPath(ENDPOINT, PATH))
|
||||
.output()
|
||||
.column(
|
||||
ENDPOINT.replace(/\/$/, '') + '/' + PATH.replace(/^\//, '')
|
||||
)
|
||||
.output()
|
||||
);
|
||||
|
||||
new Line().output()
|
||||
new Line().output();
|
||||
}
|
||||
}
|
||||
|
||||
ListCommand.description = `Lists all available requests in the config file.`
|
||||
ListCommand.flags = { ...Base.flags }
|
||||
ListCommand.description = `Lists all available requests in the config file.`;
|
||||
ListCommand.flags = { ...baseFlags };
|
||||
|
||||
module.exports = ListCommand
|
||||
module.exports = ListCommand;
|
||||
|
|
|
|||
|
|
@ -1,41 +1,34 @@
|
|||
const Base = require('../base')
|
||||
const cj = require('color-json')
|
||||
const clc = require('cli-color')
|
||||
const prompts = require('prompts')
|
||||
const { Line, Spinner } = require('clui')
|
||||
const { flags } = require('@oclif/command')
|
||||
const { expandPath } = require('../../../src/shared')
|
||||
const clc = require('cli-color');
|
||||
const jsome = require('jsome');
|
||||
const { Line, Spinner } = require('clui');
|
||||
const { flags, Command } = require('@oclif/command');
|
||||
const { baseFlags, loadConfig } = require('../utils');
|
||||
|
||||
class RequestCommand extends Base {
|
||||
class RequestCommand extends Command {
|
||||
prettyOutput(res, verbose = false) {
|
||||
let { status, body } = res.response
|
||||
let { status, body } = res.response;
|
||||
|
||||
this.spinner.stop()
|
||||
this.spinner.stop();
|
||||
|
||||
status = status.toString().startsWith(2)
|
||||
? clc.green(status)
|
||||
: clc.red(status)
|
||||
: clc.red(status);
|
||||
|
||||
new Line()
|
||||
.padding(2)
|
||||
.column('Status', 20, [clc.cyan])
|
||||
.column('Endpoint', 20, [clc.cyan])
|
||||
.output()
|
||||
.output();
|
||||
|
||||
new Line()
|
||||
.padding(2)
|
||||
.column(status, 20)
|
||||
.column(res.request.endpoint)
|
||||
.output()
|
||||
.output();
|
||||
|
||||
new Line().output()
|
||||
new Line().output();
|
||||
|
||||
const result = (verbose ? res : body) || null
|
||||
if (typeof result === 'object') {
|
||||
this.log(cj(result))
|
||||
} else if (typeof result === 'string') {
|
||||
this.log(result)
|
||||
}
|
||||
this.log(jsome.getColoredString((verbose ? res : body) || null));
|
||||
}
|
||||
|
||||
async run() {
|
||||
|
|
@ -46,84 +39,59 @@ class RequestCommand extends Base {
|
|||
'no-format': noFormat = false,
|
||||
verbose = false,
|
||||
'as-json': asJson = false,
|
||||
quiet = false,
|
||||
interactive = false
|
||||
quiet = false
|
||||
},
|
||||
args
|
||||
} = this.parse(RequestCommand)
|
||||
} = this.parse(RequestCommand);
|
||||
|
||||
const Beau = this.loadConfig(config, params)
|
||||
const Beau = loadConfig(config, params);
|
||||
|
||||
const spinnerSprite = ['⣾', '⣽', '⣻', '⢿', '⡿', '⣟', '⣯', '⣷']
|
||||
this.spinner = new Spinner('', spinnerSprite)
|
||||
const spinnerSprite = ['⣾', '⣽', '⣻', '⢿', '⡿', '⣟', '⣯', '⣷'];
|
||||
this.spinner = new Spinner('', spinnerSprite);
|
||||
|
||||
let spinnerEnabled = !noFormat && !asJson && !quiet
|
||||
|
||||
if (typeof args.alias == 'undefined' && !interactive) {
|
||||
this.error(
|
||||
'Missing 1 required argument: The alias of the request to execute.'
|
||||
)
|
||||
}
|
||||
|
||||
if (interactive) {
|
||||
const requests = Beau.requests.list.map(
|
||||
({ VERB, ALIAS, ENDPOINT, PATH }) => ({
|
||||
title: `${VERB} ${PATH} - ${ALIAS}`,
|
||||
value: ALIAS,
|
||||
description: expandPath(ENDPOINT, PATH)
|
||||
})
|
||||
)
|
||||
|
||||
const { name } = await prompts({
|
||||
name: 'name',
|
||||
message: 'Pick a Request to execute',
|
||||
type: 'select',
|
||||
choices: requests
|
||||
})
|
||||
|
||||
args.alias = name
|
||||
}
|
||||
let spinnerEnabled = !noFormat && !asJson && !quiet;
|
||||
|
||||
if (spinnerEnabled) {
|
||||
this.spinner.start()
|
||||
this.spinner.start();
|
||||
}
|
||||
|
||||
let res
|
||||
let res;
|
||||
|
||||
try {
|
||||
res = await Beau.requests.execByAlias(args.alias)
|
||||
res = await Beau.requests.execByAlias(args.alias);
|
||||
} catch (err) {
|
||||
this.spinner.stop()
|
||||
this.spinner.stop();
|
||||
|
||||
if (!quiet) {
|
||||
this.error(err.message)
|
||||
this.error(err.message);
|
||||
}
|
||||
|
||||
this.exit(1)
|
||||
this.exit(1);
|
||||
}
|
||||
|
||||
if (quiet) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
if (asJson) {
|
||||
return this.log(JSON.stringify(verbose ? res : res.response))
|
||||
return this.log(JSON.stringify(verbose ? res : res.response));
|
||||
}
|
||||
|
||||
if (noFormat) {
|
||||
this.log(res.response.status)
|
||||
this.log(res.request.endpoint)
|
||||
this.log(JSON.stringify(res.response.headers))
|
||||
this.log(JSON.stringify(res.response.body))
|
||||
return
|
||||
this.log(res.response.status);
|
||||
this.log(res.request.endpoint);
|
||||
this.log(JSON.stringify(res.response.headers));
|
||||
this.log(JSON.stringify(res.response.body));
|
||||
return;
|
||||
}
|
||||
|
||||
this.prettyOutput(res, verbose)
|
||||
this.prettyOutput(res, verbose);
|
||||
}
|
||||
}
|
||||
|
||||
RequestCommand.description = `Executes a request by name.`
|
||||
RequestCommand.description = `Executes a request by name.`;
|
||||
RequestCommand.flags = {
|
||||
...Base.flags,
|
||||
...baseFlags,
|
||||
param: flags.string({
|
||||
char: 'P',
|
||||
multiple: true,
|
||||
|
|
@ -138,21 +106,15 @@ RequestCommand.flags = {
|
|||
'as-json': flags.boolean({
|
||||
char: 'j',
|
||||
description: `Outputs the response as json.`
|
||||
}),
|
||||
|
||||
interactive: flags.boolean({
|
||||
char: 'i',
|
||||
description: 'Choose request interactively.',
|
||||
default: false
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
RequestCommand.args = [
|
||||
{
|
||||
name: 'alias',
|
||||
required: false,
|
||||
required: true,
|
||||
description: `The alias of the request to execute.`
|
||||
}
|
||||
]
|
||||
];
|
||||
|
||||
module.exports = RequestCommand
|
||||
module.exports = RequestCommand;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
const { flags, Command } = require('@oclif/command');
|
||||
const { baseFlags, openConfigFile } = require('../utils');
|
||||
const { validate } = require('../../../src/schema.js');
|
||||
|
||||
class ValidateCommand extends Command {
|
||||
async run() {
|
||||
const { flags, args } = this.parse(ValidateCommand);
|
||||
const configFile = args.alias || flags.config;
|
||||
|
||||
const config = openConfigFile(configFile);
|
||||
|
||||
let result = await validate(config);
|
||||
if (result.valid) {
|
||||
this.log(`${configFile} is valid.`);
|
||||
} else {
|
||||
this.error(result.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ValidateCommand.description = `Validates the given configuration file against Beau's configuration schema.`;
|
||||
ValidateCommand.flags = { ...baseFlags };
|
||||
ValidateCommand.args = [
|
||||
{
|
||||
name: 'alias',
|
||||
required: false,
|
||||
description: `The configuration file to validate.`
|
||||
}
|
||||
];
|
||||
module.exports = ValidateCommand;
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
const yaml = require('js-yaml');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const dotenv = require('dotenv');
|
||||
const Beau = require('../../src/beau');
|
||||
const { flags } = require('@oclif/command');
|
||||
|
||||
const openConfigFile = configFile => {
|
||||
if (!fs.existsSync(configFile)) {
|
||||
throw new Error(`The config file, ${configFile} was not found.`);
|
||||
}
|
||||
|
||||
let config;
|
||||
yaml.safeLoadAll(fs.readFileSync(configFile, 'utf-8'), doc => {
|
||||
if (typeof config === 'undefined') {
|
||||
config = doc;
|
||||
} else {
|
||||
if (typeof config.hosts === 'undefined') {
|
||||
config.hosts = [];
|
||||
}
|
||||
|
||||
config.hosts.push(doc);
|
||||
}
|
||||
});
|
||||
|
||||
return config;
|
||||
};
|
||||
|
||||
const loadConfig = (configFile, params = []) => {
|
||||
const config = openConfigFile(configFile);
|
||||
const env = dotenv.config().parsed || {};
|
||||
params = dotenv.parse(params.join('\n'));
|
||||
|
||||
const envParams = { _: Object.assign(env, params) };
|
||||
|
||||
const configFileDir = path.dirname(path.resolve(process.cwd(), configFile));
|
||||
|
||||
process.chdir(configFileDir);
|
||||
|
||||
return new Beau(config, envParams);
|
||||
};
|
||||
|
||||
const baseFlags = {
|
||||
config: flags.string({
|
||||
char: 'c',
|
||||
description: 'The configuration file to be used.',
|
||||
default: 'beau.yml'
|
||||
}),
|
||||
verbose: flags.boolean({
|
||||
char: 'V',
|
||||
description: `Show all additional information available for a command.`
|
||||
}),
|
||||
'no-format': flags.boolean({
|
||||
description: `Disables color formatting for usage on external tools.`
|
||||
})
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
openConfigFile,
|
||||
loadConfig,
|
||||
baseFlags
|
||||
};
|
||||
|
|
@ -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
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
endpoint: https://pokeapi.co/api/v2/
|
||||
|
||||
# Try replacing this pokemon using params:
|
||||
# beau request get-pokemon -P "pokemon=dragapult"
|
||||
environment:
|
||||
_:
|
||||
pokemon: ditto
|
||||
|
||||
cookiejar: true
|
||||
|
||||
GET /pokemon/$env._.pokemon: get-pokemon
|
||||
GET $get-pokemon.body.location_area_encounters: get-encounters
|
||||
|
||||
POST https://httpbin.org/anything:
|
||||
- alias: post-first-area
|
||||
payload:
|
||||
area: $get-encounters.body.0.location_area.name
|
||||
|
||||
- alias: post-pokemon-type
|
||||
payload:
|
||||
type: $get-pokemon.body.types.0.type.name
|
||||
|
||||
- alias: form-submission
|
||||
form:
|
||||
name: Dragapult
|
||||
|
||||
- alias: file-upload
|
||||
formdata:
|
||||
name: Beau
|
||||
logo: $[createReadStream('../media/beau.png')]
|
||||
|
||||
GET https://httpbin.org/status/418: teapot
|
||||
|
||||
GET https://httpbin.org/cookies/set:
|
||||
alias: set-cookies
|
||||
params:
|
||||
hello: World
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
VERSION: 1
|
||||
ENDPOINT: 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,22 @@
|
|||
version: 1
|
||||
endpoint: http://httpbin.org
|
||||
|
||||
environment:
|
||||
the:
|
||||
post: 2
|
||||
|
||||
defaults:
|
||||
headers:
|
||||
hello: 'Hello2'
|
||||
|
||||
POST /anything:
|
||||
alias: anything
|
||||
payload:
|
||||
title: $jpa:get-post.body.title
|
||||
|
||||
---
|
||||
host: jpa
|
||||
endpoint: http://jsonplaceholder.typicode.com
|
||||
|
||||
GET /posts/$env.the.post: get-post
|
||||
GET /users/$jpa:get-post.body.userId: hello
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
version: 1
|
||||
endpoint: https://httpbin.org/
|
||||
|
||||
cookiejar: true
|
||||
environment:
|
||||
params:
|
||||
name: David
|
||||
|
||||
GET /anything:
|
||||
alias: anything
|
||||
payload:
|
||||
name: $env.params.name
|
||||
|
||||
GET /cookies/set:
|
||||
alias: set-cookies
|
||||
params:
|
||||
hello: World
|
||||
|
||||
GET /status/418:
|
||||
alias: teapot
|
||||
|
||||
POST /post:
|
||||
alias: post
|
||||
formdata:
|
||||
id: $[uuid()]
|
||||
file: $[createReadStream('./github.yml')]
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
version: 1
|
||||
endpoint: 'http://jsonplaceholder.typicode.com'
|
||||
|
||||
environment:
|
||||
the:
|
||||
post: 1
|
||||
|
||||
defaults:
|
||||
headers:
|
||||
hello: $posts.body.0.userId
|
||||
|
||||
GET /posts/$env.the.post: get-post
|
||||
|
||||
GET /posts/:
|
||||
alias: posts
|
||||
headers:
|
||||
hello: false
|
||||
|
||||
POST /posts/:
|
||||
alias: new-post
|
||||
documentation:
|
||||
title: New Post
|
||||
|
||||
GET /users/$posts.body.0.userId:
|
||||
alias: post-user
|
||||
documentation:
|
||||
description: Fetches the user for a give post.
|
||||
params:
|
||||
hello: 'world'
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
endpoint: http://localhost:10080
|
||||
|
||||
plugins:
|
||||
- jwt:
|
||||
data:
|
||||
userId: 12
|
||||
name: Sergio
|
||||
secret: 'asdfasdf+asdfasdf/asdfasdfasdfasdf=='
|
||||
|
||||
GET /test:
|
||||
alias: test
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
VERSION: '1'
|
||||
ENDPOINT: https://slack.com/api
|
||||
|
||||
auth: &auth
|
||||
token: xoxp-139455775026-139455775090-147751461120-f224ed6ffee029869a0f138d0859e7d6
|
||||
|
||||
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
|
||||
BIN
media/beau.png
BIN
media/beau.png
Binary file not shown.
|
Before Width: | Height: | Size: 13 KiB |
BIN
media/usage.gif
BIN
media/usage.gif
Binary file not shown.
|
Before Width: | Height: | Size: 616 KiB |
File diff suppressed because it is too large
Load Diff
52
package.json
52
package.json
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "beau",
|
||||
"version": "0.11.3",
|
||||
"version": "0.9.5",
|
||||
"description": "Testing APIs made easy.",
|
||||
"main": "./src/beau.js",
|
||||
"author": "David Díaz <seich@martianwabbit.com>",
|
||||
"license": "MIT + Commons Clause",
|
||||
"author": "Sergio Diaz <seich@martianwabbit.com>",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"test": "jest -i",
|
||||
"test": "jest",
|
||||
"test:coverage": "jest --coverage ./src",
|
||||
"release": "np"
|
||||
},
|
||||
|
|
@ -15,32 +15,30 @@
|
|||
"/bin"
|
||||
],
|
||||
"dependencies": {
|
||||
"@oclif/command": "1.8.0",
|
||||
"@oclif/config": "1.17.0",
|
||||
"@oclif/plugin-help": "3.2.1",
|
||||
"@oclif/plugin-warn-if-update-available": "1.7.0",
|
||||
"ajv": "7.0.3",
|
||||
"beau-std": "0.9.4",
|
||||
"better-ajv-errors": "0.7.0",
|
||||
"cli-color": "2.0.0",
|
||||
"clui": "0.3.6",
|
||||
"color-json": "2.0.1",
|
||||
"deepmerge": "4.2.2",
|
||||
"dotenv": "8.2.0",
|
||||
"globby": "11.0.2",
|
||||
"is-plain-object": "5.0.0",
|
||||
"js-yaml": "4.0.0",
|
||||
"prompts": "2.4.0",
|
||||
"request": "2.88.2",
|
||||
"request-promise-native": "1.0.9",
|
||||
"requireg": "0.2.2"
|
||||
"@oclif/command": "^1.4.34",
|
||||
"@oclif/config": "^1.6.33",
|
||||
"@oclif/plugin-help": "^2.0.5",
|
||||
"@oclif/plugin-warn-if-update-available": "^1.3.9",
|
||||
"beau-std": "^0.9.4",
|
||||
"cli-color": "^1.1.0",
|
||||
"clui": "^0.3.1",
|
||||
"deepmerge": "^2.1.1",
|
||||
"dotenv": "^6.0.0",
|
||||
"globby": "^8.0.1",
|
||||
"is-plain-object": "^2.0.4",
|
||||
"joi": "^13.4.0",
|
||||
"js-yaml": "^3.12.0",
|
||||
"jsome": "^2.5.0",
|
||||
"request": "^2.87.0",
|
||||
"request-promise-native": "^1.0.5",
|
||||
"requireg": "^0.1.8"
|
||||
},
|
||||
"repository": "git@github.com:Seich/Beau.git",
|
||||
"devDependencies": {
|
||||
"jest": "26.6.3",
|
||||
"jest-watch-typeahead": "0.6.1",
|
||||
"strip-ansi": "6.0.0",
|
||||
"np": "7.2.0"
|
||||
"jest": "^23.4.1",
|
||||
"jest-watch-typeahead": "^0.2.0",
|
||||
"np": "^3.0.4",
|
||||
"strip-ansi": "^4.0.0"
|
||||
},
|
||||
"oclif": {
|
||||
"commands": "./bin/cli/commands",
|
||||
|
|
|
|||
122
schema.json
122
schema.json
|
|
@ -1,122 +0,0 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"$id": "https://beaujs.com/schema.json",
|
||||
"title": "Beaujs Requests Schema",
|
||||
"type": "object",
|
||||
"definitions": {
|
||||
"request": {
|
||||
"oneOf": [
|
||||
{ "type": "string" },
|
||||
{
|
||||
"type": "object",
|
||||
"$ref": "#/definitions/requestObject",
|
||||
"required": ["alias"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"requestObject": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"alias": {
|
||||
"type": "string",
|
||||
"description": "The name of this request."
|
||||
},
|
||||
"headers": {
|
||||
"type": "object",
|
||||
"additionalProperties": true,
|
||||
"description": "Headers that are part of this request."
|
||||
},
|
||||
"params": {
|
||||
"type": "object",
|
||||
"additionalProperties": true,
|
||||
"description": "Query String parameters to add to this request."
|
||||
},
|
||||
"payload": {
|
||||
"description": "This request's body. It is converted to json automatically if given an object. It's sent as a string otherwise.",
|
||||
"oneOf": [
|
||||
{ "type": "string" },
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"form": {
|
||||
"type": "object",
|
||||
"additionalProperties": true,
|
||||
"description": "This request's body. Sets the content-type to application/x-www-form-urlencoded."
|
||||
},
|
||||
"formdata": {
|
||||
"type": "object",
|
||||
"additionalProperties": true,
|
||||
"description": "This request's body. Sets the content-type to multipart/form-data."
|
||||
}
|
||||
},
|
||||
"allOf": [
|
||||
{ "not": { "required": ["payload", "form"] } },
|
||||
{ "not": { "required": ["payload", "formdata"] } },
|
||||
{ "not": { "required": ["formdata", "form"] } }
|
||||
]
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"version": {
|
||||
"type": "number",
|
||||
"description": "The beau version this document was created for.",
|
||||
"enum": [1]
|
||||
},
|
||||
"endpoint": {
|
||||
"type": "string",
|
||||
"description": "The root endpoint for this host."
|
||||
},
|
||||
"cookiejar": {
|
||||
"type": "boolean",
|
||||
"description": "Enable cookie support for requests?"
|
||||
},
|
||||
"host": {
|
||||
"type": "string",
|
||||
"description": "The name of the current host. It allows referencing requests between different hosts."
|
||||
},
|
||||
"defaults": {
|
||||
"description": "Default values to be added to all requests.",
|
||||
"$ref": "#/definitions/requestObject"
|
||||
},
|
||||
"plugins": {
|
||||
"description": "Plugins to be enabled for this document.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{ "type": "string" },
|
||||
{ "type": "object", "additionalProperties": true }
|
||||
]
|
||||
}
|
||||
},
|
||||
"environment": {
|
||||
"type": "object",
|
||||
"description": "Global document variables for easy access.",
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"_": {
|
||||
"type": "object",
|
||||
"description": "Environment variables brought in by cli params or dotenv.",
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"patternProperties": {
|
||||
"(GET|HEAD|POST|PUT|DELETE|CONNECT|OPTIONS|TRACE|PATCH)\\s.*": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/definitions/request"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/request"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
class DynamicValues {
|
||||
constructor(registry, settings = {}) {
|
||||
registry.defineDynamicValue('add', this.add)
|
||||
registry.defineDynamicValue('add', this.add);
|
||||
}
|
||||
|
||||
add(x, y) {
|
||||
return x + y
|
||||
return x + y;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DynamicValues
|
||||
module.exports = DynamicValues;
|
||||
|
|
|
|||
|
|
@ -1,20 +1,20 @@
|
|||
class Modifiers {
|
||||
constructor(registry, settings = {}) {
|
||||
registry.addPreRequestModifier(this.preRequest)
|
||||
registry.addPostRequestModifier(this.postRequest)
|
||||
registry.addPreRequestModifier(this.preRequest);
|
||||
registry.addPostRequestModifier(this.postRequest);
|
||||
}
|
||||
|
||||
preRequest(request, orig) {
|
||||
request.headers = request.headers || {}
|
||||
request.headers.preRequestModifier = true
|
||||
return request
|
||||
request.headers = request.headers || {};
|
||||
request.headers.preRequestModifier = true;
|
||||
return request;
|
||||
}
|
||||
|
||||
postRequest(response, orig) {
|
||||
response.body = 'Hello World'
|
||||
response.response.body = 'Hello World'
|
||||
return response
|
||||
response.body = 'Hello World';
|
||||
response.response.body = 'Hello World';
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Modifiers
|
||||
module.exports = Modifiers;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
class BeauStd {
|
||||
constructor(registry, settings) {
|
||||
registry.defineDynamicValue('createReadStream', () => {})
|
||||
registry.defineDynamicValue('createReadStream', () => {});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BeauStd
|
||||
module.exports = BeauStd;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
function Request(request) {
|
||||
if (Request.fail) {
|
||||
throw new Error()
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
@ -14,9 +14,9 @@ function Request(request) {
|
|||
statusCode: 200,
|
||||
headers: [],
|
||||
body: '{"hello": "world"}'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Request.fail = false
|
||||
Request.fail = false;
|
||||
|
||||
module.exports = Request
|
||||
module.exports = Request;
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
function requireg(name) {
|
||||
return require(name)
|
||||
return require(name);
|
||||
}
|
||||
|
||||
requireg.resolving = true
|
||||
requireg.resolving = true;
|
||||
|
||||
requireg.resolve = function (name) {
|
||||
requireg.resolve = function(name) {
|
||||
if (requireg.resolving) {
|
||||
return ''
|
||||
return '';
|
||||
} else {
|
||||
return undefined
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = requireg
|
||||
module.exports = requireg;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
module.exports = {
|
||||
...jest.requireActual('../shared'),
|
||||
...require.requireActual('../shared'),
|
||||
moduleVersion: jest.fn().mockReturnValue(1)
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -36,6 +36,18 @@ Beau {
|
|||
],
|
||||
},
|
||||
"requests": RequestList {
|
||||
"PLUGINS": Plugins {
|
||||
"autoload": Array [
|
||||
"std",
|
||||
],
|
||||
"context": Object {},
|
||||
"registry": Object {
|
||||
"dynamicValues": Array [],
|
||||
"postRequestModifiers": Array [],
|
||||
"preRequestModifiers": Array [],
|
||||
},
|
||||
},
|
||||
"REQUESTS": Array [],
|
||||
"cache": RequestCache {
|
||||
"$cache": Object {
|
||||
"env": Object {},
|
||||
|
|
@ -48,6 +60,34 @@ Beau {
|
|||
|
||||
exports[`Beau's config Loader. should load the request list using the configuration 1`] = `
|
||||
RequestList {
|
||||
"PLUGINS": Plugins {
|
||||
"autoload": Array [
|
||||
"std",
|
||||
],
|
||||
"context": Object {},
|
||||
"registry": Object {
|
||||
"dynamicValues": Array [],
|
||||
"postRequestModifiers": Array [],
|
||||
"preRequestModifiers": Array [],
|
||||
},
|
||||
},
|
||||
"REQUESTS": Array [
|
||||
Object {
|
||||
"ALIAS": "get-post",
|
||||
"COOKIEJAR": false,
|
||||
"ENDPOINT": "http://example.com",
|
||||
"REQUEST": "GET /posts/1",
|
||||
},
|
||||
Object {
|
||||
"ALIAS": "user",
|
||||
"COOKIEJAR": false,
|
||||
"ENDPOINT": "http://example.com",
|
||||
"HEADERS": Object {
|
||||
"hello": "world",
|
||||
},
|
||||
"REQUEST": "GET /user",
|
||||
},
|
||||
],
|
||||
"cache": RequestCache {
|
||||
"$cache": Object {
|
||||
"env": Object {},
|
||||
|
|
|
|||
|
|
@ -1,33 +1,33 @@
|
|||
const yaml = require('js-yaml')
|
||||
const Beau = require('../beau')
|
||||
const { moduleVersion } = require('../shared')
|
||||
const yaml = require('js-yaml');
|
||||
const Beau = require('../beau');
|
||||
const { moduleVersion } = require('../shared');
|
||||
|
||||
jest.mock('../shared')
|
||||
jest.mock('../shared');
|
||||
|
||||
const requireg = require('requireg')
|
||||
requireg.resolving = false
|
||||
const requireg = require('requireg');
|
||||
requireg.resolving = false;
|
||||
|
||||
describe(`Beau's config Loader.`, () => {
|
||||
it('should load the config', () => {
|
||||
moduleVersion.mockReturnValue(1)
|
||||
moduleVersion.mockReturnValue(1);
|
||||
|
||||
const doc = yaml.load(`
|
||||
const doc = yaml.safeLoad(`
|
||||
version: 1
|
||||
endpoint: 'http://example.com'
|
||||
|
||||
defaults:
|
||||
headers:
|
||||
authentication: hello
|
||||
`)
|
||||
`);
|
||||
|
||||
const beau = new Beau(doc)
|
||||
expect(beau).toMatchSnapshot()
|
||||
})
|
||||
const beau = new Beau(doc);
|
||||
expect(beau).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it(`should load the request list using the configuration`, () => {
|
||||
moduleVersion.mockReturnValue(1)
|
||||
moduleVersion.mockReturnValue(1);
|
||||
|
||||
const doc = yaml.load(`
|
||||
const doc = yaml.safeLoad(`
|
||||
version: 1
|
||||
endpoint: 'http://example.com'
|
||||
|
||||
|
|
@ -36,24 +36,24 @@ describe(`Beau's config Loader.`, () => {
|
|||
alias: user
|
||||
headers:
|
||||
hello: world
|
||||
`)
|
||||
`);
|
||||
|
||||
const beau = new Beau(doc)
|
||||
expect(beau.requests).toMatchSnapshot()
|
||||
})
|
||||
const beau = new Beau(doc);
|
||||
expect(beau.requests).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should display a warning if the module version and the beau file version are different', () => {
|
||||
let stdout
|
||||
let stdout;
|
||||
let spy = jest
|
||||
.spyOn(console, 'warn')
|
||||
.mockImplementation((val) => (stdout = val))
|
||||
.mockImplementation(val => (stdout = val));
|
||||
|
||||
moduleVersion.mockReturnValue(2)
|
||||
moduleVersion.mockReturnValue(2);
|
||||
|
||||
const beau = new Beau({ version: 1 })
|
||||
expect(stdout).toEqual('This Beau file expects v1. You are using v2.')
|
||||
const beau = new Beau({ version: 1 });
|
||||
expect(stdout).toEqual('This Beau file expects v1. You are using v2.');
|
||||
|
||||
spy.mockReset()
|
||||
spy.mockRestore()
|
||||
})
|
||||
})
|
||||
spy.mockReset();
|
||||
spy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,25 +1,25 @@
|
|||
const yaml = require('js-yaml')
|
||||
const Config = require('../config')
|
||||
const yaml = require('js-yaml');
|
||||
const Config = require('../config');
|
||||
|
||||
const requireg = require('requireg')
|
||||
requireg.resolving = false
|
||||
const requireg = require('requireg');
|
||||
requireg.resolving = false;
|
||||
|
||||
describe('Config', () => {
|
||||
it('should load valid config keys', () => {
|
||||
const doc = yaml.load(`
|
||||
const doc = yaml.safeLoad(`
|
||||
version: 1
|
||||
endpoint: http://martianwabbit.com
|
||||
shouldntBeAdded: true
|
||||
`)
|
||||
`);
|
||||
|
||||
const config = new Config(doc)
|
||||
expect(config.ENDPOINT).toBe(doc.endpoint)
|
||||
expect(config.VERSION).toBe(doc.version)
|
||||
expect(config.shouldntBeAdded).toBeUndefined()
|
||||
})
|
||||
const config = new Config(doc);
|
||||
expect(config.ENDPOINT).toBe(doc.endpoint);
|
||||
expect(config.VERSION).toBe(doc.version);
|
||||
expect(config.shouldntBeAdded).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should load requests', () => {
|
||||
const doc = yaml.load(`
|
||||
const doc = yaml.safeLoad(`
|
||||
endpoint: http://example.com
|
||||
|
||||
GET /profile: get-profile
|
||||
|
|
@ -29,14 +29,14 @@ describe('Config', () => {
|
|||
alias: user
|
||||
headers:
|
||||
hello: world
|
||||
`)
|
||||
`);
|
||||
|
||||
const config = new Config(doc)
|
||||
expect(Object.keys(config.REQUESTS).length).toBe(4)
|
||||
})
|
||||
const config = new Config(doc);
|
||||
expect(Object.keys(config.REQUESTS).length).toBe(4);
|
||||
});
|
||||
|
||||
it('should set up defaults for all requests', () => {
|
||||
const doc = yaml.load(`
|
||||
const doc = yaml.safeLoad(`
|
||||
version: 1
|
||||
endpoint: 'http://example.com'
|
||||
|
||||
|
|
@ -49,18 +49,18 @@ describe('Config', () => {
|
|||
alias: user
|
||||
headers:
|
||||
hello: world
|
||||
`)
|
||||
`);
|
||||
|
||||
const config = new Config(doc)
|
||||
const config = new Config(doc);
|
||||
|
||||
expect(config).toMatchSnapshot()
|
||||
Object.values(config.REQUESTS).forEach((r) => {
|
||||
expect(r.HEADERS.authentication).toMatch('hello')
|
||||
})
|
||||
})
|
||||
expect(config).toMatchSnapshot();
|
||||
Object.values(config.REQUESTS).forEach(r => {
|
||||
expect(r.HEADERS.authentication).toMatch('hello');
|
||||
});
|
||||
});
|
||||
|
||||
it('should load multiple hosts', () => {
|
||||
const doc = yaml.load(`
|
||||
const doc = yaml.safeLoad(`
|
||||
version: 1
|
||||
endpoint: http://example.org
|
||||
|
||||
|
|
@ -97,15 +97,15 @@ describe('Config', () => {
|
|||
endpoint: http://example.info
|
||||
|
||||
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', () => {
|
||||
const doc = yaml.load(`
|
||||
const doc = yaml.safeLoad(`
|
||||
hosts:
|
||||
- host: test1
|
||||
endpoint: http://example.com
|
||||
|
|
@ -113,16 +113,16 @@ describe('Config', () => {
|
|||
- host: test2
|
||||
endpoint: http://example.net
|
||||
GET /posts: posts
|
||||
`)
|
||||
`);
|
||||
|
||||
let config = new Config(doc)
|
||||
let config = new Config(doc);
|
||||
|
||||
expect(config.REQUESTS[0].ALIAS).toBe('test1:posts')
|
||||
expect(config.REQUESTS[1].ALIAS).toBe('test2:posts')
|
||||
})
|
||||
expect(config.REQUESTS[0].ALIAS).toBe('test1:posts');
|
||||
expect(config.REQUESTS[1].ALIAS).toBe('test2:posts');
|
||||
});
|
||||
|
||||
it(`should throw if host doesn't have a host key`, () => {
|
||||
const doc = yaml.load(`
|
||||
const doc = yaml.safeLoad(`
|
||||
hosts:
|
||||
- endpoint: http://example.com
|
||||
GET /posts: posts
|
||||
|
|
@ -130,13 +130,13 @@ describe('Config', () => {
|
|||
- host: test2
|
||||
endpoint: http://example.net
|
||||
GET /posts: posts
|
||||
`)
|
||||
`);
|
||||
|
||||
expect(() => new Config(doc)).toThrow()
|
||||
})
|
||||
expect(() => new Config(doc)).toThrow();
|
||||
});
|
||||
|
||||
it(`should merge host settings with global settings`, () => {
|
||||
const doc = yaml.load(`
|
||||
const doc = yaml.safeLoad(`
|
||||
defaults:
|
||||
headers:
|
||||
hello: 1
|
||||
|
|
@ -152,25 +152,9 @@ describe('Config', () => {
|
|||
headers: false
|
||||
|
||||
GET /posts: posts
|
||||
`)
|
||||
`);
|
||||
|
||||
let config = new Config(doc)
|
||||
expect(config.REQUESTS[0].HEADERS.hello).toBe(1)
|
||||
})
|
||||
|
||||
it(`should allow different settings for the same request`, () => {
|
||||
const doc = yaml.load(`
|
||||
host: https://example.com
|
||||
GET /1:
|
||||
- alias: req1
|
||||
headers:
|
||||
request: 1
|
||||
- alias: req2
|
||||
headers:
|
||||
request: 2
|
||||
`)
|
||||
|
||||
let config = new Config(doc)
|
||||
expect(config.REQUESTS.length).toBe(2)
|
||||
})
|
||||
})
|
||||
let config = new Config(doc);
|
||||
expect(config.REQUESTS[0].HEADERS.hello).toBe(1);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,41 +1,41 @@
|
|||
const Plugins = require('../plugins')
|
||||
const Request = require('../request')
|
||||
const RequestCache = require('../requestCache')
|
||||
const requireg = require('requireg')
|
||||
const Plugins = require('../plugins');
|
||||
const Request = require('../request');
|
||||
const RequestCache = require('../requestCache');
|
||||
const requireg = require('requireg');
|
||||
|
||||
describe(`Beau's plugin system`, () => {
|
||||
let request
|
||||
let plugins
|
||||
let request;
|
||||
let plugins;
|
||||
|
||||
beforeEach(() => {
|
||||
plugins = new Plugins([{ Modifiers: [Object] }, 'DynamicValues'], [])
|
||||
})
|
||||
plugins = new Plugins([{ Modifiers: [Object] }, 'DynamicValues'], []);
|
||||
});
|
||||
|
||||
it('should load all plugins', () => {
|
||||
expect(plugins.registry.preRequestModifiers.length).toBe(1)
|
||||
expect(plugins.registry.postRequestModifiers.length).toBe(1)
|
||||
expect(plugins.registry.dynamicValues.length).toBe(1)
|
||||
})
|
||||
expect(plugins.registry.preRequestModifiers.length).toBe(1);
|
||||
expect(plugins.registry.postRequestModifiers.length).toBe(1);
|
||||
expect(plugins.registry.dynamicValues.length).toBe(1);
|
||||
});
|
||||
|
||||
it(`should throw if given an invalid configuration`, () => {
|
||||
expect(() => new Plugins([{ test1: true, test2: true }])).toThrow()
|
||||
})
|
||||
expect(() => new Plugins([{ test1: true, test2: true }])).toThrow();
|
||||
});
|
||||
|
||||
it(`shouldn't do anything when given an empty array.`, () => {
|
||||
expect(new Plugins([], [])).toMatchSnapshot()
|
||||
})
|
||||
expect(new Plugins([], [])).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it(`should warn if the plugin is not available.`, () => {
|
||||
const spy = jest.spyOn(console, 'warn').mockImplementation(() => {})
|
||||
requireg.resolving = false
|
||||
const spy = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
||||
requireg.resolving = false;
|
||||
|
||||
new Plugins(['not-a-Package'])
|
||||
expect(spy).toHaveBeenCalled()
|
||||
new Plugins(['not-a-Package']);
|
||||
expect(spy).toHaveBeenCalled();
|
||||
|
||||
requireg.resolving = true
|
||||
spy.mockReset()
|
||||
spy.mockRestore()
|
||||
})
|
||||
requireg.resolving = true;
|
||||
spy.mockReset();
|
||||
spy.mockRestore();
|
||||
});
|
||||
|
||||
describe(`Request Modifiers`, () => {
|
||||
beforeEach(() => {
|
||||
|
|
@ -46,13 +46,13 @@ describe(`Beau's plugin system`, () => {
|
|||
alias: 'update'
|
||||
},
|
||||
plugins
|
||||
)
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it(`should modify the request and response using modifiers.`, async () => {
|
||||
await expect(request.exec()).resolves.toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
await expect(request.exec()).resolves.toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe(`Dynamic Values`, () => {
|
||||
beforeEach(() => {
|
||||
|
|
@ -68,34 +68,34 @@ describe(`Beau's plugin system`, () => {
|
|||
payload: 'counted $[add(1, $value2)] so far.'
|
||||
},
|
||||
plugins
|
||||
)
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
let cache = new RequestCache()
|
||||
cache.add('value2', '2')
|
||||
let cache = new RequestCache();
|
||||
cache.add('value2', '2');
|
||||
|
||||
it(`should look for dynamic values executing and replacing them`, async () => {
|
||||
let req = await request.exec(cache)
|
||||
expect(req).toHaveProperty('request.body', 'counted 3 so far.')
|
||||
})
|
||||
let req = await request.exec(cache);
|
||||
expect(req).toHaveProperty('request.body', 'counted 3 so far.');
|
||||
});
|
||||
|
||||
it(`should change the internal datatype if the only thing in the value is the dynamic value`, async () => {
|
||||
let req = await request.exec(cache)
|
||||
expect(req).toHaveProperty('request.headers.count', 3)
|
||||
})
|
||||
let req = await request.exec(cache);
|
||||
expect(req).toHaveProperty('request.headers.count', 3);
|
||||
});
|
||||
|
||||
it(`should return empty values as empty`, async () => {
|
||||
let req = await request.exec(cache)
|
||||
expect(req).toHaveProperty('request.headers.empty', '')
|
||||
})
|
||||
let req = await request.exec(cache);
|
||||
expect(req).toHaveProperty('request.headers.empty', '');
|
||||
});
|
||||
|
||||
it(`should throw when calling an undefined dynamic value`, async () => {
|
||||
request = new Request({
|
||||
request: 'POST /hello/$[notAvailable(1, 2)]',
|
||||
alias: 'say-hello'
|
||||
})
|
||||
});
|
||||
|
||||
await expect(request.exec()).rejects.toThrow()
|
||||
})
|
||||
})
|
||||
})
|
||||
await expect(request.exec()).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
const Request = require('../request')
|
||||
const RequestCache = require('../requestCache')
|
||||
const requestPromiseNativeMock = require('request-promise-native')
|
||||
const Request = require('../request');
|
||||
const RequestCache = require('../requestCache');
|
||||
const requestPromiseNativeMock = require('request-promise-native');
|
||||
|
||||
describe('Request', () => {
|
||||
let cache
|
||||
let validRequestConfig
|
||||
let invalidRequestConfig
|
||||
let request
|
||||
let requestWithoutDependencies
|
||||
let cache;
|
||||
let validRequestConfig;
|
||||
let invalidRequestConfig;
|
||||
let request;
|
||||
let requestWithoutDependencies;
|
||||
|
||||
beforeEach(() => {
|
||||
validRequestConfig = {
|
||||
|
|
@ -23,78 +23,54 @@ describe('Request', () => {
|
|||
payload: {
|
||||
username: 'seich'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
invalidRequestConfig = {
|
||||
request: `POST /session`,
|
||||
endpoint: 'http://example.com'
|
||||
}
|
||||
};
|
||||
|
||||
cache = new RequestCache()
|
||||
cache.add('session', { token: 'abc123' })
|
||||
cache.add('profile', { UserId: 14 })
|
||||
cache = new RequestCache();
|
||||
cache.add('session', { token: 'abc123' });
|
||||
cache.add('profile', { UserId: 14 });
|
||||
|
||||
request = new Request(validRequestConfig)
|
||||
request = new Request(validRequestConfig);
|
||||
requestWithoutDependencies = new Request({
|
||||
endpoint: 'http://example.com',
|
||||
request: 'GET /user',
|
||||
alias: 'show'
|
||||
})
|
||||
});
|
||||
|
||||
requestPromiseNativeMock.fail = false
|
||||
})
|
||||
requestPromiseNativeMock.fail = false;
|
||||
});
|
||||
|
||||
it('should load up the given request', () => {
|
||||
expect(request.VERB).toBe('POST')
|
||||
expect(request.ENDPOINT).toBe(validRequestConfig.endpoint)
|
||||
expect(request.HEADERS).toBeDefined()
|
||||
expect(request.PAYLOAD).toBeDefined()
|
||||
expect(request.PARAMS).toBeDefined()
|
||||
})
|
||||
expect(request.VERB).toBe('POST');
|
||||
expect(request.ENDPOINT).toBe(validRequestConfig.endpoint);
|
||||
expect(request.HEADERS).toBeDefined();
|
||||
expect(request.PAYLOAD).toBeDefined();
|
||||
expect(request.PARAMS).toBeDefined();
|
||||
});
|
||||
|
||||
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', () => {
|
||||
expect(request.DEPENDENCIES.size).toBe(2)
|
||||
expect(request.DEPENDENCIES).toContain('session')
|
||||
expect(request.DEPENDENCIES).toContain('profile')
|
||||
})
|
||||
expect(request.DEPENDENCIES.size).toBe(2);
|
||||
expect(request.DEPENDENCIES).toContain('session');
|
||||
expect(request.DEPENDENCIES).toContain('profile');
|
||||
});
|
||||
|
||||
it('should execute a request', async () => {
|
||||
await expect(request.exec(cache)).resolves.toMatchSnapshot()
|
||||
await expect(request.exec(cache)).resolves.toMatchSnapshot();
|
||||
await expect(
|
||||
requestWithoutDependencies.exec()
|
||||
).resolves.toMatchSnapshot()
|
||||
})
|
||||
).resolves.toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should throw if the request fails', async () => {
|
||||
requestPromiseNativeMock.fail = true
|
||||
await expect(requestWithoutDependencies.exec()).rejects.toThrow(Error)
|
||||
})
|
||||
|
||||
it(`should use the full url if given one as part of the path instead of the global endpoint`, async () => {
|
||||
const requestWithPath = new Request({
|
||||
endpoint: 'http://example.com',
|
||||
request: 'GET http://martianwabbit.com/user',
|
||||
alias: 'get-user'
|
||||
})
|
||||
|
||||
const requestWithoutPath = new Request({
|
||||
endpoint: 'http://example.com',
|
||||
request: 'GET /user',
|
||||
alias: 'get-user'
|
||||
})
|
||||
|
||||
await expect(requestWithPath.exec()).resolves.toHaveProperty(
|
||||
'request.endpoint',
|
||||
'http://martianwabbit.com/user'
|
||||
)
|
||||
|
||||
await expect(requestWithoutPath.exec()).resolves.toHaveProperty(
|
||||
'request.endpoint',
|
||||
'http://example.com/user'
|
||||
)
|
||||
})
|
||||
})
|
||||
requestPromiseNativeMock.fail = true;
|
||||
await expect(requestWithoutDependencies.exec()).rejects.toThrow(Error);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
const RequestCache = require('../requestCache')
|
||||
const RequestCache = require('../requestCache');
|
||||
|
||||
describe('Request Cache', () => {
|
||||
let cache
|
||||
let cache;
|
||||
|
||||
beforeEach(() => {
|
||||
cache = new RequestCache()
|
||||
cache = new RequestCache();
|
||||
|
||||
cache.add('session', {
|
||||
hello: 'World'
|
||||
})
|
||||
});
|
||||
|
||||
cache.add('array', [
|
||||
{
|
||||
|
|
@ -19,36 +19,36 @@ describe('Request Cache', () => {
|
|||
id: 2,
|
||||
name: 'Angela'
|
||||
}
|
||||
])
|
||||
})
|
||||
]);
|
||||
});
|
||||
|
||||
it('should add keys to the cache', () => {
|
||||
expect(cache.$cache.session.hello).toBe('World')
|
||||
})
|
||||
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')
|
||||
})
|
||||
expect(cache.get('session.hello')).toBe('World');
|
||||
});
|
||||
|
||||
it('should throw when given an invalid path', () => {
|
||||
expect(() => cache.get('$session.world')).toThrow()
|
||||
})
|
||||
})
|
||||
expect(() => cache.get('$session.world')).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('parse', () => {
|
||||
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', () => {
|
||||
let parsed = cache.parse({
|
||||
hello: 'hello $session.hello',
|
||||
earth: '$session.hello'
|
||||
})
|
||||
expect(parsed.hello).toBe('hello World')
|
||||
expect(parsed.earth).toBe('World')
|
||||
})
|
||||
});
|
||||
expect(parsed.hello).toBe('hello World');
|
||||
expect(parsed.earth).toBe('World');
|
||||
});
|
||||
|
||||
it('should return every non-string value as-is', () => {
|
||||
let parsed = cache.parse({
|
||||
|
|
@ -56,34 +56,34 @@ describe('Request Cache', () => {
|
|||
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')
|
||||
})
|
||||
});
|
||||
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')
|
||||
})
|
||||
let parsed = cache.parse({ hello: '$array.0.name' });
|
||||
expect(parsed.hello).toBe('Sergio');
|
||||
});
|
||||
|
||||
it('should return an object when given an undefined value', () => {
|
||||
expect(cache.parse(undefined)).toEqual({})
|
||||
})
|
||||
expect(cache.parse(undefined)).toEqual({});
|
||||
});
|
||||
|
||||
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', () => {
|
||||
expect(cache.parse(null)).toBe(null)
|
||||
})
|
||||
expect(cache.parse(null)).toBe(null);
|
||||
});
|
||||
|
||||
it(`shouldn't replace escaped variables`, () => {
|
||||
expect(cache.parse(`\\$session.hello is $session.hello`)).toBe(
|
||||
`$session.hello is World`
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
const Config = require('../config')
|
||||
const RequestList = require('../requestList')
|
||||
const requestPromiseNativeMock = require('request-promise-native')
|
||||
const Config = require('../config');
|
||||
const RequestList = require('../requestList');
|
||||
const requestPromiseNativeMock = require('request-promise-native');
|
||||
|
||||
describe('RequestList', () => {
|
||||
const endpoint = 'http://martianwabbit.com'
|
||||
const endpoint = 'http://martianwabbit.com';
|
||||
|
||||
let env = {
|
||||
environmental: true
|
||||
}
|
||||
};
|
||||
|
||||
const doc = {
|
||||
ENDPOINT: endpoint,
|
||||
|
|
@ -20,49 +20,49 @@ describe('RequestList', () => {
|
|||
lastname: 'Diaz'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let requests
|
||||
let requests;
|
||||
beforeEach(() => {
|
||||
requestPromiseNativeMock.fail = false
|
||||
requestPromiseNativeMock.fail = false;
|
||||
|
||||
let config = new Config(doc)
|
||||
requests = new RequestList(config)
|
||||
})
|
||||
let config = new Config(doc);
|
||||
requests = new RequestList(config);
|
||||
});
|
||||
|
||||
it('should allow an empty request list', () => {
|
||||
requests = new RequestList()
|
||||
expect(requests.list.length).toBe(0)
|
||||
})
|
||||
requests = new RequestList();
|
||||
expect(requests.list.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should load valid requests', () => {
|
||||
expect(requests.list.length).toBe(2)
|
||||
})
|
||||
expect(requests.list.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should fetch dependencies', async () => {
|
||||
await expect(
|
||||
requests.fetchDependencies(['get-posts'])
|
||||
).resolves.toMatchSnapshot()
|
||||
})
|
||||
).resolves.toMatchSnapshot();
|
||||
});
|
||||
|
||||
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 () => {
|
||||
requestPromiseNativeMock.fail = true
|
||||
await expect(requests.execByAlias('user')).rejects.toThrow()
|
||||
})
|
||||
requestPromiseNativeMock.fail = true;
|
||||
await expect(requests.execByAlias('user')).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('should return a cached result if available', async () => {
|
||||
const obj = { test: true }
|
||||
requests.cache.add('test', obj)
|
||||
await expect(requests.execByAlias('test')).resolves.toBe(obj)
|
||||
})
|
||||
const obj = { test: true };
|
||||
requests.cache.add('test', obj);
|
||||
await expect(requests.execByAlias('test')).resolves.toBe(obj);
|
||||
});
|
||||
|
||||
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`, () => {
|
||||
let config = new Config({
|
||||
|
|
@ -71,8 +71,8 @@ describe('RequestList', () => {
|
|||
hello: 1
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
expect(() => new RequestList(config, config)).toThrow()
|
||||
})
|
||||
})
|
||||
expect(() => new RequestList(config, config)).toThrow();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
const schema = require('../schema');
|
||||
|
||||
describe('Schema', () => {
|
||||
it(`should validate an object against the schema`, async () => {
|
||||
await expect(
|
||||
schema.validate({ endpoint: 'http://example.com' })
|
||||
).resolves.toHaveProperty('valid', true);
|
||||
});
|
||||
|
||||
it(`should indicate the error when an schema is invalid`, async () => {
|
||||
await expect(
|
||||
schema.validate({ plugins: [{ hello: 1, world: 2 }] })
|
||||
).resolves.toHaveProperty('valid', false);
|
||||
});
|
||||
});
|
||||
|
|
@ -5,9 +5,8 @@ const {
|
|||
UpperCaseKeys,
|
||||
removeOptionalKeys,
|
||||
toKebabCase,
|
||||
replaceInObject,
|
||||
expandPath
|
||||
} = require('../shared')
|
||||
replaceInObject
|
||||
} = require('../shared');
|
||||
|
||||
describe('Shared Utilities', () => {
|
||||
describe('requestRegex', () => {
|
||||
|
|
@ -22,9 +21,9 @@ describe('Shared Utilities', () => {
|
|||
['TRACE /hello', true],
|
||||
['PATCH /hello', true]
|
||||
])('should match: %s', (example, expected) => {
|
||||
expect(requestRegex.test(example)).toBe(expected)
|
||||
})
|
||||
})
|
||||
expect(requestRegex.test(example)).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('replacementRegex', () => {
|
||||
test.each([
|
||||
|
|
@ -33,9 +32,9 @@ describe('Shared Utilities', () => {
|
|||
['PUT /hi/$a.a/$a.b', ['$a.a', '$a.b']],
|
||||
[`\\$value`, ['\\$value']]
|
||||
])('should match: %s', (example, expected) => {
|
||||
expect(example.match(replacementRegex)).toEqual(expected)
|
||||
})
|
||||
})
|
||||
expect(example.match(replacementRegex)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('dynamicValueRegex', () => {
|
||||
test.each([
|
||||
|
|
@ -43,56 +42,34 @@ describe('Shared Utilities', () => {
|
|||
['$[test(1, 2, 3)]', ['$[test(1, 2, 3)]']],
|
||||
[`$[test({ \n id: 1 \n })]`, ['$[test({ \n id: 1 \n })]']]
|
||||
])('should match: %s', (example, expected) => {
|
||||
expect(example.match(dynamicValueRegex)).toEqual(expected)
|
||||
})
|
||||
})
|
||||
expect(example.match(dynamicValueRegex)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('UpperCaseKeys', () => {
|
||||
it('should uppercase all first-level keys in an object', () => {
|
||||
let a = { test: 1, Test2: 2 }
|
||||
expect(UpperCaseKeys(a)).toEqual({ TEST: 1, TEST2: 2 })
|
||||
})
|
||||
})
|
||||
let a = { test: 1, Test2: 2 };
|
||||
expect(UpperCaseKeys(a)).toEqual({ TEST: 1, TEST2: 2 });
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeOptionalKeys', () => {
|
||||
it('should remove empty objects from an object', () => {
|
||||
let a = { b: {}, c: 2, d: {} }
|
||||
expect(removeOptionalKeys(a, ['b', 'd'])).toEqual({ c: 2 })
|
||||
})
|
||||
})
|
||||
let a = { b: {}, c: 2, d: {} };
|
||||
expect(removeOptionalKeys(a, ['b', 'd'])).toEqual({ c: 2 });
|
||||
});
|
||||
});
|
||||
|
||||
describe('toKebabCase', () => {
|
||||
it('should convert camel case to kebab case', () => {
|
||||
expect(toKebabCase('helloWorld')).toBe('hello-world')
|
||||
})
|
||||
})
|
||||
expect(toKebabCase('helloWorld')).toBe('hello-world');
|
||||
});
|
||||
});
|
||||
|
||||
describe('replaceInObject', () => {
|
||||
it('should replace every value in an object with the output of a function', () => {
|
||||
let a = { b: 'b', c: 'c' }
|
||||
expect(replaceInObject(a, (obj) => 'a')).toEqual({ b: 'a', c: 'a' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('expandPath', () => {
|
||||
test.each([
|
||||
['https://alchem.ee', 'api/v1/hello'],
|
||||
['https://alchem.ee/', '/api/v1/hello'],
|
||||
['https://alchem.ee', '/api/v1/hello'],
|
||||
['https://alchem.ee/', 'api/v1/hello']
|
||||
])(
|
||||
'should add a base url to the path is the path is not a url: %s, %s',
|
||||
(url, path) => {
|
||||
expect(expandPath(url, path)).toEqual(
|
||||
'https://alchem.ee/api/v1/hello'
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
it('should return the path if its a fully fledged url on its own', () => {
|
||||
expect(
|
||||
expandPath('https://alchem.ee', 'https://martianwabbit.com')
|
||||
).toEqual('https://martianwabbit.com')
|
||||
})
|
||||
})
|
||||
})
|
||||
let a = { b: 'b', c: 'c' };
|
||||
expect(replaceInObject(a, obj => 'a')).toEqual({ b: 'a', c: 'a' });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
14
src/beau.js
14
src/beau.js
|
|
@ -1,20 +1,20 @@
|
|||
const RequestList = require('./requestList')
|
||||
const Config = require('./config')
|
||||
const { moduleVersion } = require('./shared')
|
||||
const RequestList = require('./requestList');
|
||||
const Config = require('./config');
|
||||
const { moduleVersion } = require('./shared');
|
||||
|
||||
class Beau {
|
||||
constructor(doc, env = {}) {
|
||||
this.config = new Config(doc, env)
|
||||
this.requests = new RequestList(this.config)
|
||||
this.config = new Config(doc, env);
|
||||
this.requests = new RequestList(this.config);
|
||||
|
||||
if (this.config.VERSION !== moduleVersion()) {
|
||||
console.warn(
|
||||
`This Beau file expects v${
|
||||
this.config.VERSION
|
||||
}. You are using v${moduleVersion()}.`
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Beau
|
||||
module.exports = Beau;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
const deepMerge = require('deepmerge')
|
||||
const { requestRegex, UpperCaseKeys, moduleVersion } = require('./shared')
|
||||
const Plugins = require('./plugins')
|
||||
const deepMerge = require('deepmerge');
|
||||
const { requestRegex, UpperCaseKeys, moduleVersion } = require('./shared');
|
||||
const Plugins = require('./plugins');
|
||||
|
||||
class Config {
|
||||
constructor(doc, env = {}) {
|
||||
|
|
@ -12,89 +12,81 @@ class Config {
|
|||
ENVIRONMENT: {},
|
||||
HOSTS: [],
|
||||
COOKIEJAR: false
|
||||
}
|
||||
};
|
||||
|
||||
this.configKeys = Object.keys(defaultConfigValues)
|
||||
this.configKeys = Object.keys(defaultConfigValues);
|
||||
|
||||
let config = this.loadConfig(doc)
|
||||
Object.assign(this, defaultConfigValues, config)
|
||||
let config = this.loadConfig(doc);
|
||||
Object.assign(this, defaultConfigValues, config);
|
||||
|
||||
this.ENVIRONMENT = deepMerge(this.ENVIRONMENT, env)
|
||||
this.ENVIRONMENT = deepMerge(this.ENVIRONMENT, env);
|
||||
|
||||
this.REQUESTS = []
|
||||
this.REQUESTS = [];
|
||||
|
||||
this.loadRequests(doc, {
|
||||
DEFAULTS: this.DEFAULTS,
|
||||
ENDPOINT: this.ENDPOINT
|
||||
})
|
||||
});
|
||||
|
||||
this.loadHosts(this.HOSTS, config, defaultConfigValues)
|
||||
this.loadHosts(this.HOSTS, config, defaultConfigValues);
|
||||
|
||||
this.PLUGINS = new Plugins(this.PLUGINS)
|
||||
this.PLUGINS = new Plugins(this.PLUGINS);
|
||||
}
|
||||
|
||||
loadHosts(hosts, rootConfig, defaultConfigValues) {
|
||||
hosts.forEach((host) => {
|
||||
hosts.forEach(host => {
|
||||
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(defaultConfigValues, this.loadConfig(host))
|
||||
let config = deepMerge(defaultConfigValues, this.loadConfig(host));
|
||||
|
||||
config.DEFAULTS = deepMerge(rootConfig.DEFAULTS, config.DEFAULTS)
|
||||
config.DEFAULTS = deepMerge(rootConfig.DEFAULTS, config.DEFAULTS);
|
||||
|
||||
this.loadRequests(host, {
|
||||
DEFAULTS: config.DEFAULTS,
|
||||
ENDPOINT: config.ENDPOINT,
|
||||
NAMESPACE: host.host
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
loadRequests(host, settings) {
|
||||
Object.entries(host)
|
||||
.filter(([key]) => requestRegex.test(key))
|
||||
.forEach(([key, rDefinition]) => {
|
||||
if (Array.isArray(rDefinition)) {
|
||||
rDefinition.forEach((req) =>
|
||||
this.addRequest(key, req, settings)
|
||||
)
|
||||
} else {
|
||||
this.addRequest(key, rDefinition, settings)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
addRequest(key, rDefinition, settings) {
|
||||
let requestDefinitionIsString = typeof rDefinition === 'string'
|
||||
let requests = Object.keys(host)
|
||||
.filter(key => requestRegex.test(key))
|
||||
.map(key => {
|
||||
let requestDefinitionIsString = typeof host[key] === 'string';
|
||||
let originalRequest = requestDefinitionIsString
|
||||
? { ALIAS: rDefinition }
|
||||
: rDefinition
|
||||
? { ALIAS: host[key] }
|
||||
: host[key];
|
||||
|
||||
let request = UpperCaseKeys(originalRequest)
|
||||
let request = UpperCaseKeys(originalRequest);
|
||||
|
||||
if (settings.NAMESPACE) {
|
||||
request.ALIAS = `${settings.NAMESPACE}:${request.ALIAS}`
|
||||
request.ALIAS = `${settings.NAMESPACE}:${request.ALIAS}`;
|
||||
}
|
||||
|
||||
request.REQUEST = key
|
||||
request.COOKIEJAR = this.COOKIEJAR
|
||||
request.ENDPOINT = settings.ENDPOINT
|
||||
request.REQUEST = key;
|
||||
request.COOKIEJAR = this.COOKIEJAR;
|
||||
request.ENDPOINT = settings.ENDPOINT;
|
||||
|
||||
let defaults = UpperCaseKeys(settings.DEFAULTS)
|
||||
let defaults = UpperCaseKeys(settings.DEFAULTS);
|
||||
|
||||
this.REQUESTS.push(deepMerge(defaults, request))
|
||||
return deepMerge(defaults, request);
|
||||
});
|
||||
|
||||
this.REQUESTS = this.REQUESTS.concat(requests);
|
||||
}
|
||||
|
||||
loadConfig(host) {
|
||||
let config = {}
|
||||
let config = {};
|
||||
|
||||
Object.entries(host)
|
||||
.filter(([key]) => this.configKeys.includes(key.toUpperCase()))
|
||||
.forEach(([key, value]) => (config[key.toUpperCase()] = value))
|
||||
Object.keys(host)
|
||||
.filter(k => this.configKeys.includes(k.toUpperCase()))
|
||||
.forEach(k => (config[k.toUpperCase()] = host[k]));
|
||||
|
||||
return config
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Config
|
||||
module.exports = Config;
|
||||
|
|
|
|||
102
src/plugins.js
102
src/plugins.js
|
|
@ -1,8 +1,8 @@
|
|||
const vm = require('vm')
|
||||
const requireg = require('requireg')
|
||||
const deepmerge = require('deepmerge')
|
||||
const { toKebabCase, dynamicValueRegex, replaceInObject } = require('./shared')
|
||||
const { isPlainObject } = require('is-plain-object')
|
||||
const vm = require('vm');
|
||||
const requireg = require('requireg');
|
||||
const deepmerge = require('deepmerge');
|
||||
const { toKebabCase, dynamicValueRegex, replaceInObject } = require('./shared');
|
||||
const isPlainObject = require('is-plain-object');
|
||||
|
||||
class Plugins {
|
||||
constructor(plugins = [], autoload = ['std']) {
|
||||
|
|
@ -10,110 +10,112 @@ class Plugins {
|
|||
preRequestModifiers: [],
|
||||
postRequestModifiers: [],
|
||||
dynamicValues: []
|
||||
}
|
||||
};
|
||||
|
||||
this.context = {}
|
||||
this.context = {};
|
||||
|
||||
this.autoload = autoload
|
||||
this.autoload = autoload;
|
||||
|
||||
this.loadPlugins(plugins.concat(this.autoload))
|
||||
this.loadPlugins(plugins.concat(this.autoload));
|
||||
}
|
||||
|
||||
normalizePlugins(plugins) {
|
||||
let results = {}
|
||||
let results = {};
|
||||
|
||||
plugins.forEach((plugin) => {
|
||||
let name = plugin
|
||||
let settings = undefined
|
||||
plugins.forEach(plugin => {
|
||||
let name = plugin;
|
||||
let settings = undefined;
|
||||
|
||||
if (typeof plugin === 'object') {
|
||||
let keys = Object.keys(plugin)
|
||||
let keys = Object.keys(plugin);
|
||||
|
||||
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]
|
||||
settings = plugin[name]
|
||||
name = keys[0];
|
||||
settings = plugin[name];
|
||||
}
|
||||
|
||||
results[name] = settings
|
||||
})
|
||||
results[name] = settings;
|
||||
});
|
||||
|
||||
return results
|
||||
return results;
|
||||
}
|
||||
|
||||
loadPlugins(plugins) {
|
||||
plugins = this.normalizePlugins(plugins)
|
||||
Object.keys(plugins).forEach((name) => {
|
||||
const module = `beau-${toKebabCase(name)}`
|
||||
plugins = this.normalizePlugins(plugins);
|
||||
Object.keys(plugins).forEach(name => {
|
||||
const module = `beau-${toKebabCase(name)}`;
|
||||
|
||||
if (typeof requireg.resolve(module) !== 'undefined') {
|
||||
const plugin = requireg(module)
|
||||
new plugin(this, plugins[name])
|
||||
const plugin = requireg(module);
|
||||
new plugin(this, plugins[name]);
|
||||
} else {
|
||||
if (this.autoload.includes(name)) return
|
||||
if (this.autoload.includes(name)) return;
|
||||
|
||||
console.warn(
|
||||
`Plugin ${name} couldn't be found. It is available globally?`
|
||||
)
|
||||
);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
executeModifier(modifier, obj, orig) {
|
||||
let result = deepmerge({}, obj, { isMergeableObject: isPlainObject })
|
||||
let result = deepmerge({}, obj, { isMergeableObject: isPlainObject });
|
||||
|
||||
this.registry[modifier].forEach(
|
||||
(modifier) => (result = modifier(result, orig))
|
||||
)
|
||||
modifier => (result = modifier(result, orig))
|
||||
);
|
||||
|
||||
return result
|
||||
return result;
|
||||
}
|
||||
|
||||
replaceDynamicValues(obj) {
|
||||
vm.createContext(this.context)
|
||||
return replaceInObject(obj, (val) => {
|
||||
let valIsEmpty = val.trim().length === 0
|
||||
vm.createContext(this.context);
|
||||
return replaceInObject(obj, val => {
|
||||
let valIsEmpty = val.trim().length === 0;
|
||||
|
||||
if (valIsEmpty) {
|
||||
return val
|
||||
return val;
|
||||
}
|
||||
|
||||
try {
|
||||
let onlyHasDynamic =
|
||||
val.replace(dynamicValueRegex, '').trim() === ''
|
||||
val.replace(dynamicValueRegex, '').trim() === '';
|
||||
|
||||
if (onlyHasDynamic) {
|
||||
let call
|
||||
let call;
|
||||
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 vm.runInContext(call, this.context)
|
||||
})
|
||||
return vm.runInContext(call, this.context);
|
||||
});
|
||||
} catch (e) {
|
||||
throw new Error(`DynamicValue: ` + e)
|
||||
throw new Error(`DynamicValue: ` + e);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
addPreRequestModifier(modifier) {
|
||||
this.registry.preRequestModifiers.push(modifier)
|
||||
this.registry.preRequestModifiers.push(modifier);
|
||||
}
|
||||
|
||||
addPostRequestModifier(modifier) {
|
||||
this.registry.postRequestModifiers.push(modifier)
|
||||
this.registry.postRequestModifiers.push(modifier);
|
||||
}
|
||||
|
||||
defineDynamicValue(name, fn) {
|
||||
this.registry.dynamicValues.push({ name, fn })
|
||||
this.context[name] = fn
|
||||
this.registry.dynamicValues.push({ name, fn });
|
||||
this.context[name] = fn;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Plugins
|
||||
module.exports = Plugins;
|
||||
|
|
|
|||
|
|
@ -1,71 +1,70 @@
|
|||
const request = require('request-promise-native')
|
||||
const RequestCache = require('./requestCache')
|
||||
const Plugins = require('./plugins')
|
||||
const request = require('request-promise-native');
|
||||
const RequestCache = require('./requestCache');
|
||||
const Plugins = require('./plugins');
|
||||
|
||||
const {
|
||||
requestRegex,
|
||||
replacementRegex,
|
||||
UpperCaseKeys,
|
||||
removeOptionalKeys,
|
||||
isUrl
|
||||
} = require('./shared')
|
||||
removeOptionalKeys
|
||||
} = require('./shared');
|
||||
|
||||
class Request {
|
||||
constructor(req, plugins = new Plugins()) {
|
||||
this.originalRequest = req
|
||||
this.plugins = plugins
|
||||
this.originalRequest = req;
|
||||
this.plugins = plugins;
|
||||
|
||||
req = UpperCaseKeys(req)
|
||||
Object.assign(this, req)
|
||||
req = UpperCaseKeys(req);
|
||||
Object.assign(this, req);
|
||||
|
||||
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.PATH = PATH
|
||||
this.VERB = VERB;
|
||||
this.PATH = PATH;
|
||||
|
||||
this.DEPENDENCIES = this.findDependencies(req)
|
||||
this.DEPENDENCIES = this.findDependencies(req);
|
||||
}
|
||||
|
||||
parseRequest(request) {
|
||||
const parts = request.match(requestRegex)
|
||||
const parts = request.match(requestRegex);
|
||||
|
||||
return {
|
||||
VERB: parts[1],
|
||||
PATH: parts[2]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
findDependencies(request, set = new Set()) {
|
||||
let type = typeof request
|
||||
let type = typeof request;
|
||||
|
||||
if (type === 'object' && request !== null) {
|
||||
Object.entries(request)
|
||||
.filter(([key]) => key !== 'ALIAS')
|
||||
.forEach(([key, value]) => {
|
||||
set = this.findDependencies(value, set)
|
||||
})
|
||||
if (type === 'object') {
|
||||
Object.keys(request)
|
||||
.filter(key => key !== 'ALIAS')
|
||||
.forEach(key => {
|
||||
set = this.findDependencies(request[key], set);
|
||||
});
|
||||
} else if (type === 'string') {
|
||||
const matches = []
|
||||
const matches = [];
|
||||
request.replace(
|
||||
replacementRegex,
|
||||
(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()) {
|
||||
let settings = cache.parse({
|
||||
baseUrl: '',
|
||||
baseUrl: this.ENDPOINT,
|
||||
uri: this.PATH,
|
||||
method: this.VERB,
|
||||
jar: this.COOKIEJAR,
|
||||
|
|
@ -79,10 +78,7 @@ class Request {
|
|||
json: true,
|
||||
simple: false,
|
||||
resolveWithFullResponse: true
|
||||
})
|
||||
|
||||
const isPathFullUrl = isUrl(settings.uri)
|
||||
settings.baseUrl = isPathFullUrl ? '' : this.ENDPOINT
|
||||
});
|
||||
|
||||
settings = removeOptionalKeys(settings, [
|
||||
'headers',
|
||||
|
|
@ -90,17 +86,17 @@ class Request {
|
|||
'body',
|
||||
'form',
|
||||
'formData'
|
||||
])
|
||||
]);
|
||||
|
||||
settings = this.plugins.replaceDynamicValues(settings)
|
||||
settings = this.plugins.replaceDynamicValues(settings);
|
||||
|
||||
settings = this.plugins.executeModifier(
|
||||
'preRequestModifiers',
|
||||
settings,
|
||||
this.originalRequest
|
||||
)
|
||||
);
|
||||
|
||||
const response = await request(settings)
|
||||
const response = await request(settings);
|
||||
|
||||
let results = {
|
||||
request: {
|
||||
|
|
@ -114,18 +110,18 @@ class Request {
|
|||
body: response.body
|
||||
},
|
||||
body: response.body
|
||||
}
|
||||
};
|
||||
|
||||
results = this.plugins.executeModifier(
|
||||
'postRequestModifiers',
|
||||
results,
|
||||
this.originalRequest
|
||||
)
|
||||
);
|
||||
|
||||
cache.add(this.ALIAS, results)
|
||||
cache.add(this.ALIAS, results);
|
||||
|
||||
return results
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Request
|
||||
module.exports = Request;
|
||||
|
|
|
|||
|
|
@ -1,46 +1,46 @@
|
|||
const { replacementRegex, replaceInObject } = require('./shared')
|
||||
const { replacementRegex, replaceInObject } = require('./shared');
|
||||
|
||||
class RequestCache {
|
||||
constructor() {
|
||||
this.$cache = {}
|
||||
this.$cache = {};
|
||||
}
|
||||
|
||||
exists(key) {
|
||||
return typeof this.$cache[key] !== 'undefined'
|
||||
return typeof this.$cache[key] !== 'undefined';
|
||||
}
|
||||
|
||||
add(key, value) {
|
||||
this.$cache[key] = value
|
||||
this.$cache[key] = value;
|
||||
}
|
||||
|
||||
get(path) {
|
||||
let result = this.$cache
|
||||
path.split('.').forEach((part) => {
|
||||
let result = this.$cache;
|
||||
path.split('.').forEach(part => {
|
||||
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) {
|
||||
if (item === null) {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
|
||||
return replaceInObject(item, (item) =>
|
||||
return replaceInObject(item, item =>
|
||||
item.replace(replacementRegex, (match, key) => {
|
||||
if (match.startsWith('\\')) {
|
||||
return match.replace('\\$', '$')
|
||||
return match.replace('\\$', '$');
|
||||
}
|
||||
|
||||
return this.get(key)
|
||||
return this.get(key);
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RequestCache
|
||||
module.exports = RequestCache;
|
||||
|
|
|
|||
|
|
@ -1,54 +1,59 @@
|
|||
const Request = require('./request')
|
||||
const RequestCache = require('./requestCache')
|
||||
const Request = require('./request');
|
||||
const RequestCache = require('./requestCache');
|
||||
|
||||
class RequestList {
|
||||
constructor(config = { REQUESTS: [] }) {
|
||||
this.list = this.loadRequests(config.REQUESTS, config.PLUGINS)
|
||||
this.cache = new RequestCache()
|
||||
this.PLUGINS = config.PLUGINS;
|
||||
this.REQUESTS = config.REQUESTS;
|
||||
|
||||
this.cache.add(`env`, config.ENVIRONMENT)
|
||||
this.list = this.loadRequests();
|
||||
this.cache = new RequestCache();
|
||||
|
||||
this.cache.add(`env`, config.ENVIRONMENT);
|
||||
}
|
||||
|
||||
async execByAlias(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') {
|
||||
throw new Error(`${alias} not found among the requests.`)
|
||||
throw new Error(`${alias} not found among the requests.`);
|
||||
}
|
||||
|
||||
try {
|
||||
await this.fetchDependencies(Array.from(request.DEPENDENCIES))
|
||||
return await request.exec(this.cache)
|
||||
await this.fetchDependencies(Array.from(request.DEPENDENCIES));
|
||||
return await request.exec(this.cache);
|
||||
} catch (reason) {
|
||||
throw new Error(
|
||||
`Request ${request.VERB} ${request.ENDPOINT} FAILED. \n${reason}`
|
||||
)
|
||||
`Request ${request.VERB} ${
|
||||
request.ENDPOINT
|
||||
} FAILED. \n${reason}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async fetchDependencies(dependencies) {
|
||||
dependencies = dependencies.map((d) => this.execByAlias(d))
|
||||
await Promise.all(dependencies)
|
||||
dependencies = dependencies.map(d => this.execByAlias(d));
|
||||
await Promise.all(dependencies);
|
||||
|
||||
return this.cache
|
||||
return this.cache;
|
||||
}
|
||||
|
||||
loadRequests(REQUESTS, PLUGINS) {
|
||||
let requests = []
|
||||
REQUESTS.forEach((request) => {
|
||||
loadRequests() {
|
||||
let requests = [];
|
||||
this.REQUESTS.forEach(request => {
|
||||
try {
|
||||
requests.push(new Request(request, PLUGINS))
|
||||
requests.push(new Request(request, this.PLUGINS));
|
||||
} 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;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,77 @@
|
|||
const Joi = require('joi');
|
||||
const { requestRegex } = require('./shared.js');
|
||||
|
||||
const pluginSchema = [
|
||||
Joi.string(),
|
||||
Joi.object()
|
||||
.keys(null)
|
||||
.max(1)
|
||||
];
|
||||
|
||||
const requestSchema = [
|
||||
Joi.object()
|
||||
.keys({
|
||||
HEADERS: Joi.object().keys(null),
|
||||
PAYLOAD: [Joi.object().keys(null), Joi.string()],
|
||||
PARAMS: Joi.object().keys(null),
|
||||
FORM: Joi.object().keys(null),
|
||||
ALIAS: Joi.string().required(),
|
||||
FORMDATA: Joi.object().keys(null)
|
||||
})
|
||||
.without('FORM', ['PAYLOAD', 'FORMDATA'])
|
||||
.without('PAYLOAD', ['FORM', 'FORMDATA'])
|
||||
.without('FORMDATA', ['FORM', 'PAYLOAD'])
|
||||
.rename(/headers/i, 'HEADERS', { override: true })
|
||||
.rename(/payload/i, 'PAYLOAD', { override: true })
|
||||
.rename(/params/i, 'PARAMS', { override: true })
|
||||
.rename(/form/i, 'FORM', { override: true })
|
||||
.rename(/alias/i, 'ALIAS', { override: true }),
|
||||
|
||||
Joi.string()
|
||||
];
|
||||
|
||||
const hostSchema = Joi.object()
|
||||
.keys({
|
||||
HOST: Joi.string().required(),
|
||||
ENDPOINT: Joi.string(),
|
||||
DEFAULTS: Joi.object().keys(null)
|
||||
})
|
||||
.pattern(requestRegex, requestSchema)
|
||||
.rename(/host/i, 'HOST', { override: true })
|
||||
.rename(/defaults/i, 'DEFAULTS', { override: true })
|
||||
.rename(/endpoint/i, 'ENDPOINT', { override: true });
|
||||
|
||||
const schema = Joi.object()
|
||||
.keys({
|
||||
VERSION: Joi.number().integer(),
|
||||
ENDPOINT: Joi.string().uri(),
|
||||
PLUGINS: Joi.array().items(pluginSchema),
|
||||
DEFAULTS: Joi.object(),
|
||||
ENVIRONMENT: Joi.object(),
|
||||
HOSTS: Joi.array().items(hostSchema),
|
||||
COOKIEJAR: Joi.boolean()
|
||||
})
|
||||
.pattern(requestRegex, requestSchema)
|
||||
.rename(/version/i, 'VERSION', { override: true })
|
||||
.rename(/endpoint/i, 'ENDPOINT', { override: true })
|
||||
.rename(/hosts/i, 'HOSTS', { override: true })
|
||||
.rename(/plugins/i, 'PLUGINS', { override: true })
|
||||
.rename(/defaults/i, 'DEFAULTS', { override: true })
|
||||
.rename(/environment/i, 'ENVIRONMENT', { override: true })
|
||||
.rename(/cookiejar/i, 'COOKIEJAR', { override: true });
|
||||
|
||||
const validate = async function(config) {
|
||||
try {
|
||||
await Joi.validate(config, schema, { allowUnknown: true });
|
||||
return { valid: true };
|
||||
} catch ({ name, details }) {
|
||||
return {
|
||||
valid: false,
|
||||
message: `${name}: \n ${details
|
||||
.map(d => d.message + ' @ ' + d.path)
|
||||
.join(' \n ')}`
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = { schema, validate };
|
||||
104
src/shared.js
104
src/shared.js
|
|
@ -1,5 +1,3 @@
|
|||
const { URL } = require('url')
|
||||
|
||||
const httpVerbs = [
|
||||
'GET',
|
||||
'HEAD',
|
||||
|
|
@ -10,80 +8,66 @@ const httpVerbs = [
|
|||
'OPTIONS',
|
||||
'TRACE',
|
||||
'PATCH'
|
||||
]
|
||||
];
|
||||
|
||||
const requestRegex = new RegExp(`(${httpVerbs.join('|')})\\s(.*)`, 'i')
|
||||
const replacementRegex = /(?:\\?)\$([a-zA-Z\.\d\-\_\:]+)/g
|
||||
const dynamicValueRegex = /\$\[(\w+\((?:.|[\n\r])*?\))\]/g
|
||||
const requestRegex = new RegExp(`(${httpVerbs.join('|')})\\s(.*)`, 'i');
|
||||
const replacementRegex = /(?:\\?)\$([a-zA-Z\.\d\-\_\:]+)/g;
|
||||
const dynamicValueRegex = /\$\[(\w+\((?:.|[\n\r])*?\))\]/g;
|
||||
|
||||
const UpperCaseKeys = function (obj) {
|
||||
let result = {}
|
||||
Object.entries(obj).forEach(([k, v]) => (result[k.toUpperCase()] = v))
|
||||
return result
|
||||
}
|
||||
const UpperCaseKeys = function(obj) {
|
||||
let result = {};
|
||||
Object.keys(obj).forEach(k => (result[k.toUpperCase()] = obj[k]));
|
||||
return result;
|
||||
};
|
||||
|
||||
const isEmptyObject = (obj) =>
|
||||
Object.keys(obj).length === 0 && obj.constructor === Object
|
||||
const isEmptyObject = obj =>
|
||||
Object.keys(obj).length === 0 && obj.constructor === Object;
|
||||
|
||||
const removeOptionalKeys = function (obj, optionalValues) {
|
||||
let result = {}
|
||||
const removeOptionalKeys = function(obj, optionalValues) {
|
||||
let result = {};
|
||||
|
||||
Object.entries(obj).forEach(([key, value]) => {
|
||||
if (optionalValues.includes(key) && isEmptyObject(value)) {
|
||||
return
|
||||
Object.keys(obj).forEach(key => {
|
||||
if (optionalValues.includes(key) && isEmptyObject(obj[key])) {
|
||||
return;
|
||||
}
|
||||
|
||||
result[key] = value
|
||||
})
|
||||
result[key] = obj[key];
|
||||
});
|
||||
|
||||
return result
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
const toKebabCase = function (str) {
|
||||
const toKebabCase = function(str) {
|
||||
return str
|
||||
.trim()
|
||||
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
||||
.toLowerCase()
|
||||
}
|
||||
.toLowerCase();
|
||||
};
|
||||
|
||||
const replaceInObject = function (obj, fn) {
|
||||
const replaceInObject = function(obj, fn) {
|
||||
if (obj === null) {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (typeof obj) {
|
||||
case 'undefined':
|
||||
return {}
|
||||
case 'string':
|
||||
return fn(obj)
|
||||
case 'object':
|
||||
obj = Object.assign({}, obj)
|
||||
Object.entries(obj).forEach(
|
||||
([key, value]) => (obj[key] = replaceInObject(value, fn))
|
||||
)
|
||||
default:
|
||||
return obj
|
||||
}
|
||||
}
|
||||
let type = typeof obj;
|
||||
|
||||
const moduleVersion = () => parseInt(require('../package.json').version, 10)
|
||||
|
||||
const isUrl = function (str) {
|
||||
try {
|
||||
new URL(str)
|
||||
return true
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const expandPath = (url, path) => {
|
||||
if (isUrl(path)) {
|
||||
return path
|
||||
if (type === 'undefined') {
|
||||
return {};
|
||||
}
|
||||
|
||||
return url.replace(/\/+$/, '') + '/' + path.replace(/^\/+/, '')
|
||||
}
|
||||
if (type === 'string') {
|
||||
return fn(obj);
|
||||
}
|
||||
|
||||
if (type === 'object') {
|
||||
obj = Object.assign({}, obj);
|
||||
Object.keys(obj).forEach(k => (obj[k] = replaceInObject(obj[k], fn)));
|
||||
}
|
||||
|
||||
return obj;
|
||||
};
|
||||
|
||||
const moduleVersion = () => parseInt(require('../package.json').version, 10);
|
||||
|
||||
module.exports = {
|
||||
requestRegex,
|
||||
|
|
@ -93,7 +77,5 @@ module.exports = {
|
|||
removeOptionalKeys,
|
||||
toKebabCase,
|
||||
replaceInObject,
|
||||
moduleVersion,
|
||||
isUrl,
|
||||
expandPath
|
||||
}
|
||||
moduleVersion
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue