Rails integration test against page modification hack? - ruby-on-rails-3

I'm using Capybara 1.1.2, Rails 3.1.3, rspec-rails 2.9.0, and Ruby 1.9.3p0.
Assume an app with standard and account_admin users. A standard user can create another standard user, but a standard user cannot create an account_admin user.
Of course the UI does not give the standard user the option of creating an account admin. But 30 seconds with Firebug and the user can re-write the HTML so it submits a POST request to create an account_admin.
How do I test that my app prevents this kind of simple hack?
The normal standard user test looks like this:
context "when standard user is signed in" do
before do
login_as standard_user
visit users_path # go to index
click_link('Add user') # click link like user would
end
describe "when fields are filled in" do
let(:new_email) { "new_user#example.com" }
before do
fill_in "Email", with: new_email
fill_in "Password", with: "password"
fill_in "Password confirmation", with: "password"
choose "Standard user" # radio button for Role
end
it "should create a user" do
expect { click_button submit }.to change(User, :count).by(1)
end
end
end
Is there a way to "fool" the test into taking a value not allowed on the form? I tried treating the radio button like a text field, but Capybara rejects it as a non-existent field:
fill_in "Role", with: "account_admin" # doesn't work
Direct modification of the params hash doesn't work either:
params[:role] = "account_admin" # doesn't work
Do I have to write this more like a controller test, with a direct call to post :create?

Capybara author jnicklas confirmed here that Capybara cannot make an app do things that are not available from the UI. He recommends controller tests for authorization.
However request specs written in RSpec without using Capybara syntax do allow direct use of HTML verbs (and some additional helpers) as outlined in the RSpec and Rails docs. So rather than Capybara's fill_in and click_link directives and the page object, you can use an attribute hash, verbs like get, post, post_via_redirect, and the response.body object. It's similar to a controller test, but you're using Rails' routing to choose the appropriate controller action based on the path provided. Here is an example of the latter technique:
describe "when standard user attempts to create account_admin user" do
let(:standard_user) { FactoryGirl.create(:standard_user) }
let(:attr) { { email: "account_admin#example.com",
password: "password",
password_confirmation: "password",
role: "account_admin" }
}
before do
login_as standard_user
get new_user_path
end
it "should not create a account_admin user" do
lambda do
post users_path, user: attr
end.should_not change(User, :count)
end
describe "after user posts invalid create" do
before { post_via_redirect users_path, user: attr }
# redirect to user's profile page
it { response.body.should have_selector('title', text: 'User Profile') }
it { response.body.should have_selector('div.alert.alert-error', text: 'not authorized') }
end
end

Related

Why is this FactoryGirl-created object visible in the test, but not in the view that is being tested?

I'm using RSpec, FactoryGirl, and PhantomJS.
UPDATE:
I have verified that if I create this item in my spec/support/login_macros.rb login_user method, which I call below, the listing object is available in the view.
This seems like a FactoryGirl issue. Why can I create from a factory in that helper method, but can't inside the test itself? Here is the support method:
module LoginMacros
def login_user(admin = false)
myrepo = FactoryGirl.create(:repository, :myname)
plan = FactoryGirl.create(:plan, name: "Test Multi", listing_limit: 5000, repositories_allowed: 5)
user = FactoryGirl.create(:user)
subscription = FactoryGirl.create(:subscription, user: user, plan: plan)
user.add_repository(myrepo)
listing = FactoryGirl.create(:listing, user: user, repository: myrepo)
visit login_path
fill_in 'Email', :with => user.email
fill_in 'Password', :with => user.password
click_button 'Go'
user
end
I have set transactional_fixtures to false in my spec_helper.rb. Here is my spec:
require "spec_helper"
describe "App Integration" do
let!(:user) { login_user }
let!(:myrepo) { user.repositories.where(name: "myrepo" ).first }
it "lets a user add apps to a listing", js: true do
listing = FactoryGirl.create(:listing, user: user, repository: myrepo)
puts listing.inspect
Capybara::Screenshot.screenshot_and_open_image
end
end
Now here is the problem. See that puts line above? It prints out the object.
But in the screenshot, it's as if the object were never created. Like magic!
And yet, both the User and the Repository objects are visible in the view.
Also, I can go to a different view and see that Listing object. Just not on the main page of my application!
Why would that object be visible in one view and not the other? I'm just doing this on the main page:
<h3><%= Listing.count %></h3>
And it is always, always, always zero. Makes zero sense.
It doesn't look like you're ever calling visit inside of your test, so the page would always be blank. You need to call visit root_path # (or whatever path that heading is on) before saving the screenshot.
let! blocks are invoked before it blocks. Since you're viewing the page in a let! block but creating the listing in the it block, the listing isn't created until after you've viewed the page.

