pundit 2.1.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.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +20 -0
  3. data/.github/ISSUE_TEMPLATE/feature_request.md +26 -0
  4. data/.github/PULL_REQUEST_TEMPLATE/gem_release_template.md +8 -0
  5. data/.github/pull_request_template.md +9 -0
  6. data/.github/workflows/main.yml +147 -0
  7. data/.github/workflows/push_gem.yml +33 -0
  8. data/.gitignore +1 -0
  9. data/.rubocop.yml +26 -29
  10. data/.rubocop_ignore_git.yml +7 -0
  11. data/.yardopts +1 -1
  12. data/CHANGELOG.md +120 -21
  13. data/CODE_OF_CONDUCT.md +1 -1
  14. data/CONTRIBUTING.md +3 -5
  15. data/Gemfile +23 -2
  16. data/README.md +175 -78
  17. data/Rakefile +1 -0
  18. data/SECURITY.md +19 -0
  19. data/config/rubocop-rspec.yml +5 -0
  20. data/lib/generators/pundit/install/install_generator.rb +6 -2
  21. data/lib/generators/pundit/install/templates/{application_policy.rb → application_policy.rb.tt} +7 -3
  22. data/lib/generators/pundit/policy/policy_generator.rb +6 -2
  23. data/lib/generators/pundit/policy/templates/policy.rb.tt +16 -0
  24. data/lib/generators/rspec/policy_generator.rb +7 -2
  25. data/lib/generators/rspec/templates/{policy_spec.rb → policy_spec.rb.tt} +1 -1
  26. data/lib/generators/test_unit/policy_generator.rb +7 -2
  27. data/lib/pundit/authorization.rb +251 -0
  28. data/lib/pundit/cache_store/legacy_store.rb +24 -0
  29. data/lib/pundit/cache_store/null_store.rb +27 -0
  30. data/lib/pundit/cache_store.rb +22 -0
  31. data/lib/pundit/context.rb +177 -0
  32. data/lib/pundit/policy_finder.rb +24 -3
  33. data/lib/pundit/railtie.rb +19 -0
  34. data/lib/pundit/rspec.rb +93 -20
  35. data/lib/pundit/version.rb +2 -1
  36. data/lib/pundit.rb +68 -257
  37. data/pundit.gemspec +10 -10
  38. data/spec/authorization_spec.rb +331 -0
  39. data/spec/generators_spec.rb +43 -0
  40. data/spec/policies/post_policy_spec.rb +28 -1
  41. data/spec/policy_finder_spec.rb +84 -17
  42. data/spec/pundit/helper_spec.rb +18 -0
  43. data/spec/pundit_spec.rb +110 -233
  44. data/spec/rspec_dsl_spec.rb +81 -0
  45. data/spec/simple_cov_check_action_formatter.rb +79 -0
  46. data/spec/spec_helper.rb +29 -265
  47. data/spec/support/lib/controller.rb +38 -0
  48. data/spec/support/lib/custom_cache.rb +19 -0
  49. data/spec/support/lib/instance_tracking.rb +20 -0
  50. data/spec/support/models/article.rb +4 -0
  51. data/spec/support/models/article_tag.rb +7 -0
  52. data/spec/support/models/artificial_blog.rb +7 -0
  53. data/spec/support/models/blog.rb +4 -0
  54. data/spec/support/models/comment.rb +5 -0
  55. data/spec/support/models/comment_four_five_six.rb +5 -0
  56. data/spec/support/models/comment_scope.rb +13 -0
  57. data/spec/support/models/comments_relation.rb +15 -0
  58. data/spec/support/models/customer/post.rb +11 -0
  59. data/spec/support/models/default_scope_contains_error.rb +5 -0
  60. data/spec/support/models/dummy_current_user.rb +7 -0
  61. data/spec/support/models/foo.rb +4 -0
  62. data/spec/support/models/post.rb +25 -0
  63. data/spec/support/models/post_four_five_six.rb +9 -0
  64. data/spec/support/models/project_one_two_three/avatar_four_five_six.rb +7 -0
  65. data/spec/support/models/project_one_two_three/tag_four_five_six.rb +11 -0
  66. data/spec/support/models/wiki.rb +4 -0
  67. data/spec/support/policies/article_tag_other_name_policy.rb +13 -0
  68. data/spec/support/policies/base_policy.rb +23 -0
  69. data/spec/support/policies/blog_policy.rb +5 -0
  70. data/spec/support/policies/comment_policy.rb +11 -0
  71. data/spec/support/policies/criteria_policy.rb +5 -0
  72. data/spec/support/policies/default_scope_contains_error_policy.rb +10 -0
  73. data/spec/support/policies/denier_policy.rb +7 -0
  74. data/spec/support/policies/dummy_current_user_policy.rb +9 -0
  75. data/spec/support/policies/nil_class_policy.rb +17 -0
  76. data/spec/support/policies/post_policy.rb +36 -0
  77. data/spec/support/policies/project/admin/comment_policy.rb +15 -0
  78. data/spec/support/policies/project/comment_policy.rb +17 -0
  79. data/spec/support/policies/project/criteria_policy.rb +7 -0
  80. data/spec/support/policies/project/post_policy.rb +13 -0
  81. data/spec/support/policies/project_one_two_three/avatar_four_five_six_policy.rb +6 -0
  82. data/spec/support/policies/project_one_two_three/comment_four_five_six_policy.rb +6 -0
  83. data/spec/support/policies/project_one_two_three/criteria_four_five_six_policy.rb +6 -0
  84. data/spec/support/policies/project_one_two_three/post_four_five_six_policy.rb +6 -0
  85. data/spec/support/policies/project_one_two_three/tag_four_five_six_policy.rb +6 -0
  86. data/spec/support/policies/publication_policy.rb +13 -0
  87. data/spec/support/policies/wiki_policy.rb +8 -0
  88. metadata +80 -130
  89. data/.travis.yml +0 -21
  90. data/lib/generators/pundit/policy/templates/policy.rb +0 -9
  91. /data/lib/generators/test_unit/templates/{policy_test.rb → policy_test.rb.tt} +0 -0
