dredd-rack 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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