How to write Rspec Testing for devise authenticate methods - ruby-on-rails

def self.find_for_database_authentication(warden_conditions)
conditions = warden_conditions.dup
if login = conditions.delete(:login)
where(conditions).where(["lower(username) = :value OR lower(email) = :value", { :value => login.downcase }]).first
else
where(conditions).first
end
end
Above is the method for authenticate and that code is from Model.
I unable to understand how to pass warden_conditions as parameter in this method for testing.
Can you please help me how to pass warden_condition as parameter for Rspec (Unit) Testiong?
Thanks

That seems to be a class method, and warden_conditions seems to be just a hash, so you can use something like this
let(:email) { "foo#bar.com" }
let(:warden_conditions) { { login: email } }
it "finds user by email" do
user = User.create(email: email)
authenticated = User.find_for_database_authentication(opts)
expect(authenticated).to eql user
end
it "finds user by username" do
user = User.create(username: email)
authenticated = User.find_for_database_authentication(opts)
expect(authenticated).to eql user
end

Related

Rails - sending invitations to users that already exist

I have an invite method that sends users emails to be part of a team. I have a modal that displays some users that already have an account and a textfield to input more emails and sends an invite for the person to join the platform.
But if I type an email that already exists in the database I get:
Validation failed: Email has already been taken
But want to send an email even if the person already has an account.
Here's what I have:
def invite
invite = params.dig(:invite) || {}
candidate_ids = invite.dig(:candidate_ids) || []
extra_emails = invite.dig(:extra_emails) || ""
emails = extra_emails
.split(/,|;/)
.map(&:strip)
new_users = emails.map { |email| team_email_invite(email) }
candidate_ids.concat new_users.map(&:id)
invite_data = candidate_ids.map { |uid| {
:user_id => uid,
:team_id => #team.id,
} }
TeamInvite.create(invite_data)
.map!(&:email)
respond_to do |format|
format.html { redirect_to overviews_url, notice: 'Invites sent.' }
format.json { head :no_content }
end
end
def team_email_invite(email)
user = User.new({
:email => email,
:password => SecureRandom.hex,
:role => :shadow,
})
user.add_role :recruiter
user.skip_confirmation_notification!
user.save!
end
candidate_ids - It's the users that I display on the list(all good)
extra_emails - emails in the textfield
So when I write a user that already has an account in the textfield, team_email_invite tries to create a new user and crashes.
I don't want to do something like
begin
new_users = emails.map { |email| team_email_invite(email) }
rescue
new_users=[]
end
because then it doesn't send the invite.
Any idea how to solve this?
You could use first_or_initialize. The block only gets run if the User does not already exist. Here's an example...
user = User.where(email: email).first_or_initialize do |usr|
usr.email = email
usr.password = SecureRandom.hex
usr.role = :shadow
usr.skip_confirmation_notification!
end
user.add_role :recruiter
user.save!

How to find user by email and if not, redirect him to register page

