openapi_rspec 0.3 → 0.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0367f850252eff7b267d205f702bb3424a10f88d3333b700f416fc337879ede2
4
- data.tar.gz: ba6f7e1e9f0a8a706c6d5f712d50e2c5f916e56201a1131cd73505c6ceec4ce1
3
+ metadata.gz: 6bea441305836e75d93b65e38d4cb5297c16501228e286d6f444d2870a2bfc30
4
+ data.tar.gz: 3fa60f7de6682ba67b56b17562ad5e35d0548a51e8e61a5afe3f5462a38cbd36
5
5
  SHA512:
6
- metadata.gz: 3104ab50af0749801e8a31b422f67c905f76cf75059aa5ba1121008a30990cdce08f941808914cb730b64f7a363bf5a4a839ad682b90a26352b3264b801bc445
7
- data.tar.gz: eb0a1de49ad9adea06094c06b34d250b4f95f359dedc4c78ccf1e18d98cc5c0a93ec2e6afc1ab1a5fb54bda68827a679f49a24aa46c3680c9b44dc2103fa4913
6
+ metadata.gz: 6df382e223c149c2c250b8bb97a19470be4d784fe8bdd0cebc1161b806f69aef13cc13f6ca299a9c8f3df9792d2606bb64ecb893183d669769fb4b95aed27a6f
7
+ data.tar.gz: e4cf9b350d38b189fdf73e451143ccd5388e19104f9b557f62636e41791cfc2d28aec5e3e813f0868dc95eaa84165b3471585f3fa74b5f445e0e1743ac7f59a0
data/README.md CHANGED
@@ -30,13 +30,225 @@ Or install it yourself as:
30
30
 
31
31
  ## Usage
32
32
 
33
- See spec/example_spec.rb
33
+ Add `spec/openapi_helper.rb` and set `OpenapiRspec.config.app`:
34
34
 
