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.
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,10 @@
1
+ require 'dredd/rack/configuration'
2
+ require 'dredd/rack/rake_task'
3
+ require 'dredd/rack/runner'
4
+ require 'dredd/rack/version'
5
+
6
+ module Dredd
7
+ module Rack
8
+ extend Configuration
9
+ end
10
+ end
@@ -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