Search code examples
ruby-on-rails-3capybararspec-rails

Rails integration test against page modification hack?


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?


Solution

  • 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