diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md deleted file mode 100644 index a65b26b96..000000000 --- a/.github/CONTRIBUTING.md +++ /dev/null @@ -1,345 +0,0 @@ -# Contributing Guidelines - -## Quick Start - -### Ideas - -- File an [issue][issues]. -- Explain why you want the feature. How does it help you? What for do you want the feature? - -### Bugs - -- File an [issue][issues]. -- Ideally, write a failing test and send it as a Pull Request. - -### Coding - -- Dredd is written in JavaScript [ES2015+](https://tc39.github.io/ecma262/). -- Dredd uses [Semantic Release and Conventional Changelog](#sem-rel). - -#### Recommended Workflow - -1. Fork Dredd. -2. Create a feature branch. -3. Write tests. -4. Write code. -5. Lint what you created: `npm run lint` -6. Send a Pull Request. -7. Make sure [test coverage][] didn't drop and all CI builds are passing. - - -#### Semantic Release and Conventional Changelog - -Releasing of new Dredd versions to npm is automatically managed by [Semantic Release][]. -Semantic Release makes sure correct version numbers get bumped according to the **meaning** -of your changes once your PR gets merged to `master`. - -To make it work, it's necessary to follow [Conventional Changelog][]. That basically -means all commit messages in the project should follow a particular format: - -``` -: -``` - -Where `` is: - -- `feat` - New functionality added -- `fix` - Broken functionality fixed -- `perf` - Performance improved -- `docs` - Documentation added/removed/improved/... -- `chore` - Package setup, CI setup, ... -- `refactor` - Changes in code, but no changes in behavior -- `test` - Tests added/removed/improved/... - -In the rare cases when your changes break backwards compatibility, the message -must include string `BREAKING CHANGE:`. That will result in bumping the major version. - -Seems hard? - -- See [existing commits][] as a reference -- [Commitizen CLI][] can help you to create correct commit messages -- `npm run lint` validates format of your messages - -## Handbook for Contributors and Maintainers - -### Maintainers - -[Apiary][] is the main author and maintainer of Dredd's [upstream repository][]. -Currently responsible people are: - -- [@netmilk](https://github.com/netmilk) - product decisions, feature requests -- [@honzajavorek](https://github.com/honzajavorek) - lead of development -- [@michalholasek](https://github.com/michalholasek) - team member - -### Programming Language - -Dredd is written in [JavaScript (ES2015+)][] and is meant -to be ran on server using Node.js. Before publishing to the npm registry, it is -compiled to plain ES5 JavaScript code (throwaway `lib` directory). - -Tests need pre-compiled every time because some integration tests use code -linked from `lib`. This is certainly a flaw and it slows down day-to-day development, -but untill we streamline our build pipeline, the `lib` dependency is necessary. - -Also mind that [CoffeeScript][] is production dependency (not dev dependency), -because it's needed for running user-provided hooks written in CoffeeScript. - -### Compiled vs pure JavaScript - -Dredd uses [Drafter][] for parsing [API Blueprint][] documents. Drafter is written in C++11 and needs to be compiled during installation. Because that can cause a lot of problems in some environments, there's also pure JavaScript version of the parser, [drafter.js][]. Drafter.js is fully equivalent, but it can have slower performance. Therefore there's [drafter-npm][] package, which tries to compile the C++11 version of the parser and uses the JavaScript equivalent in case of failure. - -Dredd depends on the [drafter-npm][] package. That's the reason why you can see `node-gyp` errors and failures during the installation process, even though when it's done, Dredd seems to normally work and correctly parses API Blueprint documents. - -#### Forcing the JavaScript version - -The `--no-optional` option forces the JavaScript version of Drafter and avoids any compilation attempts when installing Dredd: - -```sh -$ npm install -g dredd --no-optional -``` - -#### Troubleshooting the compilation - -If you need the performance of the C++11 parser, but you are struggling to get it installed, it's usually because of the following problems: - -- **Your machine is missing a C++11 compiler.** See how to fix this on [Windows][Windows C++11] or [Travis CI][Travis CI C++11]. -- **npm was used with Python 3.** `node-gyp`, which performs the compilation, doesn't support Python 3. If your default Python is 3 (see `python --version`), [tell npm to use an older version][npm Python]. - -### Supported Node.js Versions - -Given the [table with LTS schedule](https://github.com/nodejs/Release), only versions marked as **Maintenance** or **Active** are supported, until their **Maintenance End**. The testing matrix of Dredd's CI builds must contain all currently supported versions and must not contain any unsupported versions. The same applies for the underlying libraries, such as [Dredd Transactions][] or [Gavel.js][]. - -In following files the latest supported Node.js version should be used: - -- `appveyor.yml` - Windows CI builds -- `docs/install-node.sh` - ReadTheDocs docs builds - -### Dependencies - -New versions of dependencies are monitored by [David][] and/or [Greenkeeper][]. Security issues are monitored by [Snyk][]. - -Dependencies should not be specified in a loose way - only exact versions are allowed. Any changes to dependencies (version upgrades included) must be approved by Oracle before merged to `master`. Dredd maintainers take care of the approval. For transparency, PRs with pending dependency approval are labeled respectively. - -The internal Oracle policies about dependencies pay attention mainly to licenses. Before adding a new dependency or upgrading an existing one try to [make sure](https://github.com/davglass/license-checker) the project and all its transitive dependencies feature standard permissive licenses, including correct copyright holders and license texts. - -### Versioning - -Dredd follows [Semantic Versioning][]. To ensure certain stability of Dredd installations (e.g. in CI builds), users can pin their version. They can also use release tags: - -- `npm install dredd` - Installs the latest published version including experimental pre-release versions. -- `npm install dredd@stable` - Skips experimental pre-release versions. - -When releasing, make sure you respect the tagging: - -- To release pre-release, e.g. `42.1.0-pre.7`, use just `npm publish`. -- To release any other version, e.g. `42.1.0`, use `npm publish && npm dist-tag add dredd@42.1.0 stable`. - -Releasing process for standard versions is currently automated by [Semantic Release][]. Releasing process for pre-releases is not automated and needs to be done manually, ideally from a special git branch. - -### Testing - -Use `npm test` to run all tests. Dredd uses [Mocha][] as a test framework. -It's default options are in the `test/mocha.opts` file. - -### Windows - -Dredd is tested on the [AppVeyor][], a Windows-based CI. There are still [several known limitations][windows issues] when using Dredd on Windows, but the intention is to support it without any compromises. Any help with fixing problems on Windows is greatly appreciated! - -### Linting - -Dredd uses [eslint][] to lint the JavaScript codebase. We are using [Airbnb's styleguide](https://github.com/airbnb/javascript) -rules as a baseline with several rules disabled to allow us to have dirty -post-decaffeinate code temporarily. - -Linter is optional for local development to make easy prototyping and work -with unpolished code, but it's enforced on CI level. It is recommended you -integrate [eslint][] with your favorite editor so you see violations -immediately during coding. - -### Changelog - -Changelog is in form of [GitHub Releases][]. Currently it's automatically -generated by [Semantic Release][]. See [above](#sem-rel) to learn -about how it works. - -### Documentation - -Dredd's documentation is written in [Markdown][] using [Sphinx][]. [ReadTheDocs][] is used to build and publish the documentation: - -- [https://dredd.readthedocs.io](https://dredd.readthedocs.io) - preferred long URL -- [https://dredd.rtfd.io](https://dredd.rtfd.io) - preferred short URL - -Source of the documentation can be found in the [docs][] directory. To render Dredd's documentation on your computer, you need Python 3 and Node.js installed. - -#### Installation and Development - -1. Make sure `node` is an executable and `npm install` has been done for the Dredd directory. Extensions to the docs are written in Node.js and Sphinx needs to have a way to execute them. -2. [Get Python 3](https://www.python.org/downloads/). On macOS, run `brew install python3`. [ReadTheDocs][] build the docs with Python 3.5, so make sure you have that or higher. -3. Create a [virtual environment](https://docs.python.org/3/library/venv.html) and activate it: - - ```sh - python3 -m venv ./venv - . ./env/bin/activate - ``` - -4. Install dependencies for the docs: `pip install -r docs/requirements.txt` - -Once installed, you may use following commands: - -- `npm run docs:build` - Builds the documentation -- `npm run docs:serve` - Runs live preview of the documentation on `http://127.0.0.1:8000` - -#### Installation on ReadTheDocs - -The final documentation gets deployed on the [ReadTheDocs][]. The service, however, does not support Node.js. Therefore on ReadTheDocs, the `conf.py` configuration file for Sphinx runs `docs/install-node.sh`, which installs Node.js locally, using [nvm][]. - -#### ToC and Markdown - -Traditionally, Sphinx only supported the [reStructuredText][] format. Thanks to the [recommonmark][] project it's possible to use also [Markdown][], _almost_ as a format native to Sphinx. Dredd's docs are using the [AutoStructify][] extension to be able to specify _toctree_ and other stuff specific to reStructuredText. The ToC is generated from the _Contents_ section in the `docs/index.md` file. - -[recommonmark]: https://github.com/rtfd/recommonmark -[AutoStructify]: https://recommonmark.readthedocs.io/en/latest/auto_structify.html - -#### Node.js Extensions - -There are some extensions hooked into the build process of [Sphinx][], modifying how the documents are processed. They're written in Node.js, because: - -- It's better to have them in the same language as Dredd. -- This way they're able to import source files (e.g. `src/options.js`). - - - -By default, [Hercule][] is attached as an extension, which means you can use the :\[Title](link.md) syntax for including other Markdown files. All other extensions are custom and are automatically loaded from the `docs/_extensions` directory. - -The extension is expected to be a `.js` or `.coffee` script file, which takes `docname` as an argument, reads the Markdown document from `stdin`, modifies it, and then prints it to `stdout`. When in need of templating, extensions are expected to use the bundled `ect` templating engine. - -[Hercule]: https://www.npmjs.com/package/hercule - -#### Local References - -Currently the [recommonmark][] project has still some limitations in how references to local files work. That's why Dredd's docs have a custom implementation, which also checks whether the destination exists and fails the build in case of broken link. You can use following syntax: - -- `[Title](link.md)` to link to other documents -- `[Title](link.md#section)` to link to sections of other documents - -Any `id` HTML attributes generated for headings or manual `` anchors are considered as valid targets. While this feels very natural for a seasoned writer of Markdown, mind that it is much more error prone then [reStructuredText][]'s references. - -#### Redirects - -Redirects are documented in the `docs/redirects.yml` file. They need to be manually set in the [ReadTheDocs administration](https://readthedocs.org/dashboard/dredd/redirects/). It's up to Dredd maintainers to keep the list in sync with reality. - -You can use the [rtd-redirects](https://github.com/honzajavorek/rtd-redirects) tool to programmatically upload the redirects from `docs/redirects.yml` to ReadTheDocs admin interface. - -#### Symlinked Contributing Docs - -The `docs/contributing.md` file is a [symbolic link][] to the -`.github/CONTRIBUTING.md` file, where the actual content lives. -This is to be able to serve the same content also as -[GitHub contributing guidelines][] when someone opens a Pull Request. - -[symbolic link]: https://en.wikipedia.org/wiki/Symbolic_link -[GitHub contributing guidelines]: https://blog.github.com/2012-09-17-contributing-guidelines/ - -### Coverage - -Dredd strives for as much test coverage as possible. [Coveralls][] help us to -monitor how successful we are in achieving the goal. If a Pull Request -introduces drop in coverage, it won't be accepted unless the author or reviewer -provides a good reason why an exception should be made. - -The Travis CI build uses following commands to deliver coverage reports: - -- `npm run test:coverage` - Tests Dredd and creates the `./coverage/lcov.info` file -- `npm run coveralls` - Uploads the `./coverage/lcov.info` file to Coveralls - -The first mentioned command goes like this: - -1. [istanbul][] is used to instrument and cover the JavaScript code. -2. We run the tests on the instrumented code using Mocha with a special lcov reporter, - which gives us information about which lines were executed in a standard lcov format. -3. Because some integration tests execute the `bin/dredd` script in - a subprocess, we collect the coverage stats also in this file. The results - are appended to a dedicated lcov file. -4. All lcov files are then merged into one using [lcov-result-merger][] - and sent to Coveralls. - -#### Notes - -- Hand-made combined Mocha reporter is used to achieve running tests and collecting - coverage at the same time. -- Both Dredd code and the combined reporter decide whether to collect coverage - or not according to contents of the `COVERAGE_DIR` environment variable, which - sets the directory for temporary LCOV files created during coverage collection. - (If set, collecting takes place.) - - -### Hacking Apiary Reporter - -If you want to build something on top of the Apiary Reporter, note that it uses a public API described in following documents: - -- [Apiary Tests API for anonymous test reports][] -- [Apiary Tests API for authenticated test reports][] - -Following data are sent over the wire to Apiary: - -- [Apiary Reporter Test Data](data-structures.md#apiary-reporter-test-data) - -There is also one environment variable you could find useful: - -- `APIARY_API_URL='https://api.apiary.io'` - Allows to override host of the Apiary Tests API. - -### Misc Tips - -- When using long CLI options in tests or documentation, please always use the notation with `=` - wherever possible. For example, use `--path=/dev/null`, not `--path /dev/null`. - While both should work, the version with `=` feels - more like standard GNU-style long options and it makes arrays of arguments for `spawn` more readable. -- Using `127.0.0.1` (in code, tests, documentation) is preferred over `localhost` (see [#586](https://github.com/apiaryio/dredd/issues/586)). -- Prefer explicit `
` tags instead of [two spaces][md-two-spaces] at the end of the line when writing documentation in Markdown. - - -[Apiary]: https://apiary.io/ -[Dredd Transactions]: https://github.com/apiaryio/dredd-transactions -[Gavel.js]: https://github.com/apiaryio/gavel.js/ - -[Semantic Versioning]: https://semver.org/ -[JavaScript (ES2015+)]: https://tc39.github.io/ecma262/ -[eslint]: https://eslint.org/ -[CoffeeScript]: https://coffeescript.org -[Coveralls]: https://coveralls.io/github/apiaryio/dredd -[istanbul]: https://github.com/gotwarlost/istanbul -[lcov-result-merger]: https://github.com/mweibel/lcov-result-merger -[Markdown]: https://en.wikipedia.org/wiki/Markdown -[Sphinx]: http://www.sphinx-doc.org/ -[ReadTheDocs]: https://readthedocs.org/ -[test coverage]: https://coveralls.io/github/apiaryio/dredd -[Mocha]: https://mochajs.org/ -[Semantic Release]: https://github.com/semantic-release/semantic-release -[Conventional Changelog]: https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#user-content--git-commit-guidelines -[Commitizen CLI]: https://github.com/commitizen/cz-cli -[md-two-spaces]: https://daringfireball.net/projects/markdown/syntax#p -[AppVeyor]: https://www.appveyor.com/ -[nvm]: https://github.com/creationix/nvm -[reStructuredText]: http://www.sphinx-doc.org/en/stable/rest.html -[David]: https://david-dm.org/apiaryio/dredd -[Greenkeeper]: https://greenkeeper.io/ -[Snyk]: https://snyk.io/test/npm/dredd - -[Drafter]: https://github.com/apiaryio/drafter -[API Blueprint]: https://apiblueprint.org/ -[drafter.js]: https://github.com/apiaryio/drafter.js -[drafter-npm]: https://github.com/apiaryio/drafter-npm/ -[Windows C++11]: https://github.com/apiaryio/drafter/wiki/Building-on-Windows -[Travis CI C++11]: https://github.com/apiaryio/protagonist/blob/master/.travis.yml -[npm Python]: http://stackoverflow.com/a/22433804/325365 - -[existing commits]: https://github.com/apiaryio/dredd/commits/master -[docs]: https://github.com/apiaryio/dredd/tree/master/docs -[GitHub Releases]: https://github.com/apiaryio/dredd/releases - -[upstream repository]: https://github.com/apiaryio/dredd -[issues]: https://github.com/apiaryio/dredd/issues -[windows issues]: https://github.com/apiaryio/dredd/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aopen%20label%3AWindows%20 - -[Apiary Tests API for anonymous test reports]: https://github.com/apiaryio/dredd/blob/master/ApiaryReportingApiAnonymous.apib -[Apiary Tests API for authenticated test reports]: https://github.com/apiaryio/dredd/blob/master/ApiaryReportingApi.apib diff --git a/.github/CONTRIBUTING.rst b/.github/CONTRIBUTING.rst new file mode 100644 index 000000000..6f7c4765d --- /dev/null +++ b/.github/CONTRIBUTING.rst @@ -0,0 +1,290 @@ +.. _contributing: + +Contributing Guidelines +======================= + +Quick Start +----------- + +Ideas +~~~~~ + +- File an `issue `__. +- Explain why you want the feature. How does it help you? What for do you want the feature? + +Bugs +~~~~ + +- File an `issue `__. +- Ideally, write a failing test and send it as a Pull Request. + +Coding +~~~~~~ + +- Dredd is written in JavaScript `ES2015+ `__. +- Dredd uses :ref:`sem-rel`. + +Recommended Workflow +^^^^^^^^^^^^^^^^^^^^ + +1. Fork Dredd. +2. Create a feature branch. +3. Write tests. +4. Write code. +5. Lint what you created: ``npm run lint`` +6. Send a Pull Request. +7. Make sure `test coverage `__ didn’t drop and all CI builds are passing. + +.. _sem-rel: + +Semantic Release and Conventional Changelog +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Releasing of new Dredd versions to npm is automatically managed by `Semantic Release `__. Semantic Release makes sure correct version numbers get bumped according to the **meaning** of your changes once your PR gets merged to ``master``. + +To make it work, it’s necessary to follow `Conventional Changelog `__. That basically means all commit messages in the project should follow a particular format:: + + : + +Where ```` is: + +- ``feat`` - New functionality added +- ``fix`` - Broken functionality fixed +- ``perf`` - Performance improved +- ``docs`` - Documentation added/removed/improved/… +- ``chore`` - Package setup, CI setup, … +- ``refactor`` - Changes in code, but no changes in behavior +- ``test`` - Tests added/removed/improved/… + +In the rare cases when your changes break backwards compatibility, the message must include string ``BREAKING CHANGE:``. That will result in bumping the major version. + +Seems hard? + +- See `existing commits `__ as a reference +- `Commitizen CLI `__ can help you to create correct commit messages +- ``npm run lint`` validates format of your messages + +Handbook for Contributors and Maintainers +----------------------------------------- + +Maintainers +~~~~~~~~~~~ + +`Apiary `__ is the main author and maintainer of Dredd’s `upstream repository `__. Currently responsible people are: + +- [@netmilk](https://github.com/netmilk) - product decisions, feature requests +- [@honzajavorek](https://github.com/honzajavorek) - lead of development +- [@michalholasek](https://github.com/michalholasek) - team member + +Programming Language +~~~~~~~~~~~~~~~~~~~~ + +Dredd is written in `JavaScript (ES2015+) `__ and is meant to be ran on server using Node.js. Before publishing to the npm registry, it is compiled to plain ES5 JavaScript code (throwaway ``lib`` directory). + +Tests need pre-compiled every time because some integration tests use code linked from ``lib``. This is certainly a flaw and it slows down day-to-day development, but until we streamline our build pipeline, the ``lib`` dependency is necessary. + +Also mind that `CoffeeScript `__ is production dependency (not dev dependency), because it’s needed for running user-provided hooks written in CoffeeScript. + +.. _compiled-vs-pure-javascript: + +Compiled vs pure JavaScript +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Dredd uses `Drafter `__ for parsing `API Blueprint `__ documents. Drafter is written in C++11 and needs to be compiled during installation. Because that can cause a lot of problems in some environments, there’s also pure JavaScript version of the parser, `drafter.js `__. Drafter.js is fully equivalent, but it can have slower performance. Therefore there’s `drafter-npm `__ package, which tries to compile the C++11 version of the parser and uses the JavaScript equivalent in case of failure. + +Dredd depends on the `drafter-npm `__ package. That’s the reason why you can see ``node-gyp`` errors and failures during the installation process, even though when it’s done, Dredd seems to normally work and correctly parses API Blueprint documents. + +Forcing the JavaScript version +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The ``--no-optional`` option forces the JavaScript version of Drafter and avoids any compilation attempts when installing Dredd: + +.. code-block:: shell + + $ npm install -g dredd --no-optional + +Troubleshooting the compilation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If you need the performance of the C++11 parser, but you are struggling to get it installed, it’s usually because of the following problems: + +- **Your machine is missing a C++11 compiler.** See how to fix this on `Windows `__ or `Travis CI `__. +- **npm was used with Python 3.** ``node-gyp``, which performs the compilation, doesn’t support Python 3. If your default Python is 3 (see ``python --version``), `tell npm to use an older version `__. + +Supported Node.js Versions +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Given the `table with LTS schedule `__, only versions marked as **Maintenance** or **Active** are supported, until their **Maintenance End**. The testing matrix of Dredd’s CI builds must contain all currently supported versions and must not contain any unsupported versions. The same applies for the underlying libraries, such as `Dredd Transactions `__ or `Gavel.js `__. + +In following files the latest supported Node.js version should be used: + +- ``appveyor.yml`` - Windows CI builds +- ``docs/install-node.sh`` - ReadTheDocs docs builds + +Dependencies +~~~~~~~~~~~~ + +New versions of dependencies are monitored by `David `__ and/or `Greenkeeper `__. Security issues are monitored by `Snyk `__. + +Dependencies should not be specified in a loose way - only exact versions are allowed. Any changes to dependencies (version upgrades included) must be approved by Oracle before merged to ``master``. Dredd maintainers take care of the approval. For transparency, PRs with pending dependency approval are labeled respectively. + +The internal Oracle policies about dependencies pay attention mainly to licenses. Before adding a new dependency or upgrading an existing one try to `make sure `__ the project and all its transitive dependencies feature standard permissive licenses, including correct copyright holders and license texts. + +Versioning +~~~~~~~~~~ + +Dredd follows `Semantic Versioning `__. To ensure certain stability of Dredd installations (e.g. in CI builds), users can pin their version. They can also use release tags: + +- ``npm install dredd`` - Installs the latest published version including experimental pre-release versions. +- ``npm install dredd@stable`` - Skips experimental pre-release versions. + +When releasing, make sure you respect the tagging: + +- To release pre-release, e.g. ``42.1.0-pre.7``, use just ``npm publish``. +- To release any other version, e.g. ``42.1.0``, use ``npm publish && npm dist-tag add dredd@42.1.0 stable``. + +Releasing process for standard versions is currently automated by `Semantic Release `__. Releasing process for pre-releases is not automated and needs to be done manually, ideally from a special git branch. + +Testing +~~~~~~~ + +Use ``npm test`` to run all tests. Dredd uses `Mocha `__ as a test framework. It’s default options are in the ``test/mocha.opts`` file. + +Windows +~~~~~~~ + +Dredd is tested on the `AppVeyor `__, a Windows-based CI. There are still `several known limitations `__ when using Dredd on Windows, but the intention is to support it without any compromises. Any help with fixing problems on Windows is greatly appreciated! + +Linting +~~~~~~~ + +Dredd uses `eslint `__ to lint the JavaScript codebase. We are using `Airbnb’s styleguide `__ rules as a baseline with several rules disabled to allow us to have dirty post-decaffeinate code temporarily. + +Linter is optional for local development to make easy prototyping and work with unpolished code, but it’s enforced on CI level. It is recommended you integrate `eslint `__ with your favorite editor so you see violations immediately during coding. + +Changelog +~~~~~~~~~ + +Changelog is in form of `GitHub Releases `__. Currently it’s automatically generated by `Semantic Release `__. See `above <#sem-rel>`__ to learn about how it works. + +Documentation +~~~~~~~~~~~~~ + +Dredd’s documentation is written in `Markdown `__ using `Sphinx `__. `ReadTheDocs `__ is used to build and publish the documentation: + +- https://dredd.readthedocs.io - preferred long URL +- https://dredd.rtfd.io - preferred short URL + +Source of the documentation can be found in the `docs `__ directory. To render Dredd’s documentation on your computer, you need Python 3 and Node.js installed. + +Installation and Development +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +1. Make sure ``node`` is an executable and ``npm install`` has been done for the Dredd directory. Extensions to the docs are written in Node.js and Sphinx needs to have a way to execute them. +2. `Get Python 3 `__. On macOS, run ``brew install python3``. `ReadTheDocs `__ build the docs with Python 3.5, so make sure you have that or higher. +3. Create a `virtual environment `__ and activate it: + + .. code-block:: shell + + python3 -m venv ./venv + . ./env/bin/activate + +4. Install dependencies for the docs: ``pip install -r docs/requirements.txt`` + +Once installed, you may use following commands: + +- ``npm run docs:build`` - Builds the documentation +- ``npm run docs:serve`` - Runs live preview of the documentation on ``http://127.0.0.1:8000`` + +Installation on ReadTheDocs +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The final documentation gets deployed on the `ReadTheDocs `__. The service, however, does not support Node.js. Therefore on ReadTheDocs, the ``conf.py`` configuration file for Sphinx runs ``docs/install-node.sh``, which installs Node.js locally, using `nvm `__. + +ToC and Markdown +^^^^^^^^^^^^^^^^ + +Traditionally, Sphinx only supported the `reStructuredText `__ format. Thanks to the `recommonmark `__ project it’s possible to use also `Markdown `__, *almost* as a format native to Sphinx. Dredd’s docs are using the `AutoStructify `__ extension to be able to specify *toctree* and other stuff specific to reStructuredText. The ToC is generated from the *Contents* section in the ``docs/index.md`` file. + +Node.js Extensions +^^^^^^^^^^^^^^^^^^ + +There are some extensions hooked into the build process of `Sphinx `__, modifying how the documents are processed. They’re written in Node.js, because: + +- It’s better to have them in the same language as Dredd. +- This way they’re able to import source files (e.g. ``src/options.js``). + +By default, `Hercule `__ is attached as an extension, which means you can use the ``:[Title](link.md)`` syntax for including other Markdown files. All other extensions are custom and are automatically loaded from the ``docs/_extensions`` directory. + +The extension is expected to be a ``.js`` or ``.coffee`` script file, which takes ``docname`` as an argument, reads the Markdown document from ``stdin``, modifies it, and then prints it to ``stdout``. When in need of templating, extensions are expected to use the bundled ``ect`` templating engine. + +Local References +^^^^^^^^^^^^^^^^ + +Currently the `recommonmark `__ project has still some limitations in how references to local files work. That’s why Dredd’s docs have a custom implementation, which also checks whether the destination exists and fails the build in case of broken link. You can use following syntax: + +- ``[Title](link.md)`` to link to other documents +- ``[Title](link.md#section)`` to link to sections of other documents + +Any ``id`` HTML attributes generated for headings or manual ```` anchors are considered as valid targets. While this feels very natural for a seasoned writer of Markdown, mind that it is much more error prone then `reStructuredText `__\ ’s references. + +Redirects +^^^^^^^^^ + +Redirects are documented in the ``docs/redirects.yml`` file. They need to be manually set in the `ReadTheDocs administration `__. It’s up to Dredd maintainers to keep the list in sync with reality. + +You can use the `rtd-redirects `__ tool to programmatically upload the redirects from ``docs/redirects.yml`` to ReadTheDocs admin interface. + +Symlinked Contributing Docs +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The ``docs/contributing.md`` file is a `symbolic link `__ to the ``.github/CONTRIBUTING.md`` file, where the actual content lives. This is to be able to serve the same content also as `GitHub contributing guidelines `__ when someone opens a Pull Request. + +Coverage +~~~~~~~~ + +Dredd strives for as much test coverage as possible. `Coveralls `__ help us to monitor how successful we are in achieving the goal. If a Pull Request introduces drop in coverage, it won’t be accepted unless the author or reviewer provides a good reason why an exception should be made. + +The Travis CI build uses following commands to deliver coverage reports: + +- ``npm run test:coverage`` - Tests Dredd and creates the ``./coverage/lcov.info`` file +- ``npm run coveralls`` - Uploads the ``./coverage/lcov.info`` file to Coveralls + +The first mentioned command goes like this: + +1. `istanbul `__ is used to instrument and cover the JavaScript code. +2. We run the tests on the instrumented code using Mocha with a special lcov reporter, which gives us information about which lines were executed in a standard lcov format. +3. Because some integration tests execute the ``bin/dredd`` script in a subprocess, we collect the coverage stats also in this file. The results are appended to a dedicated lcov file. +4. All lcov files are then merged into one using `lcov-result-merger `__ and sent to Coveralls. + +Notes +^^^^^ + +- Hand-made combined Mocha reporter is used to achieve running tests and collecting coverage at the same time. +- Both Dredd code and the combined reporter decide whether to collect coverage or not according to contents of the ``COVERAGE_DIR`` environment variable, which sets the directory for temporary LCOV files created during coverage collection. (If set, collecting takes place.) + +.. _hacking-apiary-reporter: + +Hacking Apiary Reporter +~~~~~~~~~~~~~~~~~~~~~~~ + +If you want to build something on top of the Apiary Reporter, note that +it uses a public API described in following documents: + +- `Apiary Tests API for anonymous test reports `__ +- `Apiary Tests API for authenticated test reports `__ + +Following data are sent over the wire to Apiary: + +- :ref:`Apiary Reporter Test Data ` + +There is also one environment variable you could find useful: + +- ``APIARY_API_URL='https://api.apiary.io'`` - Allows to override host of the Apiary Tests API. + +Misc Tips +~~~~~~~~~ + +- When using long CLI options in tests or documentation, please always use the notation with ``=`` wherever possible. For example, use ``--path=/dev/null``, not ``--path /dev/null``. While both should work, the version with ``=`` feels more like standard GNU-style long options and it makes arrays of arguments for ``spawn`` more readable. +- Using ``127.0.0.1`` (in code, tests, documentation) is preferred over ``localhost`` (see `#586 `__). +- Prefer explicit ``
`` tags instead of `two spaces `__ at the end of the line when writing documentation in Markdown. diff --git a/.travis.yml b/.travis.yml index 900e85235..0dc34d2a8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,11 +3,8 @@ sudo: false dist: "trusty" language: "node_js" -addons: - apt: - packages: - - "python3" - - "python3-pip" +node_js: + - "10" cache: directories: - "node_modules" @@ -16,30 +13,28 @@ before_install: # so 'conventional-changelog-lint' could compare commits and lint them: marionebl/conventional-changelog-lint#7 - "git remote set-branches origin master && git fetch && git checkout master && git checkout -" - "npm -g install npm@6" - - "pip3 install --user -r docs/requirements.txt" -install: "npm install --no-optional" + - "pyenv global 3.6" +install: + - "npm install --no-optional" + - "pip install --user -r docs/requirements.txt" jobs: include: # stage 1, all following runs in parallel: - stage: "quality checks & tests" env: "JOB=quality_checks" - node_js: "10" script: "npm run lint && npm run docs:lint" - - node_js: "10" - env: "JOB=docs_build_dry_run" # why dry run? because production build happens directly on ReadTheDocs infrastructure + - env: "JOB=docs_build_dry_run" # why dry run? because production build happens directly on ReadTheDocs infrastructure script: "npm run docs:build" - - node_js: "6" - env: "JOB=node6" + - env: "JOB=node6" + node_js: "6" script: "npm run test:coverage && npm run coveralls" - - node_js: "8" - env: "JOB=node8" + - env: "JOB=node8" + node_js: "8" script: "npm run test:coverage && npm run coveralls" - - node_js: "10" - env: "JOB=node10" + - env: "JOB=node10" script: "npm run test:coverage && npm run coveralls" # stage 2 - stage: "semantic release" - node_js: "10" script: "npm run semantic-release || true" if: fork = false AND branch = master AND type = push diff --git a/doc8.ini b/doc8.ini new file mode 100644 index 000000000..02b48beed --- /dev/null +++ b/doc8.ini @@ -0,0 +1,6 @@ +[doc8] +ignore-path=docs/_build + +# Do not check line length. This project relies on writing in an editor with +# word wrap turned on. No explicit newlines, please. +ignore=D001 diff --git a/docs/_extensions/generate-cli-docs.coffee b/docs/_extensions/generate-cli-docs.coffee deleted file mode 100644 index 2d1cc0967..000000000 --- a/docs/_extensions/generate-cli-docs.coffee +++ /dev/null @@ -1,45 +0,0 @@ -# Generates CLI docs for Dredd. -# -# Purpose: -# Thanks to this we can be sure the CLI docs are always up-to-date. -# -# Usage: -# -# $ cat document-template.md | coffee generate-cli-docs.coffee > document.md - - -fs = require('fs') -path = require('path') -ect = require('ect') -clone = require('clone') - -options = require('../../src/options') - - -# Turn options into a sorted array -data = {options: []} - -for own name, attributes of options - option = clone(attributes) - option.description = option.description.trim() - option.name = name - data.options.push(option) - -data.options.sort((o1, o2) -> - switch - when o1.name < o2.name then -1 - when o1.name > o2.name then 1 - else 0 -) - - -# Process stdin -source = '' -process.stdin.on('data', (buffer) -> - source += buffer.toString() -) -process.stdin.on('end', -> - renderer = ect({root: {source}}) - rendered = renderer.render('source', data) - process.stdout.write(rendered) -) diff --git a/docs/conf.py b/docs/conf.py index 77c212662..2b53462a0 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,14 +1,11 @@ import os +import sys import re import json import subprocess import urllib.request -from docutils import nodes -from sphinx.util import console from sphinx.errors import SphinxError -from recommonmark.parser import CommonMarkParser -from recommonmark.transform import AutoStructify ########################################################################### @@ -20,18 +17,24 @@ # -- Environment ---------------------------------------------------------- +# Explicitly put the extensions directory to Python path +sys.path.append(os.path.abspath('extensions')) + +# Detect whether the build happens on ReadTheDocs IS_READTHEDOCS = os.environ.get('READTHEDOCS') == 'True' +# Specify paths docs_dir = os.path.dirname(__file__) project_dir = os.path.join(docs_dir, '..') node_modules_bin_dir = os.path.join(project_dir, 'node_modules', '.bin') +# Install all npm dependencies if on ReadTheDocs. This requires the latest +# ReadTheDocs build image, which supports Node.js out of the box. This is +# specified in the readthedocs.yml in the root of the project. if IS_READTHEDOCS: - installation_output = subprocess.getoutput('bash ' + os.path.join(docs_dir, 'install-node.sh')) - node_bin = installation_output.splitlines()[-1].strip() -else: - node_bin = 'node' + subprocess.check_call('npm install', cwd=project_dir, shell=True) +# Load package.json data with open(os.path.join(project_dir, 'package.json')) as f: package_json = json.load(f) @@ -42,13 +45,13 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ + 'cli_options', 'pygments_markdown_lexer', ] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: -source_suffix = '.md' -source_parsers = {'.md': CommonMarkParser} +source_suffix = '.rst' # The master document. master_doc = 'index' @@ -85,12 +88,7 @@ def get_release(): # Semantic Release wasn't able to determine a new version number, # either because of some error or because there are no changes which # would bump the version number. Stick to the latest released version. - if IS_READTHEDOCS: - npm_bin = node_bin.replace('/bin/node', '/bin/npm') - command = '{} {} view dredd version'.format(node_bin, npm_bin) - else: - command = 'npm view dredd version' - return subprocess.getoutput(command).strip() + return subprocess.getoutput('npm view dredd version').strip() # The full version, including alpha/beta/rc tags. release = get_release() @@ -116,10 +114,10 @@ def get_release(): # -- Options for HTML output ---------------------------------------------- -# The theme to use for HTML and HTML Help pages. See the documentation for +# The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. if IS_READTHEDOCS: - # equals to default RTD theme + # equals to the default RTD theme html_theme = 'default' else: # emulates the default RTD theme for local development @@ -168,200 +166,6 @@ def get_release(): ] -# -- Custom Extensions ---------------------------------------------------- - -def setup(app): - # Lets Hercule (https://www.npmjs.com/package/hercule) and Node.js scripts - # from the 'docs/_extensions' directory to process each document before - # it gets processed by Sphinx - init_js_extensions(app) - - # Fixing how references (local links) work with Markdown - app.connect('doctree-read', collect_ref_data) - app.connect('doctree-resolved', process_refs) - - # Better support for Markdown (see https://recommonmark.readthedocs.io/en/latest/auto_structify.html) - app.add_config_value('recommonmark_config', { - 'enable_eval_rst': True, - 'enable_auto_toc_tree': True, - 'auto_toc_tree_section': 'Contents', - }, True) - app.add_transform(AutoStructify) - - -# -- Markdown References -------------------------------------------------- - -def collect_ref_data(app, doctree): - """ - Finds all anchors and references (local links) within documents, - and saves them as meta data - """ - filename = doctree.attributes['source'].replace(docs_dir, '').lstrip('/') - docname = filename.replace('.md', '') - - anchors = [] - references = [] - - for node in doctree.traverse(nodes.raw): - if 'name=' in node.rawsource: - match = re.search(r'name="([^\"]+)', node.rawsource) - if match: - anchors.append(match.group(1)) - elif 'id=' in node.rawsource: - match = re.search(r'id="([^\"]+)', node.rawsource) - if match: - anchors.append(match.group(1)) - - for node in doctree.traverse(nodes.section): - for target in frozenset(node.attributes.get('ids', [])): - anchors.append(target) - - for node in doctree.traverse(nodes.reference): - uri = node.get('refuri') - if uri and not uri.startswith(('http://', 'https://')): - references.append(to_reference(uri, basedoc=docname)) - - app.env.metadata[docname]['anchors'] = anchors - app.env.metadata[docname]['references'] = references - -def process_refs(app, doctree, docname): - """ - Fixes all references (local links) within documents, breaks the build - if it finds any links to non-existent documents or anchors. - """ - for reference in app.env.metadata[docname]['references']: - referenced_docname, anchor = parse_reference(reference) - - if referenced_docname not in app.env.metadata: - message = "Document '{}' is referenced from '{}', but it could not be found" - raise SphinxError(message.format(referenced_docname, docname)) - - if anchor and anchor not in app.env.metadata[referenced_docname]['anchors']: - message = "Section '{}#{}' is referenced from '{}', but it could not be found" - raise SphinxError(message.format(referenced_docname, anchor, docname)) - - for node in doctree.traverse(nodes.reference): - uri = node.get('refuri') - if to_reference(uri, basedoc=docname) == reference: - node['refuri'] = to_uri(app, referenced_docname, anchor) - -def to_uri(app, docname, anchor=None): - uri = '' - - if IS_READTHEDOCS: - language = app.config.language or 'en' - version_name = os.environ.get('READTHEDOCS_VERSION') - uri = '/{}/{}'.format(language, version_name) - - uri += '/{}.html'.format(docname) - if anchor: - uri += '#{}'.format(anchor) - - return uri - -def to_reference(uri, basedoc=None): - """ - Helper function, compiles a 'reference' from given URI and base - document name - """ - if '#' in uri: - filename, anchor = uri.split('#', 1) - filename = filename or basedoc - else: - filename = uri or basedoc - anchor = None - - if not filename: - message = "For self references like '{}' you need to provide the 'basedoc' argument".format(uri) - raise ValueError(message) - - reference = os.path.splitext(filename.lstrip('/'))[0] - if anchor: - reference += '#' + anchor - return reference - -def parse_reference(reference): - """ - Helper function, parses a 'reference' to document name and anchor - """ - if '#' in reference: - docname, anchor = reference.split('#', 1) - else: - docname = reference - anchor = None - return docname, anchor - - -# -- JavaScript Extensions ------------------------------------------------ - -js_extensions_dir = os.path.join(docs_dir, '_extensions') -js_extensions = [] -js_interpreters = { - '.js': [node_bin], - '.coffee': [node_bin, os.path.join(node_modules_bin_dir, 'coffee')] -} - -def init_js_extensions(app): - """ - Looks up and registers the Node.js extensions - - Loads Node.js scripts from the 'docs/_extensions' directory, assigns - interpreters to them (node vs. coffee) and registers the 'run_js_extensions' - function. - """ - app.info(console.bold('initializing Node.js extensions... '), nonl=True) - for basename in sorted(os.listdir(js_extensions_dir)): - _, ext = os.path.splitext(basename) - - if ext in js_interpreters.keys(): - filename = os.path.join(js_extensions_dir, basename) - command = js_interpreters[ext] + [filename] - js_extensions.append((basename, command)) - - app.connect('source-read', run_js_extensions) - app.info('{} found'.format(len(js_extensions))) - app.verbose('JavaScript extensions: ' + ', '.join(dict(js_extensions).keys())) - -def run_js_extensions(app, docname, source_list): - """ - Lets all registered Node.js extensions to process given document source - - Executed for each document after the source gets read. Sequentially feeds - stdin of each registered Node.js with the source and continues with whatever - the extension sends to stdout. The extensions are provided with the document - name as the first CLI argument. - - Hercule (https://www.npmjs.com/package/hercule) is built-in as if it was - the first Node.js extension in the pipeline. - """ - source = source_list[0] - - app.verbose(console.bold("runnning JavaScript extension 'hercule'... ") + console.blue(docname)) - command = [node_bin, os.path.join(node_modules_bin_dir, 'hercule'), '--relative=' + docs_dir, '--stdin'] - source = run_extension('hercule', command, source) - - for basename, command in js_extensions: - app.verbose(console.bold("runnning JavaScript extension '{}'... ".format(basename)) + console.blue(docname)) - source = run_extension(basename, command + [docname], source) - - source_list[0] = source - -def run_extension(extension_name, command, source): - """ - Runs given command as a subprocess and lets it to modify given source - """ - proc = subprocess.Popen(command, stdout=subprocess.PIPE, stdin=subprocess.PIPE) - proc.stdin.write(source.encode('utf-8')) - proc.stdin.close() - - source = proc.stdout.read().decode('utf-8') - exit_status = proc.wait() - if exit_status: - message = "JavaScript extension '{}' finished with non-zero exit status: {}" - raise SphinxError(message.format(extension_name, exit_status)) - return source - - # -- Hacks ---------------------------------------------------------------- import sphinx.application diff --git a/docs/contributing.md b/docs/contributing.md deleted file mode 120000 index e6afed186..000000000 --- a/docs/contributing.md +++ /dev/null @@ -1 +0,0 @@ -../.github/CONTRIBUTING.md \ No newline at end of file diff --git a/docs/contributing.rst b/docs/contributing.rst new file mode 120000 index 000000000..43a190a8c --- /dev/null +++ b/docs/contributing.rst @@ -0,0 +1 @@ +../.github/CONTRIBUTING.rst \ No newline at end of file diff --git a/docs/data-structures.md b/docs/data-structures.md deleted file mode 100644 index 524d38aa2..000000000 --- a/docs/data-structures.md +++ /dev/null @@ -1,206 +0,0 @@ -# Data Structures - -Documentation of various data structures in both [Gavel.js][] and Dredd. [MSON notation](https://github.com/apiaryio/mson) is used to describe the data structures. - - -## Transaction (object) - -Transaction object is passed as a first argument to [hook functions](hooks.md) and is one of the main public interfaces in Dredd. - -- id: `GET (200) /greetings` - identifier for this transaction -- name: `./api-description.apib > My API > Greetings > Hello, world! > Retrieve Message > Example 2` (string) - reference to the transaction definition in the original API description document (see also [Dredd Transactions][]) -- origin (object) - reference to the transaction definition in the original API description document (see also [Dredd Transactions][]) - - filename: `./api-description.apib` (string) - - apiName: `My Api` (string) - - resourceGroupName: `Greetings` (string) - - resourceName: `Hello, world!` (string) - - actionName: `Retrieve Message` (string) - - exampleName: `Example 2` (string) -- host: `127.0.0.1` (string) - server hostname without port number -- port: `3000` (number) - server port number -- protocol: `https:` (enum[string]) - server protocol - - `https:` (string) - - `http:` (string) -- fullPath: `/message` (string) - expanded [URI Template][] with parameters (if any) used for the HTTP request Dredd performs to the tested server -- request (object) - the HTTP request Dredd performs to the tested server, taken from the API description - - body: `Hello world!\n` (string) - - bodyEncoding (enum) - can be manually set in [hooks](hooks.md) - - `utf-8` (string) - indicates `body` contains a textual content encoded in UTF-8 - - `base64` (string) - indicates `body` contains a binary content encoded in Base64 - - headers (object) - keys are HTTP header names, values are HTTP header contents - - uri: `/message` (string) - request URI as it was written in API description - - method: `POST` (string) -- expected (object) - the HTTP response Dredd expects to get from the tested server - - statusCode: `200` (string) - - headers (object) - keys are HTTP header names, values are HTTP header contents - - body (string) - - bodySchema (object) - JSON Schema of the response body -- real (object) - the HTTP response Dredd gets from the tested server (present only in `after` hooks) - - statusCode: `200` (string) - - headers (object) - keys are HTTP header names, values are HTTP header contents - - body (string) - - bodyEncoding (enum) - - `utf-8` (string) - indicates `body` contains a textual content encoded in UTF-8 - - `base64` (string) - indicates `body` contains a binary content encoded in Base64 -- skip: `false` (boolean) - can be set to `true` and the transaction will be skipped -- fail: `false` (enum) - can be set to `true` or string and the transaction will fail - - (string) - failure message with details why the transaction failed - - (boolean) -- test ([Transaction Test][]) - test data passed to Dredd's reporters -- results ([Transaction Results][]) - testing results - - -## Transaction Test (object) - -- start (Date) - start of the test -- end (Date) - end of the test -- duration (number) - duration of the test in milliseconds -- startedAt (number) - unix timestamp, [transaction][].startedAt -- title (string) - [transaction][].id -- request (object) - [transaction][].request -- actual (object) - [transaction][].real -- expected (object) - [transaction][].expected -- status (enum) - whether the validation passed or not, defaults to empty string - - `pass` (string) - - `fail` (string) - - `skip` (string) -- message (string) - concatenation of all messages from all [Gavel Errors](#gavel-error) in `results` or Dredd's custom message (e.g. "failed in before hook") -- results (Dredd's [transaction][].results) -- valid (boolean) -- origin (object) - [transaction][].origin - - -## Transaction Results (object) - -This is a cousin of the [Gavel Validation Result](#gavel-validation-result). - -- general (object) - contains Dredd's custom messages (e.g. "test was skipped"), formatted the same way like those from Gavel - - results (array[[Gavel Error][]]) -- statusCode ([Gavel Validator Output][]) -- headers ([Gavel Validator Output][]) -- body ([Gavel Validator Output][]) - - -## Gavel Validation Result (object) - -Can be seen also [here](https://relishapp.com/apiary/gavel/docs/javascript/request-async-api#validate). - -- statusCode ([Gavel Validator Output][]) -- headers ([Gavel Validator Output][]) -- body ([Gavel Validator Output][]) -- version (string) - version number of the Gavel Validation Result structure - - -## Gavel Validator Output (object) - -Can be seen also [here](https://relishapp.com/apiary/gavel/docs/data-validators-and-output-format#validators-output-format). - -- results (array[[Gavel Error][]]) -- realType (string) - media type -- expectedType (string) - media type -- validator (string) - validator class name -- rawData (enum) - raw output of the validator, has different structure for every validator and is saved and used in Apiary to render graphical diff by [gavel2html](https://github.com/apiaryio/gavel2html/) - - ([JsonSchema Validation Result][]) - - ([TextDiff Validation Result][]) - - -## JsonSchema Validation Result (object) - -The validation error is based on format provided by [Amanda][] and is also "documented" [here](https://github.com/apiaryio/Amanda/blob/master/docs/json/objects/error.md). Although for validation of draft4 JSON Schema Gavel uses [tv4][] library, the output then gets reshaped into the structure of Amanda's errors. - -This validation result is returned not only when validating against [JSON Schema][], but also when validating against JSON example or when validating HTTP headers. - -- length: `0` (number, default) - number of error properties -- errorMessages (object) - doesn't seem to ever contain anything or be used for anything -- *0* (object) - validation error details, property is always a string containing a number (0, 1, 2, ...) - - property (array[string]) - path to the problematic property in format of [json-pointer's `parse()` output](https://github.com/manuelstofer/json-pointer#user-content-parsestr) - - propertyValue (mixed) - real value of the problematic property (can be also `undefined` etc.) - - attributeName: `enum`, `required` (string) - name of the relevant JSON Schema attribute, which triggered the error - - attributeValue (mixed) - value of the relevant JSON Schema attribute, which triggered the error - - message (string) - error message (in case of tv4 it contains [JSON Pointer][] to the problematic property and for both Amanda and tv4 it can directly mention property names and/or values) - - validator: `enum` (string) - the same as `attributeName` - - validatorName: `error`, `enum` (string) - the same as `attributeName` - - validatorValue (mixed) - the same as `attributeValue` - - -## TextDiff Validation Result (string) - -Block of text which looks extremely similar to the standard GNU diff/patch format. Result of the [`patch_toText()` function of the `google-diff-match-patch` library](https://github.com/google/diff-match-patch/wiki/API#user-content-patch_totextpatches--text). - - -## Gavel Error (object) - -Can also be seen as part of Gavel Validator Output [here](https://relishapp.com/apiary/gavel/docs/data-validators-and-output-format#validators-output-format). - -- pointer (string) - [JSON Pointer][] path -- severity (string) - severity of the error -- message (string) - error message - - -## Apiary Reporter Test Data (object) - -- testRunId (string) - ID of the [test run](#apiary-test-run), recieved from Apiary -- origin (object) - [test][].origin -- duration (number) - duration of the test in milliseconds -- result (string) - [test][].status -- startedAt (number) - [test][].startedAt -- resultData (object) - - request (object) - [test][].request - - realResponse (object) - [test][].actual - - expectedResponse (object) - [test][].expected - - result ([Transaction Results][]) - [test][].results - -## Internal Apiary Data Structures - -These are private data structures used in Apiary internally and they are documented incompletely. They're present in this document just to provide better insight on what and how Apiary internally saves. It is closely related to what you can see in documentation for [Apiary Tests API for anonymous test reports][] and [Apiary Tests API for authenticated test reports][]. - - -### Apiary Test Run (object) - -Also known as `stats` in Dredd's code. - -- result - - tests: `0` (number, default) - total number of tests - - failures: `0` (number, default) - - errors: `0` (number, default) - - passes: `0` (number, default) - - skipped: `0` (number, default) - - start: `0` (number, default) - - end: `0` (number, default) - - duration: `0` (number, default) - - -### Apiary Test Step (object) - -- resultData - - request (object) - [test][].request - - realResponse (object) - [test][].actual - - expectedResponse (object) - [test][].expected - - result ([Transaction Results][]) - [test][].results - - -[Transaction]: #transaction -[Transaction Test]: #transaction-test -[Transaction Results]: #transaction-results -[Gavel Validation Result]: #gavel-validation-result -[Gavel Validator Output]: #gavel-validator-output -[JsonSchema Validation Result]: #jsonschema-validation-result -[TextDiff Validation Result]: #textdiff-validation-result -[Gavel Error]: #gavel-error -[Apiary Reporter Test Data]: #apiary-reporter-test-data -[Apiary Test Run]: #apiary-test-run-result -[Apiary Test Step]: #apiary-test-step-resultdata - -[transaction]: #transaction -[test]: #transaction-test - -[Amanda]: https://github.com/apiaryio/Amanda -[tv4]: https://github.com/geraintluff/tv4 -[Gavel.js]: https://github.com/apiaryio/gavel.js -[URI Template]: https://tools.ietf.org/html/rfc6570 -[JSON Pointer]: https://tools.ietf.org/html/rfc6901 -[JSON Schema]: http://json-schema.org/ - -[Apiary Tests API for anonymous test reports]: https://github.com/apiaryio/dredd/blob/master/ApiaryReportingApiAnonymous.apib -[Apiary Tests API for authenticated test reports]: https://github.com/apiaryio/dredd/blob/master/ApiaryReportingApi.apib -[Dredd Transactions]: https://github.com/apiaryio/dredd-transactions#user-content-data-structures diff --git a/docs/data-structures.rst b/docs/data-structures.rst new file mode 100644 index 000000000..5b18b5350 --- /dev/null +++ b/docs/data-structures.rst @@ -0,0 +1,229 @@ +.. _data-structures: + +Data Structures +=============== + +Documentation of various data structures in both `Gavel.js `__ and Dredd. `MSON notation `__ is used to describe the data structures. + +.. _transaction: + +Transaction (object) +-------------------- + +Transaction object is passed as a first argument to :ref:`hook functions ` and is one of the main public interfaces in Dredd. + +- id: ``GET (200) /greetings`` - identifier for this transaction +- name: ``./api-description.apib > My API > Greetings > Hello, world! > Retrieve Message > Example 2`` (string) - reference to the transaction definition in the original API description document (see also `Dredd Transactions `__) +- origin (object) - reference to the transaction definition in the original API description document (see also `Dredd Transactions `__) + + - filename: ``./api-description.apib`` (string) + - apiName: ``My Api`` (string) + - resourceGroupName: ``Greetings`` (string) + - resourceName: ``Hello, world!`` (string) + - actionName: ``Retrieve Message`` (string) + - exampleName: ``Example 2`` (string) + +- host: ``127.0.0.1`` (string) - server hostname without port number +- port: ``3000`` (number) - server port number +- protocol: ``https:`` (enum[string]) - server protocol + + - ``https:`` (string) + - ``http:`` (string) + +- fullPath: ``/message`` (string) - expanded `URI Template `__ with parameters (if any) used for the HTTP request Dredd performs to the tested server +- request (object) - the HTTP request Dredd performs to the tested server, taken from the API description + + - body: ``Hello world!\n`` (string) + - bodyEncoding (enum) - can be manually set in :ref:`hooks ` + + - ``utf-8`` (string) - indicates ``body`` contains a textual content encoded in UTF-8 + - ``base64`` (string) - indicates ``body`` contains a binary content encoded in Base64 + + - headers (object) - keys are HTTP header names, values are HTTP header contents + - uri: ``/message`` (string) - request URI as it was written in API description + - method: ``POST`` (string) + +- expected (object) - the HTTP response Dredd expects to get from the tested server + + - statusCode: ``200`` (string) + - headers (object) - keys are HTTP header names, values are HTTP header contents + - body (string) + - bodySchema (object) - JSON Schema of the response body + +- real (object) - the HTTP response Dredd gets from the tested server (present only in ``after`` hooks) + + - statusCode: ``200`` (string) + - headers (object) - keys are HTTP header names, values are HTTP header contents + - body (string) + - bodyEncoding (enum) + + - ``utf-8`` (string) - indicates ``body`` contains a textual content encoded in UTF-8 + - ``base64`` (string) - indicates ``body`` contains a binary content encoded in Base64 + +- skip: ``false`` (boolean) - can be set to ``true`` and the transaction will be skipped +- fail: ``false`` (enum) - can be set to ``true`` or string and the transaction will fail + + - (string) - failure message with details why the transaction failed + - (boolean) + +- test (:ref:`transaction-test`) - test data passed to Dredd’s reporters +- results (:ref:`transaction-results`) - testing results + +.. _transaction-test: + +Transaction Test (object) +------------------------- + +- start (Date) - start of the test +- end (Date) - end of the test +- duration (number) - duration of the test in milliseconds +- startedAt (number) - unix timestamp, :ref:`transaction `.startedAt +- title (string) - :ref:`transaction `.id +- request (object) - :ref:`transaction `.request +- actual (object) - :ref:`transaction `.real +- expected (object) - :ref:`transaction `.expected +- status (enum) - whether the validation passed or not, defaults to empty string + + - ``pass`` (string) + - ``fail`` (string) + - ``skip`` (string) + +- message (string) - concatenation of all messages from all :ref:`gavel-error` in ``results`` or Dredd’s custom message (e.g. “failed in before hook”) +- results (Dredd’s :ref:`transaction `.results) +- valid (boolean) +- origin (object) - :ref:`transaction `.origin + +.. _transaction-results: + +Transaction Results (object) +---------------------------- + +This is a cousin of the :ref:`gavel-validation-result`. + +- general (object) - contains Dredd’s custom messages (e.g. “test was skipped”), formatted the same way like those from Gavel + + - results (array[:ref:`gavel-error`]) + +- statusCode (:ref:`gavel-validator-output`) +- headers (:ref:`gavel-validator-output`) +- body (:ref:`gavel-validator-output`) + +.. _gavel-validation-result: + +Gavel Validation Result (object) +-------------------------------- + +Can be seen also `here `__. + +- statusCode (:ref:`gavel-validator-output`) +- headers (:ref:`gavel-validator-output`) +- body (:ref:`gavel-validator-output`) +- version (string) - version number of the Gavel Validation Result structure + +.. _gavel-validator-output: + +Gavel Validator Output (object) +------------------------------- + +Can be seen also `here `__. + +- results (array[:ref:`gavel-error`]) +- realType (string) - media type +- expectedType (string) - media type +- validator (string) - validator class name +- rawData (enum) - raw output of the validator, has different structure for every validator and is saved and used in Apiary to render graphical diff by `gavel2html `__ + + - (:ref:`jsonschema-validation-result`) + - (:ref:`textdiff-validation-result`) + +.. _jsonschema-validation-result: + +JsonSchema Validation Result (object) +------------------------------------- + +The validation error is based on format provided by `Amanda `__ and is also “documented” `here `__. Although for validation of draft4 JSON Schema Gavel uses `tv4 `__ library, the output then gets reshaped into the structure of Amanda’s errors. + +This validation result is returned not only when validating against `JSON Schema `__, but also when validating against JSON example or when validating HTTP headers. + +- length: ``0`` (number, default) - number of error properties +- errorMessages (object) - doesn’t seem to ever contain anything or be used for anything +- *0* (object) - validation error details, property is always a string containing a number (0, 1, 2, …) + + - property (array[string]) - path to the problematic property in format of `json-pointer’s ``parse()`` output `__ + - propertyValue (mixed) - real value of the problematic property (can be also ``undefined`` etc.) + - attributeName: ``enum``, ``required`` (string) - name of the relevant JSON Schema attribute, which triggered the error + - attributeValue (mixed) - value of the relevant JSON Schema attribute, which triggered the error + - message (string) - error message (in case of tv4 it contains `JSON Pointer `__ to the problematic property and for both Amanda and tv4 it can directly mention property names and/or values) + - validator: ``enum`` (string) - the same as ``attributeName`` + - validatorName: ``error``, ``enum`` (string) - the same as ``attributeName`` + - validatorValue (mixed) - the same as ``attributeValue`` + +.. _textdiff-validation-result: + +TextDiff Validation Result (string) +----------------------------------- + +Block of text which looks extremely similar to the standard GNU diff/patch format. Result of the ``patch_toText()`` function of the ``google-diff-match-patch`` library (`docs `__). + +.. _gavel-error: + +Gavel Error (object) +-------------------- + +Can also be seen as part of Gavel Validator Output `here `__. + +- pointer (string) - `JSON Pointer `__ path +- severity (string) - severity of the error +- message (string) - error message + +.. _apiary-reporter-test-data: + +Apiary Reporter Test Data (object) +---------------------------------- + +- testRunId (string) - ID of the :ref:`test run `, recieved from Apiary +- origin (object) - :ref:`test `.origin +- duration (number) - duration of the test in milliseconds +- result (string) - :ref:`test `.status +- startedAt (number) - :ref:`test `.startedAt +- resultData (object) + + - request (object) - :ref:`test `.request + - realResponse (object) - :ref:`test `.actual + - expectedResponse (object) - :ref:`test `.expected + - result (:ref:`transaction-results`) - :ref:`test `.results + +Internal Apiary Data Structures +------------------------------- + +These are private data structures used in Apiary internally and they are documented incompletely. They’re present in this document just to provide better insight on what and how Apiary internally saves. It is closely related to what you can see in documentation for `Apiary Tests API for anonymous test reports `__ and `Apiary Tests API for authenticated test reports `__. + +.. _apiary-test-run: + +Apiary Test Run (object) +~~~~~~~~~~~~~~~~~~~~~~~~ + +Also known as ``stats`` in Dredd’s code. + +- result + + - tests: ``0`` (number, default) - total number of tests + - failures: ``0`` (number, default) + - errors: ``0`` (number, default) + - passes: ``0`` (number, default) + - skipped: ``0`` (number, default) + - start: ``0`` (number, default) + - end: ``0`` (number, default) + - duration: ``0`` (number, default) + +.. _apiary-test-step: + +Apiary Test Step (object) +~~~~~~~~~~~~~~~~~~~~~~~~~ + +- resultData + + - request (object) - :ref:`test `.request + - realResponse (object) - :ref:`test `.actual + - expectedResponse (object) - :ref:`test `.expected + - result (:ref:`transaction-results`) - :ref:`test `.results diff --git a/docs/extensions/cli_options.py b/docs/extensions/cli_options.py new file mode 100644 index 000000000..9820bd4fe --- /dev/null +++ b/docs/extensions/cli_options.py @@ -0,0 +1,55 @@ +import os +import json +from textwrap import dedent +from docutils import nodes +from docutils.statemachine import ViewList +from docutils.parsers.rst import Directive + +from jinja2 import Template +from sphinx.util.nodes import nested_parse_with_titles + + +class CLIOptionsDirective(Directive): + required_arguments = 1 + + def run(self): + # Load options from given JSON file + options_path = os.path.join( + os.path.dirname(self.state.document['source']), + self.arguments[0], + ) + with open(options_path) as f: + options = json.load(f) + + # Generate reStructuredText markup + rst = '' + for name, attrs in sorted(options.items()): + data = { + 'name': name, + 'alias': attrs.get('alias'), + 'default': json.dumps(attrs['default']) if 'default' in attrs else None, + 'description': attrs['description'], + } + template = Template(dedent(''' + .. _{{ name }}{% if alias %}-{{ alias }}{% endif %}: + + .. option:: --{{ name }}{% if alias %}, -{{ alias }}{% endif %} + + {{ description }} + {% if default %}**Default value:** ``{{ default }}``{% endif %} + + ''')) + rst += template.render(**data) + + # Generate docutils nodes + result = ViewList() + for line in rst.splitlines(): + result.append(line, f'') + node = nodes.section(document=self.state.document) + nested_parse_with_titles(self.state, result, node) + return node.children + + +def setup(app): + app.add_directive('cli-options', CLIOptionsDirective) + return {'version': '1.0', 'parallel_read_safe': True} diff --git a/docs/hooks-go.md b/docs/hooks-go.md deleted file mode 100644 index a4f13cf2c..000000000 --- a/docs/hooks-go.md +++ /dev/null @@ -1,203 +0,0 @@ -# Writing Dredd Hooks In Go - -[![Godoc Reference](http://img.shields.io/badge/godoc-reference-5272B4.svg?style=flat-square)](https://godoc.org/github.com/snikch/goodman) - -[GitHub repository](https://github.com/snikch/goodman) - -Go hooks are using [Dredd's hooks handler socket interface](hooks-new-language.md). For using Go hooks in Dredd you have to have [Dredd already installed](quickstart.md). The Go library is called `goodman`. - -## Installation - -``` -$ go get github.com/snikch/goodman/cmd/goodman -``` - -## Usage - -Using Dredd with Go is slightly different to other languages, as a binary needs to be compiled for execution. The --hookfiles flags should point to compiled hook binaries. See below for an example hooks.go file to get an idea of what the source file behind the go binary would look like. - -``` -$ dredd apiary.apib http://127.0.0.1:3000 --server=./go-lang-web-server-to-test --language=go --hookfiles=./hook-file-binary -``` - -## API Reference - -In order to get a general idea of how the Go Hooks work, the main executable from the package $GOPATH/bin/goodman is an HTTP Server that Dredd communicates with and an RPC client. Each hookfile then acts as a corresponding RPC server. So when Dredd notifies the Hooks server what transaction event is occuring the hooks server will execute all registered hooks on each of the hookfiles RPC servers. - -You’ll need to know a few things about the `Server` type in the hooks package. - -1. The `hooks.Server` type is how you can define event callbacks such as `beforeEach`, `afterAll`, etc. - -2. To get a `hooks.Server` struct you must do the following - -```go -package main - -import ( - "github.com/snikch/goodman/hooks" - trans "github.com/snikch/goodman/transaction" -) - -func main() { - h := hooks.NewHooks() - server := hooks.NewServer(hooks.NewHooksRunner(h)) - - // Define all your event callbacks here - - // server.Serve() will block and allow the goodman server to run your defined - // event callbacks - server.Serve() - // You must close the listener at end of main() - defer server.Listener.Close() -} -``` - -2. Callbacks receive a `Transaction` instance, or an array of them - -3. A `Server` will run your `Runner` and handle receiving events on the dredd socket. - -### Runner Callback Events - -The `Runner` type has the following callback methods. - -1. `BeforeEach`, `BeforeEachValidation`, `AfterEach` - - accepts a function as a first argument passing a [Transaction object](data-structures.md#transaction) as a first argument - -2. `Before`, `BeforeValidation`, `After` - - accepts [transaction name](hooks.md#getting-transaction-names) as a first argument - - accepts a function as a second argument passing a [Transaction object](data-structures.md#transaction) as a first argument of it - -3. `BeforeAll`, `AfterAll` - - accepts a function as a first argument passing a Slice of [Transaction objects](data-structures.md#transaction) as a first argument - -Refer to [Dredd execution lifecycle](how-it-works.md#execution-life-cycle) to find when each hook callback is executed. - -### Using the Go API - -Example usage of all methods. - -```go -package main - -import ( - "fmt" - - "github.com/snikch/goodman/hooks" - trans "github.com/snikch/goodman/transaction" -) - -func main() { - h := hooks.NewHooks() - server := hooks.NewServer(hooks.NewHooksRunner(h)) - h.BeforeAll(func(t []*trans.Transaction) { - fmt.Println("before all modification") - }) - h.BeforeEach(func(t *trans.Transaction) { - fmt.Println("before each modification") - }) - h.Before("/message > GET", func(t *trans.Transaction) { - fmt.Println("before modification") - }) - h.BeforeEachValidation(func(t *trans.Transaction) { - fmt.Println("before each validation modification") - }) - h.BeforeValidation("/message > GET", func(t *trans.Transaction) { - fmt.Println("before validation modification") - }) - h.After("/message > GET", func(t *trans.Transaction) { - fmt.Println("after modification") - }) - h.AfterEach(func(t *trans.Transaction) { - fmt.Println("after each modification") - }) - h.AfterAll(func(t []*trans.Transaction) { - fmt.Println("after all modification") - }) - server.Serve() - defer server.Listener.Close() -} -``` - -## Examples - -### How to Skip Tests - -Any test step can be skipped by setting the `Skip` property of the `Transaction` instance to `true`. - -```go -package main - -import ( - "fmt" - - "github.com/snikch/goodman/hooks" - trans "github.com/snikch/goodman/transaction" -) - -func main() { - h := hooks.NewHooks() - server := hooks.NewServer(hooks.NewHooksRunner(h)) - h.Before("Machines > Machines collection > Get Machines", func(t *trans.Transaction) { - t.Skip = true - }) - server.Serve() - defer server.Listener.Close() -} -``` - -### Failing Tests Programmatically - -You can fail any step by setting the `Fail` field of the `Transaction` instance to `true` or any string with a descriptive message. - -```go -package main - -import ( - "fmt" - - "github.com/snikch/goodman/hooks" - trans "github.com/snikch/goodman/transaction" -) - -func main() { - h := hooks.NewHooks() - server := hooks.NewServer(hooks.NewHooksRunner(h)) - h.Before("Machines > Machines collection > Get Machines", func(t *trans.Transaction) { - t.Fail = true - }) - h.Before("Machines > Machines collection > Post Machines", func(t *trans.Transaction) { - t.Fail = "POST is broken" - }) - server.Serve() - defer server.Listener.Close() -} -``` - -### Modifying the Request Body Prior to Execution - -```go -package main - -import ( - "fmt" - - "github.com/snikch/goodman/hooks" - trans "github.com/snikch/goodman/transaction" -) - -func main() { - h := hooks.NewHooks() - server := hooks.NewServer(hooks.NewHooksRunner(h)) - h.Before("Machines > Machines collection > Get Machines", func(t *trans.Transaction) { - body := map[string]interface{}{} - json.Unmarshal([]byte(t.Request.Body), &body) - - body["someKey"] = "new value" - - newBody, _ := json.Marshal(body) - t.Request.body = string(newBody) - }) - server.Serve() - defer server.Listener.Close() -} -``` diff --git a/docs/hooks-go.rst b/docs/hooks-go.rst new file mode 100644 index 000000000..f9e001921 --- /dev/null +++ b/docs/hooks-go.rst @@ -0,0 +1,221 @@ +.. _hooks-go: + +Writing Dredd Hooks In Go +========================= + +|Godoc Reference| + +`GitHub repository `__ + +Go hooks are using :ref:`Dredd’s hooks handler socket interface `. For using Go hooks in Dredd you have to have :ref:`Dredd already installed `. The Go library is called ``goodman``. + +Installation +------------ + +:: + + $ go get github.com/snikch/goodman/cmd/goodman + +Usage +----- + +Using Dredd with Go is slightly different to other languages, as a binary needs to be compiled for execution. The –hookfiles flags should point to compiled hook binaries. See below for an example hooks.go file to get an idea of what the source file behind the go binary would look like. + +:: + + $ dredd apiary.apib http://127.0.0.1:3000 --server=./go-lang-web-server-to-test --language=go --hookfiles=./hook-file-binary + +API Reference +------------- + +In order to get a general idea of how the Go Hooks work, the main executable from the package $GOPATH/bin/goodman is an HTTP Server that Dredd communicates with and an RPC client. Each hookfile then acts as a corresponding RPC server. So when Dredd notifies the Hooks server what transaction event is occuring the hooks server will execute all registered hooks on each of the hookfiles RPC servers. + +You’ll need to know a few things about the ``Server`` type in the hooks package. + +1. The ``hooks.Server`` type is how you can define event callbacks such as ``beforeEach``, ``afterAll``, etc. + +2. To get a ``hooks.Server`` struct you must do the following + +.. code-block:: go + + package main + + import ( + "github.com/snikch/goodman/hooks" + trans "github.com/snikch/goodman/transaction" + ) + + func main() { + h := hooks.NewHooks() + server := hooks.NewServer(hooks.NewHooksRunner(h)) + + // Define all your event callbacks here + + // server.Serve() will block and allow the goodman server to run your defined + // event callbacks + server.Serve() + // You must close the listener at end of main() + defer server.Listener.Close() + } + +2. Callbacks receive a ``Transaction`` instance, or an array of them + +3. A ``Server`` will run your ``Runner`` and handle receiving events on the dredd socket. + +Runner Callback Events +~~~~~~~~~~~~~~~~~~~~~~ + +The ``Runner`` type has the following callback methods. + +1. ``BeforeEach``, ``BeforeEachValidation``, ``AfterEach`` + + - accepts a function as a first argument passing a :ref:`Transaction object ` as a first argument + +2. ``Before``, ``BeforeValidation``, ``After`` + + - accepts :ref:`transaction name ` as a first argument + - accepts a function as a second argument passing a :ref:`Transaction object ` as a first argument of it + +3. ``BeforeAll``, ``AfterAll`` + + - accepts a function as a first argument passing a Slice of :ref:`Transaction objects ` as a first argument + +Refer to :ref:`Dredd execution lifecycle ` to find when each hook callback is executed. + +Using the Go API +~~~~~~~~~~~~~~~~ + +Example usage of all methods. + +.. code-block:: go + + package main + + import ( + "fmt" + + "github.com/snikch/goodman/hooks" + trans "github.com/snikch/goodman/transaction" + ) + + func main() { + h := hooks.NewHooks() + server := hooks.NewServer(hooks.NewHooksRunner(h)) + h.BeforeAll(func(t []*trans.Transaction) { + fmt.Println("before all modification") + }) + h.BeforeEach(func(t *trans.Transaction) { + fmt.Println("before each modification") + }) + h.Before("/message > GET", func(t *trans.Transaction) { + fmt.Println("before modification") + }) + h.BeforeEachValidation(func(t *trans.Transaction) { + fmt.Println("before each validation modification") + }) + h.BeforeValidation("/message > GET", func(t *trans.Transaction) { + fmt.Println("before validation modification") + }) + h.After("/message > GET", func(t *trans.Transaction) { + fmt.Println("after modification") + }) + h.AfterEach(func(t *trans.Transaction) { + fmt.Println("after each modification") + }) + h.AfterAll(func(t []*trans.Transaction) { + fmt.Println("after all modification") + }) + server.Serve() + defer server.Listener.Close() + } + +Examples +-------- + +How to Skip Tests +~~~~~~~~~~~~~~~~~ + +Any test step can be skipped by setting the ``Skip`` property of the ``Transaction`` instance to ``true``. + +.. code-block:: go + + package main + + import ( + "fmt" + + "github.com/snikch/goodman/hooks" + trans "github.com/snikch/goodman/transaction" + ) + + func main() { + h := hooks.NewHooks() + server := hooks.NewServer(hooks.NewHooksRunner(h)) + h.Before("Machines > Machines collection > Get Machines", func(t *trans.Transaction) { + t.Skip = true + }) + server.Serve() + defer server.Listener.Close() + } + +Failing Tests Programmatically +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can fail any step by setting the ``Fail`` field of the ``Transaction`` instance to ``true`` or any string with a descriptive message. + +.. code-block:: go + + package main + + import ( + "fmt" + + "github.com/snikch/goodman/hooks" + trans "github.com/snikch/goodman/transaction" + ) + + func main() { + h := hooks.NewHooks() + server := hooks.NewServer(hooks.NewHooksRunner(h)) + h.Before("Machines > Machines collection > Get Machines", func(t *trans.Transaction) { + t.Fail = true + }) + h.Before("Machines > Machines collection > Post Machines", func(t *trans.Transaction) { + t.Fail = "POST is broken" + }) + server.Serve() + defer server.Listener.Close() + } + +Modifying the Request Body Prior to Execution +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: go + + package main + + import ( + "fmt" + + "github.com/snikch/goodman/hooks" + trans "github.com/snikch/goodman/transaction" + ) + + func main() { + h := hooks.NewHooks() + server := hooks.NewServer(hooks.NewHooksRunner(h)) + h.Before("Machines > Machines collection > Get Machines", func(t *trans.Transaction) { + body := map[string]interface{}{} + json.Unmarshal([]byte(t.Request.Body), &body) + + body["someKey"] = "new value" + + newBody, _ := json.Marshal(body) + t.Request.body = string(newBody) + }) + server.Serve() + defer server.Listener.Close() + } + +.. |Godoc Reference| image:: http://img.shields.io/badge/godoc-reference-5272B4.svg?style=flat-square + :target: https://godoc.org/github.com/snikch/goodman diff --git a/docs/hooks-js-sandbox.md b/docs/hooks-js-sandbox.md deleted file mode 100644 index 49c6a701b..000000000 --- a/docs/hooks-js-sandbox.md +++ /dev/null @@ -1,101 +0,0 @@ -# JavaScript Hooks In Sandbox Mode - -## Usage - -``` -$ dredd apiary.apib http://127.0.0.1:3000 --sandbox --hookfiles=./hooks*.js -``` - -### Dredd JS API Option - -Sandbox mode can be enabled in Dredd JavaScript API - -```javascript -var Dredd = require('dredd'); -var configuration = { - server: "http://127.0.0.1", - options: { - path: "./test/fixtures/single-get.apib", - sandbox: true, - hookfiles: ['./test/fixtures/sandboxed-hook.js'] - } -}; -var dredd = new Dredd(configuration); - -dredd.run(function (error, stats) { - // your callback code here -}); -``` - -## Sandboxed JavaScript Hooks API reference - -The Sandbox mode can be used for running untrusted hook code. It can be activated with a CLI switch or with the JS API. -In each hook file you can use following functions: - -`beforeAll(function)` - -`beforeEach(function)` - -`before(transactionName, function)` - -`beforeEachValidation(function)` - -`beforeValidation(transactionName, function)` - -`after(transactionName, function)` - -`afterEach(function)` - -`afterAll(function)` - -`log(string)` - -- A [Transaction Object](data-structures.md#transaction) is passed as a first argument to the hook function for `before`, `after`, `beforeEach`, `afterEach`, `beforeValidation` and `beforeEachValidation`. -- An array of Transaction Objects is passed to `beforeAll` and `afterAll`. -- Sandboxed hooks don't have an asynchronous API. Loading and running of each hook happens in it's own isolated, sandboxed context. -- Hook maximum execution time is 500ms. -- Memory limit is 1M -- You can access global `stash` object variables in each separate hook file. - `stash` is passed between contexts of each hook function execution. - This `stash` object purpose is to allow _transportation_ of user defined values - of type `String`, `Number`, `Boolean`, `null` or `Object` and `Array` (no `Functions` or callbacks). -- Hook code is evaluated with `"use strict"` directive - [details at MDN](https://mdn.io/use+strict) -- Sandboxed mode does not support hooks written in CoffeScript language -- You can print to console (via Dredd's logger) with `log` function, taking into account the used logging level `--level` option (levels: `error > warn > hook > info`) - - -### Request Stash in Sandbox Mode - -```javascript -after('First action', function (transaction) { - stash['id'] = JSON.parse(transaction.real.response); -}); - -before('Second action', function (transaction) { - newBody = JSON.parse(transaction.request.body); - newBody['id'] = stash['id']; - transaction.request.body = JSON.stringify(newBody); -}); -``` - -### Hook function context is not shared - -When __sandboxed__, hook function __context is not shared__ between even the same step hook functions. - -Note: __This is wrong__. It throws an exception. - -```javascript -var myObject = {}; - -after('First action', function (transaction) { - myObject['id'] = JSON.parse(transaction.real.response); -}); - -before('Second action', function (transaction) { - newBody = JSON.parse(transaction.request.body); - newBody['id'] = myObject['id']; - transaction.request.body = JSON.stringify(newBody); -}); -``` - -This will explode with: `ReferenceError: myObject is not defined` diff --git a/docs/hooks-js-sandbox.rst b/docs/hooks-js-sandbox.rst new file mode 100644 index 000000000..2081d2efa --- /dev/null +++ b/docs/hooks-js-sandbox.rst @@ -0,0 +1,97 @@ +.. _hooks-js-sandbox: + +JavaScript Hooks In Sandbox Mode +================================ + +Usage +----- + +:: + + $ dredd apiary.apib http://127.0.0.1:3000 --sandbox --hookfiles=./hooks*.js + +Dredd JS API Option +~~~~~~~~~~~~~~~~~~~ + +Sandbox mode can be enabled in Dredd JavaScript API + +.. code-block:: javascript + + var Dredd = require('dredd'); + var configuration = { + server: "http://127.0.0.1", + options: { + path: "./test/fixtures/single-get.apib", + sandbox: true, + hookfiles: ['./test/fixtures/sandboxed-hook.js'] + } + }; + var dredd = new Dredd(configuration); + + dredd.run(function (error, stats) { + // your callback code here + }); + +Sandboxed JavaScript Hooks API reference +---------------------------------------- + +The Sandbox mode can be used for running untrusted hook code. It can be activated with a CLI switch or with the JS API. In each hook file you can use following functions: + +.. js:function:: beforeAll(function) +.. js:function:: beforeEach(function) +.. js:function:: before(transactionName, function) +.. js:function:: beforeEachValidation(function) +.. js:function:: beforeValidation(transactionName, function) +.. js:function:: after(transactionName, function) +.. js:function:: afterEach(function) +.. js:function:: afterAll(function) +.. js:function:: log(string) + +- A :ref:`Transaction Object ` is passed as a first argument to the hook function for ``before``, ``after``, ``beforeEach``, ``afterEach``, ``beforeValidation`` and ``beforeEachValidation``. +- An array of Transaction Objects is passed to ``beforeAll`` and ``afterAll``. +- Sandboxed hooks don’t have an asynchronous API. Loading and running of each hook happens in it’s own isolated, sandboxed context. +- Hook maximum execution time is 500ms. +- Memory limit is 1M +- You can access global ``stash`` object variables in each separate hook file. ``stash`` is passed between contexts of each hook function execution. This ``stash`` object purpose is to allow *transportation* of user defined values of type ``String``, ``Number``, ``Boolean``, ``null`` or ``Object`` and ``Array`` (no ``Functions`` or callbacks). +- Hook code is evaluated with ``"use strict"`` directive - `details at MDN `__ +- Sandboxed mode does not support hooks written in CoffeScript language +- You can print to console (via Dredd’s logger) with ``log`` function, taking into account the used logging level :option:`--level` option (levels: ``error > warn > hook > info``) + +Request Stash in Sandbox Mode +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: javascript + + after('First action', function (transaction) { + stash['id'] = JSON.parse(transaction.real.response); + }); + + before('Second action', function (transaction) { + newBody = JSON.parse(transaction.request.body); + newBody['id'] = stash['id']; + transaction.request.body = JSON.stringify(newBody); + }); + +Hook function context is not shared +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When **sandboxed**, hook function **context is not shared** between even the same step hook functions. + +.. note:: + **This is wrong**. It throws an exception. + +.. code-block:: javascript + + var myObject = {}; + + after('First action', function (transaction) { + myObject['id'] = JSON.parse(transaction.real.response); + }); + + before('Second action', function (transaction) { + newBody = JSON.parse(transaction.request.body); + newBody['id'] = myObject['id']; + transaction.request.body = JSON.stringify(newBody); + }); + +This will explode with: ``ReferenceError: myObject is not defined`` diff --git a/docs/hooks-new-language.md b/docs/hooks-new-language.md deleted file mode 100644 index 3ebba01f5..000000000 --- a/docs/hooks-new-language.md +++ /dev/null @@ -1,90 +0,0 @@ -# Writing Dredd hook handler for new language - -## Dredd hooks handler client - -Dredd comes with concept of hooks language abstraction bridge via simple TCP socket. - -When you run Dredd with `--language` argument, it runs the command in argument and tries to connect to `http://127.0.0.1:61321`. If connection to the hook handling server wasn't successful, it exits with exit code `3`. - -Dredd internally registers a function for each [type of hooks](hooks.md#types-of-hooks) and when this function is executed it assigns execution `uuid` to that event, serializes received function parameters (a [Transaction object](data-structures.md#transaction) or an Array of it), sends it to the TCP socket to be handled (executed) in other language and waits until message with same `uuid` is received. After data reception it assigns received `data` back to the transaction, so other language can interact with transactions same way like [native Node.js hooks](hooks-nodejs.md). - -## Language agnostic test suite - -Dredd hooks language abstraction bridge comes with [the language agnostic test suite](https://github.com/apiaryio/dredd-hooks-template). It's written in Gherkin - language for writing [Cucumber](https://github.com/cucumber/cucumber/wiki/A-Table-Of-Content) scenarios and [Aruba CLI testing framework](https://github.com/cucumber/aruba) and it tests your new language handler integration with CLI Dredd and expected behavior from user's perspective. - -## What to implement - -If you want to write a hook handler for your language you will have to implement: - -- CLI Command runnning TCP socket server - - [Must return message `Starting` to stdout](https://github.com/apiaryio/dredd-hooks-template/blob/master/features/tcp_server.feature#L5) -- Hooks API in your language for registering code being executed during the [Dredd lifecycle](how-it-works.md#execution-life-cycle): - - before all transactions - - before each transaction - - before transaction - - before each transaction validation - - before transaction validation - - after transaction - - after each transaction - - after all transactions -- When CLI command is executed - - It loads files passed in alphabetical order with paths resolved to absolute form - - It exposes API similar to those in [Ruby](hooks-ruby.md), [Python](hooks-python.md) and [Node.js](hooks-nodejs.md) to each loaded file - - It registers functions declared in files for later execution - - starts a TCP socket server and starts listening on `http://127.0.0.1:61321`. -- When any data is received by the server - - Adds every received character to a buffer - - When delimiting newline (`\n`) character is received - - It parses the [message](#tcp-socket-message-format) in the buffer as JSON - - It looks for `event` key in received object and executes appropriate registered hooks functions - - When the hook function is being executed - - It passes value of `data` key from received object to the executed function - - Hook function is able to modify data - - When function was executed - - It should serialize message to JSON - - Send the serialized message back to the socket with same `uuid` as received - - Send a newline character as message delimiter - -## Termination - -When the testing is done, Dredd signals the hook handler process to terminate. This is done repeatedly with delays. When termination timeout is over, Dredd loses its patience and kills the process forcefully. - -- **retry delays** can be configured by [`--hooks-worker-term-retry`](usage-cli.md#hooks-worker-term-retry) -- **timeout** can be configured by [`--hooks-worker-term-timeout`](usage-cli.md#hooks-worker-term-timeout) - -On Linux or macOS, Dredd uses the `SIGTERM` signal to tell the hook handler process it should terminate. On Windows, where signals do not exist, Dredd sends the `END OF TEXT` character (`\u0003`, which is ASCII representation of Ctrl+C) to standard input of the process. - -## TCP Socket Message format - -- transaction (object) - - uuid: `234567-asdfghjkl` (string) - Id used for event unique identification on both server and client sides - - event: `event` (enum) - Event type - - beforeAll (string) - Signals the hook handler to run the `beforeAll` hooks - - beforeEach (string) - Signals the hook handler to run the `beforeEach` and `before` hooks - - beforeEachValidation (string) - Signals the hook handler to run the `beforeEachValidation` and `beforeValidation` hooks - - afterEach (string) - Signals the hook handler to run the `after` and `afterEach` hooks - - afterAll (string) - Signals the hook handler to run the `afterAll` hooks - - data (enum) - Data passed as a argument to the function - - (object) - Single Transaction object - - (array) - An array of Transaction objects, containing all transactions in the API description. Sent for `beforeAll` and `afterAll` events - -## Configuration Options - -There are several configuration options, which can help you during development: - -- `--hooks-worker-timeout` - [docs](usage-cli.md#hooks-worker-timeout) -- `--hooks-worker-connect-timeout` - [docs](usage-cli.md#hooks-worker-connect-timeout) -- `--hooks-worker-connect-retry` - [docs](usage-cli.md#hooks-worker-connect-retry) -- `--hooks-worker-after-connect-wait` - [docs](usage-cli.md#hooks-worker-after-connect-wait) -- `--hooks-worker-term-timeout` - [docs](usage-cli.md#hooks-worker-term-timeout) -- `--hooks-worker-term-retry` - [docs](usage-cli.md#hooks-worker-term-retry) -- `--hooks-worker-handler-host` - [docs](usage-cli.md#hooks-worker-handler-host) -- `--hooks-worker-handler-port` - [docs](usage-cli.md#hooks-worker-handler-port) - -## Need help? No problem! - -If you have any questions, please: - -- Have a look at the [Ruby](https://github.com/apiaryio/dredd-hooks-ruby), [Python](https://github.com/apiaryio/dredd-hooks-python), [Perl](https://github.com/ungrim97/Dredd-Hooks), and [PHP](https://github.com/ddelnano/dredd-hooks-php) hook handlers codebase for inspiration -- If you’re writing a hook handler for a compiled language, check out the [Go](https://github.com/snikch/goodman) implementation -- File an [issue in Dredd repository](https://github.com/apiaryio/dredd/issues/new) diff --git a/docs/hooks-new-language.rst b/docs/hooks-new-language.rst new file mode 100644 index 000000000..5a2d76dac --- /dev/null +++ b/docs/hooks-new-language.rst @@ -0,0 +1,120 @@ +.. _hooks-new-language: + +Writing Dredd hook handler for new language +=========================================== + +Dredd hooks handler client +-------------------------- + +Dredd comes with concept of hooks language abstraction bridge via simple TCP socket. + +When you run Dredd with :option:`--language` option, it runs the given command and tries to connect to ``http://127.0.0.1:61321``. If connection to the hook handling server wasn’t successful, it exits with exit code ``3``. + +Dredd internally registers a function for each :ref:`type of hooks ` and when this function is executed it assigns execution ``uuid`` to that event, serializes received function parameters (a :ref:`Transaction object ` or an Array of it), sends it to the TCP socket to be handled (executed) in other language and waits until message with same ``uuid`` is received. After data reception it assigns received ``data`` back to the transaction, so other language can interact with transactions same way like :ref:`native Node.js hooks `. + +Language agnostic test suite +---------------------------- + +Dredd hooks language abstraction bridge comes with `the language agnostic test suite `__. It’s written in Gherkin - language for writing `Cucumber `__ scenarios and `Aruba CLI testing framework `__ and it tests your new language handler integration with CLI Dredd and expected behavior from user’s perspective. + +What to implement +----------------- + +If you want to write a hook handler for your language you will have to implement: + +- CLI Command runnning TCP socket server + + - `Must return message ``Starting`` to stdout `__ + +- Hooks API in your language for registering code being executed during the :ref:`Dredd lifecycle `: + + - before all transactions + - before each transaction + - before transaction + - before each transaction validation + - before transaction validation + - after transaction + - after each transaction + - after all transactions + +- When CLI command is executed + + - It loads files passed in alphabetical order with paths resolved to absolute form + + - It exposes API similar to those in :ref:`Ruby `, :ref:`Python ` and :ref:`Node.js ` to each loaded file + - It registers functions declared in files for later execution + + - starts a TCP socket server and starts listening on ``http://127.0.0.1:61321``. + +- When any data is received by the server + + - Adds every received character to a buffer + - When delimiting newline (``\n``) character is received + + - It parses the :ref:`message ` in the buffer as JSON + - It looks for ``event`` key in received object and executes appropriate registered hooks functions + + - When the hook function is being executed + + - It passes value of ``data`` key from received object to the executed function + - Hook function is able to modify data + + - When function was executed + + - It should serialize message to JSON + - Send the serialized message back to the socket with same ``uuid`` as received + - Send a newline character as message delimiter + +Termination +----------- + +When the testing is done, Dredd signals the hook handler process to terminate. This is done repeatedly with delays. When termination timeout is over, Dredd loses its patience and kills the process forcefully. + +- **retry delays** can be configured by :option:`--hooks-worker-term-retry` +- **timeout** can be configured by :option:`--hooks-worker-term-timeout` + +On Linux or macOS, Dredd uses the ``SIGTERM`` signal to tell the hook handler process it should terminate. On Windows, where signals do not exist, Dredd sends the ``END OF TEXT`` character (``\u0003``, which is ASCII representation of Ctrl+C) to standard input of the process. + +.. _tcp-socket-message-format: + +TCP Socket Message format +------------------------- + +- transaction (object) + + - uuid: ``234567-asdfghjkl`` (string) - Id used for event unique identification on both server and client sides + - event: ``event`` (enum) - Event type + + - beforeAll (string) - Signals the hook handler to run the ``beforeAll`` hooks + - beforeEach (string) - Signals the hook handler to run the ``beforeEach`` and ``before`` hooks + - beforeEachValidation (string) - Signals the hook handler to run the ``beforeEachValidation`` and ``beforeValidation`` hooks + - afterEach (string) - Signals the hook handler to run the ``after`` and ``afterEach`` hooks + - afterAll (string) - Signals the hook handler to run the ``afterAll`` hooks + + - data (enum) - Data passed as a argument to the function + + - (object) - Single Transaction object + - (array) - An array of Transaction objects, containing all transactions in the API description. Sent for ``beforeAll`` and ``afterAll`` events + +Configuration Options +--------------------- + +There are several configuration options, which can help you during development: + +- :option:`--hooks-worker-timeout` +- :option:`--hooks-worker-connect-timeout` +- :option:`--hooks-worker-connect-retry` +- :option:`--hooks-worker-after-connect-wait` +- :option:`--hooks-worker-term-timeout` +- :option:`--hooks-worker-term-retry` +- :option:`--hooks-worker-handler-host` +- :option:`--hooks-worker-handler-port` + +Need help? No problem! +---------------------- + +If you have any questions, please: + +- Have a look at the `Ruby `__, `Python `__, `Perl `__, and `PHP `__ hook handlers codebase for inspiration +- If you’re writing a hook handler for a compiled language, check out the `Go `__ implementation +- File an `issue in Dredd repository `__ diff --git a/docs/hooks-nodejs.md b/docs/hooks-nodejs.md deleted file mode 100644 index 337ce0298..000000000 --- a/docs/hooks-nodejs.md +++ /dev/null @@ -1,272 +0,0 @@ -# Writing Dredd Hooks In Node.js - -## Usage - -``` -$ dredd apiary.apib http://127.0.0.1:30000 --hookfiles=./hooks*.js -``` - -## API Reference - -- For `before`, `after`, `beforeValidation`, `beforeEach`, `afterEach` and `beforeEachValidation` a [Transaction Object](hooks.md#transaction-object-structure) is passed as the first argument to the hook function. -- An array of Transaction Objects is passed to `beforeAll` and `afterAll`. -- The second argument is an optional callback function for async execution. -- Any modifications on the `transaction` object are propagated to the actual HTTP transactions. -- You can use `hooks.log` function inside the hook function to print - yours debug messages and other information. - -- [`configuration`](usage-js.md#configuration-object-for-dredd-class) object is populated on the `hooks` object - -### Sync API - -```javascript -var hooks = require('hooks'); - -hooks.beforeAll(function (transactions) { - hooks.log('before all'); -}); - -hooks.beforeEach(function (transaction) { - hooks.log('before each'); -}); - -hooks.before("Machines > Machines collection > Get Machines", function (transaction) { - hooks.log("before"); -}); - -hooks.beforeEachValidation(function (transaction) { - hooks.log('before each validation'); -}); - -hooks.beforeValidation("Machines > Machines collection > Get Machines", function (transaction) { - hooks.log("before validation"); -}); - -hooks.after("Machines > Machines collection > Get Machines", function (transaction) { - hooks.log("after"); -}); - -hooks.afterEach(function (transaction) { - hooks.log('after each'); -}); - -hooks.afterAll(function (transactions) { - hooks.log('after all'); -}) -``` - -### Async API - -When the callback is used in the hook function, callbacks can handle asynchronous function calls. - -```javascript -var hooks = require('hooks'); - -hooks.beforeAll(function (transactions, done) { - hooks.log('before all'); - done(); -}); - -hooks.beforeEach(function (transaction, done) { - hooks.log('before each'); - done(); -}); - -hooks.before("Machines > Machines collection > Get Machines", function (transaction, done) { - hooks.log("before"); - done(); -}); - -hooks.beforeEachValidation(function (transaction, done) { - hooks.log('before each validation'); - done(); -}); - -hooks.beforeValidation("Machines > Machines collection > Get Machines", function (transaction, done) { - hooks.log("before validation"); - done(); -}); - -hooks.after("Machines > Machines collection > Get Machines", function (transaction, done) { - hooks.log("after"); - done(); -}); - -hooks.afterEach(function (transaction, done) { - hooks.log('after each'); - done(); -}); - -hooks.afterAll(function (transactions, done) { - hooks.log('after all'); - done(); -}) -``` - -## Examples - -### How to Skip Tests - -Any test step can be skipped by setting `skip` property of the `transaction` object to `true`. - -```javascript -var before = require('hooks').before; - -before("Machines > Machines collection > Get Machines", function (transaction) { - transaction.skip = true; -}); -``` - -### Sharing Data Between Steps in Request Stash - -You may pass data between test steps using the response stash. - -```javascript -var hooks = require('hooks'); -var before = hooks.before; -var after = hooks.after; - -var responseStash = {}; - -after("Machines > Machines collection > Create Machine", function (transaction) { - - // saving HTTP response to the stash - responseStash[transaction.name] = transaction.real; -}); - - -before("Machines > Machine > Delete a machine", function (transaction) { - //reusing data from previous response here - var machineId = JSON.parse(responseStash['Machines > Machines collection > Create Machine'])['id']; - - //replacing id in URL with stashed id from previous response - var url = transaction.fullPath; - transaction.fullPath = url.replace('42', machineId); -}); -``` - -### Failing Tests Programmatically - -You can fail any step by setting `fail` property on `transaction` object to `true` or any string with descriptive message. - -```javascript -var before = require('hooks').before; - -before("Machines > Machines collection > Get Machines", function (transaction) { - transaction.fail = "Some failing message"; -}); -``` - -### Using Chai Assertions - -Inside hook files, you can require [Chai](http://www.chaijs.com/) and use its `assert`, `should` or `expect` interface in hooks and write your custom expectations. Dredd catches Chai's expectation error in hooks and makes transaction to fail. - -```javascript -var hooks = require('hooks'); -var before = hooks.before; -var assert = require('chai').assert; - -after("Machines > Machines collection > Get Machines", function (transaction) { - assert.isBelow(transaction.real.body.length, 100); -}); -``` - -### Modifying Transaction Request Body Prior to Execution - -```javascript -var hooks = require('hooks'); -var before = hooks.before; - -before("Machines > Machines collection > Get Machines", function (transaction) { - // parse request body from API description - var requestBody = JSON.parse(transaction.request.body); - - // modify request body here - requestBody['someKey'] = 'someNewValue'; - - // stringify the new body to request - transaction.request.body = JSON.stringify(requestBody); -}); -``` - -### Modifying Multipart Transaction Request Body Prior to Execution - -Dependencies: -- [multi-part](https://www.npmjs.com/package/multi-part) -- [stream-to-string](https://www.npmjs.com/package/stream-to-string) - -```javascript -const hooks = require('hooks'); -const fs = require('fs'); -const Multipart = require('multi-part'); -const streamToString = require('stream-to-string'); - -var before = hooks.before; - -before("Machines > Machines collection > Create Machines", async function (transaction, done) { - const form = new Multipart(); - form.append('title', 'Foo'); - form.append('photo', fs.createReadStream('./bar.jpg')); - transaction.request.body = await streamToString(form.getStream()); - transaction.request.headers['Content-Type'] = form.getHeaders()['content-type']; - done(); -}); -``` - -### Adding or Changing URI Query Parameters to All Requests - -```javascript -var hooks = require('hooks'); - -hooks.beforeEach(function (transaction) { - // add query parameter to each transaction here - var paramToAdd = "api-key=23456" - if(transaction.fullPath.indexOf('?') > -1){ - transaction.fullPath += "&" + paramToAdd; - } else{ - transaction.fullPath += "?" + paramToAdd; - } -}); -``` - -### Handling sessions - -```javascript -var hooks = require('hooks'); -var stash = {}; - -// hook to retrieve session on a login -hooks.after('Auth > /remoteauth/userpass > POST', function (transaction) { - stash['token'] = JSON.parse(transaction.real.body)['sessionId']; -}); - -// hook to set the session cookie in all following requests -hooks.beforeEach(function (transaction) { - if(stash['token'] != undefined){ - transaction.request['headers']['Cookie'] = "id=" + stash['token']; - }; -}); -``` - - -### Remove trailing newline character in expected _plain text_ bodies - -```javascript -var hooks = require('hooks'); - -hooks.beforeEach(function(transaction) { - if (transaction.expected.headers['Content-Type'] === 'text/plain') { - transaction.expected.body = transaction.expected.body.replace(/^\s+|\s+$/g, ""); - } -}); -``` - -### Using Babel - -With this workaround you can use [Babel](https://babeljs.io/) for support of all the latest JS syntactic coolness in Dredd hooks: - -``` -npm install -g babel-cli babel-preset-es2015 -echo '{ "presets": ["es2015"] }' > .babelrc -babel-node `which dredd` test/fixtures/single-get.apib http://127.0.0.1:3000 --hookfiles=./es2015.js -``` diff --git a/docs/hooks-nodejs.rst b/docs/hooks-nodejs.rst new file mode 100644 index 000000000..413458312 --- /dev/null +++ b/docs/hooks-nodejs.rst @@ -0,0 +1,295 @@ +.. _hooks-nodejs: + +Writing Dredd Hooks In Node.js +============================== + +Usage +----- + +:: + + $ dredd apiary.apib http://127.0.0.1:30000 --hookfiles=./hooks*.js + +API Reference +------------- + +- For ``before``, ``after``, ``beforeValidation``, ``beforeEach``, ``afterEach`` and ``beforeEachValidation`` a :ref:`Transaction Object ` is passed as the first argument to the hook function. +- An array of Transaction Objects is passed to ``beforeAll`` and ``afterAll``. +- The second argument is an optional callback function for async execution. +- Any modifications on the ``transaction`` object are propagated to the actual HTTP transactions. +- You can use ``hooks.log`` function inside the hook function to print yours debug messages and other information. + +- ``configuration`` (:ref:`docs `) object is populated on the ``hooks`` object + +Sync API +~~~~~~~~ + +.. code-block:: javascript + + var hooks = require('hooks'); + + hooks.beforeAll(function (transactions) { + hooks.log('before all'); + }); + + hooks.beforeEach(function (transaction) { + hooks.log('before each'); + }); + + hooks.before("Machines > Machines collection > Get Machines", function (transaction) { + hooks.log("before"); + }); + + hooks.beforeEachValidation(function (transaction) { + hooks.log('before each validation'); + }); + + hooks.beforeValidation("Machines > Machines collection > Get Machines", function (transaction) { + hooks.log("before validation"); + }); + + hooks.after("Machines > Machines collection > Get Machines", function (transaction) { + hooks.log("after"); + }); + + hooks.afterEach(function (transaction) { + hooks.log('after each'); + }); + + hooks.afterAll(function (transactions) { + hooks.log('after all'); + }) + +Async API +~~~~~~~~~ + +When the callback is used in the hook function, callbacks can handle asynchronous function calls. + +.. code-block:: javascript + + var hooks = require('hooks'); + + hooks.beforeAll(function (transactions, done) { + hooks.log('before all'); + done(); + }); + + hooks.beforeEach(function (transaction, done) { + hooks.log('before each'); + done(); + }); + + hooks.before("Machines > Machines collection > Get Machines", function (transaction, done) { + hooks.log("before"); + done(); + }); + + hooks.beforeEachValidation(function (transaction, done) { + hooks.log('before each validation'); + done(); + }); + + hooks.beforeValidation("Machines > Machines collection > Get Machines", function (transaction, done) { + hooks.log("before validation"); + done(); + }); + + hooks.after("Machines > Machines collection > Get Machines", function (transaction, done) { + hooks.log("after"); + done(); + }); + + hooks.afterEach(function (transaction, done) { + hooks.log('after each'); + done(); + }); + + hooks.afterAll(function (transactions, done) { + hooks.log('after all'); + done(); + }) + +Examples +-------- + +How to Skip Tests +~~~~~~~~~~~~~~~~~ + +Any test step can be skipped by setting ``skip`` property of the ``transaction`` object to ``true``. + +.. code-block:: javascript + + var before = require('hooks').before; + + before("Machines > Machines collection > Get Machines", function (transaction) { + transaction.skip = true; + }); + +.. _sharing-data-between-steps-in-request-stash: + +Sharing Data Between Steps in Request Stash +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You may pass data between test steps using the response stash. + +.. code-block:: javascript + + var hooks = require('hooks'); + var before = hooks.before; + var after = hooks.after; + + var responseStash = {}; + + after("Machines > Machines collection > Create Machine", function (transaction) { + + // saving HTTP response to the stash + responseStash[transaction.name] = transaction.real; + }); + + + before("Machines > Machine > Delete a machine", function (transaction) { + //reusing data from previous response here + var machineId = JSON.parse(responseStash['Machines > Machines collection > Create Machine'])['id']; + + //replacing id in URL with stashed id from previous response + var url = transaction.fullPath; + transaction.fullPath = url.replace('42', machineId); + }); + +Failing Tests Programmatically +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can fail any step by setting ``fail`` property on ``transaction`` object to ``true`` or any string with descriptive message. + +.. code-block:: javascript + + var before = require('hooks').before; + + before("Machines > Machines collection > Get Machines", function (transaction) { + transaction.fail = "Some failing message"; + }); + +.. _using-chai-assertions: + +Using Chai Assertions +~~~~~~~~~~~~~~~~~~~~~ + +Inside hook files, you can require `Chai `__ and use its ``assert``, ``should`` or ``expect`` interface in hooks and write your custom expectations. Dredd catches Chai’s expectation error in hooks and makes transaction to fail. + +.. code-block:: javascript + + var hooks = require('hooks'); + var before = hooks.before; + var assert = require('chai').assert; + + after("Machines > Machines collection > Get Machines", function (transaction) { + assert.isBelow(transaction.real.body.length, 100); + }); + +.. _modifying-transaction-request-body-prior-to-execution: + +Modifying Transaction Request Body Prior to Execution +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: javascript + + var hooks = require('hooks'); + var before = hooks.before; + + before("Machines > Machines collection > Get Machines", function (transaction) { + // parse request body from API description + var requestBody = JSON.parse(transaction.request.body); + + // modify request body here + requestBody['someKey'] = 'someNewValue'; + + // stringify the new body to request + transaction.request.body = JSON.stringify(requestBody); + }); + +Modifying Multipart Transaction Request Body Prior to Execution +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Dependencies: + +- `multi-part `__ +- `stream-to-string `__ + +.. code-block:: javascript + + const hooks = require('hooks'); + const fs = require('fs'); + const Multipart = require('multi-part'); + const streamToString = require('stream-to-string'); + + var before = hooks.before; + + before("Machines > Machines collection > Create Machines", async function (transaction, done) { + const form = new Multipart(); + form.append('title', 'Foo'); + form.append('photo', fs.createReadStream('./bar.jpg')); + transaction.request.body = await streamToString(form.getStream()); + transaction.request.headers['Content-Type'] = form.getHeaders()['content-type']; + done(); + }); + +Adding or Changing URI Query Parameters to All Requests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: javascript + + var hooks = require('hooks'); + + hooks.beforeEach(function (transaction) { + // add query parameter to each transaction here + var paramToAdd = "api-key=23456" + if(transaction.fullPath.indexOf('?') > -1){ + transaction.fullPath += "&" + paramToAdd; + } else{ + transaction.fullPath += "?" + paramToAdd; + } + }); + +Handling sessions +~~~~~~~~~~~~~~~~~ + +.. code-block:: javascript + + var hooks = require('hooks'); + var stash = {}; + + // hook to retrieve session on a login + hooks.after('Auth > /remoteauth/userpass > POST', function (transaction) { + stash['token'] = JSON.parse(transaction.real.body)['sessionId']; + }); + + // hook to set the session cookie in all following requests + hooks.beforeEach(function (transaction) { + if(stash['token'] != undefined){ + transaction.request['headers']['Cookie'] = "id=" + stash['token']; + }; + }); + +Remove trailing newline character in expected *plain text* bodies +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: javascript + + var hooks = require('hooks'); + + hooks.beforeEach(function(transaction) { + if (transaction.expected.headers['Content-Type'] === 'text/plain') { + transaction.expected.body = transaction.expected.body.replace(/^\s+|\s+$/g, ""); + } + }); + +Using Babel +~~~~~~~~~~~ + +With this workaround you can use `Babel `__ for support of all the latest JS syntactic coolness in Dredd hooks: + +:: + + npm install -g babel-cli babel-preset-es2015 + echo '{ "presets": ["es2015"] }' > .babelrc + babel-node `which dredd` test/fixtures/single-get.apib http://127.0.0.1:3000 --hookfiles=./es2015.js diff --git a/docs/hooks-perl.md b/docs/hooks-perl.md deleted file mode 100644 index 18ac46024..000000000 --- a/docs/hooks-perl.md +++ /dev/null @@ -1,214 +0,0 @@ -# Writing Dredd Hooks In Perl - -[![Build Status](https://api.travis-ci.org/ungrim97/Dredd-Hooks.svg?branch=master)](https://api.travis-ci.org/ungrim97/Dredd-Hooks.svg?branch=master) - -[GitHub repository](https://github.com/ungrim97/Dredd-Hooks) - -Perl hooks are using [Dredd's hooks handler socket interface](hooks-new-language.md). For using Perl hooks in Dredd you have to have [Dredd already installed](quickstart.md) - -## Installation - -``` -$ cpanm Dredd::Hooks -``` - -## Usage - -``` -$ dredd apiary.apib http://127.0.0.1:3000 --language=dredd-hooks-perl --hookfiles=./hooks*.pl -``` - -## API Reference - -Module `Dredd::Hooks::Methods` imports following decorators: - -1. `beforeEach`, `beforeEachValidation`, `afterEach` - - wraps a function and passes [Transaction object](data-structures.md#transaction) as a first argument to it - -2. `before`, `beforeValidation`, `after` - - accepts [transaction name](hooks.md#getting-transaction-names) as a first argument - - wraps a function and sends a [Transaction object](data-structures.md#transaction) as a first argument to it - -3. `beforeAll`, `afterAll` - - wraps a function and passes an Array of [Transaction objects](data-structures.md#transaction) as a first argument to it - -Refer to [Dredd execution life-cycle](how-it-works.md#execution-life-cycle) to find when is each hook function executed. - -### Using Perl API - -Example usage of all methods in - -```perl -use Dredd::Hooks::Methods; - -beforeAll( sub { - print 'before all' -}); - -beforeEach( sub { - print 'before each' -}) - -before( "Machines > Machines collection > Get Machines" => sub { - print 'before' -}); - -beforeEachValidation(sub { - print 'before each validation' -}); - -beforeValidation( "Machines > Machines collection > Get Machines" => sub { - print 'before validations' -}); - -after( "Machines > Machines collection > Get Machines" => sub { - print 'after' -}); - -afterEach( sub { - print 'after_each' -}); - -afterAll( sub { - print 'after_all' -}); - -``` - -## Examples - -### How to Skip Tests - -Any test step can be skipped by setting `skip` property of the `transaction` object to `true`. - -```perl -use Dredd::Hooks::Methods; -use Types::Serialiser; - -before("Machines > Machines collection > Get Machines" => sub { - my ($transaction) = @_; - - $transaction->{skip} = Types::Serialiser::true; -}); -``` - -### Sharing Data Between Steps in Request Stash - -If you want to test some API workflow, you may pass data between test steps using the response stash. - -```perl -use JSON; -use Dredd::Hooks::Methods; - -my $response_stash = {}; - -after("Machines > Machines collection > Create Machine" => sub { - my ($transaction) = @_; - - # saving HTTP response to the stash - $response_stash->{$transaction->{name}} = $transaction->{real} -}); - -before("Machines > Machine > Delete a machine" => sub { - my ($transaction) = @_; - #reusing data from previous response here - my $parsed_body = JSON->decode_json( - $response_stash->{'Machines > Machines collection > Create Machine'} - ); - my $machine_id = $parsed_body->{id}; - #replacing id in URL with stashed id from previous response - $transaction->{fullPath} =~ s/42/$machine_id/; -}); -``` - -### Failing Tests Programmatically - -You can fail any step by setting `fail` property on `transaction` object to `true` or any string with descriptive message. - -```perl -use Dredd::Hooks::Methods; - -before("Machines > Machines collection > Get Machines" => sub { - my ($transaction) = @_; - $transaction->{fail} = "Some failing message"; -}); -``` - -### Modifying Transaction Request Body Prior to Execution - -```perl -use JSON; -use Dredd::Hooks::Methods; - -before("Machines > Machines collection > Get Machines" => sub { - my ($transaction) = @_; - - # parse request body from API description - my $request_body = JSON->decode_json($transaction->{request}{body}); - - # modify request body here - $request_body->{someKey} = 'some new value'; - - # stringify the new body to request - $transaction->{request}{body} = JSON->encode_json($request_body); -}); -``` - -### Adding or Changing URI Query Parameters to All Requests - -```perl -use Dredd::Hooks::Methods; - -beforeEach( sub { - my ($transaction) = @_; - # add query parameter to each transaction here - my $param_to_add = "api-key=23456"; - - if ($transaction->{fullPath} =~ m/?/){ - $transaction->{fullPath} .= "&$param_to_add"; - } else { - $transaction->{fullPath} .= "?$param_to_add"; - } -}); -``` - -### Handling sessions - -```perl -use JSON; -use Dredd::Hooks::Methods; - -my $stash = {} - -# hook to retrieve session on a login -after('Auth > /remoteauth/userpass > POST' => sub { - my ($transaction) = @_; - - my $parsed_body = JSON->decode_json($transaction->{real}{body}); - my $stash->{token} = $parsed_body->{sessionId}; -)}; - -# hook to set the session cookie in all following requests -beforeEach( sub { - my ($transaction) = @_; - - if (exists $stash->{token}){ - $transaction->{request}{headers}{Cookie} = "id=".$stash{token}; - } -}); -``` - - -### Remove trailing newline character in expected _plain text_ bodies - -```perl -use Dredd::Hooks::Methods; - -beforeEach( - my ($transaction) = @_; - - if( $transaction->{expected}{headers}{Content-Type} eq 'text/plain'){ - $transaction->{expected}{body} = chomp($transaction->{expected}{body}); - } -}); -``` diff --git a/docs/hooks-perl.rst b/docs/hooks-perl.rst new file mode 100644 index 000000000..6b83ec3a8 --- /dev/null +++ b/docs/hooks-perl.rst @@ -0,0 +1,233 @@ +.. _hooks-perl: + +Writing Dredd Hooks In Perl +=========================== + +|Build Status| + +`GitHub repository `__ + +Perl hooks are using :ref:`Dredd’s hooks handler socket interface `. For using Perl hooks in Dredd you have to have :ref:`Dredd already installed ` + +Installation +------------ + +:: + + $ cpanm Dredd::Hooks + +Usage +----- + +:: + + $ dredd apiary.apib http://127.0.0.1:3000 --language=dredd-hooks-perl --hookfiles=./hooks*.pl + +API Reference +------------- + +Module ``Dredd::Hooks::Methods`` imports following decorators: + +1. ``beforeEach``, ``beforeEachValidation``, ``afterEach`` + + - wraps a function and passes :ref:`Transaction object ` as a first argument to it + +2. ``before``, ``beforeValidation``, ``after`` + + - accepts :ref:`transaction name ` as a first argument + - wraps a function and sends a :ref:`Transaction object ` as a first argument to it + +3. ``beforeAll``, ``afterAll`` + + - wraps a function and passes an Array of :ref:`Transaction objects ` as a first argument to it + +Refer to :ref:`Dredd execution life-cycle ` to find when is each hook function executed. + +Using Perl API +~~~~~~~~~~~~~~ + +Example usage of all methods in + +.. code-block:: perl + + use Dredd::Hooks::Methods; + + beforeAll( sub { + print 'before all' + }); + + beforeEach( sub { + print 'before each' + }) + + before( "Machines > Machines collection > Get Machines" => sub { + print 'before' + }); + + beforeEachValidation(sub { + print 'before each validation' + }); + + beforeValidation( "Machines > Machines collection > Get Machines" => sub { + print 'before validations' + }); + + after( "Machines > Machines collection > Get Machines" => sub { + print 'after' + }); + + afterEach( sub { + print 'after_each' + }); + + afterAll( sub { + print 'after_all' + }); + +Examples +-------- + +How to Skip Tests +~~~~~~~~~~~~~~~~~ + +Any test step can be skipped by setting ``skip`` property of the ``transaction`` object to ``true``. + +.. code-block:: perl + + use Dredd::Hooks::Methods; + use Types::Serialiser; + + before("Machines > Machines collection > Get Machines" => sub { + my ($transaction) = @_; + + $transaction->{skip} = Types::Serialiser::true; + }); + +Sharing Data Between Steps in Request Stash +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you want to test some API workflow, you may pass data between test steps using the response stash. + +.. code-block:: perl + + use JSON; + use Dredd::Hooks::Methods; + + my $response_stash = {}; + + after("Machines > Machines collection > Create Machine" => sub { + my ($transaction) = @_; + + # saving HTTP response to the stash + $response_stash->{$transaction->{name}} = $transaction->{real} + }); + + before("Machines > Machine > Delete a machine" => sub { + my ($transaction) = @_; + #reusing data from previous response here + my $parsed_body = JSON->decode_json( + $response_stash->{'Machines > Machines collection > Create Machine'} + ); + my $machine_id = $parsed_body->{id}; + #replacing id in URL with stashed id from previous response + $transaction->{fullPath} =~ s/42/$machine_id/; + }); + +Failing Tests Programmatically +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can fail any step by setting ``fail`` property on ``transaction`` object to ``true`` or any string with descriptive message. + +.. code-block:: perl + + use Dredd::Hooks::Methods; + + before("Machines > Machines collection > Get Machines" => sub { + my ($transaction) = @_; + $transaction->{fail} = "Some failing message"; + }); + +Modifying Transaction Request Body Prior to Execution +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: perl + + use JSON; + use Dredd::Hooks::Methods; + + before("Machines > Machines collection > Get Machines" => sub { + my ($transaction) = @_; + + # parse request body from API description + my $request_body = JSON->decode_json($transaction->{request}{body}); + + # modify request body here + $request_body->{someKey} = 'some new value'; + + # stringify the new body to request + $transaction->{request}{body} = JSON->encode_json($request_body); + }); + +Adding or Changing URI Query Parameters to All Requests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: perl + + use Dredd::Hooks::Methods; + + beforeEach( sub { + my ($transaction) = @_; + # add query parameter to each transaction here + my $param_to_add = "api-key=23456"; + + if ($transaction->{fullPath} =~ m/?/){ + $transaction->{fullPath} .= "&$param_to_add"; + } else { + $transaction->{fullPath} .= "?$param_to_add"; + } + }); + +Handling sessions +~~~~~~~~~~~~~~~~~ + +.. code-block:: perl + + use JSON; + use Dredd::Hooks::Methods; + + my $stash = {} + + # hook to retrieve session on a login + after('Auth > /remoteauth/userpass > POST' => sub { + my ($transaction) = @_; + + my $parsed_body = JSON->decode_json($transaction->{real}{body}); + my $stash->{token} = $parsed_body->{sessionId}; + )}; + + # hook to set the session cookie in all following requests + beforeEach( sub { + my ($transaction) = @_; + + if (exists $stash->{token}){ + $transaction->{request}{headers}{Cookie} = "id=".$stash{token}; + } + }); + +Remove trailing newline character in expected *plain text* bodies +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: perl + + use Dredd::Hooks::Methods; + + beforeEach( + my ($transaction) = @_; + + if( $transaction->{expected}{headers}{Content-Type} eq 'text/plain'){ + $transaction->{expected}{body} = chomp($transaction->{expected}{body}); + } + }); + +.. |Build Status| image:: https://api.travis-ci.org/ungrim97/Dredd-Hooks.svg?branch=master + :target: https://api.travis-ci.org/ungrim97/Dredd-Hooks.svg?branch=master diff --git a/docs/hooks-php.md b/docs/hooks-php.md deleted file mode 100644 index 9942fa07c..000000000 --- a/docs/hooks-php.md +++ /dev/null @@ -1,200 +0,0 @@ -# Writing Dredd Hooks In PHP - -[![Build Status](https://travis-ci.org/ddelnano/dredd-hooks-php.svg?branch=master)](https://travis-ci.org/ddelnano/dredd-hooks-php) - -[GitHub repository](https://github.com/ddelnano/dredd-hooks-php) - -PHP hooks are using [Dredd's hooks handler socket interface](hooks-new-language.md). For using PHP hooks in Dredd you have to have [Dredd already installed](quickstart.md) - -## Installation - -### Requirements - - php version >= 5.4 - -Installing dredd-hooks-php can be easily installed through the package manager, composer. - -``` -$ composer require ddelnano/dredd-hooks-php --dev -``` - -## Usage - -``` -$ dredd apiary.apib http://127.0.0.1:3000 --language=vendor/bin/dredd-hooks-php --hookfiles=./hooks*.php -``` - -## API Reference - -The `Dredd\Hooks` class provides the static methods listed below to create hooks - -1. `beforeEach`, `beforeEachValidation`, `afterEach` - - accepts a closure as a first argument passing a [Transaction object](data-structures.md#transaction) as a first argument - -2. `before`, `beforeValidation`, `after` - - accepts [transaction name](hooks.md#getting-transaction-names) as a first argument - - accepts a block as a second argument passing a [Transaction object](data-structures.md#transaction) as a first argument of it - -3. `beforeAll`, `afterAll` - - accepts a block as a first argument passing an Array of [Transaction objects](data-structures.md#transaction) as a first argument - - -Refer to [Dredd execution lifecycle](how-it-works.md#execution-life-cycle) to find when is each hook function executed. - -### Using PHP API - -Example usage of all methods. -**Very Important** The `$transaction` variable passed to the closure **MUST** be a reference. -Otherwise the `$transaction` variable will be passed by value when the closure is executed -and the changes will not be reflected. - -```php - Machines collection > Get Machines", function(&$transaction) { - - echo "before"; -}); - -Hooks::beforeEachValidation(function(&$transaction) { - - echo "before each validation"; -}); - -Hooks::beforeValidation("Machines > Machines collection > Get Machines", function(&$transaction) { - - echo "before validation"; -}); - - -Hooks::after("Machines > Machines collection > Get Machines", function(&$transaction) { - - echo "after"; -}); - -Hooks::afterEach(function(&$transaction) { - - echo "after each"; -}); - -Hooks::afterAll(function(&$transaction) { - - echo "after all"; -}); - -``` - -## Examples - -In the [dredd-hooks-php repository](https://github.com/ddelnano/dredd-hooks-php/) there is an example laravel application with instructions in the [wiki](https://github.com/ddelnano/dredd-hooks-php/wiki/Laravel-Example) - -### How to Skip Tests - -Any test step can be skipped by setting `skip` property of the `transaction` object to `true`. - -```php - Machines collection > Get Machines", function(&$transaction) { - - $transaction->skip = true; -}); -``` - -### Failing Tests Programmatically - -You can fail any step by setting `fail` property on `transaction` object to `true` or any string with descriptive message. - -```php - Machines collection > Get Machines", function(&$transaction) { - - $transaction->fail = true; -}); -``` - -### Modifying Transaction Request Body Prior to Execution - -```php - Machines collection > Get Machines", function(&$transaction) { - - $requestBody = $transaction->request->body; - - $requestBody['someKey'] = 'new value'; - - $transaction->request->body = json_encode($requestBody); -}); -``` - -### Adding or Changing URI Query Parameters to All Requests - -```php -fullPath, "?") { - - $transaction->fullPath .= "&{$paramToAdd}"; - } - - else { - - $transaction->fullPath .= "?{$paramToAdd}"; - } -}); -``` - -### Handling sessions - -```php - /remoteauto/userpass", function(&$transaction) use (&$stash) { - - $parsedBody = json_decode($transaction->real->body); - - $stash['token'] = $parseBody->sessionId; -}); - -Hooks::beforeEach(function(&$transaction) use (&$stash) { - - if ($transaction->token) { - - $transaction->request->headers->Cookie = "id={$stash['token']}s"; - } -}); -``` diff --git a/docs/hooks-php.rst b/docs/hooks-php.rst new file mode 100644 index 000000000..bdf00dd9a --- /dev/null +++ b/docs/hooks-php.rst @@ -0,0 +1,216 @@ +.. _hooks-php: + +Writing Dredd Hooks In PHP +========================== + +|Build Status| + +`GitHub repository `__ + +PHP hooks are using :ref:`Dredd’s hooks handler socket interface `. For using PHP hooks in Dredd you have to have :ref:`Dredd already installed ` + +Installation +------------ + +Requirements +~~~~~~~~~~~~ + +- php version >= 5.4 + +Installing dredd-hooks-php can be easily installed through the package manager, composer. + +:: + + $ composer require ddelnano/dredd-hooks-php --dev + +Usage +----- + +:: + + $ dredd apiary.apib http://127.0.0.1:3000 --language=vendor/bin/dredd-hooks-php --hookfiles=./hooks*.php + +API Reference +------------- + +The ``Dredd\Hooks`` class provides the static methods listed below to create hooks + +1. ``beforeEach``, ``beforeEachValidation``, ``afterEach`` + + - accepts a closure as a first argument passing a :ref:`Transaction object ` as a first argument + +2. ``before``, ``beforeValidation``, ``after`` + + - accepts :ref:`transaction name ` as a first argument + - accepts a block as a second argument passing a :ref:`Transaction object ` as a first argument of it + +3. ``beforeAll``, ``afterAll`` + + - accepts a block as a first argument passing an Array of :ref:`Transaction objects ` as a first argument + +Refer to :ref:`Dredd execution lifecycle ` to find when is each hook function executed. + +Using PHP API +~~~~~~~~~~~~~ + +Example usage of all methods. **Very Important** The ``$transaction`` variable passed to the closure **MUST** be a reference. Otherwise the ``$transaction`` variable will be passed by value when the closure is executed and the changes will not be reflected. + +.. code-block:: php + + Machines collection > Get Machines", function(&$transaction) { + + echo "before"; + }); + + Hooks::beforeEachValidation(function(&$transaction) { + + echo "before each validation"; + }); + + Hooks::beforeValidation("Machines > Machines collection > Get Machines", function(&$transaction) { + + echo "before validation"; + }); + + + Hooks::after("Machines > Machines collection > Get Machines", function(&$transaction) { + + echo "after"; + }); + + Hooks::afterEach(function(&$transaction) { + + echo "after each"; + }); + + Hooks::afterAll(function(&$transaction) { + + echo "after all"; + }); + +Examples +-------- + +In the `dredd-hooks-php repository `__ there is an example laravel application with instructions in the `wiki `__ + +How to Skip Tests +~~~~~~~~~~~~~~~~~ + +Any test step can be skipped by setting ``skip`` property of the ``transaction`` object to ``true``. + +.. code-block:: php + + Machines collection > Get Machines", function(&$transaction) { + + $transaction->skip = true; + }); + +Failing Tests Programmatically +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can fail any step by setting ``fail`` property on ``transaction`` object to ``true`` or any string with descriptive message. + +.. code-block:: php + + Machines collection > Get Machines", function(&$transaction) { + + $transaction->fail = true; + }); + +Modifying Transaction Request Body Prior to Execution +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: php + + Machines collection > Get Machines", function(&$transaction) { + + $requestBody = $transaction->request->body; + + $requestBody['someKey'] = 'new value'; + + $transaction->request->body = json_encode($requestBody); + }); + +Adding or Changing URI Query Parameters to All Requests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: php + + fullPath, "?") { + + $transaction->fullPath .= "&{$paramToAdd}"; + } + + else { + + $transaction->fullPath .= "?{$paramToAdd}"; + } + }); + +Handling sessions +~~~~~~~~~~~~~~~~~ + +.. code-block:: php + + /remoteauto/userpass", function(&$transaction) use (&$stash) { + + $parsedBody = json_decode($transaction->real->body); + + $stash['token'] = $parseBody->sessionId; + }); + + Hooks::beforeEach(function(&$transaction) use (&$stash) { + + if ($transaction->token) { + + $transaction->request->headers->Cookie = "id={$stash['token']}s"; + } + }); + +.. |Build Status| image:: https://travis-ci.org/ddelnano/dredd-hooks-php.svg?branch=master + :target: https://travis-ci.org/ddelnano/dredd-hooks-php diff --git a/docs/hooks-python.md b/docs/hooks-python.md deleted file mode 100644 index a7c4d56e4..000000000 --- a/docs/hooks-python.md +++ /dev/null @@ -1,198 +0,0 @@ -# Writing Dredd Hooks In Python - -[![Build Status](https://travis-ci.org/apiaryio/dredd-hooks-python.svg?branch=master)](https://travis-ci.org/apiaryio/dredd-hooks-python) - - -[GitHub repository](https://github.com/apiaryio/dredd-hooks-python) - -Python hooks are using [Dredd's hooks handler socket interface](hooks-new-language.md). For using Python hooks in Dredd you have to have [Dredd already installed](quickstart.md) - -## Installation - -``` -$ pip install dredd_hooks -``` - -## Usage - -``` -$ dredd apiary.apib http://127.0.0.1:3000 --language=python --hookfiles=./hooks*.py -``` - -## API Reference - -Module `dredd_hooks` imports following decorators: - -1. `before_each`, `before_each_validation`, `after_each` - - wraps a function and passes [Transaction object](data-structures.md#transaction) as a first argument to it - -2. `before`, `before_validation`, `after` - - accepts [transaction name](hooks.md#getting-transaction-names) as a first argument - - wraps a function and sends a [Transaction object](data-structures.md#transaction) as a first argument to it - -3. `before_all`, `after_all` - - wraps a function and passes an Array of [Transaction objects](data-structures.md#transaction) as a first argument to it - - -Refer to [Dredd execution life-cycle](how-it-works.md#execution-life-cycle) to find when is each hook function executed. - -### Using Python API - -Example usage of all methods in - -```python -import dredd_hooks as hooks - -@hooks.before_all -def my_before_all_hook(transactions): - print('before all') - -@hooks.before_each -def my_before_each_hook(transaction): - print('before each') - -@hooks.before -def my_before_hook(transaction): - print('before') - -@hooks.before_each_validation -def my_before_each_validation_hook(transaction): - print('before each validation') - -@hooks.before_validation -def my_before_validation_hook(transaction): - print('before validations') - -@hooks.after -def my_after_hook(transaction): - print('after') - -@hooks.after_each -def my_after_each(transaction): - print('after_each') - -@hooks.after_all -def my_after_all_hook(transactions): - print('after_all') - -``` - -## Examples - -More complex examples are to be found in the Github repository -[under the examples directory](https://github.com/apiaryio/dredd-hooks-python/tree/master/examples). If you want to share your own, don't hesitate and sumbit a PR. - -### How to Skip Tests - -Any test step can be skipped by setting `skip` property of the `transaction` object to `true`. - -```python -import dredd_hooks as hooks - -@hooks.before("Machines > Machines collection > Get Machines") -def skip_test(transaction): - transaction['skip'] = True -``` - -### Sharing Data Between Steps in Request Stash - -If you want to test some API workflow, you may pass data between test steps using the response stash. - -```python -import json -import dredd_hooks as hooks - -response_stash = {} - -@hooks.after("Machines > Machines collection > Create Machine") -def save_response_to_stash(transaction): - # saving HTTP response to the stash - response_stash[transaction['name']] = transaction['real'] - -@hooks.before("Machines > Machine > Delete a machine") -def add_machine_id_to_request(transaction): - #reusing data from previous response here - parsed_body = json.loads(response_stash['Machines > Machines collection > Create Machine']) - machine_id = parsed_body['id'] - #replacing id in URL with stashed id from previous response - transaction['fullPath'] = transaction['fullPath'].replace('42', machine_id) -``` - -### Failing Tests Programmatically - -You can fail any step by setting `fail` property on `transaction` object to `true` or any string with descriptive message. - -```python -import dredd_hooks as hooks - -@hooks.before("Machines > Machines collection > Get Machines") -def fail_transaction(transaction): - transaction['fail'] = "Some failing message" -``` - -### Modifying Transaction Request Body Prior to Execution - -```python -import json -import dredd_hooks as hooks - -@hooks.before("Machines > Machines collection > Get Machines") -def add_value_to_body(transaction): - # parse request body from API description - request_body = json.loads(transaction['request']['body']) - - # modify request body here - request_body['someKey'] = 'some new value' - - # stringify the new body to request - transaction['request']['body'] = json.dumps(request_body) -``` - -### Adding or Changing URI Query Parameters to All Requests - -```python -import dredd_hooks as hooks - -@hooks.before_each -def add_api_key(transaction): - # add query parameter to each transaction here - param_to_add = "api-key=23456" - - if '?' in transaction['fullPath']: - transaction['fullPath'] = ''.join((transaction['fullPath'], "&", param_to_add)) - else: - transaction['fullPath'] = ''.join((transaction['fullPath'], "?", param_to_add)) -``` - -### Handling sessions - -```python -import json -import dredd_hooks as hooks - -stash = {} - -# hook to retrieve session on a login -@hooks.after('Auth > /remoteauth/userpass > POST') -def stash_session_id(transaction): - parsed_body = json.loads(transaction['real']['body']) - stash['token'] = parsed_body['sessionId'] - -# hook to set the session cookie in all following requests -@hooks.before_each -def add_session_cookie(transaction): - if 'token' in stash: - transaction['request']['headers']['Cookie'] = "id=" + stash['token'] -``` - - -### Remove trailing newline character in expected _plain text_ bodies - -```python -import dredd_hooks as hooks - -@hooks.before_each -def remove_trailing_newline(transaction): - if transaction['expected']['headers']['Content-Type'] == 'text/plain': - transaction['expected']['body'] = transaction['expected']['body'].rstrip() -``` diff --git a/docs/hooks-python.rst b/docs/hooks-python.rst new file mode 100644 index 000000000..0da625031 --- /dev/null +++ b/docs/hooks-python.rst @@ -0,0 +1,214 @@ +.. _hooks-python: + +Writing Dredd Hooks In Python +============================= + +|Build Status| + +`GitHub repository `__ + +Python hooks are using :ref:`Dredd’s hooks handler socket interface `. For using Python hooks in Dredd you have to have :ref:`Dredd already installed ` + +Installation +------------ + +:: + + $ pip install dredd_hooks + +Usage +----- + +:: + + $ dredd apiary.apib http://127.0.0.1:3000 --language=python --hookfiles=./hooks*.py + +API Reference +------------- + +Module ``dredd_hooks`` imports following decorators: + +1. ``before_each``, ``before_each_validation``, ``after_each`` + + - wraps a function and passes :ref:`Transaction object ` as a first argument to it + +2. ``before``, ``before_validation``, ``after`` + + - accepts :ref:`transaction name ` as a first argument + - wraps a function and sends a :ref:`Transaction object ` as a first argument to it + +3. ``before_all``, ``after_all`` + + - wraps a function and passes an Array of :ref:`Transaction objects ` as a first argument to it + +Refer to :ref:`Dredd execution life-cycle ` to find when is each hook function executed. + +Using Python API +~~~~~~~~~~~~~~~~ + +Example usage of all methods in + +.. code-block:: python + + import dredd_hooks as hooks + + @hooks.before_all + def my_before_all_hook(transactions): + print('before all') + + @hooks.before_each + def my_before_each_hook(transaction): + print('before each') + + @hooks.before + def my_before_hook(transaction): + print('before') + + @hooks.before_each_validation + def my_before_each_validation_hook(transaction): + print('before each validation') + + @hooks.before_validation + def my_before_validation_hook(transaction): + print('before validations') + + @hooks.after + def my_after_hook(transaction): + print('after') + + @hooks.after_each + def my_after_each(transaction): + print('after_each') + + @hooks.after_all + def my_after_all_hook(transactions): + print('after_all') + +Examples +-------- + +More complex examples are to be found in the Github repository `under the examples directory `__. If you want to share your own, don’t hesitate and sumbit a PR. + +How to Skip Tests +~~~~~~~~~~~~~~~~~ + +Any test step can be skipped by setting ``skip`` property of the ``transaction`` object to ``true``. + +.. code-block:: python + + import dredd_hooks as hooks + + @hooks.before("Machines > Machines collection > Get Machines") + def skip_test(transaction): + transaction['skip'] = True + +Sharing Data Between Steps in Request Stash +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you want to test some API workflow, you may pass data between test steps using the response stash. + +.. code-block:: python + + import json + import dredd_hooks as hooks + + response_stash = {} + + @hooks.after("Machines > Machines collection > Create Machine") + def save_response_to_stash(transaction): + # saving HTTP response to the stash + response_stash[transaction['name']] = transaction['real'] + + @hooks.before("Machines > Machine > Delete a machine") + def add_machine_id_to_request(transaction): + #reusing data from previous response here + parsed_body = json.loads(response_stash['Machines > Machines collection > Create Machine']) + machine_id = parsed_body['id'] + #replacing id in URL with stashed id from previous response + transaction['fullPath'] = transaction['fullPath'].replace('42', machine_id) + +Failing Tests Programmatically +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can fail any step by setting ``fail`` property on ``transaction`` object to ``true`` or any string with descriptive message. + +.. code-block:: python + + import dredd_hooks as hooks + + @hooks.before("Machines > Machines collection > Get Machines") + def fail_transaction(transaction): + transaction['fail'] = "Some failing message" + +Modifying Transaction Request Body Prior to Execution +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + import json + import dredd_hooks as hooks + + @hooks.before("Machines > Machines collection > Get Machines") + def add_value_to_body(transaction): + # parse request body from API description + request_body = json.loads(transaction['request']['body']) + + # modify request body here + request_body['someKey'] = 'some new value' + + # stringify the new body to request + transaction['request']['body'] = json.dumps(request_body) + +Adding or Changing URI Query Parameters to All Requests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + import dredd_hooks as hooks + + @hooks.before_each + def add_api_key(transaction): + # add query parameter to each transaction here + param_to_add = "api-key=23456" + + if '?' in transaction['fullPath']: + transaction['fullPath'] = ''.join((transaction['fullPath'], "&", param_to_add)) + else: + transaction['fullPath'] = ''.join((transaction['fullPath'], "?", param_to_add)) + +Handling sessions +~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + import json + import dredd_hooks as hooks + + stash = {} + + # hook to retrieve session on a login + @hooks.after('Auth > /remoteauth/userpass > POST') + def stash_session_id(transaction): + parsed_body = json.loads(transaction['real']['body']) + stash['token'] = parsed_body['sessionId'] + + # hook to set the session cookie in all following requests + @hooks.before_each + def add_session_cookie(transaction): + if 'token' in stash: + transaction['request']['headers']['Cookie'] = "id=" + stash['token'] + +Remove trailing newline character in expected *plain text* bodies +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + import dredd_hooks as hooks + + @hooks.before_each + def remove_trailing_newline(transaction): + if transaction['expected']['headers']['Content-Type'] == 'text/plain': + transaction['expected']['body'] = transaction['expected']['body'].rstrip() + +.. |Build Status| image:: https://travis-ci.org/apiaryio/dredd-hooks-python.svg?branch=master + :target: https://travis-ci.org/apiaryio/dredd-hooks-python diff --git a/docs/hooks-ruby.md b/docs/hooks-ruby.md deleted file mode 100644 index 423362a9b..000000000 --- a/docs/hooks-ruby.md +++ /dev/null @@ -1,198 +0,0 @@ -# Writing Dredd Hooks In Ruby - -[![Build Status](https://travis-ci.org/apiaryio/dredd-hooks-ruby.svg?branch=master)](https://travis-ci.org/apiaryio/dredd-hooks-ruby) - -[GitHub repository](https://github.com/apiaryio/dredd-hooks-ruby) - -Ruby hooks are using [Dredd's hooks handler socket interface](hooks-new-language.md). For using Ruby hooks in Dredd you have to have [Dredd already installed](quickstart.md) - -## Installation - -``` -$ gem install dredd_hooks -``` - -## Usage - -``` -$ dredd apiary.apib http://127.0.0.1:3000 --language=ruby --hookfiles=./hooks*.rb -``` - -## API Reference - -Including module `Dredd::Hooks:Methods` expands current scope with methods - -1. `@before_each`, `before_each_validation`, `after_each` - - accepts a block as a first argument passing a [Transaction object](data-structures.md#transaction) as a first argument - -2. `before`, `before_validation`, `after` - - accepts [transaction name](hooks.md#getting-transaction-names) as a first argument - - accepts a block as a second argument passing a [Transaction object](data-structures.md#transaction) as a first argument of it - -3. `before_all`, `after_all` - - accepts a block as a first argument passing an Array of [Transaction objects](data-structures.md#transaction) as a first argument - - -Refer to [Dredd execution lifecycle](how-it-works.md#execution-life-cycle) to find when is each hook function executed. - -### Using Ruby API - -Example usage of all methods in - -```ruby -include DreddHooks::Methods - -before_all do |transactions| - puts 'before all' -end - -before_each do |transaction| - puts 'before each' -end - -before "Machines > Machines collection > Get Machines" do |transaction| - puts 'before' -end - -before_each_validation do |transaction| - puts 'before each validation' -end - -before_validation "Machines > Machines collection > Get Machines" do |transaction| - puts 'before validations' -end - -after "Machines > Machines collection > Get Machines" do |transaction| - puts 'after' -end - -after_each do |transaction| - puts 'after_each' -end - -after_all do |transactions| - puts 'after_all' -end -``` - -## Examples - -### How to Skip Tests - -Any test step can be skipped by setting `skip` property of the `transaction` object to `true`. - -```ruby -include DreddHooks::Methods - -before "Machines > Machines collection > Get Machines" do |transaction| - transaction['skip'] = true -end -``` - -### Sharing Data Between Steps in Request Stash - -If you want to test some API workflow, you may pass data between test steps using the response stash. - -```ruby -require 'json' -include DreddHooks::Methods - -response_stash = {} - -after "Machines > Machines collection > Create Machine" do |transaction| - # saving HTTP response to the stash - response_stash[transaction['name']] = transaction['real'] -do - -before "Machines > Machine > Delete a machine" do |transaction| - #reusing data from previous response here - parsed_body = JSON.parse response_stash['Machines > Machines collection > Create Machine'] - machine_id = parsed_body['id'] - - #replacing id in URL with stashed id from previous response - transaction['fullPath'].gsub! '42', machine_id -end -``` - -### Failing Tests Programmatically - -You can fail any step by setting `fail` property on `transaction` object to `true` or any string with descriptive message. - -```ruby -include DreddHooks::Methods - -before "Machines > Machines collection > Get Machines" do |transaction| - transaction['fail'] = "Some failing message" -end -``` - -### Modifying Transaction Request Body Prior to Execution - -```ruby -require 'json' -include DreddHooks::Methods - -before "Machines > Machines collection > Get Machines" do |transaction| - # parse request body from API description - request_body = JSON.parse transaction['request']['body'] - - # modify request body here - request_body['someKey'] = 'some new value' - - # stringify the new body to request - transaction['request']['body'] = request_body.to_json -end -``` - -### Adding or Changing URI Query Parameters to All Requests - -```ruby -include DreddHooks::Methods - -hooks.before_each do |transaction| - - # add query parameter to each transaction here - param_to_add = "api-key=23456" - - if transaction['fullPath'].include('?') - transaction['fullPath'] += "&" + param_to_add - else - transaction['fullPath'] += "?" + param_to_add - end -end -``` - -### Handling sessions - -```ruby -require 'json' -include DreddHooks::Methods - -stash = {} - -# hook to retrieve session on a login -hooks.after 'Auth > /remoteauth/userpass > POST' do |transaction| - parsed_body = JSON.parse transaction['real']['body'] - stash['token'] = parsed_body['sessionId'] -end - -# hook to set the session cookie in all following requests -hooks.beforeEach do |transaction| - unless stash['token'].nil? - transaction['request']['headers']['Cookie'] = "id=" + stash['token'] - end -end -``` - - -### Remove trailing newline character for in expected plain text bodies - -```ruby -include DreddHooks::Methods - -before_each do |transaction| - if transaction['expected']['headers']['Content-Type'] == 'text/plain' - transaction['expected']['body'] = transaction['expected']['body'].gsub(/^\s+|\s+$/g, "") - end -end -``` diff --git a/docs/hooks-ruby.rst b/docs/hooks-ruby.rst new file mode 100644 index 000000000..54a89d974 --- /dev/null +++ b/docs/hooks-ruby.rst @@ -0,0 +1,217 @@ +.. _hooks-ruby: + +Writing Dredd Hooks In Ruby +=========================== + +|Build Status| + +`GitHub repository `__ + +Ruby hooks are using :ref:`Dredd’s hooks handler socket interface `. For using Ruby hooks in Dredd you have to have :ref:`Dredd already installed ` + +Installation +------------ + +:: + + $ gem install dredd_hooks + +Usage +----- + +:: + + $ dredd apiary.apib http://127.0.0.1:3000 --language=ruby --hookfiles=./hooks*.rb + +API Reference +------------- + +Including module ``Dredd::Hooks:Methods`` expands current scope with methods + +1. ``@before_each``, ``before_each_validation``, ``after_each`` + + - accepts a block as a first argument passing a :ref:`Transaction object ` as a first argument + +2. ``before``, ``before_validation``, ``after`` + + - accepts :ref:`transaction name ` as a first argument + - accepts a block as a second argument passing a :ref:`Transaction object ` as a first argument of it + +3. ``before_all``, ``after_all`` + + - accepts a block as a first argument passing an Array of :ref:`Transaction objects ` as a first argument + +Refer to :ref:`Dredd execution lifecycle ` to find when is each hook function executed. + +Using Ruby API +~~~~~~~~~~~~~~ + +Example usage of all methods in + +.. code-block:: ruby + + include DreddHooks::Methods + + before_all do |transactions| + puts 'before all' + end + + before_each do |transaction| + puts 'before each' + end + + before "Machines > Machines collection > Get Machines" do |transaction| + puts 'before' + end + + before_each_validation do |transaction| + puts 'before each validation' + end + + before_validation "Machines > Machines collection > Get Machines" do |transaction| + puts 'before validations' + end + + after "Machines > Machines collection > Get Machines" do |transaction| + puts 'after' + end + + after_each do |transaction| + puts 'after_each' + end + + after_all do |transactions| + puts 'after_all' + end + +Examples +-------- + +How to Skip Tests +~~~~~~~~~~~~~~~~~ + +Any test step can be skipped by setting ``skip`` property of the ``transaction`` object to ``true``. + +.. code-block:: ruby + + include DreddHooks::Methods + + before "Machines > Machines collection > Get Machines" do |transaction| + transaction['skip'] = true + end + +Sharing Data Between Steps in Request Stash +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you want to test some API workflow, you may pass data between test steps using the response stash. + +.. code-block:: ruby + + require 'json' + include DreddHooks::Methods + + response_stash = {} + + after "Machines > Machines collection > Create Machine" do |transaction| + # saving HTTP response to the stash + response_stash[transaction['name']] = transaction['real'] + do + + before "Machines > Machine > Delete a machine" do |transaction| + #reusing data from previous response here + parsed_body = JSON.parse response_stash['Machines > Machines collection > Create Machine'] + machine_id = parsed_body['id'] + + #replacing id in URL with stashed id from previous response + transaction['fullPath'].gsub! '42', machine_id + end + +Failing Tests Programmatically +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can fail any step by setting ``fail`` property on ``transaction`` object to ``true`` or any string with descriptive message. + +.. code-block:: ruby + + include DreddHooks::Methods + + before "Machines > Machines collection > Get Machines" do |transaction| + transaction['fail'] = "Some failing message" + end + +Modifying Transaction Request Body Prior to Execution +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: ruby + + require 'json' + include DreddHooks::Methods + + before "Machines > Machines collection > Get Machines" do |transaction| + # parse request body from API description + request_body = JSON.parse transaction['request']['body'] + + # modify request body here + request_body['someKey'] = 'some new value' + + # stringify the new body to request + transaction['request']['body'] = request_body.to_json + end + +Adding or Changing URI Query Parameters to All Requests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: ruby + + include DreddHooks::Methods + + hooks.before_each do |transaction| + + # add query parameter to each transaction here + param_to_add = "api-key=23456" + + if transaction['fullPath'].include('?') + transaction['fullPath'] += "&" + param_to_add + else + transaction['fullPath'] += "?" + param_to_add + end + end + +Handling sessions +~~~~~~~~~~~~~~~~~ + +.. code-block:: ruby + + require 'json' + include DreddHooks::Methods + + stash = {} + + # hook to retrieve session on a login + hooks.after 'Auth > /remoteauth/userpass > POST' do |transaction| + parsed_body = JSON.parse transaction['real']['body'] + stash['token'] = parsed_body['sessionId'] + end + + # hook to set the session cookie in all following requests + hooks.beforeEach do |transaction| + unless stash['token'].nil? + transaction['request']['headers']['Cookie'] = "id=" + stash['token'] + end + end + +Remove trailing newline character for in expected plain text bodies +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: ruby + + include DreddHooks::Methods + + before_each do |transaction| + if transaction['expected']['headers']['Content-Type'] == 'text/plain' + transaction['expected']['body'] = transaction['expected']['body'].gsub(/^\s+|\s+$/g, "") + end + end + +.. |Build Status| image:: https://travis-ci.org/apiaryio/dredd-hooks-ruby.svg?branch=master + :target: https://travis-ci.org/apiaryio/dredd-hooks-ruby diff --git a/docs/hooks-rust.md b/docs/hooks-rust.md deleted file mode 100644 index ac2cb24b4..000000000 --- a/docs/hooks-rust.md +++ /dev/null @@ -1,180 +0,0 @@ -# Writing Dredd Hooks In Rust - -[![Crates.io](https://img.shields.io/crates/v/dredd-hooks.svg)](https://crates.io/crates/dredd-hooks) - -[GitHub repository](https://github.com/hobofan/dredd-hooks-rust) - -Rust hooks are using [Dredd's hooks handler socket interface](hooks-new-language.md). For using Rust hooks in Dredd you have to have [Dredd already installed](quickstart.md). The Rust library is called `dredd-hooks` and the correspondig binary `dredd-hooks-rust`. - -## Installation - -``` -$ cargo install dredd-hooks -``` - -## Usage - -Using Dredd with Rust is slightly different to other languages, as a binary needs to be compiled for execution. The --hookfiles flags should point to compiled hook binaries. See below for an example hooks.rs file to get an idea of what the source file behind the Rust binary would look like. - -``` -$ dredd apiary.apib http://127.0.0.1:3000 --server=./rust-web-server-to-test --language=rust --hookfiles=./hook-file-binary -``` - -## API Reference - -In order to get a general idea of how the Rust Hooks work, the main executable from the package `dredd-hooks` is an HTTP Server that Dredd communicates with and an RPC client. Each hookfile then acts as a corresponding RPC server. So when Dredd notifies the Hooks server what transaction event is occuring the hooks server will execute all registered hooks on each of the hookfiles RPC servers. - -You’ll need to know a few things about the `HooksServer` type in the `dredd-hooks` package. - -1. The `HooksServer` type is how you can define event callbacks such as `beforeEach`, `afterAll`, etc.. - -2. To get a `HooksServer` struct you must do the following; - -```rust -extern crate dredd_hooks; - -use dredd_hooks::{HooksServer}; - -fn main() { - let mut hooks = HooksServer::new(); - - // Define all your event callbacks here - - // HooksServer::start_from_env will block and allow the RPC server - // to receive messages from the main `dredd-hooks-rust` process. - HooksServer::start_from_env(hooks); -} -``` - -3. Callbacks receive a `Transaction` instance, or an array of them. - -### Runner Callback Events - -The `HooksServer` type has the following callback methods. - -1. `before_each`, `before_each_validation`, `after_each` - - accepts a function as a first argument passing a [Transaction object](data-structures.md#transaction) as a first argument - -2. `before`, `before_validation`, `after` - - accepts [transaction name](hooks.md#getting-transaction-names) as a first argument - - accepts a function as a second argument passing a [Transaction object](data-structures.md#transaction) as a first argument of it - -3. `before_all`, `after_all` - - accepts a function as a first argument passing a `Vec` of [Transaction objects](data-structures.md#transaction) as a first argument - -Refer to [Dredd execution lifecycle](how-it-works.md#execution-life-cycle) to find when each hook callback is executed. - -### Using the Rust API - -Example usage of all methods. - -```rust -extern crate dredd_hooks; - -use dredd_hooks::{HooksServer}; - -fn main() { - let mut hooks = HooksServer::new(); - hooks.before("/message > GET", Box::new(move |tr| { - println!("before hook handled"); - tr - })); - hooks.after("/message > GET", Box::new(move |tr| { - println!("after hook handled"); - tr - })); - hooks.before_validation("/message > GET", Box::new(move |tr| { - println!("before validation hook handled"); - tr - })); - hooks.before_all(Box::new(move |tr| { - println!("before all hook handled"); - tr - })); - hooks.after_all(Box::new(move |tr| { - println!("after all hook handled"); - tr - })); - hooks.before_each(Box::new(move |tr| { - println!("before each hook handled"); - tr - })); - hooks.before_each_validation(Box::new(move |tr| { - println!("before each validation hook handled"); - tr - })); - hooks.after_each(Box::new(move |tr| { - println!("after each hook handled"); - tr - })); - HooksServer::start_from_env(hooks); -} -``` - -## Examples - -### How to Skip Tests - -Any test step can be skipped by setting the value of the `skip` field of the `Transaction` instance to `true`. - -```rust -extern crate dredd_hooks; - -use dredd_hooks::{HooksServer}; - -fn main() { - let mut hooks = HooksServer::new(); - - // Runs only before the "/message > GET" test. - hooks.before("/message > GET", Box::new(|mut tr| { - // Set the skip flag on this test. - tr.insert("skip".to_owned(), true.into()); - // Hooks must always return the (modified) Transaction(s) that were passed in. - tr - })); - HooksServer::start_from_env(hooks); -} -``` - -### Failing Tests Programmatically - -You can fail any step by setting the value of the `fail` field of the `Transaction` instance to `true` or any string with a descriptive message. - -```rust -extern crate dredd_hooks; - -use dredd_hooks::{HooksServer}; - -fn main() { - let mut hooks = HooksServer::new(); - hooks.before("/message > GET", Box::new(|mut tr| { - // .into() can be used as an easy way to convert - // your value into the desired Json type. - tr.insert("fail".to_owned(), "Yay! Failed!".into()); - tr - })); - HooksServer::start_from_env(hooks); -} -``` - -### Modifying the Request Body Prior to Execution - -```rust -extern crate dredd_hooks; - -use dredd_hooks::{HooksServer}; - -fn main() { - let mut hooks = HooksServer::new(); - hooks.before("/message > GET", Box::new(|mut tr| { - // Try to access the "request" key as an object. - // (This will panic should the "request" key not be present.) - tr["request"].as_object_mut().unwrap() - .insert("body".to_owned(), "Hello World!".into()); - - tr - })); - HooksServer::start_from_env(hooks); -} - -``` diff --git a/docs/hooks-rust.rst b/docs/hooks-rust.rst new file mode 100644 index 000000000..8f1594658 --- /dev/null +++ b/docs/hooks-rust.rst @@ -0,0 +1,197 @@ +.. _hooks-rust: + +Writing Dredd Hooks In Rust +=========================== + +|Crates.io| + +`GitHub repository `__ + +Rust hooks are using :ref:`Dredd’s hooks handler socket interface `. For using Rust hooks in Dredd you have to have :ref:`Dredd already installed `. The Rust library is called ``dredd-hooks`` and the correspondig binary ``dredd-hooks-rust``. + +Installation +------------ + +:: + + $ cargo install dredd-hooks + +Usage +----- + +Using Dredd with Rust is slightly different to other languages, as a binary needs to be compiled for execution. The –hookfiles flags should point to compiled hook binaries. See below for an example hooks.rs file to get an idea of what the source file behind the Rust binary would look like. + +:: + + $ dredd apiary.apib http://127.0.0.1:3000 --server=./rust-web-server-to-test --language=rust --hookfiles=./hook-file-binary + +API Reference +------------- + +In order to get a general idea of how the Rust Hooks work, the main executable from the package ``dredd-hooks`` is an HTTP Server that Dredd communicates with and an RPC client. Each hookfile then acts as a corresponding RPC server. So when Dredd notifies the Hooks server what transaction event is occuring the hooks server will execute all registered hooks on each of the hookfiles RPC servers. + +You’ll need to know a few things about the ``HooksServer`` type in the ``dredd-hooks`` package. + +1. The ``HooksServer`` type is how you can define event callbacks such as ``beforeEach``, ``afterAll``, etc.. + +2. To get a ``HooksServer`` struct you must do the following; + +.. code-block:: rust + + extern crate dredd_hooks; + + use dredd_hooks::{HooksServer}; + + fn main() { + let mut hooks = HooksServer::new(); + + // Define all your event callbacks here + + // HooksServer::start_from_env will block and allow the RPC server + // to receive messages from the main `dredd-hooks-rust` process. + HooksServer::start_from_env(hooks); + } + +3. Callbacks receive a ``Transaction`` instance, or an array of them. + +Runner Callback Events +~~~~~~~~~~~~~~~~~~~~~~ + +The ``HooksServer`` type has the following callback methods. + +1. ``before_each``, ``before_each_validation``, ``after_each`` + + - accepts a function as a first argument passing a :ref:`Transaction object ` as a first argument + +2. ``before``, ``before_validation``, ``after`` + + - accepts :ref:`transaction name ` as a first argument + - accepts a function as a second argument passing a :ref:`Transaction object ` as a first argument of it + +3. ``before_all``, ``after_all`` + + - accepts a function as a first argument passing a ``Vec`` of :ref:`Transaction objects ` as a first argument + +Refer to :ref:`Dredd execution lifecycle ` to find when each hook callback is executed. + +Using the Rust API +~~~~~~~~~~~~~~~~~~ + +Example usage of all methods. + +.. code-block:: rust + + extern crate dredd_hooks; + + use dredd_hooks::{HooksServer}; + + fn main() { + let mut hooks = HooksServer::new(); + hooks.before("/message > GET", Box::new(move |tr| { + println!("before hook handled"); + tr + })); + hooks.after("/message > GET", Box::new(move |tr| { + println!("after hook handled"); + tr + })); + hooks.before_validation("/message > GET", Box::new(move |tr| { + println!("before validation hook handled"); + tr + })); + hooks.before_all(Box::new(move |tr| { + println!("before all hook handled"); + tr + })); + hooks.after_all(Box::new(move |tr| { + println!("after all hook handled"); + tr + })); + hooks.before_each(Box::new(move |tr| { + println!("before each hook handled"); + tr + })); + hooks.before_each_validation(Box::new(move |tr| { + println!("before each validation hook handled"); + tr + })); + hooks.after_each(Box::new(move |tr| { + println!("after each hook handled"); + tr + })); + HooksServer::start_from_env(hooks); + } + +Examples +-------- + +How to Skip Tests +~~~~~~~~~~~~~~~~~ + +Any test step can be skipped by setting the value of the ``skip`` field of the ``Transaction`` instance to ``true``. + +.. code-block:: rust + + extern crate dredd_hooks; + + use dredd_hooks::{HooksServer}; + + fn main() { + let mut hooks = HooksServer::new(); + + // Runs only before the "/message > GET" test. + hooks.before("/message > GET", Box::new(|mut tr| { + // Set the skip flag on this test. + tr.insert("skip".to_owned(), true.into()); + // Hooks must always return the (modified) Transaction(s) that were passed in. + tr + })); + HooksServer::start_from_env(hooks); + } + +Failing Tests Programmatically +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can fail any step by setting the value of the ``fail`` field of the ``Transaction`` instance to ``true`` or any string with a descriptive message. + +.. code-block:: rust + + extern crate dredd_hooks; + + use dredd_hooks::{HooksServer}; + + fn main() { + let mut hooks = HooksServer::new(); + hooks.before("/message > GET", Box::new(|mut tr| { + // .into() can be used as an easy way to convert + // your value into the desired Json type. + tr.insert("fail".to_owned(), "Yay! Failed!".into()); + tr + })); + HooksServer::start_from_env(hooks); + } + +Modifying the Request Body Prior to Execution +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: rust + + extern crate dredd_hooks; + + use dredd_hooks::{HooksServer}; + + fn main() { + let mut hooks = HooksServer::new(); + hooks.before("/message > GET", Box::new(|mut tr| { + // Try to access the "request" key as an object. + // (This will panic should the "request" key not be present.) + tr["request"].as_object_mut().unwrap() + .insert("body".to_owned(), "Hello World!".into()); + + tr + })); + HooksServer::start_from_env(hooks); + } + +.. |Crates.io| image:: https://img.shields.io/crates/v/dredd-hooks.svg + :target: https://crates.io/crates/dredd-hooks diff --git a/docs/hooks.md b/docs/hooks.md deleted file mode 100644 index 5eb0758f8..000000000 --- a/docs/hooks.md +++ /dev/null @@ -1,100 +0,0 @@ -# Hook Scripts - -Similar to any other testing framework, Dredd supports executing code around each test step. -Hooks are code blocks executed in defined stage of [execution lifecycle](how-it-works.md#execution-life-cycle). -In the hooks code you have an access to compiled HTTP [transaction object](#transaction-object-structure) which you can modify. - -Hooks are usually used for: - -- loading db fixtures -- cleanup after test step or steps -- handling authentication and sessions -- passing data between transactions (saving state from responses to _stash_) -- modifying request generated from API description -- changing generated expectations -- setting custom expectations -- debugging via logging stuff - -## Languages - -You can interact with your server implementation in following languages: - -- [Go](hooks-go.md) -- [JavaScript (Sandboxed)](hooks-js-sandbox.md) -- [Node.js](hooks-nodejs.md) -- [Perl](hooks-perl.md) -- [PHP](hooks-php.md) -- [Python](hooks-python.md) -- [Ruby](hooks-ruby.md) -- [Rust](hooks-rust.md) - -Dredd doesn't speak your language? [**It's very easy to write support for your language.**](hooks-new-language.md) Your contribution is more than welcome! - - -## Using Hook Files - -To use a hook file with Dredd, use the `--hookfiles` flag in the command line. -You can use this flag multiple times or use a [glob](https://www.npmjs.com/package/glob) expression for loading multiple hook files. Dredd executes hook files in alphabetical order. - -Example: - -```sh -$ dredd single-get.apib http://machines.apiary.io --hookfiles=*_hooks.* -``` - -## Getting Transaction Names - -For addressing specific test steps is used the __transaction names__ of the compiled HTTP transactions (_actions_) from the API description. - -In order to retrieve transaction names please run Dredd with the `--names` argument last and it will print all available names of transactions. - -For example, given an API Blueprint file `api-description.apib` as following: - -```apiblueprint -FORMAT: 1A - -# Machines API - -# Group Machines - -# Machines collection [/machines] - -## Get Machines [GET] - -- Response 200 (application/json; charset=utf-8) - - [{"type": "bulldozer", "name": "willy"}] - -``` - -Run this command to retrieve all transaction names: - -```sh -$ dredd single-get.apib http://machines.apiary.io --names -info: Machines > Machines collection > Get Machines -``` - -The `Machines > Machines collection > Get Machines` is the name of a transaction which you can use in your hooks. The same approach works also for Swagger documents. - -## Types of Hooks - -Dredd supports following types of hooks: - -- `beforeAll` called at the beginning of the whole test run -- `beforeEach` called before each HTTP transaction -- `before` called before some specific HTTP transaction -- `beforeEachValidation` called before each HTTP transaction is validated -- `beforeValidation` called before some specific HTTP transaction is validated -- `after` called after some specific HTTP transaction regardless its result -- `afterEach` called after each HTTP transaction -- `afterAll` called after whole test run - -Refer to [Dredd execution lifecycle](how-it-works.md#execution-life-cycle) when is each hook executed. - -### Transaction Object Structure - -The main purpose of hooks is to work with the transaction object they get as the first argument, in order to inspect or modify Dredd's behavior. See [transaction object reference](data-structures.md#transaction) to learn more about its contents. - - -[UTC ISO 8601]: http://wikipedia.org/wiki/ISO_8601 -[Gavel]: https://relishapp.com/apiary/gavel/docs diff --git a/docs/hooks.rst b/docs/hooks.rst new file mode 100644 index 000000000..7e4070f91 --- /dev/null +++ b/docs/hooks.rst @@ -0,0 +1,105 @@ +.. _hooks: + +Hook Scripts +============ + +Similar to any other testing framework, Dredd supports executing code around each test step. Hooks are code blocks executed in defined stage of :ref:`execution lifecycle `. In the hooks code you have an access to compiled HTTP :ref:`transaction object ` which you can modify. + +Hooks are usually used for: + +- loading db fixtures +- cleanup after test step or steps +- handling authentication and sessions +- passing data between transactions (saving state from responses to *stash*) +- modifying request generated from API description +- changing generated expectations +- setting custom expectations +- debugging via logging stuff + +Languages +--------- + +You can interact with your server implementation in following languages: + +- :ref:`Go ` +- :ref:`JavaScript (Sandboxed) ` +- :ref:`Node.js ` +- :ref:`Perl ` +- :ref:`PHP ` +- :ref:`Python ` +- :ref:`Ruby ` +- :ref:`Rust ` + +Dredd doesn’t speak your language? :ref:`It’s very easy to write support for your language. ` Your contribution is more than welcome! + +Using Hook Files +---------------- + +To use a hook file with Dredd, use the :option:`--hookfiles` flag in the command line. You can use this flag multiple times or use a `glob `__ expression for loading multiple hook files. Dredd executes hook files in alphabetical order. + +Example: + +.. code-block:: shell + + $ dredd single-get.apib http://machines.apiary.io --hookfiles=*_hooks.* + +.. _getting-transaction-names: + +Getting Transaction Names +------------------------- + +For addressing specific test steps is used the **transaction names** of the compiled HTTP transactions (*actions*) from the API description. + +In order to retrieve transaction names please run Dredd with the :option:`--names` option last and it will print all available names of transactions. + +For example, given an API Blueprint file ``api-description.apib`` as following: + +.. code-block:: apiblueprint + + FORMAT: 1A + + # Machines API + + # Group Machines + + # Machines collection [/machines] + + ## Get Machines [GET] + + - Response 200 (application/json; charset=utf-8) + + [{"type": "bulldozer", "name": "willy"}] + +Run this command to retrieve all transaction names: + +.. code-block:: shell + + $ dredd single-get.apib http://machines.apiary.io --names + info: Machines > Machines collection > Get Machines + +The ``Machines > Machines collection > Get Machines`` is the name of a transaction which you can use in your hooks. The same approach works also for Swagger documents. + +.. _types-of-hooks: + +Types of Hooks +-------------- + +Dredd supports following types of hooks: + +- ``beforeAll`` called at the beginning of the whole test run +- ``beforeEach`` called before each HTTP transaction +- ``before`` called before some specific HTTP transaction +- ``beforeEachValidation`` called before each HTTP transaction is validated +- ``beforeValidation`` called before some specific HTTP transaction is validated +- ``after`` called after some specific HTTP transaction regardless its result +- ``afterEach`` called after each HTTP transaction +- ``afterAll`` called after whole test run + +Refer to :ref:`Dredd execution lifecycle ` when is each hook executed. + +.. _transaction-object-structure: + +Transaction Object Structure +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The main purpose of hooks is to work with the transaction object they get as the first argument, in order to inspect or modify Dredd’s behavior. See :ref:`transaction object reference ` to learn more about its contents. diff --git a/docs/how-it-works.md b/docs/how-it-works.md deleted file mode 100644 index d342f5945..000000000 --- a/docs/how-it-works.md +++ /dev/null @@ -1,268 +0,0 @@ -# How It Works - -In a nutshell, Dredd does following: - -1. Takes your API description document, -2. creates expectations based on requests and responses documented in the document, -3. makes requests to tested API, -4. checks whether API responses match the documented responses, -5. reports the results. - -## Versioning - -Dredd follows [Semantic Versioning][]. To ensure certain stability of your Dredd installation (e.g. in CI), pin the version accordingly. You can also use release tags: - -- `npm install dredd` - Installs the latest published version including experimental pre-release versions. -- `npm install dredd@stable` - Skips experimental pre-release versions. Recommended for CI installations. - -If the `User-Agent` header isn't overridden in the API description document, Dredd uses it for sending information about its version number along with every HTTP request it does. - -## Execution Life Cycle - -Following execution life cycle documentation should help you to understand how Dredd works internally and which action goes after which. - -1. Load and parse API description documents - - Report parse errors and warnings -2. Pre-run API description check - - Missing example values for URI template parameters - - Required parameters present in URI - - Report non-parseable JSON bodies - - Report invalid URI parameters - - Report invalid URI templates -3. Compile HTTP transactions from API description documents - - Inherit headers - - Inherit parameters - - Expand URI templates with parameters -4. Load [hooks](hooks.md) -5. Test run - - Report test run `start` - - Run `beforeAll` hooks - - For each compiled transaction: - - Report `test start` - - Run `beforeEach` hook - - Run `before` hook - - Send HTTP request - - Receive HTTP response - - Run `beforeEachValidation` hook - - Run `beforeValidation` hook - - [Perform validation](#automatic-expectations) - - Run `after` hook - - Run `afterEach` hook - - Report `test end` with result for in-progress reporting - - Run `afterAll` hooks -6. Report test run `end` with result statistics - -## Automatic Expectations - -Dredd automatically generates expectations on HTTP responses based on examples in the API description with use of [Gavel.js][] library. Please refer to [Gavel][] rules if you want know more. - -### Response Headers Expectations - -- All headers specified in the API description must be present in the response. -- Names of headers are validated in the case-insensitive way. -- Only values of headers significant for content negotiation are validated. -- All other headers values can differ. - -When using [Swagger][], headers are taken from [`response.headers`][response-headers]. HTTP headers significant for content negotiation are inferred according to following rules: - -- [`produces`][produces] is propagated as response's `Content-Type` header. -- Response's `Content-Type` header overrides any `produces`. - -### Response Body Expectations - -If the HTTP response body is JSON, Dredd validates only its structure. Bodies in any other format are validated as plain text. - -To validate the structure Dredd uses [JSON Schema][] inferred from the API description under test. The effective JSON Schema is taken from following places (the order goes from the highest priority to the lowest): - -#### API Blueprint - -1. [`+ Schema`][schema-section] section - provided custom JSON Schema ([Draft v4][] and [v3][Draft v3]) will be used. -2. [`+ Attributes`][attributes-section] section with data structure description in [MSON][] - API Blueprint parser automatically generates JSON Schema from MSON. -3. [`+ Body`][body-section] section with sample JSON payload - [Gavel.js][], which is responsible for validation in Dredd, automatically infers some basic expectations described below. - -This order [exactly follows the API Blueprint specification][body-schema-attributes]. - -#### Swagger - -1. [`response.schema`][response-schema] - provided JSON Schema will be used. -2. [`response.examples`][response-examples] with sample JSON payload - [Gavel.js][], which is responsible for validation in Dredd, automatically infers some basic expectations described below. - - - -#### Gavel's Expectations - -- All JSON keys on any level given in the sample must be present in the response's JSON. -- Response's JSON values must be of the same JSON primitive type. -- All JSON values can differ. -- Arrays can have additional items, type or structure of the items is not validated. -- Plain text must match perfectly. - -### Custom Expectations - -You can make your own custom expectations in [hooks](hooks.md). For instance, check out how to employ [Chai.js assertions](hooks-nodejs.md#using-chai-assertions). - -## Making Your API Description Ready for Testing - -It's very likely that your API description document will not be testable __as is__. This section should help you to learn how to solve the most common issues. - -### URI Parameters - -Both [API Blueprint][] and [Swagger][] allow usage of URI templates (API Blueprint fully implements [RFC6570][], Swagger templates are much simpler). In order to have an API description which is testable, you need to describe all required parameters used in URI (path or query) and provide sample values to make Dredd able to expand URI templates with given sample values. Following rules apply when Dredd interpolates variables in a templated URI, ordered by precedence: - -1. Sample value (available in Swagger as [`x-example` vendor extension property](how-to-guides.md#example-values-for-request-parameters)). -2. Value of `default`. -3. First value from `enum`. - -If Dredd isn't able to infer any value for a required parameter, it will terminate the test run and complain that the parameter is _ambiguous_. - -> **Note:** The implementation of API Blueprint's request-specific parameters is still in progress and there's only experimental support for it in Dredd as of now. - -### Request Headers - -In [Swagger][] documents, HTTP headers are inferred from [`"in": "header"` parameters][parameters]. HTTP headers significant for content negotiation are inferred according to following rules: - -- [`consumes`][consumes] is propagated as request's `Content-Type` header. -- [`produces`][produces] is propagated as request's `Accept` header. -- If request body parameters are specified as `"in": "formData"`, request's `Content-Type` header is set to `application/x-www-form-urlencoded`. - -> **Note:** Processing `"in": "header"` parameters and inferring `application/x-www-form-urlencoded` from `"in": "formData"` parameters is not implemented yet ([apiaryio/fury-adapter-swagger#68](https://github.com/apiaryio/fury-adapter-swagger/issues/68), [apiaryio/fury-adapter-swagger#67](https://github.com/apiaryio/fury-adapter-swagger/issues/67)). - -### Request Body - -#### API Blueprint - -The effective request body is taken from following places (the order goes from the highest priority to the lowest): - -1. [`+ Body`][body-section] section with sample JSON payload. -2. [`+ Attributes`][attributes-section] section with data structure description in [MSON][] - API Blueprint parser automatically generates sample JSON payload from MSON. - -This order [exactly follows the API Blueprint specification][body-schema-attributes]. - -#### Swagger - -The effective request body is inferred from [`"in": "body"` and `"in": "formData"` parameters][parameters]. - -If body parameter has [`schema.example`][schema-example], it is used as a raw JSON sample for the request body. If it's not present, Dredd's [Swagger Adapter][] generates sample values from the JSON Schema provided in the [`schema`][schema] property. Following rules apply when the adapter fills values of the properties, ordered by precedence: - -1. Value of `default`. -2. First value from `enum`. -3. Dummy, generated value. - -### Empty Response Body - -If there is no body example or schema specified for the response in your API description document, Dredd won't imply any assertions. Any server response will be considered as valid. - -If you want to enforce the incoming body is empty, you can use [hooks](hooks.md): - -```javascript -:[hooks example](../test/fixtures/response/empty-body-hooks.js) -``` - -In case of responses with 204 or 205 status codes Dredd still behaves the same way, but it warns about violating the [RFC7231](https://tools.ietf.org/html/rfc7231) when the responses have non-empty bodies. - -## Choosing HTTP Transactions - -#### API Blueprint - -While [API Blueprint][] allows specifying multiple requests and responses in any -combination (see specification for the [action section][action-section]), Dredd -currently supports just separated HTTP transaction pairs like this: - -``` -+ Request -+ Response - -+ Request -+ Response -``` - -In other words, Dredd always selects just the first response for each request. - -> **Note:** Improving the support for multiple requests and responses is under development. Refer to issues [#25](https://github.com/apiaryio/dredd/issues/25) and [#78](https://github.com/apiaryio/dredd/issues/78) for details. Support for URI parameters specific to a single request within one action is also limited. Solving [#227](https://github.com/apiaryio/dredd/issues/227) should unblock many related problems. Also see [Multiple Requests and Responses](how-to-guides.md#multiple-requests-and-responses) guide for workarounds. - -#### Swagger - -The [Swagger][] format allows to specify multiple responses for a single operation. -By default Dredd tests only responses with `2xx` status codes. Responses with other -codes are marked as _skipped_ and can be activated in [hooks](hooks.md) - see the [Multiple Requests and Responses](how-to-guides.md#multiple-requests-and-responses) how-to guide. - -In [`produces`][produces] and [`consumes`][consumes], only JSON media types are supported. Only the first JSON media type in `produces` is effective, others are skipped. Other media types are respected only when provided with [explicit examples][response-examples]. - -[Default response][default-responses] is ignored by Dredd unless it is the only available response. In that case, the default response is assumed to have HTTP 200 status code. - -## Security - -Depending on what you test and how, output of Dredd may contain sensitive data. - -Mind that if you run Dredd in a CI server provided as a service (such as [CircleCI][], [Travis CI][], etc.), you are disclosing the CLI output of Dredd to third parties. - -When using [Apiary Reporter and Apiary Tests](how-to-guides.md#using-apiary-reporter-and-apiary-tests), you are sending your testing data to [Apiary][] (Dredd creators and maintainers). See their [Terms of Service][] and [Privacy Policy][]. Which data exactly is being sent to Apiary? - -- **Complete API description under test.** This means your API Blueprint or Swagger files. The API description is stored encrypted in Apiary. -- **Complete testing results.** Those can contain details of all requests made to the server under test and their responses. Apiary stores this data unencrypted, even if the original communication between Dredd and the API server under test happens to be over HTTPS. See [Apiary Reporter Test Data](data-structures.md#apiary-reporter-test-data) for detailed description of what is sent. You can [sanitize it before it gets sent](how-to-guides.md#removing-sensitive-data-from-test-reports). -- **Little meta data about your environment.** Contents of environment variables `TRAVIS`, `CIRCLE`, `CI`, `DRONE`, `BUILD_ID`, `DREDD_AGENT`, `USER`, and `DREDD_HOSTNAME` can be sent to Apiary. Your [hostname][], version of your Dredd installation, and [type][os-type], [release][os-release] and [architecture][os-arch] of your OS can be sent as well. Apiary stores this data unencrypted. - -See also [guidelines on how to develop Apiary Reporter](contributing.md#hacking-apiary-reporter). - - - -## Using HTTP(S) Proxy - -You can tell Dredd to use HTTP(S) proxy for: - -- downloading API description documents
- ([the positional argument][path-argument] or the [`--path` option][path-option] accepts also URL) -- [reporting to Apiary][apiary-reporter] - -Dredd respects `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY`, `http_proxy`, `https_proxy`, and `no_proxy` environment variables. For more information on how those work see [relevant section][request-proxies] of the underlying library's documentation. - -Dredd intentionally **does not support HTTP(S) proxies for testing**. Proxy can deliberately modify requests and responses or to behave in a very different way then the server under test. Testing over a proxy is, in the first place, testing of the proxy itself. That makes the test results irrelevant (and hard to debug). - - -[path-argument]: usage-cli.md#api-description-document-string -[path-option]: usage-cli.md#path-p -[apiary-reporter]: how-to-guides.md#using-apiary-reporter-and-apiary-tests -[request-proxies]: https://github.com/request/request#user-content-proxies - -[Apiary]: https://apiary.io/ -[Semantic Versioning]: https://semver.org/ -[API Blueprint]: https://apiblueprint.org/ -[Swagger]: https://swagger.io/ -[Gavel.js]: https://github.com/apiaryio/gavel.js -[Gavel]: https://relishapp.com/apiary/gavel/docs -[MSON]: https://github.com/apiaryio/mson -[JSON Schema]: http://json-schema.org/ -[Swagger Adapter]: https://github.com/apiaryio/fury-adapter-swagger/ -[RFC6570]: https://tools.ietf.org/html/rfc6570 -[Draft v4]: https://tools.ietf.org/html/draft-zyp-json-schema-04 -[Draft v3]: https://tools.ietf.org/html/draft-zyp-json-schema-03 - -[CircleCI]: https://circleci.com/ -[Travis CI]: https://travis-ci.org/ -[Terms of Service]: https://apiary.io/tos -[Privacy Policy]: https://apiary.io/privacy -[hostname]: https://en.wikipedia.org/wiki/Hostname -[os-type]: https://nodejs.org/api/os.html#os_os_type -[os-release]: https://nodejs.org/api/os.html#os_os_release -[os-arch]: https://nodejs.org/api/os.html#os_os_arch - -[schema-section]: https://apiblueprint.org/documentation/specification.html#def-schema-section -[parameters-section]: https://apiblueprint.org/documentation/specification.html#def-uriparameters-section -[attributes-section]: https://apiblueprint.org/documentation/specification.html#def-attributes-section -[body-section]: https://apiblueprint.org/documentation/specification.html#def-body-section -[request-section]: https://apiblueprint.org/documentation/specification.html#def-action-section -[action-section]: https://apiblueprint.org/documentation/specification.html#def-action-section -[body-schema-attributes]: https://apiblueprint.org/documentation/specification.html#relation-of-body-schema-and-attributes-sections - -[produces]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#user-content-swaggerProduces -[consumes]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#user-content-swaggerConsumes -[response-headers]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#user-content-responseHeaders -[schema]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#user-content-parameterSchema -[response-schema]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#user-content-responseSchema -[response-examples]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#user-content-responseExamples -[parameters]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#user-content-parameterObject -[operation-parameters]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#user-content-operationParameters -[paths-parameters]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#user-content-pathItemParameters -[swagger-parameters]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#user-content-swaggerParameters -[default-responses]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#user-content-responsesDefault -[schema-example]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#user-content-schemaExample diff --git a/docs/how-it-works.rst b/docs/how-it-works.rst new file mode 100644 index 000000000..ec5c3e388 --- /dev/null +++ b/docs/how-it-works.rst @@ -0,0 +1,261 @@ +.. _how-it-works: + +How It Works +============ + +In a nutshell, Dredd does following: + +1. Takes your API description document, +2. creates expectations based on requests and responses documented in the document, +3. makes requests to tested API, +4. checks whether API responses match the documented responses, +5. reports the results. + +Versioning +---------- + +Dredd follows `Semantic Versioning `__. To ensure certain stability of your Dredd installation (e.g. in CI), pin the version accordingly. You can also use release tags: + +- ``npm install dredd`` - Installs the latest published version including experimental pre-release versions. +- ``npm install dredd@stable`` - Skips experimental pre-release versions. Recommended for CI installations. + +If the ``User-Agent`` header isn’t overridden in the API description document, Dredd uses it for sending information about its version number along with every HTTP request it does. + +.. _execution-life-cycle: + +Execution Life Cycle +-------------------- + +Following execution life cycle documentation should help you to understand how Dredd works internally and which action goes after which. + +1. Load and parse API description documents + + - Report parse errors and warnings + +2. Pre-run API description check + + - Missing example values for URI template parameters + - Required parameters present in URI + - Report non-parseable JSON bodies + - Report invalid URI parameters + - Report invalid URI templates + +3. Compile HTTP transactions from API description documents + + - Inherit headers + - Inherit parameters + - Expand URI templates with parameters + +4. Load :ref:`hooks ` +5. Test run + + - Report test run ``start`` + - Run ``beforeAll`` hooks + - For each compiled transaction: + + - Report ``test start`` + - Run ``beforeEach`` hook + - Run ``before`` hook + - Send HTTP request + - Receive HTTP response + - Run ``beforeEachValidation`` hook + - Run ``beforeValidation`` hook + - :ref:`Perform validation ` + - Run ``after`` hook + - Run ``afterEach`` hook + - Report ``test end`` with result for in-progress reporting + + - Run ``afterAll`` hooks + +6. Report test run ``end`` with result statistics + +.. _automatic-expectations: + +Automatic Expectations +---------------------- + +Dredd automatically generates expectations on HTTP responses based on examples in the API description with use of `Gavel.js `__ library. Please refer to `Gavel `__ rules if you want know more. + +Response Headers Expectations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- All headers specified in the API description must be present in the response. +- Names of headers are validated in the case-insensitive way. +- Only values of headers significant for content negotiation are validated. +- All other headers values can differ. + +When using `Swagger `__, headers are taken from ``response.headers`` (`docs `__). HTTP headers significant for content negotiation are inferred according to following rules: + +- ``produces`` (`docs `__) is propagated as response’s ``Content-Type`` header. +- Response’s ``Content-Type`` header overrides any ``produces``. + +Response Body Expectations +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If the HTTP response body is JSON, Dredd validates only its structure. Bodies in any other format are validated as plain text. + +To validate the structure Dredd uses `JSON Schema `__ inferred from the API description under test. The effective JSON Schema is taken from following places (the order goes from the highest priority to the lowest): + +API Blueprint +^^^^^^^^^^^^^ + +1. `Schema `__ section - provided custom JSON Schema (`Draft v4 `__ and `v3 `__) will be used. +2. `Attributes `__ section with data structure description in `MSON `__ - API Blueprint parser automatically generates JSON Schema from MSON. +3. `Body `__ section with sample JSON payload - `Gavel.js `__, which is responsible for validation in Dredd, automatically infers some basic expectations described below. + +This order `exactly follows the API Blueprint specification `__. + +Swagger +^^^^^^^ + +1. ``response.schema`` (`docs `__) - provided JSON Schema will be used. +2. ``response.examples`` (`docs `__) with sample JSON payload - `Gavel.js `__, which is responsible for validation in Dredd, automatically infers some basic expectations described below. + +.. _gavels-expectations: + +Gavel’s Expectations +^^^^^^^^^^^^^^^^^^^^ + +- All JSON keys on any level given in the sample must be present in the response’s JSON. +- Response’s JSON values must be of the same JSON primitive type. +- All JSON values can differ. +- Arrays can have additional items, type or structure of the items is not validated. +- Plain text must match perfectly. + +Custom Expectations +~~~~~~~~~~~~~~~~~~~ + +You can make your own custom expectations in :ref:`hooks `. For instance, check out how to employ :ref:`Chai.js assertions `. + +Making Your API Description Ready for Testing +--------------------------------------------- + +It’s very likely that your API description document will not be testable **as is**. This section should help you to learn how to solve the most common issues. + +URI Parameters +~~~~~~~~~~~~~~ + +Both `API Blueprint `__ and `Swagger `__ allow usage of URI templates (API Blueprint fully implements `RFC6570 `__, Swagger templates are much simpler). In order to have an API description which is testable, you need to describe all required parameters used in URI (path or query) and provide sample values to make Dredd able to expand URI templates with given sample values. Following rules apply when Dredd interpolates variables in a templated URI, ordered by precedence: + +1. Sample value, in Swagger available as the ``x-example`` vendor extension property (:ref:`docs `). +2. Value of ``default``. +3. First value from ``enum``. + +If Dredd isn’t able to infer any value for a required parameter, it will terminate the test run and complain that the parameter is *ambiguous*. + +.. note:: + The implementation of API Blueprint’s request-specific parameters is still in progress and there’s only experimental support for it in Dredd as of now. + +Request Headers +~~~~~~~~~~~~~~~ + +In `Swagger `__ documents, HTTP headers are inferred from ``"in": "header"`` parameters (`docs `__). HTTP headers significant for content negotiation are inferred according to following rules: + +- ``consumes`` (`docs `__) is propagated as request’s ``Content-Type`` header. +- ``produces`` (`docs `__) is propagated as request’s ``Accept`` header. +- If request body parameters are specified as ``"in": "formData"``, request’s ``Content-Type`` header is set to ``application/x-www-form-urlencoded``. + +.. note:: + Processing ``"in": "header"`` parameters and inferring ``application/x-www-form-urlencoded`` from ``"in": "formData"`` parameters is not implemented yet (`apiaryio/fury-adapter-swagger#68 `__, `apiaryio/fury-adapter-swagger#67 `__). + +Request Body +~~~~~~~~~~~~ + +API Blueprint +^^^^^^^^^^^^^ + +The effective request body is taken from following places (the order goes from the highest priority to the lowest): + +1. `Body `__ section with sample JSON payload. +2. `Attributes `__ section with data structure description in `MSON `__ - API Blueprint parser automatically generates sample JSON payload from MSON. + +This order `exactly follows the API Blueprint specification `__. + +Swagger +^^^^^^^ + +The effective request body is inferred from ``"in": "body"`` and ``"in": "formData"`` parameters (`docs `__). + +If body parameter has ``schema.example`` (`docs `__), it is used as a raw JSON sample for the request body. If it’s not present, Dredd’s `Swagger Adapter `__ generates sample values from the JSON Schema provided in the ``schema`` (`docs `__) property. Following rules apply when the adapter fills values of the properties, ordered by precedence: + +1. Value of ``default``. +2. First value from ``enum``. +3. Dummy, generated value. + +.. _empty-response-body: + +Empty Response Body +~~~~~~~~~~~~~~~~~~~ + +If there is no body example or schema specified for the response in your API description document, Dredd won’t imply any assertions. Any server response will be considered as valid. + +If you want to enforce the incoming body is empty, you can use :ref:`hooks `: + +.. literalinclude:: ../test/fixtures/response/empty-body-hooks.js + :language: javascript + +In case of responses with 204 or 205 status codes Dredd still behaves the same way, but it warns about violating the `RFC7231 `__ when the responses have non-empty bodies. + +.. _choosing-http-transactions: + +Choosing HTTP Transactions +-------------------------- + +API Blueprint +~~~~~~~~~~~~~ + +While `API Blueprint `__ allows specifying multiple requests and responses in any combination (see specification for the `action section `__), Dredd currently supports just separated HTTP transaction pairs like this: + +:: + + + Request + + Response + + + Request + + Response + +In other words, Dredd always selects just the first response for each request. + +.. note:: + Improving the support for multiple requests and responses is under development. Refer to issues `#25 `__ and `#78 `__ for details. Support for URI parameters specific to a single request within one action is also limited. Solving `#227 `__ should unblock many related problems. Also see :ref:`multiple-requests-and-responses` guide for workarounds. + +Swagger +~~~~~~~ + +The `Swagger `__ format allows to specify multiple responses for a single operation. By default Dredd tests only responses with ``2xx`` status codes. Responses with other codes are marked as *skipped* and can be activated in :ref:`hooks ` - see the :ref:`multiple-requests-and-responses` how-to guide. + +In ``produces`` (`docs `__) and ``consumes`` (`docs `__), only JSON media types are supported. Only the first JSON media type in ``produces`` is effective, others are skipped. Other media types are respected only when provided with `explicit examples `__. + +`Default response `__ is ignored by Dredd unless it is the only available response. In that case, the default response is assumed to have HTTP 200 status code. + +.. _security: + +Security +-------- + +Depending on what you test and how, output of Dredd may contain sensitive data. + +Mind that if you run Dredd in a CI server provided as a service (such as `CircleCI `__, `Travis CI `__, etc.), you are disclosing the CLI output of Dredd to third parties. + +When using :ref:`Apiary Reporter and Apiary Tests `, you are sending your testing data to `Apiary `__ (Dredd creators and maintainers). See their `Terms of Service `__ and `Privacy Policy `__. Which data exactly is being sent to Apiary? + +- **Complete API description under test.** This means your API Blueprint or Swagger files. The API description is stored encrypted in Apiary. +- **Complete testing results.** Those can contain details of all requests made to the server under test and their responses. Apiary stores this data unencrypted, even if the original communication between Dredd and the API server under test happens to be over HTTPS. See :ref:`Apiary Reporter Test Data ` for detailed description of what is sent. You can :ref:`sanitize it before it gets sent `. +- **Little meta data about your environment.** Contents of environment variables ``TRAVIS``, ``CIRCLE``, ``CI``, ``DRONE``, ``BUILD_ID``, ``DREDD_AGENT``, ``USER``, and ``DREDD_HOSTNAME`` can be sent to Apiary. Your `hostname `__, version of your Dredd installation, and `type `__, `release `__ and `architecture `__ of your OS can be sent as well. Apiary stores this data unencrypted. + +See also :ref:`guidelines on how to develop Apiary Reporter `. + +.. _using-http-s-proxy: +.. _using-https-proxy: + +Using HTTP(S) Proxy +------------------- + +You can tell Dredd to use HTTP(S) proxy for: + +- downloading API description documents (the positional argument :option:`api-description-document` or the :option:`--path` option accepts also URL) +- :ref:`reporting to Apiary ` + +Dredd respects ``HTTP_PROXY``, ``HTTPS_PROXY``, ``NO_PROXY``, ``http_proxy``, ``https_proxy``, and ``no_proxy`` environment variables. For more information on how those work see `relevant section `__ of the underlying library’s documentation. + +Dredd intentionally **does not support HTTP(S) proxies for testing**. Proxy can deliberately modify requests and responses or to behave in a very different way then the server under test. Testing over a proxy is, in the first place, testing of the proxy itself. That makes the test results irrelevant (and hard to debug). diff --git a/docs/how-to-guides.md b/docs/how-to-guides.md deleted file mode 100644 index d5965b38a..000000000 --- a/docs/how-to-guides.md +++ /dev/null @@ -1,826 +0,0 @@ -# How-To Guides - -In the following guides you can find tips and best practices how to cope with some common tasks. While searching this page for particular keywords can give you quick results, reading the whole section should help you to learn some of the Dredd's core concepts and usual ways how to approach problems when testing with Dredd. - -## Isolation of HTTP Transactions - -Requests in the API description usually aren't sorted in order to comply with logical workflow of the tested application. To get the best results from testing with Dredd, you should ensure each resource action ([API Blueprint][]) or operation ([Swagger][]) is executed in isolated context. This can be easily achieved using [hooks](hooks.md), where you can provide your own setup and teardown code for each HTTP transaction. - -You should understand that testing with Dredd is an analogy to **unit tests** of your application code. In unit tests, each unit should be testable without any dependency on other units or previous tests. - -### Example - -Common case is to solve a situation where we want to test deleting of a resource. Obviously, to test deleting of a resource, we first need to create one. However, the order of HTTP transactions can be pretty much random in the API description. - -To solve the situation, it's recommended to isolate the deletion test by [hooks](hooks.md). Providing `before` hook, we can ensure the database fixture will be present every time Dredd will try to send the request to delete a category item. - -#### API Blueprint - -```apiblueprint -FORMAT: 1A - -# Categories API - -## Categories [/categories] - -### Create a Category [POST] -+ Response 201 - -## Category [/category/{id}] -+ Parameters - + id: 42 (required) - -### Delete a Category [DELETE] -+ Response 204 - -## Category Items [/category/{id}/items] -+ Parameters - + id: 42 (required) - -## Create an Item [POST] -+ Response 201 -``` - -To have an idea where we can hook our arbitrary code, we should first ask Dredd to list all available transaction names: - -``` -$ dredd api-description.apib http://127.0.0.1:3000 --names -info: Categories > Create a category -info: Category > Delete a category -info: Category Items > Create an item -``` - -Now we can create a `hooks.js` file. The file will contain setup and teardown of the database fixture: - -```javascript -hooks = require('hooks'); -db = require('./src/db'); - -beforeAll(function() { - db.cleanUp(); -}); - -afterEach(function(transaction) { - db.cleanUp(); -}); - -before('Category > Delete a Category', function() { - db.createCategory({id: 42}); -}); - -before('Category Items > Create an Item', function() { - db.createCategory({id: 42}); -}); -``` - -#### Swagger - -```yaml -swagger: "2.0" -info: - version: "0.0.0" - title: Categories API - license: - name: MIT -host: www.example.com -basePath: / -schemes: - - http -consumes: - - application/json -produces: - - application/json -paths: - /categories: - post: - responses: - 200: - description: "" - /category/{id}: - delete: - parameters: - - name: id - in: path - required: true - type: string - enum: - - "42" - responses: - 200: - description: "" - /category/{id}/items: - post: - parameters: - - name: id - in: path - required: true - type: string - enum: - - "42" - responses: - 200: - description: "" -``` - -To have an idea where we can hook our arbitrary code, we should first ask Dredd to list all available transaction names: - -``` -$ dredd api-description.yml http://127.0.0.1:3000 --names -info: /categories > POST > 200 > application/json -info: /category/{id} > DELETE > 200 > application/json -info: /category/{id}/items > POST > 200 > application/json -``` - -Now we can create a `hooks.js` file. The file will contain setup and teardown of the database fixture: - -```javascript -hooks = require('hooks'); -db = require('./src/db'); - -beforeAll(function() { - db.cleanUp(); -}); - -afterEach(function(transaction) { - db.cleanUp(); -}); - -before('/category/{id}', function() { - db.createCategory({id: 42}); -}); - -before('/category/{id}/items', function() { - db.createCategory({id: 42}); -}); -``` - -## Testing API Workflows - -Often you want to test a sequence of steps, a scenario, rather than just one request-response pair in isolation. Since the API description formats are quite limited in their support of documenting scenarios, Dredd probably isn't the best tool to provide you with this kind of testing. There are some tricks though, which can help you to work around some of the limitations. - -> **Note:** [API Blueprint][] prepares direct support for testing and scenarios. Interested? - Check out [apiaryio/api-blueprint#21](https://github.com/apiaryio/api-blueprint/issues/21)! - -To test various scenarios, you will want to write each of them into a separate API description document. To load them during a single test run, use the `--path` option ([docs](usage-cli.md#path-p)). - -For workflows to work properly, you'll also need to keep **shared context** between individual HTTP transactions. You can use [hooks](hooks.md) in order to achieve that. See tips on how to [pass data between transactions](hooks-nodejs.md#sharing-data-between-steps-in-request-stash). - -### API Blueprint Example - -Imagine we have a simple workflow described: - -```apiblueprint -FORMAT: 1A - -# My Scenario - -## POST /login - -+ Request (application/json) - - {"username": "john", "password": "d0e"} - - -+ Response 200 (application/json) - - {"token": "s3cr3t"} - -## GET /cars - -+ Response 200 (application/json) - - [ - {"id": "42", "color": "red"} - ] - -## PATCH /cars/{id} -+ Parameters - + id: 42 (string, required) - -+ Request (application/json) - - {"color": "yellow"} - -+ Response 200 (application/json) - - {"id": 42, "color": "yellow"} - -``` - -#### Writing Hooks - -To have an idea where we can hook our arbitrary code, we should first ask Dredd to list all available transaction names: - -``` -$ dredd api-description.apib http://127.0.0.1:3000 --names -info: /login > POST -info: /cars > GET -info: /cars/{id} > PATCH -``` - -Now we can create a `hooks.js` file. The code of the file will use global `stash` variable to share data between requests: - -```javascript -hooks = require('hooks'); -db = require('./src/db'); - -stash = {} - -// Stash the token we've got -after('/login > POST', function (transaction) { - stash.token = JSON.parse(transaction.real.body).token; -}); - -// Add the token to all HTTP transactions -beforeEach(function (transaction) { - if (stash.token) { - transaction.request.headers['X-Api-Key'] = stash.token - }; -}); - -// Stash the car ID we've got -after('/cars > GET', function (transaction) { - stash.carId = JSON.parse(transaction.real.body).id; -}); - -// Replace car ID in request with the one we've stashed -before('/cars/{id} > PATCH', function (transaction) { - transaction.fullPath = transaction.fullPath.replace('42', stash.carId) - transaction.request.uri = transaction.fullPath -}) -``` - -### Swagger Example - -Imagine we have a simple workflow described: - -```yaml -swagger: "2.0" -info: - version: "0.0.0" - title: Categories API - license: - name: MIT -host: www.example.com -basePath: / -schemes: - - http -consumes: - - application/json -produces: - - application/json -paths: - /login: - post: - parameters: - - name: body - in: body - required: true - schema: - type: object - properties: - username: - type: string - password: - type: string - responses: - 200: - description: "" - schema: - type: object - properties: - token: - type: string - /cars: - get: - responses: - 200: - description: "" - schema: - type: array - items: - type: object - properties: - id: - type: string - color: - type: string - /cars/{id}: - patch: - parameters: - - name: id - in: path - required: true - type: string - enum: - - "42" - - name: body - in: body - required: true - schema: - type: object - properties: - color: - type: string - responses: - 200: - description: "" - schema: - type: object - properties: - id: - type: string - color: - type: string -``` - -#### Writing Hooks - -To have an idea where we can hook our arbitrary code, we should first ask Dredd to list all available transaction names: - -``` -$ dredd api-description.yml http://127.0.0.1:3000 --names -info: /login > POST > 200 > application/json -info: /cars > GET > 200 > application/json -info: /cars/{id} > PATCH > 200 > application/json -``` - -Now we can create a `hooks.js` file. The code of the file will use global `stash` variable to share data between requests: - -```javascript -hooks = require('hooks'); -db = require('./src/db'); - -stash = {} - -// Stash the token we've got -after('/login > POST > 200 > application/json', function (transaction) { - stash.token = JSON.parse(transaction.real.body).token; -}); - -// Add the token to all HTTP transactions -beforeEach(function (transaction) { - if (stash.token) { - transaction.request.headers['X-Api-Key'] = stash.token - }; -}); - -// Stash the car ID we've got -after('/cars > GET > 200 > application/json', function (transaction) { - stash.carId = JSON.parse(transaction.real.body).id; -}); - -// Replace car ID in request with the one we've stashed -before('/cars/{id} > PATCH > 200 > application/json', function (transaction) { - transaction.fullPath = transaction.fullPath.replace('42', stash.carId) - transaction.request.uri = transaction.fullPath -}) -``` - -## Making Dredd Validation Stricter - -API Blueprint or Swagger files are usually created primarily with _documentation_ in mind. But what's enough for documentation doesn't need to be enough for _testing_. - -That applies to both [MSON][] (a language powering API Blueprint's [`+ Attributes`][apib-attributes-section] sections) and [JSON Schema][] (a language powering the Swagger format and API Blueprint's [`+ Schema`][apib-schema-section] sections). - -In following sections you can learn about how to deal with common scenarios. - -### Avoiding Additional Properties - -If you describe a JSON body which has attributes `name` and `size`, the following payload will be considered as correct: - -```json -{"name": "Sparta", "size": 300, "luck": false} -``` - -It's because in both [MSON][] and [JSON Schema][] additional properties are not forbidden by default. - -- In API Blueprint's [`+ Attributes`][apib-attributes-section] sections you can mark your object with [`fixed-type`][apib-fixed-type], which doesn't allow additional properties. -- In API Blueprint's [`+ Schema`][apib-schema-section] sections and in Swagger you can use [`additionalProperties: false`][json-schema-additional-properties] on the objects. - -### Requiring Properties - -If you describe a JSON body which has attributes `name` and `size`, the following payload will be considered as correct: - -```json -{"name": "Sparta"} -``` - -It's because properties are optional by default in both [MSON][] and [JSON Schema][] and you need to explicitly specify them as required. - -- In API Blueprint's [`+ Attributes`][apib-attributes-section] section, you can use [`required`][apib-required]. -- In API Blueprint's [`+ Schema`][apib-schema-section] sections and in Swagger you can use [`required`][json-schema-required], where you list the required properties. (Note this is true only for the [Draft v4][] JSON Schema, in older versions the `required` functionality was done differently.) - -### Validating Structure of Array Items - -If you describe an array of items, where each of the items should have a `name` property, the following payload will be considered as correct: - -```json -[{"name": "Sparta"}, {"title": "Athens"}, "Thebes"] -``` - -That's because in [MSON][], the default behavior is that you are specifying what _may_ appear in the array. - -- In API Blueprint's [`+ Attributes`][apib-attributes-section] sections you can mark your array with [`fixed-type`][apib-fixed-type], which doesn't allow array items of a different structure then specified. -- In API Blueprint's [`+ Schema`][apib-schema-section] sections and in Swagger make sure to learn about how [validation of arrays][json-schema-arrays] exactly works. - -### Validating Specific Values - -If you describe a JSON body which has attributes `name` and `size`, the following payload will be considered as correct: - -```json -{"name": "Sparta", "size": 42} -``` - -If the size should be always equal to 300, you need to specify the fact in your API description. - -- In API Blueprint's [`+ Attributes`][apib-attributes-section] sections you can mark your property with [`fixed`][apib-fixed], which turns the sample value into a required value. You can also use [`enum`][apib-enum] to provide a set of possible values. -- In API Blueprint's [`+ Schema`][apib-schema-section] sections and in Swagger you can use [`enum`][json-schema-enum] with one or more possible values. - -## Integrating Dredd with Your Test Suite - -Generally, if you want to add Dredd to your existing test suite, you can just save Dredd configuration in the `dredd.yml` file and add call for `dredd` command to your task runner. - -There are also some packages which make the integration a piece of cake: - -- [grunt-dredd](https://github.com/mfgea/grunt-dredd) -- [dredd-rack](https://github.com/gonzalo-bulnes/dredd-rack) -- [meteor-dredd](https://github.com/storeness/meteor-dredd) - -To find more, search for `dredd` in your favorite language's package index. - -## Continuous Integration - -It's a good practice to make Dredd part of your continuous integration workflow. Only that way you can ensure that application code you'll produce won't break the contract you provide in your API documentation. - -Dredd's interactive configuration wizard, `dredd init`, can help you with setting up `dredd.yml` configuration file and with modifying or generating CI configuration files for [Travis CI][] or [CircleCI][]. - -If you prefer to add Dredd yourself or you look for inspiration on how to add Dredd to other continuous integration services, see examples below. When testing in CI, always pin your Dredd version to a specific number and upgrade to newer releases manually. - - - -### `.circleci/config.yml` Configuration File for [CircleCI][] - -``` -version: 2 -jobs: - build: - docker: - - image: circleci/node:latest - steps: - - checkout - - run: npm install dredd@x.x.x --no-optional --global - - run: dredd apiary.apib http://127.0.0.1:3000 -``` - - - -### `.travis.yml` Configuration File for [Travis CI][] - -``` -before_install: - - npm install dredd@x.x.x --no-optional --global -before_script: - - dredd apiary.apib http://127.0.0.1:3000 -``` - -## Authenticated APIs - -Dredd supports all common authentication schemes: - -- Basic access authentication -- Digest access authentication -- OAuth (any version) -- CSRF tokens -- ... - -Use `user` setting in your configuration file or `--user` argument to provide HTTP basic authentication: - -``` ---user=user:password -``` - -Most of the authentication schemes use HTTP header for carrying the authentication data. If you don't want to add authentication HTTP header to every request in the API description, you can instruct Dredd to do it for you: - -``` ---header="Authorization: Basic YmVuOnBhc3M=" -``` - -## Sending Multipart Requests - -```apiblueprint -:[API Blueprint example](../test/fixtures/request/multipart-form-data.apib) -``` - -```yaml -:[Swagger example](../test/fixtures/request/multipart-form-data.yaml) -``` - -## Sending Form Data - -```apiblueprint -:[API Blueprint example](../test/fixtures/request/application-x-www-form-urlencoded.apib) -``` - -```yaml -:[Swagger example](../test/fixtures/request/application-x-www-form-urlencoded.yaml) -``` - -## Working with Images and other Binary Bodies - -The API description formats generally do not provide a way to describe binary content. The easiest solution is to describe only the media type, to [leave out the body](how-it-works.md#empty-response-body), and to handle the rest using [hooks](hooks.md). - -### Binary Request Body - -#### API Blueprint - -```apiblueprint -:[API Blueprint example](../test/fixtures/request/image-png.apib) -``` - -#### Swagger - -```yaml -:[Swagger example](../test/fixtures/request/image-png.yaml) -``` - -#### Hooks - -In hooks, you can populate the request body with real binary data. The data must be in a form of a [Base64-encoded](https://en.wikipedia.org/wiki/Base64) string. - -```javascript -:[Hooks example](../test/fixtures/request/image-png-hooks.js) -``` - -### Binary Response Body - -#### API Blueprint - -```apiblueprint -:[API Blueprint example](../test/fixtures/response/binary.apib) -``` - -#### Swagger - -```yaml -:[Swagger example](../test/fixtures/response/binary.yaml) -``` - -> **Note:** Do not use the explicit `binary` or `bytes` formats with response bodies, as Dredd is not able to properly work with those ([fury-adapter-swagger#193](https://github.com/apiaryio/fury-adapter-swagger/issues/193)). - -### Hooks - -In hooks, you can either assert the body: - -```javascript -:[Hooks example](../test/fixtures/response/binary-assert-body-hooks.js) -``` - -Or you can ignore it: - -```javascript -:[Hooks example](../test/fixtures/response/binary-ignore-body-hooks.js) -``` - -## Multiple Requests and Responses - -> **Note:** For details on this topic see also [How Dredd Works With HTTP Transactions](how-it-works.md#choosing-http-transactions). - -### API Blueprint - -To test multiple requests and responses within one action in Dredd, you need to cluster them into pairs: - -```apiblueprint -FORMAT: 1A - -# My API - -## Resource [/resource/{id}] - -+ Parameters - + id: 42 (required) - -### Update Resource [PATCH] - -+ Request (application/json) - - {"color": "yellow"} - - -+ Response 200 (application/json) - - {"color": "yellow", "id": 1} - - -+ Request Edge Case (application/json) - - {"weight": 1} - -+ Response 400 (application/vnd.error+json) - - {"message": "Validation failed"} - -``` - -Dredd will detect two HTTP transaction examples and will compile following transaction names: - -``` -$ dredd api-description.apib http://127.0.0.1 --names -info: Beginning Dredd testing... -info: Resource > Update Resource > Example 1 -info: Resource > Update Resource > Example 2 -``` - -In case you need to perform particular request with different URI parameters and standard inheritance of URI parameters isn't working for you, try [modifying transaction before its execution](hooks-nodejs.md#modifying-transaction-request-body-prior-to-execution) in hooks. - -### Swagger - -When using [Swagger][] format, by default Dredd tests only responses with `2xx` status codes. Responses with other codes are marked as _skipped_ and can be activated in [hooks](hooks.md): - -```javascript -var hooks = require('hooks'); - -hooks.before('/resource > GET > 500 > application/json', function (transaction, done) { - transaction.skip = false; - done(); -}); -``` - -## Using Apiary Reporter and Apiary Tests - -Command-line output of complex HTTP responses and expectations can be hard to read. To tackle the problem, you can use Dredd to send test reports to [Apiary][]. Apiary provides a comfortable interface for browsing complex test reports: - -``` -$ dredd apiary.apib http://127.0.0.1 --reporter=apiary -warn: Apiary API Key or API Project Subdomain were not provided. Configure Dredd to be able to save test reports alongside your Apiary API project: http://dredd.readthedocs.io/en/latest/how-to-guides/#using-apiary-reporter-and-apiary-tests -info: Beginning Dredd testing... -pass: DELETE /honey duration: 884ms -complete: 1 passing, 0 failing, 0 errors, 0 skipped, 1 total -complete: Tests took 1631ms -complete: See results in Apiary at: https://app.apiary.io/public/tests/run/74d20a82-55c5-49bb-aac9-a3a5a7450f06 -``` - -![Apiary Tests](_images/apiary-tests.png) - -### Saving Test Reports under Your Account in Apiary - -As you can see on the screenshot, the test reports are anonymous by default and will expire after some time. However, if you provide Apiary credentials, your test reports will appear on the _Tests_ page of your API Project. This is great especially for introspection of test reports from Continuous Integration. - -To get and setup credentials, just follow the tutorial in Apiary: - -![Apiary Tests Tutorial](_images/apiary-tests-tutorial.png) - -As you can see, the parameters go like this: - -``` -$ dredd -c apiaryApiKey: -c apiaryApiName: -``` - -In addition to using parameters and `dredd.yml`, you can also use environment variables: - -- `APIARY_API_KEY=` - Alternative way to pass credentials to Apiary Reporter. -- `APIARY_API_NAME=` - Alternative way to pass credentials to Apiary Reporter. - -When sending test reports to Apiary, Dredd inspects the environment where it was executed and sends some information about it alongside test results. Those are used mainly for detection whether the environment is Continuous Integration and also, they help you to identify individual test reports on the _Tests_ page. You can use the following variables to tell Dredd what to send: - -- agent (string) - `DREDD_AGENT` or current user in the OS -- hostname (string) - `DREDD_HOSTNAME` or hostname of the OS -- CI (boolean) - looks for `TRAVIS`, `CIRCLE`, `CI`, `DRONE`, `BUILD_ID`, ... - -## Example Values for Request Parameters - -While example values are natural part of the API Blueprint format, the Swagger -specification allows them only for `body` request parameters (`schema.example`). - -However, Dredd needs to know what values to use when testing described API, so -it supports `x-example` [vendor extension property][] to overcome the Swagger limitation: - -```yaml -... -paths: - /cars: - get: - parameters: - - name: limit - in: query - type: number - x-example: 42 -``` - -The `x-example` property is respected for all kinds of request parameters except -of `body` parameters, where native `schema.example` should be used. - -## Removing Sensitive Data from Test Reports - -Sometimes your API sends back sensitive information you don't want to get disclosed in [Apiary Tests](how-to-guides.md#using-apiary-reporter-and-apiary-tests) or in your CI log. In that case you can use [Hooks](hooks.md) to do sanitation. Before diving into examples below, do not forget to consider following: - -- Be sure to read [section about security](how-it-works.md#security) first. -- Only the [`transaction.test`](data-structures.md#transaction-test) object will make it to reporters. You don't have to care about sanitation of the rest of the [`transaction`](data-structures.md#transaction) object. -- The `transaction.test.message` and all the `transaction.test.results.body.results.rawData.*.message` properties contain validation error messages. While they're very useful for learning about what's wrong on command line, they can contain direct mentions of header names, header values, body properties, body structure, body values, etc., thus it's recommended their contents are completely removed to prevent unintended leaks of sensitive information. -- Without the `transaction.test.results.body.results.rawData` property [Apiary reporter](how-to-guides.md#using-apiary-reporter-and-apiary-tests) won't be able to render green/red difference between payloads. -- You can use [Ultimate 'afterEach' Guard](#sanitation-ultimate-guard) to make sure you won't leak any sensitive data by mistake. -- If your hooks crash, Dredd will send an error to reporters, alongside with current contents of the [`transaction.test`](data-structures.md#transaction-test) object. See the [Sanitation of Test Data of Transaction With Secured Erroring Hooks](#sanitation-secured-erroring-hooks) example to learn how to prevent this. - -### Sanitation of the Entire Request Body - -- [API Blueprint](https://github.com/apiaryio/dredd/blob/master/test/fixtures/sanitation/entire-request-body.apib) -- [Hooks](https://github.com/apiaryio/dredd/blob/master/test/fixtures/sanitation/entire-request-body.js) - -### Sanitation of the Entire Response Body - -- [API Blueprint](https://github.com/apiaryio/dredd/blob/master/test/fixtures/sanitation/entire-response-body.apib) -- [Hooks](https://github.com/apiaryio/dredd/blob/master/test/fixtures/sanitation/entire-response-body.js) - -### Sanitation of a Request Body Attribute - -- [API Blueprint](https://github.com/apiaryio/dredd/blob/master/test/fixtures/sanitation/request-body-attribute.apib) -- [Hooks](https://github.com/apiaryio/dredd/blob/master/test/fixtures/sanitation/request-body-attribute.js) - -### Sanitation of a Response Body Attribute - -- [API Blueprint](https://github.com/apiaryio/dredd/blob/master/test/fixtures/sanitation/response-body-attribute.apib) -- [Hooks](https://github.com/apiaryio/dredd/blob/master/test/fixtures/sanitation/response-body-attribute.js) - -### Sanitation of Plain Text Response Body by Pattern Matching - -- [API Blueprint](https://github.com/apiaryio/dredd/blob/master/test/fixtures/sanitation/plain-text-response-body.apib) -- [Hooks](https://github.com/apiaryio/dredd/blob/master/test/fixtures/sanitation/plain-text-response-body.js) - -### Sanitation of Request Headers - -- [API Blueprint](https://github.com/apiaryio/dredd/blob/master/test/fixtures/sanitation/request-headers.apib) -- [Hooks](https://github.com/apiaryio/dredd/blob/master/test/fixtures/sanitation/request-headers.js) - -### Sanitation of Response Headers - -- [API Blueprint](https://github.com/apiaryio/dredd/blob/master/test/fixtures/sanitation/response-headers.apib) -- [Hooks](https://github.com/apiaryio/dredd/blob/master/test/fixtures/sanitation/response-headers.js) - -### Sanitation of URI Parameters by Pattern Matching - -- [API Blueprint](https://github.com/apiaryio/dredd/blob/master/test/fixtures/sanitation/uri-parameters.apib) -- [Hooks](https://github.com/apiaryio/dredd/blob/master/test/fixtures/sanitation/uri-parameters.js) - -### Sanitation of Any Content by Pattern Matching - -- [API Blueprint](https://github.com/apiaryio/dredd/blob/master/test/fixtures/sanitation/any-content-pattern-matching.apib) -- [Hooks](https://github.com/apiaryio/dredd/blob/master/test/fixtures/sanitation/any-content-pattern-matching.js) - -### Sanitation of Test Data of Passing Transaction - -- [API Blueprint](https://github.com/apiaryio/dredd/blob/master/test/fixtures/sanitation/transaction-passing.apib) -- [Hooks](https://github.com/apiaryio/dredd/blob/master/test/fixtures/sanitation/transaction-passing.js) - -### Sanitation of Test Data When Transaction Is Marked as Failed in \'before\' Hook - -- [API Blueprint](https://github.com/apiaryio/dredd/blob/master/test/fixtures/sanitation/transaction-marked-failed-before.apib) -- [Hooks](https://github.com/apiaryio/dredd/blob/master/test/fixtures/sanitation/transaction-marked-failed-before.js) - -### Sanitation of Test Data When Transaction Is Marked as Failed in \'after\' Hook - -- [API Blueprint](https://github.com/apiaryio/dredd/blob/master/test/fixtures/sanitation/transaction-marked-failed-after.apib) -- [Hooks](https://github.com/apiaryio/dredd/blob/master/test/fixtures/sanitation/transaction-marked-failed-after.js) - -### Sanitation of Test Data When Transaction Is Marked as Skipped - -- [API Blueprint](https://github.com/apiaryio/dredd/blob/master/test/fixtures/sanitation/transaction-marked-skipped.apib) -- [Hooks](https://github.com/apiaryio/dredd/blob/master/test/fixtures/sanitation/transaction-marked-skipped.js) - - -### Ultimate 'afterEach' Guard Using Pattern Matching - -You can use this guard to make sure you won't leak any sensitive data by mistake. - -- [API Blueprint](https://github.com/apiaryio/dredd/blob/master/test/fixtures/sanitation/any-content-guard-pattern-matching.apib) -- [Hooks](https://github.com/apiaryio/dredd/blob/master/test/fixtures/sanitation/any-content-guard-pattern-matching.js) - - -### Sanitation of Test Data of Transaction With Secured Erroring Hooks - -If your hooks crash, Dredd will send an error to reporters, alongside with current contents of the [`transaction.test`](data-structures.md#transaction-test) object. If you want to prevent this, you need to add `try/catch` to your hooks, sanitize the test object, and gracefully fail the transaction. - -- [API Blueprint](https://github.com/apiaryio/dredd/blob/master/test/fixtures/sanitation/transaction-secured-erroring-hooks.apib) -- [Hooks](https://github.com/apiaryio/dredd/blob/master/test/fixtures/sanitation/transaction-secured-erroring-hooks.js) - - -[Apiary]: https://apiary.io/ -[API Blueprint]: https://apiblueprint.org/ -[Swagger]: https://swagger.io/ -[Travis CI]: https://travis-ci.org/ -[CircleCI]: https://circleci.com/ -[vendor extension property]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#user-content-vendorExtensions - -[MSON]: https://apiblueprint.org/documentation/mson/specification.html -[JSON Schema]: http://json-schema.org/ -[Draft v4]: https://tools.ietf.org/html/draft-zyp-json-schema-04 - -[apib-attributes-section]: https://apiblueprint.org/documentation/specification.html#def-attributes-section -[apib-schema-section]: https://apiblueprint.org/documentation/specification.html#def-schema-section -[apib-fixed-type]: https://apiblueprint.org/documentation/mson/specification.html#353-type-attribute -[apib-required]: https://apiblueprint.org/documentation/mson/specification.html#353-type-attribute -[apib-fixed]: https://apiblueprint.org/documentation/mson/specification.html#353-type-attribute -[apib-enum]: https://apiblueprint.org/documentation/mson/specification.html#212-structure-types -[json-schema-enum]: https://json-schema.org/understanding-json-schema/reference/generic.html#enumerated-values -[json-schema-additional-properties]: https://json-schema.org/understanding-json-schema/reference/object.html#properties -[json-schema-required]: https://json-schema.org/understanding-json-schema/reference/object.html#required-properties -[json-schema-arrays]: https://json-schema.org/understanding-json-schema/reference/array.html diff --git a/docs/how-to-guides.rst b/docs/how-to-guides.rst new file mode 100644 index 000000000..999b8eeaf --- /dev/null +++ b/docs/how-to-guides.rst @@ -0,0 +1,862 @@ +.. _how-to-guides: + +How-To Guides +============= + +In the following guides you can find tips and best practices how to cope with some common tasks. While searching this page for particular keywords can give you quick results, reading the whole section should help you to learn some of the Dredd’s core concepts and usual ways how to approach problems when testing with Dredd. + +Isolation of HTTP Transactions +------------------------------ + +Requests in the API description usually aren’t sorted in order to comply with logical workflow of the tested application. To get the best results from testing with Dredd, you should ensure each resource action (`API Blueprint `__) or operation (`Swagger `__) is executed in isolated context. This can be easily achieved using :ref:`hooks `, where you can provide your own setup and teardown code for each HTTP transaction. + +You should understand that testing with Dredd is an analogy to **unit tests** of your application code. In unit tests, each unit should be testable without any dependency on other units or previous tests. + +Example +~~~~~~~ + +Common case is to solve a situation where we want to test deleting of a resource. Obviously, to test deleting of a resource, we first need to create one. However, the order of HTTP transactions can be pretty much random in the API description. + +To solve the situation, it’s recommended to isolate the deletion test by :ref:`hooks `. Providing ``before`` hook, we can ensure the database fixture will be present every time Dredd will try to send the request to delete a category item. + +API Blueprint +^^^^^^^^^^^^^ + +.. code-block:: apiblueprint + + FORMAT: 1A + + # Categories API + + ## Categories [/categories] + + ### Create a Category [POST] + + Response 201 + + ## Category [/category/{id}] + + Parameters + + id: 42 (required) + + ### Delete a Category [DELETE] + + Response 204 + + ## Category Items [/category/{id}/items] + + Parameters + + id: 42 (required) + + ## Create an Item [POST] + + Response 201 + +To have an idea where we can hook our arbitrary code, we should first ask Dredd to list all available transaction names: + +:: + + $ dredd api-description.apib http://127.0.0.1:3000 --names + info: Categories > Create a category + info: Category > Delete a category + info: Category Items > Create an item + +Now we can create a ``hooks.js`` file. The file will contain setup and teardown of the database fixture: + +.. code-block:: javascript + + hooks = require('hooks'); + db = require('./src/db'); + + beforeAll(function() { + db.cleanUp(); + }); + + afterEach(function(transaction) { + db.cleanUp(); + }); + + before('Category > Delete a Category', function() { + db.createCategory({id: 42}); + }); + + before('Category Items > Create an Item', function() { + db.createCategory({id: 42}); + }); + +Swagger +^^^^^^^ + +.. code-block:: yaml + + swagger: "2.0" + info: + version: "0.0.0" + title: Categories API + license: + name: MIT + host: www.example.com + basePath: / + schemes: + - http + consumes: + - application/json + produces: + - application/json + paths: + /categories: + post: + responses: + 200: + description: "" + /category/{id}: + delete: + parameters: + - name: id + in: path + required: true + type: string + enum: + - "42" + responses: + 200: + description: "" + /category/{id}/items: + post: + parameters: + - name: id + in: path + required: true + type: string + enum: + - "42" + responses: + 200: + description: "" + +To have an idea where we can hook our arbitrary code, we should first ask Dredd to list all available transaction names: + +:: + + $ dredd api-description.yml http://127.0.0.1:3000 --names + info: /categories > POST > 200 > application/json + info: /category/{id} > DELETE > 200 > application/json + info: /category/{id}/items > POST > 200 > application/json + +Now we can create a ``hooks.js`` file. The file will contain setup and teardown of the database fixture: + +.. code-block:: javascript + + hooks = require('hooks'); + db = require('./src/db'); + + beforeAll(function() { + db.cleanUp(); + }); + + afterEach(function(transaction) { + db.cleanUp(); + }); + + before('/category/{id}', function() { + db.createCategory({id: 42}); + }); + + before('/category/{id}/items', function() { + db.createCategory({id: 42}); + }); + +Testing API Workflows +--------------------- + +Often you want to test a sequence of steps, a scenario, rather than just one request-response pair in isolation. Since the API description formats are quite limited in their support of documenting scenarios, Dredd probably isn’t the best tool to provide you with this kind of testing. There are some tricks though, which can help you to work around some of the limitations. + +.. note:: + `API Blueprint `__ prepares direct support for testing and scenarios. Interested? Check out `apiaryio/api-blueprint#21 `__! + +To test various scenarios, you will want to write each of them into a separate API description document. To load them during a single test run, use the :option:`--path` option. + +For workflows to work properly, you’ll also need to keep **shared context** between individual HTTP transactions. You can use :ref:`hooks ` in order to achieve that. See tips on how to :ref:`pass data between transactions `. + +API Blueprint Example +~~~~~~~~~~~~~~~~~~~~~ + +Imagine we have a simple workflow described: + +.. code-block:: apiblueprint + + FORMAT: 1A + + # My Scenario + + ## POST /login + + + Request (application/json) + + {"username": "john", "password": "d0e"} + + + + Response 200 (application/json) + + {"token": "s3cr3t"} + + ## GET /cars + + + Response 200 (application/json) + + [ + {"id": "42", "color": "red"} + ] + + ## PATCH /cars/{id} + + Parameters + + id: 42 (string, required) + + + Request (application/json) + + {"color": "yellow"} + + + Response 200 (application/json) + + {"id": 42, "color": "yellow"} + +Writing Hooks +^^^^^^^^^^^^^ + +To have an idea where we can hook our arbitrary code, we should first ask Dredd to list all available transaction names: + +:: + + $ dredd api-description.apib http://127.0.0.1:3000 --names + info: /login > POST + info: /cars > GET + info: /cars/{id} > PATCH + +Now we can create a ``hooks.js`` file. The code of the file will use global ``stash`` variable to share data between requests: + +.. code-block:: javascript + + hooks = require('hooks'); + db = require('./src/db'); + + stash = {} + + // Stash the token we've got + after('/login > POST', function (transaction) { + stash.token = JSON.parse(transaction.real.body).token; + }); + + // Add the token to all HTTP transactions + beforeEach(function (transaction) { + if (stash.token) { + transaction.request.headers['X-Api-Key'] = stash.token + }; + }); + + // Stash the car ID we've got + after('/cars > GET', function (transaction) { + stash.carId = JSON.parse(transaction.real.body).id; + }); + + // Replace car ID in request with the one we've stashed + before('/cars/{id} > PATCH', function (transaction) { + transaction.fullPath = transaction.fullPath.replace('42', stash.carId) + transaction.request.uri = transaction.fullPath + }) + +Swagger Example +~~~~~~~~~~~~~~~ + +Imagine we have a simple workflow described: + +.. code-block:: yaml + + swagger: "2.0" + info: + version: "0.0.0" + title: Categories API + license: + name: MIT + host: www.example.com + basePath: / + schemes: + - http + consumes: + - application/json + produces: + - application/json + paths: + /login: + post: + parameters: + - name: body + in: body + required: true + schema: + type: object + properties: + username: + type: string + password: + type: string + responses: + 200: + description: "" + schema: + type: object + properties: + token: + type: string + /cars: + get: + responses: + 200: + description: "" + schema: + type: array + items: + type: object + properties: + id: + type: string + color: + type: string + /cars/{id}: + patch: + parameters: + - name: id + in: path + required: true + type: string + enum: + - "42" + - name: body + in: body + required: true + schema: + type: object + properties: + color: + type: string + responses: + 200: + description: "" + schema: + type: object + properties: + id: + type: string + color: + type: string + +Writing Hooks +^^^^^^^^^^^^^ + +To have an idea where we can hook our arbitrary code, we should first ask Dredd to list all available transaction names: + +:: + + $ dredd api-description.yml http://127.0.0.1:3000 --names + info: /login > POST > 200 > application/json + info: /cars > GET > 200 > application/json + info: /cars/{id} > PATCH > 200 > application/json + +Now we can create a ``hooks.js`` file. The code of the file will use global ``stash`` variable to share data between requests: + +.. code-block:: javascript + + hooks = require('hooks'); + db = require('./src/db'); + + stash = {} + + // Stash the token we've got + after('/login > POST > 200 > application/json', function (transaction) { + stash.token = JSON.parse(transaction.real.body).token; + }); + + // Add the token to all HTTP transactions + beforeEach(function (transaction) { + if (stash.token) { + transaction.request.headers['X-Api-Key'] = stash.token + }; + }); + + // Stash the car ID we've got + after('/cars > GET > 200 > application/json', function (transaction) { + stash.carId = JSON.parse(transaction.real.body).id; + }); + + // Replace car ID in request with the one we've stashed + before('/cars/{id} > PATCH > 200 > application/json', function (transaction) { + transaction.fullPath = transaction.fullPath.replace('42', stash.carId) + transaction.request.uri = transaction.fullPath + }) + +Making Dredd Validation Stricter +-------------------------------- + +API Blueprint or Swagger files are usually created primarily with *documentation* in mind. But what’s enough for documentation doesn’t need to be enough for *testing*. + +That applies to both `MSON `__ (a language powering API Blueprint’s `Attributes `__ sections) and `JSON Schema `__ (a language powering the Swagger format and API Blueprint’s `Schema `__ sections). + +In following sections you can learn about how to deal with common scenarios. + +Avoiding Additional Properties +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you describe a JSON body which has attributes ``name`` and ``size``, the following payload will be considered as correct: + +.. code-block:: json + + {"name": "Sparta", "size": 300, "luck": false} + +It’s because in both `MSON `__ and `JSON Schema `__ additional properties are not forbidden by default. + +- In API Blueprint’s `Attributes `__ sections you can mark your object with ```fixed-type`` `__, which doesn’t allow additional properties. +- In API Blueprint’s `Schema `__ sections and in Swagger you can use ``additionalProperties: false`` (`docs `__) on the objects. + +Requiring Properties +~~~~~~~~~~~~~~~~~~~~ + +If you describe a JSON body which has attributes ``name`` and ``size``, the following payload will be considered as correct: + +.. code-block:: json + + {"name": "Sparta"} + +It’s because properties are optional by default in both `MSON `__ and `JSON Schema `__ and you need to explicitly specify them as required. + +- In API Blueprint’s `Attributes `__ section, you can use ```required`` `__. +- In API Blueprint’s `Schema `__ sections and in Swagger you can use ``required`` (`docs `__), where you list the required properties. (Note this is true only for the `Draft v4 `__ JSON Schema, in older versions the ``required`` functionality was done differently.) + +Validating Structure of Array Items +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you describe an array of items, where each of the items should have a ``name`` property, the following payload will be considered as correct: + +.. code-block:: json + + [{"name": "Sparta"}, {"title": "Athens"}, "Thebes"] + +That’s because in `MSON `__, the default behavior is that you are specifying what *may* appear in the array. + +- In API Blueprint’s `Attributes `__ sections you can mark your array with ``fixed-type`` (`docs `__), which doesn’t allow array items of a different structure then specified. +- In API Blueprint’s `Schema `__ sections and in Swagger make sure to learn about how `validation of arrays `__ exactly works. + +Validating Specific Values +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you describe a JSON body which has attributes ``name`` and ``size``, the following payload will be considered as correct: + +.. code-block:: json + + {"name": "Sparta", "size": 42} + +If the size should be always equal to 300, you need to specify the fact in your API description. + +- In API Blueprint’s `Attributes `__ sections you can mark your property with ``fixed`` (`docs `__), which turns the sample value into a required value. You can also use ``enum`` (`docs `__) to provide a set of possible values. +- In API Blueprint’s `Schema `__ sections and in Swagger you can use ``enum`` (`docs `__) with one or more possible values. + +Integrating Dredd with Your Test Suite +-------------------------------------- + +Generally, if you want to add Dredd to your existing test suite, you can just save Dredd configuration in the ``dredd.yml`` file and add call for ``dredd`` command to your task runner. + +There are also some packages which make the integration a piece of cake: + +- `grunt-dredd `__ +- `dredd-rack `__ +- `meteor-dredd `__ + +To find more, search for ``dredd`` in your favorite language’s package index. + +.. _continuous-integration: + +Continuous Integration +---------------------- + +It’s a good practice to make Dredd part of your continuous integration workflow. Only that way you can ensure that application code you’ll produce won’t break the contract you provide in your API documentation. + +Dredd’s interactive configuration wizard, ``dredd init``, can help you with setting up ``dredd.yml`` configuration file and with modifying or generating CI configuration files for `Travis CI `__ or `CircleCI `__. + +If you prefer to add Dredd yourself or you look for inspiration on how to add Dredd to other continuous integration services, see examples below. When testing in CI, always pin your Dredd version to a specific number and upgrade to newer releases manually. + +.. _circleyml-configuration-file-for-circleci: + +``.circleci/config.yml`` Configuration File for `CircleCI `__ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + version: 2 + jobs: + build: + docker: + - image: circleci/node:latest + steps: + - checkout + - run: npm install dredd@x.x.x --no-optional --global + - run: dredd apiary.apib http://127.0.0.1:3000 + +.. _travisyml-configuration-file-for-travis-ci: + +``.travis.yml`` Configuration File for `Travis CI `__ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + before_install: + - npm install dredd@x.x.x --no-optional --global + before_script: + - dredd apiary.apib http://127.0.0.1:3000 + +Authenticated APIs +------------------ + +Dredd supports all common authentication schemes: + +- Basic access authentication +- Digest access authentication +- OAuth (any version) +- CSRF tokens +- … + +Use ``user`` setting in your configuration file or the :option:`--user` option to provide HTTP basic authentication: + +:: + + --user=user:password + +Most of the authentication schemes use HTTP header for carrying the authentication data. If you don’t want to add authentication HTTP header to every request in the API description, you can instruct Dredd to do it for you by the :option:`--header` option: + +:: + + --header="Authorization: Basic YmVuOnBhc3M=" + +Sending Multipart Requests +-------------------------- + +.. literalinclude:: ../test/fixtures/request/multipart-form-data.apib + :language: apiblueprint + +.. literalinclude:: ../test/fixtures/request/multipart-form-data.yaml + :language: yaml + +Sending Form Data +----------------- + +.. literalinclude:: ../test/fixtures/request/application-x-www-form-urlencoded.apib + :language: apiblueprint + +.. literalinclude:: ../test/fixtures/request/application-x-www-form-urlencoded.yaml + :language: yaml + +Working with Images and other Binary Bodies +------------------------------------------- + +The API description formats generally do not provide a way to describe binary content. The easiest solution is to describe only the media type, to :ref:`leave out the body `, and to handle the rest using :ref:`hooks`. + +Binary Request Body +~~~~~~~~~~~~~~~~~~~ + +API Blueprint +^^^^^^^^^^^^^ + +.. literalinclude:: ../test/fixtures/request/image-png.apib + :language: apiblueprint + +Swagger +^^^^^^^ + +.. literalinclude:: ../test/fixtures/request/image-png.yaml + :language: yaml + +Hooks +^^^^^ + +In hooks, you can populate the request body with real binary data. The data must be in a form of a `Base64-encoded `__ string. + +.. literalinclude:: ../test/fixtures/request/image-png-hooks.js + :language: javascript + +Binary Response Body +~~~~~~~~~~~~~~~~~~~~ + +API Blueprint +^^^^^^^^^^^^^ + +.. literalinclude:: ../test/fixtures/response/binary.apib + :language: apiblueprint + +Swagger +^^^^^^^ + +.. literalinclude:: ../test/fixtures/response/binary.yaml + :language: yaml + +.. note:: + Do not use the explicit ``binary`` or ``bytes`` formats with response bodies, as Dredd is not able to properly work with those (`fury-adapter-swagger#193 `__). + +Hooks +~~~~~ + +In hooks, you can either assert the body: + +.. literalinclude:: ../test/fixtures/response/binary-assert-body-hooks.js + :language: javascript + +Or you can ignore it: + +.. literalinclude:: ../test/fixtures/response/binary-ignore-body-hooks.js + :language: javascript + +.. _multiple-requests-and-responses: + +Multiple Requests and Responses +------------------------------- + +.. note:: + For details on this topic see also :ref:`How Dredd Works With HTTP Transactions `. + +API Blueprint +~~~~~~~~~~~~~ + +To test multiple requests and responses within one action in Dredd, you need to cluster them into pairs: + +.. code-block:: apiblueprint + + FORMAT: 1A + + # My API + + ## Resource [/resource/{id}] + + + Parameters + + id: 42 (required) + + ### Update Resource [PATCH] + + + Request (application/json) + + {"color": "yellow"} + + + + Response 200 (application/json) + + {"color": "yellow", "id": 1} + + + + Request Edge Case (application/json) + + {"weight": 1} + + + Response 400 (application/vnd.error+json) + + {"message": "Validation failed"} + +Dredd will detect two HTTP transaction examples and will compile following transaction names: + +:: + + $ dredd api-description.apib http://127.0.0.1 --names + info: Beginning Dredd testing... + info: Resource > Update Resource > Example 1 + info: Resource > Update Resource > Example 2 + +In case you need to perform particular request with different URI parameters and standard inheritance of URI parameters isn’t working for you, try :ref:`modifying transaction before its execution ` in hooks. + +Swagger +~~~~~~~ + +When using `Swagger `__ format, by default Dredd tests only responses with ``2xx`` status codes. Responses with other codes are marked as *skipped* and can be activated in :ref:`hooks `: + +.. code-block:: javascript + + var hooks = require('hooks'); + + hooks.before('/resource > GET > 500 > application/json', function (transaction, done) { + transaction.skip = false; + done(); + }); + +.. _using-apiary-reporter-and-apiary-tests: + +Using Apiary Reporter and Apiary Tests +-------------------------------------- + +Command-line output of complex HTTP responses and expectations can be hard to read. To tackle the problem, you can use Dredd to send test reports to `Apiary `__. Apiary provides a comfortable interface for browsing complex test reports: + +:: + + $ dredd apiary.apib http://127.0.0.1 --reporter=apiary + warn: Apiary API Key or API Project Subdomain were not provided. Configure Dredd to be able to save test reports alongside your Apiary API project: http://dredd.readthedocs.io/en/latest/how-to-guides/#using-apiary-reporter-and-apiary-tests + info: Beginning Dredd testing... + pass: DELETE /honey duration: 884ms + complete: 1 passing, 0 failing, 0 errors, 0 skipped, 1 total + complete: Tests took 1631ms + complete: See results in Apiary at: https://app.apiary.io/public/tests/run/74d20a82-55c5-49bb-aac9-a3a5a7450f06 + +.. figure:: _images/apiary-tests.png + :alt: Apiary Tests + + Apiary Tests + +Saving Test Reports under Your Account in Apiary +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +As you can see on the screenshot, the test reports are anonymous by default and will expire after some time. However, if you provide Apiary credentials, your test reports will appear on the *Tests* page of your API Project. This is great especially for introspection of test reports from Continuous Integration. + +To get and setup credentials, just follow the tutorial in Apiary: + +.. figure:: _images/apiary-tests-tutorial.png + :alt: Apiary Tests Tutorial + + Apiary Tests Tutorial + +As you can see, the parameters go like this: + +:: + + $ dredd -c apiaryApiKey: -c apiaryApiName: + +In addition to using parameters and ``dredd.yml``, you can also use environment variables: + +- ``APIARY_API_KEY=`` - Alternative way to pass credentials to Apiary Reporter. +- ``APIARY_API_NAME=`` - Alternative way to pass credentials to Apiary Reporter. + +When sending test reports to Apiary, Dredd inspects the environment where it was executed and sends some information about it alongside test results. Those are used mainly for detection whether the environment is Continuous Integration and also, they help you to identify individual test reports on the *Tests* page. You can use the following variables to tell Dredd what to send: + +- agent (string) - ``DREDD_AGENT`` or current user in the OS +- hostname (string) - ``DREDD_HOSTNAME`` or hostname of the OS +- CI (boolean) - looks for ``TRAVIS``, ``CIRCLE``, ``CI``, ``DRONE``, ``BUILD_ID``, … + +.. _example-values-for-request-parameters: + +Example Values for Request Parameters +------------------------------------- + +While example values are natural part of the API Blueprint format, the Swagger specification allows them only for ``body`` request parameters (``schema.example``). + +However, Dredd needs to know what values to use when testing described API, so it supports ``x-example`` `vendor extension property `__ to overcome the Swagger limitation: + +.. code-block:: yaml + + ... + paths: + /cars: + get: + parameters: + - name: limit + in: query + type: number + x-example: 42 + +The ``x-example`` property is respected for all kinds of request parameters except of ``body`` parameters, where native ``schema.example`` should be used. + +.. _removing-sensitive-data-from-test-reports: + +Removing Sensitive Data from Test Reports +----------------------------------------- + +Sometimes your API sends back sensitive information you don’t want to get disclosed in :ref:`Apiary Tests ` or in your CI log. In that case you can use :ref:`Hooks ` to do sanitation. Before diving into examples below, do not forget to consider following: + +- Be sure to read :ref:`section about security ` first. +- Only the ``transaction.test`` (:ref:`docs `) object will make it to reporters. You don’t have to care about sanitation of the rest of the ``transaction`` (:ref:`docs `) object. +- The ``transaction.test.message`` and all the ``transaction.test.results.body.results.rawData.*.message`` properties contain validation error messages. While they’re very useful for learning about what’s wrong on command line, they can contain direct mentions of header names, header values, body properties, body structure, body values, etc., thus it’s recommended their contents are completely removed to prevent unintended leaks of sensitive information. +- Without the ``transaction.test.results.body.results.rawData`` property :ref:`Apiary reporter ` won’t be able to render green/red difference between payloads. +- You can use :ref:`Ultimate ‘afterEach’ Guard ` to make sure you won’t leak any sensitive data by mistake. +- If your hooks crash, Dredd will send an error to reporters, alongside with current contents of the ``transaction.test`` (:ref:`docs `) object. See the :ref:`Sanitation of Test Data of Transaction With Secured Erroring Hooks ` example to learn how to prevent this. + +Sanitation of the Entire Request Body +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- `API Blueprint `__ +- `Hooks `__ + +Sanitation of the Entire Response Body +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- `API Blueprint `__ +- `Hooks `__ + +Sanitation of a Request Body Attribute +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- `API Blueprint `__ +- `Hooks `__ + +Sanitation of a Response Body Attribute +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- `API Blueprint `__ +- `Hooks `__ + +Sanitation of Plain Text Response Body by Pattern Matching +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- `API Blueprint `__ +- `Hooks `__ + +Sanitation of Request Headers +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- `API Blueprint `__ +- `Hooks `__ + +Sanitation of Response Headers +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- `API Blueprint `__ +- `Hooks `__ + +Sanitation of URI Parameters by Pattern Matching +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- `API Blueprint `__ +- `Hooks `__ + +Sanitation of Any Content by Pattern Matching +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- `API Blueprint `__ +- `Hooks `__ + +Sanitation of Test Data of Passing Transaction +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- `API Blueprint `__ +- `Hooks `__ + +Sanitation of Test Data When Transaction Is Marked as Failed in 'before' Hook +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- `API Blueprint `__ +- `Hooks `__ + +Sanitation of Test Data When Transaction Is Marked as Failed in 'after' Hook +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- `API Blueprint `__ +- `Hooks `__ + +Sanitation of Test Data When Transaction Is Marked as Skipped +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- `API Blueprint `__ +- `Hooks `__ + +.. _sanitation-ultimate-guard: + +Ultimate ‘afterEach’ Guard Using Pattern Matching +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can use this guard to make sure you won’t leak any sensitive data by mistake. + +- `API Blueprint `__ +- `Hooks `__ + +.. _sanitation-secured-erroring-hooks: + +Sanitation of Test Data of Transaction With Secured Erroring Hooks +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If your hooks crash, Dredd will send an error to reporters, alongside with current contents of the ``transaction.test`` (:ref:`docs `) object. If you want to prevent this, you need to add ``try/catch`` to your hooks, sanitize the test object, and gracefully fail the transaction. + +- `API Blueprint `__ +- `Hooks `__ diff --git a/docs/index.md b/docs/index.md deleted file mode 100644 index 7e8d56964..000000000 --- a/docs/index.md +++ /dev/null @@ -1,89 +0,0 @@ -# Dredd — HTTP API Testing Framework - -[![npm version](https://badge.fury.io/js/dredd.svg)](https://www.npmjs.com/package/dredd) -[![Build Status](https://travis-ci.org/apiaryio/dredd.svg?branch=master)](https://travis-ci.org/apiaryio/dredd) -[![Build Status](https://ci.appveyor.com/api/projects/status/n3ixfxh72qushyr4/branch/master?svg=true)](https://ci.appveyor.com/project/Apiary/dredd/branch/master) -[![Dependency Status](https://david-dm.org/apiaryio/dredd.svg)](https://david-dm.org/apiaryio/dredd) -[![devDependency Status](https://david-dm.org/apiaryio/dredd/dev-status.svg)](https://david-dm.org/apiaryio/dredd?type=dev) -[![Documentation Status](https://readthedocs.org/projects/dredd/badge/?version=latest)](https://dredd.readthedocs.io/en/latest/) -[![Coverage Status](https://coveralls.io/repos/apiaryio/dredd/badge.svg?branch=master)](https://coveralls.io/github/apiaryio/dredd) -[![Known Vulnerabilities](https://snyk.io/test/npm/dredd/badge.svg)](https://snyk.io/test/npm/dredd) - -![Dredd - HTTP API Testing Framework](_images/dredd.png) - -> **Dredd is a language-agnostic command-line tool for validating -API description document against backend implementation of the API.** - -Dredd reads your API description and step by step validates whether your API -implementation replies with responses as they are described in the -documentation. - -## Features - -### Supported API Description Formats - -- [API Blueprint][] -- [Swagger][] - -### Supported Hooks Languages - -Dredd supports writing [hooks](hooks.md) — a glue code for each test setup and teardown. Following languages are supported: - -- [Go](hooks-go.md) -- [Node.js (JavaScript)](hooks-nodejs.md) -- [Perl](hooks-perl.md) -- [PHP](hooks-php.md) -- [Python](hooks-python.md) -- [Ruby](hooks-ruby.md) -- [Rust](hooks-rust.md) -- Didn't find your favorite language? _[Add a new one!](hooks-new-language.md)_ - -### Supported Systems - -- Linux, macOS, Windows, ... -- [Travis CI][], [CircleCI][], [Jenkins][], [AppVeyor][], ... - -## Contents - -- [Installation](installation.md) -- [Quickstart](quickstart.md) -- [How It Works](how-it-works.md) -- [How-To Guides](how-to-guides.md) -- [Usage: CLI](usage-cli.md) -- [Usage: JavaScript](usage-js.md) -- [About Hooks](hooks.md) -- [Hooks: Go](hooks-go.md) -- [Hooks: JavaScript (Sandboxed)](hooks-js-sandbox.md) -- [Hooks: Node.js](hooks-nodejs.md) -- [Hooks: Perl](hooks-perl.md) -- [Hooks: PHP](hooks-php.md) -- [Hooks: Python](hooks-python.md) -- [Hooks: Ruby](hooks-ruby.md) -- [Hooks: Rust](hooks-rust.md) -- [Hooks: Other Languages](hooks-new-language.md) -- [Data Structures](data-structures.md) -- [Contributing](contributing.md) - -## Useful Links - -- [GitHub Repository][] -- [Bug Tracker][] -- [Changelog][] - -## Example Applications - -- [Express.js](https://github.com/apiaryio/dredd-example) -- [Ruby on Rails](https://gitlab.com/theodorton/dredd-test-rails/) - - -[API Blueprint]: https://apiblueprint.org/ -[Swagger]: https://swagger.io/ - -[GitHub Repository]: https://github.com/apiaryio/dredd -[Bug Tracker]: https://github.com/apiaryio/dredd/issues?q=is%3Aopen -[Changelog]: https://github.com/apiaryio/dredd/releases - -[Travis CI]: https://travis-ci.org/ -[CircleCI]: https://circleci.com/ -[Jenkins]: https://jenkins.io/ -[AppVeyor]: https://www.appveyor.com/ diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 000000000..615d8a94c --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,99 @@ +.. _index: + +Dredd — HTTP API Testing Framework +================================== + +|npm version| |Build Status| |Windows Build Status| |Dependency Status| |devDependency Status| |Documentation Status| |Coverage Status| |Known Vulnerabilities| + +.. figure:: _images/dredd.png + :alt: Dredd - HTTP API Testing Framework + +.. + + **Dredd is a language-agnostic command-line tool for validating API description document against backend implementation of the API.** + +Dredd reads your API description and step by step validates whether your API implementation replies with responses as they are described in the documentation. + +Features +-------- + +Supported API Description Formats +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- `API Blueprint `__ +- `Swagger `__ + +Supported Hooks Languages +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Dredd supports writing :ref:`hooks ` — a glue code for each test setup and teardown. Following languages are supported: + +- :ref:`Go ` +- :ref:`Node.js (JavaScript) ` +- :ref:`Perl ` +- :ref:`PHP ` +- :ref:`Python ` +- :ref:`Ruby ` +- :ref:`Rust ` +- Didn’t find your favorite language? :ref:`Add a new one! ` + +Supported Systems +~~~~~~~~~~~~~~~~~ + +- Linux, macOS, Windows, … +- `Travis CI `__, `CircleCI `__, `Jenkins `__, `AppVeyor `__, … + +Contents +-------- + +.. toctree:: + :maxdepth: 1 + + installation + quickstart + how-it-works + how-to-guides + Usage: CLI + Usage: JavaScript + About Hooks + Hooks: Go + Hooks: JavaScript (Sandboxed) + Hooks: Node.js + Hooks: Perl + Hooks: PHP + Hooks: Python + Hooks: Ruby + Hooks: Rust + Hooks: Other languages + data-structures + contributing + +Useful Links +------------ + +- `GitHub Repository `__ +- `Bug Tracker `__ +- `Changelog `__ + +Example Applications +-------------------- + +- `Express.js `__ +- `Ruby on Rails `__ + +.. |npm version| image:: https://badge.fury.io/js/dredd.svg + :target: https://www.npmjs.com/package/dredd +.. |Build Status| image:: https://travis-ci.org/apiaryio/dredd.svg?branch=master + :target: https://travis-ci.org/apiaryio/dredd +.. |Windows Build Status| image:: https://ci.appveyor.com/api/projects/status/n3ixfxh72qushyr4/branch/master?svg=true + :target: https://ci.appveyor.com/project/Apiary/dredd/branch/master +.. |Dependency Status| image:: https://david-dm.org/apiaryio/dredd.svg + :target: https://david-dm.org/apiaryio/dredd +.. |devDependency Status| image:: https://david-dm.org/apiaryio/dredd/dev-status.svg + :target: https://david-dm.org/apiaryio/dredd?type=dev +.. |Documentation Status| image:: https://readthedocs.org/projects/dredd/badge/?version=latest + :target: https://dredd.readthedocs.io/en/latest/ +.. |Coverage Status| image:: https://coveralls.io/repos/apiaryio/dredd/badge.svg?branch=master + :target: https://coveralls.io/github/apiaryio/dredd +.. |Known Vulnerabilities| image:: https://snyk.io/test/npm/dredd/badge.svg + :target: https://snyk.io/test/npm/dredd diff --git a/docs/install-node.sh b/docs/install-node.sh deleted file mode 100755 index 539d24d18..000000000 --- a/docs/install-node.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash -# In the ReadTheDocs build, this file is ran by Sphinx' conf.py to install -# support for Node.js. - -set -e - - -if [ "$READTHEDOCS" = 'True' ]; then - if [ ! -d ~/.nvm ]; then - git clone git://github.com/creationix/nvm.git ~/.nvm - fi - . ~/.nvm/nvm.sh - - nvm install 10 - nvm use 10 - - npm install --no-optional - - echo $(which node) -fi diff --git a/docs/installation.md b/docs/installation.md deleted file mode 100644 index 1d5b6acd1..000000000 --- a/docs/installation.md +++ /dev/null @@ -1,110 +0,0 @@ -# Installation - -Dredd is a command-line application written in JavaScript. To run it on your machine or in your [Continuous Integration server][CI], you first need to have [Node.js][] installed. - - - -## Install Node.js - -### macOS - -1. Install Node.js. - - If you're using [Homebrew][], run `brew install node`. - - Otherwise [download Node.js][Download Node.js] from the official website and install Node.js using the downloaded installer. -2. Make sure both `node --version` and `npm --version` work in your Terminal. - -### Windows - -1. [Download Node.js][] from the official website and install Node.js using the downloaded installer. -2. Make sure both `node --version` and `npm --version` work in your Command Prompt. - -### Linux - -1. [Install Node.js as system package][]. -2. Make sure both `node --version` and `npm --version` work in your Terminal. - -### Pro Tips - -- [Continuous Integration section in the How-To Guides](how-to-guides.md#continuous-integration) can help you to install Dredd on CI server. -- To maintain multiple Node.js versions on your computer, check out [nvm][]. - -## Install Dredd - -1. `npm install -g dredd` -2. `dredd --version` - -If the second command works, you're done! - -### Globally vs locally - -The `-g` ensures Dredd will be installed "globally". That means you'll be able to access it from any directory just by typing `dredd`. - -If you work on projects installable by `npm`, i.e. projects containing `package.json`, you might want to have Dredd installed as a development dependency instead. Just install Dredd by `npm install dredd --save-dev`. See `package.json` of the [Dredd Example][] repository for inspiration. - -### Which Version? - -- **For development**, always go with the latest version. -- **For testing in [CI][]**, always pin your Dredd version to a specific number and upgrade to newer releases manually (but often!). - -### Why Am I Seeing Network Errors? - -In a restricted network (VPN, firewall, proxy) you can see errors similar to the following ones: - -```text -npmERR! Cannot read property 'path' of null -npmERR!code ECONNRESET -npmERR!network socket hang up -``` - -```text -Error: Command failed: git config --get remote.origin.url -ssh: connect to host github.com port 22: Operation timed out -fatal: Could not read from remote repository. -``` - -To solve these issues, you need to set your proxy settings for both `npm` and `git`: - -```sh -$ npm config set proxy "http://proxy.company.com:8080" -$ npm config set https-proxy "https://proxy.company.com:8080" - -$ git config --global http.proxy "http://proxy.company.com:8080" -$ git config --global https.proxy "https://proxy.company.com:8080" -``` - -When using `git config`, make sure you have the port specified even -when it's the standard `:80`. Also check out -[how to set up Dredd to correctly work with proxies][Dredd Proxy]. - -### Why I'm Seeing `node-gyp` or `python` Errors? - -The installation process features compilation of some C++ components, which may not be successful. In that case, errors related to `node-gyp` or `python` are printed. However, if `dredd --version` works for you when the installation is done, feel free to ignore the errors. - -In case of compilation errors, Dredd automatically uses a less performant solution written in pure JavaScript. Next time when installing Dredd, you can use `npm install -g dredd --no-optional` to skip the compilation step ([learn more about this][C++11 vs JS]). - -### Why Is the Installation So Slow? - -The installation process features compilation of some C++ components, which may take some time ([learn more about this][C++11 vs JS]). You can simplify and speed up the process using `npm install -g dredd --no-optional` if you are: - -- using Dredd exclusively with [Swagger][], -- using Dredd with small [API Blueprint][] files, -- using Dredd on Windows or other environments with complicated C++11 compiler setup. - -The `--no-optional` option avoids any compilation attempts when installing Dredd, but causes slower reading of the API Blueprint files, especially the large ones. - - -[API Blueprint]: https://apiblueprint.org/ -[Swagger]: https://swagger.io/ - -[CoffeeScript]: https://coffeescript.org/ -[CI]: how-to-guides.md#continuous-integration - -[Homebrew]: https://brew.sh/ -[Node.js]: https://nodejs.org/en/ -[nvm]: https://github.com/creationix/nvm -[Download Node.js]: https://nodejs.org/en/download/ -[Install Node.js as system package]: https://nodejs.org/en/download/package-manager/ - -[C++11 vs JS]: contributing.md#compiled-vs-pure-javascript -[Dredd Proxy]: how-it-works.md#using-http-s-proxy -[Dredd Example]: https://github.com/apiaryio/dredd-example/ diff --git a/docs/installation.rst b/docs/installation.rst new file mode 100644 index 000000000..3ac42dc3f --- /dev/null +++ b/docs/installation.rst @@ -0,0 +1,107 @@ +.. _installation: + +Installation +============ + +Dredd is a command-line application written in JavaScript. To run it on your machine or in your :ref:`Continuous Integration server `, you first need to have `Node.js `__ installed. + +.. _install-nodejs: + +Install Node.js +--------------- + +macOS +~~~~~ + +1. Install Node.js. + + - If you’re using `Homebrew `__, run ``brew install node``. + - Otherwise `download Node.js `__ from the official website and install Node.js using the downloaded installer. + +2. Make sure both ``node --version`` and ``npm --version`` work in your Terminal. + +Windows +~~~~~~~ + +1. `Download Node.js `__ from the official website and install Node.js using the downloaded installer. +2. Make sure both ``node --version`` and ``npm --version`` work in your Command Prompt. + +Linux +~~~~~ + +1. `Install Node.js as system package `__. +2. Make sure both ``node --version`` and ``npm --version`` work in your Terminal. + +Pro Tips +~~~~~~~~ + +- :ref:`Continuous Integration section in the How-To Guides ` can help you to install Dredd on CI server. +- To maintain multiple Node.js versions on your computer, check out `nvm `__. + +Install Dredd +------------- + +1. ``npm install -g dredd`` +2. ``dredd --version`` + +If the second command works, you’re done! + +Globally vs locally +~~~~~~~~~~~~~~~~~~~ + +The ``-g`` ensures Dredd will be installed “globally”. That means you’ll be able to access it from any directory just by typing ``dredd``. + +If you work on projects installable by ``npm``, i.e. projects containing ``package.json``, you might want to have Dredd installed as a development dependency instead. Just install Dredd by ``npm install dredd --save-dev``. See ``package.json`` of the `Dredd Example `__ repository for inspiration. + +Which Version? +~~~~~~~~~~~~~~ + +- **For development**, always go with the latest version. +- **For testing in CI** (:ref:`what’s CI? `), always pin your Dredd version to a specific number and upgrade to newer releases manually (but often!). + +Why Am I Seeing Network Errors? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In a restricted network (VPN, firewall, proxy) you can see errors similar to the following ones: + +.. code-block:: text + + npmERR! Cannot read property 'path' of null + npmERR!code ECONNRESET + npmERR!network socket hang up + +.. code-block:: text + + Error: Command failed: git config --get remote.origin.url + ssh: connect to host github.com port 22: Operation timed out + fatal: Could not read from remote repository. + +To solve these issues, you need to set your proxy settings for both ``npm`` and ``git``: + +.. code-block:: shell + + $ npm config set proxy "http://proxy.company.com:8080" + $ npm config set https-proxy "https://proxy.company.com:8080" + + $ git config --global http.proxy "http://proxy.company.com:8080" + $ git config --global https.proxy "https://proxy.company.com:8080" + +When using ``git config``, make sure you have the port specified even when it’s the standard ``:80``. Also check out :ref:`how to set up Dredd to correctly work with proxies `. + +Why I’m Seeing ``node-gyp`` or ``python`` Errors? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The installation process features compilation of some C++ components, which may not be successful. In that case, errors related to ``node-gyp`` or ``python`` are printed. However, if ``dredd --version`` works for you when the installation is done, feel free to ignore the errors. + +In case of compilation errors, Dredd automatically uses a less performant solution written in pure JavaScript. Next time when installing Dredd, you can use ``npm install -g dredd --no-optional`` to skip the compilation step (:ref:`learn more about this `). + +Why Is the Installation So Slow? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The installation process features compilation of some C++ components, which may take some time (:ref:`learn more about this `). You can simplify and speed up the process using ``npm install -g dredd --no-optional`` if you are: + +- using Dredd exclusively with `Swagger `__, +- using Dredd with small `API Blueprint `__ files, +- using Dredd on Windows or other environments with complicated C++11 compiler setup. + +The ``--no-optional`` option avoids any compilation attempts when installing Dredd, but causes slower reading of the API Blueprint files, especially the large ones. diff --git a/docs/quickstart.md b/docs/quickstart.md deleted file mode 100644 index a87f1a095..000000000 --- a/docs/quickstart.md +++ /dev/null @@ -1,135 +0,0 @@ -# Quickstart - -In following tutorial you can quickly learn how to test a simple HTTP API application with Dredd. The tested application will be very simple backend written in [Express.js][]. - -## Install Dredd - -``` -$ npm install -g dredd -``` - -If you're not familiar with the Node.js ecosystem or you bump into any issues, follow the [installation guide](installation.md). - -## Document Your API - -First, let's design the API we are about to build and test. That means you will need to create an API description file, which will document how your API should look like. Dredd supports two formats of API description documents: - -- [API Blueprint][] -- [Swagger][] - -If you choose API Blueprint, create a file called `api-description.apib` in the root of your project and save it with following content: - -```apiblueprint -FORMAT: 1A - -# GET / -+ Response 200 (application/json; charset=utf-8) - - {"message": "Hello World!"} -``` - -If you choose Swagger, create a file called `api-description.yml`: - -```yaml -swagger: "2.0" -info: - version: "1.0" - title: Example API - license: - name: MIT -host: www.example.com -basePath: / -schemes: - - http -paths: - /: - get: - produces: - - application/json; charset=utf-8 - responses: - 200: - description: "" - schema: - type: object - properties: - message: - type: string - required: - - message -``` - -## Implement Your API - -As we mentioned in the beginning, we'll use [Express.js][] to implement the API. Install the framework by `npm`: - -```sh -$ npm init -$ npm install express --save -``` - -Now let's code the thing! Create a file called `app.js` with following contents: - -```javascript -var app = require('express')(); - -app.get('/', function(req, res) { - res.json({message: 'Hello World!'}); -}) - -app.listen(3000); -``` - -## Test Your API - -At this moment, the implementation is ready to be tested. Let's run the server as a background process and let's test it: - -```sh -$ node app.js & -``` - -Finally, let Dredd validate whether your freshly implemented API complies with the description you have: - -```sh -$ dredd api-description.apib http://127.0.0.1:3000 # API Blueprint -$ dredd api-description.yml http://127.0.0.1:3000 # Swagger -``` - -## Configure Dredd - -Dredd can be configured by [many CLI options](usage-cli.md). It's recommended to save your Dredd configuration alongside your project, so it's easier to repeatedly execute always the same test run. Use interactive configuration wizard to create `dredd.yml` file in the root of your project: - -``` -$ dredd init -? Location of the API description document: api-description.apib -? Command to start API backend server e.g. (bundle exec rails server) -? URL of tested API endpoint: http://127.0.0.1:3000 -? Programming language of hooks: -❯ nodejs - python - ruby - ... -? Dredd is best served with Continuous Integration. Create CircleCI config for Dredd? Yes -``` - -Now you can start test run just by typing `dredd`! - -``` -$ dredd -``` - -## Use Hooks - -Dredd's [hooks](hooks.md) enable you to write some glue code in your favorite language to support enhanced scenarios in your API tests. Read the documentation about hooks to learn more on how to write them. Choose your language and install corresponding hook handler library. - -## Advanced Examples - -For more complex example applications, please refer to: - -- [Express.js example application](https://github.com/apiaryio/dredd-example) -- [Ruby on Rails example application](https://github.com/theodorton/dredd-test-rails) -- [Laravel example application](https://github.com/ddelnano/dredd-hooks-php/wiki/Laravel-Example) - - -[API Blueprint]: https://apiblueprint.org/ -[Swagger]: https://swagger.io/ -[Express.js]: http://expressjs.com/starter/hello-world.html diff --git a/docs/quickstart.rst b/docs/quickstart.rst new file mode 100644 index 000000000..45456b8cb --- /dev/null +++ b/docs/quickstart.rst @@ -0,0 +1,140 @@ +.. _quickstart: + +Quickstart +========== + +In following tutorial you can quickly learn how to test a simple HTTP API application with Dredd. The tested application will be very simple backend written in `Express.js `__. + +Install Dredd +------------- + +:: + + $ npm install -g dredd + +If you’re not familiar with the Node.js ecosystem or you bump into any issues, follow the :ref:`installation guide `. + +Document Your API +----------------- + +First, let’s design the API we are about to build and test. That means you will need to create an API description file, which will document how your API should look like. Dredd supports two formats of API description documents: + +- `API Blueprint `__ +- `Swagger `__ + +If you choose API Blueprint, create a file called ``api-description.apib`` in the root of your project and save it with following content: + +.. code-block:: apiblueprint + + FORMAT: 1A + + # GET / + + Response 200 (application/json; charset=utf-8) + + {"message": "Hello World!"} + +If you choose Swagger, create a file called ``api-description.yml``: + +.. code-block:: yaml + + swagger: "2.0" + info: + version: "1.0" + title: Example API + license: + name: MIT + host: www.example.com + basePath: / + schemes: + - http + paths: + /: + get: + produces: + - application/json; charset=utf-8 + responses: + 200: + description: "" + schema: + type: object + properties: + message: + type: string + required: + - message + +Implement Your API +------------------ + +As we mentioned in the beginning, we’ll use `Express.js `__ to implement the API. Install the framework by ``npm``: + +.. code-block:: shell + + $ npm init + $ npm install express --save + +Now let’s code the thing! Create a file called ``app.js`` with following contents: + +.. code-block:: javascript + + var app = require('express')(); + + app.get('/', function(req, res) { + res.json({message: 'Hello World!'}); + }) + + app.listen(3000); + +Test Your API +------------- + +At this moment, the implementation is ready to be tested. Let’s run the server as a background process and let’s test it: + +.. code-block:: shell + + $ node app.js & + +Finally, let Dredd validate whether your freshly implemented API complies with the description you have: + +.. code-block:: shell + + $ dredd api-description.apib http://127.0.0.1:3000 # API Blueprint + $ dredd api-description.yml http://127.0.0.1:3000 # Swagger + +Configure Dredd +--------------- + +Dredd can be configured by :ref:`many CLI options `. It’s recommended to save your Dredd configuration alongside your project, so it’s easier to repeatedly execute always the same test run. Use interactive configuration wizard to create ``dredd.yml`` file in the root of your project: + +:: + + $ dredd init + ? Location of the API description document: api-description.apib + ? Command to start API backend server e.g. (bundle exec rails server) + ? URL of tested API endpoint: http://127.0.0.1:3000 + ? Programming language of hooks: + ❯ nodejs + python + ruby + ... + ? Dredd is best served with Continuous Integration. Create CircleCI config for Dredd? Yes + +Now you can start test run just by typing ``dredd``! + +:: + + $ dredd + +Use Hooks +--------- + +Dredd’s :ref:`hooks ` enable you to write some glue code in your favorite language to support enhanced scenarios in your API tests. Read the documentation about hooks to learn more on how to write them. Choose your language and install corresponding hook handler library. + +Advanced Examples +----------------- + +For more complex example applications, please refer to: + +- `Express.js example application `__ +- `Ruby on Rails example application `__ +- `Laravel example application `__ diff --git a/docs/redirects.yml b/docs/redirects.yml index 3d4281474..0ae03ceb5 100644 --- a/docs/redirects.yml +++ b/docs/redirects.yml @@ -3,7 +3,7 @@ # to Dredd maintainers to keep this list in sync with reality. # # You can use https://github.com/honzajavorek/rtd-redirects to programmatically -# upload the redirects to ReadTheDocs admin interface. +# upload the redirects to the ReadTheDocs admin interface. redirects: # removed pages "/example/": "/quickstart.html" diff --git a/docs/requirements.txt b/docs/requirements.txt index 4f4b4e224..520ec5d07 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,6 +1,14 @@ -sphinx==1.5.6 -sphinx-autobuild==0.7.1 -sphinx-rtd-theme==0.2.4 -recommonmark==0.4.0 -pygments-markdown-lexer==0.1.0.dev39 +# Not using pipenv on purpose as ReadTheDocs do not support it yet +# https://github.com/rtfd/readthedocs.org/issues/3181 + + +# dependencies +sphinx==1.8.1 +jinja2==2.10 +-e git://github.com/jhermann/pygments-markdown-lexer.git@e651a9a3f664285b01451eb39232b1ad9af65956#egg=pygments-markdown-lexer pygments-apiblueprint==0.1.0 + +# dev dependencies +doc8==0.8.0 +sphinx-autobuild==0.7.1 +sphinx-rtd-theme==0.4.2 diff --git a/docs/usage-cli.md b/docs/usage-cli.md deleted file mode 100644 index 79cd2aedf..000000000 --- a/docs/usage-cli.md +++ /dev/null @@ -1,89 +0,0 @@ -# Command-line Interface - -## Usage - -``` -$ dredd '' '' [OPTIONS] -``` - -Example: - -``` -$ dredd ./apiary.md http://127.0.0.1:3000 -``` - -## Arguments - -### API Description Document (string) - -URL or path to the API description document (API Blueprint, Swagger).
-**Sample values:** `./api-blueprint.apib`, `./swagger.yml`, `./swagger.json`, `http://example.com/api-blueprint.apib` - -### API Location (string) - -URL, the root address of your API.
-**Sample values:** `http://127.0.0.1:3000`, `http://api.example.com` - -## Configuration File - -If you use Dredd repeatedly within a single project, the preferred way to run it is to first persist your configuration in a `dredd.yml` file. With the file in place you can then run Dredd every time simply just by: - -``` -$ dredd -``` - -Dredd offers interactive wizard to setup your `dredd.yml` file: - -``` -$ dredd init -``` - -See below how sample configuration file could look like. The structure is -the same as of the [Dredd Class configuration object](usage-js.md#configuration-object-for-dredd-class). - -```yaml -reporter: apiary -custom: - - "apiaryApiKey:yourSecretApiaryAPiKey" - - "apiaryApiName:apiName" -dry-run: null -hookfiles: "dreddhooks.js" -sandbox: false -server: rails server -server-wait: 3 -init: false -custom: {} -names: false -only: [] -output: [] -header: [] -sorted: false -user: null -inline-errors: false -details: false -method: [] -level: info -timestamp: false -silent: false -path: [] -blueprint: api-description.apib -endpoint: "http://127.0.0.1:3000" -``` - -> **Note:** Do not get confused by Dredd using a keyword `blueprint` also for paths to Swagger documents. This is for historical reasons and will be changed in the future. - -## CLI Options Reference - -Remember you can always list all available arguments by `dredd --help`. - -<% for option in @options: %> - - -### \-\-<%= option.name %><% if option.alias: %>, -<%= option.alias %><% end %> - -<%= option.description %>
-<% if option.default: %> -**Default value:** `<%- JSON.stringify(option.default) %>` -<% end %> - -<% end %> diff --git a/docs/usage-cli.rst b/docs/usage-cli.rst new file mode 100644 index 000000000..77a2fd5df --- /dev/null +++ b/docs/usage-cli.rst @@ -0,0 +1,90 @@ +.. _usage-cli: + +Command-line Interface +====================== + +Usage +----- + +:: + + $ dredd '' '' [OPTIONS] + +Example: + +:: + + $ dredd ./apiary.md http://127.0.0.1:3000 + +Arguments +--------- + +.. _api-description-document-string: + +.. option:: api-description-document + + URL or path to the API description document (API Blueprint, Swagger). + **Sample values:** ``./api-blueprint.apib``, ``./swagger.yml``, ``./swagger.json``, ``http://example.com/api-blueprint.apib`` + +.. _api-location-string: + +.. option:: api-location + + URL, the root address of your API. + **Sample values:** ``http://127.0.0.1:3000``, ``http://api.example.com`` + +Configuration File +------------------ + +If you use Dredd repeatedly within a single project, the preferred way to run it is to first persist your configuration in a ``dredd.yml`` file. With the file in place you can then run Dredd every time simply just by: + +:: + + $ dredd + +Dredd offers interactive wizard to setup your ``dredd.yml`` file: + +:: + + $ dredd init + +See below how sample configuration file could look like. The structure is the same as of the :ref:`Dredd Class configuration object `. + +.. code-block:: yaml + + reporter: apiary + custom: + - "apiaryApiKey:yourSecretApiaryAPiKey" + - "apiaryApiName:apiName" + dry-run: null + hookfiles: "dreddhooks.js" + sandbox: false + server: rails server + server-wait: 3 + init: false + custom: {} + names: false + only: [] + output: [] + header: [] + sorted: false + user: null + inline-errors: false + details: false + method: [] + level: info + timestamp: false + silent: false + path: [] + blueprint: api-description.apib + endpoint: "http://127.0.0.1:3000" + +.. note:: + Do not get confused by Dredd using a keyword ``blueprint`` also for paths to Swagger documents. This is for historical reasons and will be changed in the future. + +CLI Options Reference +--------------------- + +Remember you can always list all available arguments by ``dredd --help``. + +.. cli-options:: ../src/options.json diff --git a/docs/usage-js.md b/docs/usage-js.md deleted file mode 100644 index 89f3c3e8f..000000000 --- a/docs/usage-js.md +++ /dev/null @@ -1,109 +0,0 @@ -## Using Dredd as a JavaScript Library - -Dredd can be used directly from your JavaScript code. First, import -and configure Dredd: - -```javascript -var Dredd = require('dredd'); -var dredd = new Dredd(configuration); -``` - -Then you need to run the Dredd testing: - -```javascript -dredd.run(function (err, stats) { - // err is present if anything went wrong - // otherwise stats is an object with useful statistics -}); -``` - -As you can see, `dredd.run` is a function receiving another function as a callback. -Received arguments are `err` (error if any) and `stats` (testing statistics) with -numbers accumulated throughout the Dredd run. - - -### Configuration Object for Dredd Class - -Let's have a look at an example configuration first. (Please also see [options source](https://github.com/apiaryio/dredd/blob/master/src/options.js) to read detailed information about the `options` attributes). - -```javascript -{ - server: 'http://127.0.0.1:3000/api', // your URL to API endpoint the tests will run against - options: { - - 'path': [], // Required Array if Strings; filepaths to API description documents, can use glob wildcards - - 'dry-run': false, // Boolean, do not run any real HTTP transaction - 'names': false, // Boolean, Print Transaction names and finish, similar to dry-run - - 'level': 'info', // String, log-level (info, silly, debug, verbose, ...) - 'silent': false, // Boolean, Silences all logging output - - 'only': [], // Array of Strings, run only transaction that match these names - - 'header': [], // Array of Strings, these strings are then added as headers (key:value) to every transaction - 'user': null, // String, Basic Auth credentials in the form username:password - - 'hookfiles': [], // Array of Strings, filepaths to files containing hooks (can use glob wildcards) - - 'reporter': ['dot', 'html'], // Array of possible reporters, see folder src/reporters - - 'output': [], // Array of Strings, filepaths to files used for output of file-based reporters - - 'inline-errors': false, // Boolean, If failures/errors are display immediately in Dredd run - - 'color': true, - 'timestamp': false - }, - - 'emitter': EventEmitterInstance, // optional - listen to test progress, your own instance of EventEmitter - - 'hooksData': { - 'pathToHook' : '...' - } - - 'data': { - 'path/to/file': '...' - } -} -``` - -### Properties - -#### server (string) - -Your choice of the API endpoint to test the API description against. -It must be a valid URL (you can specify `port`, `path` and http or https `protocol`). - -#### options (object) - -Because `options.path` array is required, you must specify options. You'll end -with errors otherwise. - - - -##### options.path (object) - -**Required** Array of filepaths to API description documents. Or it can also be an URL to download the API description from internet via http(s) protocol. - -##### data (object) - -**Optional** Object with keys as `filename` and value as `blueprint`-code. - -Useful when you don't want to operate on top of filesystem and want to pass -code of your API description as a string. You get the point. - -##### hooksData (object) - -**Optional** Object with keys as `filename` and strings with JavaScript hooks code. - -Load hooks file code from string. Must be used together with sandboxed mode. - -```javascript -{ - 'data': { - './api-description.apib': 'FORMAT: 1A\n\n# My String API\n\nGET /url\n+ Response 200\n\n Some content', - './directory/another-api-description.apib': '# Another API\n\n## Group Machines\n\n### Machine [/machine]\n\n#### Read machine [GET]\n\n...' - } -} -``` diff --git a/docs/usage-js.rst b/docs/usage-js.rst new file mode 100644 index 000000000..6bbd230fe --- /dev/null +++ b/docs/usage-js.rst @@ -0,0 +1,114 @@ +.. _usage-js: + +Using Dredd as a JavaScript Library +=================================== + +Dredd can be used directly from your JavaScript code. First, import and configure Dredd: + +.. code-block:: javascript + + var Dredd = require('dredd'); + var dredd = new Dredd(configuration); + +Then you need to run the Dredd testing: + +.. code-block:: javascript + + dredd.run(function (err, stats) { + // err is present if anything went wrong + // otherwise stats is an object with useful statistics + }); + +As you can see, ``dredd.run`` is a function receiving another function as a callback. Received arguments are ``err`` (error if any) and ``stats`` (testing statistics) with numbers accumulated throughout the Dredd run. + +.. _configuration-object-for-dredd-class: + +Configuration Object for Dredd Class +------------------------------------ + +Let’s have a look at an example configuration first. (Please also see the :ref:`CLI options ` to read detailed information about the ``options`` attributes). + +.. code-block:: javascript + + { + server: 'http://127.0.0.1:3000/api', // your URL to API endpoint the tests will run against + options: { + + 'path': [], // Required Array if Strings; filepaths to API description documents, can use glob wildcards + + 'dry-run': false, // Boolean, do not run any real HTTP transaction + 'names': false, // Boolean, Print Transaction names and finish, similar to dry-run + + 'level': 'info', // String, log-level (info, silly, debug, verbose, ...) + 'silent': false, // Boolean, Silences all logging output + + 'only': [], // Array of Strings, run only transaction that match these names + + 'header': [], // Array of Strings, these strings are then added as headers (key:value) to every transaction + 'user': null, // String, Basic Auth credentials in the form username:password + + 'hookfiles': [], // Array of Strings, filepaths to files containing hooks (can use glob wildcards) + + 'reporter': ['dot', 'html'], // Array of possible reporters, see folder src/reporters + + 'output': [], // Array of Strings, filepaths to files used for output of file-based reporters + + 'inline-errors': false, // Boolean, If failures/errors are display immediately in Dredd run + + 'color': true, + 'timestamp': false + }, + + 'emitter': EventEmitterInstance, // optional - listen to test progress, your own instance of EventEmitter + + 'hooksData': { + 'pathToHook' : '...' + } + + 'data': { + 'path/to/file': '...' + } + } + +Properties +---------- + +server (string) +~~~~~~~~~~~~~~~ + +Your choice of the API endpoint to test the API description against. It must be a valid URL (you can specify ``port``, ``path`` and http or https ``protocol``). + +options (object) +~~~~~~~~~~~~~~~~ + +Because ``options.path`` array is required, you must specify options. You’ll end with errors otherwise. + +.. _optionspath-object: + +options.path (object) +^^^^^^^^^^^^^^^^^^^^^ + +**Required** Array of filepaths to API description documents. Or it can also be an URL to download the API description from internet via http(s) protocol. + +data (object) +^^^^^^^^^^^^^ + +**Optional** Object with keys as ``filename`` and value as ``blueprint``-code. + +Useful when you don’t want to operate on top of filesystem and want to pass code of your API description as a string. You get the point. + +hooksData (object) +^^^^^^^^^^^^^^^^^^ + +**Optional** Object with keys as ``filename`` and strings with JavaScript hooks code. + +Load hooks file code from string. Must be used together with sandboxed mode. + +.. code-block:: javascript + + { + 'data': { + './api-description.apib': 'FORMAT: 1A\n\n# My String API\n\nGET /url\n+ Response 200\n\n Some content', + './directory/another-api-description.apib': '# Another API\n\n## Group Machines\n\n### Machine [/machine]\n\n#### Read machine [GET]\n\n...' + } + } diff --git a/package-lock.json b/package-lock.json index fbc335274..b112f5560 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1375,12 +1375,6 @@ "integrity": "sha512-e9DBeH0Oby7EbUQN3DRWKxgbpEJO3KAZUhP0xDmPA23Qr6PmvXI7Ojy8ZthsOsz+1iSoaV9G7eWJXUOyryJ6NA==", "dev": true }, - "capture-stack-trace": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz", - "integrity": "sha1-Sm+gc5nCa7pH8LJJa00PtAjFVQ0=", - "dev": true - }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", @@ -1498,16 +1492,6 @@ "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", "integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs=" }, - "clone-regexp": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-1.0.1.tgz", - "integrity": "sha512-Fcij9IwRW27XedRIJnSOEupS7RVcXtObJXbcUOX93UCLqqOdRpkvzKywOOSizmEK/Is3S/RHX9dLdfo6R1Q1mw==", - "dev": true, - "requires": { - "is-regexp": "^1.0.0", - "is-supported-regexp-flag": "^1.0.0" - } - }, "clone-stats": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", @@ -2349,15 +2333,6 @@ } } }, - "create-error-class": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", - "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", - "dev": true, - "requires": { - "capture-stack-trace": "^1.0.0" - } - }, "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", @@ -2648,12 +2623,6 @@ "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", "dev": true }, - "duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", - "dev": true - }, "duplexify": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.0.tgz", @@ -2680,39 +2649,6 @@ "jsbn": "~0.1.0" } }, - "ect": { - "version": "0.5.9", - "resolved": "https://registry.npmjs.org/ect/-/ect-0.5.9.tgz", - "integrity": "sha1-ewTsGC67f7ORO2KeFpANsz4PTeA=", - "dev": true, - "requires": { - "coffee-script": ">= 1.5.0", - "optimist": "0.4.0" - }, - "dependencies": { - "coffee-script": { - "version": "1.12.7", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.12.7.tgz", - "integrity": "sha512-fLeEhqwymYat/MpTPUjSKHVYYl0ec2mOyALEMLmzr5i1isuG+6jfI2j2d5oBO3VIzgUXgBVIcOT9uH1TFxBckw==", - "dev": true - }, - "optimist": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.4.0.tgz", - "integrity": "sha1-y47Dfy/jqphky2eidSUOfhliCiU=", - "dev": true, - "requires": { - "wordwrap": "~0.0.2" - } - }, - "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", - "dev": true - } - } - }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -4373,12 +4309,6 @@ "integrity": "sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g=", "dev": true }, - "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true - }, "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", @@ -4787,25 +4717,6 @@ "resolved": "https://registry.npmjs.org/googlediff/-/googlediff-0.1.0.tgz", "integrity": "sha1-mazwXMBiI+tmwpAI2B+bLRjCRT0=" }, - "got": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", - "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", - "dev": true, - "requires": { - "create-error-class": "^3.0.0", - "duplexer3": "^0.1.4", - "get-stream": "^3.0.0", - "is-redirect": "^1.0.0", - "is-retry-allowed": "^1.0.0", - "is-stream": "^1.0.0", - "lowercase-keys": "^1.0.0", - "safe-buffer": "^5.0.1", - "timed-out": "^4.0.0", - "unzip-response": "^2.0.1", - "url-parse-lax": "^1.0.0" - } - }, "graceful-fs": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", @@ -4916,34 +4827,6 @@ "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", "dev": true }, - "hercule": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/hercule/-/hercule-4.0.1.tgz", - "integrity": "sha1-fxfpPdxoOVqH3HlP2cAxsur7EzE=", - "dev": true, - "requires": { - "async": "^2.1.4", - "clone-regexp": "^1.0.0", - "duplexer3": "^0.1.4", - "get-stream": "^3.0.0", - "got": "^6.5.0", - "isstream": "^0.1.2", - "left-split": "^1.0.0", - "lodash": "^4.0.0", - "meow": "^3.7.0", - "source-map": "^0.5.3", - "through2": "^2.0.0", - "through2-get": "^0.0.2" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, "home-or-tmp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", @@ -5279,30 +5162,12 @@ "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", "dev": true }, - "is-redirect": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", - "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=", - "dev": true - }, - "is-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", - "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=", - "dev": true - }, "is-resolvable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", "dev": true }, - "is-retry-allowed": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz", - "integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=", - "dev": true - }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", @@ -5315,12 +5180,6 @@ "integrity": "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=", "dev": true }, - "is-supported-regexp-flag": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-supported-regexp-flag/-/is-supported-regexp-flag-1.0.1.tgz", - "integrity": "sha512-3vcJecUUrpgCqc/ca0aWeNu64UGgxcvO60K/Fkr1N6RSvfGCTU60UKN68JDmKokgba0rFFJs12EnzOQa14ubKQ==", - "dev": true - }, "is-text-path": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz", @@ -5719,12 +5578,6 @@ "vinyl-fs": "^2.4.3" } }, - "left-split": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/left-split/-/left-split-1.0.0.tgz", - "integrity": "sha1-vsUUwjz7TJV6mXTt/aOFvFlyiAY=", - "dev": true - }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -5799,12 +5652,6 @@ "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", "dev": true }, - "lodash._baseget": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/lodash._baseget/-/lodash._baseget-3.7.2.tgz", - "integrity": "sha1-G2rh1frPPCVTI1ChPBGXy4u2dPQ=", - "dev": true - }, "lodash._bindcallback": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", @@ -5840,15 +5687,6 @@ "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", "dev": true }, - "lodash._topath": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/lodash._topath/-/lodash._topath-3.8.1.tgz", - "integrity": "sha1-PsXiYGAU9MuX91X+aRTt2L/ADqw=", - "dev": true, - "requires": { - "lodash.isarray": "^3.0.0" - } - }, "lodash.assign": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-3.2.0.tgz", @@ -5979,12 +5817,6 @@ "signal-exit": "^3.0.0" } }, - "lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "dev": true - }, "lru-cache": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", @@ -6829,12 +6661,6 @@ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" }, - "prepend-http": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", - "dev": true - }, "preserve": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", @@ -7955,35 +7781,6 @@ "xtend": "~4.0.0" } }, - "through2-get": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/through2-get/-/through2-get-0.0.2.tgz", - "integrity": "sha1-uj+J47zv1ATVb0zdYmqi1vqRXcc=", - "dev": true, - "requires": { - "lodash.get": "^3.7.0", - "through2": "^2.0.0", - "xtend": "4.0.1" - }, - "dependencies": { - "lodash.get": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-3.7.0.tgz", - "integrity": "sha1-POaK4skWg7KBzFOUEoMDy/deaR8=", - "dev": true, - "requires": { - "lodash._baseget": "^3.0.0", - "lodash._topath": "^3.0.0" - } - } - } - }, - "timed-out": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", - "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", - "dev": true - }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -8388,12 +8185,6 @@ "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", "dev": true }, - "unzip-response": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", - "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=", - "dev": true - }, "uptown": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/uptown/-/uptown-1.0.1.tgz", @@ -8427,15 +8218,6 @@ "pct-encode": "~1.0.0" } }, - "url-parse-lax": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", - "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", - "dev": true, - "requires": { - "prepend-http": "^1.0.1" - } - }, "user-home": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz", diff --git a/package.json b/package.json index 79c2f2e41..dd715f20e 100644 --- a/package.json +++ b/package.json @@ -10,19 +10,19 @@ "node": ">= 6" }, "scripts": { - "build": "babel ./src --out-dir ./lib/", - "build:watch": "babel ./src --out-dir ./lib/ --watch", - "coveralls": "scripts/coveralls.sh", - "docs:lint": "sphinx-build -nW -b linkcheck docs docs/_build/", - "docs:build": "sphinx-build -nW -b html docs docs/_build/", - "docs:serve": "sphinx-autobuild docs docs/_build/", + "build": "babel ./src --out-dir ./lib --copy-files", + "build:watch": "babel ./src --out-dir ./lib --copy-files --watch", + "coveralls": "./scripts/coveralls.sh", + "docs:lint": "doc8 ./docs && sphinx-build -nW -b linkcheck ./docs ./docs/_build/", + "docs:build": "sphinx-build -nW -b html ./docs ./docs/_build/", + "docs:serve": "sphinx-autobuild docs ./docs/_build/", "lint": "conventional-changelog-lint --from=master && eslint .", "prepare": "npm run build", "pretest": "npm run build", "semantic-release": "scripts/semantic-release.sh", - "test": "mocha \"test/**/*-test.js\"", - "test:coverage": "scripts/coverage.sh", - "test:debug": "mocha --debug-brk \"test/**/*-test.js\"" + "test": "mocha \"./test/**/*-test.js\"", + "test:coverage": "./scripts/coverage.sh", + "test:debug": "mocha --debug-brk \"./test/**/*-test.js\"" }, "repository": { "type": "git", @@ -74,12 +74,10 @@ "conventional-changelog-lint": "1.1.9", "coveralls": "2.13.0", "drafter": "1.2.0", - "ect": "0.5.9", "eslint": "4.4.1", "eslint-config-airbnb": "15.1.0", "eslint-plugin-import": "2.7.0", "express": "4.16.3", - "hercule": "4.0.1", "istanbul": "0.4.5", "lcov-result-merger": "1.2.0", "mocha": "5.2.0", diff --git a/readthedocs.yml b/readthedocs.yml new file mode 100644 index 000000000..d97e42fc5 --- /dev/null +++ b/readthedocs.yml @@ -0,0 +1,4 @@ +build: + image: latest +python: + version: 3.6 diff --git a/src/dredd.js b/src/dredd.js index b42e6a3d0..f7ec92a07 100644 --- a/src/dredd.js +++ b/src/dredd.js @@ -12,7 +12,7 @@ const logger = require('./logger'); const Runner = require('./transaction-runner'); const { applyConfiguration } = require('./configuration'); -let options = require('./options'); +const options = require('./options.json'); const PROXY_ENV_VARIABLES = ['HTTP_PROXY', 'HTTPS_PROXY', 'NO_PROXY']; const FILE_DOWNLOAD_TIMEOUT = 5000; @@ -189,11 +189,11 @@ API description document (or documents) not found on path: } downloadFile(fileUrl, callback) { - options = clone(this.configuration.http); - options.url = fileUrl; - options.timeout = FILE_DOWNLOAD_TIMEOUT; + const opts = clone(this.configuration.http); + opts.url = fileUrl; + opts.timeout = FILE_DOWNLOAD_TIMEOUT; - request.get(options, (downloadError, res, body) => { + request.get(opts, (downloadError, res, body) => { let err; if (downloadError) { logger.debug(`Downloading ${fileUrl} errored:`, `${downloadError}` || downloadError.code); diff --git a/src/options.js b/src/options.js deleted file mode 100644 index 9ca0bf5a7..000000000 --- a/src/options.js +++ /dev/null @@ -1,206 +0,0 @@ -const options = { - 'dry-run': { - alias: 'y', - description: 'Do not run any real HTTP transaction, only parse API description document and compile transactions.', - default: null - }, - - hookfiles: { - alias: 'f', - description: `Specifies a pattern to match files with before/after hooks for running tests. -Files are executed in alphabetical order.`, - default: null - }, - - language: { - alias: 'a', - description: 'Language of hookfiles. Possible options are: nodejs, ruby, python, php, perl, go, rust', - default: 'nodejs' - }, - - sandbox: { - alias: 'b', - description: 'Load and run non trusted hooks code in sandboxed container', - default: false - }, - - server: { - alias: 'g', - description: 'Run API backend server command and kill it after Dredd execution. E.g. `rails server`', - default: null - }, - - 'server-wait': { - description: 'Set delay time in seconds between running a server and test run.', - default: 3 - }, - - init: { - alias: 'i', - description: 'Run interactive configuration. Creates dredd.yml configuration file.', - default: false - }, - - custom: { - alias: 'j', - description: 'Pass custom key-value configuration data delimited by a colon. E.g. -j \'a:b\'', - default: [] - }, - - names: { - alias: 'n', - description: 'Only list names of requests (for use in a hookfile). No requests are made.', - default: false - }, - - only: { - alias: 'x', - description: 'Run only specified transaction name. Can be used multiple times', - default: [] - }, - - reporter: { - alias: 'r', - description: `Output additional report format. This option can be used -multiple times to add multiple reporters. -Options: xunit, nyan, dot, markdown, html, apiary.\n`, - default: [] - }, - - output: { - alias: 'o', - description: ` -Specifies output file when using additional file-based reporter. -This option can be used multiple times if multiple file-based reporters are used.\n`, - default: [] - }, - - header: { - alias: 'h', - description: ` -Extra header to include in every request. -This option can be used multiple times to add multiple headers.\n`, - default: [] - }, - - sorted: { - alias: 's', - description: ` -Sorts requests in a sensible way so that objects are not modified before they are created. -Order: CONNECT, OPTIONS, POST, GET, HEAD, PUT, PATCH, DELETE, TRACE.\n`, - default: false - }, - - user: { - alias: 'u', - description: 'Basic Auth credentials in the form username:password.\n', - default: null - }, - - 'inline-errors': { - alias: 'e', - description: ` -Determines whether failures and errors are displayed as they -occur (true) or aggregated and displayed at the end (false).\n`, - default: false - }, - - details: { - alias: 'd', - description: 'Determines whether request/response details are included in passing tests.\n', - default: false - }, - - method: { - alias: 'm', - description: ` -Restrict tests to a particular HTTP method (GET, PUT, POST, DELETE, PATCH). -This option can be used multiple times to allow multiple methods.\n`, - default: [] - }, - - color: { - alias: 'c', - description: 'Determines whether console output should include colors.\n', - default: true - }, - - level: { - alias: 'l', - description: 'The level of logging to output. Options: silly, debug, verbose, info, warn, error.\n', - default: 'info' - }, - - timestamp: { - alias: 't', - description: 'Determines whether console output should include timestamps.\n', - default: false - }, - - silent: { - alias: 'q', - description: 'Silences commandline output.\n', - default: false - }, - - path: { - alias: 'p', - description: 'Additional API description paths or URLs. Can be used multiple times with glob pattern for paths.', - default: [] - }, - - help: { - description: 'Show usage information.\n' - }, - - version: { - description: 'Show version number.\n' - }, - - 'hooks-worker-timeout': { - description: 'How long to wait for hooks worker to start. [ms]', - default: 5000 - }, - - 'hooks-worker-connect-timeout': { - description: 'Total hook worker connection timeout (includes all retries). [ms]', - default: 1500 - }, - - 'hooks-worker-connect-retry': { - description: 'How long to wait between attempts to connect to hooks worker. [ms]', - default: 500 - }, - - 'hooks-worker-after-connect-wait': { - description: 'How long to wait between connecting to hooks worker and start of testing. [ms]', - default: 100 - }, - - 'hooks-worker-term-timeout': { - description: 'How long to wait between trying to terminate hooks worker and killing it. [ms]', - default: 5000 - }, - - 'hooks-worker-term-retry': { - description: 'How long to wait between attempts to terminate hooks worker. [ms]', - default: 500 - }, - - 'hooks-worker-handler-host': { - description: 'Host of the hook worker.', - default: '127.0.0.1' - }, - - 'hooks-worker-handler-port': { - description: 'Port of the hook worker.', - default: 61321 - }, - - config: { - description: 'Path to dredd.yml config file.', - default: './dredd.yml' - } -}; - -module.exports = options; diff --git a/src/options.json b/src/options.json new file mode 100644 index 000000000..18ad6972e --- /dev/null +++ b/src/options.json @@ -0,0 +1,154 @@ +{ + "dry-run": { + "alias": "y", + "description": "Do not run any real HTTP transaction, only parse API description document and compile transactions.", + "default": null + }, + "hookfiles": { + "alias": "f", + "description": "Specifies a pattern to match files with before/after hooks for running tests. Files are executed in alphabetical order.", + "default": null + }, + "language": { + "alias": "a", + "description": "Language of hookfiles. Possible options are: nodejs, ruby, python, php, perl, go, rust", + "default": "nodejs" + }, + "sandbox": { + "alias": "b", + "description": "Load and run non trusted hooks code in sandboxed container", + "default": false + }, + "server": { + "alias": "g", + "description": "Run API backend server command and kill it after Dredd execution. E.g. `rails server`", + "default": null + }, + "server-wait": { + "description": "Set delay time in seconds between running a server and test run.", + "default": 3 + }, + "init": { + "alias": "i", + "description": "Run interactive configuration. Creates dredd.yml configuration file.", + "default": false + }, + "custom": { + "alias": "j", + "description": "Pass custom key-value configuration data delimited by a colon. E.g. -j 'a:b'", + "default": [] + }, + "names": { + "alias": "n", + "description": "Only list names of requests (for use in a hookfile). No requests are made.", + "default": false + }, + "only": { + "alias": "x", + "description": "Run only specified transaction name. Can be used multiple times", + "default": [] + }, + "reporter": { + "alias": "r", + "description": "Output additional report format. This option can be used multiple times to add multiple reporters. Options: xunit, nyan, dot, markdown, html, apiary.", + "default": [] + }, + "output": { + "alias": "o", + "description": "Specifies output file when using additional file-based reporter. This option can be used multiple times if multiple file-based reporters are used.", + "default": [] + }, + "header": { + "alias": "h", + "description": "Extra header to include in every request. This option can be used multiple times to add multiple headers.", + "default": [] + }, + "sorted": { + "alias": "s", + "description": "Sorts requests in a sensible way so that objects are not modified before they are created. Order: CONNECT, OPTIONS, POST, GET, HEAD, PUT, PATCH, DELETE, TRACE.", + "default": false + }, + "user": { + "alias": "u", + "description": "Basic Auth credentials in the form username:password.", + "default": null + }, + "inline-errors": { + "alias": "e", + "description": "Determines whether failures and errors are displayed as they occur (true) or aggregated and displayed at the end (false).", + "default": false + }, + "details": { + "alias": "d", + "description": "Determines whether request/response details are included in passing tests.", + "default": false + }, + "method": { + "alias": "m", + "description": "Restrict tests to a particular HTTP method (GET, PUT, POST, DELETE, PATCH). This option can be used multiple times to allow multiple methods.", + "default": [] + }, + "color": { + "alias": "c", + "description": "Determines whether console output should include colors.", + "default": true + }, + "level": { + "alias": "l", + "description": "The level of logging to output. Options: silly, debug, verbose, info, warn, error.", + "default": "info" + }, + "timestamp": { + "alias": "t", + "description": "Determines whether console output should include timestamps.", + "default": false + }, + "silent": { + "alias": "q", + "description": "Silences commandline output.", + "default": false + }, + "path": { + "alias": "p", + "description": "Additional API description paths or URLs. Can be used multiple times with glob pattern for paths.", + "default": [] + }, + "help": { + "description": "Show usage information." }, "version": { + "description": "Show version number." }, "hooks-worker-timeout": { + "description": "How long to wait for hooks worker to start. [ms]", + "default": 5000 + }, + "hooks-worker-connect-timeout": { + "description": "Total hook worker connection timeout (includes all retries). [ms]", + "default": 1500 + }, + "hooks-worker-connect-retry": { + "description": "How long to wait between attempts to connect to hooks worker. [ms]", + "default": 500 + }, + "hooks-worker-after-connect-wait": { + "description": "How long to wait between connecting to hooks worker and start of testing. [ms]", + "default": 100 + }, + "hooks-worker-term-timeout": { + "description": "How long to wait between trying to terminate hooks worker and killing it. [ms]", + "default": 5000 + }, + "hooks-worker-term-retry": { + "description": "How long to wait between attempts to terminate hooks worker. [ms]", + "default": 500 + }, + "hooks-worker-handler-host": { + "description": "Host of the hook worker.", + "default": "127.0.0.1" + }, + "hooks-worker-handler-port": { + "description": "Port of the hook worker.", + "default": 61321 + }, + "config": { + "description": "Path to dredd.yml config file.", + "default": "./dredd.yml" + } +}