Authenticated Request specs - ruby-on-rails

I understand this question has been asked before in various forms.
However I'm struggling with something that doesn't seem to be being solved in these answers. My logged in user is not persisting within the spec.
How are you supposed to replicate authentication/logged in users in a request spec?
Here's what I've tried, and what I'm doing.
I'm using Auth0 as my authentication handler. I have a signin method that's called in the Auth0 Callback, so I've jerry-rigged a mock_auth endpoint for my tests to utilize a resource object.
This is my current set up and what I've done to try and replicate the login flow.
#/spec/requests/api/v1/account_spec.rb
RSpec.describe "API V1 Accounts", type: :request do
# Factories.
...
describe "PATCH update" do
subject(:http_request) { patch endpoint, params: { account: account_params, format: :json } }
# set some defaults
let(:id) { account.id }
let(:endpoint) { "/api/v1/accounts/#{id}" }
let(:account_params) { {} }
# Configure subdomain contstraint
within_subdomain :api do
before do |example|
mock_login(resource) unless example.metadata[:skip_signin]
http_request
end
context "when no resource is logged in", :skip_signin do
# This spec passes fine, as it's skipping login.
it_behaves_like "an unauthenticated private api request"
end
context "when there is no record to be found" do
let(:id) { SecureRandom.uuid }
let(:resource) { create(:user) }
it "fails to access a record" do
expect(response).to have_http_status(:not_found)
end
end
xcontext "when the user has access permission" do
end
end
end
end
-
# config/routes.rb
post "/auth/mock/:id", to: "auth#mock", as: :mock_login if Rails.env.test?
-
# auth_controller.rb
def mock
return unless Rails.env.test?
#resource = User.find_by(params[:id]
signin(#resource)
end
def signin(resource)
reset_session
create_session(resource)
after_signin_redirect_for(resource)
end
and I'm using this helper to call it from my request spec
module Helpers
module Auth
def mock_login(resource)
post mock_login_path(resource.id)
end
end
end
RSpec.configure do |config|
config.include Helpers::Auth, type: :request
end
So. By throwing around a bunch of debuggers and binding.pry I can see that my mock_login(resource) is being called successfully and at the end of the signin method, my helper signed_in? is true. Having successfully set a session.
The issue that I'm having now, is that this is not persisting in the feature spec when it's run in the before block, or in the it block.
before do |example|
mock_login(resource) unless example.metadata[:skip_signin] # signed_in? == true!
http_request # signed_in? == nil
end
module API
module V1
class AccountsController < APIController
before_action :authenticate_resource!
# ^ This is where the spec is failing to recognise the signed in resource from the mock_login method.
before_action :set_account
# PATCH /api/v1/accounts/:id
def patch_update
# Cancancan Authorization
authorize! :update, #account
# handle patch
...
end
private
def set_account
binding.pry # We're never making it here.
#account = Account.find_by(id: params[:id])
end
...
end
end
end
def authenticate_resource!
return true if signed_in?
respond_to do |format|
format.json { head(:unauthorized) }
end
end
EDIT: A couple of changes to make it clearer what I'm asking.

Related

Rspec - Stub/allow_any_instance_of included module methods is not working

I've been trying to stub a private module method for the whole day now but with not progress.
Here is a snippet of my application controller class
class ApplicationController < ActionController::Base
include Cesid::Application
end
Cesid > Application.rb
module Cesid
module Application
extend ActiveSupport::Concern
included do
before_action :track_marketing_suite_cesid, only: [:new]
end
private
def track_marketing_suite_cesid
return unless id_token_available?
## #cesid_auth = Auth.new(#id_token)
#cesid_auth = Auth.new(id_token)
return unless #cesid_auth.present? && #cesid_auth.valid?
#cesid_admin = Admin.where(email: #cesid_auth.email).first_or_initialize
end
def id_token_available?
## #id_token.present?
id_token.present?
end
def id_token
#id_token ||= id_token_param
end
def id_token_param
cookies[:id_token]
end
end
end
Now, I'm trying to create a simple unit test for the method
id_token_available?
And I am just trying to set the id_token_param to a random value.
I've tried using this code as stated Is there a way to stub a method of an included module with Rspec?
allow_any_instance_of(Cesid).to receive(:id_token_param).and_return('hello')
but I just get this error
NoMethodError:
undefined method `allow_any_instance_of' for #<RSpec::ExampleGroups::CesidApplication::CesidAuthorizations::GetCesidApplication:0x00007fa3d200c1c0> Did you mean? allow_mass_assignment_of
Rspec file
require 'rails_helper'
describe Cesid::Application, :type => :controller do
describe 'cesid application' do
before do
allow_any_instance_of(ApplicationController).to receive(:id_token_param).and_return('hello')
end
it 'returns true if the id_token is present' do
expect(Cesid::Application.send('id_token_available?')).to eql(true)
end
end
end
Rspec version
3.5.4
This is honestly starting to drive me crazy
I see three issues:
You call allow_any_instance_of in a context in which it is not defined. allow_any_instance_of can be used in before blocks. I need to see your RSpec code to be more specific.
Actually your code is called on the ApplicationController, not on the module, therefore you need to change your stub to
allow_any_instance_of(ApplicationController).to receive(:id_token_param).and_return('hello')
Currently id_token_param will not be called at all, because id_token_available? checks the instance variable and not the return value of the id_token method that calls the id_token_param. Just change the id_token_available? to:
def id_token_available?
id_token.present?
end
There's a much better way of going about this test. The type: :controller metadata on your spec gives you an anonymous controller instance to work with.
Here's an example of how you could write this to actually test that the before_action from your module is used:
describe Cesid::Application, type: :controller do
controller(ApplicationController) do
def new
render plain: 'Hello'
end
end
describe 'cesid before_action' do
before(:each) do
routes.draw { get 'new' => 'anonymous#new' }
cookies[:id_token] = id_token
allow(Auth).to receive(:new).with(id_token)
.and_return(instance_double(Auth, valid?: false))
get :new
end
context 'when id token is available' do
let(:id_token) { 'hello' }
it 'sets #cesid_auth' do
expect(assigns(:cesid_auth)).to be_present
end
end
context 'when id token is unavailable' do
let(:id_token) { '' }
it 'does not set #cesid_auth' do
expect(assigns(:cesid_auth)).to be_nil
end
end
end
end

Rails 5 Rspec set session variable

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

RSpec: Testing concern which accesses params and request headers

I have this controller concern which accesses URL parameters and request headers.
module Authentication
extend ActiveSupport::Concern
# This concern has all authentication related functionality.
# This should be extended into ApplicationController
def current_user
# Returns a user from a request header containing a session token or nil
# or a URL parameter containing the same
token = request.headers[:token] || params[:token]
session = Session.find_by_token(token)
session.user if session
end
def authenticated?
# Is the user authenticated?
# This makes no assumptions regarding that the user is privileged enough to do something
return true if current_user
end
end
I'm unsure of how to test this in RSpec. How do I do this?
You can try shared example
# spec/support/authentication.rb
shared_examples "authentication" do
# Your tests here
let(:token) { "RandomString" }
let(:user) {create(:user)}
let(:session) {create(:session, token: token, user_id: user.id)}
describe "#authenticated?" do
it "should be authenticated" do
session.user.should eq(user)
end
end
end
# spec/lib/authentication_spec.rb
module TestAuths
class Authenticate
include Authentication
end
end
describe Authentication do
context "inclusion of the module" do
subject(:with_auth) { TestAuths::Authenticate.new }
it_behaves_like "authentication"
end
end

Devise - possible to use controller test to test user NOT signed in?

I have a controller that depends on the user being authenticated. So it looks like this
class PlansController < ApplicationController
before_action :authenticate_user!
def create
puts "here"
if user_signed_in?
puts "true"
else
puts "false"
end
end
end
My controller tests are working just fine when teh user IS signed in, i.e., when I'm writing something like this:
require 'rails_helper'
require 'devise'
RSpec.configure do |config|
config.include Devise::TestHelpers, :type => :controller
end
describe "create action" do
before do
#user = User.create(...)
sign_in :user, #user
end
it "should puts here and then true" do
post :create
# => here
# => true
end
end
But I'd also like to test what happens in the else statement. Not sure how to do this, it fundamentally doesn't even put the here. Is it possible to test this? Or should I just leave and let Devise be?
describe "create action" do
before do
#user = User.create(...)
# do not sign in user (note I have also tried to do a sign_in and then sign_out, same result)
end
it "should puts here and then true" do
post :create
# => nothing is put, not even the first here!
# => no real "error" either, just a test failure
end
end
The before_action :authenticate_user! will immediately redirect you to the default sign-in page, if the user isn't signed in, skipping the create action altogether.
The if user_signed_in? statement is moot in this case, because the user will always be signed in when that code has the chance to run.
If plans can be created with or without an authenticated user, remove the before_action line.

Setting a session value through RSpec

My current code looks like this:
/spec/support/spec_test_helper.rb
module SpecTestHelper
def login_admin
user = FactoryGirl.create(:user, type: 0)
session[:user_id] = user.id
end
end
/app/controllers/application_controller.rb
def current_user
if session[:user_id].nil?
render plain: 'Error', status: :unauthorized
else
#current_user ||= User.find(session[:user_id])
end
end
Unfortunately, session is always empty in the current_user method. Is there a way of controlling the session through RSpec?
This will change based on the spec type. For example, a feature spec will not allow you to directly modify the session. However, a controller spec will.
You will need to include the helper methods module into your example group. Say you have a WidgetsController:
require 'support/spec_test_helper'
RSpec.describe WidgetsController, type: :controller do
include SpecTestHelper
context "when not logged in" do
it "the request is unauthorized" do
get :index
expect(response).to have_http_status(:unauthorized)
end
end
context "when logged in" do
before do
login_admin
end
it "lists the user's widgets" do
# ...
end
end
end
You can also automatically include the module into all specs, or specific specs by using metadata.
I often do this by adding the configuration changes into the file which defines the helper methods:
/spec/support/spec_test_helper.rb
module SpecTestHelper
def login_admin
user = FactoryGirl.create(:user, type: 0)
session[:user_id] = user.id
end
end
RSpec.configure do |config|
config.include SpecTestHelper, type: :controller
end

Resources