data/spec/pundit_spec.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "spec_helper"
4
4
 
5
- describe Pundit do
5
+ RSpec.describe Pundit do
6
6
  let(:user) { double }
7
7
  let(:post) { Post.new(user) }
8
8
  let(:customer_post) { Customer::Post.new(user) }
@@ -10,23 +10,37 @@ describe Pundit do
10
10
  let(:comment) { Comment.new }
11
11
  let(:comment_four_five_six) { CommentFourFiveSix.new }
12
12
  let(:article) { Article.new }
13
- let(:controller) { Controller.new(user, "update", {}) }
14
13
  let(:artificial_blog) { ArtificialBlog.new }
15
14
  let(:article_tag) { ArticleTag.new }
16
- let(:comments_relation) { CommentsRelation.new }
17
- let(:empty_comments_relation) { CommentsRelation.new(true) }
15
+ let(:comments_relation) { CommentsRelation.new(empty: false) }
16
+ let(:empty_comments_relation) { CommentsRelation.new(empty: true) }
18
17
  let(:tag_four_five_six) { ProjectOneTwoThree::TagFourFiveSix.new(user) }
19
18
  let(:avatar_four_five_six) { ProjectOneTwoThree::AvatarFourFiveSix.new }
20
19
  let(:wiki) { Wiki.new }
21
- let(:thread) { Thread.new }
22
20
 
23
21
  describe ".authorize" do
24
22
  it "infers the policy and authorizes based on it" do
25
23
  expect(Pundit.authorize(user, post, :update?)).to be_truthy
26
24
  end
27
25
 
28
- it "can be given a different policy class" do
29
- expect(Pundit.authorize(user, post, :create?, policy_class: PublicationPolicy)).to be_truthy
26
+ it "returns the record on successful authorization" do
27
+ expect(Pundit.authorize(user, post, :update?)).to eq(post)
28
+ end
29
+
30
+ it "returns the record when passed record with namespace " do
31
+ expect(Pundit.authorize(user, [:project, comment], :update?)).to eq(comment)
32
+ end
33
+
34
+ it "returns the record when passed record with nested namespace " do
35
+ expect(Pundit.authorize(user, [:project, :admin, comment], :update?)).to eq(comment)
36
+ end
37
+
38
+ it "returns the policy name symbol when passed record with headless policy" do
39
+ expect(Pundit.authorize(user, :publication, :create?)).to eq(:publication)
40
+ end
41
+
42
+ it "returns the class when passed record not a particular instance" do
43
+ expect(Pundit.authorize(user, Post, :show?)).to eq(Post)
30
44
  end