I use Omniauth and Omniauth-facebook gems and I want to click on button, if user is found, then sign him in. If not, redirect him to register page.
So far I use just classic login/register:
user.rb:
def self.from_omniauth(auth, role)
user = User.where(:provider => auth.provider, :uid => auth.uid).first
if user
return user
else
registered_user = User.where(:email => auth.info.email).first
if registered_user
registered_user.provider = auth.provider
return registered_user
else
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
if (role == "1")
user.add_role :sportsman
elsif (role == "2")
user.add_role :donor
end
user.provider = auth.provider
user.uid = auth.uid
user.email = auth.info.email
user.password = Devise.friendly_token[0,20]
end
end
end
end
omniauth_callbacks_controller.rb:
def facebook
role = cookies[:role]
# signin = cookies[:signin]
user = User.from_omniauth(request.env["omniauth.auth"], role)
if user.persisted?
flash.notice = "Signed by Facebooku"
sign_in_and_redirect user
else
flash.notice = "Error, try again."
session["devise.user_attributes"] = user.attributes
redirect_to new_user_registration_url
end
end
This code works, but if user is not registered, it will register him. But I sign roles when user register himself.
Thanks for help.
Because you are already saving the user attributes into the session with
session["devise.user_attributes"] = user.attributes
Then the following should already work:
def self.from_omniauth(auth, role)
user = User.where(:provider => auth.provider, :uid => auth.uid).first
if user
return user
else
# NOTE: because you're searching "email" and not both "email" + "provider", then I assume
# that you want users to have only one shared account between all providers (i.e. if they also sign-in in Google with the exact email as his email in Facebook)
registered_user = User.where(:email => auth.info.email).first
if registered_user
# NOTE: this line doesn't do anything because registered_user is not being "saved"
registered_user.provider = auth.provider
return registered_user
else
# build a new User object (don't save yet!)
return User.new.tap do |u|
u.provider = auth.provider
u.email = auth.info.email
u.uid = uid: auth.uid
u.password = Devise.friendly_token[0,20]
# because I assume you're using "rolify" gem, don't add the roles yet here, because I don't think you can store the "roles" into the session
# add the roles logic in whatever controller you have for user registration
end
end
end
end
Then override new_with_session in your user model. new_with_session is automatically called by Devise in registrations#new. We need to set the
user attributes that we previously stored in session inside omniauth_callbacks#facebook
class User < ApplicationRecord
def self.new_with_session(params, session)
super.tap do |user|
if user_attributes = session['devise.user_attributes']
user.assign(user_attributes)
end
end
end
end
So I used simple params to get where the user come from:
<script type="text/javascript">
document.cookie = "login=0"
</script>
This code is in views/devise/registrations/new.html.erb (and in views/devise/sessions/new.html.erb) and it is a JavaScript. It tells me if user went from login page (sessions folder - login=1) or from registration page (registrations folder - login=0). Then I use this code to determinate if user is from login page AND if he is not registered yet. If both conditions are true, then he is redirected to register page. It is so simple that I am embarrassed...
def facebook
hash = request.env["omniauth.auth"]
info = hash.info
email = info["email"]
user = User.find_by_email(email)
login = cookies[:login]
if (user == nil && login == "1")
redirect_to new_user_registration_path
else
role = cookies[:role]
user = User.from_omniauth(request.env["omniauth.auth"], role)
if user.persisted?
flash.notice = "Logged in by Facebook"
sign_in_and_redirect user
else
flash.notice = "Error, try again."
session["devise.user_attributes"] = user.attributes
redirect_to new_user_registration_url
end
end
end

Rails 4 user model will not save with hash data from oauth

