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

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