rspec-authorization 0.0.1 → 0.0.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d32c0f50faa40a57496f87a21d5ec3cdbf538f01
4
- data.tar.gz: 4ad22903e43240efdf66a025c70d9812bb97c8a2
3
+ metadata.gz: 79650ca2e93b08c5ebd9008fa9e6f51e59d5245c
4
+ data.tar.gz: 64f6364b28ef24e98d203b764165201ac2c32a33
5
5
  SHA512:
6
- metadata.gz: 68c08236ab63ec53595788a07de8256941934bf1993727d2edf002ab71efd6d6681060feb64ec4e96748a22a7d5457d4cf960d132a5d1d99744e53e7c8471da5
7
- data.tar.gz: 342c1b6aa908f713b56eba516821f47af14d78e0659c8ff8f8574e11143570d1940d2f31d2a30ace4e05c4f65f02d8d4ee5555fb947cd47b30dd0393ed715840
6
+ metadata.gz: f28d44b4dec4e86ade6980070bebcbfdb0b35a9daded58db14e539c086ba380959ab81aee49a09e30839cfb1d2fcc16138c05ebc4588d1b6ea9cde4d00971262
7
+ data.tar.gz: f026a6b7e065e16d10e53bad7bfdaf66838abe3952cf421739d113b3334ba76bc077529b2f5d6788f56491fa3e3aa32520f5db42f123b63e1c20ed4652129da8
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.4
4
+ before_script:
5
+ - bundle exec rake setup
data/.yardopts ADDED
@@ -0,0 +1,3 @@
1
+ -
2
+ HISTORY.md
3
+ LICENSE.txt
data/Gemfile CHANGED
@@ -8,7 +8,14 @@ group :development, :test do
8
8
  gem 'guard-rspec'
9
9
  gem 'pry'
10
10
  gem 'terminal-notifier-guard' if `uname` =~ /Darwin/
11
+ gem "codeclimate-test-reporter", require: nil
11
12
 
12
13
  gem 'jquery-rails'
13
14
  gem 'turbolinks'
14
15
  end
16
+
17
+ group :docs do
18
+ gem "inch"
19
+ gem "rdoc"
20
+ gem "yard"
21
+ end
data/Guardfile CHANGED
@@ -4,6 +4,7 @@ guard :rspec, cmd: 'bundle exec rspec' do
4
4
  watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
5
5
  watch(%r{^tools/(.+)\.rb$}) { |m| "spec/tools/#{m[1]}_spec.rb" }
6
6
 
7
- watch(%r{^controllers/(.+)\.rb$}) { |m| "spec/controllers/#{m[1]}_spec.rb" }
7
+ watch(%r{^lib/.+/matchers/.+\.rb$}) { "spec/controllers" }
8
+ watch(%r{^controllers/(.+)\.rb$}) { |m| "spec/controllers/#{m[1]}_spec.rb" }
8
9
  end
9
10
 
