praxis 0.10.1 → 0.11pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -1
  3. data/CHANGELOG.md +47 -10
  4. data/Gemfile +1 -1
  5. data/Guardfile +1 -0
  6. data/bin/praxis +33 -4
  7. data/lib/api_browser/app/css/main.css +0 -3
  8. data/lib/praxis.rb +16 -0
  9. data/lib/praxis/action_definition.rb +16 -18
  10. data/lib/praxis/application.rb +31 -2
  11. data/lib/praxis/bootloader.rb +37 -4
  12. data/lib/praxis/bootloader_stages/environment.rb +3 -7
  13. data/lib/praxis/bootloader_stages/plugin_config_load.rb +20 -0
  14. data/lib/praxis/bootloader_stages/plugin_config_prepare.rb +18 -0
  15. data/lib/praxis/bootloader_stages/plugin_loader.rb +19 -0
  16. data/lib/praxis/bootloader_stages/plugin_setup.rb +13 -0
  17. data/lib/praxis/bootloader_stages/routing.rb +16 -6
  18. data/lib/praxis/callbacks.rb +0 -2
  19. data/lib/praxis/config.rb +3 -2
  20. data/lib/praxis/dispatcher.rb +25 -13
  21. data/lib/praxis/error_handler.rb +16 -0
  22. data/lib/praxis/links.rb +9 -4
  23. data/lib/praxis/media_type_collection.rb +2 -3
  24. data/lib/praxis/notifications.rb +41 -0
  25. data/lib/praxis/plugin.rb +18 -8
  26. data/lib/praxis/plugin_concern.rb +40 -0
  27. data/lib/praxis/request.rb +27 -7
  28. data/lib/praxis/request_stages/action.rb +7 -2
  29. data/lib/praxis/request_stages/response.rb +7 -3
  30. data/lib/praxis/request_stages/validate_payload.rb +7 -1
  31. data/lib/praxis/resource_definition.rb +37 -16
  32. data/lib/praxis/response.rb +1 -0
  33. data/lib/praxis/responses/internal_server_error.rb +13 -8
  34. data/lib/praxis/responses/validation_error.rb +10 -7
  35. data/lib/praxis/restful_doc_generator.rb +312 -0
  36. data/lib/praxis/router.rb +7 -5
  37. data/lib/praxis/skeletor/restful_routing_config.rb +12 -5
  38. data/lib/praxis/stage.rb +5 -1
  39. data/lib/praxis/stats.rb +106 -0
  40. data/lib/praxis/tasks/api_docs.rb +8 -314
  41. data/lib/praxis/version.rb +1 -1
  42. data/praxis.gemspec +4 -1
  43. data/spec/functional_spec.rb +87 -32
  44. data/spec/praxis/action_definition_spec.rb +13 -12
  45. data/spec/praxis/bootloader_spec.rb +12 -5
  46. data/spec/praxis/notifications_spec.rb +23 -0
  47. data/spec/praxis/plugin_concern_spec.rb +21 -0
  48. data/spec/praxis/request_spec.rb +56 -1
  49. data/spec/praxis/request_stages_validate_spec.rb +3 -3
  50. data/spec/praxis/resource_definition_spec.rb +44 -60
  51. data/spec/praxis/responses/internal_server_error_spec.rb +32 -16
  52. data/spec/praxis/restful_routing_config_spec.rb +15 -2
  53. data/spec/praxis/router_spec.rb +5 -3
  54. data/spec/praxis/stats_spec.rb +9 -0
  55. data/spec/praxis_mapper_plugin_spec.rb +71 -0
  56. data/spec/spec_app/app/controllers/instances.rb +12 -0
  57. data/spec/spec_app/app/controllers/volumes.rb +5 -0
  58. data/spec/spec_app/app/models/person.rb +3 -0
  59. data/spec/spec_app/config/active_record.yml +2 -0
  60. data/spec/spec_app/config/authentication.yml +3 -0
  61. data/spec/spec_app/config/authorization.yml +4 -0
  62. data/spec/spec_app/config/environment.rb +28 -1
  63. data/spec/spec_app/config/praxis_mapper.yml +6 -0
  64. data/spec/spec_app/config/sequel_model.yml +2 -0
  65. data/spec/spec_app/config/stats.yml +8 -0
  66. data/spec/spec_app/config/stats.yml.dis +8 -0
  67. data/spec/spec_app/design/resources/instances.rb +53 -16
  68. data/spec/spec_app/design/resources/volumes.rb +13 -2
  69. data/spec/spec_helper.rb +14 -0
  70. data/spec/support/spec_authentication_plugin.rb +126 -0
  71. data/spec/support/spec_authorization_plugin.rb +95 -0
  72. data/spec/support/spec_praxis_mapper_plugin.rb +157 -0
  73. data/tasks/loader.thor +6 -0
  74. data/tasks/thor/app.rb +48 -0
  75. data/tasks/thor/example.rb +283 -0
  76. data/tasks/thor/templates/generator/empty_app/.gitignore +3 -0
  77. data/tasks/thor/templates/generator/empty_app/.rspec +1 -0
  78. data/tasks/thor/templates/generator/empty_app/Gemfile +29 -0
  79. data/tasks/thor/templates/generator/empty_app/Guardfile +3 -0
  80. data/tasks/thor/templates/generator/empty_app/README.md +4 -0
  81. data/tasks/thor/templates/generator/empty_app/Rakefile +25 -0
  82. data/tasks/thor/templates/generator/empty_app/app/models/.empty_directory +0 -0
  83. data/tasks/thor/templates/generator/empty_app/app/models/.gitkeep +0 -0
  84. data/tasks/thor/templates/generator/empty_app/app/responses/.empty_directory +0 -0
  85. data/tasks/thor/templates/generator/empty_app/app/responses/.gitkeep +0 -0
  86. data/tasks/thor/templates/generator/empty_app/app/v1/controllers/.empty_directory +0 -0
  87. data/tasks/thor/templates/generator/empty_app/app/v1/controllers/.gitkeep +0 -0
  88. data/tasks/thor/templates/generator/empty_app/config.ru +7 -0
  89. data/tasks/thor/templates/generator/empty_app/config/environment.rb +17 -0
  90. data/tasks/thor/templates/generator/empty_app/config/rainbows.rb +57 -0
  91. data/tasks/thor/templates/generator/empty_app/design/api.rb +0 -0
  92. data/tasks/thor/templates/generator/empty_app/design/response_templates/.empty_directory +0 -0
  93. data/tasks/thor/templates/generator/empty_app/design/response_templates/.gitkeep +0 -0
  94. data/tasks/thor/templates/generator/empty_app/design/v1/media_types/.empty_directory +0 -0
  95. data/tasks/thor/templates/generator/empty_app/design/v1/media_types/.gitkeep +0 -0
  96. data/tasks/thor/templates/generator/empty_app/design/v1/resources/.empty_directory +0 -0
  97. data/tasks/thor/templates/generator/empty_app/design/v1/resources/.gitkeep +0 -0
  98. data/tasks/thor/templates/generator/empty_app/spec/spec_helper.rb +18 -0
  99. metadata +97 -6
  100. data/tasks/praxis_app_generator.thor +0 -307
