In my people_controller_spec.rb I have
before(:each) do
#office = FactoryGirl.create(:office)
#organization = FactoryGirl.create(:organization)
#user = FactoryGirl.create(:user, organization: #organization)
#request.session['user_id'] = #user.id
#request.session['current_organization_id'] = #user.organization.id
end
and I have this application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery
private
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
def current_organization
if session[:current_organization_id]
Organization.find(session[:current_organization_id])
else
#current_organization ||= Organization.find(current_user.organization_id)
end
end
helper_method :current_user
helper_method :current_organization
end
The session hash does not seem to persist inside the application_controller.rb that is why I get these kinds of test errors where #current_user in the application_controller.rb is nil
6) PeopleController index sorts all people alphabetically by first_name
Failure/Error: get :index, {search: {meta_sort: "first_name.asc"}}, valid_session
NoMethodError:
undefined method `organization_id' for nil:NilClass
# ./app/controllers/application_controller.rb:15:in `current_organization'
# ./app/controllers/people_controller.rb:107:in `get_orgs'
# ./spec/controllers/people_controller_spec.rb:71:in `block (3 levels) in <top (required)>'
I have already done everything but failed.
I use rails (3.2.9) and rspec-rails 2.12.2
I SOLVED THE PROBLEM AFTER SEEING THIS Devise Test Helper - sign_in does not work
I just removed all the "valid_session" method call.
In your before :each block set the session with:
session[:user_id] = #user.id
session[:current_organization_id] = #user.organization.id
This uses the session helper provided by the rspec controller macros. Also I'm not sure if the session is a HashWithIndifferentAccess like params but either way its good to keep to using the same key type.
Related
Using Rails 6, I'm redirecting from native ids (/users/id) to friendly_ids (/users/username) (following the answer brought here) which handles the redirect as such:
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
def redirect_resource_if_not_latest_friendly_id(resource)
if resource.friendly_id != params[:id]
redirect_to resource, status: 301
end
end
end
In my controller, I call the method as such:
# app/controllers/users_controller.rb
class UsersController < ApplicationController
before_action :set_user, only: [:show]
def show
redirect_resource_if_not_latest_friendly_id(set_user)
end
protected
def set_user
#user = User.friendly.find(params[:id])
end
end
It's working fine and I want to include the redirection in my test suite. I found answer and topics about how to do that with Rspec but I'm using Minitest and couldn't figure it out.
I tried a bunch of different ways (with params[:id], etc.) but to keep it simple let's say it's the following fixture, test and result.
Here is the fixture:
# test/fixtures/users.yml
one:
username: username
email: email#example.com
slug: username
Here is the test:
# test/controllers/users_controller_test.rb
class UsersControllerTest < ActionDispatch::IntegrationTest
test "should 301 redirect to the friendly_id" do
#user = users(:one)
get user_path(#user)
assert_redirected_to "/users/#{#user.slug}"
end
end
Here is the result of the test:
FAIL["test_should_301_redirect_to_the_friendly_id", #<Minitest::Reporters::Suite:0x00007fe716c66600 #name="UsersControllerTest">, 0.7785789999979897]
test_should_301_redirect_to_the_friendly_id#UsersControllerTest (0.78s)
Expected response to be a <3XX: redirect>, but was a <200: OK>
test/controllers/users_controller_test.rb:8:in `block in <class:UsersControllerTest>'
What am I doing wrong?
The problem is you're using the "whole" user record to make the request, so when you do
user_path(#user)
The route extracts the friendly_id from the resource and then your condition is evaluated to false, because the resource.friendly_id is always going to be the same as the id coming from the params.
Try instead:
get user_path(id: #user.id)
That way you can explicitly pass the #user.id through the params.
possible the problem resource.friendly_id != params[:id] as I understood they are the same
I have defined a helper method as such (for my rails engine):
module Xaaron
class ApplicationController < ActionController::Base
protect_from_forgery with: :null_session
rescue_from ActiveRecord::RecordNotFound, :with => :record_not_found
helper_method :current_user
helper_method :authenticate_user!
def current_user
#current_user ||= Xaaron::User.find_by_auth_token(cookies[:auth_token]) if cookies[:auth_token]
end
def authenticate_user!
if current_user
true
else
redirect_to xaaron.login_path
false
end
end
protected
def record_not_found
flash[:error] = 'Could not find specified role'
redirect_to xaaron.record_not_found_path
true
end
end
end
As far as I know everything above is correct in terms of creating helper methods. So now I need to use this helper method:
module Xaaron
class ApiKeysController < ActionController::Base
before_action :authenticate_user!
def index
#api_key = Xaaron::ApiKey.where(:user_id => current_user.id)
end
def create
#api_key = Xaaron::ApiKey.new(:user_id => current_user.id, :api_key => SecureRandom.hex(16))
create_api_key(#api_key)
end
def destroy
Xaaron::ApiKey.find(params[:id]).destroy
flash[:notice] = 'Api Key has been deleted.'
redirect_to xarron.api_keys_path
end
end
end
As you can see, before every action the user must be authenticated. So the authenticat_user!
method is then called.
Lets write a test for this
it "should not create an api key for those not logged in" do
post :create
expect(response).to redirect_to xaaron.login_path
end
This, we expect, to send us back to the login path because we are not signed in, and as you will recall we are using authenticate before every action in the API Controller. What do we get instead:
1) Xaaron::ApiKeysController#create should not create an api key for those not logged in
Failure/Error: post :create
NoMethodError:
undefined method `authenticate_user!' for #<Xaaron::ApiKeysController:0x007f898e908a98>
# ./spec/controllers/api_keys_controller_spec.rb:9:in `block (3 levels) in <top (required)>'
Last I checked the way I defined a helper method is how rails casts has done it, how other stack questions have done it and how rails docs states to do it - unless I missed some majour step - why isn't this working?
Maybe I haven't seen a helper method set up like this before (I'm new to rails) but the helper methods I've seen are defined without controllers.
Usually I see a file like this in the helpers folder
module SessionsHelper
def sign_in(user)
remember_token = User.new_remember_token
cookies.permanent[:remember_token] = remember_token
user.update_attribute(:remember_token, User.encrypt(remember_token))
self.current_user = user
end
def current_user=(user)
#current_user = user
end
...
and then
include SessionsHelper
In the application controller.
To me it looks like you're calling the controller a helper method, I'm not sure what the benefits of that would be - but I suppose I wouldn't.
Sorry if this wasn't helpful
I don't know what I'm doing wrong so please help me out.
So as the title says all works great in the browser. So I guess it's the spec I'm not able to get right.
I've rewrote code in my helpers, controllers and spec several times to.
Here's my spec for now:
describe "visit as admin" do
before do
sign_in admin
visit organization_users_path # row 20 of users_controller_spec
end
it "should display right page" do
page.should have_selector('title', text: "Users")
end
end
When i run it i get:
1) Organization::UsersController visit as admin should display right page
Failure/Error: visit organization_users_path
NoMethodError:
undefined method `users' for nil:NilClass
# ./app/controllers/organization/users_controller.rb:5:in `index'
# ./spec/controllers/organization/users_controller_spec.rb:20:in `block (3 levels) in <top (required)>'
And heres my controller and helper:
# helper
def current_organization
#current_organization ||= current_user.organization
end
def admin_user
unless current_user.admin?
redirect_to root_path
end
end
#controller
class Organization::UsersController < ApplicationController
before_filter :admin_user
def index
#users = current_organization.users
end
end
Edit 1: From rake routes:
organization_users GET /organization/users(.:format) organization/users#index
Seems like in the index function for the controller: current_organization is nil.
Please make sure this helper is returning a valid object:
def current_organization
#current_organization ||= current_user.organization
raise #current_organization.inspect # <--- use this to inspect this value and delete afterwards
end
The error tells you that the current_organisation is nil, and that seems logical as it's loaded through the current_user which is nil in your context.
You can take a look at https://github.com/plataformatec/devise/wiki/How-To:-Test-with-Capybara to log a user in the tests.
But don't forget to log a user which as an organization in your setup so the current_organization helper doesn't return nil
EDIT : I'm guessing you're using devise for login, if this is not the case, please let us know, what you're using for authentication
View spec failing because the ApplicationController method, logged_in?, wants a user to be returned.
Not sure how to spec this. Currently in before(:each) I have:
controller.stub!(:logged_in?).and_return(FactoryGirl.create(:active_user))
#ballots = FactoryGirl.create_list(:full_ballot, 2)
which isn't working:
Failure/Error: render
ActionView::Template::Error:
undefined method 'logged_in?' for #<#<Class:0x007f9c908e4b10>:0x007f9c90873b68>
FWIW: :active_user is the user factory for the user that is attached to the :full_ballot
Update: As requested:
class ApplicationController < ActionController::Base
...
def current_user
#current_user ||= User.find(cookies[:id_token]) if cookies[:id_token]
end
def logged_in?
current_user
end
...
end
See how devise test helpers do it. It looks like you can define this method before tests and it should work:
def logged_in?
FactoryGirl.create :active_user
end
I've been working through Michael Hartl's Rails tutorial (which is unbelievably awesome by the way).
Anyway, everything has been going pretty well and I've nearly reached the end of chapter 10. The problem is that my rspec tests have started to generate some failures and I can't figure out what's wrong.
The first failure occurred when I was working through the section on destroying users. The test
before :each do
#user = Factory :user
end
describe "as a non-signed-in user" do
it "should deny access" do
delete :destroy, :id => #user
response.should redirect_to(signin_path)
end
end
gives the error:
UsersController DELETE 'destroy' as a non-signed-in user should deny access
Failure/Error: delete :destroy, :id => #user
NoMethodError:
undefined method `admin?' for nil:NilClass
# ./app/controllers/users_controller.rb:76:in `admin_user'
# ./spec/controllers/users_controller_spec.rb:308:in `block (4 levels) in <top (required)>'
Here's the code the message references in users_controller:
def admin_user
# the error tels me that current_user = NilClass
redirect_to(root_path) unless current_user.admin?
end
So I guess this would suggest that current_user isn't working correctly and is being set to nil. Now current_user involves a lot of methods of the SessionsHelper which (afaik) deal with setting the users ID in secure cookies and referencing the cookie as they move around the site. So this suggests that there is something wrong with the cookies.
I've checked the browser and the cookie is being set, I've also gone over every part of the code and it all replicates the tutorial exactly as far as I can tell.
Is there something else I should be looking at?
Appendix
Here is the contents of the SessionsHelper module:
module SessionsHelper
def sign_in user
# rails represents cookies as a hash and deals with the conversion for us
# making the cookie "signed" makes it impervious to attack
cookies.permanent.signed[:remember_token] = [user.id, user.salt]
# this line calls the assignment operator below
self.current_user = user
end
def current_user=(user)
#current_user = user
end
# this is a getter method
def current_user
# this sets #current_user = to the user corresponding to the remember token
# but only if #current user is undefined. ie it only works once
#current_user ||= user_from_remember_token
end
def signed_in?
# return true if current_user is not nil
!current_user.nil?
end
def sign_out
cookies.delete(:remember_token)
self.current_user = nil
end
def current_user? user
# returns true if the user object == the current_user object
user == current_user
end
def authenticate
deny_access unless signed_in?
end
def deny_access
store_location
# this is a shortcut for flash notices: flash[:notice] = "Please sign in to access this page."
redirect_to signin_path, :notice => "Please sign in to access this page."
end
def redirect_back_or(default)
redirect_to(session[:return_to] || default)
clear_return_to
end
private
def user_from_remember_token
# the * allows us to give a 2 element array to a method expecting 2 seperate arguments
User.authenticate_with_salt(*remember_token)
end
def remember_token
# return [nil, nil] if the :remember_token cookie is nil
cookies.signed[:remember_token] || [nil, nil]
end
def store_location
# stores the url the browser was requesting
session[:return_to] = request.fullpath
end
def clear_return_to
session[:return_to] = nil
end
end
In your spec ypu are trying to delete user, while you are not logged in, so current_user is nil. You should prevent access to this action non singed in user.
class UsersController < ApplicationController
before_filter :authenticate, :only => [:index, :edit, :update, :destroy]
....
Just as it is described in example.
I made the same omission and this fixed worked. Thanks. I agree with ducky, Michael Hartl's Rails tutorial is tremendously awesome. It's almost a shame to +just+ call it a "Rails Tutorial." While the tutorial does indeed seem to be pointing me towards good Rails development habits, the text concisely contains much more. Very well done.