dredd-rack 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
[](https://travis-ci.org/gonzalo-bulnes/dredd-rack)
|
5
|
+
[](https://codeclimate.com/github/gonzalo-bulnes/dredd-rack)
|
6
|
+
[](https://github.com/apiaryio/dredd)
|
7
|
+
[](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
|