dredd-rack 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +4 -0
- data/LICENSE +674 -0
- data/README.md +102 -0
- data/Rakefile +41 -0
- data/lib/dredd/rack.rb +10 -0
- data/lib/dredd/rack/configuration.rb +27 -0
- data/lib/dredd/rack/rake_task.rb +111 -0
- data/lib/dredd/rack/runner.rb +166 -0
- data/lib/dredd/rack/version.rb +5 -0
- data/spec/lib/dredd/rack/configuration_spec.rb +28 -0
- data/spec/lib/dredd/rack/rake_task_spec.rb +61 -0
- data/spec/lib/dredd/rack/runner_spec.rb +223 -0
- data/spec/lib/dredd/rack_spec.rb +8 -0
- data/spec/spec_helper.rb +35 -0
- data/spec/support/spec_for_configuration_option_interface.rb +28 -0
- data/spec/support/specs_for_boolean_options.rb +18 -0
- data/spec/support/specs_for_options.rb +6 -0
- data/spec/support/specs_for_single_argument_options.rb +12 -0
- metadata +142 -0
data/README.md
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
Dredd::Rack
|
2
|
+
===========
|
3
|
+
|
4
|
+
[![Build Status](https://travis-ci.org/gonzalo-bulnes/dredd-rack.svg?branch=master)](https://travis-ci.org/gonzalo-bulnes/dredd-rack)
|
5
|
+
[![Code Climate](https://codeclimate.com/github/gonzalo-bulnes/dredd-rack.svg)](https://codeclimate.com/github/gonzalo-bulnes/dredd-rack)
|
6
|
+
[![Dredd Reference Version](https://img.shields.io/badge/dredd_reference_version-0.4.1-green.svg)](https://github.com/apiaryio/dredd)
|
7
|
+
[![Inline docs](http://inch-ci.org/github/gonzalo-bulnes/dredd-rack.svg?branch=master)](http://inch-ci.org/github/gonzalo-bulnes/dredd-rack)
|
8
|
+
|
9
|
+
This gem provides a [Dredd][dredd] runner and a `blueprint:verify` rake task to make [API blueprint][blueprint] testing convenient for Rack applications. When verifying blueprints locally, an application server is automatically set up, without need of any manual intervention.
|
10
|
+
|
11
|
+
Besides being convenient, that allows to use the API blueprints as acceptance test suites to practice [BDD][rspec-book] with Dredd and RSpec, for example, while clients developers use [Apiary][apiary] as a mock server.
|
12
|
+
|
13
|
+
[dredd]: https://github.com/apiaryio/dredd
|
14
|
+
[blueprint]: https://apiblueprint.org/
|
15
|
+
[rspec-book]: https://pragprog.com/book/achbd/the-rspec-book
|
16
|
+
[apiary]: http://apiary.io
|
17
|
+
|
18
|
+
Installation
|
19
|
+
------------
|
20
|
+
|
21
|
+
Add the gem to your `Gemfile`:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
# Gemfile
|
25
|
+
|
26
|
+
gem 'dredd-rack', '~> 1.0' # see semver.org
|
27
|
+
```
|
28
|
+
|
29
|
+
Getting Started
|
30
|
+
---------------
|
31
|
+
|
32
|
+
### Dredd::Rack::Runner
|
33
|
+
|
34
|
+
_To do._
|
35
|
+
|
36
|
+
|
37
|
+
### Rake task
|
38
|
+
|
39
|
+
Use the `dredd` rake task from your `Rakefile`:
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
# Rakefile
|
43
|
+
|
44
|
+
require 'dredd/rack'
|
45
|
+
|
46
|
+
# Configure Dredd::Rack to automatically set a server up for your application
|
47
|
+
Dredd::Rack.app Sinatra::Application # or the name of your modular-style app, or Rails app
|
48
|
+
|
49
|
+
# That's all!
|
50
|
+
|
51
|
+
# Optionally add the API blueprint verification to the default test suite
|
52
|
+
# task :default => [:spec, :dredd]
|
53
|
+
```
|
54
|
+
|
55
|
+
Run the API blueprint verification locally:
|
56
|
+
|
57
|
+
```bash
|
58
|
+
rake dredd
|
59
|
+
|
60
|
+
# or specify a remote server:
|
61
|
+
#API_HOST=http://api.example.com rake blueprint:verify
|
62
|
+
```
|
63
|
+
|
64
|
+
Usage
|
65
|
+
-----
|
66
|
+
|
67
|
+
### Custom rake task name or description
|
68
|
+
|
69
|
+
You can also define a custom rake task name or description:
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
# Rakefile
|
73
|
+
|
74
|
+
require 'dredd/rack'
|
75
|
+
|
76
|
+
# Configure Dredd::Rack to automatically set a server up for your application
|
77
|
+
Dredd::Rack.app Sinatra::Application # or the name of your modular-style app, or Rails app
|
78
|
+
|
79
|
+
namespace :blueprint do
|
80
|
+
desc 'Verify an API complies with its blueprint'
|
81
|
+
Dredd::Rack::RakeTask.new(:verify)
|
82
|
+
end
|
83
|
+
```
|
84
|
+
|
85
|
+
License
|
86
|
+
-------
|
87
|
+
|
88
|
+
Dredd::Rack provides convenient API blueprint testing to Rack applications.
|
89
|
+
Copyright (C) 2015 Gonzalo Bulnes Guilpain
|
90
|
+
|
91
|
+
This program is free software: you can redistribute it and/or modify
|
92
|
+
it under the terms of the GNU General Public License as published by
|
93
|
+
the Free Software Foundation, either version 3 of the License, or
|
94
|
+
(at your option) any later version.
|
95
|
+
|
96
|
+
This program is distributed in the hope that it will be useful,
|
97
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
98
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
99
|
+
GNU General Public License for more details.
|
100
|
+
|
101
|
+
You should have received a copy of the GNU General Public License
|
102
|
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
data/Rakefile
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
begin
|
2
|
+
require 'inch/rake'
|
3
|
+
|
4
|
+
Inch::Rake::Suggest.new(:inch) do |suggest|
|
5
|
+
suggest.args << "--private"
|
6
|
+
suggest.args << "--pedantic"
|
7
|
+
end
|
8
|
+
rescue LoadError
|
9
|
+
desc 'Inch rake task not available'
|
10
|
+
task :inch do
|
11
|
+
abort 'Inch rake task is not available. Be sure to install inch as a gem or plugin'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
begin
|
16
|
+
require 'rspec/core/rake_task'
|
17
|
+
|
18
|
+
desc 'Provide private interfaces documentation'
|
19
|
+
RSpec::Core::RakeTask.new(:spec)
|
20
|
+
|
21
|
+
namespace :spec do
|
22
|
+
desc 'Provide public interfaces documentation'
|
23
|
+
RSpec::Core::RakeTask.new(:public) do |t|
|
24
|
+
t.rspec_opts = "--tag public"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
namespace :spec do
|
29
|
+
desc 'Provide private interfaces documentation for development purpose'
|
30
|
+
RSpec::Core::RakeTask.new(:development) do |t|
|
31
|
+
t.rspec_opts = "--tag protected --tag private"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
rescue LoadError
|
35
|
+
desc 'RSpec rake task not available'
|
36
|
+
task :spec do
|
37
|
+
abort 'RSpec rake task is not available. Be sure to install rspec-core as a gem or plugin'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
task :default => ['spec:public', 'spec:development', :inch]
|
data/lib/dredd/rack.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
module Dredd
|
2
|
+
module Rack
|
3
|
+
|
4
|
+
# Hold the Dredd::Rack global configuration.
|
5
|
+
module Configuration
|
6
|
+
|
7
|
+
# Return the application to be tested locally
|
8
|
+
def app
|
9
|
+
@@app
|
10
|
+
end
|
11
|
+
|
12
|
+
# Set the application to be tested locally
|
13
|
+
#
|
14
|
+
# Any Dredd::Rack::Runner configured to run locally will serve
|
15
|
+
# this application from a Capybara::Server instance.
|
16
|
+
#
|
17
|
+
# object - the application Constant
|
18
|
+
def app=(object)
|
19
|
+
@@app = object
|
20
|
+
end
|
21
|
+
|
22
|
+
# Default configuration
|
23
|
+
@@app = nil
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'capybara'
|
2
|
+
require 'rainbow'
|
3
|
+
require 'rake'
|
4
|
+
require 'rake/tasklib'
|
5
|
+
|
6
|
+
module Dredd
|
7
|
+
module Rack
|
8
|
+
|
9
|
+
# A clonable Rake task powered by a Dredd::Rack::Runner
|
10
|
+
#
|
11
|
+
# Examples:
|
12
|
+
#
|
13
|
+
# require 'dredd/rack'
|
14
|
+
# Dredd::Rack::RakeTask.new # run it with `rake dredd`
|
15
|
+
#
|
16
|
+
# # Customize the name or description of the Rake task:
|
17
|
+
# namespace :blueprint do
|
18
|
+
# desc 'Verify an API complies with its blueprint'
|
19
|
+
# Dredd::Rack::RakeTask.new(:verify)
|
20
|
+
# end
|
21
|
+
# # run it with `rake blueprint:verify`
|
22
|
+
#
|
23
|
+
class RakeTask < ::Rake::TaskLib
|
24
|
+
|
25
|
+
# Return the task's name
|
26
|
+
attr_reader :name
|
27
|
+
|
28
|
+
# Return the task's description
|
29
|
+
attr_reader :description
|
30
|
+
|
31
|
+
# Return the Dredd::Rack::Runner instance
|
32
|
+
attr_reader :runner
|
33
|
+
|
34
|
+
# Define a task with a custom name, arguments and description
|
35
|
+
def initialize(*args)
|
36
|
+
@name = args.shift || :dredd
|
37
|
+
@description = 'Run Dredd::Rack API blueprint verification'
|
38
|
+
@runner = Dredd::Rack::Runner.new(ENV['API_HOST'])
|
39
|
+
|
40
|
+
desc description unless ::Rake.application.last_comment
|
41
|
+
task name, args do
|
42
|
+
run_task(runner)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def run_task(runner)
|
47
|
+
abort dredd_not_available_message unless dredd_available?
|
48
|
+
|
49
|
+
puts starting_message
|
50
|
+
|
51
|
+
puts command_message(runner)
|
52
|
+
|
53
|
+
success = runner.run
|
54
|
+
exit_status = $?.exitstatus
|
55
|
+
|
56
|
+
puts connection_error_message(runner) unless success if dredd_connection_error?(exit_status)
|
57
|
+
|
58
|
+
abort unless exit_status == 0
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def dredd_available?
|
64
|
+
`which dredd`
|
65
|
+
$?.exitstatus == 0
|
66
|
+
end
|
67
|
+
|
68
|
+
def dredd_connection_error?(exit_status)
|
69
|
+
exit_status == 8
|
70
|
+
end
|
71
|
+
|
72
|
+
def command_message(runner)
|
73
|
+
<<-eos.gsub /^( |\t)+/, ""
|
74
|
+
#{runner.command}
|
75
|
+
|
76
|
+
eos
|
77
|
+
end
|
78
|
+
|
79
|
+
def connection_error_message(runner)
|
80
|
+
<<-eos.gsub /^( |\t)+/, ""
|
81
|
+
|
82
|
+
#{Rainbow("Something went wrong.").red}
|
83
|
+
Maybe your API is not being served at #{runner.api_endpoint}?
|
84
|
+
|
85
|
+
Note that specifying a different host is easy:
|
86
|
+
#{Rainbow('`rake blueprint:verify API_HOST=http://localhost:4567`').yellow}
|
87
|
+
|
88
|
+
eos
|
89
|
+
end
|
90
|
+
|
91
|
+
def dredd_not_available_message
|
92
|
+
<<-eos.gsub /^( |\t)+/, ""
|
93
|
+
|
94
|
+
The #{Rainbow('dredd').red} blueprint testing tool is not available.
|
95
|
+
You may want to install it in order to validate the API blueprints.
|
96
|
+
|
97
|
+
Try #{Rainbow('`npm install dredd --global`').yellow} (use `sudo` if necessary)
|
98
|
+
or see https://github.com/apiaryio/dredd for instructions.
|
99
|
+
|
100
|
+
eos
|
101
|
+
end
|
102
|
+
|
103
|
+
def starting_message
|
104
|
+
<<-eos.gsub /^( |\t)+/, ""
|
105
|
+
|
106
|
+
#{Rainbow('Verify the API conformance against its blueprint.').blue}
|
107
|
+
eos
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
require 'capybara'
|
2
|
+
|
3
|
+
module Dredd
|
4
|
+
module Rack
|
5
|
+
|
6
|
+
# A Ruby wrapper around the Dredd API blueprint validation tool
|
7
|
+
#
|
8
|
+
# Usage:
|
9
|
+
#
|
10
|
+
# # run `dredd doc/*.apib doc/*.apib.md http://localhost:XXXX --level warning --dry-run`
|
11
|
+
# # You don't need to start any server, Dredd::Rack does it for you.
|
12
|
+
# dredd = Dredd::Rack::Runner.new
|
13
|
+
# dredd.level(:warning).dry_run!.run
|
14
|
+
#
|
15
|
+
# # You can specify an API endpoint to perform a remote validation.
|
16
|
+
# # Do not forget to serve the API at the given URL!
|
17
|
+
# #
|
18
|
+
# # runs `dredd blueprints/*.md doc/*.md https://api.example.com --no-color`
|
19
|
+
# anderson = Anderson::Rack::Runner.new 'https://api.example.com' do |options|
|
20
|
+
# options.paths_to_blueprints 'blueprints/*.md', 'doc/*.md'
|
21
|
+
# options.no_color!
|
22
|
+
# end
|
23
|
+
# anderson.run
|
24
|
+
#
|
25
|
+
class Runner
|
26
|
+
|
27
|
+
undef_method :method
|
28
|
+
|
29
|
+
NEGATABLE_BOOLEAN_OPTIONS = [:dry_run!, :names!, :sorted!, :inline_errors!,
|
30
|
+
:details!, :color!, :timestamp!, :silent!]
|
31
|
+
META_OPTIONS = [:help, :version]
|
32
|
+
BOOLEAN_OPTIONS = NEGATABLE_BOOLEAN_OPTIONS + META_OPTIONS
|
33
|
+
|
34
|
+
SINGLE_ARGUMENT_OPTIONS = [:hookfiles, :only, :reporter, :output, :header,
|
35
|
+
:user, :method, :level, :path]
|
36
|
+
OPTIONS = BOOLEAN_OPTIONS + SINGLE_ARGUMENT_OPTIONS
|
37
|
+
|
38
|
+
# Store the Dredd command line options
|
39
|
+
attr_accessor :command_parts
|
40
|
+
|
41
|
+
# Return the API endpoint
|
42
|
+
attr_reader :api_endpoint
|
43
|
+
|
44
|
+
# Initialize a runner instance
|
45
|
+
#
|
46
|
+
# The API endpoint can be local or remote.
|
47
|
+
#
|
48
|
+
# api_endpoint - the API URL as a String
|
49
|
+
#
|
50
|
+
def initialize(api_endpoint=nil)
|
51
|
+
@dredd_command = 'dredd'
|
52
|
+
@paths_to_blueprints = 'doc/*.apib doc/*.apib.md'
|
53
|
+
@api_endpoint = api_endpoint || ''
|
54
|
+
@command_parts = []
|
55
|
+
|
56
|
+
yield self if block_given?
|
57
|
+
end
|
58
|
+
|
59
|
+
# Return the Dredd command line
|
60
|
+
def command
|
61
|
+
([@dredd_command, @paths_to_blueprints, @api_endpoint] + @command_parts).join(' ')
|
62
|
+
end
|
63
|
+
|
64
|
+
# Define custom paths to blueprints
|
65
|
+
#
|
66
|
+
# paths_to_blueprints - as many Strings as paths where blueprints are located
|
67
|
+
#
|
68
|
+
# Returns self.
|
69
|
+
def paths_to_blueprints(*paths_to_blueprints)
|
70
|
+
raise ArgumentError, 'invalid path to blueprints' if paths_to_blueprints == ['']
|
71
|
+
|
72
|
+
@paths_to_blueprints = paths_to_blueprints.join(' ')
|
73
|
+
self
|
74
|
+
end
|
75
|
+
|
76
|
+
# Run Dredd
|
77
|
+
#
|
78
|
+
# Returns true if the Dredd exit status is zero, false instead.
|
79
|
+
def run
|
80
|
+
|
81
|
+
if command_valid?
|
82
|
+
start_server! unless api_remote?
|
83
|
+
Kernel.system(command)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Ensure that the runner does respond_to? its option methods
|
88
|
+
#
|
89
|
+
# See http://ruby-doc.org/core-2.2.0/Object.html#method-i-respond_to_missing-3F
|
90
|
+
def respond_to_missing?(method, include_private=false)
|
91
|
+
OPTIONS.include?(method.to_sym ) ||
|
92
|
+
NEGATABLE_BOOLEAN_OPTIONS.include?(method.to_s.gsub(/\Ano_/, '').to_sym) ||
|
93
|
+
super
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def api_remote?
|
99
|
+
!@api_endpoint.empty?
|
100
|
+
end
|
101
|
+
|
102
|
+
def command_valid?
|
103
|
+
command.has_at_least_two_arguments?
|
104
|
+
end
|
105
|
+
|
106
|
+
def start_server!
|
107
|
+
server = Capybara::Server.new(Dredd::Rack.app)
|
108
|
+
server.boot
|
109
|
+
@api_endpoint = "http://#{server.host}:#{server.port.to_s}"
|
110
|
+
end
|
111
|
+
|
112
|
+
# Private: Define as many setter methods as there are Dredd options
|
113
|
+
#
|
114
|
+
# The behaviour of Object#method_missing is not modified unless
|
115
|
+
# the called method name matches one of the Dredd options.
|
116
|
+
#
|
117
|
+
# name - Symbol for the method called
|
118
|
+
# args - arguments of the called method
|
119
|
+
#
|
120
|
+
# See also: http://ruby-doc.org/core-2.2.0/BasicObject.html#method-i-method_missing
|
121
|
+
def method_missing(name, *args)
|
122
|
+
super unless OPTIONS.include?(name.to_sym ) ||
|
123
|
+
NEGATABLE_BOOLEAN_OPTIONS.include?(name.to_s.gsub(/\Ano_/, '').to_sym)
|
124
|
+
|
125
|
+
option_flag = name.to_s.gsub('_', '-').gsub('!', '').prepend('--')
|
126
|
+
command_parts = self.command_parts.push option_flag
|
127
|
+
command_parts = self.command_parts.push args.slice(0).to_s if SINGLE_ARGUMENT_OPTIONS.include? name
|
128
|
+
self
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
class String
|
136
|
+
|
137
|
+
# Verify that a command has at least two arguments (excluding options)
|
138
|
+
#
|
139
|
+
# Examples:
|
140
|
+
#
|
141
|
+
# "dredd doc/*.apib http://api.example.com".valid? # => true
|
142
|
+
# "dredd doc/*.apib doc/*apib.md http://api.example.com".valid? # => true
|
143
|
+
# "dredd doc/*.apib http://api.example.com --level verbose".valid? # => true
|
144
|
+
# "dredd http://api.example.com".valid? # => false
|
145
|
+
# "dredd doc/*.apib --dry-run".valid? # => false
|
146
|
+
# "dredd --dry-run --level verbose".valid? # => false
|
147
|
+
#
|
148
|
+
# Known limitations:
|
149
|
+
#
|
150
|
+
# Does not support short flags. (e.g. using `-l` instead of `--level`).
|
151
|
+
# Requires options to be specified after the last argument.
|
152
|
+
#
|
153
|
+
# Note:
|
154
|
+
#
|
155
|
+
# The known limitations imply that there may be false negatives: this method
|
156
|
+
# can return false for commands that do have two arguments or more. But there
|
157
|
+
# should not be false positives: if the method returns true, then the command
|
158
|
+
# does have at least two arguments.
|
159
|
+
#
|
160
|
+
# Returns true if the command String has at least two arguments, false otherwise.
|
161
|
+
def has_at_least_two_arguments?
|
162
|
+
split('--').first.split(' ').length >= 3
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
Anderson = Dredd # Anderson::Rack::Runner.new runs just as fast as Dredd
|