My Rails 3.2 app uses OmniAuth and Devise to sign in with Twitter. The authentication system works fine. I would like to write an integration test in rspec to make sure everything works. Using the information in the wiki, I've written the following, but I know I'm missing things.
Under test.rb in config/environments, I have the following lines
OmniAuth.config.test_mode = true
OmniAuth.config.mock_auth[:twitter] = {:provider => 'twitter', :uid => '123545'}
My rspec test looks like this:
describe "Authentications" do
context "without signing into app" do
it "twitter sign in button should lead to twitter authentication page" do
visit root_path
click_link "Sign in with Twitter"
Authentication.last.uid.should == '123545'
end
end
end
Authentication is the name of my model and calling .uid in rails console returns the string fine.
I'm getting the following error when I run this test:
Failure/Error: Authentication.last.uid.should == '123545'
NoMethodError:
undefined method `uid' for nil:NilClass
Can anyone help me figure out how to use the OmniAuth mocks that are provided? An explanation for why and how it works would be appreciated as well.
I run into something similar.
After changing my mock object from using symbol keys:
OmniAuth.config.mock_auth[:twitter] = {
:uid => '1337',
:provider => 'twitter',
:info => {
:name => 'JonnieHallman'
}
}
to using string keys:
OmniAuth.config.mock_auth[:twitter] = {
'uid' => '1337',
'provider' => 'twitter',
'info' => {
'name' => 'JonnieHallman'
}
}
it worked.
And do you have
request.env["omniauth.auth"] = OmniAuth.config.mock_auth[:twitter]
somewhere in your testcase?
Did you try moving these two lines to spec_helper.rb?
OmniAuth.config.test_mode = true
OmniAuth.config.mock_auth[:twitter] = {:provider => 'twitter', :uid => '123545'}
Also add the following before block in your test file:
before do
request.env["omniauth.auth"] = OmniAuth.config.mock_auth[:twitter]
end
You can find more info on this link: https://github.com/intridea/omniauth/wiki/Integration-Testing
I highly suggest this answer
In short...
Set up the mock
Make the request
Test whatever code is attached to the callback
For example: test the session['uid'] was set (although, I opt to test only what the user sees, or, rather, does not see)
My code...
config/environments/test.rb
Rails.application.configure do
...
OmniAuth.config.test_mode = true
OmniAuth.config.mock_auth[:linkedin] = {
'provider' => 'linkedin',
'uid' => '123545',
'info'=>
{ 'email'=>'infinite#jest.com',
'first_name'=>'Dave',
'last_name'=>'Wallace' }
}
end
spec/features/sign_in_feature_spec.rb
require 'rails_helper'
feature 'Sign in with LinkedIn' do
before do
OmniAuth.config.add_mock(:linkedin, {:uid => '12345'})
end
let(:user) { create(:user) }
scenario 'with valid email and password' do
visit '/'
expect(page).to have_no_content 'Sign Out'
click_link 'nav-sign-in' # image/button: Sign in with LinkedIn
expect(page).to have_content 'Sign Out'
end
end
Let me know if/how I may improve this solution (and my code!)
Selected solution does not work for me.
My solution i get from https://gist.github.com/kinopyo/1338738
and official doc https://github.com/intridea/omniauth/wiki/Integration-Testing
here:
# in spec/support/omniauth_macros.rb
module OmniauthMacros
def mock_auth_hash
# The mock_auth configuration allows you to set per-provider (or default)
# authentication hashes to return during integration testing.
OmniAuth.config.mock_auth[:odnoklassniki] = OmniAuth::AuthHash.new({
:provider => 'odnoklassniki',
:uid => '123545',
:info => OmniAuth::AuthHash::InfoHash.new({
:name => 'mockuser'
})
})
end
end
# in spec/spec_helper.rb
RSpec.configure do |config|
# email spec
config.include(EmailSpec::Helpers)
config.include(EmailSpec::Matchers)
end
OmniAuth.config.test_mode = true
# in spec example:
visit new_user_registration_path
mock_auth_hash
find('#btn-odnoklassniki').click # here is link generated as omniauth_authorize_path(resource_name, provider)
Related
I'm trying to get exception_notification and rails to work together. I have looked at a bunch of other questions, and read through the ReadMe heavily on the exception_notification gem page. However, most other tutorials and questions all seem to be out of date by a year or so, before the major version switch to 4.0. What's extra weird is that the proper emails seem to be getting sent when in development mode, but NOT in production mode, which is where I really want them.
Here's the relevant code:
I installed with gem 'exception_notification' as it states in the ReadMe.
then... inside config/environments/production.rb
Whatever::Application.configure do
config.action_mailer.raise_delivery_errors = true
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
:address => "smtp.gmail.com",
:port => 587,
:user_name => 'blah#blah.com',
:password => 'blahblah',
:authentication => 'plain',
:enable_starttls_auto => true
}
config.action_mailer.perform_deliveries = true
config.action_mailer.default_url_options = {
:host => 'www.blah.com'
}
ActionMailer::Base.default :from => 'BlahApp <reply#blah.com>'
Paperclip.options[:command_path] = "/usr/bin/"
config.middleware.use ExceptionNotification::Rack,
:email => {
:email_prefix => "[Error!] ",
:sender_address => %{<reply#blah.com>},
:exception_recipients => ['"Test Recipient" <tester#blah.com>', '"Test 2" <test2#gmail.com>'],
}
As I said, when I copy/paste this into development.rb, it seems to work properly and send e-mails (though I'm not actually receiving them, Paperclip is catching them and opening them in the browser, and they have what I want in them). Then in production, nothing happens, and when I check the log files, it doesn't appear to be even attempting to send the e-mail. No errors received, except for the one I purposely throw.
I saw there was some issue related to SMTP stuff, so I also tried directly putting the smtp_settings directly into the email config hash of ExceptionNotification (while still having the same settings outside), but no luck with that.
I feel like I'm missing something silly. It's supposed to be dead simple to use. Any thoughts? Much thanks!
Ok, so the problem was that apparently previous devs (I inherited this project) had already set up a different kind of error catching that simply rendered different error pages. That function only ran in production, and it was blocking the call to exception_notification, which is why it just wasn't doing anything in production, but was working fine in development. So... all I did in the end was this...
In the app... this was already in the controllers/application_controller.rb file:
unless Rails.application.config.consider_all_requests_local
#Commenting this would show user a blank page but also show a detailed error message. Don't do this
#Unless absolutely necessary.
rescue_from Exception, with: lambda { |exception| render_error 500, exception }
rescue_from ActionController::RoutingError, ActionController::UnknownController, AbstractController::ActionNotFound, ActiveRecord::RecordNotFound, with: lambda { |exception| render_error 404, exception }
end
And the render_error function had some just well, rendered the error page. So I added in a step to set off the call to exception_notification like so...
def render_error(status, exception)
if (status != 404)
ExceptionNotifier::Notifier
.exception_notification(exception, options.merge(:env => env))
.deliver
end
respond_to do |format|
format.html { render template: "home/error_#{status}", layout: 'layouts/heropage', status: status }
format.all { render nothing: true, status: status }
end
end
Please add in Gemfile gem 'exception_notification', '4.1.0.rc1' and in your production.rb file
config.consider_all_requests_local = false
config.action_mailer.raise_delivery_errors = true
config.action_mailer.perform_deliveries = true
config.action_mailer.delivery_method = :smtp
config.action_mailer.default_url_options{host:'http://yourUrl.herokuapp.com/'
}
config.action_mailer.smtp_settings = {
address: "smtp.gmail.com",
port: 587,
domain: 'gmail.com',
authentication: "plain",
enable_starttls_auto: true,
user_name: "youremail#gmail.com",
password: "Password"
}
config.middleware.use ExceptionNotification::Rack,
:email => {
:email_prefix => "[Notifer] ",
:sender_address => %{"Exception Notification" < sender#domain.com>},
:exception_recipients => %w{first#domian.com second#domain.com }
}
If you have succeeded in testing post, put, and delete http methods of a Rails API protected with the doorkeeper OAuth2 provider gem, please share and I'll give you the love.
The doorkeeper wiki documentation and sample application show pretty well how to test a get method. I succeeded testing a post with something like what follows using the Capybara test driver with Cucumber. Failed to test any API that routes from put or delete. Failed to post using an rspec test.
#user = create :user
#client = create(:oauth_application)
#token = create(:oauth_token, :application => #client, :resource_owner_id => #user)
json_for_new_entry = {
date_attr: Time.now.to_date,
decimal_attr: '1.1',
string_attr: 'oath2, you make me blue',
bool_attr: false,
int_attr: 1
}.to_json
page.driver.header 'Authorization', "Bearer #{#token.token}"
page.driver.post api_entry_path, json_for_new_entry,
'CONTENT_TYPE' => 'application/json'
The factories are nothing special:
factory :user, :class => User do |user|
sequence :email do |n| "user#{n}#example.com" end
pwd = "password"
password pwd
end
factory :oauth_application, :class => Doorkeeper::Application do
sequence(:name) { |n| "application_name_#{n}" }
#redirect_uri 'urn:ietf:wg:oauth:2.0:oob'
redirect_uri 'http://localhost:3000/'
end
factory :oauth_token, :class => Doorkeeper::AccessToken do
association :application, :factory => :oauth_application
association :resource_owner_id, :factory => :user
end
My environment is a little behind latest versions:
rails gems at 3.1.12
capybara 2.2.0
cucumber 1.3.10
devise 2.2.7
warden 1.2.3
doorkeeper 0.7.4
rspec-core 2.14.5
rspec-expectations 2.14.3
rspec-mocks 2.14.3
rspec-rails 2.14.0
Assuming the intention of your test is to verify the underlying API functionality and not the doorkeeper protection then this is the hack I use:
In my base controller:
module Api
class BaseController < ActionController::Base
doorkeeper_for :all unless Rails.env.test?
private
def current_user
if Rails.env.test? && $test_user
$test_user
else
#current_user ||= User.find(doorkeeper_token.resource_owner_id)
end
end
end
end
In my tests I have a login helper:
def login(user)
$test_user = user
end
def logout
$test_user = nil
end
I'm not proud of that code but nonetheless I can now get on with my life instead of worrying about how to make rails/doorkeeper/capybara et al work together during testing.
You can use the example included in doorkeeper wiki as follow
describe Api::V1::ProfilesController do
describe 'GET #index' do
let(:token) { double :acceptable? => true }
before do
controller.stub(:doorkeeper_token) { token }
# allow(controller).to receive(:doorkeeper_token) {token} # => RSpec 3
end
it 'responds with 200' do
get :index, :format => :json
response.status.should eq(200)
end
end
end
I used the answer that Moustafa gave, but I wanted to DRY it up so I put the following into spec/support/doorkeeper_oauth.rb:
shared_context "doorkeeper_oauth", oauth: true do
let(:dummy_token) { double(:acceptable? => true) }
before do
if controller.present?
allow(controller).to receive(:doorkeeper_token) { dummy_token }
end
end
Then, in your controller spec you change the opening line slightly:
describe Api::V2::WidgetsController, oauth: true do
which pulls in the shared context via the "metadata" method.
edit: I have used this for at least GET and POST, which success in both cases.
I'm using this lucatironi tutorial to do my RM/rails/devise native authentication. I got everything working except one piece where I authenticate in the sessions_controller.rb
I'm sending in
{ session : { email: "test#five.com", password: "password" } } (bubblewrap is forcing a 'sessions' node onto my JSON!)
resource_name is :api_v1_user
controller_path is "api/v1/sessions"
class Api::V1::SessionsController < Devise::SessionsController
def create
warden.authenticate!(:scope => resource_name, :store => false, :recall => "#{controller_path}#failure")
render :status => 200,
:json => { :success => true,
:info => "Logged in",
:data => { :auth_token => current_user.authentication_token } }
end
end
All I get is an alert in the app "Login Failed"
Thanks for any help
Did you remember to call skip_before_filter :verify_authenticity_token? Given that the request is not coming from a rails-generated form, the token wouldn't be there.
So I figured this out.
authenticate! doesn't return annything
has to be "authenticate" with no "!"
that was it!
I'm looking for a simple example for a omniauth-facebook, devise, capybara, capybara-mechanize, (maybe with VCR), and rpsec integration test...
Capybara can't connect to to an external website. So capybara-mechanize would be needed. Maybe VCR could speed things up. This link describes how to get test facebook users: https://developers.facebook.com/docs/test_users/
Using a mock, as described in the omniauth docs works well for testing the logic of the model and controller. This requires the following code in specs/helpers/omniauth.rb and calling set_omniauth in the :before of your rspec integration test.
However, it would be nice to have a true integration test that runs against facebook servers. Surely, you'd want to flag such slow tests so they do not run via guard and the default rake task.
# You can read about this gist at: http://wealsodocookies.com/posts/how-to-test-facebook-login-using-devise-omniauth-rspec-and-capybara
# which is for twitter. Below is for facebook
def set_omniauth(opts = {})
default = {:provider => :facebook,
:uuid => "1234",
:facebook => {
:email => "foobar#example.com",
:gender => "Male",
:first_name => "foo",
:last_name => "bar",
:nickname => "foo bar",
:image => 'http://graph.facebook.com/659307629/picture?type=square'
}
}
credentials = default.merge(opts)
provider = credentials[:provider]
user_hash = credentials[provider]
OmniAuth.config.test_mode = true
h = {
'provider' => provider,
'uid' => credentials[:uuid],
'info' => user_hash,
"extra" => {
"info" => user_hash
}
}
OmniAuth.config.mock_auth[provider] = CollectionUtility.deep_stringify_keys(h)
end
def set_invalid_omniauth(opts = {})
credentials = { :provider => :facebook,
:invalid => :invalid_crendentials
}.merge(opts)
OmniAuth.config.test_mode = true
OmniAuth.config.mock_auth[credentials[:provider]] = credentials[:invalid]
end
I have an RSPEC integration test for a Rails 3.1 app that needs to test an api for a mobile client by issuing a POST request with JSON params and a mobile header that needs to use http_basic authentication
As the request object is not available in an integration test I'm kinda stuck
This is the code I have so far
it "successfully posts scores" do
# request.env["HTTP_ACCEPT"] = "application/json" #This causes an error as request is nly available in controller tests
post "scores", :score => {:mobile_user_id => #mobile_user.id, :points => 50, :time_taken => 7275}.to_json,
:format => :json, :user_agent => 'Mobile', 'HTTP_AUTHORIZATION' => get_basic_auth
end
The post request does not recognise that I am using http basic authentication but unsure if the format for json is correct. Any help appreciated
get_basic_auth is a helper me4thod that looks like this
def get_basic_auth
user = 'user'
pw = 'secret'
ActionController::HttpAuthentication::Basic.encode_credentials user, pw
end
I use a before_filter in my controllers that checks for mobile and http_basic_authentication that looks like this
def authorize
logger.debug("#### Authorizing request #{request.inspect}")
if mobile_device?
authenticate_or_request_with_http_basic do |username, password|
username == Mobile::Application.config.mobile_login_name && Mobile::Application.config.mobile_password
end
else
unless current_user
redirect_to login_url, :notice => "Please log in"
end
end
end
I get a redirect to login so obviously the mobile header is not being accepted so I have no idea really if any of the other headers are working
UPDATE
Figured it out
post("scores", {:score => {:mobile_user_id => #mobile_user.id, :points => 50, :time_taken => 7275}}.to_json,
{"HTTP_USER_AGENT" => "Mobile", 'HTTP_AUTHORIZATION' => get_basic_auth, 'HTTP_CONTENT_TYPE' => "application/json"})
Does the trick nicely
I figured out what I needed to do
post("scores", {:score => {:mobile_user_id => #mobile_user.id, :points => 50, :time_taken => 7275}}.to_json,
{"HTTP_USER_AGENT" => "Mobile", 'HTTP_AUTHORIZATION' => get_basic_auth, 'HTTP_CONTENT_TYPE' => "application/json"}
Obviously the "mobile" user agent is not needed to test normal json requests.
Hope that helps someone