openapi_rspec 0.2.0 → 0.5

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: 0bc7e1b91f162a32c1d4be8d87814ebd2f62175f4d8846ac570179ee46c2d389
4
- data.tar.gz: 11b302ed09c95e037be7732f0b388b912f076224ac24fb81047e9da70b53c901
3
+ metadata.gz: 748ba53aceac0ac02c274be192c1fadb895df66212e38da8bf9f88f80943f504
4
+ data.tar.gz: d2ab4b53ae116da4daf0bdaec48e2c8ff4d54b8ff98e1e4dc05bfd2d0d4c6dfc
5
5
  SHA512:
6
- metadata.gz: 3d6edc80d5593740000f0ac64315c9eeab1dd514dc8a49fbedb216f89d10cac8a088fbfefc33ea10c180e8bc74f0238a0c8b9668ebd6a89434e799636c40eea9
7
- data.tar.gz: 020771cf2c6aacdd5aa85f7cbfe8fbd499f46bcfb0096ba29c0f4e9eb03079a8e1d588172cb7df5d9aad3c10034e1fc69eab71c5de87afc4837e41ec0c0a508e
6
+ metadata.gz: 1a89fcf6b169fddf3ade6b8032354460046e5be264b38ba4b6fd164a2c5caaa1fc10a5369599701aa9b03bd347a6dbff313a3b7ca36e48e12ac2557fde4ca6e4
7
+ data.tar.gz: 15d575f392ac9ba39e2e5e11aa1732596f8ca9eaf43cbba72cf76ef036901d5bdd06ed40f8e5c7f5ff45afcd1038b2552c266e1bd420b0703642322c08ce2bde
data/README.md CHANGED
@@ -7,6 +7,7 @@
7
7
  [![Build Status](https://travis-ci.org/medsolutions/openapi_rspec.svg?branch=master)][travis]
8
8
  [![Code Climate](https://codeclimate.com/github/medsolutions/openapi_rspec/badges/gpa.svg)][codeclimate]
9
9
  [![Test Coverage](https://codeclimate.com/github/medsolutions/openapi_rspec/badges/coverage.svg)][codeclimate]
10
+ [![Ruby Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://github.com/testdouble/standard)
10
11
 
11
12
  Test your API against OpenApi v3 documentation
12
13
 
@@ -30,13 +31,238 @@ Or install it yourself as:
30
31
 
31
32
  ## Usage
32
33
 
33
- See spec/example_spec.rb
34
+ Add `spec/openapi_helper.rb` and set `OpenapiRspec.config.app`:
34
35
 
35
- ## Development
36
+ ```ruby
37
+ # spec/openapi_helper.rb
38
+
39
+ require "rails_helper"
40
+
41
+ OpenapiRspec.config.app = Rails.application # or any other Rack app, thanks to rack-test gem
42
+ ```
43
+
44
+ Then configure path to your documentation. You can use documentation defined as:
45
+ - static file with `.yaml`/`.yml` or `.json` extension
46
+ - uri to OpenAPI schema in your application
47
+
48
+ ```ruby
49
+ # spec/openapi_helper.rb
50
+
51
+ #...
52
+
53
+ # static file
54
+ API_V1 = OpenapiRspec.api("./spec/data/openapi.yml")
55
+
56
+ # application path
57
+ API_V2 = OpenapiRspec.api_by_path("/openapi.json")
58
+ ```
59
+
60
+
61
+ ### Validate documentation against the OpenAPI 3.0 Specification:
62
+
63
+ ```ruby
64
+ RSpec.describe "API v1" do
65
+ subject { API_V1 }
66
+
67
+ it "is valid OpenAPI spec" do
68
+ expect(subject).to validate_documentation
69
+ end
70
+ end
71
+ ```
72
+
73
+ #### Validate documentation against custom schema
74
+
75
+ You can validate documentation against additional custom schemata, for example, to enforce organization documentation standards:
76
+
77
+ ```ruby
78
+ # spec/openapi_helper.rb
79
+
80
+ #...
81
+
82
+ API_V1 = OpenapiRspec.api("./spec/data/openapi.yml", additional_schemas: ["./spec/data/acme_schema.yml"])
83
+ ```
84
+
85
+ ```ruby
86
+ # openapi_v1_spec.rb
87
+
88
+ RSpec.describe "API v1" do
89
+ subject { API_V1 }
90
+
91
+ it "is valid OpenAPI and ACME spec" do
92
+ expect(subject).to validate_documentation
93
+ end
94
+ end
95
+ ```
96
+
97
+ ### Validate endpoints
98
+
99
+ General example:
100
+
101
+ ```ruby
102
+ require "openapi_rspec"
103
+
104
+ RSpec.describe "API v1 /pets" do
105
+
106
+ subject { API_V1 }
107
+
108
+ get "/pets" do
109
+ headers { { "X-Client-Device" => "ios" } }
110
+ query { { tags: ["lucky"] } }
111
+
112
+ validate_code(200) do |validator|
113
+ result = JSON.parse(validator.response.body)
114
+ expect(result.first["name"]).to eq("Lucky")
115
+ end
116
+ end
117
+
118
+ post "/pets" do
119
+ params { { name: "Lucky" } }
120
+
121
+ validate_code(201)
122
+ end
123
+
124
+ get "/pets/{id}" do
125
+ let(:id) { 23 }
126
+
127
+ validate_code(200)
128
+
129
+ context "when pet not found" do
130
+ let(:id) { "bad_id" }
131
+
132
+ validate_code(404)
133
+ end
134
+ end
135
+ ```
136
+
137
+ #### Helper methods
138
+
139
+ To validate response use:
140
+ - `get`, `post`, `put`, `patch`, `delete` and `head` methods to describe operation with the path
141
+ - `validate_code` method with passed expected code
142
+
143
+ ```ruby
144
+ # ...
145
+ get "/pets" do
146
+ validate_code(200)
147
+ end
148
+ # ...
149
+ ```
150
+
151
+ To set **request body** (as form data) use `params` helper method and provide a `Hash`:
152
+
153
+ ```ruby
154
+ # ...
155
+ post "/pets" do
156
+ params { { name: "Lucky" } }
157
+
158
+ validate_code(201)
159
+ end
160
+ # ...
161
+ ```
162
+
163
+ To set **raw request body** use the `params` helper method and provide a `String`:
164
+
165
+ ```ruby
166
+ # ...
167
+ post "/pets" do
168
+ params { JSON.dump(name: "Lucky") }
169
+
170
+ validate_code(201)
171
+ end
172
+ # ...
173
+ ```
174
+
175
+
176
+ To set **path parameter** use `let` helper method:
177
+
178
+ ```ruby
179
+ # ...
180
+ get "/pets/{id}" do
181
+ let(:id) { 23 }
182
+
183
+ validate_code(200)
184
+ end
185
+ # ...
186
+ ```
187
+
188
+ To set **query parameters** use `query` helper method:
189
+
190
+ ```ruby
191
+ # ...
192
+ get "/pets" do
193
+ query { { name: "lucky" } }
194
+
195
+ validate_code(200)
196
+ end
197
+ # ...
198
+ ```
199
+
200
+ To set **header parameters** use `headers` helper method:
201
+
202
+ ```ruby
203
+ # ...
204
+ get "/pets" do
205
+ headers { { "X-User-Token" => "bad_token" } }
206
+
207
+ validate_code(401)
208
+ end
209
+ # ...
210
+ ```
211
+
212
+ #### Custom response validation
213
+
214
+ You can access response to add custom validation like this:
215
+
216
+ ```ruby
217
+ # ...
218
+ get "/pets" do
219
+ validate_code(200) do |validator|
220
+ result = JSON.parse(validator.response.body)
221
+ expect(result).not_to be_empty
222
+ end
223
+ end
224
+ # ...
225
+ ```
226
+
227
+ #### Prefix API requests
228
+
229
+ To prefix each request with `"/some_path"` use `:api_base_path` option:
230
+
231
+ ```ruby
232
+ # spec/openapi_helper.rb
233
+
234
+ #...
235
+
236
+ API_V1 = OpenapiRspec.api("./spec/data/openapi.yml", api_base_path: "/some_path")
237
+ ```
238
+
239
+ #### Validate that all documented routes are tested
240
+
241
+ To validate this we will use a small hack:
242
+
243
+ ```ruby
244
+ # spec/openapi_helper.rb
245
+
246
+ # ...
247
+
248
+ API_V1_DOC = OpenapiRspec.api("./openapi/openapi.yml", api_base_path: "/api/v1")
249
+
250
+ RSpec.configure do |config|
251
+ config.after(:suite) do
252
+ unvalidated_requests = API_V1_DOC.unvalidated_requests
253
+
254
+ if unvalidated_requests.any? && ENV["TEST_COVERAGE"]
255
+ raise unvalidated_requests.map { |path| "Path #{path.join(' ')} not tested" }.join("\n")
256
+ end
257
+ end
258
+ end
259
+ ```
260
+
261
+ Then run your specs:
36
262
 
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.
263
+ $ TEST_COVERAGE=1 rspec
38
264
 
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).
265
+ This will raise an error if any of the documented paths are not validated.
40
266
 
41
267
  ## Contributing
42
268
 
@@ -1,11 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "dry-configurable"
2
4
  require "openapi_validator"
3
- require "openapi_builder"
4
5
  require "rspec"
5
6
 
6
7
  require "openapi_rspec/helpers"
7
8
  require "openapi_rspec/matchers"
8
9
  require "openapi_rspec/module_helpers"
10
+ require "openapi_rspec/schema_loader"
9
11
  require "openapi_rspec/version"
10
12
 
11
13
  module OpenapiRspec
@@ -13,11 +15,15 @@ module OpenapiRspec
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
 
22
+ def self.api_by_path(path, **params)
23
+ doc = SchemaLoader.call(path)
24
+ api(doc, **params)
25
+ end
26
+
21
27
  RSpec.configure do |config|
22
28
  config.extend ModuleHelpers
23
29
  config.include Helpers
@@ -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,20 +1,26 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module OpenapiRspec
2
4
  module Helpers
3
- def expect_validate_request(code, metadata: {})
4
- expect(subject).to validate_request(**request_params(metadata), code: code)
5
- end
6
-
7
5
  def request_params(metadata)
8
- Hash.new.tap do |hash|
9
- hash[:method] = defined?(http_method) ? http_method : metadata[:method]
10
- hash[:path] = defined?(uri) ? uri : metadata[:uri]
11
- hash[:params] = path_params(hash[:path])
12
- hash[:params].merge!(openapi_rspec_params) if defined? openapi_rspec_params
13
- hash[:headers] = openapi_rspec_headers if defined? openapi_rspec_headers
14
- hash[:query] = openapi_rspec_query if defined? openapi_rspec_query
6
+ path = defined?(uri) ? uri : metadata[:uri]
7
+ method = defined?(http_method) ? http_method : metadata[:method]
8
+ params = if openapi_rspec_params.is_a?(Hash)
9
+ path_params(path).merge!(openapi_rspec_params)
10
+ else
11
+ openapi_rspec_params
15
12
  end
13
+
14
+ {
15
+ method: method,
16
+ path: path,
17
+ params: params,
18
+ headers: openapi_rspec_headers,
19
+ query: openapi_rspec_query,
20
+ media_type: openapi_rspec_media_type,
21
+ }
16
22
  end
17
-
23
+
18
24
  def path_params(path)
19
25
  path_params = {}
20
26
  path.scan(/\{([^\}]*)\}/).each do |param|
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "openapi_rspec/documentation_validator"
2
4
  require "openapi_rspec/request_validator"
3
5
 
@@ -1,53 +1,62 @@
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
- expect_validate_request(code, metadata: example.metadata[:openapi_rspec])
6
- instance_eval &block if block_given?
7
+ metadata = example.metadata[:openapi_rspec]
8
+ validator = RequestValidator.new(**request_params(metadata), code: code)
9
+ expect(subject).to validator
10
+ instance_exec validator, &block if block_given?
7
11
  end
8
12
  end
9
13
 
10
14
  def params(&block)
11
15
  let(:openapi_rspec_params, &block)
12
16
  end
13
-
17
+
18
+ def media_type(&block)
19
+ let(:openapi_rspec_media_type, &block)
20
+ end
21
+
14
22
  def headers(&block)
15
23
  let(:openapi_rspec_headers, &block)
16
24
  end
17
-
25
+
18
26
  def query(&block)
19
27
  let(:openapi_rspec_query, &block)
20
28
  end
21
-
29
+
22
30
  def get(*args, &block)
23
31
  process(:get, *args, &block)
24
32
  end
25
-
33
+
26
34
  def post(*args, &block)
27
35
  process(:post, *args, &block)
28
36
  end
29
-
37
+
30
38
  def put(*args, &block)
31
39
  process(:put, *args, &block)
32
40
  end
33
-
41
+
34
42
  def delete(*args, &block)
35
43
  process(:delete, *args, &block)
36
44
  end
37
-
45
+
38
46
  def head(*args, &block)
39
47
  process(:head, *args, &block)
40
48
  end
41
-
49
+
42
50
  def patch(*args, &block)
43
51
  process(:patch, *args, &block)
44
52
  end
45
-
53
+
46
54
  def process(method, uri)
47
- metadata[:openapi_rspec] = {
48
- uri: uri,
49
- method: method
50
- }
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) { {} }
51
60
  context "#{method.to_s.upcase} #{uri}" do
52
61
  yield if block_given?
53
62
  end
@@ -1,38 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry-initializer"
1
4
  require "rack/test"
2
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:, params: {}, query: {}, headers: {})
13
- @path = path
14
- @method = method
15
- @code = code
16
- @query = query
17
- @headers = headers
18
- @params = params
19
- end
26
+ def matches?(doc)
27
+ @result = doc.validate_request(path: path, method: method, code: code, media_type: media_type)
28
+ return false unless result.valid?
20
29
 
21
- attr_reader :method, :path, :code, :query, :headers, :params
30
+ perform_request(doc)
31
+ result.validate_response(body: response.body, code: response.status)
32
+ result.valid?
33
+ end
22
34
 
23
- def matches?(doc)
24
- @result = doc.validate_request(path: path, method: method, code: code)
25
- return false unless @result.valid?
35
+ def description
36
+ "return valid response with code #{code} on `#{method.to_s.upcase} #{path}`"
37
+ end
26
38
 
27
- headers.each do |key, value|
28
- header key, value
39
+ def failure_message
40
+ if response
41
+ (%W[Response: #{response.body}] + result.errors).join("\n")
42
+ else
43
+ result.errors.join("\n")
29
44
  end
30
- request(request_uri(doc), method: method, **request_params)
31
- @response = last_response
32
- @result.validate_response(body: @response.body, code: @response.status)
33
- @result.valid?
34
45
  end
35
46
 
47
+ private
48
+
36
49
  def request_uri(doc)
37
50
  path.scan(/\{([^\}]*)\}/).each do |param|
38
51
  key = param.first.to_sym
@@ -46,23 +59,19 @@ module OpenapiRspec
46
59
  "#{doc.api_base_path}#{path}?#{URI.encode_www_form(query)}"
47
60
  end
48
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
+
49
70
  def request_params
50
71
  {
51
72
  headers: headers,
52
73
  params: params,
53
74
  }
54
75
  end
55
-
56
- def description
57
- "return valid response with code #{code} on `#{method.to_s.upcase} #{path}`"
58
- end
59
-
60
- def failure_message
61
- if @response
62
- (%W(Response: #{@response.body}) + @result.errors).join("\n")
63
- else
64
- @result.errors.join("\n")
65
- end
66
- end
67
76
  end
68
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 => 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 => 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.2.0"
4
+ VERSION = "0.5"
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.2.0
4
+ version: '0.5'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Svyatoslav Kryukov
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-04-27 00:00:00.000000000 Z
11
+ date: 2020-08-13 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
@@ -98,17 +98,17 @@ dependencies:
98
98
  name: rake
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
- - - "~>"
101
+ - - ">="
102
102
  - !ruby/object:Gem::Version
103
- version: '10.0'
103
+ version: 12.3.3
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
- - - "~>"
108
+ - - ">="
109
109
  - !ruby/object:Gem::Version
110
- version: '10.0'
111
- description:
110
+ version: 12.3.3
111
+ description:
112
112
  email:
113
113
  - s.g.kryukov@yandex.ru
114
114
  executables: []
@@ -123,12 +123,13 @@ 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:
129
130
  - MIT
130
131
  metadata: {}
131
- post_install_message:
132
+ post_install_message:
132
133
  rdoc_options: []
133
134
  require_paths:
134
135
  - lib
@@ -144,7 +145,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
144
145
  version: '0'
145
146
  requirements: []
146
147
  rubygems_version: 3.0.3
147
- signing_key:
148
+ signing_key:
148
149
  specification_version: 4
149
150
  summary: Test your API against OpenApi v3 documentation
150
151
  test_files: []