openapi_first 2.10.1 → 2.11.0
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/CHANGELOG.md +4 -0
- data/README.md +82 -79
- data/lib/openapi_first/error_responses/default.rb +1 -0
- data/lib/openapi_first/error_responses/jsonapi.rb +1 -0
- data/lib/openapi_first/response_parser.rb +3 -0
- data/lib/openapi_first/test/configuration.rb +1 -1
- data/lib/openapi_first/test/observe.rb +11 -2
- data/lib/openapi_first/test/observer_middleware.rb +23 -0
- data/lib/openapi_first/test.rb +1 -1
- data/lib/openapi_first/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7687c4d4ac32654b4a2fd8880eeaeb0ca98ab6fac19b8d9a336fe5d5bb94785a
|
4
|
+
data.tar.gz: 6fb04355d69916cfc3d1dce1e21d0f163082878cf1348421a9b1006b070621f3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '0988025dd93f55354b646c8aeb3fe7d0febfd54925d368fd3aa5959067c1a0a948b193158130b5fa7fb91cf51ffe774f9f8f281e76ac3feb318e67ea1b079020'
|
7
|
+
data.tar.gz: f70094cd6b531e42e06f931ed31155385bf9dd97588cad6bc977e961e7fdd497e057a971112fe10a0457d145b8f916cabdad3995c90bc29d78d31e59184211ac
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -4,31 +4,34 @@ openapi_first is a Ruby gem for request / response validation and contract-testi
|
|
4
4
|
|
5
5
|
## Usage
|
6
6
|
|
7
|
-
Use an OAD to validate incoming requests
|
7
|
+
Use an OAD to validate incoming requests:
|
8
8
|
```ruby
|
9
9
|
use OpenapiFirst::Middlewares::RequestValidation, 'openapi/openapi.yaml'
|
10
10
|
```
|
11
11
|
|
12
|
-
Turn your request tests into contract tests against an OAD:
|
12
|
+
Turn your request tests into [contract tests](#contract-testing) against an OAD:
|
13
13
|
```ruby
|
14
14
|
# spec_helper.rb
|
15
15
|
require 'openapi_first'
|
16
16
|
OpenapiFirst::Test.setup do |config|
|
17
17
|
config.register('openapi/openapi.yaml')
|
18
18
|
end
|
19
|
-
|
20
|
-
|
19
|
+
|
20
|
+
require 'my_app'
|
21
|
+
RSpec.configure do |config|
|
22
|
+
config.include OpenapiFirst::Test::Methods[MyApp], type: :request
|
23
|
+
end
|
21
24
|
```
|
22
25
|
|
23
26
|
## Contents
|
24
27
|
|
25
28
|
<!-- TOC -->
|
26
29
|
|
27
|
-
- [Contract testing](#contract-testing)
|
28
30
|
- [Rack Middlewares](#rack-middlewares)
|
29
31
|
- [Request validation](#request-validation)
|
30
32
|
- [Response validation](#response-validation)
|
31
|
-
- [
|
33
|
+
- [Contract testing](#contract-testing)
|
34
|
+
- [Test assertions](#test-assertions)
|
32
35
|
- [Manual use](#manual-use)
|
33
36
|
- [Framework integration](#framework-integration)
|
34
37
|
- [Configuration](#configuration)
|
@@ -41,78 +44,6 @@ OpenapiFirst::Test.observe(Application)
|
|
41
44
|
|
42
45
|
<!-- /TOC -->
|
43
46
|
|
44
|
-
## Contract Testing
|
45
|
-
|
46
|
-
You can see your OpenAPI API description as a contract that your clients can rely on as how your API behaves. There are two aspects of contract testing: Validation and Coverage. By validating requests and responses, you can avoid that your API implementation processes requests or returns responses that don't match your API description. To make sure your _whole_ API description is implemented, openapi_first can check that all of your API description is covered when you test your API with [rack-test](https://github.com/rack/rack-test).
|
47
|
-
|
48
|
-
Here is how to set it up:
|
49
|
-
|
50
|
-
1. Register all OpenAPI documents to track coverage for.
|
51
|
-
This should go at the top of your test helper file before loading your application code.
|
52
|
-
```ruby
|
53
|
-
require 'openapi_first'
|
54
|
-
OpenapiFirst::Test.setup do |config|
|
55
|
-
config.register('openapi/openapi.yaml')
|
56
|
-
end
|
57
|
-
```
|
58
|
-
2. Observe your application. You can do this in one of two ways:
|
59
|
-
- Add an `app` method to your tests, which wraps your application with silent request / response validation. (✷1)
|
60
|
-
```ruby
|
61
|
-
RSpec.configure do |config|
|
62
|
-
config.include OpenapiFirst::Test::Methods[MyApp], type: :request
|
63
|
-
end
|
64
|
-
```
|
65
|
-
Or add the `app` method yourself:
|
66
|
-
|
67
|
-
```ruby
|
68
|
-
def app
|
69
|
-
OpenapiFirst::Test.app(MyApp)
|
70
|
-
end
|
71
|
-
```
|
72
|
-
- Or inject a Module to wrap (prepend) the `call` method of your Rack app Class.
|
73
|
-
|
74
|
-
NOTE: This is still work in progress. It works with basic Sinatra apps, but does not work with Hanami or Rails out of the box, yet. PRs welcome 🤗
|
75
|
-
|
76
|
-
```ruby
|
77
|
-
OpenapiFirst::Test.observe(MyApplication)
|
78
|
-
```
|
79
|
-
3. Run your tests. The Coverage feature will tell you about missing or invalid requests/responses.
|
80
|
-
|
81
|
-
(✷1): It does not matter what method of openapi_first you use to validate requests/responses. Instead of using `OpenapiFirstTest.app` to wrap your application, you could also use the [middlewares](#rack-middlewares) or [test assertion method](#test-assertions), but you would have to do that for all requests/responses defined in your API description to make coverage work.
|
82
|
-
|
83
|
-
### Configure test coverage
|
84
|
-
|
85
|
-
OpenapiFirst::Test raises an error when a request is not defined. You can deactivate this with:
|
86
|
-
|
87
|
-
```ruby
|
88
|
-
OpenapiFirst::Test.setup do |test|
|
89
|
-
# …
|
90
|
-
test.ignore_unknown_requests = true
|
91
|
-
end
|
92
|
-
```
|
93
|
-
|
94
|
-
Exclude certain _responses_ from coverage with `skip_coverage`:
|
95
|
-
|
96
|
-
```ruby
|
97
|
-
OpenapiFirst::Test.setup do |test|
|
98
|
-
# …
|
99
|
-
test.skip_response_coverage do |response_definition|
|
100
|
-
response_definition.status == '5XX'
|
101
|
-
end
|
102
|
-
end
|
103
|
-
```
|
104
|
-
|
105
|
-
Skip coverage for a request and all responses alltogether of a route with `skip_coverage`:
|
106
|
-
|
107
|
-
```ruby
|
108
|
-
OpenapiFirst::Test.setup do |test|
|
109
|
-
# …
|
110
|
-
test.skip_coverage do |path, request_method|
|
111
|
-
path == '/bookings/{bookingId}' && requests_method == 'DELETE'
|
112
|
-
end
|
113
|
-
end
|
114
|
-
```
|
115
|
-
|
116
47
|
## Rack Middlewares
|
117
48
|
|
118
49
|
### Request validation
|
@@ -222,7 +153,79 @@ use OpenapiFirst::Middlewares::ResponseValidation, 'openapi.yaml', raise_error:
|
|
222
153
|
|
223
154
|
If you are adopting OpenAPI you can use these options together with [hooks](#hooks) to get notified about requests/responses that do match your API description.
|
224
155
|
|
225
|
-
##
|
156
|
+
## Contract Testing
|
157
|
+
|
158
|
+
You can see your OpenAPI API description as a contract that your clients can rely on as how your API behaves. There are two aspects of contract testing: Validation and Coverage. By validating requests and responses, you can avoid that your API implementation processes requests or returns responses that don't match your API description. To make sure your _whole_ API description is implemented, openapi_first can check that all of your API description is covered when you test your API with [rack-test](https://github.com/rack/rack-test).
|
159
|
+
|
160
|
+
Here is how to set it up:
|
161
|
+
|
162
|
+
1. Register all OpenAPI documents to track coverage for.
|
163
|
+
This should go at the top of your test helper file before loading your application code.
|
164
|
+
```ruby
|
165
|
+
require 'openapi_first'
|
166
|
+
OpenapiFirst::Test.setup do |config|
|
167
|
+
config.register('openapi/openapi.yaml')
|
168
|
+
end
|
169
|
+
```
|
170
|
+
2. Observe your application. You can do this in multiple ways:
|
171
|
+
- Add an `app` method to your tests, which wraps your application with silent request / response validation. (✷1)
|
172
|
+
```ruby
|
173
|
+
module RequestSpecHelpers
|
174
|
+
def app
|
175
|
+
OpenapiFirst::Test.app(MyApp)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
RSpec.configure do |config|
|
180
|
+
config.include RequestSpecHelpers, type: :request
|
181
|
+
end
|
182
|
+
```
|
183
|
+
|
184
|
+
Or do this by creating a Module and including it to add an "app" method.
|
185
|
+
|
186
|
+
```ruby
|
187
|
+
RSpec.configure do |config|
|
188
|
+
config.include OpenapiFirst::Test::Methods[MyApp], type: :request
|
189
|
+
end
|
190
|
+
```
|
191
|
+
4. Run your tests. The Coverage feature will tell you about missing or invalid requests/responses.
|
192
|
+
|
193
|
+
(✷1): It does not matter what method of openapi_first you use to validate requests/responses. Instead of using `OpenapiFirstTest.app` to wrap your application, you could also use the [middlewares](#rack-middlewares) or [test assertion method](#test-assertions), but you would have to do that for all requests/responses defined in your API description to make coverage work.
|
194
|
+
|
195
|
+
### Configure test coverage
|
196
|
+
|
197
|
+
OpenapiFirst::Test raises an error when a request is not defined. You can deactivate this with:
|
198
|
+
|
199
|
+
```ruby
|
200
|
+
OpenapiFirst::Test.setup do |test|
|
201
|
+
# …
|
202
|
+
test.ignore_unknown_requests = true
|
203
|
+
end
|
204
|
+
```
|
205
|
+
|
206
|
+
Exclude certain _responses_ from coverage with `skip_coverage`:
|
207
|
+
|
208
|
+
```ruby
|
209
|
+
OpenapiFirst::Test.setup do |test|
|
210
|
+
# …
|
211
|
+
test.skip_response_coverage do |response_definition|
|
212
|
+
response_definition.status == '5XX'
|
213
|
+
end
|
214
|
+
end
|
215
|
+
```
|
216
|
+
|
217
|
+
Skip coverage for a request and all responses alltogether of a route with `skip_coverage`:
|
218
|
+
|
219
|
+
```ruby
|
220
|
+
OpenapiFirst::Test.setup do |test|
|
221
|
+
# …
|
222
|
+
test.skip_coverage do |path, request_method|
|
223
|
+
path == '/bookings/{bookingId}' && requests_method == 'DELETE'
|
224
|
+
end
|
225
|
+
end
|
226
|
+
```
|
227
|
+
|
228
|
+
### Test assertions
|
226
229
|
|
227
230
|
openapi_first ships with a simple but powerful Test method to run request and response validation in your tests without using the middlewares. This is designed to be used with rack-test or Ruby on Rails integration tests or request specs.
|
228
231
|
|
@@ -23,11 +23,14 @@ module OpenapiFirst
|
|
23
23
|
|
24
24
|
def read_body(rack_response)
|
25
25
|
buffered_body = +''
|
26
|
+
|
26
27
|
if rack_response.body.respond_to?(:each)
|
27
28
|
rack_response.body.each { |chunk| buffered_body.to_s << chunk }
|
28
29
|
return buffered_body
|
29
30
|
end
|
30
31
|
rack_response.body
|
32
|
+
rescue TypeError
|
33
|
+
raise Error, "Cannot not read response body. Response is not string-like, but is a #{rack_response.body.class}."
|
31
34
|
end
|
32
35
|
|
33
36
|
def build_headers_parser(headers)
|
@@ -10,14 +10,23 @@ module OpenapiFirst
|
|
10
10
|
# Inject silent request/response validation to observe rack apps during testing
|
11
11
|
module Observe
|
12
12
|
def self.observe(app, api: :default)
|
13
|
+
definition = OpenapiFirst::Test[api]
|
14
|
+
mod = OpenapiFirst::Test::Callable[definition]
|
15
|
+
|
16
|
+
if app.respond_to?(:call)
|
17
|
+
return if app.singleton_class.include?(Observed)
|
18
|
+
|
19
|
+
app.singleton_class.prepend(mod)
|
20
|
+
app.singleton_class.include(Observed)
|
21
|
+
return
|
22
|
+
end
|
23
|
+
|
13
24
|
unless app.instance_methods.include?(:call)
|
14
25
|
raise ObserveError, "Don't know how to observe #{app}, because it has no call instance method."
|
15
26
|
end
|
16
27
|
|
17
28
|
return if app.include?(Observed)
|
18
29
|
|
19
|
-
definition = OpenapiFirst::Test[api]
|
20
|
-
mod = OpenapiFirst::Test::Callable[definition]
|
21
30
|
app.prepend(mod)
|
22
31
|
app.include(Observed)
|
23
32
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OpenapiFirst
|
4
|
+
module Test
|
5
|
+
# Middleware that observes requests and responses. This is used to trigger hooks added by OpenapiFirst::Tests.
|
6
|
+
class ObserverMiddleware
|
7
|
+
def initialize(app, options = {})
|
8
|
+
@app = app
|
9
|
+
@definition = OpenapiFirst::Test[options.fetch(:api, :default)]
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(env)
|
13
|
+
request = Rack::Request.new(env)
|
14
|
+
|
15
|
+
@definition.validate_request(request, raise_error: false)
|
16
|
+
response = @app.call(env)
|
17
|
+
status, headers, body = response
|
18
|
+
@definition.validate_response(request, Rack::Response[status, headers, body], raise_error: false)
|
19
|
+
response
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/openapi_first/test.rb
CHANGED
@@ -40,7 +40,7 @@ module OpenapiFirst
|
|
40
40
|
yield configuration
|
41
41
|
|
42
42
|
configuration.registry.each { |name, oad| register(oad, as: name) }
|
43
|
-
configuration.apps.each { |name, app| observe(app, api: name) }
|
43
|
+
configuration.apps.each { |name, apps| apps.each { |app| observe(app, api: name) } }
|
44
44
|
Coverage.start(skip_response: configuration.skip_response_coverage, skip_route: configuration.skip_coverage)
|
45
45
|
|
46
46
|
if definitions.empty?
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: openapi_first
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.11.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andreas Haller
|
@@ -134,6 +134,7 @@ files:
|
|
134
134
|
- lib/openapi_first/test/methods.rb
|
135
135
|
- lib/openapi_first/test/minitest_helpers.rb
|
136
136
|
- lib/openapi_first/test/observe.rb
|
137
|
+
- lib/openapi_first/test/observer_middleware.rb
|
137
138
|
- lib/openapi_first/test/plain_helpers.rb
|
138
139
|
- lib/openapi_first/test/registry.rb
|
139
140
|
- lib/openapi_first/validated_request.rb
|