I am trying to add oauth to an existing login for my project, but when I retrieve the hash data and try to save the user params with user.save!, the validation rules fail.
user.rb
has_secure_password
validates :email, :format => { :with => /\A[^# ]+#[^# ]+\.[^# ]+\Z/ },
:uniqueness => { :case_sensitive => false }
validates :password, :presence => true, :on => :create
validates :username,
:format => { :with => /\A[A-Za-z0-9][A-Za-z0-9_-]{0,24}\Z/ },
:uniqueness => { :case_sensitive => false }
...
class << self
def from_omniauth(auth_hash)
user = find_or_create_by(uid: auth_hash['uid'], provider: auth_hash['provider'])
user.name = auth_hash['info']['name']
user.email = auth_hash['info']['email']
user.username = auth_hash['info']['email']
user.save!
user
end
end
The output of user.errors.full_messages gives me ["Password can't be blank", "Password can't be blank", "Email is invalid", "Username is invalid"].
What I don't understand is why the validations are failing if the data parameters have been defined (i.e. user.email) and hold the correct values.
What am I missing?
your problem is the find_or_create_by method.
this will looking for the user with uid and provider otherwise try to create it.
but without vaild username, and so on, it will always fail if there is no user with uid and provider
update:
you try to find a user with an uid and a provider. if
find_or_create_by find a valid user, it will return it. with this
you can update the data.
BUT if find_or_create_by did not find a valid user, it will create a user with the given parameter uid and provider. but to
create a valid user, your model needs a valid username, a valid
password, and a valid email
you could do something like this
def from_omniauth(auth_hash)
user = User.find_by(uid: auth_hash['uid'], provider:auth_hash['provider']) || User.new(uid: auth_hash['uid'], provider:auth_hash['provider'], password: 'your password methods')
user.name = auth_hash['info']['name']
user.email = auth_hash['info']['email']
user.username = auth_hash['info']['email']
user.save!
user
end

Rails - adding attributes on a user before it's created

When a user registers with twitter I'm trying to add his name, location, etc. to his user record. I think I want to do something like user.build
Here is the controller. This is what happens:
user = User.new
user.apply_omniauth(omni)
if user.save
flash[:notice] = "Logged In!"
sign_in_and_redirect(:user, user)
else
session[:omniauth] = omni.except('extra')
redirect_to new_user_registration_path
end
When a user doesn't exist with twitter, the user is redirected to the registration path where they finish registering. I want to add the extra stuff from twitter to their yet to be saved user account. I can't do it in the user.apply_omniauth(omni) method because that saves to the authentications table.
Any ideas?
Thanks!
You can create a flag in apply_omniauth method to make a decision to save or not.
app/models/user.rb
# def apply_omniauth(omniauth) => def apply_omniauth(omniauth, save_it)
# apply_omniauth with save it flag
def apply_omniauth(omniauth, save_it = false)
case omniauth['provider']
when 'facebook'
self.apply_facebook(omniauth)
end
self.email = omniauth['user_info']['email']
if email.blank ? build_authentications(omniauth, save_it)
end
#build authentications
def build_authentications(omniauth, save_it = false)
auth_params = {: provider = > omniauth['provider'],
: uid = > omniauth['uid'],
: token = > (omniauth['credentials']['token'] rescue nil)
}
if save_it authentications.create!(auth_params)
else authentications.build(auth_params)
end
end
#force to save
def apply_omniauth!(omniauth)
apply_omniauth(omniauth, true)
end

How to test Rspec in controller

In controller,
def admin_search
#admins = User.find(:all,:joins=>[:roles],:conditions=>["name IN (?) and email like '#{params[:email]}%'",["content team","ops team"]]).paginate(:page => params[:page], :per_page => 10)
end
please suggest me some code in rspec
First of all, it's better to extract find(:all, ...) call to User model. Call it search, for instance.
class User < ActiveRecord::Base
scope :search_by_email, lambda { |email|
joins(:roles).where(["name IN (?) and email like '#{email}%'",["content team","ops team"]])
}
end
Use it in the controller then:
def admin_search
#admins = User.search_by_email(params[:email]).paginate(:page => params[:page], :per_page => 10)
end
Now, you can test the search_by_email method in isolation - check, that it returns result for "content team" and "ops team" only, correctly works with empty email string and so on.
I don't think you have to test paginate method, as it should be already tested in kaminari, will_paginate or whatever you use. But if you want to be sure, that it is being called, than you can use mock expectations (should_receive) in the controller specs.
EDIT: How the specs could look like
describe User do
describe ".search_by_email" do
let(:content_team) { Role.create! name: "content team" }
let(:ops_team) { Role.create! name: "ops team" }
let(:another_team) { Role.create! name: "another team" }
it "should search in content team" do
content_team_user = User.create! email: "joe.black#example.com", roles: [content_team]
User.search_by_email("black").should == [content_team_user]
end
it "should search in ops team" do
ops_team_user = User.create! email: "joe.black#example.com", roles: [ops_team]
User.search_by_email("black").should == [ops_team_user]
end
it "should not search in other teams" do
other_team_user = User.create! email: "joe.black#example.com", roles: [another_team]
User.search_by_email("black").should == []
end
it "should not search by empty string" do
content_team_user = User.create! email: "joe.black#example.com", roles: [content_team_user]
User.search_by_email("").should == []
User.search_by_email(nil).should == []
end
# more specs for search...
end
end
describe UsersController do
describe "admin search" do
let(:admin_user) { double(:admin_user).as_null_object }
let(:search_string) { 'joe' }
it "should search for admin users" do
User.should_receive(:search_by_email).with(search_string).and_return([admin_user])
get :admin_search, email: search_string
assigns(:admins).should == [admin_user]
end
end
end

Resources