README
API Copilot
Write testing or data population scenarios for your APIs.
Installation — Documentation — Usage — Contributing — License
Write an API scenario to make HTTP calls on your API:
var _ = require('underscore'),
copilot = require('api-copilot');
var productsData = [
[ 'Cheese', 2.45 ],
[ 'Milk', 4 ],
[ 'Wine', 10 ]
];
// create an API scenario
var scenario = new copilot.Scenario({
name: 'My Scenario',
summary: 'Populate some data into my API.',
baseUrl: 'http://example.com/api',
defaultRequestOptions: {
json: true
}
});
// define steps to run
scenario.step('create a user', function() {
// make HTTP calls
return this.post({
url: '/users',
body: {
name: 'pgibbons',
password: 'changeme'
}
});
});
// each step gets the result from the previous step
scenario.step('create 3 products', function(response) {
var user = response.body;
var requests = _.map(productsData, function(data) {
return this.post({
url: '/products',
body: {
title: data[0],
price: data[1],
userKey: user.key
},
expect: { statusCode: 201 }
});
}, this);
// run HTTP requests in parallel
return this.all(requests);
});
scenario.step('show created data', function(responses) {
var products = _.pluck(responses, 'body');
console.log(products.length + ' products created:');
_.each(products, function(product) {
console.log('- ' + product.title);
});
});
// export the scenario for API Copilot to use
module.exports = scenario;
Run it with API Copilot:
api-copilot --log debug run myScenario
See it in action:
My Scenario
Base URL: none
STEP 1: create a user
Completed in 140ms
STEP 2: create 3 products
Completed in 1250ms
STEP 3: show created data
3 products created:
- Cheese
- Milk
- Wine
Completed in 2ms
DONE in 1.39s!
Installation
To set up your project to use API Copilot for the first time, follow the project setup procedure. If you simply want to run API Copilot scenarios in a project where the setup procedure has already been done, jump to the usage installation procedure.
Project Setup Procedure
To set up a Node.js project to use API Copilot, install api-copilot
as a development dependency:
npm install --save-dev api-copilot
You can run this again whenever you want to upgrade to the latest version of API Copilot.
If your project is in another language, you can add this minimal package.json
file to your project:
{
"name": "my-project",
"version": "1.0.0",
"private": true,
"devDependencies": {
"api-copilot": "^0.3.3"
}
}
You should add the node_modules
directory to your version control ignore file.
By default, API Copilot expects your project to have an api
directory containing scenarios.
Each scenario is a Node.js file that can be run by API Copilot.
It must end with .scenario.js
.
The structure of a project containing only API Copilot scenarios might look like this:
api/foo.scenario.js
api/bar.scenario.js
api/baz.scenario.js
package.json
Usage Installation Procedure
Install the command line interface (you might have to use sudo
):
npm install -g api-copilot-cli
In your project's directory, install API Copilot and other dependencies:
cd /path/to/project
npm install
You're now ready to run API Copilot. If you don't have any API scenarios yet, you might want to write some.
Requirements
- Node.js v0.10+
Documentation
This README is the main usage documentation.
Run api-copilot --help
for command line usage information.
API Copilot uses external libraries to provide some of its functionality; refer to their documentation for more information:
The source code is also heavily commented and run through Docker, so you can read the resulting annotated source code for more details about the implementation. It also contains inline examples.
Check the CHANGELOG for information about new features and breaking changes.
Usage
- Writing API Scenarios
- Running Scenarios from the Command Line
- Configuration Options
- Scenario Flow Control
- Making HTTP calls
- Runtime Parameters
- Multipart Form Data
- Documenting Your Scenarios
Writing API Scenarios
The first to do in a scenario file is require api-copilot
and create a Scenario
object:
var copilot = require('api-copilot');
var scenario = new copilot.Scenario({
name: 'My Demo Sample Data'
});
A scenario is basically a sequence of steps that you define using the step
method.
The data returned by each step is available in the next step.
scenario.step('create some data', function() {
return 'some data';
});
scenario.step('log the data', function(data) {
console.log(data);
});
Steps are executed in the order they are defined by default. See Flow Control for more advanced behavior.
At the end of the file, you should export the scenario object:
module.exports = scenario;
Running Scenarios from the Command Line
The api-copilot
command is provided by the separate api-copilot-cli package.
Install it with npm install -g api-copilot-cli
.
API Copilot has two sub-commands which are documented below: list
and run
.
You may run api-copilot --help
for command line instructions.
Listing available scenarios
Use api-copilot list
to list the scenarios available in the current source directory:
$\> cd /path/to/project && api-copilot list
Source directory: /path/to/project/api
(use the `-s, --source <dir>` option to list API scenarios from another directory)
Available API scenarios (3):
1) api/foo.scenario.js (foo)
2) api/bar.scenario.js (bar)
3) api/sub/baz.scenario.js (baz)
Run `api-copilot info [scenario]` for more information about a scenario.
Run `api-copilot run [scenario]` to run a scenario.
[scenario] may be either the number, path or name of the scenario.
By default, the source directory is the api
directory relative to the current working directory (where you run API Copilot from).
For example, if you are in /path/to/project
, API Copilot will look for scenarios in /path/to/project/api
.
You may set a different source directory with the -s, --source <dir>
option.
Getting information about a scenario
Use api-copilot info [scenario]
to get detailed information about a scenario.
$\> cd /path/to/project && api-copilot info api/my.scenario.js
API COPILOT SCENARIO
Populates some data into my API.
Name: My Scenario
File: /path/to/project/api/my.scenario.js
PARAMETERS (3)
foo=value (required)
This is a required parameter.
bar=/^https?:/
This should be an HTTP or HTTPS URL.
baz
Activate some feature with this flag.
BASE CONFIGURATION
Options given to the scenario object:
{
"name": "My Scenario"
}
EFFECTIVE CONFIGURATION
{
"name": "My Scenario",
"log": "info",
"source": "samples"
}
STEPS (3)
1. do stuff
2. do more stuff
3. check stuff
The info
command without a scenario argument will behave like the run command:
it will automatically run a single available scenario, or ask you which one you want to run if multiple scenarios are available.
Running a scenario
Use api-copilot run [scenario]
to run a scenario in the source directory.
If there is only one scenario available, it will be run automatically. If multiple scenarios are found, API Copilot will display the list and ask you which one you want to run:
$\> cd /path/to/project && api-copilot run
Source directory: /path/to/project/api
(use the `-s, --source <dir>` option to list API scenarios from another directory)
Available API scenarios (3):
1) api/foo.scenario.js (foo)
2) api/bar.scenario.js (bar)
3) api/sub/baz.scenario.js (baz)
Type the number, path or name of the scenario you want to run:
Auto-completion is provided for scenario names.
If multiple scenarios are available, you can also directly run one by specifying its number, path or name as argument to the run
command:
api-copilot run 1
api-copilot run api/foo.scenario.js
api-copilot run foo
Configuration Options
API Copilot can be given configuration options in multiple ways:
- as options to the Scenario object;
- from YAML configuration files:
- the
.api-copilot.yml
file in your home directory; - the
api-copilot.yml
file in the current working directory; - other configuration files specified with the
config
option;
- the
- from environment variables;
- as options on the command line (run
api-copilot --help
to see available options).
Each of these option sources has greater precedence than the previous one. For example, an option given on the command line will always overwrite the value of the same option read from a configuration file or given through an environment variable.
The following configuration options are supported:
log
— command line-l, --log <level>
— env varAPI_COPILOT_LOG=<level>
Log level (trace, debug or info). The default level is info. Use trace to see the stack trace of errors.
source
— command line-s, --source <dir>
— env varAPI_COPILOT_SOURCE=<dir>
Path to the directory where API scenarios are located. The default directory is
api
. The path can be absolute or relative to the current working directory.baseUrl
— command line-u, --base-url <url>
— env varAPI_COPILOT_BASE_URL=<url>
Override the base URL of the scenario. Only the paths of URLs will be printed in debug mode.
showTime
— command line-t, --show-time
— env varAPI_COPILOT_SHOW_TIME=1
Print the date and time with each log.
showRequest
— command line-q, --show-request
— env varAPI_COPILOT_SHOW_REQUEST=1
Print options for each HTTP request (only with debug or trace log levels).
showResponseBody
— command line-b, --show-response-body
— env varAPI_COPILOT_SHOW_RESPONSE_BODY=1
Print response body for each HTTP request (only with debug or trace log levels).
showFullUrl
— command line--show-full-url
— env varAPI_COPILOT_SHOW_FULL_URL=1
Always print full URLs even when a base URL is configured (only with debug or trace log levels).
requestPipeline
— command line--request-pipeline <n>
— env varAPI_COPILOT_REQUEST_PIPELINE=<n>
Maximum number of HTTP requests to run in parallel (no limit by default). See request pipeline.
requestCooldown
— command line--request-cooldown <ms>
— env varAPI_COPILOT_REQUEST_COOLDOWN=<ms>
If set and an HTTP request ends, no other request will be started before this time (in milliseconds) has elapsed (no cooldown by default). See request pipeline.
requestDelay
— command line--request-delay <ms>
— env varAPI_COPILOT_REQUEST_DELAY=<ms>
If set and an HTTP request starts, no other request will be started before this time (in milliseconds) has elapsed (no delay by default). See request pipeline.
summary
Short summary explaining what your scenario does. This will be displayed in the info command output.
This option cannot be changed on the command line.
Additionally, these command-line- and environment-only options can be used to customize from which configuration files options are read from:
-c, --config <file>
— env varAPI_COPILOT_CONFIG=<file>
Add a configuration file to read options from. The path can be absolute or relative to the current working directory. This option can be used multiple times.
WARNING: note that by default, API Copilot will attempt to read
~/.api-copilot.yml
and$PWD/api-copilot.yml
. Using the--config
option disables these default configuration files unless you also set the--default-configs
option.--default-configs
— env varAPI_COPILOT_DEFAULT_CONFIGS=1
In conjunction with the
--config
option, this causes the default configuration files (~/.api-copilot.yml
and$PWD/api-copilot.yml
) to still be read instead of being ignored.
Changing the Configuration while a Scenario is Running
In any step of the scenario, you may change the configuration with the configure
method:
var scenario = new Scenario({
name: 'myScenario',
summary: 'Populates some data into my API.',
baseUrl: 'http://example.com/foo'
});
scenario.step('first step', function() {
// this HTTP request will use the baseUrl configured above
return this.get({ url: '/' });
});
scenario.step('second step', function(response) {
// change the baseUrl
this.configure({ baseUrl: 'http://example.com/bar' });
// this HTTP request will use the newly configured baseUrl
return this.get({ url: '/' });
});
Scenario Flow Control
To complete a step and send a result to the next step, you can simply return the result:
scenario.step('compute data', function() {
return computeSomeDataSynchronously();
});
scenario.step('log data', function(data) {
console.log(data);
});
Note that this only works for synchronous steps. Asynchronous steps and HTTP Calls are described later.
If you want to pass multiple results to the next step, use the success
method:
scenario.step('compute data', function() {
return this.success('foo', 'bar', 'baz');
});
scenario.step('log data', function(result1, result2, result3) {
console.log(result1);
console.log(result2);
console.log(result3);
});
Again, this is only for synchronous steps.
To skip a step and log an informational message, use the skip
method:
scenario.step('compute data', function() {
if (someCondition) {
return this.skip('no computation needed');
} else {
return 'some data';
}
});
To fail a step and stop the whole scenario, use the fail
method:
scenario.step('log data', function(data) {
if (data == null) {
return this.fail('data was not computed');
}
console.log(data);
});
Or you can simply throw an error:
scenario.step('log data', function(data) {
if (data == null) {
throw new Error('data was not computed');
}
console.log(data);
});
To complete the scenario successfully and not run any more steps, use the complete
method:
scenario.step('might be done here', function(done) {
if (done) {
// further steps will not be executed
return this.complete();
}
// otherwise continue
return computeSomeStuff();
});
To make an asynchronous step, return a promise instead of a value (see the q library).
API Copilot provides you the defer
method to generate a deferred object.
This object has a promise property which you can return;
then resolve or reject the deferred object once your asynchronous operation is complete.
scenario.step('async step', function() {
var deferred = this.defer();
// this operation is asynchronous, so it will be executed later
asyncStuff('input', function(err, result) {
if (err) {
deferred.reject(err);
} else {
deferred.resolve(result);
}
});
// return the promise object
return deferred.promise;
});
You can simplify callback-based asynchronous code by requiring q and using its Node adaptation functions:
var q = require('q');
scenario.step('async step', function() {
return q.nfcall(asyncStuff, 'input');
});
To do this, you must add q
to your own development dependencies by running npm install --save-dev q
.
You may also return any promise that follows the Promises/A+ standard.
To continue with another step than the next one, call the setNextStep
method at any time during the execution of the current step:
scenario.step('first step', function() {
this.setNextStep('third step');
return this.success('some data');
});
scenario.step('second step', function() {
// this step will be skipped
});
scenario.step('third step', function(data) {
console.log(data);
});
Making HTTP Calls
API Copilot includes the request library to make HTTP calls.
Use the get
, head
, post
, put
, patch
and delete
methods to start a call.
These methods return a promise thath you can return from your step.
The next step will receive the HTTP response.
scenario.step('call API', function() {
return this.get({
url: 'http://example.com/'
});
});
scenario.step('log response body', function(response) {
console.log(response.body); // response body string
});
You can also use the more generic request
method that supports any HTTP verb:
scenario.step('an API call', function() {
return this.request({
method: 'GET',
url: 'http://example.com/'
});
});
If an I/O error occurs, the error message will be logged and the scenario will stop.
Note that server errors do not interrupt the scenario. It is your responsibility to check the response from the server:
scenario.step('an API call', function() {
return this.get({
url: 'http://example.com'
});
});
scenario.step('handle API data', function(response) {
if (response.statusCode != 200) {
return this.fail('server responded with unexpected status code ' + response.statusCode);
}
console.log(response.body);
});
Passing the json option causes the request body and response body to be serialized/deserialized as JSON:
scenario.step('an API call', function() {
return this.post({
url: 'http://example.com',
json: true,
body: {
foo: 'bar'
}
}); // the request body will be {"foo":"bar"}
});
scenario.step('handle API data', function(response) {
console.log(response.body); // the body will be a javascript object: { foo: 'bar' }
});
Read the request documentation for more HTTP configuration options.
Default Request Options
If you need to re-use options for many requests, you can use setDefaultRequestOptions
in a step.
It will apply the specified options to all subsequent requests.
scenario.step('step 1', function() {
this.setDefaultRequestOptions({
json: true
});
// the `json` option will be added to this request
return this.post({
url: '/foo',
body: {
some: 'data'
}
});
});
scenario.step('step 2', function(response) {
// this request will also have the `json` option
return this.post({
url: '/bar',
body: {
more: 'data'
}
});
});
You can also extend default request options (without overriding all of them) with extendDefaultRequestOptions
:
scenario.step('step 3', function(response) {
this.extendDefaultRequestOptions({
headers: {
'X-Custom': 'value',
'X-Custom-2': 'value 2'
}
});
// this request will have both the `json` and `headers` options
return this.post({
url: '/baz',
body: {
more: 'data'
}
});
});
If you need to add options to a sub-object, like headers
, use mergeDefaultRequestOptions
:
scenario.step('step 4', function(response) {
this.mergeDefaultRequestOptions({
headers: {
// add a header without overriding previous ones
'X-Custom-B': 'value B',
// also clear a specific header without clearing them all
'X-Custom-2': undefined
}
});
// this request will have the X-Custom and X-Custom-B headers,
// while the X-Custom-2 header was cleared
return this.post({
url: '/baz',
body: {
more: 'data'
}
});
});
Clear previously configured default request options with clearDefaultRequestOptions
:
scenario.step('step 5', function(response) {
// clear specific request option(s) by name
this.clearDefaultRequestOptions('headers');
// this request will only have the `json` option added since the `headers` option was cleared
return this.post({
url: '/qux',
body: {
more: 'data'
}
});
});
scenario.step('step 6', function(response) {
// clear all default request options
this.clearDefaultRequestOptions();
// no additional options will be added
return this.post({
url: '/quxx',
body: 'some text'
});
});
Request Filters
Request filters are run just before an HTTP call is made, allowing you to customize the request based on all its options:
// define a filter function
// the `requestOptions` argument will be the actual options passed to the request library
function signRequest(requestOptions) {
requestOptions.headers = {
'X-Signature': sha1(requestOptions.method + '\n' + requestOptions.url)
};
// the filter function must return the updated request options
return requestOptions;
}
scenario.step('HTTP call with signature authentication', function() {
// add the filter
this.addRequestFilter(signRequest);
// the X-Signature header will automatically be added to this and subsequent requests
this.get({
url: '/foo'
});
});
Remove request filters with removeRequestFilters
:
scenario.step('another step', function() {
// remove specific request filter(s)
this.removeRequestFilters(signRequest);
// remove all requests filters
this.removeRequestFilters();
});
You can also identify filters by name:
scenario.step('HTTP call with named filters', function() {
// add named filters
this.addRequestFilter('foo', fooFilter);
this.addRequestFilter('bar', barFilter);
this.addRequestFilter('baz', bazFilter);
// adding a new filter for the same name overrides the previous filter
this.addRequestFilter('foo', anotherFooFilter);
// remove filters with a given name or names
this.removeRequestFilters('foo');
this.removeRequestFilters('bar', 'baz');
// remove all request filters
this.removeRequestFilters();
});
You can make a request filters asynchronous by returning a promise for the request options:
scenario.step('asynchronous filters', function() {
this.addRequestFilter('signature', function(requestOptions) {
// make a deferred object with the q library
var deferred = q.defer();
// launch your asynchronous operation
getSomeAsyncRequestOptions(requestOptions, function(err, options) {
if (err) {
return deferred.reject(err);
}
// resolve the deferred object when done
deferred.resolve(options);
});
// return the promise
return deferred.promise;
});
this.get({
url: '/foo'
});
});
Expecting a Specific Response
You can specify expected properties of an HTTP response with the expect
option.
To check that the status code is the one you expect, specify an expected statusCode
:
scenario.step('step', function() {
return this.post({
url: 'http://example.com/foo',
body: {
some: 'data'
},
expect: {
// the request will fail and the scenario will be interrupted
// if the status code of the response is not the expected one
statusCode: 201
}
});
});
Check that the code is in a given range using a regular expression:
scenario.step('step', function() {
return this.post({
url: 'http://example.com/foo',
body: {
some: 'data'
},
expect: {
// the request will fail and the scenario will be interrupted
// if the status code of the response is not in the 2xx range
statusCode: /^2/
}
});
});
You can also specify an array of expected status codes:
scenario.step('step', function() {
return this.get({
url: 'http://example.com',
expect: {
// the request will fail and the scenario will be interrupted
// if the status code of the response is not among these
statusCode: [ 200, 204, /^3/ ]
}
});
});
To specify a custom error message, pass an object with the expected
value as the value
option, and the message as the message
option:
scenario.step('step', function() {
return this.get({
url: 'http://example.com',
expect: {
statusCode: {
value: /^2/,
message: 'Must be in the 2xx range.'
}
}
});
});
To build an error message from the expected and actual values,
pass a function as the message
option:
scenario.step('step', function() {
return this.get({
url: 'http://example.com',
expect: {
statusCode: {
value: [ 200, 201, 204 ],
message: function(expected, actual) {
return "Expected " + actual + " to be in " + expected;
}
}
}
});
});
Running requests in parallel
Pass an array of HTTP requests to the all
method to run them in parallel:
scenario.step('many HTTP calls', function() {
return this.all([
this.get('http://example.com/foo'),
this.get('http://example.com/bar'),
this.get('http://example.com/baz')
]);
});
scenario.step('handle responses', function(responses) {
// you get the three responses in the next step
// when all requests have completed
responses.length; // #=> 3
});
The all
method turns an array of promises into a single promise that is resolved
when all the promises in the array have been resolved. The resolution value will be
an array of the resolved values.
Read about q combinations for more information.
Configuring the request pipeline
Parallelism might be an issue in some situations. Maybe your backend cannot handle as many concurrent requests as are issued by your scenario, or maybe your API limits the frequency of calls.
The request pipeline allows you to limit how often HTTP requests are started. It has three configuration options.
requestPipeline —
<n>
This limits the number of HTTP requests that can be made in parallel. For example, if set to 3, no more than 3 HTTP requests will run concurrently at any given time.
scenario.step('limit request concurrency', function() {
scenario.configure({ requestPipeline: 3 });
return this.all([
// the first three requests will be executed concurrently
this.get({ url: 'http://example.com/a' }), // starts at time 0, takes 50ms
this.get({ url: 'http://example.com/b' }), // starts at time 0, takes 100ms
this.get({ url: 'http://example.com/c' }), // starts at time 0, takes 100ms
// the next three requests will start one by one as soon as each of the first three finishes
this.get({ url: 'http://example.com/d' }), // starts at time 50, after the first request finishes
this.get({ url: 'http://example.com/e' }), // starts at time 100, after the second request finishes
this.get({ url: 'http://example.com/f' }) // starts at time 100, after the third request finishes
]);
});
requestCooldown —
<ms>
When set, the request cooldown guarantees that after an HTTP request end, no other request will start before the cooldown time (in milliseconds) has elapsed.
scenario.step('wait after each request', function() {
scenario.configure({ requestPipeline: 1, requestCooldown: 250 });
// after the first HTTP request, each request will wait for the previous one to finish,
// since the request pipeline is set to 1, and then 250 additional milliseconds (the
// request cooldown), before starting
return this.all([
this.get({ url: 'http://example.com/a' }), // starts at time 0, takes 100ms
this.get({ url: 'http://example.com/b' }), // starts at time 350 (last response time + cooldown), takes 100ms
this.get({ url: 'http://example.com/c' }), // starts at time 700 (last response time + cooldown), takes 100ms
this.get({ url: 'http://example.com/d' }), // starts at time 1050 (last response time + cooldown), takes 100ms
]);
});
requestDelay —
<ms>
When set, the request delay guarantees that after an HTTP request starts, no other request will start before the delay time (in milliseconds) has elapsed. This can be used to spread out concurrent requests so that they are not all started at exactly the same time.
scenario.step('wait after each request', function() {
scenario.configure({ requestDelay: 50 });
// each request will start 50ms after the previous one has started
// but they will still run concurrently if they take more than 50ms to complete
return this.all([
this.get({ url: 'http://example.com/a' }), // starts at time 0
this.get({ url: 'http://example.com/b' }), // starts at time 50
this.get({ url: 'http://example.com/c' }), // starts at time 100
this.get({ url: 'http://example.com/d' }), // starts at time 150
]);
});
Runtime Parameters
Scenarios can be configured to take custom runtime parameters with the addParam
method:
var scenario = new Scenario({
name: 'scenario with parameters'
});
scenario.addParam('foo');
scenario.addParam('bar', { flag: true });
These parameters can then be supplied on the command line:
#> api-copilot -p foo=value -p bar
Retrieve them with the param
method when running the scenario:
scenario.step('step', function() {
this.param('foo'); // "value"
this.param('bar'); // true
});
Note that all parameters must be registered with the addParam
method.
Trying to retrieve unknown parameters will throw an error and stop the scenario.
scenario.step('step', function() {
this.param('unknown'); // throws Error
});
Use the info command to obtain a list of the parameters for a scenario. See parameter options on how to configure and document parameters.
Parameter options
required —
boolean, default: true
Parameters are required by default. Set this option to
false
to make them optional.
scenario.addParam('optionalFeature', {
required: false
});
If you try to run a scenario without giving a value for a required parameter, you will be prompted to enter a value:
The scenario cannot be run because the following parameters are either missing or invalid:
- foo is required; set it with `-p foo=value` or `--param foo=value`
You will now be asked for the missing or corrected values.
Press Ctrl-C to quit.
foo=value (required)
This is a required parameter.
Enter a value for foo:
default —
any, default: undefined
Default value of the parameter when not configured.
scenario.addParam('url', {
default: 'http://example.com'
});
flag —
boolean, default: false
Set this option to
true
to make your parameter a boolean flag.Boolean flags are specified without a value on the command line:
-p foo
or--params foo
. Trying to give them a value will produce an error and prevent the scenario from running.When retrieving the value with the
param
method in a scenario, it will be eithertrue
orundefined
(if the flag was not given).
scenario.addParam('coolFeature', {
flag: true
});
pattern —
regexp, default: none
Validate your parameter with a regular expression.
The scenario will only run if the supplied parameter value matches; otherwise, you will be prompted for a new value.
scenario.addParam('backendUrl', {
pattern: /^https?:/
});
description —
string, default: none
Additional documentation for your parameter. It will be displayed in the info command output.
scenario.addParam('backendUrl', {
description: 'The URL to our cool backend. Must be HTTPS.'
});
Sample output:
backendUrl=value (required)
The URL to our cool backend. Must be HTTPS.
valueDescription —
string, default: none
Custom value description for your parameter.
The default description of a parameter is
foo=value
. If you specified apattern
option, it will print the pattern instead, e.g.backendUrl=/^https?:/
.Set a
valueDescription
to customize this.
scenario.addParam('backendUrl', {
valueDescription='url'
});
Sample output:
backendUrl=url
obfuscate —
boolean, default: false
If set, the value of this parameter will be obfuscated when displayed before the scenario starts running.
Note that this is automatically set for parameters whose name contains the words
password
,secret
orauthToken
(case-insensitive). If you have such a parameter that you do not want obfuscated, set the option tofalse
.
scenario.addParam('password');
scenario.addParam('foo', {
obfuscate: true
});
Sample output when running a scenario:
Runtime parameters:
password = "************"
foo = "***"
processor —
function(value, previousValue), default: none
If set, the parameter will take the value obtained by calling this function with the configured value. If a default value is set, it will be given as second argument. This can be used to coerce values of a given type, such as numbers.
scenario.addParam('n', {
processor: parseInt
});
If a parameter is given multiple times at the command line, or is an array in the scenario object or configuration file, the processor function will be called once for each value, with that value as the first argument. The second argument will be the result returned by the processor function for the previous value, or the default value the first time.
This can be used to construct a parameter value from multiple values.
// this scenario can use multiple URLs
scenario.addParam('urls', {
// start with an empty list
default: [],
// every time a -p urls=<url> is given, add it to the list
processor: function(value, urls) {
urls.push(value);
return urls;
}
});
// if called with `-p urls=http://example.com/a -p urls=http://example.com/b`,
// the value of the `urls` parameter will be [ "http://example.com/a", "http://example.com/b" ]
Loading parameters from another source
By default, runtime parameters can come from three sources:
- the options given to the scenario object;
- the YAML configuration file;
- command line parameters.
To load more parameters from elsewhere, add a loading function to your scenario object with the loadParametersWith
method:
var scenario = new Scenario({
name: 'scenario with lots of parameters',
params: { some: 'initial', parameter: 'values' }
});
// load more parameters from anywhere
scenario.loadParametersWith(function(params) {
// each loading function is passed all previously loaded parameters,
// including those from options, the configuration file and the command line
params.more = 'parameters';
// each loading function must return the updated parameters
return params;
});
// return a promise to support asynchronous parameter loading
scenario.loadParametersWith(function() {
var deferred = q.defer(); // promises with the q library
loadParametersAsynchronously(function(err, moreParams) {
if (err) {
return deferred.reject(err);
}
// resolve the promise once the parameters have been loaded
deferred.resolve(moreParams);
});
// return a promise for the updated parameters
return deferred.promise;
});
Parameter loading functions are run in the order they are added to the scenario object.
Documenting parameters
In addition to the description option shown above,
the addParam
method returns a parameter object which will emit a describe
event when its documentation is printed.
You can listen to this event to print more documentation:
var myParam = scenario.addParam('myParam');
myParam.on('describe', function(print) {
// use the provided print function to have your text correctly indented
print('More');
print('information.');
// it supports new lines as well
print('Even\nmore\ninformation.');
});
Sample info output:
myParam=value
More
information.
Even
more
information.
Multipart Form Data
The request library included in API Copilot supports multipart/form-data
requests with the form-data library.
Check out its documentation about forms.
Using only the request library, this is how you would upload a file:
var fs = require('fs');
var r = request.post('http://example.com/upload', function(err, response, body) {
if (err) {
return console.error('Upload failed: ' + err);
}
console.log('Upload successful! Server responded with: ' + body);
})
var form = r.form()
form.append('my_file', fs.createReadStream('/path/to/file.ext'));
To retrieve the request object and create a form with API Copilot, you will need to supply a handler function. The handler function is called with the request object before the HTTP request starts.
var fs = require('fs');
function createFileUploadHandler(file) {
return function (request) {
var form = request.form();
form.append('file', fs.createReadStream(file));
};
}
scenario.step('file upload', function() {
var file = '/path/to/file.ext';
return this.post({
url: 'http://example.com/',
handler: createFileUploadHandler(file)
});
});
Note: the request handler must be synchronous since the HTTP request will start immediately at the next tick.
Documenting Your Scenarios
Scenarios with many parameters can become quite complicated to understand and use.
Start by specifying a short summary. Then document your parameters with the description option and the describe event.
To add additional documentation at the end of the info command output, listen to the scenario:info
event on the scenario object:
var copilot = require('api-copilot');
var scenario = new copilot.Scenario({
name: 'My Complex Scenario'
});
scenario.on('scenario:info', function() {
console.log('Additional Information:');
console.log();
console.log(' To use this scenario, you must ... and ... first.');
console.log();
});
Contributing
- Fork
- Create a topic branch -
git checkout -b feature
- Push to your branch -
git push origin feature
- Create a pull request from your branch
Please add a changelog entry with your name for new features and bug fixes.
License
API Copilot is licensed under the MIT License. See LICENSE.txt for the full text.