I would like to test a helper method using Minitest (minitest-rails) - but the helper method depends on current_user, a Devise helper method available to controllers and view.
app/helpers/application_helper.rb
def user_is_admin? # want to test
current_user && current_user.admin?
end
test/helpers/application_helper_test.rb
require 'test_helper'
class ApplicationHelperTest < ActionView::TestCase
test 'user is admin method' do
assert user_is_admin? # but current_user is undefined
end
end
Note that I am able to test other helper methods that do not rely on current_user.
When you test a helper in Rails, the helper is included in the test object. (The test object is an instance of ActionView::TestCase.) Your helper's user_is_admin? method is expecting a method named current_user to also exist. On the controller and view_context objects this method is provided by Devise, but it is not on your test object, yet. Let's add it:
require 'test_helper'
class ApplicationHelperTest < ActionView::TestCase
def current_user
users :default
end
test 'user is admin method' do
assert user_is_admin?
end
end
The object returned by current_user is up to you. Here we've returned a data fixture. You could return any object here that would make sense in the context of your test.
Related
I am trying to make sure that the following method
def current_user
current_user = current_member
end
Is available to all actions in all my controllers
I have tried putting it in the ApplicationsController with no luck.
I tried using solutions in the following
Where to put Ruby helper methods for Rails controllers?
With no effect.
What is the Rails-way solution to this?
I have the same method in my ApplicationsHelper and I can access it in my views no problem.
EDIT:
To give more detail.
I had an application with an authentication system I built from scratch and this used a function in a SessionHelper file called "current_user"
I have been implementing Devise into my app, and maintained my user model to hold the user details, but created a member model to hold the devise authentication information (i.e. keep the user profile info separate from the table devise is using as suggested by the doc).
This gives me a devise helper method called current_member (based on my naming of the model).
I have "current_user" all over my app, both in controller actions and in views.
I want to create an app-wide helper that will alias current_member to current_user. Strictly speaking in my question my function is wrong - this will assign current_user to an instance of the member class. Since there is a one to one relationship between member and user, with the foreign key being member.id the correct function is....
def current_user
if member_signed_in?
current_user = User.find_by_member_id(current_member.id)
end
end
My ApplicationHelper:
module ApplicationHelper
def current_user
if member_signed_in?
current_user = User.find_by_member_id(current_member.id)
end
end
end
This takes care of current_user in all the views,
But I can't get it to work in the controllers...see for example this code in my "show" action of the UserController
def show
#associates = []
#colleagues = current_user.nearbys(1000).take(20)
#colleagues.each do |associate|
unless current_user.following?(associate) || current_user == associate
#associates.push(associate)
end
end
impressionist(#user)
end
Forget the logic- I am just using geocoder to find nearly users. Its that current_user is resolving to "nil".
Even if I put
before_action :current_user
def current_user
if member_signed_in?
current_user = User.find_by_member_id(current_member.id)
end
end
In the UserController, current_user isn't working within the action. I have current_user in actions of other controllers too and the app breaks at these points, but not when current_user is in a view.
Let me know if you need more info.
EDIT 2:
I added
before_action :authenticate_member!
To the UsersController, but this still had no effect.
EDIT 3:
I'm an idiot. The nil class error was occurring because I had no seed data in the database, thus the
#colleagues = current_user.nearbys(1000).take(20)
#colleagues was nil, and therefore calling "take" on nil was throwing an error.
Rookie mistake.
When you define application actions, if you want them to be available across all other actions you need to set a before filter. So in your application controller you would have something like:
before_action :current_user
def current_user
current_user = current_member
end
This would run this action before any other action in your application regardless of the controller
I think there are two parts of your question.
1 - where to put functions that are needed across all controllers ?
So the general answer is to put them in the ApplicationController, because normally all the other controllers are inheriting from ApplicationController
2 - About the error you are getting.
My guess is , you are not loading devise before you call the devise method. So try something like
class ApplicationController < ActionController::Base
before_action :authenticate_member!
before_action :current_user
def current_user
#your method
end
end
and as a suggestion, since you are using the same method in the helpers, to keep things DRY, you can make the controller method a helper method. So it will be available across the views
class ApplicationController < ActionController::Base
helper_method :current_user
before_action :authenticate_member!
before_action :current_user
def current_user
#your method
end
end
so in your view you can use current_user
If all of that fails, as #abbott567 said post your error log.
HTH
I'm writing some tests for my Django Rest Framework and trying to keep them as simple as possible. Before, I was creating objects using factory boy in order to have saved objects available for GET requests.
Why are my POST requests in the tests not creating an actual object in my test database? Everything works fine using the actual API, but I can't get the POST in the tests to save the object to make it available for GET requests. Is there something I'm missing?
from rest_framework import status
from rest_framework.test import APITestCase
# from .factories import InterestFactory
class APITestMixin(object):
"""
mixin to perform the default API Test functionality
"""
api_root = '/v1/'
model_url = ''
data = {}
def get_endpoint(self):
"""
return the API endpoint
"""
url = self.api_root + self.model_url
return url
def test_create_object(self):
"""
create a new object
"""
response = self.client.post(self.get_endpoint(), self.data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(response.data, self.data)
# this passes the test and the response says the object was created
def test_get_objects(self):
"""
get a list of objects
"""
response = self.client.get(self.get_endpoint())
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, self.data)
# this test fails and says the response is empty [] with no objects
class InterestTests(APITestCase, APITestMixin):
def setUp(self):
self.model_url = 'interests/'
self.data = {
'id': 1,
'name': 'hiking',
}
# self.interest = InterestFactory.create(name='travel')
"""
if I create the object with factory boy, the object is
there. But I don't want to have to do this - I want to use
the data that was created in the POST request
"""
You can see the couple lines of commented out code which are the object that I need to create through factory boy because the object does not get created and saved (although the create test does pass and say the object is created).
I didn't post any of the model, serializer or viewsets code because the actual API works, this is a question specific to the test.
First of all, Django TestCase (APITestCase's base class) encloses the test code in a database transaction that is rolled back at the end of the test (refer). That's why test_get_objects cannot see objects which created in test_create_object
Then, from (Django Testing Docs)
Having tests altering each others data, or having tests that depend on another test altering data are inherently fragile.
The first reason came into my mind is that you cannot rely on the execution order of tests. For now, the order within a TestCase seems to be alphabetical. test_create_object just happened to be executed before test_get_objects. If you change the method name to test_z_create_object, test_get_objects will go first. So better to make each test independent
Solution for your case, if you anyway don't want database reset after each test, use APISimpleTestCase
More recommended, group tests. E.g., rename test_create_object, test_get_objects to subtest_create_object, subtest_get_objects. Then create another test method to invoke the two tests as needed
In my project I need to use a global variable like the one of devise (current_user) because in the ability.rb I want to know in which project he is located. Something like current_project. Maybe an attribute of the session, like the project_id.
Someone have done something like that? Thanks a lot!
try this :
You actually need to store the variable in a session.
Taking the example of current_user it self :-
In your application controller ( /app/controllers/application_controller.rb ) add a helper method as follows:
class ApplicationController < ActionController::Base
protect_from_forgery
helper_method :current_user
private
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
end
The current_user method gets the current user by its id, using the id from the session variable, and caches the result in an instance variable. We’ll make it a helper method too so that we can use it in the application’s view code.
reference - http://railscasts.com/episodes/250-authentication-from-scratch?view=asciicast
I'm starting with the Rails 4.1 Pundit / Devise app from RailsApps.org and continue to get undefined method errors when 'authorize User' is called in the User controller. The user can register, log in, and edit their account info. When the Users link is clicked, the following results:
NoMethodError in UsersController#index
undefined method `authorize' for #
Here is the UsersController...
class UsersController < ApplicationController
before_filter :authenticate_user!
after_action :verify_authorized
def index
#users = User.all
authorize User # <== This is the line the error occurs on
end
def show
#user = User.find(params[:id])
authorize #user
end
def update
#user = User.find(params[:id])
authorize #user
if #user.update_attributes(secure_params)
redirect_to users_path, :notice => "User updated."
else
redirect_to users_path, :alert => "Unable to update user."
end
end
def destroy
user = User.find(params[:id])
authorize user
user.destroy
redirect_to users_path, :notice => "User deleted."
end
private
def secure_params
params.require(:user).permit(:role)
end
end
and the ApplicationController:
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
end
Any thoughts on how to resolve this?
Thanks in advance!
Comment: This is from the RailsApp Pundit Quickstart guide to explain authorize
The keyword authorize is a helper method that provides a shortcut to a longer statement that implements the actual authorization. We never see the full statement because we use the helper method, but if we were to use it, it would look like this:
raise "not authorized" unless UserPolicy.new(current_user, User).index?
The authorize helper method finds a UserPolicy class and instantiates it, passing the current_user object and either the User class or an instance of the User model, and calling an index? method to return true or false. You may wonder why we can provide either the User class (as in the index action) or the #user instance variable (as in the show action).
Pundit looks for a policy object when authorize is called from a controller action. We already saw that Pundit will find a UserPolicy if given any of these arguments:
authorize User – the User class
authorize #user – an instance variable that is an instance of the User class
authorize user – a simple variable that is an instance of the User class
authorize #users – an array of User objects
To me, it seems as if the helper method is found sometimes like in show and update but not index.
It looks like this issue is being discussed here: https://github.com/RailsApps/rails-devise-pundit/issues/10
Basically, your solutions are to:
A) Restart the rails server and the problem should go away. You will have to do this whenever the problem shows up (editing the file, etc). (It shouldn't happen in production if it's any consolation)
B) Move the code in config/intiializers/pundit.rb in to ApplicationController (without the included do...end block)
User is a class name, not an instance. Also authorize used for create/update/edit actions. For index you should use Policy.
For example, UserPolicy:
def index
#users = UserPolicy::Scope.new(current_user, User).resolve
end
I have a controller method that I'm annotating like so:
#Secured(['ROLE_ADMIN'])
def save() {
... // code ommitted
}
I'm trying to write a unit test to verify that only the admin user can hit the URL:
def "Only the admin user should be able to invoke save"() {
given:
def user = createNonAdminUser() // let's pretend this method exists
controller.springSecurityService = Mock(SpringSecurityService)
controller.springSecurityService.currentUser >> user
when:
controller.save()
then:
view ==~ 'accessdenied'
}
However, the view returned is the save view and not the access denied view. It looks like it's bypassing the #Secured annotation altogether. Is there a way to test #Secured annotations from either a unit test or integration test?
Try this:
SpringSecurityUtils.doWithAuth('superuser') {
controller.save()
}
http://greybeardedgeek.net/2011/05/13/testing-grails-controllers-with-spock/
You would need to login the user before calling controller save if you are not doing it already in createNonAdminUser().
SpringSecurityUtils.reauthenticate username, password
Possibly related to this question.