@@ -1,3 +1,3 @@
1
1
  module Praxis
2
- VERSION = '0.10.1'
2
+ VERSION = '0.11pre'
3
3
  end
data/praxis.gemspec CHANGED
@@ -28,8 +28,10 @@ Gem::Specification.new do |spec|
28
28
  spec.add_dependency 'mime', '~> 0'
29
29
  spec.add_dependency 'praxis-mapper', '~> 3.1'
30
30
  spec.add_dependency 'praxis-blueprints', '~> 1.1'
31
- spec.add_dependency 'attributor', '~> 2.2'
31
+ spec.add_dependency 'attributor', '~> 2.3.0'
32
32
  spec.add_dependency 'thor', '~> 0'
33
+ spec.add_dependency 'terminal-table', '~> 1.4'
34
+ spec.add_dependency 'harness', '~> 2'
33
35
 
34
36
  spec.add_development_dependency 'bundler', '~> 1.6'
35
37
  spec.add_development_dependency 'rake', '~> 0.9'
@@ -47,4 +49,5 @@ Gem::Specification.new do |spec|
47
49
  spec.add_development_dependency 'simplecov', '~> 0'
48
50
  spec.add_development_dependency 'fuubar', '~> 2'
49
51
  spec.add_development_dependency 'yard', '~> 0'
