openapi_rspec 0.3 → 0.4

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