35
- ## Development
35
+ ```ruby
36
+ # spec/openapi_helper.rb
37
+
38
+ require "rails_helper"
39
+
40
+ OpenapiRspec.config.app = Rails.application # or any other Rack app, thanks to rack-test gem
41
+ ```
42
+
43
+ Then configure path to your documentation. You can use documentation defined as:
44
+ - static file with `.yaml`/`.yml` or `.json` extension
45
+ - uri to OpenAPI schema in your application
46
+
47
+ ```ruby
48
+ # spec/openapi_helper.rb
49
+
50
+ #...
51
+
52
+ # static file
53
+ API_V1 = OpenapiRspec.api("./spec/data/openapi.yml")
54
+
55
+ # application path
56
+ API_V2 = OpenapiRspec.api_by_path("/openapi.json")
57
+ ```
58
+
59
+
60
+ ### Validate documentation against the OpenAPI 3.0 Specification:
61
+
62
+ ```ruby
63
+ RSpec.describe "API v1" do
64
+ subject { API_V1 }
65
+
66
+ it "is valid OpenAPI spec" do
67
+ expect(subject).to validate_documentation
68
+ end
69
+ end
70
+ ```
71
+
72
+ #### Validate documentation against custom schema
73
+
74
+ You can validate documentation against additional custom schemata, for example, to enforce organization documentation standards:
75
+
76
+ ```ruby
77
+ # spec/openapi_helper.rb
78
+
79
+ #...
80
+
81
+ API_V1 = OpenapiRspec.api("./spec/data/openapi.yml", additional_schemas: ["./spec/data/acme_schema.yml"])
82
+ ```
83
+
84
+ ```ruby
85
+ # openapi_v1_spec.rb
86
+
87
+ RSpec.describe "API v1" do
88
+ subject { API_V1 }
89
+
90
+ it "is valid OpenAPI and ACME spec" do
91
+ expect(subject).to validate_documentation
92
+ end
93
+ end
94
+ ```
95
+
96
+ ### Validate endpoints
97
+
98
+ General example:
99
+
100
+ ```ruby
101
+ require "openapi_rspec"
102
+
103
+ RSpec.describe "API v1 /pets" do
104
+
105
+ subject { API_V1 }
106
+
107
+ get "/pets" do
108
+ headers { { "X-Client-Device" => "ios" } }
109
+ query { { tags: ["lucky"] } }
110
+
111
+ validate_code(200) do |validator|
112
+ result = JSON.parse(validator.response.body)
113
+ expect(result.first["name"]).to eq("Lucky")
114
+ end
115
+ end
116
+
117
+ post "/pets" do
118
+ params { { name: "Lucky" } }
119
+
120
+ validate_code(201)
121
+ end
122
+
123
+ get "/pets/{id}" do
124
+ let(:id) { 23 }
125
+
126
+ validate_code(200)
127
+
128
+ context "when pet not found" do
129
+ let(:id) { "bad_id" }
130
+
131
+ validate_code(404)
132
+ end
133
+ end
134
+ ```
135
+
136
+ #### Helper methods
137
+
138
+ To validate response use:
139
+ - `get`, `post`, `put`, `patch`, `delete` and `head` methods to describe operation with the path
140
+ - `validate_code` method with passed expected code
141
+
142
+ ```ruby
143
+ # ...
144
+ get "/pets" do
145
+ validate_code(200)
146
+ end
147
+ # ...
148
+ ```
149
+
150
+ To set **request body** use `params` helper method:
151
+
152
+ ```ruby
153
+ # ...
154
+ post "/pets" do
155
+ params { { name: "Lucky" } }
156
+
157
+ validate_code(201)
158
+ end
159
+ # ...
160
+ ```
161
+
162
+ To set **path parameter** use `let` helper method:
163
+
164
+ ```ruby
165
+ # ...
166
+ get "/pets/{id}" do
167
+ let(:id) { 23 }
168
+
169
+ validate_code(200)
170
+ end
171
+ # ...
172
+ ```
173
+
174
+ To set **query parameters** use `query` helper method:
175
+
176
+ ```ruby
177
+ # ...
178
+ get "/pets" do
179
+ query { { name: "lucky" } }
180
+
181
+ validate_code(200)
182
+ end
183
+ # ...
184
+ ```
185
+
186
+ To set **header parameters** use `headers` helper method:
187
+
188
+ ```ruby
189
+ # ...
190
+ get "/pets" do
191
+ headers { { "X-User-Token" => "bad_token" } }
192
+
193
+ validate_code(401)
194
+ end
195
+ # ...
196
+ ```
197
+
198
+ #### Custom response validation
199
+
200
+ You can access response to add custom validation like this:
201
+
202
+ ```ruby
203
+ # ...
204
+ get "/pets" do
205
+ validate_code(200) do |validator|
206
+ result = JSON.parse(validator.response.body)
207
+ expect(result).not_to be_empty
208
+ end
209
+ end
210
+ # ...
211
+ ```
212
+
213
+ #### Prefix API requests
214
+
215
+ To prefix each request with `"/some_path"` use `:api_base_path` option:
216
+
217
+ ```ruby
218
+ # spec/openapi_helper.rb
219
+
220
+ #...
221
+
222
+ API_V1 = OpenapiRspec.api("./spec/data/openapi.yml", api_base_path: "/some_path")
223
+ ```
224
+
225
+ #### Validate that all documented routes are tested
226
+
227
+ To validate this we will use a small hack:
228
+
229
+ ```ruby
230
+ # spec/openapi_helper.rb
231
+
232
+ # ...
233
+
234
+ API_V1_DOC = OpenapiRspec.api("./openapi/openapi.yml", api_base_path: "/api/v1")
235
+
236
+ RSpec.configure do |config|
237
+ config.after(:suite) do
238
+ unvalidated_requests = API_V1_DOC.unvalidated_requests
239
+
240
+ if unvalidated_requests.any? && ENV["TEST_COVERAGE"]
241
+ raise unvalidated_requests.map { |path| "Path #{path.join(' ')} not tested" }.join("\n")
242
+ end
243
+ end
244
+ end
245
+ ```
246
+
247
+ Then run your specs:
36
248
 
37
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
249
+ $ TEST_COVERAGE=1 rspec
38
250
 
39
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
251
+ This will raise an error if any of the documented paths are not validated.
40
252
 
41
253
  ## Contributing
42
254
 
data/lib/openapi_rspec.rb CHANGED
@@ -1,40 +1,27 @@
1
- require "dry-configurable"
2
- require "openapi_validator"
3
- require "openapi_builder"
4
- require "rspec"
1
+ # frozen_string_literal: true
5
2
 
6
- require "openapi_rspec/helpers"
7
- require "openapi_rspec/matchers"
8
- require "openapi_rspec/module_helpers"
9
- require "openapi_rspec/version"
3
+ require 'dry-configurable'
4
+ require 'openapi_validator'
5
+ require 'rspec'
6
+
7
+ require 'openapi_rspec/helpers'
8
+ require 'openapi_rspec/matchers'
9
+ require 'openapi_rspec/module_helpers'
10
+ require 'openapi_rspec/schema_loader'
11
+ require 'openapi_rspec/version'
10
12
 
11
13
  module OpenapiRspec
12
14
  extend Dry::Configurable
13
15
 
14
16
  setting :app, reader: true
15
17
 
