praxis 0.18.1 → 0.19.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|