简体   繁体   中英

Issues with Rails 3.1, RSpec, and Authlogic

I am new to development with Rails - I read a number of books and am now diving in headfirst. I have a basic application set up with a User model, and UserSessions via Authlogic. I am testing with RSpec.

I had all of my tests passing earlier yesterday (kind of, see below) then implemented password reset functionality (via e-mail, HTTP), and now a good chunk of my tests have broken - appears to be something with logging in / Authlogic.

What I was alluding to above was that since the get-go, I've been having issues with Authlogic in my tests... it doesn't seem to want to log in. In Request specs (perhaps this is best addressed in a separate question) I can't log in (via visiting the login_path and fill_in fields) unless I manually sign up the user (signup_path, fill_in etc.) within that spec. Factory doesn't seem to do the trick... Everywhere I've looked says to do @user = Factory.create(:user) then UserSession.create(@user) but it tends to fail for me. Sometimes I'm able to use a spec_helper method (sign_in(@user)) to get logged in, other times it fails.

I should note that despite anything my tests are saying, everything works in development mode. Reading about TDD first made me gung-ho on testing, but this is crippling my motivation!

Anyways... here is some relevant code. Please let me know what else is required.

Typical errors:

Failure/Error: response.should render_template('users/show')expecting <"users/show"> but rendering with <"users/new,>.....

6) UsersController GET index for non admin users should protect the page
     Failure/Error: response.should redirect_to(root_path)
       Expected response to be a redirect to <http://test.host/> but was a redirect to <http://test.host/user_sessions/new>
     # ./spec/controllers/users_controller_spec.rb:72:in `block (4 levels) in <top (required)>'

  11) UsersController POST 'create' success should redirect to the user show page
     Failure/Error: response.should redirect_to(user_path(assigns(:user)))
       Expected response to be a redirect to <http://test.host/users/5> but was a redirect to <http://test.host/>
     # ./spec/controllers/users_controller_spec.rb:160:in `block (4 levels) in <top (required)>'

  13) UsersController POST 'create' success should be logged in
 Failure/Error: UserSession.find.should_not be_nil
   expected: not nil
        got: nil

Application Controller

class ApplicationController < ActionController::Base
  protect_from_forgery
  helper_method :logged_in?, :current_user, :admin?
  force_ssl if Rails.env.production?

  private

  def logged_in? # allows use of be_logged_in in RSpec
    !current_user.nil?
  end

  def admin? # For RSpec
    current_user.admin?
  end

  def current_user_session
    return @current_user_session if defined?(@current_user_session)
    @current_user_session = UserSession.find
  end

  def current_user
    return @current_user if defined?(@current_user)
    @current_user = current_user_session && current_user_session.record
  end

  def require_user
    unless current_user
      store_location
      flash[:error] = "You must be logged in to access this page"
      redirect_to new_user_session_path
      return false
    end
  end

  def require_no_user
    if current_user
      store_location
      flash[:error] = "You must be logged out to access this page"
      redirect_to user_path(current_user.id)
      return false
    end
  end

  def require_admin
    unless (current_user && current_user.admin?)
      redirect_to root_path
    end
  end

  def store_location
    session[:return_to] = request.fullpath
  end

  def redirect_back_or_default(default)
    redirect_to(session[:return_to] || default)
    session[:return_to] = nil
  end

end

UserSessions Controller

class UserSessionsController < ApplicationController
  before_filter :require_no_user, :only => [:new, :create]
  before_filter :require_user, :only => :destroy

  def new
    @user_session = UserSession.new
    @title = "Log In"
  end

  def create
    @user_session = UserSession.new(params[:user_session])
    if @user_session.save
      user = @user_session.user   
      flash[:success] = "Login successful!"
      redirect_back_or_default user_path(user.id)
    else
      flash[:error] = "Incorrect user name or password"
      redirect_to new_user_session_path
    end
  end

  def destroy
    current_user_session.destroy
    flash[:notice] = "You have been logged out"
    redirect_back_or_default(new_user_session_path)
  end

end 

User Controller:

