Rails test Facebook methods using Koala and RSpec - ruby-on-rails

I'm making an application that uses Facebook info with Rails, Omniauth and Koala.
Using Ryan Bates Facebook Railscasts, I use this facebook method on my User model, to get the users uid, auth_token, etc from Facebook's API.
def facebook
#facebook ||= Koala::Facebook::API.new(oauth_token)
end
I also have another method that calls the facebook method to get info from the user's friends,
lets call this, facebook_friends
On my UsersController show action I have this:
def show
#user = User.find(params[:id])
#friends = #user.facebook_friends
end
So I use those two instance variables in my show template.
Writing tests with RSpec
Here's one test I'm trying to make
feature 'As a logged out user' do
background do
#user = FactoryGirl.create(:user)
end
scenario 'redirects to root path' do
visit user_path #user
page.current_path.should == root_path
end
end
But when I run it, I get this error:
1) As a logged out user redirects to root path
Failure/Error: visit user_path #user
Koala::Facebook::APIError:
OAuthException: Invalid OAuth access token.
# ./app/models/user.rb:45:in `friends_birthday'
# ./app/controllers/users_controller.rb:7:in `show'
# ./spec/requests/user_integration_spec.rb:22:in `block (2 levels) in <top (required)>'
Since I dont want to use a valid access token everytime I run the tests
How can I fake the facebook call on the User model using RSpec?

VCR is the answer, you could check excelled railscasts about it http://railscasts.com/episodes/291-testing-with-vcr
Some examples you can find in my sample project: https://github.com/lucassus/locomotive/blob/master/spec/features/user_facebook_connect_spec.rb

Related

How to use Google Oauth2 for both signing in users and creating new user accounts in a rails app?

