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.
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/'))