praxis 0.9 → 0.10.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.
- 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/'))
|