praxis 0.18.1 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +57 -1
- data/Gemfile +2 -1
- data/README.md +21 -27
- data/lib/api_browser/app/index.html +3 -3
- data/lib/api_browser/app/js/app.js +23 -3
- data/lib/api_browser/app/js/controllers/action.js +33 -21
- data/lib/api_browser/app/js/controllers/controller.js +3 -25
- data/lib/api_browser/app/js/controllers/menu.js +61 -51
- data/lib/api_browser/app/js/controllers/trait.js +10 -0
- data/lib/api_browser/app/js/controllers/type.js +8 -5
- data/lib/api_browser/app/js/directives/fixed_if_fits.js +9 -2
- data/lib/api_browser/app/js/directives/menu_item.js +59 -0
- data/lib/api_browser/app/js/directives/readable_list.js +87 -0
- data/lib/api_browser/app/js/directives/url.js +16 -0
- data/lib/api_browser/app/js/factories/Configuration.js +1 -2
- data/lib/api_browser/app/js/factories/Documentation.js +49 -7
- data/lib/api_browser/app/js/factories/PageInfo.js +9 -0
- data/lib/api_browser/app/js/factories/normalize_attributes.js +1 -2
- data/lib/api_browser/app/js/factories/template_for.js +9 -4
- data/lib/api_browser/app/sass/modules/_sidebar.scss +54 -15
- data/lib/api_browser/app/sass/praxis.scss +4 -0
- data/lib/api_browser/app/views/action.html +72 -41
- data/lib/api_browser/app/views/builtin/field-selector.html +24 -0
- data/lib/api_browser/app/views/controller.html +9 -10
- data/lib/api_browser/app/views/directives/menu_item.html +8 -0
- data/lib/api_browser/app/views/directives/url.html +3 -0
- data/lib/api_browser/app/views/layout.html +2 -2
- data/lib/api_browser/app/views/menu.html +8 -14
- data/lib/api_browser/app/views/navbar.html +1 -1
- data/lib/api_browser/app/views/trait.html +13 -0
- data/lib/api_browser/app/views/type/details.html +1 -1
- data/lib/api_browser/app/views/type.html +1 -1
- data/lib/api_browser/app/views/types/embedded/field-selector.html +13 -0
- data/lib/api_browser/app/views/types/label/primitive.html +1 -1
- data/lib/api_browser/app/views/types/standalone/array.html +3 -0
- data/lib/praxis/action_definition.rb +15 -2
- data/lib/praxis/collection.rb +17 -5
- data/lib/praxis/controller.rb +12 -3
- data/lib/praxis/docs/generator.rb +11 -7
- data/lib/praxis/extensions/field_expansion.rb +59 -0
- data/lib/praxis/extensions/field_selection/field_selector.rb +125 -0
- data/lib/praxis/extensions/field_selection.rb +10 -0
- data/lib/praxis/extensions/mapper_selectors.rb +16 -0
- data/lib/praxis/extensions/rendering.rb +43 -0
- data/lib/praxis/links.rb +1 -0
- data/lib/praxis/media_type.rb +87 -3
- data/lib/praxis/media_type_collection.rb +1 -1
- data/lib/praxis/media_type_identifier.rb +6 -1
- data/lib/praxis/plugins/praxis_mapper_plugin.rb +29 -10
- data/lib/praxis/restful_doc_generator.rb +11 -8
- data/lib/praxis/tasks/api_docs.rb +6 -5
- data/lib/praxis/types/multipart_array.rb +1 -1
- data/lib/praxis/version.rb +1 -1
- data/lib/praxis.rb +5 -0
- data/praxis.gemspec +4 -3
- data/spec/api_browser/factories/configuration_spec.js +32 -0
- data/spec/api_browser/factories/documentation_spec.js +75 -25
- data/spec/api_browser/factories/normalize_attributes_spec.js +0 -5
- data/spec/praxis/{types/collection_spec.rb → collection_spec.rb} +36 -23
- data/spec/praxis/extensions/field_expansion_spec.rb +96 -0
- data/spec/praxis/extensions/field_selection/field_selector_spec.rb +92 -0
- data/spec/praxis/extensions/rendering_spec.rb +63 -0
- data/spec/praxis/links_spec.rb +6 -0
- data/spec/praxis/media_type_collection_spec.rb +0 -1
- data/spec/praxis/media_type_identifier_spec.rb +15 -1
- data/spec/praxis/media_type_spec.rb +101 -3
- data/spec/praxis/plugins/praxis_mapper_plugin_spec.rb +33 -24
- data/spec/praxis/request_stages/request_stage_spec.rb +1 -1
- data/spec/praxis/types/multipart_array_spec.rb +14 -4
- data/spec/spec_app/app/controllers/instances.rb +6 -1
- data/spec/spec_app/config/environment.rb +2 -1
- data/spec/spec_app/design/resources/instances.rb +1 -0
- data/spec/spec_helper.rb +3 -1
- data/spec/support/spec_media_types.rb +224 -1
- metadata +50 -16
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'praxis/extensions/field_selection'
|
4
|
+
|
5
|
+
describe Praxis::Extensions::FieldSelection::FieldSelector do
|
6
|
+
|
7
|
+
let(:type) { described_class.for(Address) }
|
8
|
+
|
9
|
+
|
10
|
+
subject(:field_selector) { type.load(fields) }
|
11
|
+
|
12
|
+
context '.example' do
|
13
|
+
subject(:example) { type.example }
|
14
|
+
|
15
|
+
it 'generates a list of 3 random attribute names' do
|
16
|
+
example_attributes = example.fields.keys
|
17
|
+
expect(example_attributes).to have(3).items
|
18
|
+
expect(example_attributes - Address.attributes.keys).to be_empty
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'validates' do
|
22
|
+
expect(type.example.validate).to be_empty
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
context '.load' do
|
28
|
+
let(:parsed_fields) { double('fields') }
|
29
|
+
|
30
|
+
it 'loads nil and an empty string as true' do
|
31
|
+
expect(type.load(nil).fields).to be(true)
|
32
|
+
expect(type.load('').fields).to be(true)
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
it 'loads fields' do
|
37
|
+
fields = 'id,name,owner{name}'
|
38
|
+
|
39
|
+
expect(Attributor::FieldSelector).to receive(:load).
|
40
|
+
with(fields).and_return(parsed_fields)
|
41
|
+
|
42
|
+
result = type.load(fields)
|
43
|
+
expect(result.fields).to be parsed_fields
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
context '#dump' do
|
49
|
+
it 'dumps nested fields properly' do
|
50
|
+
fields = 'id,name,owner{name}'
|
51
|
+
result = type.load(fields)
|
52
|
+
expect(result.dump).to eq fields
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'dumps a nil or "" value as ""' do
|
56
|
+
expect(type.load(nil).dump).to eq ''
|
57
|
+
expect(type.load('').dump).to eq ''
|
58
|
+
end
|
59
|
+
end
|
60
|
+
context '.validate' do
|
61
|
+
let(:selector_string) { 'id,name' }
|
62
|
+
it 'loads and calls the instance' do
|
63
|
+
example = double('example')
|
64
|
+
expect(type).to receive(:load).with(selector_string, ['$']).and_return(example)
|
65
|
+
expect(example).to receive(:validate).and_return([])
|
66
|
+
type.validate(selector_string)
|
67
|
+
end
|
68
|
+
context 'validating subattributes' do
|
69
|
+
let(:selector_string) { 'id,resident,owner{age}' }
|
70
|
+
it 'validates subattributes' do
|
71
|
+
errors = type.validate(selector_string)
|
72
|
+
expect(errors).to match_array([
|
73
|
+
"Attribute with name resident not found in Address",
|
74
|
+
"Attribute with name age not found in Person"
|
75
|
+
])
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
context '#validate' do
|
82
|
+
it do
|
83
|
+
expect(type.validate('id,name')).to be_empty
|
84
|
+
expect(type.validate('id,state')).to have(1).items
|
85
|
+
expect(type.validate('id,owner{name}')).to have(0).items
|
86
|
+
expect(type.validate('id,owner{foo}')).to have(1).items
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
|
92
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Praxis::Extensions::Rendering do
|
4
|
+
|
5
|
+
let(:test_class) do
|
6
|
+
Struct.new(:media_type, :expanded_fields, :response, :request) do |klass|
|
7
|
+
include Praxis::Extensions::Rendering
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
let(:media_type) { Person }
|
12
|
+
let(:expanded_fields) { {id: true, name: true} }
|
13
|
+
let(:response) { double('response', headers: {}) }
|
14
|
+
let(:request) { double('request') }
|
15
|
+
|
16
|
+
let(:object) { {id: '1', name: 'bob', href: '/people/bob'} }
|
17
|
+
subject(:instance) { test_class.new(media_type, expanded_fields, response, request) }
|
18
|
+
|
19
|
+
context '#render' do
|
20
|
+
subject(:output) { instance.render(object) }
|
21
|
+
it 'loads and renders the object' do
|
22
|
+
expect(output).to eq(id: 1, name: 'bob')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context '#display' do
|
27
|
+
context 'without exception' do
|
28
|
+
before do
|
29
|
+
expect(response).to receive(:body=).with({id: 1, name: 'bob'})
|
30
|
+
end
|
31
|
+
|
32
|
+
subject!(:output) { instance.display(object) }
|
33
|
+
|
34
|
+
it 'returns the response' do
|
35
|
+
expect(output).to be(response)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'sets the Content-Type header' do
|
39
|
+
expect(response.headers['Content-Type']).to eq 'application/vnd.acme.person'
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'with a rendering exception' do
|
44
|
+
let(:handler_params) do
|
45
|
+
{
|
46
|
+
summary: "Circular Rendering Error when rendering response. " +
|
47
|
+
"Please especify a view to narrow the dependent fields, or narrow your field set.",
|
48
|
+
exception: circular_exception,
|
49
|
+
request: request,
|
50
|
+
stage: :action,
|
51
|
+
errors: nil
|
52
|
+
}
|
53
|
+
end
|
54
|
+
let(:circular_exception){ Praxis::Renderer::CircularRenderingError.new(object, "ctx") }
|
55
|
+
it 'catches a circular rendering exception' do
|
56
|
+
expect(instance).to receive(:render).and_raise(circular_exception)
|
57
|
+
expect(Praxis::Application.instance.validation_handler).to receive(:handle!).with(handler_params)
|
58
|
+
instance.display(object)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
data/spec/praxis/links_spec.rb
CHANGED
@@ -19,6 +19,11 @@ describe Praxis::Links do
|
|
19
19
|
expect(link.for(Address)).to eq(link)
|
20
20
|
end
|
21
21
|
|
22
|
+
it 'are marked as anonymous' do
|
23
|
+
described = link.for(Address).describe
|
24
|
+
expect(described[:anonymous]).to be(true)
|
25
|
+
end
|
26
|
+
|
22
27
|
context 'contents' do
|
23
28
|
subject(:view) { link.view(:default) }
|
24
29
|
|
@@ -34,6 +39,7 @@ describe Praxis::Links do
|
|
34
39
|
subject(:rendered_links){ example.render(view: :default)[:links] }
|
35
40
|
|
36
41
|
it 'should use the :link for rendering its attributes' do
|
42
|
+
example.render(view: :default)[:links]
|
37
43
|
expect(rendered_links[:owner]).to eq( example.owner.render(view: :link))
|
38
44
|
end
|
39
45
|
end
|
@@ -3,9 +3,23 @@ require "spec_helper"
|
|
3
3
|
describe Praxis::MediaTypeIdentifier do
|
4
4
|
let(:example) { 'application/ice-cream+sundae; nuts="true"; fudge="true"' }
|
5
5
|
|
6
|
-
subject { described_class.
|
6
|
+
subject { described_class.load(example) }
|
7
7
|
|
8
8
|
context '.load' do
|
9
|
+
context 'of nil values' do
|
10
|
+
let(:example) { nil }
|
11
|
+
it 'return nil' do
|
12
|
+
expect(subject).to be(nil)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
context 'of empty string values' do
|
17
|
+
let(:example) { "" }
|
18
|
+
it 'returns nil' do
|
19
|
+
expect(subject).to be(nil)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
9
23
|
it 'parses type/subtype' do
|
10
24
|
expect(subject.type).to eq('application')
|
11
25
|
expect(subject.subtype).to eq('ice-cream')
|
@@ -38,6 +38,14 @@ describe Praxis::MediaType do
|
|
38
38
|
end
|
39
39
|
|
40
40
|
|
41
|
+
context 'loading' do
|
42
|
+
it do
|
43
|
+
Person.load({id: 1})
|
44
|
+
Person.load(owner_resource)
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
41
49
|
|
42
50
|
context 'accessor methods' do
|
43
51
|
subject(:address_klass) { address.class }
|
@@ -144,7 +152,7 @@ describe Praxis::MediaType do
|
|
144
152
|
|
145
153
|
subject(:described){ Address.describe }
|
146
154
|
|
147
|
-
its(:keys) { should match_array( [:attributes, :description, :display_name, :family, :id, :identifier, :key, :name, :views] ) }
|
155
|
+
its(:keys) { should match_array( [:attributes, :description, :display_name, :family, :id, :identifier, :key, :name, :views, :requirements] ) }
|
148
156
|
its([:attributes]) { should be_kind_of(::Hash) }
|
149
157
|
its([:description]) { should be_kind_of(::String) }
|
150
158
|
its([:display_name]) { should be_kind_of(::String) }
|
@@ -162,10 +170,10 @@ describe Praxis::MediaType do
|
|
162
170
|
its([:name]) { should eq(Address.name) }
|
163
171
|
its([:identifier]) { should eq(Address.identifier.to_s) }
|
164
172
|
it 'should include the defined views' do
|
165
|
-
expect( subject[:views].keys ).to
|
173
|
+
expect( subject[:views].keys ).to match_array([:default, :master, :link])
|
166
174
|
end
|
167
175
|
it 'should include the defined attributes' do
|
168
|
-
expect( subject[:attributes].keys ).to
|
176
|
+
expect( subject[:attributes].keys ).to match_array([:id, :name, :owner, :custodian, :residents, :residents_summary, :links])
|
169
177
|
end
|
170
178
|
end
|
171
179
|
|
@@ -173,4 +181,94 @@ describe Praxis::MediaType do
|
|
173
181
|
it 'has specs'
|
174
182
|
end
|
175
183
|
|
184
|
+
context Praxis::MediaType::FieldResolver do
|
185
|
+
let(:expander) { Praxis::FieldExpander }
|
186
|
+
let(:user_view) { User.views[:default] }
|
187
|
+
|
188
|
+
let(:fields) { expander.expand(user_view) }
|
189
|
+
|
190
|
+
let(:field_resolver) { Praxis::MediaType::FieldResolver }
|
191
|
+
|
192
|
+
subject(:output) { field_resolver.resolve(User,fields) }
|
193
|
+
|
194
|
+
|
195
|
+
it 'merges link stuff in properly' do
|
196
|
+
expect(output).to_not have_key(:links)
|
197
|
+
expect(output).to have_key(:primary_blog)
|
198
|
+
|
199
|
+
expect(output[:primary_blog]).to eq({href: true})
|
200
|
+
end
|
201
|
+
|
202
|
+
it 'resolves link aliases (from link ... using:)' do
|
203
|
+
expect(output).to have_key(:posts_summary)
|
204
|
+
expect(output[:posts_summary]).to eq({href: true})
|
205
|
+
end
|
206
|
+
|
207
|
+
context 'merging top-level attributes with those from links' do
|
208
|
+
let(:user_view) { User.views[:extended] }
|
209
|
+
|
210
|
+
subject(:primary_blog_output) { output[:primary_blog] }
|
211
|
+
it 'merges them' do
|
212
|
+
expected = {
|
213
|
+
href: true,
|
214
|
+
id: true,
|
215
|
+
name: true,
|
216
|
+
description: true
|
217
|
+
}
|
218
|
+
expect(primary_blog_output).to eq(expected)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
context 'deep-merging fields' do
|
223
|
+
let(:fields) do
|
224
|
+
{
|
225
|
+
primary_blog: {
|
226
|
+
owner: { first: true, last: true }
|
227
|
+
},
|
228
|
+
links: {
|
229
|
+
primary_blog: {
|
230
|
+
owner: { href: true }
|
231
|
+
}
|
232
|
+
}
|
233
|
+
}
|
234
|
+
end
|
235
|
+
|
236
|
+
it 'does a deep-merge for sub-attributes' do
|
237
|
+
expected = {
|
238
|
+
owner: {first: true, last: true, href: true}
|
239
|
+
}
|
240
|
+
expect(output[:primary_blog]).to eq(expected)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
context 'resolving collections' do
|
245
|
+
let(:fields) { {:id=>true, :posts=>[{:href=>true}]}}
|
246
|
+
it 'strips arrays from the incoming fields' do
|
247
|
+
expect(output).to eq(id: true, posts: {href: true})
|
248
|
+
end
|
249
|
+
|
250
|
+
it 'supports multi-dimensional collections' do
|
251
|
+
fields = {
|
252
|
+
id: true,
|
253
|
+
post_matrix:[[{title: true, href: true}]]
|
254
|
+
}
|
255
|
+
output = field_resolver.resolve(User,fields)
|
256
|
+
expect(output).to eq(id: true, post_matrix:{href: true, title: true})
|
257
|
+
end
|
258
|
+
|
259
|
+
it 'supports nesting structs and arrays collections' do
|
260
|
+
fields = {
|
261
|
+
id: true,
|
262
|
+
daily_posts: [
|
263
|
+
{day: true, posts: [{id: true}]}
|
264
|
+
]
|
265
|
+
}
|
266
|
+
output = field_resolver.resolve(User,fields)
|
267
|
+
expect(output).to eq(id: true, daily_posts:{day: true, posts: {id:true}})
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
end
|
272
|
+
|
273
|
+
|
176
274
|
end
|
@@ -8,7 +8,7 @@ describe Praxis::Plugins::PraxisMapperPlugin do
|
|
8
8
|
context 'Plugin' do
|
9
9
|
context 'configuration' do
|
10
10
|
subject { config }
|
11
|
-
its(:log_stats) { should eq '
|
11
|
+
its(:log_stats) { should eq 'detailed' }
|
12
12
|
its(:stats_log_level) { should eq :info }
|
13
13
|
its(:repositories) { should have_key("default") }
|
14
14
|
|
@@ -24,11 +24,11 @@ describe Praxis::Plugins::PraxisMapperPlugin do
|
|
24
24
|
end
|
25
25
|
|
26
26
|
context 'Request' do
|
27
|
-
|
27
|
+
|
28
28
|
it 'should have identity_map accessors' do
|
29
29
|
expect(Praxis::Plugins::PraxisMapperPlugin::Request.instance_methods).to include(:identity_map,:identity_map=)
|
30
30
|
end
|
31
|
-
|
31
|
+
|
32
32
|
it 'should have silence_mapper_stats accessors' do
|
33
33
|
expect(Praxis::Plugins::PraxisMapperPlugin::Request.instance_methods)
|
34
34
|
.to include(:silence_mapper_stats,:silence_mapper_stats=)
|
@@ -45,20 +45,29 @@ describe Praxis::Plugins::PraxisMapperPlugin do
|
|
45
45
|
around(:each) do |example|
|
46
46
|
orig_level = Praxis::Application.instance.logger.level
|
47
47
|
Praxis::Application.instance.logger.level = 2
|
48
|
-
config.log_stats = 'detailed'; plugin.setup!
|
49
48
|
example.run
|
50
|
-
config.log_stats = 'skip'; plugin.setup!
|
51
49
|
Praxis::Application.instance.logger.level = orig_level
|
52
50
|
end
|
53
51
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
get '/api/clouds/1/instances/2?junk=foo&api_version=1.0', nil, 'rack.input' => the_body,'CONTENT_TYPE' => "application/json", 'global_session' => session
|
52
|
+
context 'with no identity_map set in the request' do
|
53
|
+
it 'does not log stats' do
|
54
|
+
expect(Praxis::Plugins::PraxisMapperPlugin::Statistics).to_not receive(:log)
|
55
|
+
the_body = StringIO.new("{}") # This is a funny, GET request expecting a body
|
56
|
+
get '/api/clouds/1/instances/2?api_version=1.0', nil, 'rack.input' => the_body,'CONTENT_TYPE' => "application/json", 'global_session' => session
|
60
57
|
|
61
|
-
|
58
|
+
expect(last_response.status).to eq(200)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
context 'with an identity_map set in the request' do
|
62
|
+
it 'logs stats' do
|
63
|
+
expect(Praxis::Plugins::PraxisMapperPlugin::Statistics).to receive(:log).
|
64
|
+
with(kind_of(Praxis::Request),kind_of(Praxis::Mapper::IdentityMap), 'detailed').
|
65
|
+
and_call_original
|
66
|
+
the_body = StringIO.new("{}") # This is a funny, GET request expecting a body
|
67
|
+
get '/api/clouds/1/instances/2?create_identity_map=true&api_version=1.0', nil, 'rack.input' => the_body,'CONTENT_TYPE' => "application/json", 'global_session' => session
|
68
|
+
|
69
|
+
expect(last_response.status).to eq(200)
|
70
|
+
end
|
62
71
|
end
|
63
72
|
|
64
73
|
end
|
@@ -77,39 +86,39 @@ describe Praxis::Plugins::PraxisMapperPlugin do
|
|
77
86
|
context 'when the request silences mapper stats' do
|
78
87
|
let(:request){ double('request', silence_mapper_stats: true ) }
|
79
88
|
it 'should not log anything' do
|
80
|
-
expect(Praxis::Plugins::PraxisMapperPlugin::Statistics).to_not receive(:to_logger)
|
89
|
+
expect(Praxis::Plugins::PraxisMapperPlugin::Statistics).to_not receive(:to_logger)
|
81
90
|
end
|
82
91
|
end
|
83
|
-
|
92
|
+
|
84
93
|
context 'without the request silencing mapper stats' do
|
85
94
|
context 'when log_stats = detailed' do
|
86
95
|
it 'should call the detailed method' do
|
87
|
-
expect(Praxis::Plugins::PraxisMapperPlugin::Statistics).to receive(:detailed).with(identity_map)
|
96
|
+
expect(Praxis::Plugins::PraxisMapperPlugin::Statistics).to receive(:detailed).with(identity_map)
|
88
97
|
end
|
89
98
|
end
|
90
|
-
|
99
|
+
|
91
100
|
context 'when log_stats = short' do
|
92
101
|
let(:log_stats){ 'short' }
|
93
102
|
it 'should call the short method' do
|
94
|
-
expect(Praxis::Plugins::PraxisMapperPlugin::Statistics).to receive(:short).with(identity_map)
|
103
|
+
expect(Praxis::Plugins::PraxisMapperPlugin::Statistics).to receive(:short).with(identity_map)
|
95
104
|
end
|
96
105
|
end
|
97
|
-
|
106
|
+
|
98
107
|
context 'when log_stats = skip' do
|
99
108
|
let(:log_stats){ 'skip' }
|
100
|
-
|
109
|
+
|
101
110
|
it 'should not log anything' do
|
102
|
-
expect(Praxis::Plugins::PraxisMapperPlugin::Statistics).to_not receive(:to_logger)
|
111
|
+
expect(Praxis::Plugins::PraxisMapperPlugin::Statistics).to_not receive(:to_logger)
|
103
112
|
end
|
104
113
|
end
|
105
|
-
|
114
|
+
|
106
115
|
context 'when there is no identity map' do
|
107
116
|
let(:identity_map) { nil }
|
108
117
|
it 'should not log anything' do
|
109
118
|
expect(Praxis::Plugins::PraxisMapperPlugin::Statistics).to_not receive(:to_logger)
|
110
|
-
end
|
119
|
+
end
|
111
120
|
end
|
112
|
-
|
121
|
+
|
113
122
|
context 'when no queries are logged in the identity map' do
|
114
123
|
let(:queries){ {} }
|
115
124
|
it 'should log a special message' do
|
@@ -117,7 +126,7 @@ describe Praxis::Plugins::PraxisMapperPlugin do
|
|
117
126
|
.with("No database interactions observed.")
|
118
127
|
end
|
119
128
|
end
|
120
|
-
|
129
|
+
|
121
130
|
end
|
122
131
|
end
|
123
132
|
|
@@ -10,7 +10,7 @@ describe Praxis::RequestStages::RequestStage do
|
|
10
10
|
|
11
11
|
let(:stage_class) { Class.new(Praxis::RequestStages::RequestStage) }
|
12
12
|
|
13
|
-
let(:request) {
|
13
|
+
let(:request) { Praxis::Request.new({}) }
|
14
14
|
let(:controller){ controller_class.new(request) }
|
15
15
|
|
16
16
|
|
@@ -19,6 +19,8 @@ describe Praxis::Types::MultipartArray do
|
|
19
19
|
payload Attributor::Tempfile
|
20
20
|
end
|
21
21
|
|
22
|
+
file 'thumbnail', Attributor::Tempfile
|
23
|
+
|
22
24
|
part 'image', Attributor::Tempfile, filename: true
|
23
25
|
|
24
26
|
end
|
@@ -50,6 +52,9 @@ describe Praxis::Types::MultipartArray do
|
|
50
52
|
entity = MIME::Application.new('{"first_name": "Frank"}', 'json')
|
51
53
|
form_data.add entity,'stuff2'
|
52
54
|
|
55
|
+
entity = MIME::Application.new('SOMEBINARYDATA', 'jpg')
|
56
|
+
form_data.add entity,'thumbnail', 'thumb.jpg'
|
57
|
+
|
53
58
|
entity = MIME::Application.new('', 'jpg')
|
54
59
|
form_data.add entity,'image', 'image.jpg'
|
55
60
|
|
@@ -82,6 +87,10 @@ describe Praxis::Types::MultipartArray do
|
|
82
87
|
stuff2 = payload.part('stuff2')
|
83
88
|
expect(stuff2.payload['first_name']).to eq 'Frank'
|
84
89
|
|
90
|
+
thumb = payload.part('thumbnail')
|
91
|
+
thumb.payload.rewind
|
92
|
+
expect(thumb.payload.read).to eq 'SOMEBINARYDATA'
|
93
|
+
|
85
94
|
image = payload.part('image')
|
86
95
|
expect(image.filename).to eq 'image.jpg'
|
87
96
|
end
|
@@ -144,7 +153,7 @@ describe Praxis::Types::MultipartArray do
|
|
144
153
|
|
145
154
|
context 'attributes' do
|
146
155
|
subject(:attributes) { description[:attributes] }
|
147
|
-
its(:keys) { should match_array ['title', 'files', 'image']}
|
156
|
+
its(:keys) { should match_array ['title', 'files', 'thumbnail', 'image']}
|
148
157
|
|
149
158
|
context 'the "title" part' do
|
150
159
|
subject(:title_description) { attributes['title'] }
|
@@ -176,6 +185,7 @@ describe Praxis::Types::MultipartArray do
|
|
176
185
|
end
|
177
186
|
|
178
187
|
end
|
188
|
+
|
179
189
|
end
|
180
190
|
|
181
191
|
context 'pattern attributes' do
|
@@ -266,7 +276,7 @@ describe Praxis::Types::MultipartArray do
|
|
266
276
|
|
267
277
|
let(:default_format) { 'xml' }
|
268
278
|
|
269
|
-
let(:output) { example.dump(default_format: default_format) }
|
279
|
+
let(:output) { example.dump(default_format: default_format) }
|
270
280
|
|
271
281
|
let(:parts) { Praxis::MultipartParser.parse({'Content-Type'=>example.content_type}, output).last }
|
272
282
|
|
@@ -288,8 +298,8 @@ describe Praxis::Types::MultipartArray do
|
|
288
298
|
stuff = parts.find { |part| part.name == 'stuff' }
|
289
299
|
expect(stuff.content_type.to_s).to eq 'application/json'
|
290
300
|
expect(stuff.payload).to eq json_handler.generate(example.part('stuff').payload.dump)
|
291
|
-
|
292
|
-
# instances just specify 'application/vnd.acme.instance', and should
|
301
|
+
|
302
|
+
# instances just specify 'application/vnd.acme.instance', and should
|
293
303
|
# have xml suffix appended
|
294
304
|
instances = parts.select { |part| part.name == 'instances' }
|
295
305
|
instances.each_with_index do |instance, i|
|
@@ -46,8 +46,13 @@ class Instances < BaseClass
|
|
46
46
|
response
|
47
47
|
end
|
48
48
|
|
49
|
-
def show(cloud_id:, id:, junk:, **other_params)
|
49
|
+
def show(cloud_id:, id:, junk:, create_identity_map:, **other_params)
|
50
|
+
if create_identity_map
|
51
|
+
request.identity_map
|
52
|
+
end
|
53
|
+
|
50
54
|
payload = request.payload
|
55
|
+
|
51
56
|
response.body = {cloud_id: cloud_id, id: id, junk: junk, other_params: other_params, payload: payload && payload.dump}
|
52
57
|
response.headers['Content-Type'] = 'application/json'
|
53
58
|
response
|
@@ -23,7 +23,8 @@ Praxis::Application.configure do |application|
|
|
23
23
|
|
24
24
|
application.bootloader.use Praxis::Plugins::PraxisMapperPlugin, {
|
25
25
|
config_data: {
|
26
|
-
repositories: { default: {adapter: 'sqlite', database: ':memory:'} }
|
26
|
+
repositories: { default: {adapter: 'sqlite', database: ':memory:'} },
|
27
|
+
log_stats: 'detailed'
|
27
28
|
}
|
28
29
|
}
|
29
30
|
|
@@ -55,6 +55,7 @@ module ApiResources
|
|
55
55
|
attribute :junk, String, default: ''
|
56
56
|
attribute :some_date, DateTime, default: DateTime.parse('2012-12-21')
|
57
57
|
attribute :fail_filter, Attributor::Boolean, default: false
|
58
|
+
attribute :create_identity_map, Attributor::Boolean, default: false
|
58
59
|
end
|
59
60
|
|
60
61
|
payload required: false do
|
data/spec/spec_helper.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
|
+
require 'coveralls'
|
2
|
+
Coveralls.wear!
|
3
|
+
|
1
4
|
$:.unshift File.expand_path(__dir__)
|
2
5
|
$:.unshift File.expand_path('../lib',__dir__)
|
3
6
|
$:.unshift File.expand_path('support',__dir__)
|
4
7
|
|
5
8
|
require 'bundler'
|
6
9
|
Bundler.setup :default, :test
|
7
|
-
|
8
10
|
require 'simplecov'
|
9
11
|
SimpleCov.start 'praxis'
|
10
12
|
|