Skip to content
This repository was archived by the owner on Nov 8, 2024. It is now read-only.

Commit 3943e16

Browse files
authored
Merge pull request #42 from apiaryio/honzajavorek/cli
Add CLI
2 parents 56bdec2 + fa1e3da commit 3943e16

File tree

13 files changed

+508
-586
lines changed

13 files changed

+508
-586
lines changed

.circleci/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
steps:
1717
- checkout
1818
- run:
19-
name: Install dev dependencies (Node.js)
19+
name: Install dependencies (Node.js)
2020
command: npm install
2121
- run:
2222
name: Install the reference hooks implementation (Python)

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
node_modules
2-
test
2+
dredd-hooks-template-*.tgz

CONTRIBUTING.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Contributing
2+
3+
Clone the repository and install dependencies:
4+
5+
```
6+
$ git clone "https://github.com/apiaryio/dredd-hooks-template.git"
7+
$ cd ./dredd-hooks-template
8+
$ npm install
9+
```
10+
11+
## Smoke Test
12+
13+
There is a [smoke test](https://en.wikipedia.org/wiki/Smoke_testing_(software)), which ensures everything works as described in the tutorial above. It uses the [Python hooks handler](https://github.com/apiaryio/dredd-hooks-python) as the reference implementation, so have it installed:
14+
15+
```
16+
$ pip install dredd_hooks
17+
```
18+
19+
Then you can run the smoke test:
20+
21+
```
22+
$ npm test
23+
```
24+
25+
## Feature Files Linter
26+
27+
The feature files syntax is validated automatically. To perform the validation locally, use the `lint` script:
28+
29+
```
30+
$ npm run lint
31+
```
32+
33+
## Introducing Changes
34+
35+
The test suite uses the [Python hooks](https://github.com/apiaryio/dredd-hooks-python) as a reference implementation. To introduce a change to this test suite, follow these steps:
36+
37+
1. Implement the new behavior in the Python hooks.
38+
1. Change the feature files living in the Python hooks repo to describe the new behavior. If needed, add a local `steps.js` file implementing missing test steps.
39+
1. Make sure the Python hooks pass that changed test suite.
40+
1. Release a new version of the Python hooks.
41+
1. Generalize the changed (or added) feature files with placeholders, comment-out the code examples, and copy the files over to this repository. Add missing test steps implementations to the `steps.js`. Create a Pull Request.
42+
1. Make sure the smoke test passes under the Pull Request.
43+
1. Merge the Pull Request and let Semantic Release to produce a new version of the test suite package.
44+
1. Go to Python hooks repository and [upgrade](README.md#upgrading) the test suite package there. Remove any local `steps.js` as the necessary steps should already be implemented in the new version of the test suite package.
45+
1. Go to Ruby hooks repository and [upgrade](README.md#upgrading) the test suite package there.
46+
1. Go to all remaining repositories with hooks handlers and issue Pull Requests for the maintainers which help them to kick-off the upgrade. It's okay if they're incomplete, their purpose is to advertise the changes, to initiate the upgrade, and to be helpful to the maintainers.

README.md

Lines changed: 39 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,60 @@
1-
# Cross-Language Test Suite for Dredd Hooks
1+
# Cross-Language Test Suite for Dredd Hooks Handlers
22

3-
[![Build Status](https://travis-ci.org/apiaryio/dredd-hooks-template.svg?branch=master)](https://travis-ci.org/apiaryio/dredd-hooks-template) [![Greenkeeper badge](https://badges.greenkeeper.io/apiaryio/dredd-hooks-template.svg)](https://greenkeeper.io/)
3+
[![Build Status](https://travis-ci.org/apiaryio/dredd-hooks-template.svg?branch=master)](https://travis-ci.org/apiaryio/dredd-hooks-template)
44

5-
Language-agnostic BDD test suite for boilerplating implementation of [Dredd][] [hooks][] handler for a new language. It tests the public interface of the hooks handler and ensures it will work as Dredd expects. It's written in [Gherkin][] and ran by [Aruba][].
5+
[Dredd](https://dredd.org) is a tool for testing web APIs. It supports [hooks](http://dredd.org/en/latest/hooks/index.html) written in [many languages](http://dredd.org/en/latest/hooks/index.html#supported-languages). To support a particular language, it needs an adapter, so-called hooks handler. This [BDD](https://en.wikipedia.org/wiki/Behavior-driven_development) test suite ensures that the public interface of each hooks handler works as Dredd expects. The test suite is written in [Gherkin](https://github.com/cucumber/cucumber/wiki/Gherkin) and uses [Cucumber](https://github.com/cucumber/cucumber-js) as a test runner.
66

7-
[Aruba]: https://github.com/cucumber/aruba
8-
[Gherkin]: https://github.com/cucumber/cucumber/wiki/Gherkin
9-
[Dredd]: https://github.com/apiaryio/dredd
10-
[hooks]: https://dredd.readthedocs.io/en/latest/hooks/
7+
To use the test suite, first read the docs about [how to create a new hooks handler](http://dredd.org/en/latest/hooks/new-language.html) for Dredd. Implement your hooks handler and then continue with the following guide.
118

12-
## Usage
13-
14-
### Install the features dependencies
9+
## Installation
1510

16-
```bash
17-
npm install -g dredd
18-
git clone https://github.com/apiaryio/dredd-hooks-template.git
19-
cd dredd-hooks-template
20-
bundle install
21-
```
11+
1. Make sure you have [Node.js](https://nodejs.org/) (ideally version 10 or higher) and [npm](https://www.npmjs.com/package/npm) available.
12+
1. Create a `package.json` file in the root of your project. This is where your JavaScript dependencies are going to be specified:
2213

23-
### Enable the features for your language
14+
```json
15+
{
16+
"scripts": { "test": "dredd-hooks-template test" },
17+
"private": true
18+
}
19+
```
2420

25-
1. Open the feature files in `./features/*.feature`
26-
1. In all of them, replace:
27-
- `{{mylanguage}}` by the hooks handler command for you language
28-
- `{{myextension}}` by the extension for your language
29-
1. Implement the code examples in your language
30-
1. Run the test suite: `bundle exec cucumber`
21+
1. Run `npm install dredd-hooks-template --save-dev` to install and declare this test suite as your development dependency.
22+
1. Run `npx dredd-hooks-template init` to get a copy of the test suite in the `./features` directory.
23+
1. Open the feature files in `./features/*.feature` and in all of them
3124

32-
### Add the features to your project
25+
- replace `dredd-hooks-{{mylanguage}}` with a path to your hooks handler executable which you want to get tested (e.g. `./bin/dredd_hooks`)
26+
- replace `{{myextension}}` by the extension of the hooks handler language (e.g. `.py`),
27+
- uncomment the code blocks and rewrite them to the hooks handler language.
3328

34-
If the test suite did run as expected, you can now add the features to your project.
35-
To do so, copy to your project:
29+
Now you have the test suite ready.
3630

37-
1. the entire `features/` directory
38-
1. the `Gemfile`, `Gemfile.lock` and `.ruby-version`
31+
## Usage
3932

40-
Your should now be able to install the features dependencies and run the test suite in your project.
33+
Every time you run `npx dredd-hooks-template test` (or `npm test`), you should see the test suite running. The end goal is that all the tests pass:
4134

42-
Finally, make `bundle exec cucumber` part of your test suite and CI (see `.travis.example.yml` if you are using [Travis CI][travis]).
35+
![test suite passing](passing.png)
4336

44-
[travis]: https://travis-ci.org
37+
You should add the `package.json` file to Git. When starting from scratch, you can run `npm install` to install the JavaScript dependencies.
4538

46-
## Development
39+
<a name="upgrading"></a>
4740

48-
The feature files syntax is validated automatically. To perform the validation locally:
41+
## Upgrading
4942

50-
```bash
51-
# Install the dependencies
52-
npm install
43+
[Watch for newer versions](https://github.com/apiaryio/dredd-hooks-template/releases) of the [dredd-hooks-template package](https://www.npmjs.com/package/dredd-hooks-template) and upgrade regularly to keep up with development of Dredd and the test suite itself. To upgrade, run:
5344

54-
# Run the linter
55-
npm test
5645
```
46+
$ npx dredd-hooks-template upgrade
47+
```
48+
49+
It upgrades the package to the latest version and copies the latest feature files to the project's `./features/` directory. It won't overwrite the existing files as the names of the new files get suffixed with version. Then it's up to you to compare the old and new files, spot changes, and update the project's test suite.
50+
51+
## Reference Implementations
52+
53+
The [Python hooks](https://github.com/apiaryio/dredd-hooks-python) and the [Ruby hooks](https://github.com/apiaryio/dredd-hooks-ruby) can be used as examples of how to use this cross-language test suite.
5754
58-
## Examples
55+
## Keep Tests Running with CI
5956
60-
The [Dredd Hooks Ruby gem][ruby] and the [Dredd Hooks Python package][python] can be used as references to use this cross-language test suite.
57+
To make sure the hooks handler will always work correctly with Dredd and the expectations won't get accidentally broken, put the tests into [Travis CI](https://travis-ci.org), which runs the tests for each change on your repository. See existing configuration files for inspiration:
6158
62-
[ruby]: https://github.com/apiaryio/dredd-hooks-ruby
63-
[python]: https://github.com/apiaryio/dredd-hooks-python
59+
- Python: [.travis.yml](https://github.com/apiaryio/dredd-hooks-python/blob/master/.travis.yml)
60+
- Ruby: [.travis.yml](https://github.com/apiaryio/dredd-hooks-ruby/blob/master/.travis.yml)

cli/copyFeatures.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
const fs = require('fs');
2+
const path = require('path');
3+
const glob = require('glob');
4+
5+
6+
/**
7+
* Copies all '*.feature' files from the 'srcDir' directory to the 'dstDir'
8+
* directory, allowing for a transformation of the file basename and
9+
* the content on the way using the 'transform()' function
10+
*/
11+
module.exports = function copyFeatures(srcDir, dstDir, transform) {
12+
glob.sync(path.join(srcDir, '*.feature')).forEach((featureSrcPath) => {
13+
const featureSrcContent = fs.readFileSync(featureSrcPath, { encoding: 'utf-8' });
14+
const {
15+
basename: featureBasename,
16+
content: featureContent,
17+
} = transform({
18+
basename: path.basename(featureSrcPath),
19+
content: featureSrcContent,
20+
});
21+
const featurePath = path.join(dstDir, featureBasename);
22+
fs.writeFileSync(featurePath, featureContent, { encoding: 'utf-8' });
23+
});
24+
}

cli/index.js

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#!/usr/bin/env node
2+
3+
const fs = require('fs-extra');
4+
const path = require('path');
5+
6+
const run = require('./run');
7+
const copyFeatures = require('./copyFeatures');
8+
const replaceDredd = require('./replaceDredd');
9+
10+
11+
PROJECT_DIR = process.cwd();
12+
NODE_MODULES_DIR = path.join(PROJECT_DIR, 'node_modules');
13+
14+
FEATURES_SRC_DIR = path.join(NODE_MODULES_DIR, 'dredd-hooks-template', 'features');
15+
FEATURES_DIR = path.join(PROJECT_DIR, 'features');
16+
STEPS_DIR = path.join(FEATURES_SRC_DIR, 'support');
17+
18+
NODE_BIN = process.execPath;
19+
DREDD_BIN = path.join(NODE_MODULES_DIR, '.bin', 'dredd');
20+
CUCUMBER_BIN = path.join(NODE_MODULES_DIR, '.bin', 'cucumber-js');
21+
22+
23+
function init() {
24+
// create a 'features' directory in the project we're initializing
25+
fs.mkdirSync(FEATURES_DIR);
26+
27+
// copy '*.feature' files from the template to the project, process each
28+
// file so it references the right Dredd executable
29+
copyFeatures(FEATURES_SRC_DIR, FEATURES_DIR, ({ basename, content }) => (
30+
{
31+
basename,
32+
content: replaceDredd(content, DREDD_BIN),
33+
}
34+
));
35+
}
36+
37+
38+
function test() {
39+
// use cucumber executable installed with the template package and run it
40+
// with the 'features' directory in the project (also use steps from
41+
// the template package)
42+
run(NODE_BIN, [CUCUMBER_BIN, FEATURES_DIR, '--require', STEPS_DIR], { cwd: PROJECT_DIR });
43+
}
44+
45+
46+
function upgrade() {
47+
// read the project's package.json and get currently installed version
48+
// of the 'dredd-hooks-template' package
49+
const packageDataPath = path.join(PROJECT_DIR, 'package.json');
50+
const packageData = JSON.parse(fs.readFileSync(packageDataPath, { encoding: 'utf-8' }));
51+
const currentVersion = packageData.devDependencies['dredd-hooks-template'];
52+
53+
// ask npm about the latest published version of the 'dredd-hooks-template'
54+
// package
55+
const proc = run('npm', ['view', 'dredd-hooks-template', 'version'], { cwd: PROJECT_DIR });
56+
const version = proc.stdout.toString().trim();
57+
58+
// halt in case the project already depends on the latest version
59+
if (currentVersion === version) {
60+
console.log(`The test suite template is up to date!`);
61+
return;
62+
}
63+
64+
// upgrade the package
65+
const package = `dredd-hooks-template@${version}`;
66+
run('npm', ['install', package, '--save-dev'], { cwd: PROJECT_DIR });
67+
68+
// copy '*.feature' files from the upgraded 'dredd-hooks-template' package
69+
// to the project, but don't overwrite the existing feature files, add these
70+
// as new ones, suffixed with the 'dredd-hooks-template' version
71+
copyFeatures(FEATURES_SRC_DIR, FEATURES_DIR, ({ basename, content }) => (
72+
{
73+
basename: `${basename}~${version}`,
74+
content: replaceDredd(content, DREDD_BIN),
75+
}
76+
));
77+
}
78+
79+
80+
// poor man's CLI implementation (it's just three commands, so not worth
81+
// a dedicated library)
82+
if (process.argv.length > 3) {
83+
process.exitCode = 1;
84+
console.error('Wrong number of arguments');
85+
} else {
86+
const command = process.argv[2];
87+
if (command === 'init') {
88+
init();
89+
} else if (command === 'test') {
90+
test();
91+
} else if (command === 'upgrade') {
92+
upgrade();
93+
} else {
94+
process.exitCode = 1;
95+
console.error('Available commands: init, test, upgrade');
96+
console.error('See https://github.com/apiaryio/dredd-hooks-template README')
97+
}
98+
}

cli/replaceDredd.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* Replaces mentions of the 'dredd' command in given '*.feature' file
3+
* content with a specific path to Dredd executable
4+
*/
5+
module.exports = function replaceDredd(text, dreddBin) {
6+
return text
7+
.replace(/I have "dredd"/g, `I have "${dreddBin}"`)
8+
.replace(/I run `dredd /g, `I run \`${dreddBin} `);
9+
};

cli/run.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
const { spawnSync } = require('child_process');
2+
3+
4+
/**
5+
* Like 'spawnSync()', but prints the stdout/stderr of the subprocess
6+
* by default and throws in case the command failed
7+
*/
8+
module.exports = function run(command, args, options) {
9+
const proc = spawnSync(command, args, { ...options, stdio: 'inherit' });
10+
if (proc.error) throw error;
11+
if (proc.status) throw new Error(`'${[command].concat(args).join(' ')}' failed`);
12+
return proc;
13+
};

0 commit comments

Comments
 (0)