praxis 0.13.0 → 0.14.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 (135) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/.travis.yml +15 -2
  4. data/CHANGELOG.md +54 -1
  5. data/bin/praxis +49 -2
  6. data/lib/api_browser/Gruntfile.js +247 -90
  7. data/lib/api_browser/app/bower_components/angular-mocks/.bower.json +19 -0
  8. data/lib/api_browser/app/bower_components/angular-mocks/README.md +57 -0
  9. data/lib/api_browser/app/bower_components/angular-mocks/angular-mocks.js +2193 -0
  10. data/lib/api_browser/app/bower_components/angular-mocks/bower.json +9 -0
  11. data/lib/api_browser/app/bower_components/angular-mocks/package.json +27 -0
  12. data/lib/api_browser/app/bower_components/angular/.bower.json +6 -5
  13. data/lib/api_browser/app/bower_components/angular/README.md +23 -4
  14. data/lib/api_browser/app/bower_components/angular/angular-csp.css +6 -0
  15. data/lib/api_browser/app/bower_components/angular/angular.js +2287 -1597
  16. data/lib/api_browser/app/bower_components/angular/angular.min.js +212 -205
  17. data/lib/api_browser/app/bower_components/angular/angular.min.js.gzip +0 -0
  18. data/lib/api_browser/app/bower_components/angular/angular.min.js.map +3 -3
  19. data/lib/api_browser/app/bower_components/angular/bower.json +2 -1
  20. data/lib/api_browser/app/bower_components/angular/package.json +25 -0
  21. data/lib/api_browser/app/bower_components/showdown/.bower.json +39 -0
  22. data/lib/api_browser/app/bower_components/showdown/.jshintignore +2 -0
  23. data/lib/api_browser/app/bower_components/showdown/.travis.yml +8 -0
  24. data/lib/api_browser/app/bower_components/showdown/Gruntfile.js +100 -0
  25. data/lib/api_browser/app/bower_components/showdown/README.md +317 -0
  26. data/lib/api_browser/app/bower_components/showdown/bower.json +26 -0
  27. data/lib/api_browser/app/bower_components/showdown/compressed/Showdown.js +1606 -0
  28. data/lib/api_browser/app/bower_components/showdown/compressed/Showdown.js.map +1 -0
  29. data/lib/api_browser/app/bower_components/showdown/compressed/Showdown.min.js +2 -0
  30. data/lib/api_browser/app/bower_components/showdown/compressed/extensions/github.min.js +2 -0
  31. data/lib/api_browser/app/bower_components/showdown/compressed/extensions/github.min.js.map +1 -0
  32. data/lib/api_browser/app/bower_components/showdown/compressed/extensions/prettify.min.js +2 -0
  33. data/lib/api_browser/app/bower_components/showdown/compressed/extensions/prettify.min.js.map +1 -0
  34. data/lib/api_browser/app/bower_components/showdown/compressed/extensions/table.min.js +2 -0
  35. data/lib/api_browser/app/bower_components/showdown/compressed/extensions/table.min.js.map +1 -0
  36. data/lib/api_browser/app/bower_components/showdown/compressed/extensions/twitter.min.js +2 -0
  37. data/lib/api_browser/app/bower_components/showdown/compressed/extensions/twitter.min.js.map +1 -0
  38. data/lib/api_browser/app/bower_components/showdown/license.txt +34 -0
  39. data/lib/api_browser/app/bower_components/showdown/package.json +47 -0
  40. data/lib/api_browser/app/bower_components/showdown/src/extensions/github.js +25 -0
  41. data/lib/api_browser/app/bower_components/showdown/src/extensions/prettify.js +29 -0
  42. data/lib/api_browser/app/bower_components/showdown/src/extensions/table.js +106 -0
  43. data/lib/api_browser/app/bower_components/showdown/src/extensions/twitter.js +42 -0
  44. data/lib/api_browser/app/bower_components/showdown/src/ng-showdown.js +150 -0
  45. data/lib/api_browser/app/bower_components/showdown/src/showdown.js +1454 -0
  46. data/lib/api_browser/app/index.html +6 -4
  47. data/lib/api_browser/app/js/app.js +1 -2
  48. data/lib/api_browser/app/js/controllers/action.js +4 -4
  49. data/lib/api_browser/app/js/controllers/controller.js +1 -1
  50. data/lib/api_browser/app/js/controllers/menu.js +5 -3
  51. data/lib/api_browser/app/js/controllers/type.js +5 -5
  52. data/lib/api_browser/app/js/directives/attribute_description.js +5 -5
  53. data/lib/api_browser/app/js/directives/attribute_table.js +1 -1
  54. data/lib/api_browser/app/js/directives/attribute_table_row.js +2 -2
  55. data/lib/api_browser/app/js/directives/no_container.js +1 -1
  56. data/lib/api_browser/app/js/directives/request_body.js +5 -5
  57. data/lib/api_browser/app/js/directives/request_headers.js +3 -6
  58. data/lib/api_browser/app/js/directives/request_parameters.js +3 -6
  59. data/lib/api_browser/app/js/directives/type_label.js +4 -5
  60. data/lib/api_browser/app/js/factories/Documentation.js +4 -4
  61. data/lib/api_browser/app/js/factories/PayloadTemplates.js +2 -2
  62. data/lib/api_browser/app/js/factories/TypeTemplates.js +3 -3
  63. data/lib/api_browser/app/js/filters/markdown.js +6 -0
  64. data/lib/api_browser/app/js/filters/resource_name.js +2 -2
  65. data/lib/api_browser/app/sass/modules/_header.scss +2 -7
  66. data/lib/api_browser/app/sass/{main.scss → praxis.scss} +0 -0
  67. data/lib/api_browser/app/sass/variables/_bootstrap-variables.scss +370 -367
  68. data/lib/api_browser/app/views/action.html +2 -2
  69. data/lib/api_browser/app/views/controller.html +2 -2
  70. data/lib/api_browser/app/views/directives/attribute_description.html +1 -1
  71. data/lib/api_browser/app/views/layout.html +2 -11
  72. data/lib/api_browser/app/views/navbar.html +9 -0
  73. data/lib/api_browser/app/views/resource/_actions.html +1 -1
  74. data/lib/api_browser/app/views/type.html +2 -2
  75. data/lib/api_browser/app/views/type/_details.html +2 -1
  76. data/lib/api_browser/bower.json +5 -0
  77. data/lib/api_browser/package.json +18 -7
  78. data/lib/praxis.rb +8 -3
  79. data/lib/praxis/action_definition.rb +28 -6
  80. data/lib/praxis/api_definition.rb +30 -2
  81. data/lib/praxis/api_general_info.rb +36 -0
  82. data/lib/praxis/bootloader.rb +1 -0
  83. data/lib/praxis/collection.rb +34 -0
  84. data/lib/praxis/controller.rb +7 -0
  85. data/lib/praxis/dispatcher.rb +3 -0
  86. data/lib/praxis/links.rb +2 -8
  87. data/lib/praxis/media_type.rb +6 -24
  88. data/lib/praxis/media_type_collection.rb +6 -2
  89. data/lib/praxis/plugin_concern.rb +2 -1
  90. data/lib/praxis/request.rb +24 -15
  91. data/lib/praxis/request_stages/request_stage.rb +19 -4
  92. data/lib/praxis/request_stages/validate_params_and_headers.rb +1 -1
  93. data/lib/praxis/request_stages/validate_payload.rb +1 -1
  94. data/lib/praxis/resource_definition.rb +45 -10
  95. data/lib/praxis/response_definition.rb +46 -27
  96. data/lib/praxis/restful_doc_generator.rb +94 -7
  97. data/lib/praxis/simple_media_type.rb +2 -9
  98. data/lib/praxis/stage.rb +1 -4
  99. data/lib/praxis/tasks/api_docs.rb +51 -19
  100. data/lib/praxis/tasks/routes.rb +19 -15
  101. data/lib/praxis/types/media_type_common.rb +31 -0
  102. data/lib/praxis/types/multipart.rb +4 -4
  103. data/lib/praxis/version.rb +1 -1
  104. data/praxis.gemspec +2 -2
  105. data/spec/api_browser/factories/documentation_spec.js +50 -0
  106. data/spec/api_browser/filters/attribute_name_spec.js +23 -0
  107. data/spec/functional_spec.rb +62 -10
  108. data/spec/praxis/action_definition_spec.rb +12 -4
  109. data/spec/praxis/api_definition_spec.rb +159 -0
  110. data/spec/praxis/api_general_info_spec.rb +36 -0
  111. data/spec/praxis/bootloader_spec.rb +10 -1
  112. data/spec/praxis/media_type_collection_spec.rb +46 -53
  113. data/spec/praxis/media_type_spec.rb +6 -6
  114. data/spec/praxis/request_stage_spec.rb +7 -2
  115. data/spec/praxis/request_stages_validate_spec.rb +12 -7
  116. data/spec/praxis/resource_definition_spec.rb +62 -0
  117. data/spec/praxis/response_definition_spec.rb +26 -16
  118. data/spec/praxis/stage_spec.rb +4 -8
  119. data/spec/praxis/types/collection_spec.rb +144 -0
  120. data/spec/spec_app/app/controllers/instances.rb +8 -2
  121. data/spec/spec_app/design/api.rb +11 -0
  122. data/spec/spec_app/design/media_types/instance.rb +12 -0
  123. data/spec/spec_app/design/media_types/volume.rb +9 -2
  124. data/spec/spec_app/design/media_types/volume_snapshot.rb +9 -6
  125. data/spec/spec_app/design/resources/instances.rb +25 -10
  126. data/spec/support/spec_media_types.rb +1 -1
  127. data/spec/support/spec_resource_definitions.rb +2 -0
  128. data/tasks/thor/app.rb +15 -10
  129. data/tasks/thor/example.rb +115 -115
  130. data/tasks/thor/templates/generator/empty_app/.gitignore +2 -0
  131. data/tasks/thor/templates/generator/empty_app/docs/app.js +1 -0
  132. data/tasks/thor/templates/generator/empty_app/docs/styles.scss +3 -0
  133. metadata +50 -9
  134. data/lib/api_browser/app/css/main.css +0 -4511
  135. data/lib/praxis/types/collection.rb +0 -17
