Topic: restful_authentication unit test

I have been following along the screencast #67 over at http://railscasts.com/episodes/67. While the plugin seems to be working fine, one of its unit tests fails and I cannot figure out why. I am running on rails 1.2.3

Note: I used "player" instead of "user" for my user model.

$ ruby test/unit/player_test.rb

Loaded suite test/unit/player_test
Started
........F....Finished in 0.171904 seconds.

  1) Failure:test_should_require_password(PlayerTest)
    [test/unit/player_test.rb:23:in `test_should_require_password'   
    /Users/ia00stai/railsdev/signmeup/trunk/signmeup/config/../lib/authenticated_test_helper.rb:
16:in `assert_difference'
     /Users/ia00stai/railsdev/signmeup/trunk/signmeup/config/../lib/authenticated_test_helper.rb:
24:in `assert_no_difference'
     test/unit/player_test.rb:
21:in `test_should_require_password']:<nil> is not true.

13 tests, 25 assertions, 1 failures, 0 errors


From the model player.rb:

require 'digest/sha1'
class Player < ActiveRecord::Base
  # Virtual attribute for the unencrypted password
  attr_accessor :password

  validates_presence_of     :login, :email
  validates_presence_of     :password,                   :if => :password_required?
  validates_presence_of     :password_confirmation,      :if => :password_required?
  validates_length_of       :password, :within => 4..40, :if => :password_required?
  validates_confirmation_of :password,                   :if => :password_required?
  validates_length_of       :login,    :within => 3..40
  validates_length_of       :email,    :within => 3..100
  validates_uniqueness_of   :login, :email, :case_sensitive => false
  before_save :encrypt_password
 
  # prevents a user from submitting a crafted form that bypasses activation
  # anything else you want your user to change should be added here.
  attr_accessible :login, :email, :password, :password_confirmation, :name
 
  # Authenticates a user by their login name and unencrypted password.  Returns the user or nil.
  def self.authenticate(login, password)
    u = find_by_login(login) # need to get the salt
    u && u.authenticated?(password) ? u : nil
  end

  # Encrypts some data with the salt.
  def self.encrypt(password, salt)
    Digest::SHA1.hexdigest("--#{salt}--#{password}--")
  end

  # Encrypts the password with the user salt
  def encrypt(password)
    self.class.encrypt(password, salt)
  end

  def authenticated?(password)
    crypted_password == encrypt(password)
  end

  def remember_token?
    remember_token_expires_at && Time.now.utc < remember_token_expires_at
  end

  # These create and unset the fields required for remembering users between browser closes
  def remember_me
    remember_me_for 2.weeks
  end

  def remember_me_for(time)
    remember_me_until time.from_now.utc
  end

  def remember_me_until(time)
    self.remember_token_expires_at = time
    self.remember_token            = encrypt("#{email}--#{remember_token_expires_at}")
    save(false)
  end

  def forget_me
    self.remember_token_expires_at = nil
    self.remember_token            = nil
    save(false)
  end

  protected
    # before filter
    def encrypt_password
      return if password.blank?
      self.salt = Digest::SHA1.hexdigest("--#{Time.now.to_s}--#{login}--") if new_record?
      self.crypted_password = encrypt(password)
    end
   
    def password_required?
      crypted_password.blank? || !password.blank?
    end
   
end


player_test.rb:

require File.dirname(__FILE__) + '/../test_helper'

class PlayerTest < Test::Unit::TestCase
  fixtures :players

  def test_should_create_player
    assert_difference 'Player.count' do
      player = create_player
      assert !player.new_record?, "#{player.errors.full_messages.to_sentence}"
    end
  end

  def test_should_require_login
    assert_no_difference 'Player.count' do
      u = create_player(:login => nil)
      assert u.errors.on(:login)
    end
  end

  def test_should_require_password
    assert_no_difference 'Player.count' do
      u = create_player(:password => nil)
      assert u.errors.on(:password)
    end
  end

  def test_should_require_password_confirmation
    assert_no_difference 'Player.count' do
      u = create_player(:password_confirmation => nil)
      assert u.errors.on(:password_confirmation)
    end
  end

  def test_should_require_email
    assert_no_difference 'Player.count' do
      u = create_player(:email => nil)
      assert u.errors.on(:email)
    end
  end

  def test_should_reset_password
    players(:quentin).update_attributes(:password => 'new password', :password_confirmation => 'new password')
    assert_equal players(:quentin), Player.authenticate('quentin', 'new password')
  end

  def test_should_not_rehash_password
    players(:quentin).update_attributes(:login => 'quentin2')
    assert_equal players(:quentin), Player.authenticate('quentin2', 'test')
  end

  def test_should_authenticate_player
    assert_equal players(:quentin), Player.authenticate('quentin', 'test')
  end

  def test_should_set_remember_token
    players(:quentin).remember_me
    assert_not_nil players(:quentin).remember_token
    assert_not_nil players(:quentin).remember_token_expires_at
  end

  def test_should_unset_remember_token
    players(:quentin).remember_me
    assert_not_nil players(:quentin).remember_token
    players(:quentin).forget_me
    assert_nil players(:quentin).remember_token
  end

  def test_should_remember_me_for_one_week
    before = 1.week.from_now.utc
    players(:quentin).remember_me_for 1.week
    after = 1.week.from_now.utc
    assert_not_nil players(:quentin).remember_token
    assert_not_nil players(:quentin).remember_token_expires_at
    assert players(:quentin).remember_token_expires_at.between?(before, after)
  end

  def test_should_remember_me_until_one_week
    time = 1.week.from_now.utc
    players(:quentin).remember_me_until time
    assert_not_nil players(:quentin).remember_token
    assert_not_nil players(:quentin).remember_token_expires_at
    assert_equal players(:quentin).remember_token_expires_at, time
  end

  def test_should_remember_me_default_two_weeks
    before = 2.weeks.from_now.utc
    players(:quentin).remember_me
    after = 2.weeks.from_now.utc
    assert_not_nil players(:quentin).remember_token
    assert_not_nil players(:quentin).remember_token_expires_at
    assert players(:quentin).remember_token_expires_at.between?(before, after)
  end

  protected
    def create_player(options = {})
      Player.create({ :login => 'quire', :email => 'quire@example.com', :password => 'quire', :password_confirmation => 'quire', :name => 'quire' }.merge(options))
    end
end


authenticated_test_helper.rb:

module AuthenticatedTestHelper
  # Sets the current player in the session from the player fixtures.
  def login_as(player)
    @request.session[:player] = player ? players(player).id : nil
  end

  def authorize_as(user)
    @request.env["HTTP_AUTHORIZATION"] = user ? "Basic #{Base64.encode64("#{users(user).login}:test")}" : nil
  end

  # taken from edge rails / rails 2.0.  Only needed on Rails 1.2.3
  def assert_difference(expressions, difference = 1, message = nil, &block)
    expression_evaluations = [expressions].flatten.collect{|expression| lambda { eval(expression, block.binding) } }
   
    original_values = expression_evaluations.inject([]) { |memo, expression| memo << expression.call }
    yield
    expression_evaluations.each_with_index do |expression, i|
      assert_equal original_values[i] + difference, expression.call, message
    end
  end

  # taken from edge rails / rails 2.0.  Only needed on Rails 1.2.3
  def assert_no_difference(expressions, message = nil, &block)
    assert_difference expressions, 0, message, &block
  end
end


... and here the controller:

class PlayersController < ApplicationController
  def index
    @players = Player.find(:all)

    respond_to do |format|
      format.html # index.rhtml
      format.xml  { render :xml => @players.to_xml }
    end
  end
 
  # render new.rhtml
  def new
    @player = Player.new
  end

  def create
    @player = Player.new(params[:player])
    @player.save!
    self.current_player = @player
    redirect_back_or_default('/')
    flash[:notice] = "New Player Created"
  rescue ActiveRecord::RecordInvalid
    render :action => 'new'
  end
 
  def show
    @player = Player.find(params[:id])

    respond_to do |format|
      format.html # show.rhtml
      format.xml  { render :xml => @player.to_xml }
    end
  end
 
  # GET /players/1;edit
  def edit
    @player = Player.find(params[:id])
  end
 
  # PUT /players/1
  # PUT /players/1.xml
  def update
    #todo: updated player does not get saved.
    @player = Player.find(params[:id])

    respond_to do |format|
      if @player.update_attributes(params[:player])
        flash[:notice] = 'Player was successfully updated.'
        format.html { redirect_to player_url(@player) }
        format.xml  { head :ok }
      else
        format.html { render :action => "edit" }
        format.xml  { render :xml => @player.errors.to_xml }
      end
    end
  end
 
  # DELETE /players/1
  # DELETE /players/1.xml
  def destroy
    #todo: to be tested
    @player = Player.find(params[:id])
    @player.destroy

    respond_to do |format|
      format.html { redirect_to players_url }
      format.xml  { head :ok }
    end
  end

end


Thank you very much for your help