varvet / pundit

Minimal authorization through OO design and pure Ruby classes
MIT License
8.28k stars 630 forks source link

Tests: Setting custom context #652

Closed aximuseng closed 4 years ago

aximuseng commented 4 years ago

My app uses additional context:

https://github.com/varvet/pundit#additional-context

class UserContext
  attr_reader :user, :enrollment

  def initialize(user, enrollment)
    @user = user
    @enrollment = enrollment
  end
end

Here is my application policy:

class ApplicationPolicy
  attr_reader :enrollment, :record

  def initialize(enrollment, record)
    @enrollment = enrollment
    @record = record
  end
end

In my application controller:

def pundit_user
    current_enrollment
  end

So here is a typical policy:

def download?
    return true if enrollment.has_role?('administrator')
    false
  end

The enrollment model has various helpers for my authorzation scheme. I tried this in both my test_helper.rb and individual tests:

enrollment = enrollments(:administrator)

Thats a valid fixture but my tests will fail with:

NoMethodError: undefined method 'has_role?' for nil:NilClass.

Clearly the enrollment in the policy is not set. Done lots of searching on this but could not find another similar question etc. Also tried setting pundit_user in my tests - no luck.

Linuus commented 4 years ago

You’ll have to show more code for anyone to answer.

From what you’ve shown so far I don’t think this issue has anything to do with Pundit.

aximuseng commented 4 years ago

I updated the post with more detail. Pundit work great in the app and I don’t think that Pundit is the problem - just that I may be missing something.

Linuus commented 4 years ago

You will only receive the context and record in the initializer.

def initialize(context, record)
  @user = context.user
  @enrollment = context.enrollment
  @record = record
end
aximuseng commented 4 years ago

To clarify - are you saying I can just add this initializer to my tests?

Linuus commented 4 years ago

No. I’m saying your ApplicationPolicy initializer is wrong :)

aximuseng commented 4 years ago

I ended up with this working (in my app):

  def initialize(enrollment, record)
    @user = enrollment.user
    @enrollment = enrollment
    @record = record
  end

This is still fails in Minitest. You suggested code failed but I figured out is perhaps should be:

  def initialize(context, record)
    @user = context.user
    @enrollment = context
    @record = record
  end

This worked in my app but tests still fail:

NoMethodError: undefined method 'user' for nil:NilClass

In my Application controller my enrollment is set via a session variable. I am honestly at a lost where the magic happens and Pundit has the enrollment / context passed to it via the initialization.

To make matters worse I didn't build this - I had a contractor build this years ago. It works great except the tests. I would also be happen to just disable authorization in the tests completely and then I can write specific policy tests to confirm that my auth is good.

Linuus commented 4 years ago

You’ll have to show current_enrollment. It returns nil in your test for some reason. Also, show the test.

aximuseng commented 4 years ago

It's nil because its based on a session variable session[:enrollment_id] . I did some more digging and it doesn't look good.

Here is one of my controller tests where I added the session (only to find out you can't do this anymore):

  test "should get index" do
    get wells_url, session: { enrollment_id: 10 }
    assert_response :success
  end

After the user authenticates my ApplicationController has a method where if the session[:enrollment_id] is not set it looks up the current_users enrollments and sets the last one and then switches tenants etc. This is then used in the current_enrollment method.

I can't seem to find anywhere how you can set the session in a controller test - this may be a dead end. Can I just disable pundit completely in these tests?

Linuus commented 4 years ago

Try controller.session[...] = ...

If that doesn’t work, call the endpoint that sets that variable in your controller.

However, as I said above, this isn’t related to Pundit really so I’ll close it.

aximuseng commented 4 years ago

Thanks for your help - I agree it's not Pundit. controller.session[...] = didn't work. Also tried calling the method that sets that variable and I get NameError: undefined local variable or method 'set_identity'.

Linuus commented 4 years ago

Also tried calling the method that sets that variable and I get NameError: undefined local variable or method 'set_identity'.

I meant actually posting to it in the test.

post login_or_what_it_is_url, params: { ... }

aximuseng commented 4 years ago

Talk about a rabbit hole - I had a line in my application controller that was intended to skip the setting of the enrollment etc. if the user was not signed in but I had added another redundant conditional that fails in the test but passes in dev/ pro. The tests run fine now. Thanks again.