I'm trying to do integration test for our login process we have overwrite session create process like this:
module UsersDashboard
class SessionsController < Devise::SessionsController
protect_from_forgery :except => [:create, :mfa_callback, :authenticate]
def test
session[:test] = 'doodoo'
self.resource = warden.authenticate!(auth_options)
render text: 'hi'
end
# Used for User Dashboard login
def create
current_user = warden.authenticate!(auth_options)
resource = current_user
current_user.update_attribute(:mfa_authenticated, false)
# some code here
session[:channel] = 'some value'
p "session channel in create: #{session[:channel]}, #{session.class}"
redirect_url = 'some value here'
redirect_to redirect_url
end
end
and here is my test code:
require 'test_helper'
include Warden::Test::Helpers
class DashboardOkTest < ActionDispatch::IntegrationTest
test "Dashboard sign in and visiting various pages inside dashboard " do
post_via_redirect '/users_dashboard/sessions/mani'
p session[:test]
mani = users(:mani)
post_via_redirect '/users/sign_in', 'user[email]' => mani.email, 'user[password]' => '123456'
assert_response :success
assert_equal '/mfa/index', path
assert_select '#choose-mfa-method', 'Choose Your Acceptto Multi-Factor Auth Method:'
login_as mani, scope: :user
p "session: #{session[:channel]}, #{session.inspect}"
assert request.query_parameters.has_key?(:channel)
tfa = Tfa.find_by_channel(request.query_parameters[:channel])
assert tfa
tfa.update_attribute(:status, 'approved')
get "/users_dashboard/sessions/authenticate?channel=#{tfa.channel}"
end
end
as you can see in the output:
"doodoo"
"session channel in create: 123, ActionDispatch::Request::Session"
"session: , #<ActionDispatch::Request::Session:0x7fabecaa9458 not yet loaded>"
"session channel is nil"
session[:channel] doesn't work and session is not loaded yet! interesting point is I have created a test method in Session Controller as you can see and if we set session[:test] before calling self.resource = warden.authenticate!(auth_options) session works and it prints 'doodoo' but it's not the case for create method if we set session[:channel] before self.resource = warden.authenticate!(auth_options) it still doesn't work.
It looks like devise has some kind of wrapper that nullifies session after create call. and all this just happens in test while session and everything else works in development server in web.
Any idea how to set a session for test inside create method?
Related
I have a request test spec which tests a POST request.
In my PaymentController (which handles the POST request), i have
before_action :require_user, :require_order
which basically gets the user and order based on the session.
Any idea how i can set session variable(user_id and order_id) in my request test spec?
Since Rails 5.0 the best way is to use the keyword arguments in the controller/request tests:
get :index, params: { ... }, session: { user_id: 1, ... }
If you are using a authentication library like Devise, Clearance and such, there are various helpers to stub a logged in user, see here the Documentation for Devise:
before(:each) do
# or def setup if using minitest
#request.env["devise.mapping"] = Devise.mappings[:user]
sign_in User.create(...)
end
For Rails < 5, this solution works
def _set_session(key, value)
Warden.on_next_request do |proxy|
proxy.raw_session[key] = value
end
end
_set_session(:access_acount_id, user.id)
Example
def _set_session(key, value)
Warden.on_next_request do |proxy|
proxy.raw_session[key] = value
end
end
context 'When there is something' do
let(:yesterday) { 1.day.ago }
let(:updates_enabled) { true }
before do
_set_session(:access_acount_id, user.id)
login_as(user, scope: :user)
end
it 'does not update records' do
visit dashboard_path
expect(page).to have_css('.popup#my-popup')
end
end
I want to set Rails.cache in rspec controller test, however the cache is always blank inside the controller method. What is approach to do this?
User Model
def user_token
Rails.cache.fetch(id)
end
User Controller
if current_user.user_token
#user = #account.users.find(params[:id])
#user.revoke_seat(:admin, current_user)
render :template => "/admin/users/revoke_seat"
else
render :js => "window.location.href='#{server_url}/oauth/authorize?response_type=code&client_id=#{client_id}&state=#{request.referrer}?auto_revoke_seat=true&redirect_uri=#{auth_service_callback_url}";
end
Rspec
before do
users(:admin).stub(:internal_admin?).and_return(true)
login_as :admin
Rails.cache.write(user.id, "testToken", expires_in: 2.minutes)
end
it "should redirect to authentication service to generate access token" do
expect(user).to receive(:user_token).and_return(true)
xhr :put, :revoke_seat, account_id: account.id, id: user.id
expect(response).to render_template('admin/users/revoke_seat')
expect(assigns(:account)).to eq(account)
expect(assigns(:user)).to eq(user)
end
I would change the before method as:
before do
users(:admin).stub(:internal_admin?).and_return(true)
login_as :admin
allow(Rails.cache).to receive(:fetch).with(user.id).and_return("testToken")
end
How do I stub current_user in Rails 5?
I expected this:
#controller.stub :current_user, mock_user do
end
to work, but there is no #controller in ActionDispatch::IntegrationTest? I use sorcery gem for authentication.
You could try the wiki example:
class TeamsControllerTest < ActionController::TestCase
include Sorcery::TestHelpers::Rails::Integration
include Sorcery::TestHelpers::Rails::Controller
setup do
#team = teams(:one)
#user = users(:one)
login_user(user = #user, route = login_url) # replace with your login url path
end
test "should get index" do
get :index
assert_response :success
assert_not_nil assigns(:teams)
end
That will set your test user as current_user.
I am trying to spec the following.
I need to return all entities that are linked to the logged in user. Subsequently I need to create the user before the fact and then ensure that the specific user is logged in. I am struggling to achieve this with controller macros. My specs are failing as follows
1) Yougov::Surveys::ProfilesController GET :index returns all profiles linked to the loged in user with the same country and client as the linked survey
Failure/Error: sign_in user
RuntimeError:
Could not find a valid mapping for nil
# /Users/donovan.thomson/.rvm/gems/ruby-2.2.2#insight-app/gems/devise-2.2.8/lib/devise/mapping.rb:42:in `find_scope!'
# /Users/donovan.thomson/.rvm/gems/ruby-2.2.2#insight-app/gems/devise-2.2.8/lib/devise/test_helpers.rb:46:in `sign_in'
# ./spec/support/controller_macros.rb:17:in `block in login_specific_user'
So a basic scaffolding of my controller looks as follows :
class ProfilesController < ApplicationController
def index
render json: Profile.where(user_id: current_user.id)
end
end
I assume this means the user is not being logged in as I would expect
My spec is as follows
require 'spec_helper'
describe ProfilesController, type: :controller do
before do
#user = FactoryGirl.create :user
#profile = FactoryGirl.create :profile, user: #user
FactoryGirl.create :profile
end
describe "GET :index" do
login_specific_user(#user)
it "returns all profiles linked to the loged in user with the same country and client as the linked survey" do
get :index
expect(response.body).to eq(#profile.to_json)
end
end
end
My controller macro's are as follows:
module ControllerMacros
def login_admin
before :each do
sign_in ControllerMacros.get_user(#request, :admin, :admin_user)
end
end
def login_user
before :each do
sign_in ControllerMacros.get_user(#request, :user)
end
end
def login_specific_user(user)
before :each do
sign_in user
end
end
class << self
def get_user(req, mapping, type=mapping)
req.env["devise.mapping"] = Devise.mappings[mapping]
user = FactoryGirl.create(type)
user.confirm!
user
end
end
end
I solved this by not using controller macros and just adding the following to my before block
before do
#user = FactoryGirl.create :user
#user.confirm!
sign_in #user
end
I'm implementing a lazy login feature. My cucumber feature should describe it:
Feature: User log in
Scenario: Lazy login
Given I didn't log out the last time I was on the site
When I go to the homepage
Then I should automatically be logged in
And these are my step definitions:
Given(/^I didn't log out the last time I was on the site$/) do
user = FactoryGirl.create(:user)
visit new_user_session_path
fill_in('user[email]', with: user.email)
fill_in('user[password]', with: 'test123')
click_button('Sign in')
Capybara.reset_sessions!
end
When(/^I go to the homepage$/) do
visit root_path
end
Then(/^I should automatically be logged in$/) do #<-- Fails here
page.should have_content("Logout")
end
This is what happens when a user logs in: the cookies.signed[:auth_token] gets set. This will be used by a before filter in my ApplicationController so that users who open a fresh browser will be logged in automatically:
class SessionsController < Devise::SessionsController
def create
super
if user_signed_in?
puts 'yesssssss'
session[:user_id] = current_user.id
current_user.remember_me! if current_user.remember_token.blank?
cookies.signed[:auth_token] = {
:value => current_user.remember_token,
:domain => "mysite.com",
:secure => !(Rails.env.test? || Rails.env.development?)
}
puts "current_user.remember_token = #{current_user.remember_token}"
puts 'cookies:'
puts cookies.signed[:auth_token]
end
end
end
This is the before filter in my ApplicationController:
def sign_in_through_cookie
logger.info "logging in by cookie"
puts "logging in by cookie"
puts cookies.signed[:auth_token] #<-- PROBLEM: this returns nil.
return true if !current_user.nil?
if !cookies[:auth_token].nil? && cookies[:auth_token] != ''
user = User.find_by_remember_token(cookies.signed[:auth_token])
return false if user.blank?
sign_in(user)
puts 'success'
return true
else
return false
end
end
So the issue is that in the last step of my cucumber feature, cookies.signed[:auth_token] returns nil. I'm guessing this is just a capybara thing. So do I actually have to set a cookie in the test as opposed to using the one in my controller?
So eventually I figured it out after trying a lot of different things.
Given(/^I didn't log out the last time I was on the site$/) do
user = FactoryGirl.create(:user)
visit new_user_session_path
fill_in('user[email]', with: user.email)
fill_in('user[password]', with: 'test123')
click_button('Sign in')
Capybara.current_session.driver.request.cookies.[]('auth_token').should_not be_nil
auth_token_value = Capybara.current_session.driver.request.cookies.[]('auth_token')
Capybara.reset_sessions!
page.driver.browser.set_cookie("auth_token=#{auth_token_value}")
end
When(/^I go to the homepage$/) do
visit root_path
end
Then(/^I should automatically be logged in$/) do
page.should have_content("Logout")
end
UPDATE:
Here's what I use in case I'm using Selenium for some of the tests:
if Capybara.current_session.driver.class == Capybara::Selenium::Driver
auth_token = page.driver.browser.manage.cookie_named('auth_token')[:value]
page.driver.browser.manage.delete_all_cookies
page.driver.browser.manage.add_cookie(:name => "auth_token", :value => auth_token)
else
puts "cookies = #{Capybara.current_session.driver.request.cookies}"
Capybara.current_session.driver.request.cookies.[]('auth_token').should_not be_nil
auth_token_value = Capybara.current_session.driver.request.cookies.[]('auth_token')
Capybara.reset_sessions!
page.driver.browser.set_cookie("auth_token=#{auth_token_value}")
end
Use https://github.com/nruth/show_me_the_cookies which wraps the driver methods. It has methods for getting cookies, deleting cookies, and a method for creating cookies called create_cookie.
I needed just to test the cookie values
Inspiration taken from https://collectiveidea.com/blog/archives/2012/01/05/capybara-cucumber-and-how-the-cookie-crumbles
and ported to Rails 5.x
Create features/support/cookies.rb
With content
module Capybara
class Session
def cookies
#cookies ||= ActionDispatch::Request.new(Rails.application.env_config.deep_dup).cookie_jar
end
end
end
Before do
allow_any_instance_of(ActionDispatch::Request).to receive(:cookie_jar).and_return(page.cookies)
allow_any_instance_of(ActionDispatch::Request).to receive(:cookies).and_return(page.cookies)
end
Then the step for testing
Then('is set cookie {string} with value {string}') do |cookie, value|
expect(page.cookies.signed[cookie]).to eq value
end