class UsersController < ApplicationController
  before_filter :require_no_user, :only => [:new, :create]
  before_filter :require_user, :only => [:index, :show, :edit, :update, :destroy, :change_password]
  before_filter :require_admin, :only => [:index, :destroy]
  before_filter :check_for_last, :only => [:destroy]

  def new
    @title = "Sign Up"
    @user = User.new
  end

  def index
    @title = "User Index"
    @users = User.find(:all)
  end

  def create
    @user = User.new(params[:user])
    if @user.save
      flash[:notice] = "Thanks for signing up, we've delivered an email to you with instructions on how to complete your registration!"
      @user.deliver_verification_instructions!
      redirect_to root_url
      #UserSession.create @user # Turned off Authlogic automatic session maintenance.  See https://github.com/binarylogic/authlogic/issues/262
    else
      @title = "Sign Up"
      render :new
    end
  end

  def show
    if current_user.admin?
      @user = User.find(params[:id]) || current_user
    else
      @user = current_user
    end
    @title = "Profile for #{@user.name}"
  end

  def edit
    if current_user.admin? && (params[:id] != current_user.id)
      redirect_to user_path(current_user.id), :notice => "Although you are an admin, you tried to edit someone else's profile.  Do this otherwise (console)     to avoid errors"
    end
    @user = current_user
    @title = "Edit User: #{@user.name}"
  end

  def update
    @user = @current_user # makes our views "cleaner" and more consistent
    if @user.update_attributes(params[:user])
      flash[:success] = "Edited your profile!"
      redirect_to user_path(@user)
    else
      render :edit
    end
  end

  def destroy
    User.find(params[:id]).destroy
    flash[:success] = "User destroyed."
    redirect_to users_path
  end

  def change_password
    @user = current_user
    @title = "Reset Password"
  end

  def update_password
    @user = current_user
    redirect_to root_path unless @user
    @user.password = params[:user][:password]  
    @user.password_confirmation = params[:user][:password_confirmation]  
    if @user.save
      UserSession.create @user # Turned off Authlogic automatic session maintenance.  See https://github.com/binarylogic/authlogic/issues/262
      flash[:success] = "Password successfully updated"  
      redirect_to user_path(@user.id)
    else  
      render :action => :change_password 
    end  
  end

  private

  def check_for_last
    if (User.all.size == 1)
      flash[:notice] = "You are trying to delete the last user... do that in console"
      redirect_to users_path
      return false 
    end
  end

end

User Model:

class User < ActiveRecord::Base

  acts_as_authentic do |c| 
    c.logged_in_timeout(30.minutes)
    c.login_field = :email 
    c.maintain_sessions = false
  end

  attr_accessible :name, :email, :password, :password_confirmation
  validates :name, :length => (2..50)
  validates :password, :length => {:minimum => 4}

  def deliver_verification_instructions!
    reset_perishable_token!  
    UserMailer.verification_instructions(self).deliver
  end

  def verify!
    self.verified = true
    self.save
  end

  def deliver_password_reset_instructions!
    reset_perishable_token!  
    UserMailer.password_reset_instructions(self).deliver
  end

end

UserSession Model:

class UserSession < Authlogic::Session::Base    
    logout_on_timeout true 
  consecutive_failed_logins_limit 10 
  failed_login_ban_for 1.hour

  validate :check_if_verified

  private

  def check_if_verified
    errors.add(:base, "You have not yet verified your account") unless attempted_record && attempted_record.verified
  end
end

UserSession Controller Spec

require 'spec_helper'