I'm working on google authentication for a rails app. Currently using the omniauth-google-oauth2 gem to implement Google auth. I've managed to have users sign in using google. However, I'd also like users to be able to sign up using google. My problem is that I've matched the google callback URL to a particular controller action (sessions#create).
Is it possible to choose between 2 redirect URIs based on whether users are signing in or signing up? Currently, my only idea is to create new google client credentials to be used for sign up, I hope there is a better way.
You don't need to have 2 redirect uris, you just need to do some more work when receiving the callback. For instance:
class SessionsController < ApplicationController
...
def create
email = auth_hash['info']['email'] # assuming your omniauth hash is auth_hash and you're requiring the email scope
#user = User.find_by(email: email) if !email.blank? # assuming your user model is User
if #user
login_user(#user) # use your login method
elsif !email.blank?
#user = User.new(name: auth_hash['info']['name'], email: email)
unless #user.save!(validate: false) # validate false because I'm enforcing passwords on devise - hence I need to allow passwordless register here)
# deal with error on saving
end
else
# deal with no found user and no email
end
end
protected
def auth_hash
request.env['omniauth.auth']
end
end
I've written all steps but the creation process can be shortened to:
#user = User.create_with(name: auth_hash['info']['name']).find_or_initialize_by(email: email)
#user.save! if #user.new_record?
if #user
login_user(#user)
else
# deal with no user
end
Nonetheless, you can't be sure the user is going to give you scope access to the email, so personally I think the first version, even if a bit lengthier is more robust. Then on the shorter version there's also the problem of, if #user is false, why is so? And will require you to add more logic to figure out why is that, whereas in the first one it's much easier to apply the correct response to each situation.

Testing login flow with RSpec in Rails app

I'm using RSpec to test controller behavior in rails. One of the expectations I have is the following scenario:
User attempts to access a protected (requires login) url with params /home?val=123.
That user is redirected to login page.
After login the user should be redirected back to the URL originally requested, with relevant params.
I'm using devise for my auth system (using omniauth), and I've set up the recommended macros to login the user
and all that.
I'm a bit stuck on how to test the flow of the above behavior:
describe HomeController do
context "user is logged out" do
it "redirects back to original page after signin" do
# user is not signed in - redirected to login page
get :index, { :val => '12345' }
current_user.should_not be_present
expect(response).to redirect_to(login_page_path)
# should now login the user and verify
# that request.fullpath == '/home?val=12345'
# ?...
end
end
end
Does it even belong in a controller spec? Any help or example/s will be appreciated. thanks.
Luacassus looks like he has a decent answer, but have you looked at request testing with Capybara?
There's a really decent tutorial on Rails Casts here.
That's basically what we use to test our sign-up tests. However, we're not testing omniauth so it might not be exactly what you're looking for.

How to sign user in using RSpec

I am trying to write a RSpec before filter to log a user in. I have a products controller. In order to view products, a user must be logged in. I added a login method to spec/support/utilities like so:
def login(user)
post login_path, email: user.email, password: "password"
end
Then I called the method in a before filter in my spec/controllers/products test:
before :each do
user = FactoryGirl.create(:user)
login(user)
end
When I run the test I get the following error:
The action '/login' could not be found for ProductsController
I have a route for /login and my user authentication is simple - just like Railscasts #250 Authentication from Scratch. What am I missing?
If this is a controller spec, then the problem is that Routes are not available when unit testing controllers. The point is to test in isolation, which means no routes
Login is calling the ProductsController, that does not have a login method. As other told you, I wouldn't call one controller when you are testing another. Therefore, I think you should login some other way. For example:
def login(user)
session[:user_id] = user.id
end

OmniAuth RailsCast (episode 235) issue with Remind Me (episode 274)

I followed Ryan Bates'/RailsCasts tutorial for cookies login and remind me functionality in an application I am building.
[reference: http://railscasts.com/episodes/274-remember-me-reset-password?view=comments]
I wanted to introduce his OmniAuth functionality for the same application.
[reference: http://railscasts.com/episodes/235-omniauth-part-1]
I'm not using devise. I am correctly loading the Twitter app page, the error occurs when redirecting back to my application. I have the callback URL correctly set within Twitter.
I am getting an undefined method "authentications" for nil:NilClass error with OmniAuth and my authentications_controller. Specifically my console reads:
NoMethodError (undefined method authentications for nil:NilClass):app/controllers/authentications_controller.rb:9:in create'.
Here is the Authentications Controller code:
class AuthenticationsController < ApplicationController
def index
authentications = Authentication.all
end
def create
auth = request.env["omniauth.auth"]
current_user.authentications.create(:provider => auth['provider'], :uid => auth['uid'])
flash[:notice] = "Authentication was successful."
redirect_to authentications_url
end
Here is the current_user helper method in my Applications Controller.
private
def current_user
#current_user ||= User.find_by_auth_token!(cookies[:auth_token]) if cookies[:auth_token]
end
helper_method :current_user
User has many authentications.
Authentications belong to user.
I'd like to enable OmniAuth to work with Twitter so users can login with their Twitter account by avoiding the error I am receiving while maintaining my current_user code
Help is appreciated.
You're not taking into account the case where the user is not logged in.
What you're doing in your create action is associating the user's account on your site to his account on Twitter.
You can't use current_user without making sure that the user is logged in. Otherwise it will return nil, and that's why it's saying undefined method authentications for nil:NilClass.
Take a look at the next railscast that goes into more details: http://railscasts.com/episodes/236-omniauth-part-2

Devise+CanCan AccessDenied redirect differs between dev and test environments

I have a Rails 3.1 (RC5) app with Devise and CanCan. Both are configured well and working as expected except that when I run integration tests to ensure that AccessDenied is being redirected as desired, the redirect goes to Devise's sign in instead of the application root. I can verify in my test that the user is still logged in and can still access applicable parts of the app.
The redirect is defined in this short controller, which the other restricted controllers inherit (instead of directly inheriting ApplicationController).
class AuthorizedController < ApplicationController
before_filter :authenticate_user!
rescue_from CanCan::AccessDenied do |exception|
redirect_to root_url, :alert => exception.message
end
end
The restricted controllers look like this:
class Admin::UsersController < AuthorizedController
load_and_authorize_resource
def index
#users = User.all.order('name')
end
...
end
I am using the default (ActionDispatch::IntegrationTest) integration test; the only additional testing gems I have are Capybara, Machinist, and Faker (no RSpec, Cucumber, etc.).
My test looks like:
def test_user_permissions
sign_in users(:user)
get admin_users_path
assert_response :redirect
assert_redirected_to root_url
end
The test fails with:
Expected response to be a redirect to <http://www.example.com/> but was a redirect to <http://www.example.com/users/sign_in>
When I test this by logging in as a restricted user in my dev environment, I am redirected to '/' as expected, but using the same type of user in the integration tests fails.
In the integration tests, the user is not actually being logged out, although the redirect makes it look like that is happening. When I change the test to not test the redirection target and continue trying other URLs, the user is still logged in and the test passes.
Addendum & Solution:
I originally did not include the sign_in method that held the key clue. Here it is:
module ActionController
class IntegrationTest
include Capybara::DSL
def sign_in (user, password = 'Passw0rd')
sign_out
visit root_path
fill_in 'Email', :with => user.email
fill_in 'Password', :with => password
click_button 'Sign in'
signed_in? user
end
...
end
end
I was mixing Capybara access methods (visit, click_button, etc.) in sign_in and vanilla integration test access methods (get, etc.) in the test. When I used Webrat (before Capybara) this mixing worked as I expected, but evidently Capybara's session state is handled separately, so access via the Capybara methods was authenticated, but access via the vanilla integration test methods was not.
You didn't post your devise config in ApplicationController, but it looks like the devise Identity/Sign-in checks are loading up before the CanCan Authorization checks (which makes sense).
It looks like your test sign in setup isn't working correctly, because '/users/sign_in' is the default redirect for devise when there isn't a valid user session.
Because the devise identity check is failing, it's never hitting your CanCan Authorization check. Why ask what the user can do if there isn't a user yet.
The before_filters will execute from the base ApplicationController first on up the chain in the order they are defined, so subclass filters after base class filters. This is why I think you've got Devise config in your base ApplicationController causing this not to hit CanCan.
If you post your Devise config/code we may be able to help you debug it further.
EDIT / TLDR:
You are seeing a redirect to login because Devise doesn't think a valid user session exists. Your test "sign_in()" helper isn't working the way you think it is. That's why this works in dev mode with a live user session via manual login.

Resources