I was having a heck of a time figuring out how to login and logout using response objects from Rails. The standard blogs were ok, but I finally diagnosed it, and I wanted to record it here.
app.get '/'
assert_response :success
app.get '/auth_only_url'
assert_response 302
user = User.find(:user_to_login)
app.post '/signin_url',
:user_email => user.email,
:user_password => '<password in clear>'
assert_response 302
app.follow_redirect!
assert_response :success
app.get '/auth_only_url'
assert_response :success
Note, the above implies that you redirect after a failed auth request, and also that you redirect after logging in.
To ensure that you load the fixtures into your test environment DB (which normally occurs during rake test), make sure you execute the following:
rake db:fixtures:load RAILS_ENV=test
(From Patrick Richie)
The default URL will appear to be 'www.example.com', as this default host as set in ActionController::Integration::Session
ActionController::Integration::Session.new.host=> "www.example.com"
It is set in actionpack/lib/action_controller/integration.rb#75
To change it in the integration test, do the following:
session = open_session do |s| s.host = 'my-example-host.com' end
'www.example.com' is the default host as set in ActionController::Integration::Session
>> ActionController::Integration::Session.new.host
=> "www.example.com"
It is set in actionpack/lib/action_controller/integration.rb#75
You should be able to change it in your integration test by doing the following:
session = open_session do |s|
s.host = 'my-example-host.com'
end
Related
How do you set a signed cookie within a Rails integration test?
For example, assuming I load the :user_id from the cookie, this is the way I would expect to test it, but no joy:
test "success when cookie properly set" do
cookies.signed[:user_id] = #user.id
get user_url(#user)
assert_response :success
end
I understand that its testing the implementation, but in some cases its necessary.
Update: Works But Ugly
I have a work around, but its pretty ugly. This passes the cookie as a header to the get request and requires that you manually sign the and format the cookie:
test "success when cookie properly set" do
signed_user_id="eyJfcmFpb..."
get user_url(#user), headers: { "HTTP_COOKIE" => "user_id=#{ signed_user_id }" }
assert_response :success
end
I was looking at the RailsGuides and I saw this example:
require 'test_helper'
class UserFlowsTest < ActionDispatch::IntegrationTest
test "login and browse site" do
# login via https
https!
get "/login"
assert_response :success
post_via_redirect "/login", username: users(:david).username, password: users(:david).password
assert_equal '/welcome', path
assert_equal 'Welcome david!', flash[:notice]
https!(false)
get "/articles/all"
assert_response :success
assert assigns(:articles)
end
end
I am confused about the variable "path" in the line assert_equal '/welcome', path. What is this exactly? Is this the url that would show up in your browser after we perform the post_via_redirect action right before it?
I looked into the helpers available in the integration tests, but the document didn't say anything about this "path" variable.
Additionally, I try it at my rails app and it works fine, but when I tried to call it at one of my controller tests, it gave me and undefined local variables or method error. So does it mean that the "path" variable is only available in the integration tests? What if I want to use it in the other types of tests?
path is the url of your current page.
path is request.url without the domain name.
In this test you've been redirected from '/login' to '/welcome'.
I'm writing an integration test to make sure my webapp isn't vulnerable to session fixation.
I have manually verified that reset_session is actually firing in the authentication logic, and further that the cookie does indeed change when I log in with my web browser (so, I'm not vulnerable to session fixation anymore), but I can't get my RSpec integration test to successfully verify this.
Here is my RSpec integration test.
require 'spec_helper'
describe "security" do
self.use_transactional_fixtures = false
append_after(:each) do
ALL_MODELS.each &:delete_all
end
describe "session fixation" do
it "should change the cookie session id after logging in" do
u = test_user :active_user => true,
:username => "nobody#example.com",
:password => "asdfasdf"
u.save!
https!
get_via_redirect "/login"
assert_response :success
cookie = response.header["Set-Cookie"].split(";").select{|x| x.match(/_session/)}[0].split("=")[1].strip
post_via_redirect "/login", "user[email]" => "nobody#example.com",
"user[password]" => "asdfasdf",
"user[remember_me]" => "1"
assert_response :success
path.should eql("/dashboard")
cookie.should_not eql(response.header["Set-Cookie"].split(";").select{|x| x.match(/_session/)}[0].split("=")[1].strip)
end
end
end
Everything works except for the very last assert. The cookie doesn't change.
Are there any known issues with RSpec/Rails integration tests where reset_session doesn't work as expected? What can I do to write a test that verifies session fixation is not an issue?
So I eventually did end up figuring this out.
I was trying to edit the response header directly to test cookies, but I guess that's not the blessed way.
In integration tests with Rails 2.x anyway, there's a cookies hash that you can use. Here's what the test ended up looking like:
u = test_user :active_user => true,
:username => "nobody#example.com",
:password => "asdfasdf"
u.save!
https!
get_via_redirect "/login"
assert_response :success
cookie = cookies['_session']
cookie.should be_present
path.should == "/login"
post_via_redirect "/login", "user[email]" => "nobody#example.com",
"user[password]" => "asdfasdf",
"user[remember_me]" => "1"
assert_response :success
path.should eql("/?login_success=1")
new_cookie = cookies['_session']
new_cookie.should be_present
cookie.should_not eql(new_cookie)
I'm trying to figure out how to run integration tests that are storybased and where AJAX redirects appear during the 'story'.
Simple login example: I login to the website using
def user.logs_in(email, pwd)
get root_path
assert_response :success
assert_template 'index'
post :post, session_path, :email => email, :pwd => pwd
assert_response :redirect
assert_redirect_to backend_path
follow_redirect!
assert_response :success
assert_template 'index'
assert session[:user_id]
end
Unfortunately the login process is AJAX based and insted of a 301-redirect it returns
document.location.href = "<%= backend_path >";
which gives back a 200-code meaning that
assert_response :redirect
fails.
How can i handle AJAX redirects in my integration tests?
If there is no redirect, of course you won't be able to (successfully) test for it. So instead test for what you are doing: You want the response to be a 200 and the body to be a Javascript snippet. Or just use assert_template, too.
If you want to test if your Javascript-Login-Button actually evals the js body and how the browser reacts to that you would have to use something like Selenium.
Does anyone know how to make rspec follow a redirect (in a controller spec)? (e.g test/unit has follow_redirect!)
I have tried "follow_redirect!" and "follow_redirect" but only get
undefined method `follow_redirect!' for #<Spec::Rails::Example::ControllerExampleGroup::Subclass_1:0xb6df5294>
For example:
When I create an account the page is redirected to accounts page and my new account should be at the top of the list.
it "should create an account" do
post :create, :name => "My New Account"
FOLLOW_REDIRECT!
response.code.should == "200"
accounts = assigns[:accounts]
accounts[0].name.should == "My New Account"
end
But FOLLOW_REDIRECT! needs to be changed to something that actually works.
I think this is the default behavior for rspec-rails controller tests, in the sense that you can set an expectation on the response status and/or path, and test for success.
For example:
it "should create an account" do
post :create
response.code.should == "302"
response.should redirect_to(accounts_path)
end
You can access the redirect location with
response.headers['Location']
you could then request that directly.
If you want to test the redirect you are moving outside of the rspec-rails domain.
You can use Webrat or some other integration-test framework to test this.
The easiest way to solve this without resorting to integration testing is probably to mock out the method that is causing the redirect.
The spec is out of scope, if you want to follow a redirect use request spec, the equivalent of integration test in Test::Unit.
In request specs follow_redirect! works as well as in Test::Unit.
Or if you want to redirect inmediately use _via_redirect as suffix for the verb, example:
post_via_redirect :create, user: #user
Try to use integration/request tests. They are using web-like acces through routing to controllers.
For example:
I have for Rails 2 app in file /spec/integration/fps_spec.rb
require 'spec_helper'
describe "FinPoradci" do
it "POST /fps.html with params" do
fp_params={:accord_id => "FP99998", :under_acc => "OM001", :first_name => "Pavel", :last_name => "Novy"}
fp_test=FinPoradce.new(fp_params)
#after create follow redirection to show
post_via_redirect "/fps", {:fp => fp_params}
response.response_code.should == 200 # => :found , not 302 :created
new_fp=assigns(:fp)
new_fp.should_not be_empty
new_fp.errors.should be_empty
flash[:error].should be_empty
flash[:notice].should_not be_empty
response.should render_template(:show)
end
end
and it works. Until you want to send headers (for basic http authorization).
env={'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials(user,password)}
post_via_redirect "/fps", {:fp => fp_params}, env
is fine for create, but after redirection it returns 401 and needs new authorization.
SO I have to split it in 2 tests: creation and show on result of creation.
For RSpec / Capybara + Rails
response_headers['Location']
But it works only if there is no delay before redirect.
If it is there, then it's harder to follow the logic.