I'm working on a Rails 3 web app at the moment with RSpec 2 and we're using Devise for authentication. One (and soon many) of our controllers require the user to be logged in. I know Devise provides the sign_in test helper, but can it be used with an RSpec or Mocha mock object?
I originally tried #user = mock_model(User) where user is the Devise class. This wouldn't work with sign_in :user, #user as get 'index' would redirect to the sign in form.
Does anyone have any experience testing with Devise and can help?
We had a similar problem, but using Factory Girl. We solved it like so:
In spec_helper.rb:
config.include Devise::TestHelpers, :type => :controller
In the controller spec (just a wrapper method):
def login_user(user)
sign_in user
end
Then in each method you require, you can do:
login_user(Factory(:user))
... where you have defined a user object in factories.rb. Not sure if this will work with mocks though.
A mock is never going to work. When you say sign in, the user is stored in session (basically, the user class and its id). When you access the controller, another user object is retrieved based on the stored data. The best way to solve the problem is using something that persists the object, like Factory Girl.
I hit the same issue. I'm doing the following for now:
before(:each) do
# sign_in mock_user
request.env['warden'] = mock(Warden, :authenticate => mock_user,
:authenticate! => mock_user)
end
I've created an issue for this here: https://github.com/plataformatec/devise/issues#issue/928
Go vote!
None of them worked for me (MRI 1.9.3-preview1, rails 3.0.1.rc5).
this is the solution i found : http://blog.joshmcarthur.com/post/6407481655/integration-tests-with-devise-and-rspec
Related
This SO answer shows how to authenticate the rails_admin gem when you have a roll your own authentication. The method follows this pattern. However, this solution no longer works with rails 5. Instead, when trying to access an admin view, the following exception is triggered in config/initializers/rails_admin.rb
undefined method `signed_in?' for #<RailsAdmin::MainController:0x007fbe38628ab0>
How do you fix this?
The usual view helpers, like signed_in? or current_user are no longer accessible in the initializer, so the solution that I came up with was:
config.authenticate_with do
current_user = User.find_by_id(session[:user_id])
raise 'You must be admin' unless current_user.try(:admin?)
end
Is it possible to use Ruby on Rails' before_filter method to check that one of multiple things is true? Specifically, I am using Devise in which I have defined a user and an admin and I would like to ensure that whoever is accessing a specific controller is one or the other. I have looked for answers in documentation and SO and haven't had any luck.
Thanks, here are my versions (if it helps):
Ruby 1.9.3
Rails 3.2.6
Devise 2.2.3
It is possible. I'm hesitant to actually place this as an answer because it doesn't get any simpler than this. I am assuming that you have an admin? method that returns true or false depending on the user's role.
before_filter :check_if_admin
protected
def check_if_admin
if signed_in?
raise 'Only admins allowed!' unless current_user.admin?
else
# or you can use the authenticate_user! devise provides to only allow signed_in users
raise 'Please sign in!'
end
end
I'm trying to figure out before_filters and I was hoping for a little help.
I have a simple blog app, with entries that can be (draft or published) and (public or private). I was wondering how I can do my authentication?
I currently have:
before_filter :authenticate, :except => [ :show ]
So that blocks all the CRUD actions. For show, I need to check that:
If its a draft, that the logged in user owns the entry.
If its private, a user is logged in (in this, all logged in users can see private entries)
I can do it within the action itself, but it seems that the rails way of doing it, is in a before_filter.
Thanks!
Do it in the before filter. If you are using devise then the current_user method is available if a user is logged in. Otherwise replace current_user with the value returned from your authenticate method.
def find_post
#post = Post.find(params[:id])
redirect_to root_path if #post.draft && #post.user != current_user
redirect_to root_path if #post.private && !current_user
end
Using Rails 3.0.6, Omniauth 0.2.0 and Devise 1.2.1, I'm encountering the following situation:
I want to offer users the option to authenticate via Facebook. I have a user system set up using Devise and I can successfully auth using Facebook. I've spent several hours trying to code the behavior I want for one specific situation:
user is not logged in
user has a site account
user authenticates via Facebook
I offer the user 2 choices at this point
create an account (can be a dummy account with no provided info)
link this Facebook authentication with an existing account
I'm having trouble with the latter option. The user has already authenticated but I still need him to log in with his site account. I have an action in my AuthenticationsController that will associate this authentication with a logged in user. Devise doesn't seem to offer a way for me to log the user in while staying in the same action, though. This was my first attempt to do this
class AuthenticationsController < ApplicationController
before_filter :authenticate_user!, :only => :auth_link_existing_user
...
def auth_link_existing_user
...
end
However, using this method, if the user logs in, they're simply redirected to my site's root page. I know I can change Devise's sign-in redirect, but that will be for all sign-ins. I wanted only this situation to have a separate redirect.
After reading through this mailing list question, I tried to extend SessionsController with my own custom behavior:
def create
resource = warden.authenticate!(:scope => resource_name, :recall => "#{controller_path}#new")
set_flash_message(:notice, :signed_in) if is_navigational_format?
sign_in(resource_name, resource)
if params[:redirect] #new
redirect_to params[:redirect].to_sym #new
else
respond_with resource, :location => redirect_location(resource_name, resource)
end
end
This doesn't work either. I've defined my auth_link_existing_user route to use a POST verb (which seems accurate) and redirects can only be GETs.
So now I do have a solution in mind: copy and paste code from Devise's authenticate_user! helper into a new function which can be called within a controller action without redirecting. This seems less than ideal to me because it's duplication of code and increases coupling--a Devise or Warden update that changes this behavior will break my code as well.
Has anyone else tried something like this and come up with a more elegant solution? Do you see a simpler way for me to offer this or similar behavior to my users?
UPDATE: For anyone who wants to use my dirty solution at the end, this is what I did:
def auth_link_existing_user
# FROM Devise/sessions/create
resource = warden.authenticate!(:scope => :user, :recall => "registrations#auth_new")
set_flash_message(:notice, :signed_in) if is_navigational_format?
sign_in(:user, resource)
# method defined in Ryan Bates' Railscast for Omniauth w/Devise
current_user.apply_omniauth(session[:omniauth])
current_user.save
end
note that this action MUST be placed in your sessions controller. If not, Warden will give you an "invalid email/password" error. It was an incredibly long debugging process to find the source.
With this in place, I use a login form to submit to this action after the user has authenticated.
I like how clean your solution is, though it goes deeper into the stack.
Here is how I've implemented something similar by following the Devise+Omniauth Facebook example on the Devise wiki and modifying the facebook method to pass on the session information to the Login form, with something like this:
if #user.persisted?
flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Facebook"
sign_in_and_redirect #user, :event => :authentication
else
session["devise.facebook_data"] = request.env["omniauth.auth"]
redirect_to new_user_session_url
end
Then, in your case, I'd check in Login controller action for session["devise.facebook_data"], submit the uid + token with the form and apply_omniauth if present.
I recently added the Devise engine to a project with an existing authentication system (from the Rails Tutorial).
Before I added Devise, my UsersController handled all user registration facilities new, create, edit, destroy and I had a lot of rspec tests written for them. Of course now that registration is handled by Devise, I've had to remove those tests from users_controller_spec.rb.
Where can I put those tests to ensure that the Devise registration process is working correctly?
EDIT:
Ok I believe I've made some progress with this since I posted the question.
First I overrided the Devise registrations controller by making my own RegistrationsController in /app/controllers/registrations_controller.rb.
class RegistrationsController < Devise::RegistrationsController
end
Then I tell Devise to use this controller for registrations in my /config/routes.rb file:
devise_for :users, :controllers => { :registrations => "registrations" }
Now all I need to do it make my tests in /spec/controllers/registrations_controller_spec.rb work.
Am I on the right track?
That would be a good solution if you planned on overriding some of the controller methods. Typically, you don't test other frameworks like this that already have their own suite of tests. You are just duplicating the tests. That said, if you are more comfortable having your own, go for it.