52
+ spec.add_development_dependency 'sqlite3', '~> 1'
50
53
  end
@@ -1,17 +1,32 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe 'Functional specs' , focus: true do
3
+ describe 'Functional specs' do
4
4
 
5
5
  def app
6
6
  Praxis::Application.instance
7
7
  end
8
8
 
9
+ let(:session) { double("session", valid?: true)}
10
+
9
11
  context 'index' do
12
+
10
13
  context 'with an incorrect response_content_type param' do
14
+ around do |example|
15
+ logger = app.logger
16
+ app.logger = Logger.new(StringIO.new)
17
+
18
+ example.call
19
+
20
+ app.logger = logger
21
+ end
22
+
11
23
  it 'fails to validate the response' do
12
- get '/clouds/1/instances?response_content_type=somejunk&api_version=1.0', nil, 'HTTP_FOO' => "bar"
24
+ get '/clouds/1/instances?response_content_type=somejunk&api_version=1.0', nil, 'HTTP_FOO' => "bar", 'global_session' => session
25
+ expect(last_response.status).to eq(400)
13
26
  response = JSON.parse(last_response.body)
14
- expect(response['name']).to eq('Praxis::Exceptions::Validation')
27
+
28
+ expect(response['name']).to eq('ValidationError')
29
+
15
30
  expect(response["message"]).to match(/Bad Content-Type/)
16
31
  end
17
32
 
@@ -22,10 +37,10 @@ describe 'Functional specs' , focus: true do
22
37
  before do
23
38
  expect(Praxis::Application.instance.config).to receive(:praxis).and_return(praxis_config)
24
39
  end
25
-
40
+
26
41
  it 'fails to validate the response' do
27
42
  expect {
28
- get '/clouds/1/instances?response_content_type=somejunk&api_version=1.0'
43
+ get '/clouds/1/instances?response_content_type=somejunk&api_version=1.0',nil, 'global_session' => session
29
44
  }.to_not raise_error
30
45
  end
31
46
 
@@ -35,18 +50,20 @@ describe 'Functional specs' , focus: true do
35
50
  end
36
51
 
37
52
  it 'works' do
38
- get '/clouds/1/instances/2?junk=foo&api_version=1.0'
53
+ get '/clouds/1/instances/2?junk=foo&api_version=1.0', nil, 'global_session' => session
39
54
  expect(last_response.status).to eq(200)