16
- def self.api(doc, build: false, **params)
17
- doc = OpenapiBuilder.call(doc).data if build
18
+ def self.api(doc, **params)
18
19
  OpenapiValidator.call(doc, **params)
19
20
  end
20
21
 
21
- def self.api_by_path(openapi_path, **params)
22
- session = Rack::Test::Session.new(config.app)
23
- begin
24
- response = session.get(openapi_path)
25
- rescue StandardError => e
26
- raise "Unable to perform GET request for swagger json: #{openapi_path} - #{e}."
27
- end
28
-
29
- parsed_doc = case openapi_path.split(".").last
30
- when "yml", "yaml"
31
- YAML.load(response.body)
32
- when "json"
33
- JSON.parse(response.body)
34
- else
35
- raise "Unable to parse OpenAPI doc, '#{openapi_path}' is undefined format"
36
- end
37
- OpenapiValidator.call(parsed_doc, **params)
22
+ def self.api_by_path(path, **params)
23
+ doc = SchemaLoader.call(path)
24
+ api(doc, **params)
38
25
  end
39
26
 
40
27
  RSpec.configure do |config|
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module OpenapiRspec
2
4
  class DocumentationValidator
3
5
  def matches?(doc)
@@ -6,7 +8,7 @@ module OpenapiRspec
6
8
  end
7
9
 
8
10
  def description
9
- "be a valid documentation"
11
+ 'be a valid OpenAPI documentation'
10
12
  end
11
13
 
12
14
  def failure_message
@@ -1,15 +1,18 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module OpenapiRspec
2
4
  module Helpers
3
5
  def request_params(metadata)
4
- {}.tap do |hash|
5
- hash[:method] = defined?(http_method) ? http_method : metadata[:method]
6
- hash[:path] = defined?(uri) ? uri : metadata[:uri]
7
- hash[:media_type] = openapi_rspec_media_type if defined? openapi_rspec_media_type
8
- hash[:params] = path_params(hash[:path])
9
- hash[:params].merge!(openapi_rspec_params) if defined? openapi_rspec_params
10
- hash[:headers] = openapi_rspec_headers if defined? openapi_rspec_headers
11
- hash[:query] = openapi_rspec_query if defined? openapi_rspec_query
12
- end
6
+ path = defined?(uri) ? uri : metadata[:uri]
7
+ method = defined?(http_method) ? http_method : metadata[:method]
8
+ {
9
+ method: method,
10
+ path: path,
11
+ params: path_params(path).merge!(openapi_rspec_params),
12
+ headers: openapi_rspec_headers,
13
+ query: openapi_rspec_query,
14
+ media_type: openapi_rspec_media_type
15
+ }
13
16
  end
14
17
 
15
18
  def path_params(path)
@@ -1,5 +1,7 @@
1
- require "openapi_rspec/documentation_validator"
2
- require "openapi_rspec/request_validator"
1
+ # frozen_string_literal: true
2
+
3
+ require 'openapi_rspec/documentation_validator'
4
+ require 'openapi_rspec/request_validator'
3
5
 
4
6
  module OpenapiRspec
5
7
  module Matchers
@@ -1,8 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module OpenapiRspec
2
4
  module ModuleHelpers
3
5
  def validate_code(code, &block)
4
6
  specify do |example|
5
- validator = RequestValidator.new(**request_params(example.metadata[:openapi_rspec]), code: code)
7
+ metadata = example.metadata[:openapi_rspec]
8
+ validator = RequestValidator.new(**request_params(metadata), code: code)
6
9
  expect(subject).to validator
7
10
  instance_exec validator, &block if block_given?
8
11
  end
@@ -49,10 +52,11 @@ module OpenapiRspec
49
52
  end
50
53
 
51
54
  def process(method, uri)
52
- metadata[:openapi_rspec] = {
53
- uri: uri,
54
- method: method
55
- }
55
+ metadata[:openapi_rspec] = { uri: uri, method: method }
56
+ let(:openapi_rspec_media_type) { 'application/json' }
57
+ let(:openapi_rspec_params) { {} }
58
+ let(:openapi_rspec_headers) { {} }
59
+ let(:openapi_rspec_query) { {} }
56
60
  context "#{method.to_s.upcase} #{uri}" do
57
61
  yield if block_given?
58
62
  end
@@ -1,40 +1,51 @@
1
- require "rack/test"
2
- require "uri"
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry-initializer'
4
+ require 'rack/test'
5
+ require 'uri'
3
6
 
4
7
  module OpenapiRspec
5
8
  class RequestValidator
9
+ extend Dry::Initializer
6
10
  include Rack::Test::Methods
7
11
 
