pundit-matchers icon indicating copy to clipboard operation
pundit-matchers copied to clipboard

to_not permit_actions behavior is misleading

Open oneamtu opened this issue 5 years ago • 7 comments

Hi,

We just ran into a bit of unexpected behavior in our app when using .to_not permit_actions. The gist of it is that we had an action that was both permitted and not permitted in this setup

  it { is_expected.to permit_actions([:new, :create, :edit]) }
  it { is_expected.not_to permit_actions([:edit, :destroy]) }

The specs were passing even though nothing changed between the 2 examples. The problem is that the permit_actions matcher does the equivalent of a policy.new? && policy.create? && policy.edit?. to_not negates that into !(policy.new? && policy.create? && policy.edit?) which is equivalent to !policy.new? || !policy.create? || !policy.edit?, so it's enough for one action to not be permitted for the whole matcher to pass. That's counterintuitive, but it does look like the matcher implements a negative message. Using the forbid_actions matcher fixes it for our purposes.

I'm not sure how to fix the negative matcher issue. It doesn't look like rspec matchers has a special negative matcher that one can define, so I'm not sure how to disallow this behavior. Maybe updating the docs or changing the error message would be a good first step?

Thank you for making this gem!

-Octavian

oneamtu avatar Dec 24 '19 20:12 oneamtu

Requiring the forbid_actions matcher would be ideal in this case (this case is exactly what it was designed for). Unfortunately I don't know of any programmatic way that we can prevent or warn programmers from using not_to and permit_actions together.

If the spec passes, you won't see an error message, so updating the error message wouldn't help in that case.

I would happy to receive a PR with a warning somewhere obvious (near the top of the readme, for example), recommending the forbid matchers and the reasoning for using these. If this applies to other matchers, those sections would need updating too.

chrisalley avatar Mar 21 '20 08:03 chrisalley

RSpec does support a negated matcher with separate logic: https://relishapp.com/rspec/rspec-expectations/v/3-9/docs/custom-matchers/define-a-custom-matcher#matcher-with-separate-logic-for-expect().to-and-expect().not-to

RSpec::Matchers.define :contain do |*expected|
  match do |actual|
    expected.all? { |e| actual.include?(e) }
  end

  match_when_negated do |actual|
    expected.none? { |e| actual.include?(e) }
  end
end

dewyze avatar Apr 08 '20 21:04 dewyze

I opened a pull request with an incredibly verbose warning, if you have an edits I'm happy to make them. This is pretty obnoxious. It is possible to use the negated matcher to force the forbid_actions behavior instead, but that would be a breaking change, right?

dewyze avatar Apr 08 '20 21:04 dewyze

Thanks, I'll take a look your PR. I've set up a seperate issue for discussing a potential 2.0 release: #33

chrisalley avatar Apr 08 '20 22:04 chrisalley

@chrisalley If you want any help with cleanup for a 2.0, let me know. I could contribute a bit.

dewyze avatar Apr 08 '20 22:04 dewyze

:+1:, thank you for the contribution! Super helpful.

On Wed, Apr 8, 2020, 18:36 John DeWyze [email protected] wrote:

@chrisalley https://github.com/chrisalley If you want any help with cleanup for a 2.0, let me know. I could contribute a bit.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/chrisalley/pundit-matchers/issues/30#issuecomment-611229612, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAAG4JPIWR47W2CWDIOI3KDRLT36NANCNFSM4J7AWTEA .

oneamtu avatar Apr 10 '20 01:04 oneamtu

I've merged your PR containing the warning, @dewyze; thanks for the contribution. I'll keep this issue open until a cleaner solution is implemented in Pundit Matchers 2.

chrisalley avatar Jul 04 '21 09:07 chrisalley

I understand that not_to *_actions can be automatically generated in some scenarios, but a simpler solution could be to disable negated matchers for *_actions and force to use the opposite expectation

Ref:

https://github.com/rspec/rspec-expectations/blob/8a468eea0a2ee456db4d6e2dccccd66ff269b862/lib/rspec/matchers/built_in/operators.rb#L58-L60

tagliala avatar May 02 '23 10:05 tagliala

Yes, I think simplifying the implementation here and disabling the negated matchers would be sensible and clearer from both a security and maintenance standpoint.

It's already noted in the readme that it is "it is recommended to always use is_expected.to rather than is_expected.not_to when using Pundit Matchers" and that the current behaviour may change in a future version.

The next steps could be:

  • Disable is_expected.not_to. For consistency, we could enforce the use of is_expected.to for all of the matchers.
  • If is_expected.not_to is used, display a warning to use an appropriate forbid matcher instead.
  • Since this a breaking change, release a new major version of the gem.

chrisalley avatar May 03 '23 08:05 chrisalley

@tagliala is investigating a way to support negated matchers as part of some experimental work.

chrisalley avatar May 08 '23 09:05 chrisalley

Closed via #81

tagliala avatar May 23 '23 09:05 tagliala

@chrisalley this can be closed as addressed in 3.0.0.beta1

tagliala avatar May 25 '23 16:05 tagliala