@@ -33,7 +33,7 @@ describe Praxis::MediaType do
33
33
  its(:description) { should be_kind_of(String) }
34
34
 
35
35
  context 'links' do
36
- context 'with an custom links attribute' do
36
+ context 'with a custom links attribute' do
37
37
  subject(:person) { Person.new(owner_resource) }
38
38
 
39
39
  its(:links) { should be_kind_of(Array) }
@@ -62,15 +62,15 @@ describe Praxis::MediaType do
62
62
  its([:owner]) { should eq(Person.dump(owner_resource, view: :link)) }
63
63
  its([:super]) { should eq(Person.dump(manager_resource, view: :link)) }
64
64
 
65
- context 'for a collection' do
65
+ context 'for a collection summary' do
66
66
  let(:volume) { Volume.example }
67
- let(:snapshots) { volume.snapshots }
67
+ let(:snapshots_summary) { volume.snapshots_summary }
68
68
  let(:output) { volume.render(:default) }
69
69
  subject { links[:snapshots] }
70
70
 
71
- its([:name]) { should eq(snapshots.name) }
72
- its([:size]) { should eq(snapshots.size) }
73
- its([:href]) { should eq(snapshots.href) }
71
+ its([:name]) { should eq(snapshots_summary.name) }
72
+ its([:size]) { should eq(snapshots_summary.size) }
73
+ its([:href]) { should eq(snapshots_summary.href) }
74
74
  end
