praxis 0.13.0 → 0.14.0

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