40
- expect(JSON.parse(last_response.body)).to eq({"cloud_id" => 1, "id"=>2, "junk"=>"foo",
55
+ expect(JSON.parse(last_response.body)).to eq({"cloud_id" => 1, "id"=>2, "junk"=>"foo",
41
56
  "other_params"=>{
42
57
  "some_date"=>"2012-12-21T00:00:00+00:00",
43
- "fail_filter"=>nil},
58
+ "fail_filter"=>false},
44
59
  "payload"=>{"something"=>nil, "optional"=>"not given"}})
45
- expect(last_response.headers).to eq({"Content-Type"=>"application/vnd.acme.instance", "Content-Length"=>"213"})
46
- end
60
+ expect(last_response.headers).
61
+ to eq({"Content-Type"=>"application/vnd.acme.instance",
62
+ "Content-Length"=>"214", 'Spec-Middleware' => 'used'})
63
+ end
47
64
 
48
65
  it 'returns early when making the before filter break' do
49
- get '/clouds/1/instances/2?junk=foo&api_version=1.0&fail_filter=true'
66
+ get '/clouds/1/instances/2?junk=foo&api_version=1.0&fail_filter=true', nil, 'global_session' => session
50
67
  expect(last_response.status).to eq(401)
51
68
  end
52
69
 
@@ -66,7 +83,7 @@ describe 'Functional specs' , focus: true do
66
83
  let(:body) { form.body.to_s }
67
84
 
68
85
  it 'works' do
69
- post '/clouds/1/instances?api_version=1.0', body, 'CONTENT_TYPE' => content_type
86
+ post '/clouds/1/instances?api_version=1.0', body, 'CONTENT_TYPE' => content_type, 'global_session' => session
70
87
 
71
88
  _reponse_preamble, response = Praxis::MultipartParser.parse(last_response.headers, last_response.body)
72
89
  expect(response).to have(1).item
@@ -103,7 +120,7 @@ describe 'Functional specs' , focus: true do
103
120
 
104
121
  context 'with a valid payload' do
105
122
  before do
106
- post '/clouds/1/instances/2/files?api_version=1.0', body, 'CONTENT_TYPE' => content_type
123
+ post '/clouds/1/instances/2/files?api_version=1.0', body, 'CONTENT_TYPE' => content_type, 'global_session' => session
107
124
  end
108
125
 
109
126
  subject(:response) { JSON.parse(last_response.body) }
@@ -131,9 +148,9 @@ describe 'Functional specs' , focus: true do
131
148
  end
132
149
 
133
150
  let(:body) { form.body.to_s }
134
-
151
+
135
152
  it 'returns an error' do
136
- post '/clouds/1/instances/2/files?api_version=1.0', body, 'CONTENT_TYPE' => content_type
153
+ post '/clouds/1/instances/2/files?api_version=1.0', body, 'CONTENT_TYPE' => content_type, 'global_session' => session
137
154
  response = JSON.parse(last_response.body)
138
155
 
139
156
  expect(response['name']).to eq('ValidationError')
@@ -141,7 +158,7 @@ describe 'Functional specs' , focus: true do
141
158
  end
142
159
 
143
160
  end
144
-
161
+
145
162
  context 'with an extra key in the form' do
146
163
  let(:form) do
147
164
  form_data = MIME::Multipart::FormData.new
@@ -161,29 +178,32 @@ describe 'Functional specs' , focus: true do
161
178
 
162
179
  let(:body) { form.body.to_s }
163
180
  subject(:response) { JSON.parse(last_response.body) }
164
-
181
+
165
182
  before do
166
- post '/clouds/1/instances/2/files?api_version=1.0', body, 'CONTENT_TYPE' => content_type
183
+ post '/clouds/1/instances/2/files?api_version=1.0', body, 'CONTENT_TYPE' => content_type, 'global_session' => session
167
184
  end
168
185
  its(:keys){ should eq(['destination_path','file','options'])}
169
186
  its(['options']){ should eq({"extra_thing"=>"I am extra"})}
170
187
  end
171
188
 
189
+
190
+
191
+
172
192
  end
173
193
 
174
194
 
175
195
  context 'not found and API versions' do
176
196
  context 'when no version is speficied' do
177
197
  it 'it tells you which available api versions would match' do
178
- get '/clouds/1/instances/2?junk=foo'
179
-
198
+ get '/clouds/1/instances/2?junk=foo',nil, 'global_session' => session
199
+
180
200
  expect(last_response.status).to eq(404)
181
201
  expect(last_response.headers["Content-Type"]).to eq("text/plain")
182
202
  expect(last_response.body).to eq("NotFound. Your request did not specify an API version. Available versions = \"1.0\".")
183
203
  end
184
204
  it 'it just gives you a simple not found when nothing would have matched' do
185
- get '/foobar?junk=foo'
186
-
205
+ get '/foobar?junk=foo', nil, 'global_session' => session
206
+
187
207
  expect(last_response.status).to eq(404)
188
208
  expect(last_response.headers["Content-Type"]).to eq("text/plain")
189
209
  expect(last_response.body).to eq("NotFound")
@@ -192,40 +212,75 @@ describe 'Functional specs' , focus: true do
192
212
 
193
213
  context 'when some version is speficied, but wrong' do
194
214
  it 'it tells you which possible correcte api versions exist' do
195
- get '/clouds/1/instances/2?junk=foo&api_version=50.0'
196
-
215
+ get '/clouds/1/instances/2?junk=foo&api_version=50.0', nil, 'global_session' => session
216
+
197
217
  expect(last_response.status).to eq(404)
198
218
  expect(last_response.headers["Content-Type"]).to eq("text/plain")
199
219
  expect(last_response.body).to eq("NotFound. Your request speficied API version = \"50.0\". Available versions = \"1.0\".")
200
220
  end
201
221
  end
202
-
222
+
203
223
  end
204
224
 
205
225
  context 'volumes' do
206
226
  context 'when no authorization header is passed' do
207
227
  it 'works as expected' do
208
- get '/volumes/123?api_version=1.0&junk=stuff'#, nil, 'AUTHORIZATION' => 'foobar'
228
+ get '/v1.0/volumes/123?junk=stuff', nil, 'global_session' => session
209
229
  expect(last_response.status).to eq(200)
210
230
  expect(JSON.parse(last_response.body)).to eq({"id"=>123,
211
231
  "other_params"=>{
212
232
  "junk"=>"stuff",
213
- "some_date"=>"2012-12-21T00:00:00+00:00"}
214
- })
233
+ "some_date"=>"2012-12-21T00:00:00+00:00"}
234
+ })
215
235
  expect(last_response.headers["Content-Type"]).to eq("application/vnd.acme.volume")