describe UserSessionsController do
  render_views

    before(:each) do
        @valid_user = Factory.create(:user)
      activate_authlogic
    end

    describe "Authlogic & Application Controller tests" do

    it "should allow login of a specified user, and logged_in? should provide correct response" do
      sign_in(@valid_user)
      UserSession.find.user.should == @valid_user
      logged_in?.should be_true
      logged_in?.email.should == @valid_user.email #logged_in function actually returns session
    end

    it "should allow logout of a specified user and logged_in? should provide correct response" do
      sign_in(@valid_user)
      logged_in?.should be_true
      sign_out
      logged_in?.should be_false
    end

    end # authlogic tests

    describe "GET 'new'" do

    it "should only get new if logged out" do
      sign_in(@valid_user)     
      get :new
      flash[:error].should =~ /You must be logged out/i
      response.should redirect_to user_path(@valid_user.id)
      response.code.should == "302" # redirected by require_no_user
    end

  end # get new

  describe "POST 'create'" do
    it "should create user session" do
      logged_in?.should be_false
      post :create, :user_session => { :email => @valid_user.email, :password => @valid_user.password }
      logged_in?.should be_true
    end

    it "should redirect to user show page, with flash success" do
      post :create, :user_session => { :email => @valid_user.email, :password => @valid_user.password }   
      assigns[:user_session].user.should == @valid_user
      response.code.should == "302"
      response.should redirect_to(user_path(@valid_user.id))
      flash[:success].should =~ /login succ/i 
    end    

    it "should not create user session for invalid password" do
      logged_in?.should be_false
      post :create, :user_session => { :email => @valid_user.email, :password => "" }
      logged_in?.should be_false
    end

    it "should redirect back to login page on invalid params, show proper flash" do
      post :create, :user_session => { :email => @valid_user.email, :password => "" }
      response.should redirect_to new_user_session_path
      ## BROKEN BECAUSE OF BEFORE_FILTER ##
      # response.should render_template('new')
      #response.should have_selector("div.flash.error", :content => "Incorrect user")
      flash[:error].should =~ /Incorrect user name or password/i 
    end

    it "should only create if logged out" do
      sign_in(@valid_user)
      post :create, :user_session => { :email => @valid_user.email, :password => @valid_user.password }
      flash[:error].should =~ /You must be logged out/i
      response.code.should == "302"
      response.should redirect_to user_path(@valid_user.id) 
    end
  end # post create

  describe "DELETE destroy" do
    it "should destroy user session if logged in" do
      sign_in(@valid_user)
      logged_in?.should be_true
      delete :destroy
      UserSession.find.should be_nil
      response.should redirect_to new_user_session_path
    end

    it "should not destroy if logged out" do
      logged_in?.should be_false
      delete :destroy, :user_session => { :email => @valid_user.email, :password => @valid_user.password }
      flash[:error].should =~ /You must be logged in/i
      response.code.should == "302" 
      response.should redirect_to new_user_session_path
    end

    it "should redirect to login page and show correct flash on logout" do
      sign_in(@valid_user)
      logged_in?.should be_true
      delete :destroy
      flash[:notice].should =~ /You have been logged/i
      response.should redirect_to new_user_session_path
    end

  end # delete destroy

end # specs

User Controller Spec

require 'spec_helper'