data/HISTORY.md ADDED
@@ -0,0 +1,32 @@
1
+
2
+ [v0.0.2 / 2014-11-11] (https://github.com/hendrauzia/rspec-authorization/tree/v0.0.2)
3
+ =====================
4
+
5
+ * Add RESTful methods matcher chaining
6
+ * Add object, callback and controller action stub
7
+ * Add documentation to have_permission_for matcher
8
+ * Add request documentation
9
+ * Refactor and add documentation to example
10
+ * Refactor and add documentation to example group
11
+ * Add documentation to gem requirement
12
+ * Add documentation to route
13
+ * Add yard as documentation generator
14
+ * Add documentation badge
15
+ * Add coverage, dependencies and security badges
16
+ * Fix rails test app spec
17
+ * Fix travis build
18
+ * Add travis badge and config
19
+ * Add gem version badge and usage
20
+
21
+ [v0.0.1 / 2014-11-05] (https://github.com/hendrauzia/rspec-authorization/tree/v0.0.1)
22
+ =====================
23
+
24
+ * Add have_permission_for matcher for restful actions
25
+ * Add rails test app template
26
+ * Add clean and reset rake task
27
+ * Update dependency to ruby 2.1.4
28
+ * Add rails test app for spec to run against
29
+ * Add rspec-rails for specs
30
+ * Add rspec-authorization gem scaffold using bundler
31
+ * Initial commit
32
+
data/README.md CHANGED
@@ -1,22 +1,75 @@
1
- # Rspec::Authorization
1
+ # RSpec::Authorization
2
2
 
3
- RSpec matcher for declarative_authorization. A neat way of asserting declarative_authorization's rules inside controller using RSpec matcher.
3
+ [![GitHub](http://img.shields.io/badge/github-hendrauzia/rspec--authorization-blue.svg)](http://github.com/hendrauzia/rspec-authorization)
4
+ [![Documentation](http://img.shields.io/badge/docs-rdoc.info-blue.svg)](http://rubydoc.org/github/hendrauzia/rspec-authorization)
5
+
6
+ [![Gem Version](https://badge.fury.io/rb/rspec-authorization.svg)](http://badge.fury.io/rb/rspec-authorization)
7
+ [![Build Status](https://travis-ci.org/hendrauzia/rspec-authorization.svg)](https://travis-ci.org/hendrauzia/rspec-authorization)
8
+ [![Code Climate](https://codeclimate.com/github/hendrauzia/rspec-authorization/badges/gpa.svg)](https://codeclimate.com/github/hendrauzia/rspec-authorization)
9
+ [![Test Coverage](https://codeclimate.com/github/hendrauzia/rspec-authorization/badges/coverage.svg)](https://codeclimate.com/github/hendrauzia/rspec-authorization)
10
+ [![Dependency Status](https://gemnasium.com/hendrauzia/rspec-authorization.svg)](https://gemnasium.com/hendrauzia/rspec-authorization)
11
+ [![security](https://hakiri.io/github/hendrauzia/rspec-authorization/master.svg)](https://hakiri.io/github/hendrauzia/rspec-authorization/master)
12
+ [![Inline docs](http://inch-ci.org/github/hendrauzia/rspec-authorization.svg?branch=master)](http://inch-ci.org/github/hendrauzia/rspec-authorization)
13
+
14
+ RSpec matcher for declarative_authorization. A neat way of asserting
15
+ declarative_authorization's rules inside controller using RSpec matcher.
4
16
 
5
17
  ## Installation
6
18
 
7
19
  Add this line to your application's Gemfile:
8
20
 
9
- ```ruby
10
- gem 'rspec-authorization'
11
- ```
21
+
22
+ gem 'rspec-authorization', group: :test
23
+
12
24
 
13
25
  And then execute:
14
26
 
15
- $ bundle
27
+ bundle
16
28
 
17
29
  Or install it yourself as:
18
30
 
19
- $ gem install rspec-authorization
31
+ gem install rspec-authorization
32
+
33
+ ## Requirement
34
+
35
+ Current development focus is as follows, future development may support other
36
+ dependencies, following are requirements for this gem:
37
+
38
+ - declarative_authorization 1.0.0.pre
39
+ - rails 4.x
40
+ - rspec-rails 3.x
41
+
42
+ ## Usage
43
+
44
+ In your controller spec:
45
+
46
+ describe ArticlesController do
47
+ it { is_expected.to have_permission_for(:a_role).to(:restful_action_name) }
48
+
49
+ it { is_expected.to have_permission_for(:writer).to(:index) }
50
+ it { is_expected.to have_permission_for(:writer).to(:show) }
51
+ it { is_expected.to have_permission_for(:writer).to(:new) }
52
+ it { is_expected.to have_permission_for(:writer).to(:create) }
53
+ it { is_expected.not_to have_permission_for(:writer).to(:edit) }
54
+ it { is_expected.not_to have_permission_for(:writer).to(:update) }
55
+ it { is_expected.not_to have_permission_for(:writer).to(:destroy) }
56
+ end
57
+
58
+ You can also use convenience RESTful methods matcher:
59
+
60
+ describe ArticlesController do
61
+ it { is_expected.to have_permission_for(:user).to_read }
62
+ it { is_expected.not_to have_permission_for(:user).to_create }
63
+ it { is_expected.not_to have_permission_for(:user).to_update }
64
+ it { is_expected.not_to have_permission_for(:user).to_delete }
65
+
66
+ it { is_expected.to have_permission_for(:writer).to_read }
67
+ it { is_expected.to have_permission_for(:writer).to_create }
68
+ it { is_expected.to have_permission_for(:writer).to_update }
69
+ it { is_expected.not_to have_permission_for(:writer).to_delete }
70
+
71
+ it { is_expected.to have_permission_for(:editor).to_manage }
72
+ end
20
73
 
21
74
  ## Contributing
22
75
 
@@ -1,27 +1,50 @@
1
1
  module RSpec::Authorization
2
2
  module Adapters
3
+ # Wrapper to generate and immediately run example from +RSpec::Core::Example+.
4
+ # The purpose of this class is to abstract the running of an example without
5
+ # unnecessary artifacts from an RSpec example, such as: reporter, generated
6
+ # description, context and expectation.
7
+ #
8
+ # The sole purpose of this class is to generate the minimum required component
9
+ # needed to create and run an example, for our matcher to run against. The trick
10
+ # to run the example without producing unnecessary artifacts is to trigger
11
+ # the example's before and after hook manually without running any expectations.
3
12
  class Example
4
- attr_reader :group, :example
13
+ # @return [Class] RSpec example group
14
+ attr_reader :group_target
15
+ # @return [RSpec::Core::Example] instance of RSpec example
16
+ attr_reader :target
5
17
 
6
- def initialize(group)
7
- @group = group
8
- @example = RSpec::Core::Example.new(group, "", {})
18
+ # Initialize example using RSpec example group. The RSpec example group can
19
+ # be retrieved using example group's target, consider the following example:
20
+ #
21
+ # group = ExampleGroup.new(ArticlesController)
22
+ # example = Example.new(group.target)
23
+ #
24
+ # @param group_target [Class] RSpec example group from +ExampleGroup#target+
25
+ # @see ExampleGroup#target
26
+ def initialize(group_target)
27
+ @group_target = group_target
28
+ @target = RSpec::Core::Example.new(group_target, "", {})
9
29
 
10
30
  set_example_group_instance
11
- end
12
-
13
- def run
14
31
  run_before_example
32
+ run_after_example
15
33
  end
16
34
 
17
35
  private
18
36
 
19
37
  def set_example_group_instance
20
- @example.instance_variable_set :@example_group_instance, group.new
38
+ target.instance_variable_set :@example_group_instance, group_target.new
21
39
  end
22
40
 
23
41
  def run_before_example
24
- @example.send :run_before_example
42
+ target.send :run_before_example
43
+ end
44
+
45
+ def run_after_example
46
+ target.example_group.hooks.run(:after, :example, target)
47
+ target.example_group_instance.teardown_mocks_for_rspec
25
48
  end
26
49
  end
27
50
  end
@@ -0,0 +1,78 @@
1
+ module RSpec::Authorization
2
+ module Adapters
3
+ # Wrapper to generate +RSpec::Core::ExampleGroup+. The example group
4
+ # includes +RSpec::Rails::ControllerExampleGroup+ to add behavior testing,
5
+ # which provides methods to test the controller, such as: +get+, +post+,
6
+ # +patch+, +put+ and +delete+.
7
+ #
8
+ # Consider the following example:
9
+ #
10
+ # group = ExampleGroup.new(ArticlesController)
11
+ # group.target # => RSpec::ExampleGroups::ArticlesController
12
+ #
13
+ # Instantiating the class for a second time produce a different result:
14
+ #
15
+ # group = ExampleGroup.new(ArticlesController)
16
+ # group.target # => RSpec::ExampleGroups::ArticlesController_2
17
+ class ExampleGroup
18
+ # @return [Class] a class namespaced from +RSPec::ExampleGroups+
19
+ attr_reader :target
20
+
21
+ # Initialize example group using controller's class.
22
+ #
23
+ # @param klass [Class] controller's class for an example group
24
+ def initialize(klass)
25
+ @target = RSpec::Core::ExampleGroup.describe(klass) do
26
+ include RSpec::Rails::ControllerExampleGroup
27
+ end
28
+ end
29
+
30
+ # Run example for our example group. The example is actualy has
31
+ # no example at all, it is simply a wrapper for +RSpec::Core::Example+,
32
+ # running an actual example would produce unnecessary artifacts, while all
33
+ # we need is a simple wrapper on controller behavior.
34
+ #
35
+ # @return [Example] example object that has been run
36
+ # @see Example
37
+ def run_example
38
+ Example.new(target)
39
+ end
40
+
41
+ # Add instruction to be run before our example. Instructions added will be
42
+ # run inside the context of our example group, consider the following
43
+ # example:
44
+ #
45
+ # before do
46
+ # get :index
47
+ # end
48
+ #
49
+ # The above statements behave exactly the same as RSpec +before+ method, and
50
+ # this method serve as a proxy to RSpec +before+ method call. That means
51
+ # everything that we can do inside +before+ method will be available inside
52
+ # +before+ to.
53
+ #
54
+ # @return [Array<RSpec::Core::Hooks::BeforeHook>] an array of before hook filter
55
+ def before(&block)
56
+ target.before(&block)
57
+ end
58
+
59
+ # Add instruction to be run after our example. Instructions added will be
60
+ # run inside the context of our example group, consider the following
61
+ # example:
62
+ #
63
+ # after do
64
+ # get :index
65
+ # end
66
+ #
67
+ # The above statements behave exactly the same as RSpec +after+ method, and
68
+ # this method serve as a proxy to RSpec +after+ method call. That means
69
+ # everything that we can do inside +after+ method will be available inside
70
+ # +after+ to.
71
+ #
72
+ # @return [Array<RSpec::Core::Hooks::AfterHook>] an array of after hook filter
73
+ def after(&block)
74
+ target.after(&block)
75
+ end
76
+ end
77
+ end
78
+ end
@@ -1,16 +1,86 @@
1
1
  module RSpec::Authorization
2
2
  module Adapters
3
+ # Create a request using wrapper around RSpec example. The request is made
4
+ # possible through wrapping the call around RSpec example, this approach
5
+ # is choosen because we need to get as close as possible to how the request
6
+ # is made inside RSpec.
7
+ #
8
+ # Once the request is created, it will immediately run and we can evaluate the
9
+ # response, consider the following example.
10
+ #
11
+ # request = Request.new(ArticlesController, :index, :user)
12
+ # request.response.status # => 200
13
+ #
14
+ # Since the request is actually creating a request to the provided controller,
15
+ # it will run everything inside the controller, and no stubbing is performed.
16
+ # Therefore exception inside controller will bubble up to the request and may
17
+ # cause unexpected result. That is with one exception, there is one exception,
18
+ # ActiveRecord::RecordNotFound is rescued and bypassed, therefore it will not
19
+ # affect the request.
3
20
  class Request
4
- attr_reader :klass, :action, :role, :group, :route, :response
21
+ # @return [Class] controller class name
22
+ attr_reader :klass
23
+ # @return [Symbol] controller action name
24
+ attr_reader :action
25
+ # @return [Symbol] role name from +config/authorization_rules.rb+
26
+ attr_reader :role
27
+ # @return [ExampleGroup] example group of the request
28
+ # @see ExampleGroup
29
+ attr_reader :group
30
+ # @return [Route] route object of the action
31
+ # @see Route
32
+ attr_reader :route
33
+ # @return [ActionController::TestResponse] response object of the request
34
+ attr_reader :response
5
35
 
36
+ # Create request object and immediately run the request. Inside initialization
37
+ # a lot of stuff get stubbed, it is to allow request to be run with assumptions,
38
+ # following are the assumptions composed as private method run inside the
39
+ # initializer:
40
+ #
41
+ # +stub_current_user+::
42
+ # The request is expected to run as a user with provided role. There is
43
+ # no user created whatsoever, it only creates a double with role_symbols
44
+ # defined and return it inside +current_user+.
45
+ #
46
+ # +stub_authorization_load_controller_object+::
47
+ # The request is expected to ignore declarative_authorization controller's
48
+ # object loading.
49
+ #
50
+ # +stub_authorization_load_object+::
51
+ # The request is expected to ignore declarative_authorization object
52
+ # loading. This is different from the above, and is expected to be called
53
+ # if controller object isn't loaded.
54
+ #
55
+ # +stub_callbacks+::
56
+ # The request is expected to ignore all callbacks defined inside the
57
+ # controller.
58
+ #
59
+ # +stub_action+::
60
+ # The request is expected to ignore all statements run inside the action
61
+ # and configured to render nothing instead.
62
+ #
63
+ # All of the above assumptions is expected to run only inside this request
64
+ # only, and not to change the behavior of the application outside of this
65
+ # request.
66
+ #
67
+ # @param klass [Class] controller class name to request from
68
+ # @param action [Symbol] action name, currently only support RESTful action
69
+ # @param role [Symbol] role name from +config/authorization_rules.rb+
6
70
  def initialize(klass, action, role)
7
71
  @klass, @action, @role = klass, action, role
8
- @group, @route = Group.new(klass), Route.new(action)
72
+ @group, @route = ExampleGroup.new(klass), Route.new(action)
73
+
74
+ @authorization_stubbed = false
9
75
 
10
76
  stub_current_user
77
+ stub_authorization_load_controller_object
78
+ stub_authorization_load_object
79
+ stub_callbacks
80
+ stub_action
11
81
 
12
82
  setup_response_retrieval
13
- dispatch
83
+ group.run_example
14
84
  end
15
85
 
16
86
  private
@@ -32,24 +102,69 @@ module RSpec::Authorization
32
102
  end
33
103
  end
34
104
 
35
- def setup_response_retrieval
36
- args = route
37
- setter = response_setter
105
+ def stub_authorization_load_controller_object
106
+ group.before do
107
+ allow(controller).to receive(:load_controller_object)
108
+ end
109
+ end
110
+
111
+ def stub_authorization_load_object
112
+ return if @authorization_stubbed
113
+ @authorization_stubbed = true
38
114
 
39
115
  group.before do
40
- begin
41
- send *args
42
- rescue ActiveRecord::RecordNotFound
43
- response.status = 404
116
+ Authorization::ControllerPermission.instance_eval do
117
+ alias_method :load_object_original, :load_object
118
+ define_method :load_object do |*args, &block|
119
+ begin
120
+ load_object_original(*args, &block)
121
+ rescue ActiveRecord::RecordNotFound
122
+ end
123
+ end
44
124
  end
125
+ end
45
126
 
46
- setter.call(response)
127
+ group.after do
128
+ Authorization::ControllerPermission.instance_eval do
129
+ remove_method :load_object
130
+
131
+ alias_method :load_object, :load_object_original
132
+ remove_method :load_object_original
133
+ end
47
134
  end
48
135
  end
49
136
 
50
- def dispatch
51
- example = Example.new(group)
52
- example.run
137
+ def stub_callbacks
138
+ group.before do
139
+ methods = controller._process_action_callbacks.map(&:filter).split(:filter_access_filter).last
140
+ controller.instance_eval do
141
+ methods.each do |method|
142
+ define_singleton_method method do |*args, &block|
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
148
+
149
+ def stub_action
150
+ args = route
151
+ group.before do
152
+ controller.instance_eval do
153
+ define_singleton_method args.action do |*args, &block|
154
+ render nothing: true
155
+ end
156
+ end
157
+ end
158
+ end
159
+
160
+ def setup_response_retrieval
161
+ args = route
162
+ setter = response_setter
163
+
164
+ group.before do
165
+ send *args
166
+ setter.call(response)
167
+ end
53
168
  end
54
169
  end
55
170
  end
@@ -1,8 +1,107 @@
1
1
  module RSpec::Authorization
2
2
  module Adapters
3
+ # Generate route that defines RESTful method using provided action. It is
4
+ # used primarily in creating a new request and infered automatically to an
5
+ # array using the defined +#to_a+ method.
6
+ #
7
+ # Route generation has 3 primary part, which are:
8
+ # - +verb+
9
+ # - +action+
10
+ # - +params+
11
+ #
12
+ # The first part, +verb+ refers to RESTful method, which is described in
13
+ # +DICTIONARIES+, +action+ refers to controller action name, and +params+
14
+ # refers to parameters used in a request. This object can be automatically
15
+ # inferred as an array.
16
+ #
17
+ # Creating a route will generate a route object:
18
+ #
19
+ # route = Route.new(:index)
20
+ # route # => #<Route:...>
21
+ #
22
+ # This will infer the object as an array:
23
+ #
24
+ # route = Route.new(:show)
25
+ # send *route # => [:get, :show, { id: 1 }]
26
+ #
27
+ # @see #to_a
3
28
  class Route
29
+ # @return [Symbol] returns route action name
4
30
  attr_reader :action
5
31
 
32
+ # Initializing a route requires a RESTful action name that the route want
33
+ # to generate. The action that get passed is assigned to +action+ attributes
34
+ # and is publicly accessible.
35
+ #
36
+ # route = Route.new(:index)
37
+ # route.action # => :index
38
+ #
39
+ # Currently +Route+ only support RESTful action, passing non-RESTful action
40
+ # name is possible, but will result in unexpected result.
41
+ #
42
+ # @param action [Symbol] RESTful action name
43
+ # @see #verb
44
+ def initialize(action)
45
+ @action = action
46
+ end
47
+
48
+ # This method is used to retrieve a dummy params that's used for an action.
49
+ # Tipically an action for a resource member requires a param, while action
50
+ # for resource collection doesn't need any param.
51
+ #
52
+ # This will return a dummy params:
53
+ #
54
+ # route = Route.new(:show)
55
+ # route.params # => { id: 1 }
56
+ #
57
+ # This will return nil for a collection resource action:
58
+ #
59
+ # route = Route.new(:index)
60
+ # route.params # => nil
61
+ #
62
+ # @return [Hash, nil] a dummy hash params
63
+ def params
64
+ PARAMS[action]
65
+ end
66
+
67
+ # Return verb used for the RESTful action. The verb uses +DICTIONARIES+ to
68
+ # retrieve a valid method for an action. This method intentionally throws
69
+ # error if a non-RESTful action is used.
70
+ #
71
+ # route = Route.new(:index)
72
+ # route.verb # => :get
73
+ #
74
+ # This will throw error:
75
+ #
76
+ # route = Route.new(:an_action)
77
+ # route.verb # => KeyError: key not found: :an_action
78
+ #
79
+ # @return [Symbol] RESTful verb to be used for an action
80
+ def verb
81
+ DICTIONARIES.fetch(action)
82
+ end
83
+
84
+ # This method is used to convert Route object into an array. This method also
85
+ # used to automatically infer +Route+ as an array on method that uses +to_a+,
86
+ # therefore we don't need to manually invoke +#to_a+ on every call that
87
+ # requires an array object.
88
+ #
89
+ # This will return an array:
90
+ #
91
+ # route = Route.new(:show)
92
+ # route.to_a # => [:get, :show, { id: 1 }]
93
+ #
94
+ # This will also return an array inferred automatically
95
+ #
96
+ # send *route # => [:get, :show, { id: 1 }]
97
+ #
98
+ # @return [Array] an array of verb, action and params
99
+ def to_a
100
+ [verb, action, params]
101
+ end
102
+
103
+ private
104
+
6
105
  DICTIONARIES = {
7
106
  index: :get,
8
107
  show: :get,
@@ -19,22 +118,6 @@ module RSpec::Authorization
19
118
  update: { id: 1 },
20
119
  destroy: { id: 1 }
21
120
  }
22
-
23
- def initialize(action)
24
- @action = action
25
- end
26
-
27
- def params
28
- PARAMS[action]
29
- end
30
-
31
- def verb
32
- DICTIONARIES.fetch(action)
33
- end
34
-
35
- def to_a
36
- [verb, action, params]
37
- end
38
121
  end
39
122
  end
40
123
  end
@@ -1,10 +1,10 @@
1
1
  require "rspec/authorization/adapters/example"
2
- require "rspec/authorization/adapters/group"
2
+ require "rspec/authorization/adapters/example_group"
3
3
  require "rspec/authorization/adapters/request"
4
4
  require "rspec/authorization/adapters/route"
5
5
 
6
6
  module RSpec::Authorization
7
- module Adapters
7
+ module Adapters # :nodoc:
8
8
  end
9
9
  end
10
10
 
@@ -1,43 +1,158 @@
1
1
  module RSpec::Authorization
2
2
  module Matchers
3
+ # Include this module to enable the +have_permission_for+ matcher inside RSpec.
4
+ # The following module is to be included only inside controller spec, this will
5
+ # add the capability to do a matcher against decalrative_authorization rules as
6
+ # follows:
7
+ #
8
+ # it { is_expected.to have_permission_for(:user).to(:index) }
9
+ #
10
+ # For your convenience, the following configuration has been enabled inside
11
+ # RSpec configuration.
12
+ #
13
+ # RSpec.configure do |config|
14
+ # config.include RSpec::Authorization::Matchers::HavePermissionFor, type: :controller
15
+ # end
3
16
  module HavePermissionFor
17
+ # Matcher to check permission of a role for a given controller in a spec. The
18
+ # following statement shows you how to use this matcher:
19
+ #
20
+ # describe ArticlesController do
21
+ # it { is_expected.to have_permission_for(:user).to(:index) }
22
+ # end
23
+ #
24
+ # Currently this matcher only support RESTful action check, to check the
25
+ # controller against +config/authorization_rules.rb+. Skipping the +#to+
26
+ # method will result in default action assigned as +:index+.
27
+ #
28
+ # Therefore, the following statement is exactly the same as above:
29
+ #
30
+ # it { is_expected.to have_permission_for(:user) }
31
+ #
32
+ # === RESTful methods
33
+ #
34
+ # For your convenience, there are 4 RESTful methods available to be chained
35
+ # from the matcher, which are:
36
+ #
37
+ # - +to_read+
38
+ # - +to_create+
39
+ # - +to_update+
40
+ # - +to_delete+
41
+ #
42
+ # Consider the following example:
43
+ #
44
+ # it { is_expected.to have_permission_for(:user).to_read }
45
+ # it { is_expected.to have_permission_for(:user).to_edit }
46
+ # it { is_expected.not_to have_permission_for(:user).to_update }
47
+ # it { is_expected.not_to have_permission_for(:user).to_delete }
48
+ #
49
+ # The above method is not related to declarative_authorization privileges,
50
+ # and serve simply as convinience method, below is a table of RESTful actions
51
+ # for each method mentioned above:
52
+ #
53
+ # Method RESTful action
54
+ # ------------------------------------
55
+ # to_read [:index, :show]
56
+ # to_edit [:edit, :update]
57
+ # to_create [:new, :create]
58
+ # to_delete [:destroy]
59
+ #
60
+ # Matcher for RESTful methods is slightly different than that of a single
61
+ # method, following is how RESTful methods request results evaluated:
62
+ #
63
+ # all_requests (of #to_read) matches? does_not_match?
64
+ # -------------------------------------------------------------------
65
+ # {index: true, show: true} true false
66
+ # {index: true, show: false} false false
67
+ # {index: false, show: false} false true
68
+ #
69
+ # @param role [Symbol] role name to matched against
4
70
  def have_permission_for(role)
5
71
  HavePermissionFor.new(role)
6
72
  end
7
73
 
8
- # @private
9
- class HavePermissionFor
74
+ class HavePermissionFor # :nodoc: all
10
75
  include Adapters
11
76
 
12
- attr_reader :controller, :role, :action
77
+ attr_reader :controller, :role, :behave, :actions, :results
13
78
 
14
79
  def initialize(role)
15
- @role = role
16
- @action = :index
80
+ @role = role
17
81
  end
18
82
 
19
83
  def to(action)
20
- @action = action
84
+ @behave = action
85
+ @actions = [behave]
86
+
87
+ self
88
+ end
89
+
90
+ def to_read
91
+ @behave = :read
92
+ @actions = %i(index show)
93
+
94
+ self
95
+ end
96
+
97
+ def to_create
98
+ @behave = :create
99
+ @actions = %i(new create)
100
+
21
101
  self
22
102
  end
23
103
 
104
+ def to_update
105
+ @behave = :update
106
+ @actions = %i(edit update)
107
+
108
+ self
109
+ end
110
+
111
+ def to_delete
112
+ @behave = :delete
113
+ @actions = %i(destroy)
114
+
115
+ self
116
+ end
117
+
118
+ def to_manage
119
+ @behave = :manage
120
+ @actions = %i(index show new create edit update destroy)
121
+
122
+ self
123
+ end
124
+
125
+ def all_requests
126
+ actions.map do |action|
127
+ request = Request.new(controller.class, action, role)
128
+ [action, request.response.status != 403]
129
+ end
130
+ end
131
+
24
132
  def matches?(controller)
25
133
  @controller = controller
134
+ @results = Hash[all_requests]
135
+
136
+ true unless results.value? false
137
+ end
138
+
139
+ def does_not_match?(controller)
140
+ @controller = controller
141
+ @results = Hash[all_requests]
26
142
 
27
- request = Request.new(controller.class, action, role)
28
- request.response.status != 403
143
+ true unless results.value? true
29
144
  end
30
145
 
31
146
  def failure_message
32
- "Expected #{controller.class} to have permission for #{role} to #{action}"
147
+ "Expected #{controller.class} to have permission for #{role} to #{behave}. #{results}"
33
148
  end
34
149
 
35
150
  def failure_message_when_negated
36
- "Did not expect #{controller.class} to have permission for #{role} to #{action}"
151
+ "Did not expect #{controller.class} to have permission for #{role} to #{behave}. #{results}"
37
152
  end
38
153
 
39
154
  def description
40
- "have permission for #{role} on #{action}"
155
+ "have permission for #{role} to #{behave}"
41
156
  end
42
157
  end
43
158
  end
@@ -1,7 +1,7 @@
1
1
  require 'rspec/authorization/matchers/have_permission_for'
2
2
 
3
3
  module RSpec::Authorization
4
- module Matchers
4
+ module Matchers # :nodoc:
5
5
  end
6
6
  end
7
7
 
@@ -1,5 +1,5 @@
1
- module Rspec
1
+ module RSpec
2
2
  module Authorization
3
- VERSION = "0.0.1"
3
+ VERSION = "0.0.2"
4
4
  end
5
5
  end
@@ -3,7 +3,7 @@ require "rspec/authorization/adapters"
3
3
  require "rspec/authorization/matchers"
4
4
  require "rspec/authorization/version"
5
5
 
6
- module RSpec
7
- module Authorization
6
+ module RSpec # :nodoc:
7
+ module Authorization # :nodoc:
8
8
  end
9
9
  end
@@ -4,7 +4,7 @@ require 'rspec/authorization/version'
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = "rspec-authorization"
7
- spec.version = Rspec::Authorization::VERSION
7
+ spec.version = RSpec::Authorization::VERSION
8
8
  spec.authors = ["Hendra Uzia"]
9
9
  spec.email = ["hendra.uzia@gmail.com"]
10
10
  spec.summary = %q{RSpec matcher for declarative_authorization.}
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
18
18
  spec.require_paths = ["lib"]
19
19
 
20
20
  spec.add_runtime_dependency "declarative_authorization"
21
- spec.add_runtime_dependency "rspec-rails"
21
+ spec.add_runtime_dependency "rspec-rails", "~> 3.0"
22
22
 
23
23
  spec.add_development_dependency "bundler", "~> 1.7"
24
24
  spec.add_development_dependency "rails", "~> 4.0"
@@ -0,0 +1,21 @@
1
+ require 'rails_helper'
2
+
3
+ describe ArticlesController do
4
+ it { is_expected.to have_permission_for(:user).to(:index) }
5
+ it { is_expected.not_to have_permission_for(:user).to(:show) }
6
+ it { is_expected.not_to have_permission_for(:user).to_create }
7
+ it { is_expected.not_to have_permission_for(:user).to_update }
8
+ it { is_expected.not_to have_permission_for(:user).to_delete }
9
+
10
+ it { is_expected.to have_permission_for(:premium).to_read }
11
+ it { is_expected.not_to have_permission_for(:premium).to_create }
12
+ it { is_expected.not_to have_permission_for(:premium).to_update }
13
+ it { is_expected.not_to have_permission_for(:premium).to_delete }
14
+
15
+ it { is_expected.to have_permission_for(:writer).to_read }
16
+ it { is_expected.to have_permission_for(:writer).to_create }
17
+ it { is_expected.not_to have_permission_for(:writer).to_update }
18
+ it { is_expected.not_to have_permission_for(:writer).to_delete }
19
+
20
+ it { is_expected.to have_permission_for(:editor).to_manage }
21
+ end
@@ -0,0 +1,28 @@
1
+ require 'rails_helper'
2
+
3
+ include RSpec::Authorization::Adapters
4
+
5
+ describe ExampleGroup do
6
+ let(:klass) { ArticlesController }
7
+ let(:example_group) { ExampleGroup.new(klass) }
8
+
9
+ let(:before_instruction) { ->{ :before } }
10
+ let(:after_instruction) { ->{ :after } }
11
+
12
+ describe "target" do
13
+ subject { example_group.target }
14
+ its(:described_class) { is_expected.to eq klass }
15
+ end
16
+
17
+ describe "#before" do
18
+ specify { expect(example_group.before(&before_instruction).last.block).to eq before_instruction }
19
+ end
20
+
21
+ describe "#after" do
22
+ specify { expect(example_group.after(&after_instruction).first.block).to eq after_instruction }
23
+ end
24
+
25
+ describe "#run_example" do
26
+ specify { expect(example_group.run_example).to be_a_kind_of(Example) }
27
+ end
28
+ end
@@ -4,23 +4,13 @@ include RSpec::Authorization::Adapters
4
4
 
5
5
  describe Example do
6
6
  let(:klass) { GroupTestClass }
7
- let(:group) { Group.new(klass) }
8
- let(:example) { Example.new(group) }
7
+ let(:group) { ExampleGroup.new(klass) }
8
+ let(:example) { Example.new(group.target) }
9
9
 
10
- subject { example }
11
-
12
- its(:group) { is_expected.to eq group }
13
- its(:example) { is_expected.to be_a_kind_of RSpec::Core::Example }
10
+ before { allow_any_instance_of(Example).to receive(:run_before_example) }
14
11
 
15
- context "private" do
16
- describe "#set_example_group_instance" do
17
- before { example.send :set_example_group_instance }
18
- specify { expect(example.example.instance_variable_get :@example_group_instance).to be_a_kind_of group }
19
- end
12
+ subject { example }
20
13
 
21
- describe "#run_before_example" do
22
- before { expect(example.example).to receive(:run_before_example).and_return(true) }
23
- specify { expect(example.example.send :run_before_example).to be_present }
24
- end
25
- end
14
+ its(:group_target) { is_expected.to eq group.target }
15
+ its(:target) { is_expected.to be_a_kind_of RSpec::Core::Example }
26
16
  end
@@ -3,9 +3,9 @@ require 'rails_helper'
3
3
  include RSpec::Authorization::Adapters
4
4
 
5
5
  describe Request do
6
- let(:klass) { PostsController }
7
- let(:action) { :index }
8
- let(:role) { :user }
6
+ let(:klass) { ArticlesController }
7
+ let(:action) { :update }
8
+ let(:role) { :editor }
9
9
  let(:request) { Request.new(klass, action, role) }
10
10
 
11
11
  subject { request }
data/spec/spec_helper.rb CHANGED
@@ -1,3 +1,6 @@
1
+ require "codeclimate-test-reporter"
2
+ CodeClimate::TestReporter.start
3
+
1
4
  require 'rspec/its'
2
5
 
3
6
  $LOAD_PATH << File.expand_path('../../tools', __FILE__)
@@ -3,7 +3,8 @@ require 'rails_test_app'
3
3
 
4
4
  describe RailsTestApp do
5
5
  let(:test_app) { RailsTestApp.new(:version) }
6
- let(:dummy_file) { 'tmp/dummy-rails-test-app' }
6
+ let(:temp_path) { "tmp" }
7
+ let(:dummy_file) { "#{temp_path}/dummy-rails-test-app" }
7
8
 
8
9
  describe ".new" do
9
10
  subject(:test_app) { RailsTestApp.new("4.1.6", "--skip-javascript") }
@@ -18,6 +19,7 @@ describe RailsTestApp do
18
19
  before { allow(test_app).to receive(:create_command).and_return("touch #{dummy_file}") }
19
20
  before { allow(test_app).to receive(:template_param) }
20
21
  before { allow(test_app).to receive(:option) }
22
+ before { `mkdir #{temp_path}` }
21
23
  after { `rm -rf #{dummy_file}` }
22
24
 
23
25
  context "path does not exists" do
@@ -5,8 +5,8 @@ rules = "config/authorization_rules.rb"
5
5
  run "mkdir ../../../config"
6
6
  run "ln -s ../spec/.rails/rails-#{Rails::VERSION::STRING}/#{rules} ../../../#{rules}"
7
7
 
8
- generate "scaffold post --skip-assets --skip-helper"
9
- generate "migration AddUserIdToPosts user:references"
8
+ generate "scaffold article --skip-assets --skip-helper"
9
+ generate "migration AddUserIdToArticles user:references"
10
10
 
11
11
  rake "db:migrate"
12
12
  run "bundle exec rake db:migrate RAILS_ENV=test"
@@ -14,27 +14,38 @@ run "bundle exec rake db:migrate RAILS_ENV=test"
14
14
  first_line = /\A.*/
15
15
  last_line = /^.*\Z/
16
16
 
17
- inject_into_file "app/models/post.rb", %q{
17
+ inject_into_file "app/models/article.rb", %q{
18
18
  belongs_to :user
19
19
  }, after: first_line
20
20
 
21
21
  inject_into_file "app/models/user.rb", %q{
22
- has_many :posts
22
+ has_many :articles
23
23
  }, after: first_line
24
24
 
25
25
  inject_into_file "config/authorization_rules.rb", %q{
26
+ role :editor do
27
+ has_permission_on :articles, to: :manage
28
+ end
29
+
30
+ role :writer do
31
+ has_permission_on :articles, to: %i(read create)
32
+ end
33
+
34
+ role :premium do
35
+ has_permission_on :articles, to: :read
36
+ end
37
+
26
38
  role :user do
27
- has_permission_on :posts, to: :manage
39
+ has_permission_on :articles, to: :index
28
40
  end
29
41
  }, after: first_line
30
42
 
31
43
  inject_into_file "app/controllers/application_controller.rb", %q{
32
44
  def current_user
33
- User.where(id: session[:user_id]).presence
34
45
  end
35
46
  }, before: last_line
36
47
 
37
- inject_into_file "app/controllers/posts_controller.rb", %q{
48
+ inject_into_file "app/controllers/articles_controller.rb", %q{
38
49
  filter_resource_access
39
50
  }, after: first_line
40
51
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rspec-authorization
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hendra Uzia
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-11-05 00:00:00.000000000 Z
11
+ date: 2014-11-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: declarative_authorization
@@ -28,16 +28,16 @@ dependencies:
28
28
  name: rspec-rails
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: '3.0'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ">="
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: '3.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: bundler
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -120,15 +120,18 @@ files:
120
120
  - ".gitignore"
121
121
  - ".rspec"
122
122
  - ".ruby-version"
123
+ - ".travis.yml"
124
+ - ".yardopts"
123
125
  - Gemfile
124
126
  - Guardfile
127
+ - HISTORY.md
125
128
  - LICENSE.txt
126
129
  - README.md
127
130
  - Rakefile
128
131
  - lib/rspec/authorization.rb
129
132
  - lib/rspec/authorization/adapters.rb
130
133
  - lib/rspec/authorization/adapters/example.rb
131
- - lib/rspec/authorization/adapters/group.rb
134
+ - lib/rspec/authorization/adapters/example_group.rb
132
135
  - lib/rspec/authorization/adapters/request.rb
133
136
  - lib/rspec/authorization/adapters/route.rb
134
137
  - lib/rspec/authorization/matchers.rb
@@ -136,9 +139,9 @@ files:
136
139
  - lib/rspec/authorization/version.rb
137
140
  - rspec-authorization.gemspec
138
141
  - spec/.keep
139
- - spec/controllers/posts_controller_spec.rb
142
+ - spec/controllers/articles_controller_spec.rb
143
+ - spec/lib/rspec/authorization/adapters/example_group_spec.rb
140
144
  - spec/lib/rspec/authorization/adapters/example_spec.rb
141
- - spec/lib/rspec/authorization/adapters/group_spec.rb
142
145
  - spec/lib/rspec/authorization/adapters/request_spec.rb
143
146
  - spec/lib/rspec/authorization/adapters/route_spec.rb
144
147
  - spec/rails_helper.rb
@@ -176,12 +179,13 @@ specification_version: 4
176
179
  summary: RSpec matcher for declarative_authorization.
177
180
  test_files:
178
181
  - spec/.keep
179
- - spec/controllers/posts_controller_spec.rb
182
+ - spec/controllers/articles_controller_spec.rb
183
+ - spec/lib/rspec/authorization/adapters/example_group_spec.rb
180
184
  - spec/lib/rspec/authorization/adapters/example_spec.rb
181
- - spec/lib/rspec/authorization/adapters/group_spec.rb
182
185
  - spec/lib/rspec/authorization/adapters/request_spec.rb
183
186
  - spec/lib/rspec/authorization/adapters/route_spec.rb
184
187
  - spec/rails_helper.rb
185
188
  - spec/spec_helper.rb
186
189
  - spec/support/group_test_class.rb
187
190
  - spec/tools/rails_test_app_spec.rb
191
+ has_rdoc:
@@ -1,14 +0,0 @@
1
- module RSpec::Authorization
2
- module Adapters
3
- class Group
4
- class << self
5
- def new(klass)
6
- RSpec::Core::ExampleGroup.describe klass do
7
- include RSpec::Rails::ControllerExampleGroup
8
- end
9
- end
10
- end
11
- end
12
- end
13
- end
14
-
@@ -1,11 +0,0 @@
1
- require 'rails_helper'
2
-
3
- describe PostsController do
4
- it { is_expected.to have_permission_for(:user).to(:index) }
5
- it { is_expected.to have_permission_for(:user).to(:new) }
6
- it { is_expected.to have_permission_for(:user).to(:create) }
7
- it { is_expected.to have_permission_for(:user).to(:show) }
8
- it { is_expected.to have_permission_for(:user).to(:edit) }
9
- it { is_expected.to have_permission_for(:user).to(:update) }
10
- it { is_expected.to have_permission_for(:user).to(:destroy) }
11
- end
@@ -1,13 +0,0 @@
1
- require 'rails_helper'
2
-
3
- include RSpec::Authorization::Adapters
4
-
5
- describe Group do
6
- let(:klass) { GroupTestClass }
7
- let(:group) { Group.new(klass) }
8
-
9
- describe ".new" do
10
- subject { group }
11
- its(:described_class) { is_expected.to eq GroupTestClass }
12
- end
13
- end