216
- end
236
+ end
217
237
  end
218
238
  context 'when an authorization header is passed' do
219
239
  it 'returns 401 when it does not match "secret" ' do
220
- get '/volumes/123?api_version=1.0&junk=stuff', nil, 'HTTP_AUTHORIZATION' => 'foobar'
240
+ get '/v1.0/volumes/123?junk=stuff', nil, 'HTTP_AUTHORIZATION' => 'foobar', 'global_session' => session
221
241
  expect(last_response.status).to eq(401)
222
242
  expect(last_response.body).to match(/Authentication info is invalid/)
223
243
  end
224
244
  it 'succeeds as expected when it matches "secret" ' do
225
- get '/volumes/123?api_version=1.0&junk=stuff', nil, 'HTTP_AUTHORIZATION' => 'the secret'
245
+ get '/v1.0/volumes/123?junk=stuff', nil, 'HTTP_AUTHORIZATION' => 'the secret', 'global_session' => session
246
+ expect(last_response.status).to eq(200)
247
+ end
248
+
249
+ end
250
+
251
+ context 'index action with no args defined' do
252
+ it 'dispatches successfully' do
253
+ get '/v1.0/volumes', nil, 'HTTP_AUTHORIZATION' => 'the secret', 'global_session' => session
226
254
  expect(last_response.status).to eq(200)
227
255
  end
228
-
229
256
  end
230
257
  end
258
+
259
+ context 'auth_plugin' do
260
+ it 'can terminate' do
261
+ post '/clouds/23/instances/1/terminate?api_version=1.0', nil, 'global_session' => session
262
+ expect(last_response.status).to eq(200)
263
+ end
264
+
265
+ it 'can not stop' do
266
+ post '/clouds/23/instances/1/stop?api_version=1.0', nil, 'global_session' => session
267
+ expect(last_response.status).to eq(403)
268
+ end
269
+ end
270
+
271
+ context 'with mismatch between Content-Type and payload' do
272
+ let(:body) { '{}' }
273
+ let(:content_type) { 'application/x-www-form-urlencoded' }
274
+
275
+ before do
276
+ post '/clouds/1/instances/2/terminate?api_version=1.0', body, 'CONTENT_TYPE' => content_type, 'global_session' => session
277
+ end
278
+
279
+ it 'returns a useful error message' do
280
+ body = JSON.parse(last_response.body)
281
+ expect(body['name']).to eq('ValidationError')
282
+ expect(body['message']).to match("For request Content-Type: 'application/x-www-form-urlencoded'")
283
+ end
284
+ end
285
+
231
286
  end
@@ -12,14 +12,16 @@ describe Praxis::ActionDefinition do
12
12
  media_type 'application/json'
13
13
  version '1.0'
14
14
  routing { prefix '/api/hello_world' }
15
- payload { attribute :inherited, String }
16
- headers { header "Inherited", String }
17
- params { attribute :inherited, String }
15
+ action_defaults do
16
+ payload { attribute :inherited, String }
17
+ headers { header "Inherited", String }
18
+ params { attribute :inherited, String }
19
+ end
18
20
  end
19
21
  end
20
22
 
21
23
  subject(:action) do
