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 +4 -4
- data/README.md +216 -4
- data/lib/openapi_rspec.rb +14 -27
- data/lib/openapi_rspec/documentation_validator.rb +3 -1
- data/lib/openapi_rspec/helpers.rb +12 -9
- data/lib/openapi_rspec/matchers.rb +4 -2
- data/lib/openapi_rspec/module_helpers.rb +9 -5
- data/lib/openapi_rspec/request_validator.rb +41 -34
- data/lib/openapi_rspec/schema_loader.rb +32 -0
- data/lib/openapi_rspec/version.rb +3 -1
- metadata +8 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6bea441305836e75d93b65e38d4cb5297c16501228e286d6f444d2870a2bfc30
|
4
|
+
data.tar.gz: 3fa60f7de6682ba67b56b17562ad5e35d0548a51e8e61a5afe3f5462a38cbd36
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
33
|
+
Add `spec/openapi_helper.rb` and set `OpenapiRspec.config.app`:
|
34
34
|
|
35
|
-
|
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
|
-
|
249
|
+
$ TEST_COVERAGE=1 rspec
|
38
250
|
|
39
|
-
|
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
|
-
|
2
|
-
require "openapi_validator"
|
3
|
-
require "openapi_builder"
|
4
|
-
require "rspec"
|
1
|
+
# frozen_string_literal: true
|
5
2
|
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
|
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,
|
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(
|
22
|
-
|
23
|
-
|
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
|
-
|
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
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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,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
|
-
|
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
|
-
|
54
|
-
|
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
|
-
|
2
|
-
|
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
|
-
|
30
|
+
perform_request(doc)
|
31
|
+
result.validate_response(body: response.body, code: response.status)
|
32
|
+
result.valid?
|
33
|
+
end
|
28
34
|
|
29
|
-
|
30
|
-
|
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
|
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.
|
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-
|
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:
|
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
|
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
|
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:
|