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