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 +4 -4
- data/CHANGELOG.md +8 -0
- data/lib/praxis/blueprint.rb +1 -0
- data/lib/praxis/collection.rb +1 -1
- data/lib/praxis/docs/open_api/media_type_object.rb +2 -2
- data/lib/praxis/docs/open_api/operation_object.rb +1 -1
- data/lib/praxis/docs/open_api/schema_object.rb +46 -14
- data/lib/praxis/docs/open_api/tag_object.rb +4 -4
- data/lib/praxis/docs/open_api_generator.rb +37 -70
- data/lib/praxis/mapper/resource.rb +27 -3
- data/lib/praxis/response.rb +11 -1
- data/lib/praxis/tasks/api_docs.rb +2 -1
- data/lib/praxis/version.rb +1 -1
- data/praxis.gemspec +1 -1
- data/spec/functional_spec.rb +4 -4
- data/spec/praxis/mapper/resource_spec.rb +13 -0
- data/spec/praxis/response_spec.rb +8 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0ade185970597f7537bf75a761bb1e1a01739f2e696ad006f8421037320051c3
|
4
|
+
data.tar.gz: fd9e948ba2f7d48c0abc94f70bec9cad3f594b41d82f9ce9445df28be010fe14
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
data/lib/praxis/blueprint.rb
CHANGED
data/lib/praxis/collection.rb
CHANGED
@@ -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
|
-
|
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
|
8
|
+
attr_reader :type
|
9
9
|
|
10
10
|
def initialize(info:)
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
-
|
31
|
-
|
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
|
-
|
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
|
|
@@ -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, :
|
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
|
49
|
+
def initialize
|
46
50
|
require 'yaml'
|
47
|
-
|
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
|
-
|
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
|
-
|
83
|
-
|
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
|
-
|
87
|
-
|
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
|
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.,
|
172
|
-
component_schemas =
|
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
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
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
|
-
|
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
|
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
|
data/lib/praxis/response.rb
CHANGED
@@ -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.
|
10
|
+
generator = Praxis::Docs::OpenApiGenerator.instance
|
11
|
+
generator.configure_root(Dir.pwd)
|
11
12
|
generator.save!
|
12
13
|
end
|
13
14
|
|
data/lib/praxis/version.rb
CHANGED
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
|
data/spec/functional_spec.rb
CHANGED
@@ -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(
|
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 '
|
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 '
|
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(
|
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.
|
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.
|
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-
|
12
|
+
date: 2022-02-07 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|