pundit-matchers 1.7.0 → 3.1.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/lib/pundit/matchers/actions_matcher.rb +55 -0
- data/lib/pundit/matchers/attributes_matcher.rb +84 -0
- data/lib/pundit/matchers/base_matcher.rb +27 -0
- data/lib/pundit/matchers/forbid_all_actions_matcher.rb +43 -0
- data/lib/pundit/matchers/forbid_only_actions_matcher.rb +60 -0
- data/lib/pundit/matchers/permit_actions_matcher.rb +69 -0
- data/lib/pundit/matchers/permit_all_actions_matcher.rb +43 -0
- data/lib/pundit/matchers/permit_attributes_matcher.rb +65 -0
- data/lib/pundit/matchers/permit_only_actions_matcher.rb +60 -0
- data/lib/pundit/matchers/utils/policy_info.rb +92 -0
- data/lib/pundit/matchers.rb +139 -326
- metadata +52 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 379030415885d5cd2557f7540639e6c835f53cfa5a88895fd3e5f7cbbdf1b88f
|
4
|
+
data.tar.gz: 5a5e5f7586d31f4328e3e0b5bf722d3465a681ef77340a90d44b6517f04eb700
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5c513efb4f063cd3bd15d43c0a68d001bb4ca4ef806861b27cc0de39f7102d6aab8be0ee2b64213b83f8991d38d9e3f62ae6f7b0c03f0ae87f2f1f71fba87b0c
|
7
|
+
data.tar.gz: a32970e872d608219955154cdd43812b36a4d0d37363e09d0e17e341f297c23afb0e947756711b1df3d1c0aad3bcc48a43935663072b87656f196bfdb049afab
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base_matcher'
|
4
|
+
|
5
|
+
module Pundit
|
6
|
+
module Matchers
|
7
|
+
# This is the base action matcher class. Matchers related to actions should inherit from this class.
|
8
|
+
class ActionsMatcher < BaseMatcher
|
9
|
+
# Error message when actions are not implemented in a policy.
|
10
|
+
ACTIONS_NOT_IMPLEMENTED_ERROR = "'%<policy>s' does not implement %<actions>s"
|
11
|
+
# Error message when at least one action must be specified.
|
12
|
+
ARGUMENTS_REQUIRED_ERROR = 'At least one action must be specified'
|
13
|
+
# Error message when only one action may be specified.
|
14
|
+
ONE_ARGUMENT_REQUIRED_ERROR = 'Only one action may be specified'
|
15
|
+
|
16
|
+
# Initializes a new instance of the ActionsMatcher class.
|
17
|
+
#
|
18
|
+
# @param expected_actions [Array<String, Symbol>] The expected actions to be checked.
|
19
|
+
#
|
20
|
+
# @raise [ArgumentError] If no actions are specified.
|
21
|
+
def initialize(*expected_actions)
|
22
|
+
raise ArgumentError, ARGUMENTS_REQUIRED_ERROR if expected_actions.empty?
|
23
|
+
|
24
|
+
super()
|
25
|
+
@expected_actions = expected_actions.flatten.map(&:to_sym).sort
|
26
|
+
end
|
27
|
+
|
28
|
+
# Ensures that only one action is specified.
|
29
|
+
#
|
30
|
+
# @raise [ArgumentError] If more than one action is specified.
|
31
|
+
#
|
32
|
+
# @return [ActionsMatcher] The object itself.
|
33
|
+
def ensure_single_action!
|
34
|
+
raise ArgumentError, ONE_ARGUMENT_REQUIRED_ERROR if expected_actions.size > 1
|
35
|
+
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
attr_reader :expected_actions
|
42
|
+
|
43
|
+
def check_actions!
|
44
|
+
non_explicit_actions = (expected_actions - policy_info.actions)
|
45
|
+
missing_actions = non_explicit_actions.reject { |action| policy_info.policy.respond_to?(:"#{action}?") }
|
46
|
+
return if missing_actions.empty?
|
47
|
+
|
48
|
+
raise ArgumentError, format(
|
49
|
+
ACTIONS_NOT_IMPLEMENTED_ERROR,
|
50
|
+
policy: policy_info, actions: missing_actions
|
51
|
+
)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base_matcher'
|
4
|
+
|
5
|
+
module Pundit
|
6
|
+
module Matchers
|
7
|
+
# The AttributesMatcher class is used to test whether a Pundit policy allows or denies access to certain attributes.
|
8
|
+
class AttributesMatcher < BaseMatcher
|
9
|
+
# Error message to be raised when no attributes are specified.
|
10
|
+
ARGUMENTS_REQUIRED_ERROR = 'At least one attribute must be specified'
|
11
|
+
# Error message to be raised when only one attribute may be specified.
|
12
|
+
ONE_ARGUMENT_REQUIRED_ERROR = 'Only one attribute may be specified'
|
13
|
+
|
14
|
+
# Initializes a new instance of the AttributesMatcher class.
|
15
|
+
#
|
16
|
+
# @param expected_attributes [Array<String, Symbol, Hash>] The list of attributes to be tested.
|
17
|
+
def initialize(*expected_attributes)
|
18
|
+
raise ArgumentError, ARGUMENTS_REQUIRED_ERROR if expected_attributes.empty?
|
19
|
+
|
20
|
+
super()
|
21
|
+
@expected_attributes = flatten_attributes(expected_attributes)
|
22
|
+
@options = {}
|
23
|
+
end
|
24
|
+
|
25
|
+
# Specifies the action to be tested.
|
26
|
+
#
|
27
|
+
# @param action [Symbol, String] The action to be tested.
|
28
|
+
# @return [AttributesMatcher] The current instance of the AttributesMatcher class.
|
29
|
+
def for_action(action)
|
30
|
+
@options[:action] = action
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
# Ensures that only one attribute is specified.
|
35
|
+
#
|
36
|
+
# @raise [ArgumentError] If more than one attribute is specified.
|
37
|
+
#
|
38
|
+
# @return [AttributesMatcher] The object itself.
|
39
|
+
def ensure_single_attribute!
|
40
|
+
raise ArgumentError, ONE_ARGUMENT_REQUIRED_ERROR if expected_attributes.size > 1
|
41
|
+
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
attr_reader :expected_attributes, :options
|
48
|
+
|
49
|
+
def permitted_attributes(policy)
|
50
|
+
@permitted_attributes ||=
|
51
|
+
if options.key?(:action)
|
52
|
+
flatten_attributes(policy.public_send(:"permitted_attributes_for_#{options[:action]}"))
|
53
|
+
else
|
54
|
+
flatten_attributes(policy.permitted_attributes)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def action_message
|
59
|
+
" when authorising the '#{options[:action]}' action"
|
60
|
+
end
|
61
|
+
|
62
|
+
# Flattens and sorts a hash or array of attributes into an array of symbols.
|
63
|
+
#
|
64
|
+
# This is a private method used internally by the `Matcher` class to convert
|
65
|
+
# attribute lists into a flattened, sorted array of symbols. The resulting
|
66
|
+
# array can be used to compare attribute lists.
|
67
|
+
#
|
68
|
+
# @param attributes [String, Symbol, Array, Hash] the attributes to be flattened.
|
69
|
+
# @return [Array<Symbol>] the flattened, sorted array of symbols.
|
70
|
+
def flatten_attributes(attributes)
|
71
|
+
case attributes
|
72
|
+
when String, Symbol
|
73
|
+
[attributes.to_sym]
|
74
|
+
when Array
|
75
|
+
attributes.flat_map { |item| flatten_attributes(item) }.sort
|
76
|
+
when Hash
|
77
|
+
attributes.flat_map do |key, value|
|
78
|
+
flatten_attributes(value).map { |item| :"#{key}[#{item}]" }
|
79
|
+
end.sort
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'utils/policy_info'
|
4
|
+
|
5
|
+
module Pundit
|
6
|
+
module Matchers
|
7
|
+
# This is the base class for all matchers in the Pundit Matchers library.
|
8
|
+
class BaseMatcher
|
9
|
+
# Error message when an ambiguous negated matcher is used.
|
10
|
+
AMBIGUOUS_NEGATED_MATCHER_ERROR = <<~MSG
|
11
|
+
`expect().not_to %<name>s` is not supported since it creates ambiguity.
|
12
|
+
MSG
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
attr_reader :policy_info
|
17
|
+
|
18
|
+
def setup_policy_info!(policy)
|
19
|
+
@policy_info = Pundit::Matchers::Utils::PolicyInfo.new(policy)
|
20
|
+
end
|
21
|
+
|
22
|
+
def user_message
|
23
|
+
" for '#{policy_info.user}'"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base_matcher'
|
4
|
+
|
5
|
+
module Pundit
|
6
|
+
module Matchers
|
7
|
+
# This matcher tests whether a policy forbids all actions.
|
8
|
+
class ForbidAllActionsMatcher < BaseMatcher
|
9
|
+
# A description of the matcher.
|
10
|
+
#
|
11
|
+
# @return [String] Description of the matcher.
|
12
|
+
def description
|
13
|
+
'forbid all actions'
|
14
|
+
end
|
15
|
+
|
16
|
+
# Checks if the given policy forbids all actions.
|
17
|
+
#
|
18
|
+
# @param policy [Object] The policy to test.
|
19
|
+
# @return [Boolean] True if the policy forbids all actions, false otherwise.
|
20
|
+
def matches?(policy)
|
21
|
+
setup_policy_info! policy
|
22
|
+
|
23
|
+
policy_info.permitted_actions.empty?
|
24
|
+
end
|
25
|
+
|
26
|
+
# Raises a NotImplementedError
|
27
|
+
# @raise NotImplementedError
|
28
|
+
# @return [void]
|
29
|
+
def does_not_match?(_policy)
|
30
|
+
raise NotImplementedError, format(AMBIGUOUS_NEGATED_MATCHER_ERROR, name: 'forbid_all_actions')
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns a failure message if the matcher fails.
|
34
|
+
#
|
35
|
+
# @return [String] Failure message.
|
36
|
+
def failure_message
|
37
|
+
message = +"expected '#{policy_info}' to forbid all actions,"
|
38
|
+
message << " but permitted #{policy_info.permitted_actions}"
|
39
|
+
message << user_message
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'actions_matcher'
|
4
|
+
|
5
|
+
module Pundit
|
6
|
+
module Matchers
|
7
|
+
# This matcher tests whether a policy forbids only the expected actions.
|
8
|
+
class ForbidOnlyActionsMatcher < ActionsMatcher
|
9
|
+
# A description of the matcher.
|
10
|
+
#
|
11
|
+
# @return [String] Description of the matcher.
|
12
|
+
def description
|
13
|
+
"forbid only #{expected_actions}"
|
14
|
+
end
|
15
|
+
|
16
|
+
# Checks if the given policy forbids only the expected actions.
|
17
|
+
#
|
18
|
+
# @param policy [Object] The policy to test.
|
19
|
+
# @return [Boolean] True if the policy forbids only the expected actions, false otherwise.
|
20
|
+
def matches?(policy)
|
21
|
+
setup_policy_info! policy
|
22
|
+
check_actions!
|
23
|
+
|
24
|
+
@actual_actions = policy_info.forbidden_actions - expected_actions
|
25
|
+
@extra_actions = policy_info.permitted_actions & expected_actions
|
26
|
+
|
27
|
+
actual_actions.empty? && extra_actions.empty?
|
28
|
+
end
|
29
|
+
|
30
|
+
# Raises a NotImplementedError
|
31
|
+
# @raise NotImplementedError
|
32
|
+
# @return [void]
|
33
|
+
def does_not_match?(_policy)
|
34
|
+
raise NotImplementedError, format(AMBIGUOUS_NEGATED_MATCHER_ERROR, name: 'forbid_only_actions')
|
35
|
+
end
|
36
|
+
|
37
|
+
# The failure message when the expected actions and the forbidden actions do not match.
|
38
|
+
#
|
39
|
+
# @return [String] A failure message when the expected actions and the forbidden actions do not match.
|
40
|
+
def failure_message
|
41
|
+
message = +"expected '#{policy_info}' to forbid only #{expected_actions},"
|
42
|
+
message << " but forbade #{actual_actions}" unless actual_actions.empty?
|
43
|
+
message << extra_message unless extra_actions.empty?
|
44
|
+
message << user_message
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
attr_reader :actual_actions, :extra_actions
|
50
|
+
|
51
|
+
def extra_message
|
52
|
+
if actual_actions.empty?
|
53
|
+
" but permitted #{extra_actions}"
|
54
|
+
else
|
55
|
+
" and permitted #{extra_actions}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'actions_matcher'
|
4
|
+
|
5
|
+
module Pundit
|
6
|
+
module Matchers
|
7
|
+
# This matcher tests whether a policy permits or forbids the expected actions.
|
8
|
+
class PermitActionsMatcher < ActionsMatcher
|
9
|
+
# A description of the matcher.
|
10
|
+
#
|
11
|
+
# @return [String] Description of the matcher.
|
12
|
+
def description
|
13
|
+
"permit #{expected_actions}"
|
14
|
+
end
|
15
|
+
|
16
|
+
# Checks if the given policy permits the expected actions.
|
17
|
+
#
|
18
|
+
# @param policy [Object] The policy to test.
|
19
|
+
# @return [Boolean] True if the policy permits the expected actions, false otherwise.
|
20
|
+
def matches?(policy)
|
21
|
+
setup_policy_info! policy
|
22
|
+
check_actions!
|
23
|
+
|
24
|
+
@actual_actions = expected_actions.reject do |action|
|
25
|
+
policy.public_send(:"#{action}?")
|
26
|
+
end
|
27
|
+
|
28
|
+
actual_actions.empty?
|
29
|
+
end
|
30
|
+
|
31
|
+
# Checks if the given policy forbids the expected actions.
|
32
|
+
#
|
33
|
+
# @param policy [Object] The policy to test.
|
34
|
+
# @return [Boolean] True if the policy forbids the expected actions, false otherwise.
|
35
|
+
def does_not_match?(policy)
|
36
|
+
setup_policy_info! policy
|
37
|
+
check_actions!
|
38
|
+
|
39
|
+
@actual_actions = expected_actions.select do |action|
|
40
|
+
policy.public_send(:"#{action}?")
|
41
|
+
end
|
42
|
+
|
43
|
+
actual_actions.empty?
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns a failure message when the expected actions are forbidden.
|
47
|
+
#
|
48
|
+
# @return [String] A failure message when the expected actions are not forbidden.
|
49
|
+
def failure_message
|
50
|
+
message = +"expected '#{policy_info}' to permit #{expected_actions},"
|
51
|
+
message << " but forbade #{actual_actions}"
|
52
|
+
message << user_message
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns a failure message when the expected actions are permitted.
|
56
|
+
#
|
57
|
+
# @return [String] A failure message when the expected actions are permitted.
|
58
|
+
def failure_message_when_negated
|
59
|
+
message = +"expected '#{policy_info}' to forbid #{expected_actions},"
|
60
|
+
message << " but permitted #{actual_actions}"
|
61
|
+
message << user_message
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
attr_reader :actual_actions
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base_matcher'
|
4
|
+
|
5
|
+
module Pundit
|
6
|
+
module Matchers
|
7
|
+
# This matcher tests whether a policy permits all actions.
|
8
|
+
class PermitAllActionsMatcher < BaseMatcher
|
9
|
+
# A description of the matcher.
|
10
|
+
#
|
11
|
+
# @return [String] A description of the matcher.
|
12
|
+
def description
|
13
|
+
'permit all actions'
|
14
|
+
end
|
15
|
+
|
16
|
+
# Checks if the given policy permits all actions.
|
17
|
+
#
|
18
|
+
# @param policy [Object] The policy to test.
|
19
|
+
# @return [Boolean] True if the policy permits all actions, false otherwise.
|
20
|
+
def matches?(policy)
|
21
|
+
setup_policy_info! policy
|
22
|
+
|
23
|
+
policy_info.forbidden_actions.empty?
|
24
|
+
end
|
25
|
+
|
26
|
+
# Raises a NotImplementedError
|
27
|
+
# @raise NotImplementedError
|
28
|
+
# @return [void]
|
29
|
+
def does_not_match?(_policy)
|
30
|
+
raise NotImplementedError, format(AMBIGUOUS_NEGATED_MATCHER_ERROR, name: 'permit_all_actions')
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns a failure message when the policy does not permit all actions.
|
34
|
+
#
|
35
|
+
# @return [String] A failure message when the policy does not permit all actions.
|
36
|
+
def failure_message
|
37
|
+
message = +"expected '#{policy_info}' to permit all actions,"
|
38
|
+
message << " but forbade #{policy_info.forbidden_actions}"
|
39
|
+
message << user_message
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'attributes_matcher'
|
4
|
+
|
5
|
+
module Pundit
|
6
|
+
module Matchers
|
7
|
+
# This matcher tests whether a policy permits or forbids the mass assignment of the expected attributes.
|
8
|
+
class PermitAttributesMatcher < AttributesMatcher
|
9
|
+
# A description of the matcher.
|
10
|
+
#
|
11
|
+
# @return [String] A description of the matcher.
|
12
|
+
def description
|
13
|
+
"permit the mass assignment of #{expected_attributes}"
|
14
|
+
end
|
15
|
+
|
16
|
+
# Checks if the given policy permits the mass assignment of the expected attributes.
|
17
|
+
#
|
18
|
+
# @param policy [Object] The policy to test.
|
19
|
+
# @return [Boolean] True if the policy permits the mass assignment of the expected attributes, false otherwise.
|
20
|
+
def matches?(policy)
|
21
|
+
setup_policy_info! policy
|
22
|
+
|
23
|
+
@actual_attributes = expected_attributes - permitted_attributes(policy)
|
24
|
+
|
25
|
+
actual_attributes.empty?
|
26
|
+
end
|
27
|
+
|
28
|
+
# Checks if the given policy forbids the mass assignment of the expected attributes.
|
29
|
+
#
|
30
|
+
# @param policy [Object] The policy to test.
|
31
|
+
# @return [Boolean] True if the policy forbids the mass assignment of the expected attributes, false otherwise.
|
32
|
+
def does_not_match?(policy)
|
33
|
+
setup_policy_info! policy
|
34
|
+
|
35
|
+
@actual_attributes = expected_attributes & permitted_attributes(policy)
|
36
|
+
|
37
|
+
actual_attributes.empty?
|
38
|
+
end
|
39
|
+
|
40
|
+
# The failure message when the expected attributes are forbidden.
|
41
|
+
#
|
42
|
+
# @return [String] A failure message when the expected attributes are not permitted.
|
43
|
+
def failure_message
|
44
|
+
message = +"expected '#{policy_info}' to permit the mass assignment of #{expected_attributes}"
|
45
|
+
message << action_message if options.key?(:action)
|
46
|
+
message << ", but forbade the mass assignment of #{actual_attributes}"
|
47
|
+
message << user_message
|
48
|
+
end
|
49
|
+
|
50
|
+
# The failure message when the expected attributes are permitted.
|
51
|
+
#
|
52
|
+
# @return [String] A failure message when the expected attributes are forbidden.
|
53
|
+
def failure_message_when_negated
|
54
|
+
message = +"expected '#{policy_info}' to forbid the mass assignment of #{expected_attributes}"
|
55
|
+
message << action_message if options.key?(:action)
|
56
|
+
message << ", but permitted the mass assignment of #{actual_attributes}"
|
57
|
+
message << user_message
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
attr_reader :actual_attributes
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'actions_matcher'
|
4
|
+
|
5
|
+
module Pundit
|
6
|
+
module Matchers
|
7
|
+
# This matcher tests whether a policy permits only the expected actions.
|
8
|
+
class PermitOnlyActionsMatcher < ActionsMatcher
|
9
|
+
# A description of the matcher.
|
10
|
+
#
|
11
|
+
# @return [String] A description of the matcher.
|
12
|
+
def description
|
13
|
+
"permit only #{expected_actions}"
|
14
|
+
end
|
15
|
+
|
16
|
+
# Checks if the given policy permits only the expected actions.
|
17
|
+
#
|
18
|
+
# @param policy [Object] The policy to test.
|
19
|
+
# @return [Boolean] True if the policy permits only the expected actions, false otherwise.
|
20
|
+
def matches?(policy)
|
21
|
+
setup_policy_info! policy
|
22
|
+
check_actions!
|
23
|
+
|
24
|
+
@actual_actions = policy_info.permitted_actions - expected_actions
|
25
|
+
@extra_actions = policy_info.forbidden_actions & expected_actions
|
26
|
+
|
27
|
+
actual_actions.empty? && extra_actions.empty?
|
28
|
+
end
|
29
|
+
|
30
|
+
# Raises a NotImplementedError
|
31
|
+
# @raise NotImplementedError
|
32
|
+
# @return [void]
|
33
|
+
def does_not_match?(_policy)
|
34
|
+
raise NotImplementedError, format(AMBIGUOUS_NEGATED_MATCHER_ERROR, name: 'permit_only_actions')
|
35
|
+
end
|
36
|
+
|
37
|
+
# The failure message when the expected actions and the permitted actions do not match.
|
38
|
+
#
|
39
|
+
# @return [String] A failure message when the expected actions and the permitted actions do not match.
|
40
|
+
def failure_message
|
41
|
+
message = +"expected '#{policy_info}' to permit only #{expected_actions},"
|
42
|
+
message << " but permitted #{actual_actions}" unless actual_actions.empty?
|
43
|
+
message << extra_message unless extra_actions.empty?
|
44
|
+
message << user_message
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
attr_reader :actual_actions, :extra_actions
|
50
|
+
|
51
|
+
def extra_message
|
52
|
+
if actual_actions.empty?
|
53
|
+
" but forbade #{extra_actions}"
|
54
|
+
else
|
55
|
+
" and forbade #{extra_actions}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pundit
|
4
|
+
module Matchers
|
5
|
+
module Utils
|
6
|
+
# This class provides methods to retrieve information about a policy class,
|
7
|
+
# such as the actions it defines and which of those actions are permitted
|
8
|
+
# or forbidden. It also provides a string representation of the policy class name
|
9
|
+
# and the user object associated with the policy.
|
10
|
+
class PolicyInfo
|
11
|
+
# Error message when policy does not respond to `user_alias`.
|
12
|
+
USER_NOT_IMPLEMENTED_ERROR = <<~MSG
|
13
|
+
'%<policy>s' does not implement '%<user_alias>s'. You may want to
|
14
|
+
configure an alias, which you can do as follows:
|
15
|
+
|
16
|
+
Pundit::Matchers.configure do |config|
|
17
|
+
# Alias for all policies
|
18
|
+
config.default_user_alias = :%<user_alias>s
|
19
|
+
|
20
|
+
# Per-policy alias
|
21
|
+
config.user_aliases = { '%<policy>s' => :%<user_alias>s }
|
22
|
+
end
|
23
|
+
MSG
|
24
|
+
|
25
|
+
attr_reader :policy
|
26
|
+
|
27
|
+
# Initializes a new instance of PolicyInfo.
|
28
|
+
#
|
29
|
+
# @param policy [Class] The policy class to collect details about.
|
30
|
+
def initialize(policy)
|
31
|
+
@policy = policy
|
32
|
+
check_user_alias!
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns a string representation of the policy class name.
|
36
|
+
#
|
37
|
+
# @return [String] A string representation of the policy class name.
|
38
|
+
def to_s
|
39
|
+
policy.class.name
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns the user object associated with the policy.
|
43
|
+
#
|
44
|
+
# @return [Object] The user object associated with the policy.
|
45
|
+
def user
|
46
|
+
@user ||= policy.public_send(user_alias)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns an array of all actions defined in the policy class.
|
50
|
+
#
|
51
|
+
# It assumes that actions are defined as public instance methods that end with a question mark.
|
52
|
+
#
|
53
|
+
# @return [Array<Symbol>] An array of all actions defined in the policy class.
|
54
|
+
def actions
|
55
|
+
@actions ||= policy_public_methods.grep(/\?$/).sort.map do |policy_method|
|
56
|
+
policy_method.to_s.delete_suffix('?').to_sym
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Returns an array of all permitted actions defined in the policy class.
|
61
|
+
#
|
62
|
+
# @return [Array<Symbol>] An array of all permitted actions defined in the policy class.
|
63
|
+
def permitted_actions
|
64
|
+
@permitted_actions ||= actions.select { |action| policy.public_send(:"#{action}?") }
|
65
|
+
end
|
66
|
+
|
67
|
+
# Returns an array of all forbidden actions defined in the policy class.
|
68
|
+
#
|
69
|
+
# @return [Array<Symbol>] An array of all forbidden actions defined in the policy class.
|
70
|
+
def forbidden_actions
|
71
|
+
@forbidden_actions ||= actions - permitted_actions
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def policy_public_methods
|
77
|
+
@policy_public_methods ||= policy.public_methods - Object.instance_methods
|
78
|
+
end
|
79
|
+
|
80
|
+
def user_alias
|
81
|
+
@user_alias ||= Pundit::Matchers.configuration.user_alias(policy)
|
82
|
+
end
|
83
|
+
|
84
|
+
def check_user_alias!
|
85
|
+
return if policy.respond_to?(user_alias)
|
86
|
+
|
87
|
+
raise ArgumentError, format(USER_NOT_IMPLEMENTED_ERROR, policy: self, user_alias: user_alias)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
data/lib/pundit/matchers.rb
CHANGED
@@ -1,373 +1,186 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
module Pundit
|
4
|
-
module Matchers
|
5
|
-
class Configuration
|
6
|
-
attr_accessor :user_alias
|
7
|
-
|
8
|
-
def initialize
|
9
|
-
@user_alias = :user
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
class << self
|
14
|
-
def configure
|
15
|
-
yield(configuration)
|
16
|
-
end
|
17
|
-
|
18
|
-
def configuration
|
19
|
-
@configuration ||= Pundit::Matchers::Configuration.new
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
RSpec::Matchers.define :forbid_action do |action, *args|
|
24
|
-
match do |policy|
|
25
|
-
if args.any?
|
26
|
-
!policy.public_send("#{action}?", *args)
|
27
|
-
else
|
28
|
-
!policy.public_send("#{action}?")
|
29
|
-
end
|
30
|
-
end
|
1
|
+
# frozen_string_literal: true
|
31
2
|
|
32
|
-
|
33
|
-
"#{policy.class} does not forbid #{action} for " +
|
34
|
-
policy.public_send(Pundit::Matchers.configuration.user_alias)
|
35
|
-
.inspect + '.'
|
36
|
-
end
|
37
|
-
|
38
|
-
failure_message_when_negated do |policy|
|
39
|
-
"#{policy.class} does not permit #{action} for " +
|
40
|
-
policy.public_send(Pundit::Matchers.configuration.user_alias)
|
41
|
-
.inspect + '.'
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
RSpec::Matchers.define :forbid_actions do |*actions|
|
47
|
-
actions.flatten!
|
48
|
-
match do |policy|
|
49
|
-
return false if actions.count < 1
|
50
|
-
@allowed_actions = actions.select do |action|
|
51
|
-
policy.public_send("#{action}?")
|
52
|
-
end
|
53
|
-
@allowed_actions.empty?
|
54
|
-
end
|
55
|
-
|
56
|
-
attr_reader :allowed_actions
|
57
|
-
|
58
|
-
zero_actions_failure_message = 'At least one action must be ' \
|
59
|
-
'specified when using the forbid_actions matcher.'
|
60
|
-
|
61
|
-
failure_message do |policy|
|
62
|
-
if actions.count.zero?
|
63
|
-
zero_actions_failure_message
|
64
|
-
else
|
65
|
-
"#{policy.class} expected to forbid #{actions}, but allowed " \
|
66
|
-
"#{allowed_actions} for " +
|
67
|
-
policy.public_send(Pundit::Matchers.configuration.user_alias)
|
68
|
-
.inspect + '.'
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
failure_message_when_negated do |policy|
|
73
|
-
if actions.count.zero?
|
74
|
-
zero_actions_failure_message
|
75
|
-
else
|
76
|
-
"#{policy.class} expected to permit #{actions}, but forbade " \
|
77
|
-
"#{allowed_actions} for " +
|
78
|
-
policy.public_send(Pundit::Matchers.configuration.user_alias)
|
79
|
-
.inspect + '.'
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
RSpec::Matchers.define :forbid_edit_and_update_actions do
|
85
|
-
match do |policy|
|
86
|
-
!policy.edit? && !policy.update?
|
87
|
-
end
|
88
|
-
|
89
|
-
failure_message do |policy|
|
90
|
-
"#{policy.class} does not forbid the edit or update action for " +
|
91
|
-
policy.public_send(Pundit::Matchers.configuration.user_alias)
|
92
|
-
.inspect + '.'
|
93
|
-
end
|
3
|
+
require 'rspec/core'
|
94
4
|
|
95
|
-
|
96
|
-
"#{policy.class} does not permit the edit or update action for " +
|
97
|
-
policy.public_send(Pundit::Matchers.configuration.user_alias)
|
98
|
-
.inspect + '.'
|
99
|
-
end
|
100
|
-
end
|
5
|
+
require_relative 'matchers/permit_actions_matcher'
|
101
6
|
|
102
|
-
|
103
|
-
# Map single object argument to an array, if necessary
|
104
|
-
attributes = attributes.is_a?(Array) ? attributes : [attributes]
|
7
|
+
require_relative 'matchers/permit_attributes_matcher'
|
105
8
|
|
106
|
-
|
107
|
-
|
9
|
+
require_relative 'matchers/forbid_all_actions_matcher'
|
10
|
+
require_relative 'matchers/forbid_only_actions_matcher'
|
108
11
|
|
109
|
-
|
110
|
-
|
111
|
-
policy.send("permitted_attributes_for_#{@action}").include? attribute
|
112
|
-
else
|
113
|
-
policy.permitted_attributes.include? attribute
|
114
|
-
end
|
115
|
-
end
|
12
|
+
require_relative 'matchers/permit_all_actions_matcher'
|
13
|
+
require_relative 'matchers/permit_only_actions_matcher'
|
116
14
|
|
117
|
-
|
118
|
-
|
15
|
+
module Pundit
|
16
|
+
# Matchers module provides a set of RSpec matchers for testing Pundit policies.
|
17
|
+
module Matchers
|
18
|
+
# A Proc that negates the description of a matcher.
|
19
|
+
NEGATED_DESCRIPTION = ->(description) { description.gsub(/^permit/, 'forbid') }
|
119
20
|
|
120
|
-
|
21
|
+
# Configuration class for Pundit Matchers.
|
22
|
+
class Configuration
|
23
|
+
# The default user object value
|
24
|
+
DEFAULT_USER_ALIAS = :user
|
121
25
|
|
122
|
-
|
123
|
-
@
|
124
|
-
|
26
|
+
# The default user object in policies.
|
27
|
+
# @return [Symbol|String]
|
28
|
+
attr_accessor :default_user_alias
|
125
29
|
|
126
|
-
|
127
|
-
|
30
|
+
# Policy-specific user objects.
|
31
|
+
#
|
32
|
+
# @example Use +:client+ as user alias for class +Post+
|
33
|
+
# config.user_aliases = { 'Post' => :client }
|
34
|
+
#
|
35
|
+
# @return [Hash]
|
36
|
+
attr_accessor :user_aliases
|
128
37
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
elsif defined? @action
|
133
|
-
"#{policy.class} expected to forbid the mass assignment of the " \
|
134
|
-
"attributes #{attributes} when authorising the #{@action} action, " \
|
135
|
-
'but allowed the mass assignment of the attributes ' \
|
136
|
-
"#{allowed_attributes} for " +
|
137
|
-
policy.public_send(Pundit::Matchers.configuration.user_alias)
|
138
|
-
.inspect + '.'
|
139
|
-
else
|
140
|
-
"#{policy.class} expected to forbid the mass assignment of the " \
|
141
|
-
"attributes #{attributes}, but allowed the mass assignment of " \
|
142
|
-
"the attributes #{allowed_attributes} for " +
|
143
|
-
policy.public_send(Pundit::Matchers.configuration.user_alias)
|
144
|
-
.inspect + '.'
|
38
|
+
def initialize
|
39
|
+
@default_user_alias = DEFAULT_USER_ALIAS
|
40
|
+
@user_aliases = {}
|
145
41
|
end
|
146
|
-
end
|
147
42
|
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
"attributes #{attributes} when authorising the #{@action} action, " \
|
154
|
-
'but permitted the mass assignment of the attributes ' \
|
155
|
-
"#{allowed_attributes} for " +
|
156
|
-
policy.public_send(Pundit::Matchers.configuration.user_alias)
|
157
|
-
.inspect + '.'
|
158
|
-
else
|
159
|
-
"#{policy.class} expected to permit the mass assignment of the " \
|
160
|
-
"attributes #{attributes}, but permitted the mass assignment of " \
|
161
|
-
"the attributes #{allowed_attributes} for " +
|
162
|
-
policy.public_send(Pundit::Matchers.configuration.user_alias)
|
163
|
-
.inspect + '.'
|
43
|
+
# Returns the user object for the given policy.
|
44
|
+
#
|
45
|
+
# @return [Symbol]
|
46
|
+
def user_alias(policy)
|
47
|
+
user_aliases.fetch(policy.class.name, default_user_alias)
|
164
48
|
end
|
165
49
|
end
|
166
|
-
end
|
167
|
-
|
168
|
-
RSpec::Matchers.define :forbid_new_and_create_actions do
|
169
|
-
match do |policy|
|
170
|
-
!policy.new? && !policy.create?
|
171
|
-
end
|
172
|
-
|
173
|
-
failure_message do |policy|
|
174
|
-
"#{policy.class} does not forbid the new or create action for " +
|
175
|
-
policy.public_send(Pundit::Matchers.configuration.user_alias)
|
176
|
-
.inspect + '.'
|
177
|
-
end
|
178
|
-
|
179
|
-
failure_message_when_negated do |policy|
|
180
|
-
"#{policy.class} does not permit the new or create action for " +
|
181
|
-
policy.public_send(Pundit::Matchers.configuration.user_alias)
|
182
|
-
.inspect + '.'
|
183
|
-
end
|
184
|
-
end
|
185
50
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
51
|
+
class << self
|
52
|
+
# Configures Pundit Matchers.
|
53
|
+
#
|
54
|
+
# @yieldparam [Configuration] configuration the configuration object to be modified.
|
55
|
+
def configure
|
56
|
+
yield(configuration)
|
192
57
|
end
|
193
|
-
end
|
194
58
|
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
failure_message_when_negated do |policy|
|
202
|
-
"#{policy.class} does not forbid #{action} for " +
|
203
|
-
policy.public_send(Pundit::Matchers.configuration.user_alias)
|
204
|
-
.inspect + '.'
|
205
|
-
end
|
206
|
-
end
|
207
|
-
|
208
|
-
RSpec::Matchers.define :permit_actions do |*actions|
|
209
|
-
actions.flatten!
|
210
|
-
match do |policy|
|
211
|
-
return false if actions.count < 1
|
212
|
-
@forbidden_actions = actions.reject do |action|
|
213
|
-
policy.public_send("#{action}?")
|
59
|
+
# Returns the configuration object for Pundit Matchers.
|
60
|
+
#
|
61
|
+
# @return [Configuration] the configuration object.
|
62
|
+
def configuration
|
63
|
+
@configuration ||= Pundit::Matchers::Configuration.new
|
214
64
|
end
|
215
|
-
@forbidden_actions.empty?
|
216
65
|
end
|
217
66
|
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
it { is_expected.to permit_actions([:new, :create, :edit]) } \
|
226
|
-
it { is_expected.not_to permit_actions([:edit, :destroy]) } \
|
227
|
-
|
228
|
-
In this case, edit would be true and destroy would be false, but both \
|
229
|
-
tests would pass.'
|
230
|
-
|
231
|
-
return true if actions.count < 1
|
232
|
-
@forbidden_actions = actions.reject do |action|
|
233
|
-
policy.public_send("#{action}?")
|
234
|
-
end
|
235
|
-
!@forbidden_actions.empty?
|
67
|
+
# Creates a matcher that tests if the policy permits a given action.
|
68
|
+
#
|
69
|
+
# @param [String|Symbol] action the action to be tested.
|
70
|
+
# @return [PermitActionsMatcher] the matcher object.
|
71
|
+
def permit_action(action)
|
72
|
+
PermitActionsMatcher.new(action).ensure_single_action!
|
236
73
|
end
|
237
74
|
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
75
|
+
# @!macro [attach] RSpec::Matchers.define_negated_matcher
|
76
|
+
# @!method $1
|
77
|
+
#
|
78
|
+
# The negated matcher of {$2}.
|
79
|
+
#
|
80
|
+
# Same as +expect(policy).not_to $2(*args)+.
|
81
|
+
RSpec::Matchers.define_negated_matcher :forbid_action, :permit_action, &NEGATED_DESCRIPTION
|
242
82
|
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
policy.public_send(Pundit::Matchers.configuration.user_alias)
|
250
|
-
.inspect + '.'
|
251
|
-
end
|
83
|
+
# Creates a matcher that tests if the policy permits a set of actions.
|
84
|
+
#
|
85
|
+
# @param [Array<String, Symbol>] actions the actions to be tested.
|
86
|
+
# @return [PermitActionsMatcher] the matcher object.
|
87
|
+
def permit_actions(*actions)
|
88
|
+
PermitActionsMatcher.new(*actions)
|
252
89
|
end
|
253
90
|
|
254
|
-
|
255
|
-
if actions.count.zero?
|
256
|
-
zero_actions_failure_message
|
257
|
-
else
|
258
|
-
"#{policy.class} expected to forbid #{actions}, but allowed " \
|
259
|
-
"#{forbidden_actions} for " +
|
260
|
-
policy.public_send(Pundit::Matchers.configuration.user_alias)
|
261
|
-
.inspect + '.'
|
262
|
-
end
|
263
|
-
end
|
264
|
-
end
|
91
|
+
RSpec::Matchers.define_negated_matcher :forbid_actions, :permit_actions, &NEGATED_DESCRIPTION
|
265
92
|
|
266
|
-
|
267
|
-
|
268
|
-
|
93
|
+
# Creates a matcher that tests if the policy permits all actions.
|
94
|
+
#
|
95
|
+
# @note The negative form +not_to permit_all_actions+ is not supported
|
96
|
+
# since it creates ambiguity. Instead use +to forbid_all_actions+.
|
97
|
+
#
|
98
|
+
# @return [PermitAllActionsMatcher] the matcher object.
|
99
|
+
def permit_all_actions
|
100
|
+
PermitAllActionsMatcher.new
|
269
101
|
end
|
270
102
|
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
103
|
+
# Creates a matcher that tests if the policy forbids all actions.
|
104
|
+
#
|
105
|
+
# @note The negative form +not_to forbid_all_actions+ is not supported
|
106
|
+
# since it creates ambiguity. Instead use +to permit_all_actions+.
|
107
|
+
#
|
108
|
+
# @return [ForbidAllActionsMatcher] the matcher object.
|
109
|
+
def forbid_all_actions
|
110
|
+
ForbidAllActionsMatcher.new
|
275
111
|
end
|
276
112
|
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
113
|
+
# Creates a matcher that tests if the policy permits the edit and update actions.
|
114
|
+
#
|
115
|
+
# @return [PermitActionsMatcher] the matcher object.
|
116
|
+
def permit_edit_and_update_actions
|
117
|
+
PermitActionsMatcher.new(:edit, :update)
|
281
118
|
end
|
282
|
-
end
|
283
|
-
|
284
|
-
RSpec::Matchers.define :permit_mass_assignment_of do |attributes|
|
285
|
-
# Map single object argument to an array, if necessary
|
286
|
-
attributes = attributes.is_a?(Array) ? attributes : [attributes]
|
287
119
|
|
288
|
-
|
289
|
-
|
120
|
+
RSpec::Matchers.define_negated_matcher :forbid_edit_and_update_actions, :permit_edit_and_update_actions,
|
121
|
+
&NEGATED_DESCRIPTION
|
290
122
|
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
end
|
297
|
-
end
|
298
|
-
|
299
|
-
@forbidden_attributes.empty?
|
123
|
+
# Creates a matcher that tests if the policy permits the new and create actions.
|
124
|
+
#
|
125
|
+
# @return [PermitActionsMatcher] the matcher object.
|
126
|
+
def permit_new_and_create_actions
|
127
|
+
PermitActionsMatcher.new(:new, :create)
|
300
128
|
end
|
301
129
|
|
302
|
-
|
130
|
+
RSpec::Matchers.define_negated_matcher :forbid_new_and_create_actions, :permit_new_and_create_actions,
|
131
|
+
&NEGATED_DESCRIPTION
|
303
132
|
|
304
|
-
|
305
|
-
|
133
|
+
# Creates a matcher that tests if the policy permits only a set of actions.
|
134
|
+
#
|
135
|
+
# @note The negative form +not_to permit_only_actions+ is not supported
|
136
|
+
# since it creates ambiguity. Instead use +to forbid_only_actions+.
|
137
|
+
#
|
138
|
+
# @param [Array<String, Symbol>] actions the actions to be tested.
|
139
|
+
# @return [PermitOnlyActionsMatcher] the matcher object.
|
140
|
+
def permit_only_actions(*actions)
|
141
|
+
PermitOnlyActionsMatcher.new(*actions)
|
306
142
|
end
|
307
143
|
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
'but forbade the mass assignment of the attributes ' \
|
318
|
-
"#{forbidden_attributes} for " +
|
319
|
-
policy.public_send(Pundit::Matchers.configuration.user_alias)
|
320
|
-
.inspect + '.'
|
321
|
-
else
|
322
|
-
"#{policy.class} expected to permit the mass assignment of the " \
|
323
|
-
"attributes #{attributes}, but forbade the mass assignment of the " \
|
324
|
-
"attributes #{forbidden_attributes} for " +
|
325
|
-
policy.public_send(Pundit::Matchers.configuration.user_alias)
|
326
|
-
.inspect + '.'
|
327
|
-
end
|
144
|
+
# Creates a matcher that tests if the policy forbids only a set of actions.
|
145
|
+
#
|
146
|
+
# @note The negative form +not_to forbid_only_actions+ is not supported
|
147
|
+
# since it creates ambiguity. Instead use +to permit_only_actions+.
|
148
|
+
#
|
149
|
+
# @param [Array<String, Symbol>] actions the actions to be tested.
|
150
|
+
# @return [ForbidOnlyActionsMatcher] the matcher object.
|
151
|
+
def forbid_only_actions(*actions)
|
152
|
+
ForbidOnlyActionsMatcher.new(*actions)
|
328
153
|
end
|
329
154
|
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
'but forbade the mass assignment of the attributes ' \
|
337
|
-
"#{forbidden_attributes} for " +
|
338
|
-
policy.public_send(Pundit::Matchers.configuration.user_alias)
|
339
|
-
.inspect + '.'
|
340
|
-
else
|
341
|
-
"#{policy.class} expected to forbid the mass assignment of the " \
|
342
|
-
"attributes #{attributes}, but forbade the mass assignment of the " \
|
343
|
-
"attributes #{forbidden_attributes} for " +
|
344
|
-
policy.public_send(Pundit::Matchers.configuration.user_alias)
|
345
|
-
.inspect + '.'
|
346
|
-
end
|
155
|
+
# Creates a matcher that tests if the policy permits mass assignment of an attribute.
|
156
|
+
#
|
157
|
+
# @param [String, Symbol, Hash] attribute the attribute to be tested.
|
158
|
+
# @return [PermitAttributesMatcher] the matcher object.
|
159
|
+
def permit_attribute(attribute)
|
160
|
+
PermitAttributesMatcher.new(attribute).ensure_single_attribute!
|
347
161
|
end
|
348
|
-
end
|
349
162
|
|
350
|
-
|
351
|
-
match do |policy|
|
352
|
-
policy.new? && policy.create?
|
353
|
-
end
|
354
|
-
|
355
|
-
failure_message do |policy|
|
356
|
-
"#{policy.class} does not permit the new or create action for " +
|
357
|
-
policy.public_send(Pundit::Matchers.configuration.user_alias)
|
358
|
-
.inspect + '.'
|
359
|
-
end
|
163
|
+
RSpec::Matchers.define_negated_matcher :forbid_attribute, :permit_attribute, &NEGATED_DESCRIPTION
|
360
164
|
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
165
|
+
# Creates a matcher that tests if the policy permits mass assignment of a set of attributes.
|
166
|
+
#
|
167
|
+
# @param [Array<String, Symbol, Hash>] attributes the attributes to be tested.
|
168
|
+
# @return [PermitAttributesMatcher] the matcher object.
|
169
|
+
def permit_attributes(*attributes)
|
170
|
+
PermitAttributesMatcher.new(*attributes)
|
171
|
+
end
|
172
|
+
|
173
|
+
RSpec::Matchers.define_negated_matcher :forbid_attributes, :permit_attributes, &NEGATED_DESCRIPTION
|
174
|
+
|
175
|
+
# @!macro [attach] RSpec::Matchers.alias_matcher
|
176
|
+
# @!method $1
|
177
|
+
#
|
178
|
+
# An alias matcher for {$2}.
|
179
|
+
RSpec::Matchers.alias_matcher :permit_mass_assignment_of, :permit_attributes
|
180
|
+
RSpec::Matchers.alias_matcher :forbid_mass_assignment_of, :forbid_attributes
|
366
181
|
end
|
367
182
|
end
|
368
183
|
|
369
|
-
|
370
|
-
|
371
|
-
config.include Pundit::Matchers
|
372
|
-
end
|
184
|
+
RSpec.configure do |config|
|
185
|
+
config.include Pundit::Matchers
|
373
186
|
end
|
metadata
CHANGED
@@ -1,49 +1,71 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pundit-matchers
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 3.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Alley
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-06-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name: rspec-
|
14
|
+
name: rspec-core
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 3.
|
19
|
+
version: '3.12'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 3.
|
26
|
+
version: '3.12'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: rspec-expectations
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
34
|
-
|
33
|
+
version: '3.12'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.12'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec-mocks
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
35
46
|
- !ruby/object:Gem::Version
|
36
|
-
version:
|
37
|
-
type: :
|
47
|
+
version: '3.12'
|
48
|
+
type: :runtime
|
38
49
|
prerelease: false
|
39
50
|
version_requirements: !ruby/object:Gem::Requirement
|
40
51
|
requirements:
|
41
52
|
- - "~>"
|
42
53
|
- !ruby/object:Gem::Version
|
43
|
-
version: '
|
44
|
-
|
54
|
+
version: '3.12'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec-support
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.12'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
45
67
|
- !ruby/object:Gem::Version
|
46
|
-
version:
|
68
|
+
version: '3.12'
|
47
69
|
description: A set of RSpec matchers for testing Pundit authorisation policies
|
48
70
|
email: chris@chrisalley.info
|
49
71
|
executables: []
|
@@ -51,10 +73,21 @@ extensions: []
|
|
51
73
|
extra_rdoc_files: []
|
52
74
|
files:
|
53
75
|
- lib/pundit/matchers.rb
|
54
|
-
|
76
|
+
- lib/pundit/matchers/actions_matcher.rb
|
77
|
+
- lib/pundit/matchers/attributes_matcher.rb
|
78
|
+
- lib/pundit/matchers/base_matcher.rb
|
79
|
+
- lib/pundit/matchers/forbid_all_actions_matcher.rb
|
80
|
+
- lib/pundit/matchers/forbid_only_actions_matcher.rb
|
81
|
+
- lib/pundit/matchers/permit_actions_matcher.rb
|
82
|
+
- lib/pundit/matchers/permit_all_actions_matcher.rb
|
83
|
+
- lib/pundit/matchers/permit_attributes_matcher.rb
|
84
|
+
- lib/pundit/matchers/permit_only_actions_matcher.rb
|
85
|
+
- lib/pundit/matchers/utils/policy_info.rb
|
86
|
+
homepage: https://github.com/punditcommunity/pundit-matchers
|
55
87
|
licenses:
|
56
88
|
- MIT
|
57
|
-
metadata:
|
89
|
+
metadata:
|
90
|
+
rubygems_mfa_required: 'true'
|
58
91
|
post_install_message:
|
59
92
|
rdoc_options: []
|
60
93
|
require_paths:
|
@@ -63,14 +96,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
63
96
|
requirements:
|
64
97
|
- - ">="
|
65
98
|
- !ruby/object:Gem::Version
|
66
|
-
version: '0'
|
99
|
+
version: '3.0'
|
67
100
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
101
|
requirements:
|
69
102
|
- - ">="
|
70
103
|
- !ruby/object:Gem::Version
|
71
104
|
version: '0'
|
72
105
|
requirements: []
|
73
|
-
rubygems_version: 3.
|
106
|
+
rubygems_version: 3.4.12
|
74
107
|
signing_key:
|
75
108
|
specification_version: 4
|
76
109
|
summary: RSpec matchers for Pundit policies
|