22
- described_class.new('foo', resource_definition) do
24
+ Praxis::ActionDefinition.new('foo', resource_definition) do
23
25
  routing { get '/:one' }
24
26
  payload { attribute :two, String }
25
27
  headers { header "X_REQUESTED_WITH", 'XMLHttpRequest' }
@@ -44,7 +46,7 @@ describe Praxis::ActionDefinition do
44
46
  action.response :ok
45
47
  action.response :internal_server_error
46
48
  end
47
-
49
+
48
50
  it { should be_kind_of Hash }
49
51
  it { should include :ok }
50
52
  it { should include :internal_server_error }
@@ -64,9 +66,8 @@ describe Praxis::ActionDefinition do
64
66
  attribute :more, Attributor::Integer
65
67
  end
66
68
 
67
- expect(subject.params.attributes.keys).to match_array([
68
- :one, :inherited, :more
69
- ])
69
+ attributes = subject.params.attributes.keys
70
+ expect(attributes).to match_array([:one, :inherited, :more])
70
71
  end
71
72
  end
72
73
 
@@ -77,18 +78,18 @@ describe Praxis::ActionDefinition do
77
78
  end
78
79
 
79
80
  expect(subject.payload.attributes.keys).to match_array([
80
- :two, :inherited, :more
81
+ :two, :inherited, :more
81
82
  ])
82
83
  end
83
84
  end
84
85
 
85
86
  describe '#headers' do
86
- it 'is backed by a Hash' do
87
+ it 'is backed by a Hash' do
87
88
  expect(subject.headers.type < Attributor::Hash).to be(true)
88
89
  end
89
90
 
90
91
  it 'is has case_sensitive_load enabled' do
91
- expect(subject.headers.type.options[:case_insensitive_load]).to be(true)
92
+ expect(subject.headers.type.options[:case_insensitive_load]).to be(true)
92
93
  end
93
94
 
94
95
  it 'merges in more headers' do
@@ -97,7 +98,7 @@ describe Praxis::ActionDefinition do
97
98
  end
98
99
 
99
100
  expect(subject.headers.attributes.keys).to match_array([
100
- "X_REQUESTED_WITH", "Inherited", "more"
101
+ "X_REQUESTED_WITH", "Inherited", "more"
101
102
  ])
102
103
  end
103
104
  end
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  describe Praxis::Bootloader do
4
4
  let(:application) do
5
- instance_double("Praxis::Application", config: "config", root: "root", plugins: [])
5
+ instance_double("Praxis::Application", config: "config", root: "root", plugins: {})
6
6
  end
7
7
 
8
8
  subject(:bootloader) {Praxis::Bootloader.new(application)}
@@ -10,7 +10,7 @@ describe Praxis::Bootloader do
10
10
  context 'attributes' do
11
11
  its(:application) {should be(application)}
12
12
  it 'stages' do
13
- init_stages = [:environment, :initializers, :lib, :design, :app, :routing, :warn_unloaded_files]
13
+ init_stages = [:environment, :plugins, :initializers, :lib, :design, :app, :routing, :warn_unloaded_files]
14
14
  expect(bootloader.stages.map {|s| s.name}).to eq(init_stages)
15
15
  end
16
16
  its(:config) {should be(application.config)}
@@ -49,10 +49,17 @@ describe Praxis::Bootloader do
49
49
  end
50
50
 
51
51
  context ".use" do
52
+ let(:plugin) do
53
+ Class.new(Praxis::Plugin) do
54
+ def config_key
55
+ :foo
56
+ end
57
+ end
58
+ end
59
+
52
60
  it "plugin add to application" do
53
- bootloader.use(Praxis::Plugin)
54
- new_plugin = Praxis::Plugin.new(bootloader.application)
55
- expect(bootloader.application.plugins.last.class).to be(Praxis::Plugin)
61
+ bootloader.use(plugin)
62
+ expect(bootloader.application.plugins[:foo].class).to be(plugin)
56
63
  end
57
64
  end
58
65
  end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe Praxis::Notifications do