31
45
 
32
46
  it "works with anonymous class policies" do
@@ -34,14 +48,51 @@ describe Pundit do
34
48
  expect { Pundit.authorize(user, article_tag, :destroy?) }.to raise_error(Pundit::NotAuthorizedError)
35
49
  end
36
50
 
37
- it "raises an error with a query and action" do
51
+ it "raises an error with the policy, query and record" do
38
52
  # rubocop:disable Style/MultilineBlockChain
39
53
  expect do
40
54
  Pundit.authorize(user, post, :destroy?)
41
- end.to raise_error(Pundit::NotAuthorizedError, "not allowed to destroy? this Post") do |error|
55
+ end.to raise_error(Pundit::NotAuthorizedError, "not allowed to PostPolicy#destroy? this Post") do |error|
42
56
  expect(error.query).to eq :destroy?
43
57
  expect(error.record).to eq post
44
- expect(error.policy).to eq Pundit.policy(user, post)
58
+ expect(error.policy).to have_attributes(
59
+ user: user,
60
+ record: post
61
+ )
62
+ expect(error.policy).to be_a(PostPolicy)
63
+ end
64
+ # rubocop:enable Style/MultilineBlockChain
65
+ end
66
+
67
+ it "raises an error with the policy, query and record when the record is namespaced" do
68
+ # rubocop:disable Style/MultilineBlockChain
69
+ expect do
70
+ Pundit.authorize(user, [:project, :admin, comment], :destroy?)
71
+ end.to raise_error(Pundit::NotAuthorizedError,
72
+ "not allowed to Project::Admin::CommentPolicy#destroy? this Comment") do |error|
73
+ expect(error.query).to eq :destroy?
74
+ expect(error.record).to eq comment
75
+ expect(error.policy).to have_attributes(
76
+ user: user,
77
+ record: comment
78
+ )
79
+ expect(error.policy).to be_a(Project::Admin::CommentPolicy)
80
+ end
81
+ # rubocop:enable Style/MultilineBlockChain
82
+ end
83
+
84
+ it "raises an error with the policy, query and the class name when a Class is given" do
85
+ # rubocop:disable Style/MultilineBlockChain
86
+ expect do
87
+ Pundit.authorize(user, Post, :destroy?)
88
+ end.to raise_error(Pundit::NotAuthorizedError, "not allowed to PostPolicy#destroy? Post") do |error|
89
+ expect(error.query).to eq :destroy?
90
+ expect(error.record).to eq Post
91
+ expect(error.policy).to have_attributes(
92
+ user: user,
93
+ record: Post
94
+ )
95
+ expect(error.policy).to be_a(PostPolicy)
45
96
  end
46
97
  # rubocop:enable Style/MultilineBlockChain
47
98
  end
@@ -51,6 +102,41 @@ describe Pundit do
51
102
  Pundit.authorize(user, wiki, :update?)
52
103
  end.to raise_error(Pundit::InvalidConstructorError, "Invalid #<WikiPolicy> constructor is called")
53
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
54
140
  end
55
141
 
56
142
  describe ".policy_scope" do
@@ -94,8 +180,8 @@ describe Pundit do
94
180
 
95
181
  it "raises an original error with a policy scope that contains error" do
96
182
  expect do
97
- Pundit.policy_scope(user, Thread)
98
- end.to raise_error(ArgumentError)
183
+ Pundit.policy_scope(user, DefaultScopeContainsError)
184
+ end.to raise_error(RuntimeError, "This is an arbitrary error that should bubble up")
99
185
  end
100
186
  end
101
187
 
@@ -360,231 +446,22 @@ describe Pundit do
360
446
  end
361
447
  end
362
448
 