75
75
  end
76
76
 
@@ -50,6 +50,13 @@ describe Praxis::RequestStages::RequestStage do
50
50
  context 'execute_with_around' do
51
51
  end
52
52
 
53
+ context '#setup!' do
54
+ it 'sets up the deferred callbacks' do
55
+ expect(stage).to receive(:setup_deferred_callbacks!).once
56
+ stage.setup!
57
+ end
58
+ end
59
+
53
60
  context "#execute" do
54
61
  before do
55
62
  stage.stages.push(substage_1, substage_2, substage_3)
@@ -107,8 +114,6 @@ describe Praxis::RequestStages::RequestStage do
107
114
  end
108
115
 
109
116
  it "sets up and executes callbacks" do
110
- expect(stage).to receive(:setup!)
111
- expect(stage).to receive(:setup_deferred_callbacks!)
112
117
  expect(stage).to receive(:execute)
113
118
  expect(stage).to receive(:execute_callbacks).once.with(before_callbacks)
114
119
  expect(stage).to receive(:execute_callbacks).once.with(after_callbacks)
@@ -9,14 +9,19 @@ describe Praxis::RequestStages::Validate do
9
9
 
10
10
  let(:action) { controller.definition.actions[:show] }
11
11
 
12
+ let(:env) do
13
+ e = Rack::MockRequest.env_for('/instances/1?cloud_id=1&api_version=1.0')
14
+ e['rack.input'] = StringIO.new('something=given')
15
+ e['HTTP_VERSION'] = 'HTTP/1.1'
16
+ e['HTTP_HOST'] = 'rightscale'
17
+ e
18
+ end
19
+
12
20
  let(:request) do
