Testing login/logout with rspec (preserve session data between two API calls) - ruby-on-rails

I have the following API methods in my Rails app that I would like to test:
/current-user.json
/login.json
/logout.json
I want to run the following test in RSpec:
Check that the user is logged out at /current-user.json
Login by sending an access token to /login.json
Check that the user is logged in at /current-user.json
The problem is that I set session[:user_id] in the control once the user logs in. It seems like the session doesn't carry over between requests. Is there a way to test in RSpec while preserving the session data between request?
Here is the code from my spec file
https://gist.github.com/2c077538bddfdb9c76bd

Have you tried the Mechanize library?
require 'Mechanize'
m = Mechanize.new
m.keep_alive = true
m.body = YOUR_JSON
m.basic_auth(YOUR_USER, YOUR_PASSWORD)
m.get("#{server_url}/login.json")
m.get("#{server_url}/current.user")
m.get("#{server_url}/login.json")
m.get("#{server_url}/current.user")

Related

Integration Testing with Devise + Omniauth + Rspec + Capybara

I am trying to write some integration tests using Rspec (feature) alongside Devise and Omniauth. The OAuth provider I am using is azure_activedirectory.
I have followed the tutorial here at the Omniauth wiki
I don't think I am doing it right. When I launch an integration test, the link for the login (localhost:3000/omniauth/azure_activedirectory) behaves like it would in dev or production, with the link directing the client to the omniauth portal page.
Of course given this is a test, I can't store credentials here.
It appears from the code in the wiki link that it should instead feed a login using a mock_auth.
Here is my current spec code:
require 'rails_helper'
Capybara.app_host = "http://lvh.me"
OmniAuth.config.test_mode = true
OmniAuth.config.add_mock(:azure_activedirectory, {:uid => '12345'})
RSpec.feature "AuthenticatesUser", type: :feature do
before do
Rails.application.env_config["devise.mapping"] = Devise.mappings[:user]
Rails.application.env_config["omniauth.auth"] = OmniAuth.config.mock_auth[:azure_activedirectory]
end
scenario "User can login", js: true do
WebMock.allow_net_connect!
visit '/'
click_link 'login'
binding.pry
WebMock.disable_net_connect!(allow_localhost: true) # Re enable with local host
end
end
So what am I doing wrong? I feel that I am using the example code in a very incorrect way or not understanding the process.
Or is the next step just setting up a testing oauth instance and configuring it that way to use live credentials from Azure.
Thanks for the help in advance.
UPDATE
This has to do with Capybara and the JS driver starting or running on a different server.
If in the interactive ruby console in the Selenium browser OmniAuth.config.test_mode == false. If I set it using the console in the web browser, everything 'works'.
I found the solution.
You can not put OmniAuth into test mode in your specs. As capybara with js is in a separate thread, it has no idea about any config that was being set in the specs.
The solution was moving the declarations
OmniAuth.config.test_mode = true
OmniAuth.config.auth_mocks = NewAuthMock
into the test.rb environment file, that way Capybara would spawn with the correct config.

rails twitter search different results on heroku WIERD

This is the weirdest thing on earth.
I'm using Rails 4 and the twitter gem.
I set a global variable, '$twitter' to access the twitter client.
Local development console:
$twitter.search("spinningheelkickpodcast")
returns 14 tweets.
Heroku console:
$twitter.search("spinningheelkickpodcast")
returns 9 tweets.
What the hell? why?
heroku database and local database works independently, They both contains different data.
I got same result from both console.
FYI those credentials should be moved out of your initializer and into environment variables e.g. ENV['TWITTER_ACCESS_TOKEN'] etc..
You have a few options with regard to accessing the client throughout the code.
Store the Twitter client in a global variable. Global variables are generally considered a bad idea. $twitter_client = Twitter::REST::Client.new(...)
Create a singleton class. This probably won't be thread safe.
Use the factory pattern to generate a new client when/where you need it. For example,
In app/services/twitter_api.rb:
class TwitterAPI
def client
#client ||= Twitter::REST::Client.new do |config|
config.key = ENV['VALUE'] # for each required credential
end
end
end
TwitterAPI.new.client.do_something()

