rspec-document_requests 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 07dd25c82e673ad273e0c18f127ec929c0869b28
4
+ data.tar.gz: b9b67877628275e67bdf87e8d2729ca7c1babcd1
5
+ SHA512:
6
+ metadata.gz: 5d09018dd25cd1e11a5a8e196bf544f93a51e211323a95930387c9245a11ecbaf152e1bcc084e4ae0a04a26fd7ded43d6a62f2ace38693dda9e05c16203b9352
7
+ data.tar.gz: 5162824b78e0486125a648b75c7e395842b94119f99481b4db66bccf758b6d9514f8ca73d18ab2287b19d417f52cdf9dc7810d7948436902bf76f67c9824fe78
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.gem
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.2
@@ -0,0 +1,13 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
4
+
5
+ We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion.
6
+
7
+ Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
8
+
9
+ Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
10
+
11
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
12
+
13
+ This Code of Conduct is adapted from the [Contributor Covenant](http:contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rspec-document_requests.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,245 @@
1
+ # RSpec::DocumentRequests [![Gem Version](https://badge.fury.io/rb/rspec-document_requests.svg)](http://badge.fury.io/rb/rspec-document_requests)
2
+
3
+ This gem is an extension to [rspec-rails](https://github.com/rspec/rspec-rails),
4
+ which adds the capability to automatically document requests made by your
5
+ request specs. This will help you document your API effortlessly.
6
+
7
+ This was made after checking out
8
+ [rspec_api_documentation](https://github.com/zipmark/rspec_api_documentation),
9
+ in which I didn't like the fact that it forces you into its own DSL (which is
10
+ basically a small subset of RSpec DSL). If you liked it, you'll probably like
11
+ this one more.
12
+
13
+ ## Installation
14
+
15
+ Add this line to your application's Gemfile:
16
+
17
+ ```ruby
18
+ gem 'rspec-document_requests'
19
+ ```
20
+
21
+ And then execute:
22
+
23
+ $ bundle
24
+
25
+ Or install it yourself as:
26
+
27
+ $ gem install rspec-document_requests
28
+
29
+ ## Usage
30
+
31
+ ### Require
32
+
33
+ Require the DSL _after_ you require `rspec/rails` (most likely in your
34
+ `spec/rails_helper.rb`):
35
+
36
+ ```ruby
37
+ # spec/rails_helper.rb
38
+
39
+ ...
40
+ require 'rspec/rails'
41
+ require 'rspec/document_requests/dsl' # <- this line
42
+ ...
43
+ ```
44
+
45
+ ### Marking code to document
46
+
47
+ In your example group (`describe`/`context`), simply add the `doc: true` metadata:
48
+
49
+ ```ruby
50
+ # spec/requests/session_spec.rb
51
+
52
+ RSpec.describe "Session resource", type: :request, doc: true do
53
+ describe "Create session" do
54
+ # User creation, will not be documented in "Session resource" documentation (nodoc)
55
+ before do
56
+ nodoc { post "/users", user: { username: "myuser", password: "123123" } }
57
+ end
58
+
59
+ context "Correct password" do
60
+ before { post "/session", session: { username: "myuser", password: "123123" } }
61
+ it { ... }
62
+ end
63
+
64
+ context "Incorrect password" do
65
+ before { post "/session", session: { username: "myuser", password: "456456" } }
66
+ it { ... }
67
+ end
68
+
69
+ # Extra test, will not be documented (doc: false)
70
+ context "Incorrect username", doc: false do
71
+ before { post "/session", session: { username: "wronguser", password: "123123" } }
72
+ it { ... }
73
+ end
74
+ end
75
+ end
76
+ ```
77
+
78
+ ### Running in documentation mode
79
+
80
+ To prevent every rspec run deleting all your documentation, this gem only
81
+ documents the requests when `DOC=true` environment variable is set. This will
82
+ also exclude any specs without `doc: true` metadata to make this run faster.
83
+
84
+ DOC=true rake spec
85
+
86
+ ### Explaining the request
87
+
88
+ **NOTE:** This DSL is not available in `doc: false` example groups (`describe`/`context`).
89
+
90
+ Just before your request, it's a good idea to explain (everything is optional):
91
+
92
+ ```ruby
93
+ # spec/requests/session_spec.rb
94
+
95
+ RSpec.describe "Session resource", type: :request, doc: true do
96
+ describe "Create session" do
97
+ before do
98
+ explain "Creating the user"
99
+ post "/users", user: { username: "myuser", password: "123123" }
100
+ end
101
+
102
+ before do
103
+ explain do # No request explanation
104
+ request do
105
+ parameter 'session[username]', "The username", required: true, type: :string
106
+ parameter 'session[password]', required: true, type: :string # No explanation
107
+ header 'Content-Type', ... # you get the point
108
+ end
109
+ response do
110
+ parameter 'message', "Message from the server", required: true # No type
111
+ parameter 'session_id', "The session ID" # Not required and no type
112
+ header 'Set-Cookie', ...
113
+ end
114
+ end
115
+ post "/session", session: { username: "myuser", password: "123123" }
116
+ end
117
+ it { ... }
118
+ end
119
+ end
120
+ ```
121
+
122
+ **NOTE:** Explaining response parameters only works when this gem can
123
+ parse the response body, see [here](#configuration) how to configure it.
124
+
125
+ ### Configuration
126
+
127
+ These are the possible configurations:
128
+
129
+ ```ruby
130
+ # spec/document_requests_helper.rb
131
+
132
+ RSpec::DocumentRequests.configure do |config|
133
+ # These are the default values
134
+
135
+ config.directory = "doc" # From Rails.root. CAREFUL: directory/root gets deleted!
136
+ config.root = "Requests" # I actually use API, figured this is a better default
137
+ # Example groups with less than this amount of example group (describe/context)
138
+ # levels under it will be grouped under its parent example group.
139
+ config.group_levels = 1
140
+ # Currently only markdown available with the gem.
141
+ # Contribute more by checking out lib/rspec/document_requests/writers/base.rb.
142
+ config.writer = RSpec::DocumentRequests:::Writers::Markdown
143
+ # Converts example groups (describe/context) to filenames (and directories), the default simple
144
+ # lower-cases and uses dash (-) for spaces.
145
+ config.filename_generator = -> (name) { name.downcase.gsub(/[_ ]+/, '-') }
146
+ # Allows showing response body as a table of parameters (with explanations).
147
+ # Don't forget to contribute more!
148
+ config.response_parser = -> (response) {
149
+ case
150
+ when response.content_type.json? then JSON.parse(response.body)
151
+ end
152
+ }
153
+
154
+ config.include_request_parameters = nil # nil means not used
155
+ config.exclude_request_parameters = []
156
+ config.hide_request_parameters = [] # Displays '...' instead of actual value
157
+ config.include_response_parameters = nil
158
+ config.exclude_response_parameters = []
159
+ config.hide_response_parameters = []
160
+ end
161
+ ```
162
+
163
+ Don't forget to require your file:
164
+
165
+ ```ruby
166
+ # .rspec
167
+
168
+ --require document_requests_helper
169
+
170
+ ```
171
+
172
+ ## REQUIRED best practices
173
+
174
+ It's always a good idea to follow this best-practice, but for this gem to work
175
+ it's necessary.
176
+
177
+ The implementation documents example groups (`describe`/`context`), and not
178
+ examples (`it`/`specify`).
179
+
180
+ It is important that you do not make requests
181
+ (`get`/`post`/`put`/`patch`/`delete`/`head`) from inside an example
182
+ (`it`/`specify`). It will only document requests from the first example of each
183
+ example group (`describe`/`context`).
184
+
185
+ It does however work only from inside examples (`it`/`specify`)
186
+ so requests from any form of `before`/`after`/`around` that is _not_ `:each`
187
+ will not be documented.
188
+
189
+ This best practice has other upsides other than making this gem work which I
190
+ will not describe here. But here is a nice example for this best practice to follow:
191
+
192
+ ```ruby
193
+ RSpec.describe "Some interface (class/feature/API resource)", doc: true do
194
+ subject { response }
195
+
196
+ describe "An interface within the interface (method/action/sub-feature)" do
197
+ before { post "/api/action", param: param_value }
198
+
199
+ context "Some scenario (attributes/params/prerequisite)" do
200
+ let(:param_value) { "scenario value" }
201
+
202
+ it { should have_http_status :ok }
203
+ # "body is not wrong" will not be documented
204
+ specify("body is not wrong") { expect(response.body).to eq "something" }
205
+ end
206
+
207
+ context "Another scenario" do
208
+ let(:param_value) { "another scenario" }
209
+
210
+ ...
211
+ end
212
+ end
213
+
214
+ context "Some scenario" do
215
+ before { nodoc { post "/api/scenario_prerequisite" } }
216
+
217
+ describe "An interface" do
218
+ before { get "/api/result" }
219
+
220
+ ...
221
+ end
222
+ end
223
+ end
224
+ ```
225
+
226
+ ## Development
227
+
228
+ After checking out the repo, run `bin/setup` to install dependencies.
229
+ Then, run `bin/console` for an interactive prompt that will allow you to experiment.
230
+
231
+ ## Contributing
232
+
233
+ The gem works, but it's missing some basics:
234
+
235
+ * Unit tests (specs).
236
+ * Example generated documentations.
237
+ * More writers (see `lib/rspec/document_requests/writers/base.rb`).
238
+
239
+ So...
240
+
241
+ 1. Fork it ( https://github.com/odedniv/rspec-document_requests/fork )
242
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
243
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
244
+ 4. Push to the branch (`git push origin my-new-feature`)
245
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/UNLICENSE ADDED
@@ -0,0 +1,24 @@
1
+ This is free and unencumbered software released into the public domain.
2
+
3
+ Anyone is free to copy, modify, publish, use, compile, sell, or
4
+ distribute this software, either in source code form or as a compiled
5
+ binary, for any purpose, commercial or non-commercial, and by any
6
+ means.
7
+
8
+ In jurisdictions that recognize copyright laws, the author or authors
9
+ of this software dedicate any and all copyright interest in the
10
+ software to the public domain. We make this dedication for the benefit
11
+ of the public at large and to the detriment of our heirs and
12
+ successors. We intend this dedication to be an overt act of
13
+ relinquishment in perpetuity of all present and future rights to this
14
+ software under copyright law.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ For more information, please refer to <http://unlicense.org/>
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "rspec/document_requests"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,21 @@
1
+ require 'rspec/document_requests/version'
2
+
3
+ module RSpec
4
+ module DocumentRequests
5
+ autoload :Configuration, 'rspec/document_requests/configuration'
6
+ autoload :Request, 'rspec/document_requests/request'
7
+ autoload :Explanation, 'rspec/document_requests/explanation'
8
+ autoload :DSL, 'rspec/document_requests/dsl'
9
+ autoload :OrganizedRequest, 'rspec/document_requests/organized_request'
10
+ autoload :Builder, 'rspec/document_requests/builder'
11
+ autoload :Writers, 'rspec/document_requests/writers'
12
+
13
+ def self.configuration
14
+ @configuration ||= Configuration.new
15
+ end
16
+
17
+ def self.configure
18
+ yield self.configuration
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,98 @@
1
+ module RSpec
2
+ module DocumentRequests
3
+ class Builder
4
+ def initialize
5
+ clean
6
+ @root = OrganizedRequest.organize
7
+ write
8
+ end
9
+
10
+ private
11
+
12
+ def config
13
+ DocumentRequests.configuration
14
+ end
15
+
16
+ def clean
17
+ root_directory = config.directory.join(config.filename_generator.call(config.root))
18
+ root_filename = root_directory.sub_ext(config.writer::EXTENSION)
19
+
20
+ root_directory.rmtree if root_directory.exist?
21
+ root_filename.delete if root_filename.exist?
22
+ end
23
+
24
+ def write(organized_request = @root, fullpath: config.directory)
25
+ @current = organized_request
26
+ @current_path = fullpath
27
+
28
+ @current_path.mkpath
29
+ @current_path.join(@current.filename).sub_ext(config.writer::EXTENSION).open('wb') do |file|
30
+ @writer = config.writer.new(file)
31
+ write_breadcrumb
32
+ write_title
33
+ @current.ungrouped_children.each { |child| write_child(child) }
34
+ write_recursive_requests(@current)
35
+ end
36
+
37
+ @current.ungrouped_children.each do |child|
38
+ # @current unusable from here on-end
39
+ write(child, fullpath: fullpath.join(organized_request.filename))
40
+ end
41
+ end
42
+
43
+ def write_recursive_requests(child)
44
+ missing_levels = []
45
+ if not child == @current
46
+ missing = child
47
+ missing_levels.unshift(missing.description) while (missing = missing.parent) and missing != @current
48
+ end
49
+
50
+ child.example_requests.to_a.uniq { |e,| e.example_group }.each do |example, requests|
51
+ write_example_title(example, missing_levels: missing_levels) unless child == @current
52
+ requests.each { |request| write_request(request, missing_levels: missing_levels) }
53
+ end
54
+
55
+ child.grouped_children.each_with_index do |grandchild, i|
56
+ write_recursive_requests(grandchild)
57
+ end
58
+ end
59
+
60
+ def write_breadcrumb
61
+ current = @current
62
+ parent_tree = []
63
+ parent_tree.unshift(current) while current = current.parent
64
+
65
+ return if parent_tree.empty?
66
+
67
+ parent_path = Pathname.new('.').join(*parent_tree.length.times.map { '..' })
68
+ parent_tree.each do |parent|
69
+ @writer.breadcrumb(
70
+ description: parent.description,
71
+ filename: parent_path.join(parent.filename).sub_ext(config.writer::EXTENSION),
72
+ last: parent == @current.parent,
73
+ )
74
+ parent_path = parent_path.split[0]
75
+ end
76
+ end
77
+
78
+ def write_title
79
+ @writer.title(@current.description)
80
+ end
81
+
82
+ def write_child(child)
83
+ @writer.child(
84
+ description: child.description,
85
+ filename: @current.filename.join(child.filename).sub_ext(config.writer::EXTENSION),
86
+ )
87
+ end
88
+
89
+ def write_example_title(example, missing_levels:)
90
+ @writer.example_title(example.example_group.metadata[:description], missing_levels: missing_levels)
91
+ end
92
+
93
+ def write_request(request, missing_levels:)
94
+ @writer.request(request, missing_levels: missing_levels)
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,35 @@
1
+ module RSpec
2
+ module DocumentRequests
3
+ class Configuration
4
+ def self.add_property(name, default = nil)
5
+ attr_writer name
6
+ define_method name do
7
+ instance_variable_get(:"@#{name}") || default
8
+ end
9
+ end
10
+
11
+ add_property :directory, ::Rails.root.join("doc")
12
+ add_property :root, "Requests"
13
+ add_property :group_levels, 1
14
+ add_property :writer, Writers::Markdown
15
+ add_property :filename_generator, -> (name) { name.downcase.gsub(/[_ ]+/, '-') }
16
+ add_property :response_parser, -> (response) {
17
+ case
18
+ when response.content_type.json? then JSON.parse(response.body)
19
+ end
20
+ }
21
+
22
+ [:request, :response].each do |side|
23
+ [:parameters, :headers].each do |part|
24
+ add_property :"include_#{side}_#{part}", nil
25
+ add_property :"exclude_#{side}_#{part}", []
26
+ add_property :"hide_#{side}_#{part}", []
27
+ end
28
+ end
29
+
30
+ def directory=(directory)
31
+ @directory = ::Rails.root.join(directory)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,63 @@
1
+ module RSpec
2
+ module DocumentRequests
3
+ module DSL
4
+ class << self
5
+ attr_accessor :documented_requests, :currently_documented_example
6
+ end
7
+ self.documented_requests = []
8
+ self.currently_documented_example = nil
9
+
10
+ [:get, :post, :patch, :put, :delete, :head].each do |method|
11
+ define_method(method) do |path, parameters = nil, headers_or_env = nil|
12
+ result = super(path, parameters, headers_or_env)
13
+
14
+ if not @document_request_prevented and DSL.currently_documented_example
15
+ DSL.documented_requests << Request.new(
16
+ explanation: document_request_explanation,
17
+ example: DSL.currently_documented_example,
18
+ method: method.to_s.upcase,
19
+ path: path,
20
+ request_parameters: parameters,
21
+ request_headers: headers,
22
+ response: response,
23
+ )
24
+ end
25
+ @document_request_explanation = Explanation.new
26
+ DSL.currently_documented_example = nil
27
+
28
+ result
29
+ end
30
+ end
31
+
32
+ def explain(message = nil, &block)
33
+ document_request_explanation.message = message
34
+ document_request_explanation.instance_eval(&block) if block_given?
35
+ end
36
+
37
+ def document_request_explanation
38
+ @document_request_explanation ||= Explanation.new
39
+ end
40
+
41
+ def nodoc
42
+ @document_request_prevented = true
43
+ begin
44
+ yield
45
+ ensure
46
+ @document_request_prevented = false
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ RSpec.configure do |config|
54
+ if ENV['DOC']
55
+ config.filter_run :doc
56
+ config.run_all_when_everything_filtered = false
57
+
58
+ config.after(:suite) { RSpec::DocumentRequests::Builder.new }
59
+ config.before { |ex| RSpec::DocumentRequests::DSL.currently_documented_example = ex }
60
+ end
61
+
62
+ config.include RSpec::DocumentRequests::DSL, doc: true
63
+ end
@@ -0,0 +1,39 @@
1
+ module RSpec
2
+ module DocumentRequests
3
+ class Explanation
4
+ class Side
5
+ attr_accessor :parameters, :headers
6
+
7
+ def initialize
8
+ @parameters = {}
9
+ @headers = {}
10
+ end
11
+
12
+ def parameter(name, *args)
13
+ @parameters[name] = Request::Parameter.new(*args)
14
+ end
15
+
16
+ def header(name, *args)
17
+ @headers[name] = Request::Parameter.new(*args)
18
+ end
19
+ end
20
+
21
+ attr_accessor :message
22
+
23
+ def initialize
24
+ @request = Side.new
25
+ @response = Side.new
26
+ end
27
+
28
+ def request(&block)
29
+ return @request if not block_given?
30
+ @request.instance_eval(&block)
31
+ end
32
+
33
+ def response(&block)
34
+ return @request if not block_given?
35
+ @response.instance_eval(&block)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,56 @@
1
+ module RSpec
2
+ module DocumentRequests
3
+ class OrganizedRequest
4
+ attr_reader :parent, :filename, :description, :example_requests, :children, :levels
5
+ def initialize(description:, parent: nil)
6
+ @description = description
7
+ @parent = parent
8
+ @filename = Pathname.new(DocumentRequests.configuration.filename_generator.call(description))
9
+ @example_requests = Hash.new { |h, k| h[k] = [] }
10
+ @children = {}
11
+ @levels = Hash.new { |h, k| h[k] = 0 }
12
+ @parent.increase_level(self) if @parent
13
+ end
14
+
15
+ def child(description)
16
+ @children[DocumentRequests.configuration.filename_generator.call(description)] ||= OrganizedRequest.new(description: description, parent: self)
17
+ end
18
+
19
+ def increase_level(child)
20
+ @grouped_children = @ungrouped_children = nil
21
+ @levels[child] += 1
22
+ @parent.increase_level(self) if @parent
23
+ end
24
+
25
+ def max_level
26
+ @levels.values.max || 0
27
+ end
28
+
29
+ def grouped_children
30
+ @grouped_children ||= @children.values.select { |child| child.max_level < DocumentRequests.configuration.group_levels }.sort_by(&:filename)
31
+ end
32
+
33
+ def ungrouped_children
34
+ @ungrouped_children ||= (@children.values - grouped_children).sort_by(&:filename)
35
+ end
36
+
37
+ def self.organize
38
+ root = OrganizedRequest.new(description: DocumentRequests.configuration.root)
39
+
40
+ DSL.documented_requests.each do |request|
41
+ current = request.example.example_group.metadata
42
+ metadata_tree = [current]
43
+ metadata_tree.unshift(current) while current = current[:parent_example_group]
44
+
45
+ organized_request = root
46
+ metadata_tree.each do |metadata|
47
+ organized_request = organized_request.child(metadata[:description])
48
+ end
49
+ organized_request.example_requests[request.example] << request
50
+ end
51
+
52
+ root
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,107 @@
1
+ module RSpec
2
+ module DocumentRequests
3
+ class Request
4
+ class Parameter
5
+ attr_accessor :message, :required, :type, :value
6
+ def initialize(message = nil, required: false, type: nil, value: nil)
7
+ @message = message
8
+ @required = required
9
+ @type = type
10
+ @value = value
11
+ end
12
+ end
13
+
14
+ attr_reader :explanation, :example, :method, :path
15
+ attr_reader :request_parameters, :request_headers
16
+ attr_reader :response, :parsed_response, :response_parameters, :response_headers
17
+ def initialize(explanation:, example:, method:, path:, request_parameters:, request_headers:, response:)
18
+ @explanation = explanation
19
+ @example = example
20
+ @method = method
21
+ @path = path
22
+ @response = response
23
+
24
+ process_request_parameters(request_parameters)
25
+ process_request_headers(request_headers)
26
+ process_response_parameters
27
+ process_response_headers
28
+ end
29
+
30
+ private
31
+
32
+ def self.filter_values(name)
33
+ define_method(name) do
34
+ values = instance_variable_get(:"@#{name}")
35
+ next values if values.nil?
36
+
37
+ included_values = DocumentRequests.configuration.send(:"include_#{name}")
38
+ excluded_values = DocumentRequests.configuration.send(:"exclude_#{name}")
39
+ hidden_values = DocumentRequests.configuration.send(:"hide_#{name}")
40
+
41
+ values.select! do |k, v|
42
+ next false if included_values and included_values.exclude?(k)
43
+ next false if excluded_values.include?(k)
44
+ values[k].value = "..." if hidden_values.include?(k)
45
+ true
46
+ end
47
+ values
48
+ end
49
+ end
50
+
51
+ public
52
+
53
+ filter_values :request_parameters
54
+ filter_values :request_headers
55
+ filter_values :response_parameters
56
+ filter_values :response_headers
57
+
58
+ private
59
+
60
+ def process_request_parameters(parameters, prefix: nil)
61
+ @request_parameters = {}
62
+ process_parameters(request_parameters, @request_parameters, explanation: @explanation.request.parameters)
63
+ end
64
+
65
+ def process_request_headers(headers)
66
+ @request_headers = {}
67
+ headers.each do |name, value|
68
+ @request_headers[name] = @explanation.request.headers[name] || Parameter.new
69
+ @request_headers[name].value = value
70
+ end
71
+ @explanation.request.headers.each { |name, header| @request_headers[name] ||= header }
72
+ end
73
+
74
+ def process_response_parameters(parameters = nil)
75
+ @parsed_response = DocumentRequests.configuration.response_parser.call(response)
76
+ if @parsed_response
77
+ @response_parameters = {}
78
+ process_parameters(@parsed_response, @response_parameters, explanation: @explanation.response.parameters)
79
+ end
80
+ end
81
+
82
+ def process_response_headers
83
+ @response_headers = {}
84
+ @response.headers.each do |name, value|
85
+ @response_headers[name] = @explanation.response.headers[name] || Parameter.new
86
+ @response_headers[name].value = value
87
+ end
88
+ @explanation.response.headers.each { |name, header| @response_headers[name] ||= header }
89
+ end
90
+
91
+ def process_parameters(input, output, explanation:, prefix: nil)
92
+ input.each do |key, value|
93
+ name = prefix ? "#{prefix}[#{key}]" : key
94
+ if value.is_a?(Hash)
95
+ process_parameters(value, output, explanation: explanation, prefix: name)
96
+ else
97
+ output[name] = explanation[name] || Parameter.new
98
+ output[name].value = value
99
+ end
100
+ end
101
+ if prefix.nil?
102
+ explanation.each { |name, parameter| output[name] ||= parameter }
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,5 @@
1
+ module RSpec
2
+ module DocumentRequests
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,8 @@
1
+ module RSpec
2
+ module DocumentRequests
3
+ module Writers
4
+ autoload :Base, 'rspec/document_requests/writers/base'
5
+ autoload :Markdown, 'rspec/document_requests/writers/markdown'
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,33 @@
1
+ module RSpec
2
+ module DocumentRequests
3
+ module Writers
4
+ class Base
5
+ #EXTENSION = ".something"
6
+
7
+ def initialize(file)
8
+ @file = file
9
+ end
10
+
11
+ def breadcrumb(description:, filename:, last:)
12
+ raise NotImplementedError
13
+ end
14
+
15
+ def title(description)
16
+ raise NotImplementedError
17
+ end
18
+
19
+ def child(description:, filename:)
20
+ raise NotImplementedError
21
+ end
22
+
23
+ def request_title(description, missing_levels:)
24
+ raise NotImplementedError
25
+ end
26
+
27
+ def request(request, missing_levels:)
28
+ raise NotImplementedError
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,108 @@
1
+ module RSpec
2
+ module DocumentRequests
3
+ module Writers
4
+ class Markdown < Base
5
+ EXTENSION = ".md"
6
+
7
+ def breadcrumb(description:, filename:, last:)
8
+ @file.write "[#{description}](#{filename})"
9
+ if not last
10
+ @file.write " > "
11
+ else
12
+ @file.puts
13
+ @file.puts
14
+ end
15
+ end
16
+
17
+ def title(description)
18
+ @file.puts "# #{description}"
19
+ @file.puts
20
+ end
21
+
22
+ def child(description:, filename:)
23
+ @file.puts "* [#{description}](#{filename})"
24
+ end
25
+
26
+ def example_title(description, missing_levels:)
27
+ @file.puts "## #{missing_levels.map { |l| "#{l} > " }.join} #{description}"
28
+ @file.puts
29
+ end
30
+
31
+ def request(request, missing_levels:)
32
+ @file.write <<FILE
33
+ ### Request #{"(#{request.explanation.message})" if request.explanation.message}
34
+
35
+ #{request.method} #{request.path}
36
+
37
+ FILE
38
+
39
+ if request.request_parameters.any?
40
+ @file.write <<FILE
41
+
42
+ #### Parameters
43
+
44
+ FILE
45
+ parameters_table(request.request_parameters)
46
+ end
47
+
48
+ if request.request_headers.any?
49
+ @file.write <<FILE
50
+
51
+ #### Headers
52
+
53
+ FILE
54
+ parameters_table(request.request_headers)
55
+ end
56
+
57
+ @file.write <<FILE
58
+
59
+ ### Response
60
+
61
+ #### Status
62
+
63
+ #{request.response.status} #{request.response.status_message}
64
+
65
+ FILE
66
+
67
+ if request.response_parameters and request.response_parameters.any?
68
+ @file.write <<FILE
69
+ #### Parameters
70
+
71
+ FILE
72
+ parameters_table(request.response_parameters)
73
+ end
74
+
75
+ @file.write <<FILE
76
+
77
+ #### Body
78
+
79
+ #{request.response.body}
80
+
81
+ FILE
82
+
83
+ if request.response_headers.any?
84
+ @file.write <<FILE
85
+
86
+ #### Headers
87
+
88
+ FILE
89
+ parameters_table(request.response_headers)
90
+ end
91
+ end
92
+
93
+ private
94
+
95
+ def parameters_table(parameters)
96
+ @file.write <<FILE
97
+ | Name | Type | Required? | Value | Explanation |
98
+ |------|------|-----------|-------|-------------|
99
+ FILE
100
+
101
+ parameters.sort.each do |name, parameter|
102
+ @file.puts "| #{name} | #{parameter.type} | #{"Required" if parameter.required} | #{parameter.value} | #{parameter.message} |"
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rspec/document_requests/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "rspec-document_requests"
8
+ spec.version = RSpec::DocumentRequests::VERSION
9
+ spec.authors = ["Oded Niv"]
10
+ spec.email = ["oded.niv@gmail.com"]
11
+
12
+ spec.summary = %q{Automatically documents requests generated by RSpec examples.}
13
+ spec.description = %q{Use this gem to document your API with your specs.}
14
+ spec.homepage = "https://github.com/odedniv/rspec-document_requests"
15
+ spec.license = "UNLICENSE"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_runtime_dependency "rspec-rails", ">= 3.0"
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.9"
25
+ spec.add_development_dependency "rake", "~> 10.0"
26
+ end
metadata ADDED
@@ -0,0 +1,108 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rspec-document_requests
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Oded Niv
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2015-06-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec-rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '3.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '3.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.9'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.9'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ description: Use this gem to document your API with your specs.
56
+ email:
57
+ - oded.niv@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - ".rspec"
64
+ - ".travis.yml"
65
+ - CODE_OF_CONDUCT.md
66
+ - Gemfile
67
+ - README.md
68
+ - Rakefile
69
+ - UNLICENSE
70
+ - bin/console
71
+ - bin/setup
72
+ - lib/rspec/document_requests.rb
73
+ - lib/rspec/document_requests/builder.rb
74
+ - lib/rspec/document_requests/configuration.rb
75
+ - lib/rspec/document_requests/dsl.rb
76
+ - lib/rspec/document_requests/explanation.rb
77
+ - lib/rspec/document_requests/organized_request.rb
78
+ - lib/rspec/document_requests/request.rb
79
+ - lib/rspec/document_requests/version.rb
80
+ - lib/rspec/document_requests/writers.rb
81
+ - lib/rspec/document_requests/writers/base.rb
82
+ - lib/rspec/document_requests/writers/markdown.rb
83
+ - rspec-document_requests.gemspec
84
+ homepage: https://github.com/odedniv/rspec-document_requests
85
+ licenses:
86
+ - UNLICENSE
87
+ metadata: {}
88
+ post_install_message:
89
+ rdoc_options: []
90
+ require_paths:
91
+ - lib
92
+ required_ruby_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ required_rubygems_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ requirements: []
103
+ rubyforge_project:
104
+ rubygems_version: 2.4.7
105
+ signing_key:
106
+ specification_version: 4
107
+ summary: Automatically documents requests generated by RSpec examples.
108
+ test_files: []