13
- env = Rack::MockRequest.env_for('/instances/1?cloud_id=1&api_version=1.0')
14
- env['rack.input'] = StringIO.new('something=given')
15
- env['HTTP_VERSION'] = 'HTTP/1.1'
16
- env['HTTP_HOST'] = 'rightscale'
17
- request = Praxis::Request.new(env)
18
- request.action = action
19
- request
21
+ r = Praxis::Request.new(env)
22
+ r.route_params = {id: 1}
23
+ r.action = action
24
+ r
20
25
  end
21
26
 
22
27
  context 'given a request' do
@@ -34,6 +34,16 @@ describe Praxis::ResourceDefinition do
34
34
  expect(index).to be_kind_of(Praxis::ActionDefinition)
35
35
  expect(index.description).to eq("index description")
36
36
  end
37
+
38
+ it 'complains if action names are not symbols' do
39
+ expect do
40
+ Class.new do
41
+ include Praxis::ResourceDefinition
42
+ action "foo" do
43
+ end
44
+ end
45
+ end.to raise_error(ArgumentError,/Action names must be defined using symbols/)
46
+ end
37
47
  end
38
48
 
39
49
 
@@ -113,4 +123,56 @@ describe Praxis::ResourceDefinition do
113
123
 
114
124
  end
115
125
 