describe UsersController do
  render_views

  before(:each) do
    activate_authlogic
  end

  describe "GET 'new'" do

    it "should only work if not logged in" do
      user = Factory.create(:user)
      sign_in(user)
      get :new
      flash[:error].should =~ /must be logged out/i
      response.should redirect_to user_path(user)
    end
  end

  describe "GET index" do

    before(:each) do
      @admin_user = Factory.create(:user, :admin => true)
      @signed_in_user = Factory.create(:user)
      @user1 = Factory.create(:user)
      @user2 = Factory.create(:user)
      @user3 = Factory.create(:user)
      @users = [@admin_user, @signed_in_user, @user1, @user2, @user3]
    end

    describe "for non signed in users" do
      it "should deny access" do
        get :index
        flash[:error].should =~ /must be logged in/i
        response.should redirect_to new_user_session_path
      end    
    end

    describe "for non admin users" do
      it "should protect the page" do
        sign_in(@signed_in_user)
        get :index
        response.should redirect_to(root_path)
        flash[:error].should be_nil
        flash[:success].should be_nil
        flash[:notice].should be_nil               
      end
    end

    describe "for admin users" do

      before(:each) do
        sign_in(@admin_user)
      end    
    end      
  end

  describe "POST 'create'" do

    describe "failure" do

      before(:each) do
        @attr = {:name=>"", :email=>"", :password=>"", :password_confirmation=>""}
      end

      it "should not create a user" do
        lambda do
          post :create, :user => @attr
        end.should_not change(User, :count)
      end

      it "should render the 'new' page" do
        post :create, :user => @attr
        response.should render_template('new')
        response.should have_selector("div.error_messages")
      end

      it "should only create a user if not logged in" do
        @another_user = Factory.create(:user)
        sign_in(@another_user)
        @valid_attr = { :name=> 'New User', :email => 'somemail@gmail.com',:password => 'foobar', :password_confirmation => 'foobar' }
        post :create, :user => @attr
        flash[:error].should =~ /must be logged out/i
        response.should redirect_to user_path(@another_user)
      end

    end #failure

    describe "success" do

      before(:each) do
        @attr = { :name=> 'New User', :email => 'somemail@gmail.com',:password => 'foobar', :password_confirmation => 'foobar' }
      end

      it "should create a user" do
        lambda do
          post :create, :user => @attr
        end.should change(User, :count).by(1)
      end

      it "should redirect to the user show page" do
        post :create, :user => @attr
        assigns[:user].email.should == "somemail@gmail.com"
        response.should redirect_to(user_path(assigns(:user)))
      end

      it "should have a welcome message" do
        post :create, :user => @attr
        flash[:success].should =~ /signed/i
      end

       it "should be logged in" do
        post :create, :user => @attr
        UserSession.find.should_not be_nil
      end

    end #success
  end # post create

  describe "GET edit" do

    before(:each) do
      @valid_user = Factory.create(:user)
      sign_in(@valid_user)
    end

    describe "success" do    

      it "should have the right title" do
        get :edit, :id => @valid_user
        response.should have_selector("title", :content => "Edit User: #{@valid_user.name}")
      end 

    end # success

    describe "failure" do

      it "should not get edit if logged out" do
        sign_out
        get :edit, :id => @valid_user
        flash[:error].should =~ /must be logged in/i
        response.should redirect_to new_user_session_path
      end

    end # failure   
  end # GET edit

  describe "GET show" do

    before(:each) do
      @valid_user = Factory.create(:user)
      sign_in(@valid_user)
    end

    describe "success" do

      it "should get show if logged in" do
       get :show, :id => @valid_user
       response.should be_success
      end 

    end # success

    describe "failure" do

      it "should not get show if logged out" do
        sign_out
        get :show, :id => @valid_user
        flash[:error].should =~ /must be logged in/i
        response.should redirect_to new_user_session_path
      end

      it "should only be able to see their own page" do
        @another_user = Factory.create(:user)
        get :show, :id => @another_user
        response.should have_selector("title", :content => "Profile for #{@valid_user.name}")
      end

    end # failure   

    describe "admin users" do

      before(:each) do
        @admin_user = Factory.create(:user, :admin => true)
        sign_out
        sign_in(@admin_user)
      end

      it "should get their own show page if that is the selection" do
        get :show, :id => @admin_user
        response.should have_selector("title", :content => "Profile for #{@admin_user.name}")
      end

      it "should also be able to get other people's show page if that is the selection" do
        get :show, :id => @valid_user
        response.should have_selector("title", :content => "Profile for #{@valid_user.name}")
      end

      it "should not show admin content on admin login" do
        sign_in(@valid_user)
        get :show, :id => @valid_user
        response.should_not have_selector("section.admin>ul>li>a", :content => "Show Users", :href => users_path)
      end

    end

  end # GET show

  describe "PUT update" do

    before(:each) do
      @valid_user = Factory.create(:user)
      sign_in(@valid_user)
    end

    describe "success" do

      before(:each) do
        @new_attrs = { :name => "new name", :email => "myname@gmail.com", :password => @valid_user.password, :password_confirmation => @valid_user.password}
      end

      it "should correctly update the user with valid attributes" do
        put :update, :id => @valid_user.id, :user => @new_attrs
        assigns[:user].name.should == "new name"
        assigns[:user].email.should == "myname@gmail.com"
        User.find(@valid_user.id).email.should == "myname@gmail.com"
      end

      it "should redirect to the user show page" do
        put :update, :id => @valid_user.id, :user => @new_attrs
        response.should redirect_to user_path(@valid_user)
        flash[:success].should =~ /edited/i
      end

    end # success

    describe "failure" do

      before(:each) do
        @bad_attrs = { :name => "", :email => "nottaken"}
      end

      it "should not update user attributes if invalid" do
        original_email = @valid_user.email   
        put :update, :id => @valid_user, :user => @bad_attrs
        @valid_user.reload
        @valid_user.email.should == original_email
        logged_in?.should be_true
        User.find_by_email(original_email).should_not be_nil
      end

      it "should re-render the edit page" do
        put :update, :id => @valid_user, :user => @bad_attrs
        #controller.stub!(:require_user).and_return(true)
        response.should render_template('edit')
        logged_in?.should be_true
      end

      it "should not allow edit, even with valid attributes, if not logged in" do
        sign_out
        original_email = @valid_user.email
        put :update, :id => @valid_user, :user => @new_attrs
        @valid_user.reload
        @valid_user.email.should == original_email
        response.should redirect_to new_user_session_path
        logged_in?.should be_false
      end

      it "should not allow you to take another user's email" do
        @another_user = Factory.create(:user)
        put :update, :id => @valid_user, :user => { :name => "valid name", :email => @another_user.email}
        response.should render_template('edit')
        response.should have_selector("div.error_messages")
      end

    end #failure

  end # post update

  describe "DELETE destroy" do

    before(:each) do
       @valid_user = Factory.create(:user)
     end

    describe "as a non-signed-in user" do
      it "should deny access" do
        delete :destroy, :id => @valid_user
        flash[:error].should =~ /must be logged in/i
        response.should redirect_to new_user_session_path
      end
    end

    describe "as a non-admin user" do
      it "should protect the page" do
        sign_in(@valid_user)
        delete :destroy, :id => @valid_user
        response.should redirect_to(root_path)
        flash[:error].should be_nil
        flash[:success].should be_nil
        flash[:notice].should be_nil               
      end
    end

    describe "as an admin user" do

      before(:each) do
        @admin = Factory(:user, :admin => true)
        sign_in(@admin)
      end

      it "should destroy the user" do
        lambda do
          delete :destroy, :id => @valid_user
        end.should change(User, :count).by(-1)
      end

      it "should redirect to the users page with the correct flash" do
        delete :destroy, :id => @valid_user
        response.should redirect_to(users_path)
        flash[:success].should =~ /user destroyed/i
      end

    end

  end

