openapi_first 2.7.4 → 2.8.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2d3e13bc24feccc8015701ca7360e77fbb8c44d8c7dad9e87310f52b04ddebe6
4
- data.tar.gz: 8a5895ce7b3a5a7a2669e862f4821125538573a6e694db36e8d2687580c3e0c9
3
+ metadata.gz: d5a5311b4ca774b9a0cb80e85f29243f46e1c5c762241b015090834971373a7b
4
+ data.tar.gz: 640be1c8c02b8a86b5c9d562b7ac837b73881473c54cf9ea731779352e0cee2d
5
5
  SHA512:
6
- metadata.gz: 3420becb820a470942b12e39d7e59197f9a50696a0f63c2af601ce26cad4e95c6b6e9d915e5b3f4528da78bf1f012993af9401d1863578e8e64c2059896c71bb
7
- data.tar.gz: fe61cf2f437b9a725b377b7dcc5917ac130639ca67514450bee3ba919f289f083fa232b9a94770d175232da2546b5b999c5e544abe20d0dba060adc6d4eb6f07
6
+ metadata.gz: 42ab4483c694d921c68fcb30099ecd779e789c5bfc9163f97a70327f5ef1aacdc7b7abae66c70341e2edd4ff5821d4fe6e9ab3e0d65b081963685272d094e5e2
7
+ data.tar.gz: 7bbc02b4e061a012663b115758b1f43a6fd4642687d07711d90e7032a800fedb453e3b32a762658bd5185d9cea0f8859b2436d28f4bea12f0bebd4bb613aeeff
data/CHANGELOG.md CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 2.8.0
6
+
7
+ ### OpenapiFirst::Test is now stricter and more configurable
8
+
9
+ Changes:
10
+ - Changed OpenapiFirst::Test to raises an "invalid response" error if it sees an invalid response (https://github.com/ahx/openapi_first/issues/366).
11
+ You can change this back to the old behavior by setting `OpenapiFirst::Test::Configuration#response_raise_error = false` (but you shouldn't).
12
+ - Added `Test.setup { it.observe(MyApp) }`, `Test.observe(App, api: :my_api)` and internal `Test::Callable[]` to inject request/response validation in rack app as an alternative to overwrite the `app` method in a test
13
+ - Added `Test::Configuration#ignored_unknown_status` to configure response status(es) that do not have to be descriped in the API description. 404 statuses are ignored by default.
14
+ - Changed `OpenapiFirst::Test` to make tests fail if API description is not covered by tests. You can adapt this behavior via `OpenapiFirst::Test.setup` / `skip_response_coverage` or deactivate coverage with `OpenapiFirst::Test::Configuration#report_coverage = false` or `report_coverage = :warn`
15
+
5
16
  ## 2.7.4
6
17
 
7
18
  - Return 400 if Rack cannot parse query string instead of raising an exception. Fixes https://github.com/ahx/openapi_first/issues/372
data/README.md CHANGED
@@ -1,8 +1,24 @@
1
1
  # openapi_first
2
2
 
3
- openapi_first is a Ruby gem for request / response validation and contract-testing against an [OpenAPI](https://www.openapis.org/) 3.0 or 3.1 API description. It makes an APIFirst workflow easy and reliable.
3
+ openapi_first is a Ruby gem for request / response validation and contract-testing against an [OpenAPI](https://www.openapis.org/) 3.0 or 3.1 Openapi API description (OAD). It makes an APIFirst workflow easy and reliable.
4
4
 
5
- You can use openapi_first on production for [request validation](#request-validation) and in your [tests](#contract-testing) to avoid API drift with it's request/response validation and coverage features.
5
+ ## Usage
6
+
7
+ Use an OAD to validate incoming requests in production:
8
+ ```ruby
9
+ use OpenapiFirst::Middlewares::RequestValidation, 'openapi/openapi.yaml'
10
+ ```
11
+
12
+ Turn your request tests into contract tests against an OAD:
13
+ ```ruby
14
+ # spec_helper.rb
15
+ require 'openapi_first'
16
+ OpenapiFirst::Test.setup do |config|
17
+ config.register('openapi/openapi.yaml')
18
+ end
19
+ require 'application' # Load Application code
20
+ OpenapiFirst::Test.observe(Application)
21
+ ```
6
22
 
7
23
  ## Contents
8
24
 
@@ -35,27 +51,28 @@ Here is how to set it up:
35
51
  This should go at the top of your test helper file before loading your application code.
36
52
  ```ruby
37
53
  require 'openapi_first'
38
- OpenapiFirst::Test.setup do |test|
39
- test.register('openapi/openapi.yaml')
40
- # Optional: Make tests fail if coverage is below minimum
41
- test.minimum_coverage = 100
42
- # Optional: Skip certain responses, which are described in your API description, but need no test coverage
43
- test.skip_response_coverage { it.status == '500' } #
44
- end
45
- ```
46
- 2. Add an `app` method to your tests by including a Module. This `app` method wraps your application with silent request / response validation. This validates all requests/responses in your test run. (✷1)
47
- ```ruby
48
- RSpec.configure do |config|
49
- config.include OpenapiFirst::Test::Methods[MyApp], type: :request
50
- end
51
- ```
52
- Or add the `app` method yourself:
53
-
54
- ```ruby
55
- def app
56
- OpenapiFirst::Test.app(MyApp)
54
+ OpenapiFirst::Test.setup do |config|
55
+ config.register('openapi/openapi.yaml')
57
56
  end
58
57
  ```
58
+ 2. Observe your application. You can do this in one of two ways:
59
+ - Inject a Module to wrap (prepend) the `call` method of your Rack app Class.
60
+ ```ruby
61
+ OpenapiFirst::Test.observe(MyApplication)
62
+ ```
63
+ - Or add an `app` method to your tests, which wraps your application with silent request / response validation. (✷1)
64
+ ```ruby
65
+ RSpec.configure do |config|
66
+ config.include OpenapiFirst::Test::Methods[MyApp], type: :request
67
+ end
68
+ ```
69
+ Or add the `app` method yourself:
70
+
71
+ ```ruby
72
+ def app
73
+ OpenapiFirst::Test.app(MyApp)
74
+ end
75
+ ```
59
76
  3. Run your tests. The Coverage feature will tell you about missing or invalid requests/responses.
60
77
 
61
78
  (✷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.
@@ -30,6 +30,7 @@ module OpenapiFirst
30
30
  HOOKS.each do |hook|
31
31
  define_method(hook) do |&block|
32
32
  hooks[hook] << block
33
+ block
33
34
  end
34
35
  end
35
36
 
@@ -86,14 +86,15 @@ module OpenapiFirst
86
86
 
87
87
  response_match = route.match_response(status: rack_response.status, content_type: rack_response.content_type)
88
88
  error = response_match.error
89
- if error
90
- ValidatedResponse.new(rack_response, error:)
91
- else
92
- response_match.response.validate(rack_response)
93
- end.tap do |validated|
94
- @config.hooks[:after_response_validation]&.each { |hook| hook.call(validated, rack_request, self) }
95
- raise validated.error.exception(validated) if raise_error && validated.invalid?
96
- end
89
+ validated = if error
90
+ ValidatedResponse.new(rack_response, error:)
91
+ else
92
+ response_match.response.validate(rack_response)
93
+ end
94
+ @config.hooks[:after_response_validation]&.each { |hook| hook.call(validated, rack_request, self) }
95
+ raise validated.error.exception(validated) if raise_error && validated.invalid?
96
+
97
+ validated
97
98
  end
98
99
 
99
100
  private
@@ -23,7 +23,9 @@ module OpenapiFirst
23
23
  register(/json/i, lambda do |body|
24
24
  JSON.parse(body)
25
25
  rescue JSON::ParserError
26
- Failure.fail!(:invalid_response_body, message: 'Response body is invalid: Failed to parse response body as JSON')
26
+ return Failure.fail!(:invalid_response_body, message: 'JSON response body must not be empty') if body.empty?
27
+
28
+ Failure.fail!(:invalid_response_body, message: 'Failed to parse response body as JSON')
27
29
  end)
28
30
  end
29
31
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenapiFirst
4
+ module Test
5
+ # Return a Module with a call method that wrapps silent request/response validation to monitor a Rack app
6
+ # This is used by Openapi::Test.observe
7
+ module Callable
8
+ # Returns a Module with a `call(env)` method that wraps super inside silent request/response validation
9
+ # You can use this like `Application.prepend(OpenapiFirst::Test.app_module)` to monitor your app during testing.
10
+ def self.[](definition)
11
+ Module.new.tap do |mod|
12
+ mod.define_method(:call) do |env|
13
+ request = Rack::Request.new(env)
14
+
15
+ definition.validate_request(request, raise_error: false)
16
+ response = super(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
24
+ end
25
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenapiFirst
4
+ module Test
5
+ # Helper class to setup tests
6
+ class Configuration
7
+ def initialize
8
+ @minimum_coverage = 100
9
+ @coverage_formatter = Coverage::TerminalFormatter
10
+ @coverage_formatter_options = {}
11
+ @skip_response_coverage = nil
12
+ @response_raise_error = true
13
+ @ignored_unknown_status = [404]
14
+ @report_coverage = true
15
+ @registry = {}
16
+ @apps = {}
17
+ end
18
+
19
+ # Register OADs, but don't load them just yet
20
+ # @param [OpenapiFirst::OAD] oad The OAD to register
21
+ # @param [Symbol] as The name to register the OAD under
22
+ def register(oad, as: :default)
23
+ @registry[as] = oad
24
+ end
25
+
26
+ # Observe a rack app
27
+ def observe(app, api: :default)
28
+ @apps[api] = app
29
+ end
30
+
31
+ attr_accessor :coverage_formatter_options, :coverage_formatter, :response_raise_error, :minimum_coverage
32
+ attr_reader :registry, :apps, :report_coverage, :ignored_unknown_status
33
+
34
+ # Configure report coverage
35
+ # @param [Boolean, :warn] value Whether to report coverage or just warn.
36
+ def report_coverage=(value)
37
+ allowed_values = [true, false, :warn]
38
+ unless allowed_values.include?(value)
39
+ raise ArgumentError, "'report_coverage' must be one of #{allowed_values}, but was #{value.inspect}"
40
+ end
41
+
42
+ @report_coverage = value
43
+ end
44
+
45
+ def skip_response_coverage(&block)
46
+ return @skip_response_coverage unless block_given?
47
+
48
+ @skip_response_coverage = block
49
+ end
50
+ end
51
+ end
52
+ end
@@ -10,10 +10,14 @@ module OpenapiFirst
10
10
  @focused = focused && !verbose
11
11
  end
12
12
 
13
- # This takes a list of Coverage::Plan instances and outputs a String
14
13
  def format(coverage_result)
14
+ coverage = coverage_result.coverage
15
15
  @out = StringIO.new
16
- coverage_result.plans.each { |plan| format_plan(plan) }
16
+ if coverage.zero?
17
+ @out.puts 'API Coverage did not detect any API requests for the registered API descriptions. ' \
18
+ 'Make sure to observe your application using OpenapiFirst::Test.'
19
+ end
20
+ coverage_result.plans.each { |plan| format_plan(plan) } if coverage.positive?
17
21
  @out.string
18
22
  end
19
23
 
@@ -85,7 +89,9 @@ module OpenapiFirst
85
89
  def explain_unfinished_request(request)
86
90
  return 'No requests tracked!' unless request.requested?
87
91
 
88
- "All requests invalid! (#{request.last_error_message.inspect})" unless request.any_valid_request?
92
+ return if request.any_valid_request?
93
+
94
+ "All requests invalid! (#{request.last_error_message.inspect})"
89
95
  end
90
96
 
91
97
  def response_label(response)
@@ -12,26 +12,12 @@ module OpenapiFirst
12
12
 
13
13
  Result = Data.define(:plans, :coverage)
14
14
 
15
+ @current_run = {}
16
+
15
17
  class << self
16
18
  attr_reader :current_run
17
19
 
18
- def install
19
- return if @installed
20
-
21
- @after_request_validation = lambda do |validated_request, oad|
22
- track_request(validated_request, oad)
23
- end
24
-
25
- @after_response_validation = lambda do |validated_response, request, oad|
26
- track_response(validated_response, request, oad)
27
- end
28
-
29
- OpenapiFirst.configure do |config|
30
- config.after_request_validation(&@after_request_validation)
31
- config.after_response_validation(&@after_response_validation)
32
- end
33
- @installed = true
34
- end
20
+ def install = Test.install
35
21
 
36
22
  def start(skip_response: nil)
37
23
  @current_run = Test.definitions.values.to_h do |oad|
@@ -40,16 +26,11 @@ module OpenapiFirst
40
26
  end
41
27
  end
42
28
 
43
- def uninstall
44
- configuration = OpenapiFirst.configuration
45
- configuration.hooks[:after_request_validation].delete(@after_request_validation)
46
- configuration.hooks[:after_response_validation].delete(@after_response_validation)
47
- @installed = nil
48
- end
29
+ def uninstall = Test.uninstall
49
30
 
50
31
  # Clear current coverage run
51
32
  def reset
52
- @current_run = nil
33
+ @current_run = {}
53
34
  end
54
35
 
55
36
  def track_request(request, oad)
@@ -66,13 +47,13 @@ module OpenapiFirst
66
47
 
67
48
  # Returns all plans (Plan) that were registered for this run
68
49
  def plans
69
- current_run&.values
50
+ current_run.values
70
51
  end
71
52
 
72
53
  private
73
54
 
74
55
  def coverage
75
- return 0 unless plans
56
+ return 0 if plans.empty?
76
57
 
77
58
  plans.sum(&:coverage) / plans.length
78
59
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenapiFirst
4
+ module Test
5
+ class ObserveError < Error; end
6
+
7
+ # @visible private
8
+ module Observed; end
9
+
10
+ # Inject silent request/response validation to observe rack apps during testing
11
+ module Observe
12
+ def self.observe(app, api: :default)
13
+ unless app.instance_methods.include?(:call)
14
+ raise ObserveError, "Don't know how to observe #{app}, because it has no call instance method."
15
+ end
16
+
17
+ return if app.include?(Observed)
18
+
19
+ definition = OpenapiFirst::Test[api]
20
+ mod = OpenapiFirst::Test::Callable[definition]
21
+ app.prepend(mod)
22
+ app.include(Observed)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenapiFirst
4
+ module Test
5
+ class NotRegisteredError < Error; end
6
+ class AlreadyRegisteredError < Error; end
7
+
8
+ # @visibility private
9
+ module Registry
10
+ def definitions
11
+ @definitions ||= {}
12
+ end
13
+
14
+ # Register an OpenAPI definition for testing
15
+ # @param path_or_definition [String, Definition] Path to the OpenAPI file or a Definition object
16
+ # @param as [Symbol] Name to register the API definition as
17
+ def register(path_or_definition, as: :default)
18
+ if definitions.key?(as) && as == :default
19
+ raise(
20
+ AlreadyRegisteredError,
21
+ "#{definitions[as].filepath.inspect} is already registered " \
22
+ "as ':default' so you cannot register #{path_or_definition.inspect} without " \
23
+ 'giving it a custom name. Please call register with a custom key like: ' \
24
+ "#{name}.register(#{path_or_definition.inspect}, as: :my_other_api)"
25
+ )
26
+ end
27
+
28
+ definition = OpenapiFirst.load(path_or_definition)
29
+ definitions[as] = definition
30
+ definition
31
+ end
32
+
33
+ def [](api)
34
+ definitions.fetch(api) do
35
+ option = api == :default ? '' : ", as: #{api.inspect}"
36
+ raise(NotRegisteredError,
37
+ "API description '#{api.inspect}' not found." \
38
+ "Please call #{name}.register('myopenapi.yaml'#{option}) " \
39
+ 'once before running tests.')
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -1,10 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'test/configuration'
4
+ require_relative 'test/registry'
5
+
3
6
  module OpenapiFirst
4
7
  # Test integration
5
8
  module Test
6
9
  autoload :Coverage, 'openapi_first/test/coverage'
7
10
  autoload :Methods, 'openapi_first/test/methods'
11
+ autoload :Callable, 'openapi_first/test/callable'
12
+ autoload :Observe, 'openapi_first/test/observe'
13
+ extend Registry
14
+
15
+ class CoverageError < Error; end
16
+
17
+ # Inject request/response validation in a rack app class
18
+ def self.observe(app, api: :default)
19
+ Observe.observe(app, api:)
20
+ end
8
21
 
9
22
  def self.minitest?(base)
10
23
  base.include?(::Minitest::Assertions)
@@ -12,58 +25,23 @@ module OpenapiFirst
12
25
  false
13
26
  end
14
27
 
15
- # Helper class to setup tests
16
- class Setup
17
- def initialize
18
- @minimum_coverage = 0
19
- @coverage_formatter = Coverage::TerminalFormatter
20
- @coverage_formatter_options = {}
21
- @skip_response_coverage = nil
22
- yield self
23
- end
24
-
25
- def register(oad, as: :default)
26
- Test.register(oad, as:)
27
- end
28
-
29
- attr_accessor :minimum_coverage, :coverage_formatter_options, :coverage_formatter
30
-
31
- def skip_response_coverage(&block)
32
- return @skip_response_coverage unless block_given?
33
-
34
- @skip_response_coverage = block
35
- end
36
-
37
- # This called at_exit
38
- def handle_exit
39
- coverage = Coverage.result.coverage
40
- # :nocov:
41
- puts 'API Coverage did not detect any API requests for the registered API descriptions' if coverage.zero?
42
- if coverage.positive?
43
- Test.report_coverage(
44
- formatter: coverage_formatter,
45
- **coverage_formatter_options
46
- )
47
- end
48
- return unless minimum_coverage > coverage
49
-
50
- puts "API Coverage fails with exit 2, because API coverage of #{coverage}% " \
51
- "is below minimum of #{minimum_coverage}%!"
52
- exit 2
53
- # :nocov:
54
- end
28
+ def self.configuration
29
+ @configuration ||= Configuration.new
55
30
  end
56
31
 
57
32
  # Sets up OpenAPI test coverage and OAD registration.
58
- # @yieldparam [OpenapiFirst::Test::Setup] setup A setup for configuration
59
- def self.setup(&)
33
+ # @yieldparam [OpenapiFirst::Test::Configuration] configuration A configuration to setup test integration
34
+ def self.setup
60
35
  unless block_given?
61
- raise ArgumentError, "Please provide a block to #{self.class}.setup to register you API descriptions"
36
+ raise ArgumentError, "Please provide a block to #{self.class}.confgure to register you API descriptions"
62
37
  end
63
38
 
64
- Coverage.install
65
- setup = Setup.new(&)
66
- Coverage.start(skip_response: setup.skip_response_coverage)
39
+ install
40
+ yield configuration
41
+
42
+ configuration.registry.each { |name, oad| register(oad, as: name) }
43
+ configuration.apps.each { |name, app| observe(app, api: name) }
44
+ Coverage.start(skip_response: configuration.skip_response_coverage)
67
45
 
68
46
  if definitions.empty?
69
47
  raise NotRegisteredError,
@@ -72,17 +50,37 @@ module OpenapiFirst
72
50
  "OpenapiFirst::Test.setup { |test| test.register('myopenapi.yaml') }"
73
51
  end
74
52
 
53
+ @exit_handler = method(:handle_exit)
54
+
75
55
  @setup ||= at_exit do
76
- setup.handle_exit
56
+ # :nocov:
57
+ @exit_handler&.call
58
+ # :nocov:
77
59
  end
78
60
  end
79
61
 
62
+ def self.handle_exit
63
+ return unless configuration.report_coverage
64
+
65
+ report_coverage(
66
+ formatter: configuration.coverage_formatter,
67
+ **configuration.coverage_formatter_options
68
+ )
69
+ return unless configuration.report_coverage == true
70
+
71
+ coverage = Coverage.result.coverage
72
+ return if coverage >= configuration.minimum_coverage
73
+
74
+ puts "API Coverage fails with exit 2, because not all described requests and responses have been tested (#{coverage.round(4)}% covered)." # rubocop:disable Layout/LineLength
75
+
76
+ exit 2
77
+ end
78
+
80
79
  # Print the coverage report
81
80
  # @param formatter A formatter to define the report.
82
81
  # @output [IO] An output where to puts the report.
83
82
  def self.report_coverage(formatter: Coverage::TerminalFormatter, **)
84
- coverage_result = Coverage.result
85
- puts formatter.new(**).format(coverage_result)
83
+ puts formatter.new(**).format(Coverage.result)
86
84
  end
87
85
 
88
86
  # Returns the Rack app wrapped with silent request, response validation
@@ -97,42 +95,37 @@ module OpenapiFirst
97
95
  end
98
96
  end
99
97
 
100
- class NotRegisteredError < StandardError; end
101
- class AlreadyRegisteredError < StandardError; end
102
-
103
- @definitions = {}
104
-
105
- class << self
106
- attr_reader :definitions
107
-
108
- # Register an OpenAPI definition for testing
109
- # @param path_or_definition [String, Definition] Path to the OpenAPI file or a Definition object
110
- # @param as [Symbol] Name to register the API definition as
111
- def register(path_or_definition, as: :default)
112
- if definitions.key?(as) && as == :default
113
- raise(
114
- AlreadyRegisteredError,
115
- "#{definitions[as].filepath.inspect} is already registered " \
116
- "as ':default' so you cannot register #{path_or_definition.inspect} without " \
117
- 'giving it a custom name. Please call register with a custom key like: ' \
118
- "OpenapiFirst::Test.register(#{path_or_definition.inspect}, as: :my_other_api)"
119
- )
98
+ def self.install
99
+ return if @installed
100
+
101
+ OpenapiFirst.configure do |config|
102
+ @after_request_validation = config.after_request_validation do |validated_request, oad|
103
+ Coverage.track_request(validated_request, oad)
120
104
  end
121
105
 
122
- definition = OpenapiFirst.load(path_or_definition)
123
- definitions[as] = definition
124
- definition
125
- end
106
+ @after_response_validation = config.after_response_validation do |validated_response, rack_request, oad|
107
+ if validated_response.invalid? && raise_response_error?(validated_response)
108
+ raise validated_response.error.exception
109
+ end
126
110
 
127
- def [](api)
128
- definitions.fetch(api) do
129
- option = api == :default ? '' : ", as: #{api.inspect}"
130
- raise(NotRegisteredError,
131
- "API description '#{api.inspect}' not found." \
132
- "Please call OpenapiFirst::Test.register('myopenapi.yaml'#{option}) " \
133
- 'once before running tests.')
111
+ Coverage.track_response(validated_response, rack_request, oad)
134
112
  end
135
113
  end
114
+ @installed = true
115
+ end
116
+
117
+ def self.raise_response_error?(validated_response)
118
+ configuration.response_raise_error && !configuration.ignored_unknown_status.include?(validated_response.status)
119
+ end
120
+
121
+ def self.uninstall
122
+ configuration = OpenapiFirst.configuration
123
+ configuration.hooks[:after_request_validation].delete(@after_request_validation)
124
+ configuration.hooks[:after_response_validation].delete(@after_response_validation)
125
+ definitions.clear
126
+ @configuration = nil
127
+ @installed = nil
128
+ @exit_handler = nil
136
129
  end
137
130
  end
138
131
  end
@@ -11,7 +11,7 @@ module OpenapiFirst
11
11
  def call(parsed_request)
12
12
  body = parsed_request.body
13
13
  if body.nil?
14
- Failure.fail!(:invalid_body, message: 'Request body is not defined') if @required
14
+ Failure.fail!(:invalid_body, message: 'Request body must not be empty') if @required
15
15
  return
16
16
  end
17
17
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OpenapiFirst
4
- VERSION = '2.7.4'
4
+ VERSION = '2.8.0'
5
5
  end
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.7.4
4
+ version: 2.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andreas Haller
@@ -123,6 +123,8 @@ files:
123
123
  - lib/openapi_first/schema/validation_error.rb
124
124
  - lib/openapi_first/schema/validation_result.rb
125
125
  - lib/openapi_first/test.rb
126
+ - lib/openapi_first/test/callable.rb
127
+ - lib/openapi_first/test/configuration.rb
126
128
  - lib/openapi_first/test/coverage.rb
127
129
  - lib/openapi_first/test/coverage/plan.rb
128
130
  - lib/openapi_first/test/coverage/request_task.rb
@@ -131,7 +133,9 @@ files:
131
133
  - lib/openapi_first/test/coverage/terminal_formatter.rb
132
134
  - lib/openapi_first/test/methods.rb
133
135
  - lib/openapi_first/test/minitest_helpers.rb
136
+ - lib/openapi_first/test/observe.rb
134
137
  - lib/openapi_first/test/plain_helpers.rb
138
+ - lib/openapi_first/test/registry.rb
135
139
  - lib/openapi_first/validated_request.rb
136
140
  - lib/openapi_first/validated_response.rb
137
141
  - lib/openapi_first/validators/request_body.rb