126
+ context '#canonical_path' do
127
+ context 'setting the action' do
128
+ it 'reads the specified action' do
129
+ expect(subject.canonical_path).to eq(subject.actions[:show])
130
+ end
131
+ it 'cannot be done if already been defined' do
132
+ expect{
133
+ resource_definition.canonical_path :reset
134
+ }.to raise_error(/'canonical_path' can only be defined once./)
135
+ end
136
+ end
137
+ context 'if none specified' do
138
+ subject(:resource_definition) do
139
+ Class.new do
140
+ include Praxis::ResourceDefinition
141
+ action :show do
142
+ end
143
+ end
144
+ end
145
+ it 'defaults to the :show action' do
146
+ expect(subject.canonical_path).to eq(subject.actions[:show])
147
+ end
148
+ end
149
+ context 'with an undefined action' do
150
+ subject(:resource_definition) do
151
+ Class.new do
152
+ include Praxis::ResourceDefinition
153
+ canonical_path :non_existent
154
+ end
155
+ end
156
+ it 'raises an error' do
157
+ expect{
158
+ subject.canonical_path
159
+ }.to raise_error(/Action 'non_existent' does not exist/)
160
+ end
161
+ end
162
+ end
163
+
164
+ context '#to_href' do
165
+ it 'accesses the path expansion functions of the primary route' do
166
+ expect(subject.to_href( id: 1)).to eq("/people/1")
167
+ end
168
+ end
169
+ context '#parse_href' do
170
+ let(:parsed){ resource_definition.parse_href("/people/1") }
171
+ it 'accesses the path expansion functions of the primary route' do
172
+ expect(parsed).to have_key(:id)
173
+ end
174
+ it 'coerces the types as specified in the resource definition' do
175
+ expect(parsed[:id]).to be_kind_of(Integer)
176
+ end
177
+ end
116
178
  end
@@ -365,7 +365,16 @@ describe Praxis::ResponseDefinition do
365
365
 
366
366
  context 'when content type matches the mediatype of the spec' do
367
367
  let(:response_headers) { {'Content-Type' => content_type } }
368
- it 'should raise error telling you so' do
368
+ it 'validates successfully' do
369
+ expect {
370
+ response_definition.validate_content_type!(response)
371
+ }.to_not raise_error
372
+ end
373
+ end
374
+
375
+ context 'when content type includes a parameter' do
376
+ let(:response_headers) { {'Content-Type' => "#{content_type};collection=true" } }
377
+ it 'validates successfully' do
369
378
  expect {
370
379
  response_definition.validate_content_type!(response)
371
380
  }.to_not raise_error
@@ -405,7 +414,7 @@ describe Praxis::ResponseDefinition do
405
414
  it 'validates each part' do
406
415
  response_definition.parts
407
416
  expect {
408
- response_definition.validate_parts!(response)
417
+ response_definition.validate_parts!(response)
409
418
  }.to_not raise_error
410
419
  end
411
420
 
@@ -425,6 +434,7 @@ describe Praxis::ResponseDefinition do
425
434
 
426
435
  end
427
436
 
437
+
428
438
  context 'with invalid definitions' do
429
439
  it 'raises an error if status code is not part of the definition' do
430
440
  expect do
@@ -442,19 +452,19 @@ describe Praxis::ResponseDefinition do
442
452
  let(:parts) { nil }
443
453
 
444
454
  let(:response) do
445
- Praxis::ResponseDefinition.new(:custom) do
446
- status 300
447
- end
455
+ Praxis::ResponseDefinition.new(:custom) do
456
+ status 300
457
+ end
448
458
  end
449
459
  subject(:output) { response.describe }
450
-
460
+
451
461
  before do
452
462
  response.description(description) if description
453
463
  response.location(location) if location
454
- response.parts(parts) if parts
464
+ response.parts(parts) if parts
455
465
  response.headers(headers) if headers
456
466
  end
457
-
467
+
458
468
  context 'for a definition without parts' do
459
469
  it{ should be_kind_of(::Hash) }
460
470
  its([:description]){ should be(description) }
@@ -465,33 +475,33 @@ describe Praxis::ResponseDefinition do
465
475
  expect( output[:headers]['Header1'] ).to eq({value: 'Value1' ,type: :string })
466
476
  end
467
477
  end
468
-
478
+
469
479
  context 'for a definition with (homogeneous) parts' do
470
480
  subject(:described_parts){ output[:parts_like] }
471
481
  context 'using :like' do
472
482
  let(:parts) { {like: :ok, media_type: 'foobar'} }
473
-
483
+
474
484
  it 'should contain a parts_like key with a hash' do
475
485
  expect( output ).to have_key(:parts_like)
476
486
  end
477
-
478
- it{ should be_kind_of(::Hash) }
487
+
488
+ it{ should be_kind_of(::Hash) }
479
489
  its([:media_type]){ should == { identifier: 'foobar'} }
