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) { "[email protected]" }
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: "[email protected]",
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