How do you get rspec to output what it encountered rather than it "didn't find what it expected"?

I have been struggling to using ruby/rspec/capybara/devise to test my code. A simple test I am trying to write is for signing in. I have a valid user sign in and expect to see an h1 tag as defined in the following code:
describe "Authentication" do
subject { page }
describe "with valid information" do
let(:user) { FactoryGirl.create(:user) }
before { sign_in_with user.email }
it { should have_css('h1', text: "Welcome to the Test") }
end
end
Problem is that I get this in return:
1) Authentication signin page with valid information
Failure/Error: it { should have_css('h1', text: "Welcome to the Test") }
expected css "h1" with text "Welcome to the Test" to return something
# ./spec/requests/authentication_pages_spec.rb:34:in `block (4 levels) in <top (required)>'
Is there a way to output what the test found in the h1 (or that it didn't find it at all?) instead of it didn't find what it expected? There is a good chance my sign_in method is not working, but I can't validate that because I'm not sure what the test sees after sign_in_with executes.
Thanks and happy to provide more context if it's helpful.
EDIT
Updated code to reflect subject of tests.
... I'm not sure what the test sees after sign_in_with executes.
You can open a snapshot of the current page with save_and_open_page:
describe "with valid information" do
let(:user) { FactoryGirl.create(:user) }
before { sign_in_with user.email }
it { save_and_open_page; should have_css('h1', text: "Welcome to the Test") }
end
There is no subject, you can't use it to represent result. For Capybrara, you need to check the page object returned.
Let's rewrite the test like this:
describe "with valid information" do
let(:user) { FactoryGirl.create(:user) }
before do
sign_in_with user.email
end
it "signs in successfully" do
expect(page).to have_css('h1', text: "Welcome to the Test")
end
end
Or better, with Capybara story DSL
feature "User sign in" do
given(:user) { FactoryGirl.create(:user) }
scenario "with valid information " do
sign_in_with user.email
expect(page).to have_css('h1', text: "Welcome to the Test")
end
scenario "with invalid filing" do
sign_in_with "foo#bar.com"
expect(page).to have_text("Invalid email or password")
end
end

Devise & Rspec: User requires account activation in requests spec even after confirmed

I have the following code in my requests spec:
describe 'Poll' do
subject { page }
context 'as system admin' do
let(:user) { Fabricate(:system_admin) }
before { login user}
it 'is accessible' do
visit '/admin/poll'
current_path.should == '/admin/poll'
end
describe 'sending poll' do
it 'sends to all users' do
save_and_open_page
end
end
end
end
The login user doesn't seem to work even if the method seems to be working fine. I tried using login user inside the it 'is accessible' do block and that specs works fine if I do it that way. If I remove it from there and put it in a before block like above. The user doesn't stay signed in. I put in a save_and_open_page to debug and I get this notification in the page:
Your account was not activated yet. If a reset password link was sent to you, use that link to change your password.
I'm using Devise, RSpec, Capybara and Rails 3. I've also set user to confirm! in my Fabrication file. Below is how it looks:
Fabricator(:system_admin) do
first_name { sequence(:first_name) { |n| "Person#{n}"} }
last_name { sequence(:last_name) {|n| "#{n}" } }
email { sequence(:email) { |n| "person_#{n}#example.com"} }
password "foobar"
password_confirmation "foobar"
company_name { sequence(:company_name) { |n| "google#{n}" } }
role "system_admin"
after_create do |user|
user.confirm!
user.create_company
end
end
Question: What could be the problem? How come the user isn't staying logged in and why do I get that message saying that I should activate my account? Isn't user.confirm! enough?
could this be the problem?
Fabricate(:system_admin) != Fabricator(:system_admin)
So if you debug your save_and_open_page and it tells you that the account is not activated it seemes your fabricate is not working properly. have you tried and debug that?
what does your save_and_open_page do? does it try to use the user for something? because I have experienced when defined with a let, if not touched the variable(user in this case) then it does no exist on that context. besides. whats the error when you run the specs like this on it "is acessible"? just says there is no user logged in?
so you can either stub your methods for login(for example if you have method called current_user that gives you the logged in user or something) or instead of using let, instiate like:
user = Fabricate(:system_admin)
but hey there is a lot of good advices here:
http://betterspecs.org/
it seems like your blocks context and describe are too complex. I am also not following this guidelines 100% but I think I should and you too would benefit from this.
let me know if you found out another reason why its not working!
I think before(:each) should resolve the problem
Add this Devise method:
confirmed_at { Time.now }
So your after_create method should looks like:
after_create do |user|
user.confirm!
user.confirmed_at { Time.now }
user.create_company
end

Handling complex post-login redirection logic in Rails

I have a rails app that needs to redirect users to different pages based on some criteria after they log in (using Devise & OmniAuth). This logic could be pseudo-coded like this:
if the user is an admin
if no url was specified before login (original_uri)
- redirect to admin panel
else
- redirect to original_uri
else
if the user filled up his profile data
if no url was specified before login
- redirect to user's home page
else
if original_uri is allowed (not restricted to that user)
- redirect to original_uri
else
- redirect to user's home page
else
- redirect to profile page
or as an rspec integration example:
describe "complex routing" do
context "user is an admin" do
let(:user) { create(:admin) }
context "an original URL was specified before login" do
it "redirects to the original URL"
end
context "no original URL was specified" do
it "redirects to the admin panel"
end
end
context "user is not an admin" do
let(:user) { create(:user, :completed_profile => false) }
context "with complete profile info" do
before(:each) { user.completed_profile = true }
context "an original URL was specified before login" do
it "redirects to original URL if not restricted"
it "redirects to home page if URL is restricted"
end
context "no original URL was specified" do
it "redirects to home page"
end
end
context "with incomplete profile" do
it "redirects to profile page"
end
end
end
As can be seen, this gets quite complex and not very obvious (or easy to test). In addition, the thought of this sitting in a before_filter :decide_routing as a method call makes me cringe.
What would be a good way to abstract this and make this cleaner, testable and simpler to manage in the future (in case more logic needs to be added or changed)?
Any thoughts will be great - thanks.
Does this does it?
class ApplicationController < ActionController::Base
protect_from_forgery
before_filter :authenticate_user!
protected
def after_sign_in_path_for(resource)
# if user is not active, redirect him so he completes registration
if resource.active?
super
else
# complete_the_effing_registration_path
end
end
end

Authorization spec not working for put request

Im on chp 9 of Michael Hartls Ruby On Rails Tutorial. In my authentication_pages_specs.rb the 'submitting to the update action' is causing the spec to crash with the error "undefined local variable or method `user' for # (NameError)"
require 'spec_helper'
describe "Authentication" do
subject { page }
describe "authorization" do
describe "for non signed-in users" do
let(:user) { FactoryGirl.create(:user) }
describe "in the users controller" do
describe "visiting the edit page" do
before { visit edit_user_path(user)}
it { should have_selector('title', text: 'Sign in')}
end
describe "submitting to the update action" do
describe { put user_path(user)} #Error on this line
specify { response.should redirect_to(signin_path) }
end
end
end
end
end
Whats cauing the spec to crash ?
Thank You
Putting in before block the put part fixes the problem