12
+ option :path
13
+ option :method
14
+ option :code
15
+ option :media_type
16
+ option :params
17
+ option :query
18
+ option :headers
19
+
20
+ attr_reader :response, :result
21
+
8
22
  def app
9
23
  OpenapiRspec.app
10
24
  end
11
25
 
12
- def initialize(path:, method:, code:, media_type: "application/json", params: {}, query: {}, headers: {})
13
- @path = path
14
- @method = method
15
- @code = code
16
- @media_type = media_type
17
- @query = query
18
- @headers = headers
19
- @params = params
20
- end
21
-
22
- attr_reader :method, :path, :code, :media_type, :query, :headers, :params, :response
23
-
24
26
  def matches?(doc)
25
27
  @result = doc.validate_request(path: path, method: method, code: code, media_type: media_type)
28
+ return false unless result.valid?
26
29
 
27
- return false unless @result.valid?
30
+ perform_request(doc)
31
+ result.validate_response(body: response.body, code: response.status)
32
+ result.valid?
33
+ end
28
34
 
29
- headers.each do |key, value|
30
- header key, value
35
+ def description
36
+ "return valid response with code #{code} on `#{method.to_s.upcase} #{path}`"
37
+ end
38
+
39
+ def failure_message
40
+ if response
41
+ (%W[Response: #{response.body}] + result.errors).join("\n")
42
+ else
43
+ result.errors.join("\n")
31
44
  end
32
- request(request_uri(doc), method: method, **request_params)
33
- @response = last_response
34
- @result.validate_response(body: @response.body, code: @response.status)
35
- @result.valid?
36
45
  end
37
46
 
47
+ private
48
+
38
49
  def request_uri(doc)
39
50
  path.scan(/\{([^\}]*)\}/).each do |param|
40
51
  key = param.first.to_sym
@@ -48,23 +59,19 @@ module OpenapiRspec
48
59
  "#{doc.api_base_path}#{path}?#{URI.encode_www_form(query)}"
49
60
  end
50
61
 
62
+ def perform_request(doc)
63
+ headers.each do |key, value|
64
+ header key, value
65
+ end
66
+ request(request_uri(doc), method: method, **request_params)
67
+ @response = last_response
68
+ end
69
+
51
70
  def request_params
52
71
  {
53
72
  headers: headers,
54
- params: params,
73
+ params: params
55
74
  }
56
75
  end
57
-
58
- def description
59
- "return valid response with code #{code} on `#{method.to_s.upcase} #{path}`"
60
- end
61
-
62
- def failure_message
63
- if @response
64
- (%W(Response: #{@response.body}) + @result.errors).join("\n")
65
- else
66
- @result.errors.join("\n")
67
- end
68
- end
69
76
  end
70
77
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenapiRspec
4
+ module SchemaLoader
5
+ def self.call(path, app: OpenapiRspec.config.app)
6
+ response = request(path, app)
7
+ parse(response)
8
+ end
9
+
10
+ def self.parse(schema)
11
+ begin
12
+ JSON.parse(schema)
13
+ rescue JSON::ParserError
14
+ YAML.safe_load(schema)
15
+ end
16
+ rescue StandardError => e
17
+ raise "Unable to parse OpenAPI schema. #{e}"
18
+ end
19
+
20
+ def self.request(path, app)
21
+ session = Rack::Test::Session.new(app)
22
+ response = session.get(path)
23
+
24
+ raise "Response code: #{response.status}" unless response.successful?
25
+ raise 'Empty body' if response.body.empty?
26
+
27
+ response.body
28
+ rescue StandardError => e
29
+ raise "Unable to perform GET request for the OpenAPI schema '#{path}'. #{e}"
30
+ end
31
+ end
32
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module OpenapiRspec
2
- VERSION = "0.3"
4
+ VERSION = '0.4'
3
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openapi_rspec
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.3'
4
+ version: '0.4'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Svyatoslav Kryukov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-09-14 00:00:00.000000000 Z
11
+ date: 2019-10-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dry-configurable
@@ -25,19 +25,19 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0.8'
27
27
  - !ruby/object:Gem::Dependency
28
- name: openapi_builder
28
+ name: dry-initializer
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0.1'
33
+ version: '3.0'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ">="
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0.1'
40
+ version: '3.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: openapi_validator
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -123,6 +123,7 @@ files:
123
123
  - lib/openapi_rspec/matchers.rb
124
124
  - lib/openapi_rspec/module_helpers.rb
125
125
  - lib/openapi_rspec/request_validator.rb
126
+ - lib/openapi_rspec/schema_loader.rb
126
127
  - lib/openapi_rspec/version.rb
127
128
  homepage: https://github.com/medsolutions/openapi_rspec
128
129
  licenses: