How do I check to see if a user will be redirected to his/her profile after login (Rails Tutorial Ch. 9)?

1.8k views Asked by At

I'm working on chapter 9, exercise 1 of the Rails Tutorial. I'm unsure of what to do in order to test that, after a normal login, the user will be redirected to his/her profile. The exercise's hint implies that I should modify the "successful edit with friendly forwarding" test in users_edit_test.rb in order to compare the value of session[:forwarding_url] with that of the user profile path. To do that, I added

assert_equal session[:forwarding_url], user_path

to the end of the function. However, when I do rake test, I get this error:

Expected: nil
Actual: "/users/762146111"

Can anyone assist me with this problem?

Update: Here are the controller and helper that are being tested:

class SessionsController < ApplicationController
  def new
  end

  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      log_in user
      params[:session][:remember_me] == '1' ? remember(user) : forget(user)
      redirect_back_or user
    else
      flash.now[:danger] = 'Invalid email/password combination'
      render 'new'
    end
  end

  def destroy
    log_out if logged_in?
    redirect_to root_url
  end
end

module SessionsHelper
  # Logs in the given user.
  def log_in(user)
    session[:user_id] = user.id
  end

  # Remembers a user in a persistent session.
  def remember(user)
    user.remember
    cookies.permanent.signed[:user_id] = user.id
    cookies.permanent[:remember_token] = user.remember_token
  end

  # Returns true if the given user is the current user.
  def current_user?(user)
    user == current_user
  end

  # Returns the current logged-in user (if any).
  def current_user
    if (user_id = session[:user_id])
      @current_user ||= User.find_by(id: user_id)
    elsif (user_id = cookies.signed[:user_id])
      user = User.find_by(id: user_id)
      if user && user.authenticated?(cookies[:remember_token])
        log_in user
        @current_user = user
      end
    end
  end

  # Returns true if the user is logged in, false otherwise.
  def logged_in?
    !current_user.nil?
  end

  # Forgets a persistent session.
  def forget(user)
    user.forget
    cookies.delete(:user_id)
    cookies.delete(:remember_token)
  end

  # Logs out the current user.
  def log_out
    forget(current_user)
    session.delete(:user_id)
    @current_user = nil
  end

  # Redirects to stored located (or to the default).
  def redirect_back_or(default)
    redirect_to(session[:forwarding_url] || default)
    session.delete(:forwarding_url)
  end

  # Stores the URL trying to be accessed.
  def store_location
    session[:forwarding_url] = request.url if request.get?
  end
end
3

There are 3 answers

2
M. Layton On BEST ANSWER

I'm way late in answering this question, so it might be a moot point now, but I'm working on this exercise in the tutorial right now myself and had to struggle to come up with the correct answer, but I finally did. So I thought I'd share what I did to get the tests to pass in case you're still seeking an answer. First I'll point out where the flaw in your current logic is (because I made the same mistake initially and had to process the logic flow of the code to figure out what went wrong).