480
490
  its([:status]){ should == 200 }
481
491
  end
482
492
  context 'using a full response definition block' do
483
493
  let(:parts) do
484
- Proc.new do
494
+ Proc.new do
485
495
  status 234
486
496
  media_type 'custom_media'
487
497
  end
488
498
  end
489
-
499
+
490
500
  it 'should contain a parts_like key with a hash' do
491
501
  expect( output ).to have_key(:parts_like)
492
502
  end
493
-
494
- it{ should be_kind_of(::Hash) }
503
+
504
+ it{ should be_kind_of(::Hash) }
495
505
  its([:media_type]){ should == { identifier: 'custom_media'} }
496
506
  its([:status]){ should == 234 }
497
507
  end
@@ -13,8 +13,6 @@ describe Praxis::Stage do
13
13
 
14
14
  context ".run" do
15
15
  it "sets up and execute callbacks" do
16
- expect(stage).to receive('setup!')
17
- expect(stage).to receive('setup_deferred_callbacks!')
18
16
  expect(stage).to receive('execute')
19
17
  expect(stage).to receive('execute_callbacks').twice
20
18
  stage.run
@@ -22,7 +20,10 @@ describe Praxis::Stage do
22
20
  end
23
21
 
24
22
  context ".setup!" do
25
- it "should do something"
23
+ it 'should call setup_deferred_callbacks' do
24
+ expect(stage).to receive('setup_deferred_callbacks!')
25
+ stage.setup!
26
+ end
26
27
  end
27
28
 
28
29
  context ".setup_deferred_callbacks!" do
@@ -40,11 +41,6 @@ describe Praxis::Stage do
40
41
  end
41
42
 
42
43
  context ".execute" do
43
- it "raises error when @stages is empty" do
44
- error_msg = 'Subclass must implement Stage#execute'
45
- expect{stage.execute}.to raise_error(NotImplementedError, error_msg)
46
- end
47
-
48
44
  it "runs all the stages" do
49
45
  double_stage = double("stage")
50
46
  expect(double_stage).to receive('run')
