praxis 0.18.1 → 0.19.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +57 -1
  3. data/Gemfile +2 -1
  4. data/README.md +21 -27
  5. data/lib/api_browser/app/index.html +3 -3
  6. data/lib/api_browser/app/js/app.js +23 -3
  7. data/lib/api_browser/app/js/controllers/action.js +33 -21
  8. data/lib/api_browser/app/js/controllers/controller.js +3 -25
  9. data/lib/api_browser/app/js/controllers/menu.js +61 -51
  10. data/lib/api_browser/app/js/controllers/trait.js +10 -0
  11. data/lib/api_browser/app/js/controllers/type.js +8 -5
  12. data/lib/api_browser/app/js/directives/fixed_if_fits.js +9 -2
  13. data/lib/api_browser/app/js/directives/menu_item.js +59 -0
  14. data/lib/api_browser/app/js/directives/readable_list.js +87 -0
  15. data/lib/api_browser/app/js/directives/url.js +16 -0
  16. data/lib/api_browser/app/js/factories/Configuration.js +1 -2
  17. data/lib/api_browser/app/js/factories/Documentation.js +49 -7
  18. data/lib/api_browser/app/js/factories/PageInfo.js +9 -0
  19. data/lib/api_browser/app/js/factories/normalize_attributes.js +1 -2
  20. data/lib/api_browser/app/js/factories/template_for.js +9 -4
  21. data/lib/api_browser/app/sass/modules/_sidebar.scss +54 -15
  22. data/lib/api_browser/app/sass/praxis.scss +4 -0
  23. data/lib/api_browser/app/views/action.html +72 -41
  24. data/lib/api_browser/app/views/builtin/field-selector.html +24 -0
  25. data/lib/api_browser/app/views/controller.html +9 -10
  26. data/lib/api_browser/app/views/directives/menu_item.html +8 -0
  27. data/lib/api_browser/app/views/directives/url.html +3 -0
  28. data/lib/api_browser/app/views/layout.html +2 -2
  29. data/lib/api_browser/app/views/menu.html +8 -14
  30. data/lib/api_browser/app/views/navbar.html +1 -1
  31. data/lib/api_browser/app/views/trait.html +13 -0
  32. data/lib/api_browser/app/views/type/details.html +1 -1
  33. data/lib/api_browser/app/views/type.html +1 -1
  34. data/lib/api_browser/app/views/types/embedded/field-selector.html +13 -0
  35. data/lib/api_browser/app/views/types/label/primitive.html +1 -1
  36. data/lib/api_browser/app/views/types/standalone/array.html +3 -0
  37. data/lib/praxis/action_definition.rb +15 -2
  38. data/lib/praxis/collection.rb +17 -5
  39. data/lib/praxis/controller.rb +12 -3
  40. data/lib/praxis/docs/generator.rb +11 -7
  41. data/lib/praxis/extensions/field_expansion.rb +59 -0
  42. data/lib/praxis/extensions/field_selection/field_selector.rb +125 -0
  43. data/lib/praxis/extensions/field_selection.rb +10 -0
  44. data/lib/praxis/extensions/mapper_selectors.rb +16 -0
  45. data/lib/praxis/extensions/rendering.rb +43 -0
  46. data/lib/praxis/links.rb +1 -0
  47. data/lib/praxis/media_type.rb +87 -3
  48. data/lib/praxis/media_type_collection.rb +1 -1
  49. data/lib/praxis/media_type_identifier.rb +6 -1
  50. data/lib/praxis/plugins/praxis_mapper_plugin.rb +29 -10
  51. data/lib/praxis/restful_doc_generator.rb +11 -8
  52. data/lib/praxis/tasks/api_docs.rb +6 -5
  53. data/lib/praxis/types/multipart_array.rb +1 -1
  54. data/lib/praxis/version.rb +1 -1
  55. data/lib/praxis.rb +5 -0
  56. data/praxis.gemspec +4 -3
  57. data/spec/api_browser/factories/configuration_spec.js +32 -0
  58. data/spec/api_browser/factories/documentation_spec.js +75 -25
  59. data/spec/api_browser/factories/normalize_attributes_spec.js +0 -5
  60. data/spec/praxis/{types/collection_spec.rb → collection_spec.rb} +36 -23
  61. data/spec/praxis/extensions/field_expansion_spec.rb +96 -0
  62. data/spec/praxis/extensions/field_selection/field_selector_spec.rb +92 -0
  63. data/spec/praxis/extensions/rendering_spec.rb +63 -0
  64. data/spec/praxis/links_spec.rb +6 -0
  65. data/spec/praxis/media_type_collection_spec.rb +0 -1
  66. data/spec/praxis/media_type_identifier_spec.rb +15 -1
  67. data/spec/praxis/media_type_spec.rb +101 -3
  68. data/spec/praxis/plugins/praxis_mapper_plugin_spec.rb +33 -24
  69. data/spec/praxis/request_stages/request_stage_spec.rb +1 -1
  70. data/spec/praxis/types/multipart_array_spec.rb +14 -4
  71. data/spec/spec_app/app/controllers/instances.rb +6 -1
  72. data/spec/spec_app/config/environment.rb +2 -1
  73. data/spec/spec_app/design/resources/instances.rb +1 -0
  74. data/spec/spec_helper.rb +3 -1
  75. data/spec/support/spec_media_types.rb +224 -1
  76. 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
@@ -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
@@ -100,7 +100,6 @@ describe Praxis::MediaTypeCollection do
100
100
  it { should eq(snapshots.collect(&:render)) }
101
101
  end
102
102
 
103
-
104
103
  end
105
104
 
106
105
  context '#validate' do
@@ -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.new(example) }
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 match( [:default, :master] )
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 match( [:id, :name, :owner, :custodian, :residents, :residents_summary, :links] )
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 'skip' }
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
- it 'logs stats' do
55
- expect(Praxis::Plugins::PraxisMapperPlugin::Statistics).to receive(:log).
56
- with(kind_of(Praxis::Request),kind_of(Praxis::Mapper::IdentityMap), 'detailed').
57
- and_call_original
58
- the_body = StringIO.new("{}") # This is a funny, GET request expecting a body
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
- expect(last_response.status).to eq(200)
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) { instance_double("Praxis::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