363
- describe "#verify_authorized" do
364
- it "does nothing when authorized" do
365
- controller.authorize(post)
366
- controller.verify_authorized
367
- end
368
-
369
- it "raises an exception when not authorized" do
370
- expect { controller.verify_authorized }.to raise_error(Pundit::AuthorizationNotPerformedError)
371
- end
372
- end
373
-
374
- describe "#verify_policy_scoped" do
375
- it "does nothing when policy_scope is used" do
376
- controller.policy_scope(Post)
377
- controller.verify_policy_scoped
378
- end
379
-
380
- it "raises an exception when policy_scope is not used" do
381
- expect { controller.verify_policy_scoped }.to raise_error(Pundit::PolicyScopingNotPerformedError)
382
- end
383
- end
384
-
385
- describe "#pundit_policy_authorized?" do
386
- it "is true when authorized" do
387
- controller.authorize(post)
388
- expect(controller.pundit_policy_authorized?).to be true
389
- end
390
-
391
- it "is false when not authorized" do
392
- expect(controller.pundit_policy_authorized?).to be false
393
- end
394
- end
395
-
396
- describe "#pundit_policy_scoped?" do
397
- it "is true when policy_scope is used" do
398
- controller.policy_scope(Post)
399
- expect(controller.pundit_policy_scoped?).to be true
400
- end
449
+ describe ".included" do
450
+ it "includes Authorization module" do
451
+ klass = Class.new
401
452
 
402
- it "is false when policy scope is not used" do
403
- expect(controller.pundit_policy_scoped?).to be false
404
- end
405
- end
406
-
407
- describe "#authorize" do
408
- it "infers the policy name and authorizes based on it" do
409
- expect(controller.authorize(post)).to be_truthy
410
- end
411
-
412
- it "returns the record on successful authorization" do
413
- expect(controller.authorize(post)).to be(post)
414
- end
415
-
416
- it "can be given a different permission to check" do
417
- expect(controller.authorize(post, :show?)).to be_truthy
418
- expect { controller.authorize(post, :destroy?) }.to raise_error(Pundit::NotAuthorizedError)
419
- end
420
-
421
- it "can be given a different policy class" do
422
- expect(controller.authorize(post, :create?, policy_class: PublicationPolicy)).to be_truthy
423
- end
424
-
425
- it "works with anonymous class policies" do
426
- expect(controller.authorize(article_tag, :show?)).to be_truthy
427
- expect { controller.authorize(article_tag, :destroy?) }.to raise_error(Pundit::NotAuthorizedError)
428
- end
429
-
430
- it "throws an exception when the permission check fails" do
431
- expect { controller.authorize(Post.new) }.to raise_error(Pundit::NotAuthorizedError)
432
- end
433
-
434
- it "throws an exception when a policy cannot be found" do
435
- expect { controller.authorize(Article) }.to raise_error(Pundit::NotDefinedError)
436
- end
437
-
438
- it "caches the policy" do
439
- expect(controller.policies[post]).to be_nil
440
- controller.authorize(post)
441
- expect(controller.policies[post]).not_to be_nil
442
- end
443
-
444
- it "raises an error when the given record is nil" do
445
- expect { controller.authorize(nil, :destroy?) }.to raise_error(Pundit::NotAuthorizedError)
446
- end
447
-
448
- it "raises an error with a invalid policy constructor" do
449
- expect { controller.authorize(wiki, :destroy?) }.to raise_error(Pundit::InvalidConstructorError)
450
- end
451
- end
452
-
453
- describe "#skip_authorization" do
454
- it "disables authorization verification" do
455
- controller.skip_authorization
456
- expect { controller.verify_authorized }.not_to raise_error
457
- end
458
- end
459
-
460
- describe "#skip_policy_scope" do
461
- it "disables policy scope verification" do
462
- controller.skip_policy_scope
463
- expect { controller.verify_policy_scoped }.not_to raise_error
464
- end
465
- end
466
-
467
- describe "#pundit_user" do
468
- it "returns the same thing as current_user" do
469
- expect(controller.pundit_user).to eq controller.current_user
470
- end
471
- end
472
-
473
- describe "#policy" do
474
- it "returns an instantiated policy" do
475
- policy = controller.policy(post)
476
- expect(policy.user).to eq user
477
- expect(policy.post).to eq post
478
- end
479
-
480
- it "throws an exception if the given policy can't be found" do
481
- expect { controller.policy(article) }.to raise_error(Pundit::NotDefinedError)
482
- end
483
-
484
- it "raises an error with a invalid policy constructor" do
485
- expect { controller.policy(wiki) }.to raise_error(Pundit::InvalidConstructorError)
486
- end
487
-
488
- it "allows policy to be injected" do
489
- new_policy = OpenStruct.new
490
- controller.policies[post] = new_policy
491
-
492
- expect(controller.policy(post)).to eq new_policy
493
- end
494
- end
495
-
496
- describe "#policy_scope" do
497
- it "returns an instantiated policy scope" do
498
- expect(controller.policy_scope(Post)).to eq :published
499
- end
500
-
501
- it "allows policy scope class to be overriden" do
502
- expect(controller.policy_scope(Post, policy_scope_class: PublicationPolicy::Scope)).to eq :published
503
- end
504
-
505
- it "throws an exception if the given policy can't be found" do
506
- expect { controller.policy_scope(Article) }.to raise_error(Pundit::NotDefinedError)
507
- end
508
-
509
- it "raises an error with a invalid policy scope constructor" do
510
- expect { controller.policy_scope(Wiki) }.to raise_error(Pundit::InvalidConstructorError)
511
- end
512
-
513
- it "allows policy_scope to be injected" do
514
- new_scope = OpenStruct.new
515
- controller.policy_scopes[Post] = new_scope
516
-
517
- expect(controller.policy_scope(Post)).to eq new_scope
518
- end
519
- end
453
+ expect do
454
+ klass.include Pundit
455
+ end.to output.to_stderr
520
456
 
