praxis 2.0.pre.19 → 2.0.pre.20

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: 58ae0c1f92272e7fd0aaaa779fb679db4b14f6e8d2f9df08efaec211bd3cc4f0
4
- data.tar.gz: 19b73ba6239911b4b0f6d3097e58671f556f7835514b7fa52269c1a81f47d837
3
+ metadata.gz: 0ade185970597f7537bf75a761bb1e1a01739f2e696ad006f8421037320051c3
4
+ data.tar.gz: fd9e948ba2f7d48c0abc94f70bec9cad3f594b41d82f9ce9445df28be010fe14
5
5
  SHA512:
6
- metadata.gz: f488d80d4e1495b3574bce59a20826c418ac663d6417838a24f7ef92770e73dd789fe3b9d432a5663b4e6cd5abe665be5d4fc96919c20d93dac0cd981732b4ec
7
- data.tar.gz: b8ffc24d28567d4b00a83c8b03bfd00539791af3a675ec315858a800d9e7890bf5bbcb1b811cd91ef8a13c9edc10be96eefb605e34139c789b5ec174b6975342
6
+ metadata.gz: 66e64aafead42894ca61670c3a1ec3411d274d8d6cd80111d28088efd33f4c9641ed88d031cb308d0f0a7896264d877d4aa38a33a7f4944abc56aeb86dac2b81
7
+ data.tar.gz: e98a655a44a2e0d43b384e14d03ede406ace7ac79a41805e5b31178a07c902ac118e70e8ea8ef26d3cbfb70eca7404e53dd5a636f98b543c91dd90b1385926f0
data/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  ## next
4
4
 