@@ -0,0 +1,144 @@
1
+ require "spec_helper"
2
+
3
+ describe Praxis::Collection do
4
+
5
+ let(:type) { Volume }
6
+ let(:example) { Volume.example('example-volume') }
7
+
8
+ let(:snapshots) { example.snapshots }
9
+
10
+ subject(:media_type_collection) do
11
+ Volume.attributes[:snapshots].type
12
+ end
13
+
14
+ context '.of' do
15
+ let(:media_type) do
16
+ Class.new(Praxis::MediaType) do
17
+ identifier 'an-awesome-type'
18
+ end
19
+ end
20
+
21
+ subject!(:collection) do
22
+ Praxis::Collection.of(media_type)
23
+ end
24
+
25
+ its(:identifier) { should eq 'an-awesome-type;type=collection' }
26
+
27
+ it 'sets the collection on the media type' do
28
+ expect(media_type::Collection).to be(collection)
29
+ end
30
+
31
+ it 'returns an existing Collection type' do
32
+ expect(Praxis::Collection.of(media_type)).to be(collection)
33
+ end
34
+
35
+ it 'works with explicitly-defined collections' do
36
+ expect(Praxis::Collection.of(Volume)).to be(Volume::Collection)
37
+ end
38
+ end
39
+
40
+ context 'defined explicitly' do
41
+ subject(:type) { Volume::Collection }
42
+ its(:member_type) { should be Volume }
43
+ its(:identifier) { should eq 'application/vnd.acme.volumes' }
44
+
45
+ end
46
+
47
+ context '.member_type' do
48
+ its(:member_type){ should be(VolumeSnapshot) }
49
+ its(:member_attribute){ should be_kind_of(Attributor::Attribute) }
50
+ its('member_attribute.type'){ should be(VolumeSnapshot) }
51
+ end
52
+
53
+ context '.load' do
54
+ let(:volume_data) do
55
+ {
56
+ id: 1,
57
+ name: 'bob',
58
+ snapshots: snapshots_data
59
+ }
60
+ end
61
+
62
+ let(:snapshots_data) {
63
+ nil
64
+ }
65
+
66
+
67
+ context 'loading an array' do
68
+ let(:snapshots_data) do
69
+ [{id: 1, name: 'snapshot-1'},
70
+ {id: 2, name: 'snapshot-2'}]
71
+ end
72
+
73
+ let(:volume) { Volume.load(volume_data) }
74
+ subject(:snapshots) { volume.snapshots }
75
+
76
+ it 'sets the collection members' do
77
+ expect(snapshots).to have(2).items
78
+
79
+ expect(snapshots[0].id).to eq(1)
80
+ expect(snapshots[0].name).to eq('snapshot-1')
81
+ expect(snapshots[1].id).to eq(2)
82
+ expect(snapshots[1].name).to eq('snapshot-2')
83
+ end
84
+
85
+ end
86
+ end
87
+
88
+
89
+ context '#render' do
90
+
91
+
92
+ context 'for members' do
93
+ let(:volume_output) { example.render(:default) }
94
+
95
+ subject(:output) { volume_output[:snapshots] }
96
+
97
+ it { should eq(snapshots.collect(&:render)) }
98
+ end
99
+
100
+
101
+ end
102
+
103
+ context '#validate' do
104
+ let(:volume_data) do
105
+ {
106
+ id: 1,
107
+ name: 'bob',
108
+ snapshots: snapshots_data
109
+ }
110
+ end
111
+
112
+ let(:snapshots_data) {
113
+ nil
114
+ }
115
+
116
+
117
+ context 'for an array' do
118
+ let(:snapshots_data) do
119
+ [{id: 1, name: 'snapshot-1'},
120
+ {id: 2, name: 'snapshot-2'}]
121
+ end
122
+
123
+ let(:volume) { Volume.load(volume_data) }
124
+ let(:snapshots) { volume.snapshots }
125
+
126
+ it 'validates' do
127
+ expect(volume.validate).to be_empty
128
+ end
129
+
130
+ context 'with invalid members' do
131
+ let(:snapshots_data) do
132
+ [{id: 1, name: 'invalid-1'},
133
+ {id: 2, name: 'snapshot-2'}]
134
+ end
135
+
136
+ it 'returns the error' do
137
+ expect(volume.validate).to have(1).item
138
+ expect(volume.validate[0]).to match(/value \(invalid-1\) does not match regexp/)
139
+ end
140
+ end
141
+ end
142
+ end
143
+
144
+ end
@@ -54,14 +54,14 @@ class Instances < BaseClass
54
54
  def bulk_create(cloud_id:)
55
55
  self.response = BulkResponse.new
56
56
 
57
+
57
58
  request.payload.each do |instance_id,instance|
58
59
  part_body = JSON.pretty_generate(key: instance_id, value: instance.render(:create))
59
60
  headers = {
60
61
  'Status' => '201',
61
62
  'Content-Type' => Instance.identifier,
62
- 'Location' => self.class.definition.actions[:show].primary_route.path.expand(cloud_id: cloud_id, id: instance.id)
63
+ 'Location' => definition.to_href(cloud_id: cloud_id, id: instance.id)
63
64
  }
64
-
65
65
  part = Praxis::MultipartPart.new(part_body, headers)
66
66
 
67
67
  response.add_part(instance_id, part)
@@ -98,4 +98,10 @@ class Instances < BaseClass
98
98
  response
99
99
  end
100
100
 
101
+ def update(id:, cloud_id:)
102
+ response.body = JSON.pretty_generate(request.payload.dump)
103
+ response.headers['Content-Type'] = 'application/vnd.acme.instance'
104
+ response
105
+ end
106
+
101
107
  end