praxis 0.9 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -0
- data/CHANGELOG.md +51 -0
- data/CONTRIBUTING.md +3 -2
- data/Gemfile +1 -1
- data/README.md +5 -3
- data/lib/api_browser/app/css/main.css +4 -0
- data/lib/api_browser/app/js/directives/attribute_description.js +17 -14
- data/lib/api_browser/app/js/directives/type_label.js +7 -7
- data/lib/api_browser/app/sass/modules/_body.scss +8 -1
- data/lib/api_browser/app/views/directives/attribute_description/_example.html +9 -4
- data/lib/api_browser/app/views/directives/attribute_table_row/_links.html +4 -3
- data/lib/api_browser/app/views/resource/_actions.html +11 -5
- data/lib/praxis/action_definition/headers_dsl_compiler.rb +6 -6
- data/lib/praxis/action_definition.rb +19 -4
- data/lib/praxis/api_definition.rb +3 -3
- data/lib/praxis/application.rb +1 -0
- data/lib/praxis/bootloader_stages/routing.rb +3 -3
- data/lib/praxis/bootloader_stages/warn_unloaded_files.rb +1 -0
- data/lib/praxis/callbacks.rb +35 -0
- data/lib/praxis/controller.rb +18 -35
- data/lib/praxis/file_group.rb +12 -7
- data/lib/praxis/links.rb +4 -0
- data/lib/praxis/media_type_collection.rb +21 -15
- data/lib/praxis/request.rb +2 -7
- data/lib/praxis/request_stages/action.rb +10 -7
- data/lib/praxis/request_stages/request_stage.rb +45 -10
- data/lib/praxis/request_stages/validate.rb +7 -3
- data/lib/praxis/responses/http.rb +0 -24
- data/lib/praxis/responses/internal_server_error.rb +40 -0
- data/lib/praxis/responses/validation_error.rb +1 -0
- data/lib/praxis/version.rb +1 -1
- data/lib/praxis.rb +5 -24
- data/praxis.gemspec +3 -3
- data/spec/functional_spec.rb +62 -17
- data/spec/praxis/action_definition_spec.rb +14 -6
- data/spec/praxis/callbacks_spec.rb +107 -0
- data/spec/praxis/controller_spec.rb +0 -38
- data/spec/praxis/file_group_spec.rb +5 -0
- data/spec/praxis/media_type_collection_spec.rb +20 -7
- data/spec/praxis/request_spec.rb +15 -1
- data/spec/praxis/request_stage_spec.rb +93 -0
- data/spec/praxis/request_stages_action_spec.rb +61 -0
- data/spec/praxis/request_stages_validate_spec.rb +1 -1
- data/spec/praxis/responses/internal_server_error_spec.rb +51 -0
- data/spec/praxis/responses/validation_error_spec.rb +66 -0
- data/spec/praxis/router_spec.rb +5 -1
- data/spec/spec_app/app/concerns/authenticated.rb +15 -0
- data/spec/spec_app/app/concerns/basic_api.rb +12 -0
- data/spec/spec_app/app/concerns/log_wrapper.rb +14 -0
- data/spec/spec_app/app/controllers/base_class.rb +14 -0
- data/spec/spec_app/app/controllers/instances.rb +31 -5
- data/spec/spec_app/app/controllers/volumes.rb +17 -0
- data/spec/spec_app/config/environment.rb +1 -0
- data/spec/spec_app/design/api.rb +3 -3
- data/spec/spec_app/design/media_types/volume.rb +1 -1
- data/spec/spec_app/design/media_types/volume_snapshot.rb +1 -0
- data/spec/spec_app/design/resources/instances.rb +10 -3
- data/spec/spec_app/design/resources/volumes.rb +27 -0
- data/spec/support/spec_resource_definitions.rb +32 -0
- data/tasks/praxis_app_generator.thor +2 -2
- metadata +23 -9
data/lib/praxis/request.rb
CHANGED
@@ -73,13 +73,8 @@ module Praxis
|
|
73
73
|
|
74
74
|
def load_headers(context)
|
75
75
|
return unless action.headers
|
76
|
-
defined_headers = action.
|
77
|
-
|
78
|
-
name.to_s
|
79
|
-
else
|
80
|
-
"HTTP_#{name}"
|
81
|
-
end
|
82
|
-
hash[name] = self.env[env_name] if self.env.has_key? env_name
|
76
|
+
defined_headers = action.precomputed_header_keys_for_rack.each_with_object(Hash.new) do |(upper,original), hash|
|
77
|
+
hash[original] = self.env[upper] if self.env.has_key? upper
|
83
78
|
end
|
84
79
|
self.headers = action.headers.load(defined_headers, context)
|
85
80
|
end
|
@@ -1,19 +1,22 @@
|
|
1
1
|
module Praxis
|
2
2
|
module RequestStages
|
3
3
|
|
4
|
-
class Action < RequestStage
|
5
|
-
|
4
|
+
class Action < RequestStage
|
5
|
+
|
6
6
|
def execute
|
7
7
|
response = controller.send(action.name, **request.params_hash)
|
8
|
-
|
8
|
+
case response
|
9
|
+
when String
|
9
10
|
controller.response.body = response
|
10
|
-
|
11
|
+
when Praxis::Response
|
11
12
|
controller.response = response
|
12
|
-
|
13
|
-
|
13
|
+
else
|
14
|
+
raise "Action #{action.name} in #{controller.class} returned #{response.inspect}. Only Response objects or Strings allowed."
|
15
|
+
end
|
14
16
|
controller.response.request = request
|
17
|
+
nil # Action cannot return its OK request, as it would indicate the end of the stage chain
|
15
18
|
end
|
16
|
-
|
19
|
+
|
17
20
|
end
|
18
21
|
|
19
22
|
end
|
@@ -1,6 +1,9 @@
|
|
1
1
|
module Praxis
|
2
2
|
module RequestStages
|
3
3
|
|
4
|
+
# Special Stage what will hijack the run and execute methods to:
|
5
|
+
# 1- Run specific controller callbacks (in addition to any normal callbacks)
|
6
|
+
# 2- Shortcut the controller callback chain if any returns a Response object
|
4
7
|
class RequestStage < Stage
|
5
8
|
extend Forwardable
|
6
9
|
|
@@ -17,32 +20,64 @@ module Praxis
|
|
17
20
|
if conditions.has_key?(:actions)
|
18
21
|
next unless conditions[:actions].include? action.name
|
19
22
|
end
|
20
|
-
block.call(controller)
|
23
|
+
result = block.call(controller)
|
24
|
+
return result if result && result.kind_of?(Praxis::Response)
|
21
25
|
end
|
22
26
|
end
|
27
|
+
nil
|
23
28
|
end
|
24
|
-
|
29
|
+
|
25
30
|
def run
|
26
31
|
setup!
|
27
32
|
setup_deferred_callbacks!
|
33
|
+
|
28
34
|
execute_callbacks(self.before_callbacks)
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
35
|
+
# Shortcut lifecycle if filters return a response (non-nil but non-response-class response is ignored)
|
36
|
+
r = execute_controller_callbacks(controller.class.before_callbacks)
|
37
|
+
return r if r
|
38
|
+
|
39
|
+
result = execute_with_around
|
40
|
+
# Still allow the after callbacks to shortcut it if necessary.
|
41
|
+
r = execute_controller_callbacks(controller.class.after_callbacks)
|
42
|
+
return r if r
|
43
|
+
execute_callbacks(self.after_callbacks)
|
33
44
|
|
34
45
|
result
|
35
46
|
end
|
36
47
|
|
37
|
-
|
48
|
+
def execute_with_around
|
49
|
+
cb = controller.class.around_callbacks[ path ]
|
50
|
+
if cb == nil || cb.empty?
|
51
|
+
execute
|
52
|
+
else
|
53
|
+
inner_proc = proc { execute }
|
54
|
+
|
55
|
+
applicable = cb.select do |(conditions, handler)|
|
56
|
+
if conditions.has_key?(:actions)
|
57
|
+
(conditions[:actions].include? action.name) ? true : false
|
58
|
+
else
|
59
|
+
true
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
chain = applicable.reverse.inject(inner_proc) do |blk, (conditions, handler)|
|
64
|
+
if blk
|
65
|
+
proc{ handler.call(controller,blk) }
|
66
|
+
else
|
67
|
+
proc{ handler.call }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
chain.call
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
38
74
|
def execute
|
39
75
|
raise NotImplementedError, 'Subclass must implement Stage#execute' unless @stages.any?
|
40
76
|
|
41
77
|
@stages.each do |stage|
|
42
|
-
|
43
|
-
return
|
78
|
+
shortcut = stage.run
|
79
|
+
return shortcut if shortcut && shortcut.kind_of?(Praxis::Response)
|
44
80
|
end
|
45
|
-
|
46
81
|
nil
|
47
82
|
end
|
48
83
|
|
@@ -3,9 +3,13 @@ module Praxis
|
|
3
3
|
|
4
4
|
class Validate < RequestStage
|
5
5
|
|
6
|
-
def
|
7
|
-
|
8
|
-
|
6
|
+
def initialize(name, context,**opts)
|
7
|
+
super
|
8
|
+
# Add our sub-stages
|
9
|
+
@stages = [
|
10
|
+
RequestStages::ValidateParamsAndHeaders.new(:params_and_headers, context, parent: self),
|
11
|
+
RequestStages::ValidatePayload.new(:payload, context, parent: self)
|
12
|
+
]
|
9
13
|
end
|
10
14
|
|
11
15
|
def execute
|
@@ -125,30 +125,6 @@ module Praxis
|
|
125
125
|
end
|
126
126
|
|
127
127
|
|
128
|
-
# A generic error message, given when an unexpected condition was encountered and no more specific message is suitable.
|
129
|
-
class InternalServerError < Praxis::Response
|
130
|
-
self.status = 500
|
131
|
-
|
132
|
-
attr_accessor :error
|
133
|
-
|
134
|
-
def initialize(error: nil, **opts)
|
135
|
-
super(**opts)
|
136
|
-
@error = error
|
137
|
-
end
|
138
|
-
|
139
|
-
def format!(exception = @error) #_exception(exception)
|
140
|
-
if @error
|
141
|
-
msg = {
|
142
|
-
name: exception.class.name,
|
143
|
-
message: exception.message,
|
144
|
-
backtrace: exception.backtrace
|
145
|
-
}
|
146
|
-
msg[:cause] = format!(exception.cause) if exception.cause
|
147
|
-
@body = msg
|
148
|
-
end
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
128
|
ApiDefinition.define do |api|
|
153
129
|
self.constants.each do |class_name|
|
154
130
|
response_class = self.const_get(class_name)
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Praxis
|
2
|
+
|
3
|
+
module Responses
|
4
|
+
|
5
|
+
# A generic error message, given when an unexpected condition was encountered and no more specific message is suitable.
|
6
|
+
class InternalServerError < Praxis::Response
|
7
|
+
self.status = 500
|
8
|
+
attr_accessor :error
|
9
|
+
|
10
|
+
def initialize(error: nil, **opts)
|
11
|
+
super(**opts)
|
12
|
+
@headers['Content-Type'] = 'application/json' #TODO: might want an error mediatype
|
13
|
+
@error = error
|
14
|
+
end
|
15
|
+
|
16
|
+
def format!(exception = @error) #_exception(exception)
|
17
|
+
if @error
|
18
|
+
msg = {
|
19
|
+
name: exception.class.name,
|
20
|
+
message: exception.message,
|
21
|
+
backtrace: exception.backtrace
|
22
|
+
}
|
23
|
+
msg[:cause] = format!(exception.cause) if exception.cause
|
24
|
+
@body = msg
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
ApiDefinition.define do |api|
|
32
|
+
api.response_template :internal_server_error do
|
33
|
+
description "When an internal server error occurs..."
|
34
|
+
status 500
|
35
|
+
media_type "application/json"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
data/lib/praxis/version.rb
CHANGED
data/lib/praxis.rb
CHANGED
@@ -30,6 +30,7 @@ module Praxis
|
|
30
30
|
autoload :Bootloader, 'praxis/bootloader'
|
31
31
|
autoload :Config, 'praxis/config'
|
32
32
|
autoload :Controller, 'praxis/controller'
|
33
|
+
autoload :Callbacks, 'praxis/callbacks'
|
33
34
|
autoload :Dispatcher, 'praxis/dispatcher'
|
34
35
|
autoload :Exception, 'praxis/exception'
|
35
36
|
autoload :FileGroup,'praxis/file_group'
|
@@ -70,30 +71,10 @@ module Praxis
|
|
70
71
|
autoload :Validation, 'praxis/exceptions/validation'
|
71
72
|
end
|
72
73
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
autoload :NoContent, 'praxis/responses/http'
|
78
|
-
autoload :MultipleChoices, 'praxis/responses/http'
|
79
|
-
autoload :MovedPermanently, 'praxis/responses/http'
|
80
|
-
autoload :Found, 'praxis/responses/http'
|
81
|
-
autoload :SeeOther, 'praxis/responses/http'
|
82
|
-
autoload :NotModified, 'praxis/responses/http'
|
83
|
-
autoload :TemporaryRedirect, 'praxis/responses/http'
|
84
|
-
autoload :BadRequest, 'praxis/responses/http'
|
85
|
-
autoload :Unauthorized, 'praxis/responses/http'
|
86
|
-
autoload :Forbidden, 'praxis/responses/http'
|
87
|
-
autoload :NotFound, 'praxis/responses/http'
|
88
|
-
autoload :MethodNotAllowed, 'praxis/responses/http'
|
89
|
-
autoload :NotAcceptable, 'praxis/responses/http'
|
90
|
-
autoload :Conflict, 'praxis/responses/http'
|
91
|
-
autoload :PreconditionFailed, 'praxis/responses/http'
|
92
|
-
autoload :UnprocessableEntity, 'praxis/responses/http'
|
93
|
-
autoload :InternalServerError, 'praxis/responses/http'
|
94
|
-
|
95
|
-
autoload :ValidationError, 'praxis/responses/validation_error'
|
96
|
-
end
|
74
|
+
# Avoid loading responses (and templates) lazily as they need to be registered in time
|
75
|
+
require 'praxis/responses/http'
|
76
|
+
require 'praxis/responses/internal_server_error'
|
77
|
+
require 'praxis/responses/validation_error'
|
97
78
|
|
98
79
|
module BootloaderStages
|
99
80
|
autoload :FileLoader, 'praxis/bootloader_stages/file_loader'
|
data/praxis.gemspec
CHANGED
@@ -23,12 +23,12 @@ Gem::Specification.new do |spec|
|
|
23
23
|
|
24
24
|
spec.add_dependency 'rack', '~> 1'
|
25
25
|
spec.add_dependency 'mustermann', '~> 0'
|
26
|
-
spec.add_dependency 'activesupport', '
|
26
|
+
spec.add_dependency 'activesupport', '>= 3'
|
27
27
|
spec.add_dependency 'ruport', '~> 1'
|
28
28
|
spec.add_dependency 'mime', '~> 0'
|
29
29
|
spec.add_dependency 'praxis-mapper', '~> 3.1'
|
30
|
-
spec.add_dependency 'praxis-blueprints', '~> 1.
|
31
|
-
spec.add_dependency 'attributor', '~> 2'
|
30
|
+
spec.add_dependency 'praxis-blueprints', '~> 1.1'
|
31
|
+
spec.add_dependency 'attributor', '~> 2.2'
|
32
32
|
spec.add_dependency 'thor', '~> 0'
|
33
33
|
|
34
34
|
spec.add_development_dependency 'bundler', '~> 1.6'
|
data/spec/functional_spec.rb
CHANGED
@@ -9,7 +9,7 @@ describe 'Functional specs' , focus: true do
|
|
9
9
|
context 'index' do
|
10
10
|
context 'with an incorrect response_content_type param' do
|
11
11
|
it 'fails to validate the response' do
|
12
|
-
get '/clouds/1/instances?response_content_type=somejunk&api_version=1.0'
|
12
|
+
get '/clouds/1/instances?response_content_type=somejunk&api_version=1.0', nil, 'HTTP_FOO' => "bar"
|
13
13
|
response = JSON.parse(last_response.body)
|
14
14
|
expect(response['name']).to eq('Praxis::Exceptions::Validation')
|
15
15
|
expect(response["message"]).to match(/Bad Content-Type/)
|
@@ -37,10 +37,18 @@ describe 'Functional specs' , focus: true do
|
|
37
37
|
it 'works' do
|
38
38
|
get '/clouds/1/instances/2?junk=foo&api_version=1.0'
|
39
39
|
expect(last_response.status).to eq(200)
|
40
|
-
expect(JSON.parse(last_response.body)).to eq({"cloud_id" => 1, "id"=>2, "junk"=>"foo",
|
41
|
-
|
40
|
+
expect(JSON.parse(last_response.body)).to eq({"cloud_id" => 1, "id"=>2, "junk"=>"foo",
|
41
|
+
"other_params"=>{
|
42
|
+
"some_date"=>"2012-12-21T00:00:00+00:00",
|
43
|
+
"fail_filter"=>nil},
|
44
|
+
"payload"=>{"something"=>nil, "optional"=>"not given"}})
|
45
|
+
expect(last_response.headers).to eq({"Content-Type"=>"application/vnd.acme.instance", "Content-Length"=>"213"})
|
42
46
|
end
|
43
47
|
|
48
|
+
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'
|
50
|
+
expect(last_response.status).to eq(401)
|
51
|
+
end
|
44
52
|
|
45
53
|
context 'bulk_create multipart' do
|
46
54
|
|
@@ -112,41 +120,53 @@ describe 'Functional specs' , focus: true do
|
|
112
120
|
end
|
113
121
|
end
|
114
122
|
|
115
|
-
context 'with
|
116
|
-
|
117
|
-
|
118
|
-
|
123
|
+
context 'with a missing value in form' do
|
124
|
+
let(:form) do
|
125
|
+
form_data = MIME::Multipart::FormData.new
|
126
|
+
|
127
|
+
text = MIME::Text.new('DOCKER_HOST=tcp://127.0.0.1:2375')
|
128
|
+
form_data.add text, 'file', 'docker'
|
129
|
+
|
130
|
+
form_data
|
119
131
|
end
|
120
132
|
|
133
|
+
let(:body) { form.body.to_s }
|
134
|
+
|
121
135
|
it 'returns an error' do
|
122
136
|
post '/clouds/1/instances/2/files?api_version=1.0', body, 'CONTENT_TYPE' => content_type
|
123
137
|
response = JSON.parse(last_response.body)
|
138
|
+
|
124
139
|
expect(response['name']).to eq('ValidationError')
|
125
|
-
expect(response[
|
140
|
+
expect(response['errors']).to eq(["Attribute $.payload.key(\"destination_path\") is required"])
|
126
141
|
end
|
127
142
|
|
128
143
|
end
|
129
|
-
|
130
|
-
context 'with
|
144
|
+
|
145
|
+
context 'with an extra key in the form' do
|
131
146
|
let(:form) do
|
132
147
|
form_data = MIME::Multipart::FormData.new
|
133
148
|
|
149
|
+
destination_path = MIME::Text.new('/etc/defaults')
|
150
|
+
form_data.add destination_path, 'destination_path'
|
151
|
+
|
134
152
|
text = MIME::Text.new('DOCKER_HOST=tcp://127.0.0.1:2375')
|
135
153
|
form_data.add text, 'file', 'docker'
|
136
154
|
|
155
|
+
# TEST EXTRA KEYS USING THE MULTIPART FORM
|
156
|
+
other = MIME::Text.new('I am extra')
|
157
|
+
form_data.add other, 'extra_thing'
|
158
|
+
|
137
159
|
form_data
|
138
160
|
end
|
139
161
|
|
140
162
|
let(:body) { form.body.to_s }
|
163
|
+
subject(:response) { JSON.parse(last_response.body) }
|
141
164
|
|
142
|
-
|
165
|
+
before do
|
143
166
|
post '/clouds/1/instances/2/files?api_version=1.0', body, 'CONTENT_TYPE' => content_type
|
144
|
-
response = JSON.parse(last_response.body)
|
145
|
-
|
146
|
-
expect(response['name']).to eq('ValidationError')
|
147
|
-
expect(response['errors']).to eq(["Attribute $.payload.get(\"destination_path\") is required"])
|
148
167
|
end
|
149
|
-
|
168
|
+
its(:keys){ should eq(['destination_path','file','options'])}
|
169
|
+
its(['options']){ should eq({"extra_thing"=>"I am extra"})}
|
150
170
|
end
|
151
171
|
|
152
172
|
end
|
@@ -179,8 +199,33 @@ describe 'Functional specs' , focus: true do
|
|
179
199
|
expect(last_response.body).to eq("NotFound. Your request speficied API version = \"50.0\". Available versions = \"1.0\".")
|
180
200
|
end
|
181
201
|
end
|
182
|
-
|
183
202
|
|
184
203
|
end
|
185
204
|
|
205
|
+
context 'volumes' do
|
206
|
+
context 'when no authorization header is passed' do
|
207
|
+
it 'works as expected' do
|
208
|
+
get '/volumes/123?api_version=1.0&junk=stuff'#, nil, 'AUTHORIZATION' => 'foobar'
|
209
|
+
expect(last_response.status).to eq(200)
|
210
|
+
expect(JSON.parse(last_response.body)).to eq({"id"=>123,
|
211
|
+
"other_params"=>{
|
212
|
+
"junk"=>"stuff",
|
213
|
+
"some_date"=>"2012-12-21T00:00:00+00:00"}
|
214
|
+
})
|
215
|
+
expect(last_response.headers["Content-Type"]).to eq("application/vnd.acme.volume")
|
216
|
+
end
|
217
|
+
end
|
218
|
+
context 'when an authorization header is passed' do
|
219
|
+
it 'returns 401 when it does not match "secret" ' do
|
220
|
+
get '/volumes/123?api_version=1.0&junk=stuff', nil, 'HTTP_AUTHORIZATION' => 'foobar'
|
221
|
+
expect(last_response.status).to eq(401)
|
222
|
+
expect(last_response.body).to match(/Authentication info is invalid/)
|
223
|
+
end
|
224
|
+
it 'succeeds as expected when it matches "secret" ' do
|
225
|
+
get '/volumes/123?api_version=1.0&junk=stuff', nil, 'HTTP_AUTHORIZATION' => 'the secret'
|
226
|
+
expect(last_response.status).to eq(200)
|
227
|
+
end
|
228
|
+
|
229
|
+
end
|
230
|
+
end
|
186
231
|
end
|
@@ -13,7 +13,7 @@ describe Praxis::ActionDefinition do
|
|
13
13
|
version '1.0'
|
14
14
|
routing { prefix '/api/hello_world' }
|
15
15
|
payload { attribute :inherited, String }
|
16
|
-
headers { header
|
16
|
+
headers { header "Inherited", String }
|
17
17
|
params { attribute :inherited, String }
|
18
18
|
end
|
19
19
|
end
|
@@ -22,7 +22,7 @@ describe Praxis::ActionDefinition do
|
|
22
22
|
described_class.new('foo', resource_definition) do
|
23
23
|
routing { get '/:one' }
|
24
24
|
payload { attribute :two, String }
|
25
|
-
headers { header
|
25
|
+
headers { header "X_REQUESTED_WITH", 'XMLHttpRequest' }
|
26
26
|
params { attribute :one, String }
|
27
27
|
end
|
28
28
|
end
|
@@ -34,8 +34,8 @@ describe Praxis::ActionDefinition do
|
|
34
34
|
its('params.attributes') { should have_key :inherited }
|
35
35
|
its('payload.attributes') { should have_key :two }
|
36
36
|
its('payload.attributes') { should have_key :inherited }
|
37
|
-
its('headers.attributes') { should have_key
|
38
|
-
its('headers.attributes') { should have_key
|
37
|
+
its('headers.attributes') { should have_key "X_REQUESTED_WITH" }
|
38
|
+
its('headers.attributes') { should have_key "Inherited" }
|
39
39
|
end
|
40
40
|
|
41
41
|
context '#responses' do
|
@@ -83,13 +83,21 @@ describe Praxis::ActionDefinition do
|
|
83
83
|
end
|
84
84
|
|
85
85
|
describe '#headers' do
|
86
|
+
it 'is backed by a Hash' do
|
87
|
+
expect(subject.headers.type < Attributor::Hash).to be(true)
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'is has case_sensitive_load enabled' do
|
91
|
+
expect(subject.headers.type.options[:case_insensitive_load]).to be(true)
|
92
|
+
end
|
93
|
+
|
86
94
|
it 'merges in more headers' do
|
87
95
|
subject.headers do
|
88
|
-
header
|
96
|
+
header "more"
|
89
97
|
end
|
90
98
|
|
91
99
|
expect(subject.headers.attributes.keys).to match_array([
|
92
|
-
|
100
|
+
"X_REQUESTED_WITH", "Inherited", "more"
|
93
101
|
])
|
94
102
|
end
|
95
103
|
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Praxis::Callbacks do
|
4
|
+
let(:controller){ double("controller", request: "request") }
|
5
|
+
|
6
|
+
subject do
|
7
|
+
Class.new {
|
8
|
+
include Praxis::Callbacks
|
9
|
+
|
10
|
+
# implements PeopleResource
|
11
|
+
|
12
|
+
before :validate, actions: [:index] do |controller|
|
13
|
+
"before"
|
14
|
+
end
|
15
|
+
|
16
|
+
before actions: [:show] do |controller|
|
17
|
+
end
|
18
|
+
|
19
|
+
after :response, actions: [:show] do |controller|
|
20
|
+
"after"
|
21
|
+
end
|
22
|
+
|
23
|
+
around :action , actions: [:foobar] do |controller, callee|
|
24
|
+
controller.request
|
25
|
+
callee.call
|
26
|
+
end
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
context '.before' do
|
31
|
+
let(:validate_conditions) { subject.before_callbacks[[:validate]][0][0] }
|
32
|
+
let(:validate_block) { subject.before_callbacks[[:validate]][0][1] }
|
33
|
+
|
34
|
+
it 'sets up the before_callbacks' do
|
35
|
+
expect(subject.before_callbacks.keys).to match_array([[:validate], [:action]])
|
36
|
+
expect(validate_conditions).to eq({:actions => [:index]})
|
37
|
+
expect(validate_block).to be_kind_of(Proc)
|
38
|
+
expect(validate_block.call(controller)).to eq("before")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context '.after' do
|
43
|
+
let(:response_conditions) { subject.after_callbacks[[:response]][0][0] }
|
44
|
+
let(:response_block) { subject.after_callbacks[[:response]][0][1] }
|
45
|
+
|
46
|
+
it 'sets up the after_callbacks' do
|
47
|
+
expect(subject.after_callbacks.keys).to match_array([[:response]])
|
48
|
+
expect(response_conditions).to eq({:actions => [:show]})
|
49
|
+
expect(response_block).to be_kind_of(Proc)
|
50
|
+
expect(response_block.call(controller)).to eq("after")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
context '.around' do
|
56
|
+
let(:callee_result){ "result" }
|
57
|
+
let(:callee){ double("callee", call: callee_result) }
|
58
|
+
let(:around_conditions) { subject.around_callbacks[[:action]][0][0] }
|
59
|
+
let(:around_block) { subject.around_callbacks[[:action]][0][1] }
|
60
|
+
|
61
|
+
it 'sets up the before_callbacks' do
|
62
|
+
expect(subject.around_callbacks.keys).to match_array([[:action]])
|
63
|
+
expect(around_conditions).to eq({:actions => [:foobar]})
|
64
|
+
expect(around_block).to be_kind_of(Proc)
|
65
|
+
end
|
66
|
+
it 'passes the right parameters to the block call' do
|
67
|
+
expect(controller).to receive(:request)
|
68
|
+
expect(callee).to receive(:call)
|
69
|
+
expect(around_block.call( controller,callee )).to eq(callee_result)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# DONT DO THIS...this is just a test to show what would happen
|
74
|
+
context 'inheriting callbacks from base classes ' do
|
75
|
+
let!(:child1) {
|
76
|
+
Class.new(subject) do
|
77
|
+
before actions: [:child1] do |controller|
|
78
|
+
"before show child1"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
}
|
82
|
+
let!(:child2) {
|
83
|
+
Class.new(subject) do
|
84
|
+
before actions: [:child2] do |controller|
|
85
|
+
"before show child2"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
}
|
89
|
+
|
90
|
+
describe '.before_callbacks (but same for after and around)' do
|
91
|
+
context 'for child1 (and viceversa for child2)' do
|
92
|
+
let(:inherited_callbacks){ child1.before_callbacks }
|
93
|
+
it "will inherits not just the base callbacks, but child2 also!" do
|
94
|
+
expect(inherited_callbacks).to be_a(Hash)
|
95
|
+
expect(inherited_callbacks.keys).to eq([[:validate],[:action]])
|
96
|
+
action_callbacks = inherited_callbacks[[:action]]
|
97
|
+
expect(action_callbacks).to have(3).items
|
98
|
+
action_callback_options = action_callbacks.map{|options, block| options}
|
99
|
+
expect(action_callback_options).to eq([{:actions=>[:show]},
|
100
|
+
{:actions=>[:child1]},
|
101
|
+
{:actions=>[:child2]}])
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
@@ -32,42 +32,4 @@ describe Praxis::Controller do
|
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
|
-
context '.actions' do
|
36
|
-
it 'gets the controller actions' do
|
37
|
-
expect(subject.actions.keys).to match_array([:index, :show])
|
38
|
-
expect(subject.actions[:index]).to be_kind_of(Praxis::ActionDefinition)
|
39
|
-
expect(subject.actions[:index].name).to eq(:index)
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
context '.action' do
|
44
|
-
it 'gets the index action of the controller' do
|
45
|
-
expect(subject.action(:index)).to be_kind_of(Praxis::ActionDefinition)
|
46
|
-
expect(subject.action(:index).name).to eq(:index)
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
context '.before' do
|
51
|
-
let(:validate_conditions) { subject.before_callbacks[[:validate]][0][0] }
|
52
|
-
let(:validate_block) { subject.before_callbacks[[:validate]][0][1] }
|
53
|
-
|
54
|
-
it 'sets up the before_callbacks' do
|
55
|
-
expect(subject.before_callbacks.keys).to match_array([[:validate], [:action]])
|
56
|
-
expect(validate_conditions).to eq({:actions => [:index]})
|
57
|
-
expect(validate_block).to be_kind_of(Proc)
|
58
|
-
expect(validate_block.call(*validate_conditions)).to eq("before")
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
context '.after' do
|
63
|
-
let(:response_conditions) { subject.after_callbacks[[:response]][0][0] }
|
64
|
-
let(:response_block) { subject.after_callbacks[[:response]][0][1] }
|
65
|
-
|
66
|
-
it 'sets up the after_callbacks' do
|
67
|
-
expect(subject.after_callbacks.keys).to match_array([[:response]])
|
68
|
-
expect(response_conditions).to eq({:actions => [:show]})
|
69
|
-
expect(response_block).to be_kind_of(Proc)
|
70
|
-
expect(response_block.call(*response_conditions)).to eq("after")
|
71
|
-
end
|
72
|
-
end
|
73
35
|
end
|
@@ -4,6 +4,11 @@ describe Praxis::FileGroup do
|
|
4
4
|
let(:app) { Praxis::Application.instance }
|
5
5
|
let(:layout) { app.file_layout }
|
6
6
|
|
7
|
+
context '#initialize' do
|
8
|
+
it 'raises an error if given nil for the base path' do
|
9
|
+
expect { Praxis::FileGroup.new(nil) }.to raise_error(ArgumentError)
|
10
|
+
end
|
11
|
+
end
|
7
12
|
context '#base' do
|
8
13
|
it 'returns the base path for the group' do
|
9
14
|
expect(layout[:design].base.to_s).to eq(File.join(app.root, 'design/'))
|