5
+ ## 2.0.pre.20
6
+ * Changed the behavior of dev-mode when validate_responses. Now they return a 500 status code (instead of a 400) but with the same validation error format body.
7
+ * validate_responses is meant to catch the application returning non-compliant responses for development only. As such, a 500 is much more appropriate and clear, as the validation is done on the behavior of the server, and not on the information sent by the client (i.e., it is a server problem, not reacting the way the API is defined)
8
+ * Introduced a method to reload a Resouce (.reload), which will clear the memoized values and call record.reload as well
9
+ * Open API Generation enhancements:
10
+ * Fixed type discovery (where some types wouldn't be included in the output)
11
+ * Changed the generation to output named types into components, and use `$ref` to point to them whenever appropriate
12
+ * Report nullable attributes
5
13
  ## 2.0.pre.19
6
14
  * Introduced a new DSL for the `FilteringParams` type that allows filters for common attributes in your Media Types:
7
15
  * The new `any` DSL allows you to define which final leaf attribute to always allow, and with which operators and/or fuzzy restrictions.
@@ -38,6 +38,7 @@ module Praxis
38
38
  end
39
39
  end
40
40
  include Attributor::Type
41
+ include Attributor::Container
41
42
  include Attributor::Dumpable
42
43
 
43
44
  extend Finalizable
@@ -37,7 +37,7 @@ module Praxis
37
37
  the_type = @attribute&.type || member_type
38
38
  {
39
39
  type: json_schema_type,
40
- items: { '$ref': "#/components/schemas/#{the_type.id}" }
40
+ items: the_type.as_json_schema
41
41
  }
42
42
  end
43
43
  end
@@ -27,9 +27,9 @@ module Praxis
27
27
  return {} if type.is_a? SimpleMediaType # NOTE: skip if it's a SimpleMediaType?? ... is that correct?
28
28
 
29
29
  the_schema = if type.anonymous? || !(type < Praxis::MediaType) # Avoid referencing custom/simple Types? (i.e., just MTs)
30
- SchemaObject.new(info: type).dump_schema
30
+ SchemaObject.new(info: type).dump_schema(shallow: false, allow_ref: false)
31
31
  else
32
- { '$ref': "#/components/schemas/#{type.id}" }
32
+ SchemaObject.new(info: type).dump_schema(shallow: true, allow_ref: true)
33
33
  end
34
34
 
35
35
  if example_payload
@@ -23,7 +23,6 @@ module Praxis
23
23
  all_tags = tags + action.traits
24
24
  h = {
25
25
  summary: action.name.to_s,
26
- description: action.description,
27
26
  # externalDocs: {}, # TODO/FIXME
28
27
  operationId: id,
29
28
  responses: ResponsesObject.new(responses: action.responses).dump
@@ -32,6 +31,7 @@ module Praxis
32
31
  # security: [{}]
33
32
  # servers: [{}]
34
33
  }
34
+ h[:description] = action.description if action.description
35
35
  h[:tags] = all_tags.uniq unless all_tags.empty?
36
36
  h[:parameters] = all_parameters unless all_parameters.empty?
37
37
  h[:requestBody] = RequestBodyObject.new(attribute: action.payload).dump if action.payload
@@ -5,33 +5,65 @@ module Praxis
5
5
  module OpenApi
6
6
  class SchemaObject
7
7
  # https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#schema-object
8
- attr_reader :type, :attribute
8
+ attr_reader :type
9
9
 
10
10
  def initialize(info:)
11
- # info could be an attribute ... or a type?
12
- if info.is_a? Attributor::Attribute
13
- @attribute = info
11
+ @attribute_options = {}
12
+
13
+ # info could be an attribute ... or a type
14
+ if info.is_a?(Attributor::Attribute)
15
+ @type = info.type
16
+ # Save the options that might be attached to the attribute, to add them to the type schema later
17
+ @attribute_options = info.options
14
18
  else
15
19
  @type = info
16
20
  end
21
+
22
+ # Mediatypes have the description method, lower types don't
23
+ @attribute_options[:description] = @type.description if @type.respond_to?(:description)
24
+ @collection = type.respond_to?(:member_type)
17
25
  end
18
26
 
19
27
  def dump_example
20
- ex = \
21
- if attribute
22
- attribute.example
23
- else
24
- type.example
25
- end
28
+ ex = type.example
26
29
  ex.respond_to?(:dump) ? ex.dump : ex
27
30
  end
28
31
 
29
- def dump_schema
30
- if attribute
31
- attribute.as_json_schema(shallow: true, example: nil)
32
+ def dump_schema(shallow: false, allow_ref: false)
33
+ # We will dump schemas for mediatypes by simply creating a reference to the components' section
34
+ if type < Attributor::Container
35
+ if (type < Praxis::Blueprint || type < Attributor::Model) && allow_ref && !type.anonymous?
36
+ # TODO: Do we even need a description?
37
+ h = @attribute_options[:description] ? { 'description' => @attribute_options[:description] } : {}
38
+
39
+ Praxis::Docs::OpenApiGenerator.instance.register_seen_component(type)
40
+ h.merge('$ref' => "#/components/schemas/#{type.id}")
41
+ elsif @collection
42
+ items = OpenApi::SchemaObject.new(info: type.member_type).dump_schema(allow_ref: allow_ref, shallow: false)
43
+ h = @attribute_options[:description] ? { 'description' => @attribute_options[:description] } : {}
44
+ h.merge(type: 'array', items: items)
45
+ else
46
+ # Object
47
+ props = type.attributes.transform_values do |definition|
48
+ OpenApi::SchemaObject.new(info: definition).dump_schema(allow_ref: true, shallow: shallow)
49
+ end
50
+ { type: :object, properties: props } # TODO: Example?
51
+ end
32
52
  else
33
- type.as_json_schema(shallow: true, example: nil)
53
+ # OpenApi::SchemaObject.new(info:target).dump_schema(allow_ref: allow_ref, shallow: shallow)
54
+ # TODO...we need to make sure we can use refs in the underlying components after the first level...
55
+ # ... maybe we need to loop over the attributes if it's an object/struct?...
56
+ h = type.as_json_schema(shallow: shallow, example: nil, attribute_options: @attribute_options)
57
+
58
+ # Tag on OpenAPI specific requirements that aren't already added in the underlying JSON schema model
59
+ # Nullable: (it seems we need to ensure there is a null option to the enum, if there is one)
60
+ if @attribute_options[:null]
61
+ h[:nullable] = @attribute_options[:null]
62
+ h[:enum] = h[:enum] + [nil] if h[:enum] && !h[:enum].include?(nil)
63
+ end
64
+ h
34
65
  end
66
+
35
67
  # # TODO: FIXME: return a generic object type if the passed info was weird.
36
68
  # return { type: :object } unless info
37
69
 
@@ -12,11 +12,11 @@ module Praxis
12
12
  end
13
13
 
14
14
  def dump
15
- {
16
- name: name,
17
- description: description
15
+ h = description ? { description: description } : {}
16
+ h.merge(
17
+ name: name
18
18
  # externalDocs: ???,
19
- }
19
+ )
20
20
  end
21
21
  end
22
22
  end
@@ -9,6 +9,7 @@ module Praxis
9
9
  module Docs
10
10
  class OpenApiGenerator
11
11
  require 'active_support/core_ext/enumerable' # For index_by
12
+ include Singleton
12
13
 
13
14
  API_DOCS_DIRNAME = 'docs/openapi'
14
15
  EXCLUDED_TYPES_FROM_OUTPUT = Set.new([
@@ -26,7 +27,7 @@ module Praxis
26
27
  Attributor::URI
27
28
  ]).freeze
28
29
 
29
- attr_reader :resources_by_version, :types_by_id, :infos_by_version, :doc_root_dir
30
+ attr_reader :resources_by_version, :infos_by_version, :doc_root_dir
30
31
 
31
32
  # substitutes ":params_like_so" for {params_like_so}
32
33
  def self.templatize_url(string)
@@ -34,24 +35,37 @@ module Praxis
34
35
  end
35
36
 
36
37
  def save!
38
+ raise 'You need to configure the root directory before saving (configure_root(<dir>))' unless @root
39
+
37
40
  initialize_directories
38
41
  # Restrict the versions listed in the index file to the ones for which we have at least 1 resource
39
42
  write_index_file(for_versions: resources_by_version.keys)
40
43
  resources_by_version.each_key do |version|
44
+ @seen_components_for_current_version = Set.new
41
45
  write_version_file(version)
42
46
  end
43
47
  end
44
48
 
45
- def initialize(root)
49
+ def initialize
46
50
  require 'yaml'
47
- @root = root
51
+
48
52
  @resources_by_version = Hash.new do |h, k|
49
53
  h[k] = Set.new
50
54
  end
51
-
55
+ # List of types that we have seen/marked as necessary to list in the components/schemas section
56
+ # These should contain any mediatype define in the versioned controllers plus any type
57
+ # for which we've explicitly rendered a $ref schema
58
+ @seen_components_for_current_version = Set.new
52
59
  @infos = ApiDefinition.instance.infos
53
60
  collect_resources
54
- collect_types
61
+ end
62
+
63
+ def configure_root(root)
64
+ @root = root
65
+ end
66
+
67
+ def register_seen_component(type)
68
+ @seen_components_for_current_version.add(type)
55
69
  end
56
70
 
57
71
  private
@@ -69,63 +83,16 @@ module Praxis
69
83
  end
70
84
  end
71
85
 
72
- def collect_types
73
- @types_by_id = ObjectSpace.each_object(Class).select do |obj|
74
- obj < Attributor::Type
75
- end.index_by(&:id)
76
- end
77
-
78
86
  def write_index_file(for_versions:)
79
87
  # TODO. create a simple html file that can link to the individual versions available
80
88
  end
81
89
 
82
- def scan_types_for_version(version, dumped_resources)
83
- found_media_types = resources_by_version[version].select(&:media_type).collect { |r| r.media_type.describe }
84
-
90
+ # TODO: Change this function name to scan_default_mediatypes...
91
+ def collect_default_mediatypes(endpoints)
85
92
  # We'll start by processing the rendered mediatypes
86
- processed_types = Set.new(resources_by_version[version].select do |r|
87
- r.media_type && !r.media_type.is_a?(Praxis::SimpleMediaType)
93
+ Set.new(endpoints.select do |endpoint|
94
+ endpoint.media_type && !endpoint.media_type.is_a?(Praxis::SimpleMediaType)
88
95
  end.collect(&:media_type))
89
-
90
- newfound = Set.new
91
- found_media_types.each do |mt|
92
- newfound += scan_dump_for_types({ type: mt }, processed_types)
93
- end
94
- # Then will process the rendered resources (noting)
95
- newfound += scan_dump_for_types(dumped_resources, Set.new)
96
-
97
- # At this point we've done a scan of the dumped resources and mediatypes.
98
- # In that scan we've discovered a bunch of types, however, many of those might have appeared in the JSON
99
- # rendered in just shallow mode, so it is not guaranteed that we've seen all the available types.
100
- # For that we'll do a (non-shallow) dump of all the types we found, and scan them until the scans do not
101
- # yield types we haven't seen before
102
- until newfound.empty?
103
- dumped = newfound.collect(&:describe)
104
- processed_types += newfound
105
- newfound = scan_dump_for_types(dumped, processed_types)
106
- end
107
- processed_types
108
- end
109
-
110
- def scan_dump_for_types(data, processed_types)
111
- newfound_types = Set.new
112
- case data
113
- when Array
114
- data.collect { |item| newfound_types += scan_dump_for_types(item, processed_types) }
115
- when Hash
116
- if data.key?(:type) && data[:type].is_a?(Hash) && (%i[id name family] - data[:type].keys).empty?
117
- type_id = data[:type][:id]
118
- unless type_id.nil? || type_id == Praxis::SimpleMediaType.id # SimpleTypes shouldn't be collected
119
- unless types_by_id[type_id]
120
- raise "Error! We have detected a reference to a 'Type' with id='#{type_id}' which is not derived from Attributor::Type" \
121
- ' Document generation cannot proceed.'
122
- end
123
- newfound_types << types_by_id[type_id] unless processed_types.include? types_by_id[type_id]
124
- end
125
- end
126
- data.values.map { |item| newfound_types += scan_dump_for_types(item, processed_types) }
127
- end
128
- newfound_types
129
96
  end
130
97
 
131
98
  def write_version_file(version)
@@ -133,11 +100,11 @@ module Praxis
133
100
  # # Hack, let's "inherit/copy" all traits of a version from the global definition
134
101
  # # Eventually traits should be defined for a version (and inheritable from global) so we'll emulate that here
135
102
  # version_info[:traits] = infos_by_version[:traits]
136
- dumped_resources = dump_resources(resources_by_version[version])
137
- processed_types = scan_types_for_version(version, dumped_resources)
138
103
 
104
+ # We'll for sure include any of the default mediatypes in the endpoints for this version
105
+ @seen_components_for_current_version.merge(collect_default_mediatypes(resources_by_version[version]))
139
106
  # Here we have:
140
- # processed types: which includes mediatypes and normal types...real classes
107
+ # processed types: which includes default mediatypes for the processed endpoints
141
108
  # processed resources for this version: resources_by_version[version]
142
109
 
143
110
  info_object = OpenApi::InfoObject.new(version: version, api_definition_info: @infos[version])
@@ -168,8 +135,8 @@ module Praxis
168
135
  end
169
136
  full_data[:tags] = full_data[:tags] + tags_for_traits unless tags_for_traits.empty?
170
137
 
171
- # Include only MTs (i.e., not custom types or simple types...)
172
- component_schemas = reusable_schema_objects(processed_types.select { |t| t < Praxis::MediaType })
138
+ # Include only MTs and Blueprints (i.e., no simple types...)
139
+ component_schemas = add_component_schemas(@seen_components_for_current_version.clone, {})
173
140
 
174
141
  # 3- Then adding all of the top level Mediatypes...so we can present them at the bottom, otherwise they don't show
175
142
  tags_for_mts = component_schemas.map do |(name, _info)|
@@ -251,16 +218,16 @@ module Praxis
251
218
  end
252
219
  end
253
220
 
254
- def reusable_schema_objects(types)
255
- types.each_with_object({}) do |(type), accum|
256
- the_type = \
257
- if type.respond_to? :as_json_schema
258
- type
259
- else # If it is a blueprint ... for now, it'd be through the attribute
260
- type.attribute
261
- end
262
- accum[type.id] = the_type.as_json_schema(shallow: false)
221
+ def add_component_schemas(types_to_add, components_hash)
222
+ initial = @seen_components_for_current_version.dup
223
+ types_to_add.each_with_object(components_hash) do |(type), accum|
224
+ # For components, we want the first level to be fully dumped (only references below that)
225
+ accum[type.id] = OpenApi::SchemaObject.new(info: type).dump_schema(allow_ref: false, shallow: false)
263
226
  end
227
+ newfound = @seen_components_for_current_version - initial
228
+ # Process the new types if they have discovered
229
+ add_component_schemas(newfound, components_hash) unless newfound.empty?
230
+ components_hash
264
231
  end
265
232
 
266
233
  def convert_to_parameter_object(params)
@@ -13,6 +13,8 @@ module Praxis
13
13
 
14
14
  class << self
15
15
  attr_reader :model_map, :properties
16
+ # Names of the memoizable things (without the @__ prefix)
17
+ attr_accessor :memoized_variables
16
18
  end
17
19
 
18
20
  # TODO: also support an attribute of sorts on the versioned resource module. ie, V1::Resources.api_version.
@@ -32,6 +34,7 @@ module Praxis
32
34
 
33
35
  @properties = superclass.properties.clone
34
36
  @_filters_map = {}
37
+ @memoized_variables = []
35
38
  end
36
39
  end
37
40
 
@@ -130,10 +133,13 @@ module Praxis
130
133
 
131
134
  return unless association_resource_class
132
135
 
136
+ memoized_variables << name
133
137
  module_eval <<-RUBY, __FILE__, __LINE__ + 1
134
138
  def #{name}
139
+ return @__#{name} if instance_variable_defined?("@__#{name}")
140
+
135
141
  records = record.#{name}
136
- return nil if records.nil?
142
+ return nil if records.nil?
137
143
  @__#{name} ||= #{association_resource_class}.wrap(records)
138
144
  end
139
145
  RUBY
@@ -151,6 +157,7 @@ module Praxis
151
157
  end
152
158
 
153
159
  def self.define_delegation_for_related_attribute(resource_name, resource_attribute)
160
+ memoized_variables << resource_attribute
154
161
  module_eval <<-RUBY, __FILE__, __LINE__ + 1
155
162
  def #{resource_attribute}
156
163
  @__#{resource_attribute} ||= if (rec = self.#{resource_name})
@@ -164,6 +171,7 @@ module Praxis
164
171
  related_resource_class = model_map[related_association[:model]]
165
172
  return unless related_resource_class
166
173
 
174
+ memoized_variables << resource_attribute
167
175
  module_eval <<-RUBY, __FILE__, __LINE__ + 1
168
176
  def #{resource_attribute}
169
177
  @__#{resource_attribute} ||= if (rec = self.#{resource_name})
@@ -181,10 +189,10 @@ module Praxis
181
189
  else
182
190
  name.to_s
183
191
  end
184
-
192
+ memoized_variables << ivar_name
185
193
  module_eval <<-RUBY, __FILE__, __LINE__ + 1
186
194
  def #{name}
187
- return @__#{ivar_name} if defined? @__#{ivar_name}
195
+ return @__#{ivar_name} if instance_variable_defined?("@__#{ivar_name}")
188
196
  @__#{ivar_name} = record.#{name}
189
197
  end
190
198
  RUBY
@@ -239,6 +247,22 @@ module Praxis
239
247
  @record = record
240
248
  end
241
249
 
250
+ def reload
251
+ clear_memoization
252
+ reload_record
253
+ end
254
+
255
+ def clear_memoization
256
+ self.class.memoized_variables.each do |name|
257
+ ivar = "@__#{name}"
258
+ remove_instance_variable(ivar) if instance_variable_defined?(ivar)
259
+ end
260
+ end
261
+
262
+ def reload_record
263
+ record.reload
264
+ end
265
+
242
266
  def respond_to_missing?(name, *)
243
267
  @record.respond_to?(name) || super
244
268
  end
@@ -110,8 +110,18 @@ module Praxis
110
110
  raise Exceptions::Validation, "Attempting to return a response with name #{response_name} " \
111
111
  'but no response definition with that name can be found'
112
112
  end
113
-
114
113
  response_definition.validate(self, validate_body: validate_body)
114
+ rescue Exceptions::Validation => e
115
+ ve = Application.instance.validation_handler.handle!(
116
+ summary: 'Error validating response',
117
+ exception: e,
118
+ request: request,
119
+ stage: 'response',
120
+ errors: e.errors
121
+ )
122
+ body = ve.format!
123
+
124
+ Responses::InternalServerError.new(body: body)
115
125
  end
116
126
  end
117
127
  end
@@ -7,7 +7,8 @@ namespace :praxis do
7
7
  require 'fileutils'
8
8
 
9
9
  Praxis::Blueprint.caching_enabled = false
10
- generator = Praxis::Docs::OpenApiGenerator.new(Dir.pwd)
10
+ generator = Praxis::Docs::OpenApiGenerator.instance
11
+ generator.configure_root(Dir.pwd)
11
12
  generator.save!
12
13
  end
13
14
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Praxis
4
- VERSION = '2.0.pre.19'
4
+ VERSION = '2.0.pre.20'
5
5
  end
data/praxis.gemspec CHANGED
@@ -51,6 +51,6 @@ Gem::Specification.new do |spec|
51
51
  spec.add_development_dependency 'rspec-collection_matchers', '~> 1'
52
52
  spec.add_development_dependency 'rspec-its', '~> 1'
53
53
  # Just for the query selector extensions etc...
54
- spec.add_development_dependency 'activerecord', '> 4','< 7'
54
+ spec.add_development_dependency 'activerecord', '> 4', '< 7'
55
55
  spec.add_development_dependency 'sequel', '~> 5'
56
56
  end
@@ -88,7 +88,7 @@ describe 'Functional specs' do
88
88
 
89
89
  it 'fails to validate the response' do
90
90
  get '/api/clouds/1/instances?response_content_type=somejunk&api_version=1.0', nil, 'HTTP_FOO' => 'bar', 'global_session' => session
91
- expect(last_response.status).to eq(400)
91
+ expect(last_response.status).to eq(500)
92
92
  response = JSON.parse(last_response.body)
93
93
 
94
94
  expect(response['name']).to eq('ValidationError')
@@ -104,7 +104,7 @@ describe 'Functional specs' do
104
104
  expect(Praxis::Application.instance.config).to receive(:praxis).and_return(praxis_config)
105
105
  end
106
106
 
107
- it 'fails to validate the response' do
107
+ it 'does not validate the response and succeeds' do
108
108
  expect do
109
109
  get '/api/clouds/1/instances?response_content_type=somejunk&api_version=1.0', nil, 'global_session' => session
110
110
  end.to_not raise_error
@@ -400,7 +400,7 @@ describe 'Functional specs' do
400
400
  its(['root_volume']) { should be(nil) }
401
401
  end
402
402
 
403
- context 'with an invalid name' do
403
+ context 'returning an invalid name' do
404
404
  let(:request_payload) { { name: 'Invalid Name' } }
405
405
 
406
406
  its(['name']) { should eq 'ValidationError' }
@@ -408,7 +408,7 @@ describe 'Functional specs' do
408
408
  its(['errors']) { should match_array [/\$\.name value \(Invalid Name\) does not match regexp/] }
409
409
 
410
410
  it 'returns a validation error' do
411
- expect(last_response.status).to eq(400)
411
+ expect(last_response.status).to eq(500)
412
412
  end
413
413
  end
414
414
  end
@@ -133,6 +133,19 @@ describe Praxis::Mapper::Resource do
133
133
  allow(record).to receive(:other_model).and_return(other_record)
134
134
  expect(resource.other_resource).to be(SimpleResource.new(record).other_resource)
135
135
  end
136
+
137
+ it 'memoizes result of related associations' do
138
+ expect(record).to receive(:parent).once.and_return(parent_record)
139
+ expect(resource.parent).to be(resource.parent)
140
+ end
141
+
142
+ it 'can clear memoization' do
143
+ expect(record).to receive(:parent).twice.and_return(parent_record)
144
+
145
+ expect(resource.parent).to be(resource.parent) # One time only calling the record parent method
146
+ resource.clear_memoization
147
+ expect(resource.parent).to be(resource.parent) # One time only time calling the record parent method after the reset
148
+ end
136
149
  end
137
150
 
138
151
  context '.wrap' do
@@ -61,9 +61,15 @@ describe Praxis::Response do
61
61
  end
62
62
 
63
63
  it 'should raise an error' do
64
+ resp = nil
64
65
  expect do
65
- response.validate(action)
66
- end.to raise_error(Praxis::Exceptions::Validation, /response definition with that name can be found/)
66
+ resp = response.validate(action)
67
+ end.to_not raise_error
68
+
69
+ expect(resp.status).to eq(500)
70
+ expect(resp.body[:name]).to eq('ValidationError')
71
+ expect(resp.body[:summary]).to eq('Error validating response')
72
+ expect(resp.body[:errors]).to include(/response definition with that name can be found/)
67
73
  end
68
74
  end
69
75
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: praxis
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.pre.19
4
+ version: 2.0.pre.20
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josep M. Blanquer
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2022-01-10 00:00:00.000000000 Z
12
+ date: 2022-02-07 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport