praxis 0.9 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -0
  3. data/CHANGELOG.md +51 -0
  4. data/CONTRIBUTING.md +3 -2
  5. data/Gemfile +1 -1
  6. data/README.md +5 -3
  7. data/lib/api_browser/app/css/main.css +4 -0
  8. data/lib/api_browser/app/js/directives/attribute_description.js +17 -14
  9. data/lib/api_browser/app/js/directives/type_label.js +7 -7
  10. data/lib/api_browser/app/sass/modules/_body.scss +8 -1
  11. data/lib/api_browser/app/views/directives/attribute_description/_example.html +9 -4
  12. data/lib/api_browser/app/views/directives/attribute_table_row/_links.html +4 -3
  13. data/lib/api_browser/app/views/resource/_actions.html +11 -5
  14. data/lib/praxis/action_definition/headers_dsl_compiler.rb +6 -6
  15. data/lib/praxis/action_definition.rb +19 -4
  16. data/lib/praxis/api_definition.rb +3 -3
  17. data/lib/praxis/application.rb +1 -0
  18. data/lib/praxis/bootloader_stages/routing.rb +3 -3
  19. data/lib/praxis/bootloader_stages/warn_unloaded_files.rb +1 -0
  20. data/lib/praxis/callbacks.rb +35 -0
  21. data/lib/praxis/controller.rb +18 -35
  22. data/lib/praxis/file_group.rb +12 -7
  23. data/lib/praxis/links.rb +4 -0
  24. data/lib/praxis/media_type_collection.rb +21 -15
  25. data/lib/praxis/request.rb +2 -7
  26. data/lib/praxis/request_stages/action.rb +10 -7
  27. data/lib/praxis/request_stages/request_stage.rb +45 -10
  28. data/lib/praxis/request_stages/validate.rb +7 -3
  29. data/lib/praxis/responses/http.rb +0 -24
  30. data/lib/praxis/responses/internal_server_error.rb +40 -0
  31. data/lib/praxis/responses/validation_error.rb +1 -0
  32. data/lib/praxis/version.rb +1 -1
  33. data/lib/praxis.rb +5 -24
  34. data/praxis.gemspec +3 -3
  35. data/spec/functional_spec.rb +62 -17
  36. data/spec/praxis/action_definition_spec.rb +14 -6
  37. data/spec/praxis/callbacks_spec.rb +107 -0
  38. data/spec/praxis/controller_spec.rb +0 -38
  39. data/spec/praxis/file_group_spec.rb +5 -0
  40. data/spec/praxis/media_type_collection_spec.rb +20 -7
  41. data/spec/praxis/request_spec.rb +15 -1
  42. data/spec/praxis/request_stage_spec.rb +93 -0
  43. data/spec/praxis/request_stages_action_spec.rb +61 -0
  44. data/spec/praxis/request_stages_validate_spec.rb +1 -1
  45. data/spec/praxis/responses/internal_server_error_spec.rb +51 -0
  46. data/spec/praxis/responses/validation_error_spec.rb +66 -0
  47. data/spec/praxis/router_spec.rb +5 -1
  48. data/spec/spec_app/app/concerns/authenticated.rb +15 -0
  49. data/spec/spec_app/app/concerns/basic_api.rb +12 -0
  50. data/spec/spec_app/app/concerns/log_wrapper.rb +14 -0
  51. data/spec/spec_app/app/controllers/base_class.rb +14 -0
  52. data/spec/spec_app/app/controllers/instances.rb +31 -5
  53. data/spec/spec_app/app/controllers/volumes.rb +17 -0
  54. data/spec/spec_app/config/environment.rb +1 -0
  55. data/spec/spec_app/design/api.rb +3 -3
  56. data/spec/spec_app/design/media_types/volume.rb +1 -1
  57. data/spec/spec_app/design/media_types/volume_snapshot.rb +1 -0
  58. data/spec/spec_app/design/resources/instances.rb +10 -3
  59. data/spec/spec_app/design/resources/volumes.rb +27 -0
  60. data/spec/support/spec_resource_definitions.rb +32 -0
  61. data/tasks/praxis_app_generator.thor +2 -2
  62. metadata +23 -9
@@ -73,13 +73,8 @@ module Praxis
73
73
 
74
74
  def load_headers(context)
75
75
  return unless action.headers
76
- defined_headers = action.headers.attributes.keys.each_with_object(Hash.new) do |name, hash|
77
- env_name = if name == :CONTENT_TYPE || name == :CONTENT_LENGTH
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
- if response.kind_of? String
8
+ case response
9
+ when String
9
10
  controller.response.body = response
10
- else
11
+ when Praxis::Response
11
12
  controller.response = response
12
- end
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
- execute_controller_callbacks(controller.class.before_callbacks)
30
- result = execute
31
- execute_controller_callbacks(controller.class.after_callbacks)
32
- execute_callbacks(self.after_callbacks)
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
- result = stage.run
43
- return result if result.kind_of?(Praxis::Response)
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 setup!
7
- @stages << RequestStages::ValidateParamsAndHeaders.new(:params_and_headers, context, parent: self)
8
- @stages << RequestStages::ValidatePayload.new(:payload, context, parent: self)
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
+
@@ -5,6 +5,7 @@ module Praxis
5
5
  class ValidationError < BadRequest
6
6
  def initialize(errors: nil, exception: nil, **opts)
7
7
  super(**opts)
8
+ @headers['Content-Type'] = 'application/json' #TODO: might want an error mediatype
8
9
  @errors = errors
9
10
  @exception = exception
10
11
  end
@@ -1,3 +1,3 @@
1
1
  module Praxis
2
- VERSION = '0.9'
2
+ VERSION = '0.10.0'
3
3
  end
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
- module Responses
74
- autoload :Ok, 'praxis/responses/http'
75
- autoload :Created, 'praxis/responses/http'
76
- autoload :Accepted, 'praxis/responses/http'
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', '~> 4'
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.0'
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'
@@ -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", "other_params"=>{"some_date"=>"2012-12-21T00:00:00+00:00"}, "payload"=>{"something"=>nil, "optional"=>"not given"}})
41
- expect(last_response.headers).to eq({"Content-Type"=>"application/vnd.acme.instance", "Content-Length"=>"188"})
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 unknown key in form' do
116
- before do
117
- junk = MIME::Text.new('junk_value')
118
- form.add junk, 'junk_name'
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["message"]).to match(/Unknown key received:/)
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 a missing value in form' do
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
- it 'returns an error' do
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 :inherited, String }
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 :X_REQUESTED_WITH, 'XMLHttpRequest' }
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 :X_REQUESTED_WITH }
38
- its('headers.attributes') { should have_key :INHERITED }
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 :more
96
+ header "more"
89
97
  end
90
98
 
91
99
  expect(subject.headers.attributes.keys).to match_array([
92
- :X_REQUESTED_WITH, :INHERITED, :MORE
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/'))