LinkedIn OAuth 2.0: undefined local variable or method `oauth' for #<LinkedinController:0x7d15970>

I've been using the gem LinkedIn OAuth 2.0. Right now I can get it to generate the linkedin signin page. However, the next thing that is supposed to happen is it sends to my callback link a code which I use to generate an access token. The problem is that the variable 'oauth' is generated in the authenticate action but then needs to be used again in the callback action. I've tried generating the oauth variable again using the exact same parameters, but when I do that I get an SSL certificate error. It seems like the exact same oauth instance needs to be used in both cases. Let me know if you have any thoughts. My code is below:
def authenticate
require "linkedin-oauth2"
LinkedIn.configure do |config|
config.client_id = "Mycode"
config.client_secret = "Mysecret"
# This must exactly match the redirect URI you set on your application's
# settings page. If your redirect_uri is dynamic, pass it into
# `auth_code_url` instead.
config.redirect_uri = "http://localhost:3000/auth/linkedin/callback"
end
oauth = LinkedIn::OAuth2.new()
url = oauth.auth_code_url
redirect_to url
end
def callback
require "linkedin-oauth2"
code = params[:code]
access_token = oauth.get_access_token(code)
api = LinkedIn::API.new(access_token)
my_job_titles = api.profile(fields: ["id", {"positions" => ["title"]}])
puts my_job_titles
redirect_to("/")
end
end
Getting an SSL certificate error doesn't mean that the instantiation is wrong. I don't know that gem, but I can't see why would that be a problem.
The require and the configuration block should not be inside the method (maybe you forgot the configuration from the second method?); the best place for those is in config/initializers/linkedin_oauth2.rb.
If you don't want to load it at startup, then you can put those in a private method oauth with memoization:
def oauth
#oauth ||=
begin
require "linkedin-oauth2"
LinkedIn.configure do |config|
...
end
LinkedIn::OAuth2.new()
end
end
If the SSL error still occurs, you should investigate that. You can try creating a simple Ruby script with some example from the gem's readme, just to test the connection to LinkedIn.
Looks like the gem is using the faraday gem for HTTP, you can also try using that directly to make a simple call to LinkedIn.

Capybara / Poltergeist / Devise #current_user is nil - Rails 4

I got a feature spec which receives a token from an external site and redirects back to my app. Upon redirecting, the client is served an orders#new view, which calls #current_user.items. This call works in all environments except test. Capybara / Poltergeist or Selenium get undefined method items for nil class
I've tried:
assigning #current_user in before(:each)
changing the web driver
setting #current_user to User.first for Rails.env.test?in my
controller
How can I make sure #current_user works in test?
You need to either login in your feature spec, or use wardens test mode through devise to fake the login - see https://github.com/plataformatec/devise/wiki/How-To:-Test-with-Capybara - so that the current user is set (along with the relevant session cookies). Feature tests and the app each run separately when using any Capybara driver other than rack-test so attempting to set #current_user in a before(:each) isn't going to work. Setting it in the controller would probably work for that controller action but won't set the session cookies if you're following links, etc - and doing something like that in a controller just for testing is generally a bad idea.
Tom's answer made me realize that cookies are not stored because Capybara hosts are not static. I ended up explicitly setting the server_port, server_host and app_host in the rails_helper which worked like a charm:
Capybara.app_host = 'http://localhost:3000'
Capybara.server_host = 'localhost'
Capybara.server_port = '3000'
My app is not using Devise and Warden. Otherwise, Tom's answer would've worked.

using VCR with Rspec in feature scenarios

I have a Rails 4 app that uses a custom authentication gem that authenticates users against a third-party API. The app requires authentication for most actions on the site (visitors can do very little).
I am trying to use VCR to record the api request made during authentication for all of the integration tests, but all examples that I can find on SO and the Relish documentation only cover how to do this with Rspec in a 'describe do' spec, as referenced here:
https://www.relishapp.com/vcr/vcr/v/1-6-0/docs/test-frameworks/usage-with-rspec
Since no customers are involved on this project, I am writing integration tests with Rspec and Capybara instead of Cucumber, so my tests are using the 'feature/scenario' format like so:
feature 'posts' do
scenario 'a user can log in' do
# use vcr for api request
sign_in_user # refers to a method that handles the api call to log in a user, which is what I would like VCR to record.
expect(page).to have_content("User signed in successfully")
end
end
Using the command described in the documentation:
use_vcr_cassette
inside of the 'scenario' block, returns an error:
Failure/Error: use_vcr_cassette
undefined local variable or method `use_vcr_cassette' for #<RSpec::ExampleGroups::Posts:0x007fb858369c38>
I followed the documentation to setup VCR in my spec/rails_helper.rb (which is included by the spec/spec_helper.rb)... which basically looks like this:
require 'vcr'
VCR.configure do |c|
c.cassette_library_dir = 'support/vcr_cassettes'
c.hook_into :webmock
end
Obviously added gem 'vcr' to my Gemfile development/test group and it is a thing in console and binding.pry from inside of a test.
Has anyone used VCR inside of a Rspec feature? or have any suggestions on what I might do as a workaround?
Thanks in advance
Solution: Taryn East got me to the solution, but it is slightly different than the link posted for anyone trying to do this moving forward.
here is the most basic config in spec/rails_helper.rb or spec/spec_helper.rb:
require 'vcr'
VCR.configure do |c|
c.cassette_library_dir = 'spec/cassettes'
c.hook_into :webmock
c.configure_rspec_metadata!
end
using c.configure_rspec_metadata! is required for Rspec to handle the :vcr tag.
And in an Rspec Feature spec:
feature 'users' do
scenario 'logged in users should be able to do stuff', :vcr do
# authenticate user or make other http request here
end
end
Oddly enough, in my tests - VCR is recording the response and if passes the first time, but fails the second time. I traced this to the response being stored differently than it is received.
On a normal request (using excon) like so:
resp = Excon.post(url, :body => data, :headers => { "Content-Type" => "application/x-www-form-urlencoded", "Authorization" => authorization_header })
The response has a header that is accessible in this format:
resp.headers["oauth_token"]
which returns an oauth token.
In the VCR response, it is being stored differently and only accessible as:
resp.headers["Oauth-Token"]
Which is weird, but workable. This may be a bug with VCR or some issue with Excon... too busy to figure that one out right now, but just a heads up in case anyone else uses this setup and gets a passing test with the live http request and a failing test when using the VCR cassette. A quick workaround is to either change the VCR cassette data to match what your code expects, or modify your code to accept either available value.

Resources