In your test, you're testing for equality between with assert_equal session[:forwarding_url], user_path(@user). The problem is, the user's user_path is never set as the forwarding url. In the sessions_helper.rb file, the redirect_back_or() method has this line: redirect_to(session[:forwarding_url] || default) (the default argument passed to this method is user when it's called in the create action of the sessions controller). So this line in the redirect_back_or() method can be read in plain English as

Redirect to the forwarding url if one has been set by the store_session method. If the forwarding url is nil, redirect to the default page, which in this case is the user page.

In order to test for equality as you're trying to do (and as I initially tried), it requires that at some point user_path(@user) or user would have to be set as the session's forwarding url. store_session takes no parameters to do this, so the only way user_path(@user) could be set as the forwarding url is if an unauthorized user were attempting to access that page and were first forced to log in then be redirected there (as best I can tell, this text does not require a person to be logged in to view a single user's page). So, user_path(@user) would never conceivably be set as the forwarding url in the store_session method, and thusly, assert_equal session[:forwarding_url], user_path(@user) would never evaluate to true.

At this point, the error message you cited in your original question is extremely helpful in solving your problem.

Expected: nil
Actual: "/users/762146111"

If you recall from the early chapters of Hartl's text (or from the Rails API) assert_equal tests take two required parameters: the expected value and the actual value and then compare the two. This is reflected in your error message: session[:forwarding_url] evaluates to nil while user_path(@user) evaluates to a specific user's url. Since, like I stated earlier, the user's profile page should never conceivably be set as the forwarding url, we can eliminate user_path(@user) entirely from our test.

Fortunately for us, the exercise in the textbook doesn't require that we user assert_equal to get the correct result. We just need to test that session[:forwarding_url] has the right value. That should be pretty easy. Let me walk through it step by step:

  1. User is not logged in and trying to access a page served via the index, edit, update, or destroy controller actions.
  2. Before filter catches this and runs the logged_in_user method. unless logged_in? evaluates to false, so the code in this method is run.
  3. store_location is called inside logged_in_user and the unauthorized user's get request is stored as the forwarding url. Simultaneously, the user is redirected to the log in page.
  4. Assuming the user enters valid credentials to successfully log in, this calls the session controller's create method, to create a new session. This action logs the user in and calls redirect_back_or().
  5. redirect_back_or() checks to see if session[:forwarding_url] has a value associated with it -- we know it was because store_location assigned a value to this key in step #3. So the first half of redirect_back_or()'s || statement is executed and the now-authenticated user gets redirected to the page they were initially trying to access before they logged in.
  6. redirect_back_or() then deletes the value associated with the forwarding url, since the user has been redirected there already.
  7. Now we try to log in again, this time by directly accessing the log-in page. This happens successfully, and the session controller's create action is called again.
  8. This time, we weren't trying to access a page we didn't have authorization to access, so the store_session method never gets called. Therefore, the forwarding_url is currently nil.
  9. When the session is created and redirect_back_or() gets called, session[:forwarding_url] evaluates to nil, so the second half of the || statement gets executed, namely redirect_to user (since user is the parameter passed to the method as its default).

To summarize, the correct value of session[:forwarding_url] for subsequent login attempts should be nil. You can write this test one of two ways:

  1. You can still use assert_equal to test for equality:

    assert_equal session[:forwarding_url], nil

  2. You can refactor your test to be more succinct and direct:

    assert_nil session[:forwarding_url]

I prefer test #2, but I tested them both and it worked just fine both ways.

Though not explicitly required in the original exercise, I also added a test after this one to ensure all subsequent redirects are sent to the user_path(@user):

assert_nil session[:forwarding_url]
assert_redirected_to @user



TL;DR: session[:forwarding_url] will never equal user_path(@user). If it hasn't been stored with store_location (which is the case with all subsequent login attempts), it will evaluate to nil, so a passing test for this exercise must be written one of two ways:

assert_nil session[:forwarding_url]
OR
assert_equal session[:forwarding_url], nil

6
ChiefRockaChris On

You need to pass the param you want to the path call such as:

assert_equal session[:forwarding_url], user_path(@user)

or whatever your user param is (current_user, etc.)

0
Kidquick On

M. Layton's answer is correct. Just for completeness, this is the resulting green test, along with a few comments to explain the logic.

test "successful edit with friendly forwarding" do
    # Issue first page request.
    get edit_user_path(@user)
    # User is not logged in, so requested URL is stored in session[:forwarding_url]

    log_in_as(@user)

    # User is now logged in and should be redirected to the
    # URL stored in session[:forwarding_url].
    assert_redirected_to edit_user_path(@user)
    # sessions[:forwarding_url] should now be nil.

    name = "My Name"
    email = "[email protected]"
    patch user_path(@user), user: { name: name,
                                    email: email,
                                    password: "",
                                    password_confirmation: "" }
    assert_not flash.empty?
    assert_redirected_to @user
    @user.reload
    assert_equal name, @user.name
    assert_equal email, @user.email

    # On subsequent login attempts, user should be redirected to their profile page
    # This block is the answer to the exercise.
    assert_nil session[:forwarding_url]
    log_in_as(@user)
    assert_redirected_to user_url(@user)
end