4
+
5
+ let(:events) { [] }
6
+
7
+ before do
8
+ Praxis::Notifications.subscribe('render') do |name, start, finish, id, payload|
9
+ events << payload
10
+ end
11
+
12
+ Praxis::Notifications.instrument('render', extra: :information) do
13
+ end
14
+
15
+ Praxis::Notifications.instrument('render', extra: :single)
16
+ end
17
+
18
+ it 'works' do
19
+ expect(events).to have(2).items
20
+ expect(events).to match [{extra: :information}, {extra: :single}]
21
+ end
22
+
23
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ describe Praxis::PluginConcern do
4
+
5
+ it 'works' do
6
+ expect(Praxis::Request.instance_methods).to include(:user_abilities)
7
+ end
8
+
9
+ context 'ActionDefinition' do
10
+ subject(:action) { ApiResources::Instances.actions[:terminate] }
11
+ its(:required_abilities) { should match_array [:terminate, :read] }
12
+
13
+ context '#describe' do
14
+ subject(:describe) { action.describe }
15
+ it { should have_key :required_abilities}
16
+ its([:required_abilities]) { should match_array action.required_abilities}
17
+ end
18
+
19
+ end
20
+
21
+ end
@@ -31,8 +31,63 @@ describe Praxis::Request do
31
31
  its(:path) { should eq('/instances/1') }
32
32
  its(:raw_params) { should eq({'junk' => 'foo'}) }
33
33
  its(:version) { should eq('1.0') }
34
+
35
+ context 'path versioning' do
36
+ its('class.path_version_prefix'){ should eq("/v") }
37
+ its(:path_version_matcher){ should be_kind_of(Regexp) }
38
+ it 'uses a "/v*" default matcher with a "version" named capture' do
39
+ match = subject.path_version_matcher.match("/v5.5/something")
40
+ expect(match).to_not be(nil)
41
+ expect(match['version']).to eq("5.5")
42
+ end
43
+ end
44
+
45
+ context 'loading api version' do
46
+ let(:request) { Praxis::Request.new(env) }
47
+ subject(:version){ request.version( version_options ) }
48
+ context 'using X-Api-Header' do
49
+ let(:env){ {'HTTP_X_API_VERSION' => "5.0", "PATH_INFO" => "/something"} }
50
+ let(:version_options){ {using: :header} }
51
+ it { should eq('5.0') }
52
+ end
53
+ context 'using query param' do
54
+ let(:env) { Rack::MockRequest.env_for('/instances/1?junk=foo&api_version=5.0') }
55
+ let(:version_options){ {using: :params} }
56
+ it { should eq('5.0') }
57
+ end
58
+ context 'using path (with the default pattern matcher)' do
59
+ let(:env){ Rack::MockRequest.env_for('/v5.0/instances/1?junk=foo') }
60
+ let(:version_options){ {using: :path} }
61
+ it { should eq('5.0') }
62
+ end
63
+
64
+ context 'using a method that it is not allowed in the definition' do
65
+ context 'allowing query param but passing it through a header' do
66
+ let(:env){ {'HTTP_X_API_VERSION' => "5.0", "PATH_INFO" => "/something"} }
67
+ let(:version_options){ {using: :params} }
68
+ it { should eq('n/a') }
69
+ end
70
+ context 'allowing header but passing it through param' do
71
+ let(:env) { Rack::MockRequest.env_for('/instances/1?junk=foo&api_version=5.0') }
72
+ let(:version_options){ {using: :header} }
73
+ it { should eq('n/a') }
74
+ end
75
+ end
76
+
77
+ context 'using defaults' do
78
+ subject(:version){ request.version }
79
+ context 'would succeed if passed through the header' do
80
+ let(:env){ {'HTTP_X_API_VERSION' => "5.0", "PATH_INFO" => "/something"} }
81
+ it { should eq('5.0') }
82
+ end
83
+ context 'would succeed if passed through params' do
84
+ let(:env) { Rack::MockRequest.env_for('/instances/1?junk=foo&api_version=5.0') }
85
+ it { should eq('5.0') }
86
+ end
87
+ end
88
+ end
34
89
 
35
- context 'with a multipart requset' do
90
+ context 'with a multipart request' do
36
91
  let(:form) do
37
92
  form_data = MIME::Multipart::FormData.new
38
93