521
- describe "#permitted_attributes" do
522
- it "checks policy for permitted attributes" do
523
- params = ActionController::Parameters.new(
524
- post: {
525
- title: "Hello",
526
- votes: 5,
527
- admin: true
528
- }
529
- )
530
-
531
- action = "update"
532
-
533
- expect(Controller.new(user, action, params).permitted_attributes(post).to_h).to eq(
534
- "title" => "Hello",
535
- "votes" => 5
536
- )
537
- expect(Controller.new(double, action, params).permitted_attributes(post).to_h).to eq("votes" => 5)
538
- end
539
-
540
- it "checks policy for permitted attributes for record of a ActiveModel type" do
541
- params = ActionController::Parameters.new(
542
- customer_post: {
543
- title: "Hello",
544
- votes: 5,
545
- admin: true
546
- }
547
- )
548
-
549
- action = "update"
550
-
551
- expect(Controller.new(user, action, params).permitted_attributes(customer_post).to_h).to eq(
552
- "title" => "Hello",
553
- "votes" => 5
554
- )
555
- expect(Controller.new(double, action, params).permitted_attributes(customer_post).to_h).to eq(
556
- "votes" => 5
557
- )
457
+ expect(klass).to include Pundit::Authorization
558
458
  end
559
- end
560
459
 
561
- describe "#permitted_attributes_for_action" do
562
- it "is checked if it is defined in the policy" do
563
- params = ActionController::Parameters.new(
564
- post: {
565
- title: "Hello",
566
- body: "blah",
567
- votes: 5,
568
- admin: true
569
- }
570
- )
571
-
572
- action = "revise"
573
- expect(Controller.new(user, action, params).permitted_attributes(post).to_h).to eq("body" => "blah")
574
- end
575
-
576
- it "can be explicitly set" do
577
- params = ActionController::Parameters.new(
578
- post: {
579
- title: "Hello",
580
- body: "blah",
581
- votes: 5,
582
- admin: true
583
- }
584
- )
585
-
586
- action = "update"
587
- expect(Controller.new(user, action, params).permitted_attributes(post, :revise).to_h).to eq("body" => "blah")
460
+ it "warns about deprecation" do
461
+ klass = Class.new
462
+ expect do
463
+ klass.include Pundit
464
+ end.to output(a_string_starting_with("'include Pundit' is deprecated")).to_stderr
588
465
  end
589
466
  end
590
467
 
@@ -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