openapi_rspec 0.2.0 → 0.5

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: 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: []