end

User factory:

Factory.define :valid_user , :class => User do |u|
  u.name "brandon"
  u.email "brandon@example.com"
  u.password "foobar"
  u.password_confirmation "foobar"
end

Factory.define :invalid_user , :class => User do |u|
  u.name ""
  u.email "brandon@example"
  u.password "f"
  u.password_confirmation "f"
end

Factory.sequence :email do |n|
  "person#{n}@example.com"
end

Factory.sequence :name do |n|
  "John Doe the #{n}"
end

Factory.define :user do |f|

  f.name {Factory.next(:name)}
  f.email {Factory.next(:email)}
  f.password "foobar"
  f.password_confirmation "foobar"
end 

Spec helper

require 'database_cleaner'
require 'spork'
require 'factory_girl'
require 'authlogic/test_case'

Spork.prefork do
  # Loading more in this block will cause your tests to run faster. However,
  # if you change any configuration or code from libraries loaded here, you'll
  # need to restart spork for it take effect.  

    def sign_in(user)
        u = UserSession.create(user)
    end

    def sign_out
        us = UserSession.find
        us.destroy
    end

    def logged_in?
      current_user_session = UserSession.find
      return current_user_session.record if current_user_session
      return false
    end

  DatabaseCleaner.strategy = :truncation

    # This file is copied to spec/ when you run 'rails generate rspec:install'
    ENV["RAILS_ENV"] ||= 'test'
    require File.expand_path("../../config/environment", __FILE__)
    require 'rspec/rails'
    require 'rspec/autorun'
  require 'webrat'

  include Authlogic::TestCase

    # Requires supporting ruby files with custom matchers and macros, etc,
    # in spec/support/ and its subdirectories.
    Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}

  Webrat.configure do |config|
    config.mode = :rails
  end

    RSpec.configure do |config|
        # == Mock Framework
        #
        # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
        #
        # config.mock_with :mocha
        # config.mock_with :flexmock
        # config.mock_with :rr
        config.mock_with :rspec

    ApplicationController.skip_before_filter :activate_authlogic
    config.before(:each, :type => :request) do
      activate_authlogic
      #UserSession.create(User.find_by_email!(email))
    end

        # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
        config.fixture_path = "#{::Rails.root}/spec/fixtures"

        # If you're not using ActiveRecord, or you'd prefer not to run each of your
        # examples within a transaction, remove the following line or assign false
        # instead of true.
        config.use_transactional_fixtures = true

        # If true, the base class of anonymous controllers will be inferred
        # automatically. This will be the default behavior in future versions of
        # rspec-rails.
        config.infer_base_class_for_anonymous_controllers = false
    end

end

Spork.each_run do
  # This code will be run each time you run your specs.

  FactoryGirl.reload 
  ActiveSupport::Dependencies.clear

  RSpec.configure do |config|

    config.before(:each) do
          DatabaseCleaner.start
        end

        config.after(:each) do
          DatabaseCleaner.clean
        end

  end

 # DatabaseCleaner.clean

end

As an aside (somewhat, might be related to my other issues) I keep getting this frustrating error

  16) UsersController GET edit success should render the 'edit' page
 Failure/Error: response.should render_template('edit')
   expecting <"edit"> but rendering with <"">
 # ./spec/controllers/users_controller_spec.rb:203:in `block (4 levels) in <top (required)>'

I appreciate any help that can be provided - I am at my wits end!!. I will post any additional code that you need. THANKS!

您没有在调用setup :activate_authlogic

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM