pundit 2.4.0 → 2.5.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/.github/workflows/main.yml +92 -57
- data/.rubocop.yml +18 -8
- data/.rubocop_ignore_git.yml +7 -0
- data/.yardopts +1 -1
- data/CHANGELOG.md +61 -42
- data/Gemfile +22 -2
- data/README.md +30 -0
- data/Rakefile +1 -0
- data/lib/generators/pundit/install/install_generator.rb +3 -1
- data/lib/generators/pundit/policy/policy_generator.rb +3 -1
- data/lib/generators/rspec/policy_generator.rb +4 -1
- data/lib/generators/test_unit/policy_generator.rb +4 -1
- data/lib/pundit/authorization.rb +152 -77
- data/lib/pundit/cache_store/legacy_store.rb +7 -0
- data/lib/pundit/cache_store/null_store.rb +9 -0
- data/lib/pundit/cache_store.rb +22 -0
- data/lib/pundit/context.rb +76 -26
- data/lib/pundit/policy_finder.rb +22 -1
- data/lib/pundit/railtie.rb +19 -0
- data/lib/pundit/rspec.rb +67 -6
- data/lib/pundit/version.rb +2 -1
- data/lib/pundit.rb +39 -14
- data/pundit.gemspec +8 -12
- data/spec/authorization_spec.rb +60 -3
- data/spec/policy_finder_spec.rb +5 -1
- data/spec/pundit/helper_spec.rb +18 -0
- data/spec/pundit_spec.rb +37 -11
- data/spec/rspec_dsl_spec.rb +81 -0
- data/spec/simple_cov_check_action_formatter.rb +79 -0
- data/spec/spec_helper.rb +22 -339
- data/spec/support/lib/controller.rb +38 -0
- data/spec/support/lib/custom_cache.rb +19 -0
- data/spec/support/lib/instance_tracking.rb +20 -0
- data/spec/support/models/article.rb +4 -0
- data/spec/support/models/article_tag.rb +7 -0
- data/spec/support/models/artificial_blog.rb +7 -0
- data/spec/support/models/blog.rb +4 -0
- data/spec/support/models/comment.rb +5 -0
- data/spec/support/models/comment_four_five_six.rb +5 -0
- data/spec/support/models/comment_scope.rb +13 -0
- data/spec/support/models/comments_relation.rb +15 -0
- data/spec/support/models/customer/post.rb +11 -0
- data/spec/support/models/default_scope_contains_error.rb +5 -0
- data/spec/support/models/dummy_current_user.rb +7 -0
- data/spec/support/models/foo.rb +4 -0
- data/spec/support/models/post.rb +25 -0
- data/spec/support/models/post_four_five_six.rb +9 -0
- data/spec/support/models/project_one_two_three/avatar_four_five_six.rb +7 -0
- data/spec/support/models/project_one_two_three/tag_four_five_six.rb +11 -0
- data/spec/support/models/wiki.rb +4 -0
- data/spec/support/policies/article_tag_other_name_policy.rb +13 -0
- data/spec/support/policies/base_policy.rb +23 -0
- data/spec/support/policies/blog_policy.rb +5 -0
- data/spec/support/policies/comment_policy.rb +11 -0
- data/spec/support/policies/criteria_policy.rb +5 -0
- data/spec/support/policies/default_scope_contains_error_policy.rb +10 -0
- data/spec/support/policies/denier_policy.rb +7 -0
- data/spec/support/policies/dummy_current_user_policy.rb +9 -0
- data/spec/support/policies/nil_class_policy.rb +17 -0
- data/spec/support/policies/post_policy.rb +36 -0
- data/spec/support/policies/project/admin/comment_policy.rb +15 -0
- data/spec/support/policies/project/comment_policy.rb +17 -0
- data/spec/support/policies/project/criteria_policy.rb +7 -0
- data/spec/support/policies/project/post_policy.rb +13 -0
- data/spec/support/policies/project_one_two_three/avatar_four_five_six_policy.rb +6 -0
- data/spec/support/policies/project_one_two_three/comment_four_five_six_policy.rb +6 -0
- data/spec/support/policies/project_one_two_three/criteria_four_five_six_policy.rb +6 -0
- data/spec/support/policies/project_one_two_three/post_four_five_six_policy.rb +6 -0
- data/spec/support/policies/project_one_two_three/tag_four_five_six_policy.rb +6 -0
- data/spec/support/policies/publication_policy.rb +13 -0
- data/spec/support/policies/wiki_policy.rb +8 -0
- metadata +62 -158
- data/spec/dsl_spec.rb +0 -30
- /data/lib/generators/pundit/install/templates/{application_policy.rb → application_policy.rb.tt} +0 -0
- /data/lib/generators/pundit/policy/templates/{policy.rb → policy.rb.tt} +0 -0
- /data/lib/generators/rspec/templates/{policy_spec.rb → policy_spec.rb.tt} +0 -0
- /data/lib/generators/test_unit/templates/{policy_test.rb → policy_test.rb.tt} +0 -0
data/lib/pundit/rspec.rb
CHANGED
@@ -1,13 +1,29 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# Array#to_sentence
|
4
|
+
require "active_support/core_ext/array/conversions"
|
5
|
+
|
3
6
|
module Pundit
|
7
|
+
# Namespace for Pundit's RSpec integration.
|
4
8
|
module RSpec
|
9
|
+
# Namespace for Pundit's RSpec matchers.
|
5
10
|
module Matchers
|
6
11
|
extend ::RSpec::Matchers::DSL
|
7
12
|
|
13
|
+
# @!method description=(description)
|
8
14
|
class << self
|
15
|
+
# Used to build a suitable description for the Pundit `permit` matcher.
|
16
|
+
# @api public
|
17
|
+
# @param value [String, Proc]
|
18
|
+
# @example
|
19
|
+
# Pundit::RSpec::Matchers.description = ->(user, record) do
|
20
|
+
# "permit user with role #{user.role} to access record with ID #{record.id}"
|
21
|
+
# end
|
9
22
|
attr_writer :description
|
10
23
|
|
24
|
+
# Used to retrieve a suitable description for the Pundit `permit` matcher.
|
25
|
+
# @api private
|
26
|
+
# @private
|
11
27
|
def description(user, record)
|
12
28
|
return @description.call(user, record) if defined?(@description) && @description.respond_to?(:call)
|
13
29
|
|
@@ -32,15 +48,21 @@ module Pundit
|
|
32
48
|
end
|
33
49
|
|
34
50
|
failure_message_proc = lambda do |policy|
|
35
|
-
was_were = @violating_permissions.count > 1 ? "were" : "was"
|
36
51
|
"Expected #{policy} to grant #{permissions.to_sentence} on " \
|
37
|
-
"#{record} but #{@violating_permissions.to_sentence} #{
|
52
|
+
"#{record} but #{@violating_permissions.to_sentence} #{was_or_were} not granted"
|
38
53
|
end
|
39
54
|
|
40
55
|
failure_message_when_negated_proc = lambda do |policy|
|
41
|
-
was_were = @violating_permissions.count > 1 ? "were" : "was"
|
42
56
|
"Expected #{policy} not to grant #{permissions.to_sentence} on " \
|
43
|
-
"#{record} but #{@violating_permissions.to_sentence} #{
|
57
|
+
"#{record} but #{@violating_permissions.to_sentence} #{was_or_were} granted"
|
58
|
+
end
|
59
|
+
|
60
|
+
def was_or_were
|
61
|
+
if @violating_permissions.count > 1
|
62
|
+
"were"
|
63
|
+
else
|
64
|
+
"was"
|
65
|
+
end
|
44
66
|
end
|
45
67
|
|
46
68
|
description do
|
@@ -53,21 +75,57 @@ module Pundit
|
|
53
75
|
failure_message(&failure_message_proc)
|
54
76
|
failure_message_when_negated(&failure_message_when_negated_proc)
|
55
77
|
else
|
78
|
+
# :nocov:
|
79
|
+
# Compatibility with RSpec < 3.0, released 2014-06-01.
|
56
80
|
match_for_should(&match_proc)
|
57
81
|
match_for_should_not(&match_when_negated_proc)
|
58
82
|
failure_message_for_should(&failure_message_proc)
|
59
83
|
failure_message_for_should_not(&failure_message_when_negated_proc)
|
84
|
+
# :nocov:
|
85
|
+
end
|
86
|
+
|
87
|
+
if ::RSpec.respond_to?(:current_example)
|
88
|
+
def current_example
|
89
|
+
::RSpec.current_example
|
90
|
+
end
|
91
|
+
else
|
92
|
+
# :nocov:
|
93
|
+
# Compatibility with RSpec < 3.0, released 2014-06-01.
|
94
|
+
def current_example
|
95
|
+
example
|
96
|
+
end
|
97
|
+
# :nocov:
|
60
98
|
end
|
61
99
|
|
62
100
|
def permissions
|
63
|
-
current_example
|
64
|
-
|
101
|
+
current_example.metadata.fetch(:permissions) do
|
102
|
+
raise KeyError, <<~ERROR.strip
|
103
|
+
No permissions in example metadata, did you forget to wrap with `permissions :show?, ...`?
|
104
|
+
ERROR
|
105
|
+
end
|
65
106
|
end
|
66
107
|
end
|
67
108
|
# rubocop:enable Metrics/BlockLength
|
68
109
|
end
|
69
110
|
|
111
|
+
# Mixed in to all policy example groups to provide a DSL.
|
70
112
|
module DSL
|
113
|
+
# @example
|
114
|
+
# describe PostPolicy do
|
115
|
+
# permissions :show?, :update? do
|
116
|
+
# it { is_expected.to permit(user, own_post) }
|
117
|
+
# end
|
118
|
+
# end
|
119
|
+
#
|
120
|
+
# @example focused example group
|
121
|
+
# describe PostPolicy do
|
122
|
+
# permissions :show?, :update?, :focus do
|
123
|
+
# it { is_expected.to permit(user, own_post) }
|
124
|
+
# end
|
125
|
+
# end
|
126
|
+
#
|
127
|
+
# @param list [Symbol, Array<Symbol>] a permission to describe
|
128
|
+
# @return [void]
|
71
129
|
def permissions(*list, &block)
|
72
130
|
metadata = { permissions: list, caller: caller }
|
73
131
|
|
@@ -81,6 +139,9 @@ module Pundit
|
|
81
139
|
end
|
82
140
|
end
|
83
141
|
|
142
|
+
# Mixed in to all policy example groups.
|
143
|
+
#
|
144
|
+
# @private not useful
|
84
145
|
module PolicyExampleGroup
|
85
146
|
include Pundit::RSpec::Matchers
|
86
147
|
|
data/lib/pundit/version.rb
CHANGED
data/lib/pundit.rb
CHANGED
@@ -1,33 +1,53 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_support"
|
4
|
+
|
3
5
|
require "pundit/version"
|
4
6
|
require "pundit/policy_finder"
|
5
|
-
require "active_support/concern"
|
6
|
-
require "active_support/core_ext/string/inflections"
|
7
|
-
require "active_support/core_ext/object/blank"
|
8
|
-
require "active_support/core_ext/module/introspection"
|
9
|
-
require "active_support/dependencies/autoload"
|
10
7
|
require "pundit/authorization"
|
11
8
|
require "pundit/context"
|
9
|
+
require "pundit/cache_store"
|
12
10
|
require "pundit/cache_store/null_store"
|
13
11
|
require "pundit/cache_store/legacy_store"
|
12
|
+
require "pundit/railtie" if defined?(Rails)
|
14
13
|
|
15
14
|
# @api private
|
16
15
|
# To avoid name clashes with common Error naming when mixing in Pundit,
|
17
16
|
# keep it here with compact class style definition.
|
18
17
|
class Pundit::Error < StandardError; end # rubocop:disable Style/ClassAndModuleChildren
|
19
18
|
|
19
|
+
# Hello? Yes, this is Pundit.
|
20
|
+
#
|
20
21
|
# @api public
|
21
22
|
module Pundit
|
22
|
-
|
23
|
+
# @api private
|
24
|
+
# @deprecated See {Pundit::PolicyFinder}
|
25
|
+
SUFFIX = Pundit::PolicyFinder::SUFFIX
|
23
26
|
|
24
27
|
# @api private
|
28
|
+
# @private
|
25
29
|
module Generators; end
|
26
30
|
|
27
31
|
# Error that will be raised when authorization has failed
|
28
32
|
class NotAuthorizedError < Error
|
29
|
-
|
30
|
-
|
33
|
+
# @see #initialize
|
34
|
+
attr_reader :query
|
35
|
+
# @see #initialize
|
36
|
+
attr_reader :record
|
37
|
+
# @see #initialize
|
38
|
+
attr_reader :policy
|
39
|
+
|
40
|
+
# @overload initialize(message)
|
41
|
+
# Create an error with a simple error message.
|
42
|
+
# @param [String] message A simple error message string.
|
43
|
+
#
|
44
|
+
# @overload initialize(options)
|
45
|
+
# Create an error with the specified attributes.
|
46
|
+
# @param [Hash] options The error options.
|
47
|
+
# @option options [String] :message Optional custom error message. Will default to a generalized message.
|
48
|
+
# @option options [Symbol] :query The name of the policy method that was checked.
|
49
|
+
# @option options [Object] :record The object that was being checked with the policy.
|
50
|
+
# @option options [Class] :policy The class of policy that was used for the check.
|
31
51
|
def initialize(options = {})
|
32
52
|
if options.is_a? String
|
33
53
|
message = options
|
@@ -70,10 +90,11 @@ module Pundit
|
|
70
90
|
end
|
71
91
|
|
72
92
|
class << self
|
73
|
-
# @see
|
93
|
+
# @see Pundit::Context#authorize
|
74
94
|
def authorize(user, record, query, policy_class: nil, cache: nil)
|
75
95
|
context = if cache
|
76
|
-
|
96
|
+
policy_cache = CacheStore::LegacyStore.new(cache)
|
97
|
+
Context.new(user: user, policy_cache: policy_cache)
|
77
98
|
else
|
78
99
|
Context.new(user: user)
|
79
100
|
end
|
@@ -81,29 +102,33 @@ module Pundit
|
|
81
102
|
context.authorize(record, query: query, policy_class: policy_class)
|
82
103
|
end
|
83
104
|
|
84
|
-
# @see
|
105
|
+
# @see Pundit::Context#policy_scope
|
85
106
|
def policy_scope(user, *args, **kwargs, &block)
|
86
107
|
Context.new(user: user).policy_scope(*args, **kwargs, &block)
|
87
108
|
end
|
88
109
|
|
89
|
-
# @see
|
110
|
+
# @see Pundit::Context#policy_scope!
|
90
111
|
def policy_scope!(user, *args, **kwargs, &block)
|
91
112
|
Context.new(user: user).policy_scope!(*args, **kwargs, &block)
|
92
113
|
end
|
93
114
|
|
94
|
-
# @see
|
115
|
+
# @see Pundit::Context#policy
|
95
116
|
def policy(user, *args, **kwargs, &block)
|
96
117
|
Context.new(user: user).policy(*args, **kwargs, &block)
|
97
118
|
end
|
98
119
|
|
99
|
-
# @see
|
120
|
+
# @see Pundit::Context#policy!
|
100
121
|
def policy!(user, *args, **kwargs, &block)
|
101
122
|
Context.new(user: user).policy!(*args, **kwargs, &block)
|
102
123
|
end
|
103
124
|
end
|
104
125
|
|
126
|
+
# Rails view helpers, to allow a slightly different view-specific
|
127
|
+
# implementation of the methods in {Pundit::Authorization}.
|
128
|
+
#
|
105
129
|
# @api private
|
106
130
|
module Helper
|
131
|
+
# @see Pundit::Authorization#pundit_policy_scope
|
107
132
|
def policy_scope(scope)
|
108
133
|
pundit_policy_scope(scope)
|
109
134
|
end
|
data/pundit.gemspec
CHANGED
@@ -16,20 +16,16 @@ Gem::Specification.new do |gem|
|
|
16
16
|
|
17
17
|
gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
18
18
|
gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
19
|
-
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
20
19
|
gem.require_paths = ["lib"]
|
21
20
|
|
22
|
-
gem.metadata = {
|
21
|
+
gem.metadata = {
|
22
|
+
"rubygems_mfa_required" => "true",
|
23
|
+
"bug_tracker_uri" => "https://github.com/varvet/pundit/issues",
|
24
|
+
"changelog_uri" => "https://github.com/varvet/pundit/blob/main/CHANGELOG.md",
|
25
|
+
"documentation_uri" => "https://github.com/varvet/pundit/blob/main/README.md",
|
26
|
+
"homepage_uri" => "https://github.com/varvet/pundit",
|
27
|
+
"source_code_uri" => "https://github.com/varvet/pundit"
|
28
|
+
}
|
23
29
|
|
24
30
|
gem.add_dependency "activesupport", ">= 3.0.0"
|
25
|
-
gem.add_development_dependency "actionpack", ">= 3.0.0"
|
26
|
-
gem.add_development_dependency "activemodel", ">= 3.0.0"
|
27
|
-
gem.add_development_dependency "bundler"
|
28
|
-
gem.add_development_dependency "pry"
|
29
|
-
gem.add_development_dependency "railties", ">= 3.0.0"
|
30
|
-
gem.add_development_dependency "rake"
|
31
|
-
gem.add_development_dependency "rspec", ">= 3.0.0"
|
32
|
-
gem.add_development_dependency "rubocop"
|
33
|
-
gem.add_development_dependency "simplecov", ">= 0.17.0"
|
34
|
-
gem.add_development_dependency "yard"
|
35
31
|
end
|
data/spec/authorization_spec.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "spec_helper"
|
4
|
+
require "action_controller/metal/strong_parameters"
|
4
5
|
|
5
6
|
describe Pundit::Authorization do
|
6
7
|
def to_params(*args, **kwargs, &block)
|
@@ -8,7 +9,7 @@ describe Pundit::Authorization do
|
|
8
9
|
end
|
9
10
|
|
10
11
|
let(:controller) { Controller.new(user, "update", to_params({})) }
|
11
|
-
let(:user) { double }
|
12
|
+
let(:user) { double("user") }
|
12
13
|
let(:post) { Post.new(user) }
|
13
14
|
let(:comment) { Comment.new }
|
14
15
|
let(:article) { Article.new }
|
@@ -157,7 +158,7 @@ describe Pundit::Authorization do
|
|
157
158
|
end
|
158
159
|
|
159
160
|
it "allows policy to be injected" do
|
160
|
-
new_policy =
|
161
|
+
new_policy = double
|
161
162
|
controller.policies[post] = new_policy
|
162
163
|
|
163
164
|
expect(controller.policy(post)).to eq new_policy
|
@@ -182,7 +183,7 @@ describe Pundit::Authorization do
|
|
182
183
|
end
|
183
184
|
|
184
185
|
it "allows policy_scope to be injected" do
|
185
|
-
new_scope =
|
186
|
+
new_scope = double
|
186
187
|
controller.policy_scopes[Post] = new_scope
|
187
188
|
|
188
189
|
expect(controller.policy_scope(Post)).to eq new_scope
|
@@ -271,4 +272,60 @@ describe Pundit::Authorization do
|
|
271
272
|
expect(Controller.new(user, action, params).permitted_attributes(post, :revise).to_h).to eq("body" => "blah")
|
272
273
|
end
|
273
274
|
end
|
275
|
+
|
276
|
+
describe "#pundit_reset!" do
|
277
|
+
it "allows authorize to react to a user change" do
|
278
|
+
expect(controller.authorize(post)).to be_truthy
|
279
|
+
|
280
|
+
controller.current_user = double
|
281
|
+
controller.pundit_reset!
|
282
|
+
expect { controller.authorize(post) }.to raise_error(Pundit::NotAuthorizedError)
|
283
|
+
end
|
284
|
+
|
285
|
+
it "allows policy to react to a user change" do
|
286
|
+
expect(controller.policy(DummyCurrentUser).user).to be user
|
287
|
+
|
288
|
+
new_user = double("new user")
|
289
|
+
controller.current_user = new_user
|
290
|
+
controller.pundit_reset!
|
291
|
+
expect(controller.policy(DummyCurrentUser).user).to be new_user
|
292
|
+
end
|
293
|
+
|
294
|
+
it "allows policy scope to react to a user change" do
|
295
|
+
expect(controller.policy_scope(DummyCurrentUser)).to be user
|
296
|
+
|
297
|
+
new_user = double("new user")
|
298
|
+
controller.current_user = new_user
|
299
|
+
controller.pundit_reset!
|
300
|
+
expect(controller.policy_scope(DummyCurrentUser)).to be new_user
|
301
|
+
end
|
302
|
+
|
303
|
+
it "resets the pundit context" do
|
304
|
+
expect(controller.pundit.user).to be(user)
|
305
|
+
|
306
|
+
new_user = double
|
307
|
+
controller.current_user = new_user
|
308
|
+
expect { controller.pundit_reset! }.to change { controller.pundit.user }.from(user).to(new_user)
|
309
|
+
end
|
310
|
+
|
311
|
+
it "clears pundit_policy_authorized? flag" do
|
312
|
+
expect(controller.pundit_policy_authorized?).to be false
|
313
|
+
|
314
|
+
controller.skip_authorization
|
315
|
+
expect(controller.pundit_policy_authorized?).to be true
|
316
|
+
|
317
|
+
controller.pundit_reset!
|
318
|
+
expect(controller.pundit_policy_authorized?).to be false
|
319
|
+
end
|
320
|
+
|
321
|
+
it "clears pundit_policy_scoped? flag" do
|
322
|
+
expect(controller.pundit_policy_scoped?).to be false
|
323
|
+
|
324
|
+
controller.skip_policy_scope
|
325
|
+
expect(controller.pundit_policy_scoped?).to be true
|
326
|
+
|
327
|
+
controller.pundit_reset!
|
328
|
+
expect(controller.pundit_policy_scoped?).to be false
|
329
|
+
end
|
330
|
+
end
|
274
331
|
end
|
data/spec/policy_finder_spec.rb
CHANGED
@@ -2,13 +2,17 @@
|
|
2
2
|
|
3
3
|
require "spec_helper"
|
4
4
|
|
5
|
-
class Foo; end
|
6
5
|
RSpec.describe Pundit::PolicyFinder do
|
7
6
|
let(:user) { double }
|
8
7
|
let(:post) { Post.new(user) }
|
9
8
|
let(:comment) { CommentFourFiveSix.new }
|
10
9
|
let(:article) { Article.new }
|
11
10
|
|
11
|
+
describe "SUFFIX" do
|
12
|
+
specify { expect(described_class::SUFFIX).to eq "Policy" }
|
13
|
+
specify { expect(Pundit::SUFFIX).to eq(described_class::SUFFIX) }
|
14
|
+
end
|
15
|
+
|
12
16
|
describe "#scope" do
|
13
17
|
subject { described_class.new(post) }
|
14
18
|
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
RSpec.describe Pundit::Helper do
|
6
|
+
let(:user) { double }
|
7
|
+
let(:controller) { Controller.new(user, "update", double) }
|
8
|
+
let(:view) { Controller::View.new(controller) }
|
9
|
+
|
10
|
+
describe "#policy_scope" do
|
11
|
+
it "doesn't flip pundit_policy_scoped?" do
|
12
|
+
scoped = view.policy_scope(Post)
|
13
|
+
|
14
|
+
expect(scoped).to be(Post.published)
|
15
|
+
expect(controller).not_to be_pundit_policy_scoped
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/spec/pundit_spec.rb
CHANGED
@@ -43,15 +43,6 @@ RSpec.describe Pundit do
|
|
43
43
|
expect(Pundit.authorize(user, Post, :show?)).to eq(Post)
|
44
44
|
end
|
45
45
|
|
46
|
-
it "can be given a different policy class" do
|
47
|
-
expect(Pundit.authorize(user, post, :create?, policy_class: PublicationPolicy)).to be_truthy
|
48
|
-
end
|
49
|
-
|
50
|
-
it "can be given a different policy class using namespaces" do
|
51
|
-
expect(PublicationPolicy).to receive(:new).with(user, comment).and_call_original
|
52
|
-
expect(Pundit.authorize(user, [:project, comment], :create?, policy_class: PublicationPolicy)).to be_truthy
|
53
|
-
end
|
54
|
-
|
55
46
|
it "works with anonymous class policies" do
|
56
47
|
expect(Pundit.authorize(user, article_tag, :show?)).to be_truthy
|
57
48
|
expect { Pundit.authorize(user, article_tag, :destroy?) }.to raise_error(Pundit::NotAuthorizedError)
|
@@ -111,6 +102,41 @@ RSpec.describe Pundit do
|
|
111
102
|
Pundit.authorize(user, wiki, :update?)
|
112
103
|
end.to raise_error(Pundit::InvalidConstructorError, "Invalid #<WikiPolicy> constructor is called")
|
113
104
|
end
|
105
|
+
|
106
|
+
context "when passed a policy class" do
|
107
|
+
it "uses the passed policy class" do
|
108
|
+
expect(Pundit.authorize(user, post, :create?, policy_class: PublicationPolicy)).to be_truthy
|
109
|
+
end
|
110
|
+
|
111
|
+
# This is documenting past behaviour.
|
112
|
+
it "doesn't cache the policy class" do
|
113
|
+
cache = {}
|
114
|
+
|
115
|
+
expect do
|
116
|
+
Pundit.authorize(user, post, :create?, policy_class: PublicationPolicy, cache: cache)
|
117
|
+
Pundit.authorize(user, post, :create?, policy_class: PublicationPolicy, cache: cache)
|
118
|
+
end.to change { PublicationPolicy.instances }.by(2)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
context "when passed a policy class while simultaenously passing a namespace" do
|
123
|
+
it "uses the passed policy class" do
|
124
|
+
expect(PublicationPolicy).to receive(:new).with(user, comment).and_call_original
|
125
|
+
expect(Pundit.authorize(user, [:project, comment], :create?, policy_class: PublicationPolicy)).to be_truthy
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
context "when passed an explicit cache" do
|
130
|
+
it "uses the hash assignment interface on the cache" do
|
131
|
+
custom_cache = CustomCache.new
|
132
|
+
|
133
|
+
Pundit.authorize(user, post, :update?, cache: custom_cache)
|
134
|
+
|
135
|
+
expect(custom_cache.to_h).to match({
|
136
|
+
post => kind_of(PostPolicy)
|
137
|
+
})
|
138
|
+
end
|
139
|
+
end
|
114
140
|
end
|
115
141
|
|
116
142
|
describe ".policy_scope" do
|
@@ -154,8 +180,8 @@ RSpec.describe Pundit do
|
|
154
180
|
|
155
181
|
it "raises an original error with a policy scope that contains error" do
|
156
182
|
expect do
|
157
|
-
Pundit.policy_scope(user,
|
158
|
-
end.to raise_error(
|
183
|
+
Pundit.policy_scope(user, DefaultScopeContainsError)
|
184
|
+
end.to raise_error(RuntimeError, "This is an arbitrary error that should bubble up")
|
159
185
|
end
|
160
186
|
end
|
161
187
|
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
RSpec.describe "Pundit RSpec DSL" do
|
6
|
+
include Pundit::RSpec::PolicyExampleGroup
|
7
|
+
|
8
|
+
let(:fake_rspec) do
|
9
|
+
double = class_double(RSpec::ExampleGroups)
|
10
|
+
double.extend(::Pundit::RSpec::DSL)
|
11
|
+
double
|
12
|
+
end
|
13
|
+
let(:block) { proc { "block content" } }
|
14
|
+
|
15
|
+
let(:user) { double }
|
16
|
+
let(:other_user) { double }
|
17
|
+
let(:post) { Post.new(user) }
|
18
|
+
let(:policy) { PostPolicy }
|
19
|
+
|
20
|
+
it "calls describe with the correct metadata and without :focus" do
|
21
|
+
expected_metadata = { permissions: %i[item1 item2], caller: instance_of(Array) }
|
22
|
+
expect(fake_rspec).to receive(:describe).with("item1 and item2", match(expected_metadata)) do |&block|
|
23
|
+
expect(block.call).to eq("block content")
|
24
|
+
end
|
25
|
+
|
26
|
+
fake_rspec.permissions(:item1, :item2, &block)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "calls describe with the correct metadata and with :focus" do
|
30
|
+
expected_metadata = { permissions: %i[item1 item2], caller: instance_of(Array), focus: true }
|
31
|
+
expect(fake_rspec).to receive(:describe).with("item1 and item2", match(expected_metadata)) do |&block|
|
32
|
+
expect(block.call).to eq("block content")
|
33
|
+
end
|
34
|
+
|
35
|
+
fake_rspec.permissions(:item1, :item2, :focus, &block)
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "#permit" do
|
39
|
+
context "when not appropriately wrapped in permissions" do
|
40
|
+
it "raises a descriptive error" do
|
41
|
+
expect do
|
42
|
+
expect(policy).to permit(user, post)
|
43
|
+
end.to raise_error(KeyError, <<~MSG.strip)
|
44
|
+
No permissions in example metadata, did you forget to wrap with `permissions :show?, ...`?
|
45
|
+
MSG
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
permissions :edit?, :update? do
|
50
|
+
it "succeeds when action is permitted" do
|
51
|
+
expect(policy).to permit(user, post)
|
52
|
+
end
|
53
|
+
|
54
|
+
context "when it fails" do
|
55
|
+
it "fails with a descriptive error message" do
|
56
|
+
expect do
|
57
|
+
expect(policy).to permit(other_user, post)
|
58
|
+
end.to raise_error(RSpec::Expectations::ExpectationNotMetError, <<~MSG.strip)
|
59
|
+
Expected PostPolicy to grant edit? and update? on Post but edit? and update? were not granted
|
60
|
+
MSG
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context "when negated" do
|
65
|
+
it "succeeds when action is not permitted" do
|
66
|
+
expect(policy).not_to permit(other_user, post)
|
67
|
+
end
|
68
|
+
|
69
|
+
context "when it fails" do
|
70
|
+
it "fails with a descriptive error message" do
|
71
|
+
expect do
|
72
|
+
expect(policy).not_to permit(user, post)
|
73
|
+
end.to raise_error(RSpec::Expectations::ExpectationNotMetError, <<~MSG.strip)
|
74
|
+
Expected PostPolicy not to grant edit? and update? on Post but edit? and update? were granted
|
75
|
+
MSG
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "simplecov"
|
4
|
+
require "json"
|
5
|
+
|
6
|
+
class SimpleCovCheckActionFormatter
|
7
|
+
SourceFile = Data.define(:source_file) do
|
8
|
+
def covered_strength = source_file.covered_strength
|
9
|
+
def covered_percent = source_file.covered_percent
|
10
|
+
|
11
|
+
def to_json(*args)
|
12
|
+
{
|
13
|
+
filename: source_file.filename,
|
14
|
+
covered_percent: covered_percent.nan? ? 0.0 : covered_percent,
|
15
|
+
coverage: source_file.coverage_data,
|
16
|
+
covered_strength: covered_strength.nan? ? 0.0 : covered_strength,
|
17
|
+
covered_lines: source_file.covered_lines.count,
|
18
|
+
lines_of_code: source_file.lines_of_code
|
19
|
+
}.to_json(*args)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
Result = Data.define(:result) do
|
24
|
+
def included?(source_file) = result.filenames.include?(source_file.filename)
|
25
|
+
|
26
|
+
def files
|
27
|
+
result.files.filter_map do |source_file|
|
28
|
+
next unless result.filenames.include? source_file.filename
|
29
|
+
|
30
|
+
SourceFile.new(source_file)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_json(*args) # rubocop:disable Metrics/AbcSize
|
35
|
+
{
|
36
|
+
timestamp: result.created_at.to_i,
|
37
|
+
command_name: result.command_name,
|
38
|
+
files: files,
|
39
|
+
metrics: {
|
40
|
+
covered_percent: result.covered_percent,
|
41
|
+
covered_strength: result.covered_strength.nan? ? 0.0 : result.covered_strength,
|
42
|
+
covered_lines: result.covered_lines,
|
43
|
+
total_lines: result.total_lines
|
44
|
+
}
|
45
|
+
}.to_json(*args)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
FormatterWithOptions = Data.define(:formatter) do
|
50
|
+
def new = formatter
|
51
|
+
end
|
52
|
+
|
53
|
+
class << self
|
54
|
+
def with_options(...)
|
55
|
+
FormatterWithOptions.new(new(...))
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def initialize(output_filename: "coverage.json", output_directory: SimpleCov.coverage_path)
|
60
|
+
@output_filename = output_filename
|
61
|
+
@output_directory = output_directory
|
62
|
+
end
|
63
|
+
|
64
|
+
attr_reader :output_filename, :output_directory
|
65
|
+
|
66
|
+
def output_filepath = File.join(output_directory, output_filename)
|
67
|
+
|
68
|
+
def format(result_data)
|
69
|
+
result = Result.new(result_data)
|
70
|
+
json = JSON.generate(result)
|
71
|
+
File.write(output_filepath, json)
|
72
|
+
puts output_message(result_data)
|
73
|
+
json
|
74
|
+
end
|
75
|
+
|
76
|
+
def output_message(result)
|
77
|
+
"Coverage report generated for #{result.command_name} to #{output_filepath}. #{result.covered_lines} / #{result.total_lines} LOC (#{result.covered_percent.round(2)}%) covered." # rubocop:disable Layout/LineLength
|
78
|
+
end
|
79
|
+
end
|