I converted an app from Rails 3.2 to 4.2 that was using the cancan gem. Googling around and checking on their github says it's enought to just replace gem cancan with gem cancancan without changing anything. This doesn't seem to work. My test fails:
Failures:
1) Redactable fields Manual redaction Redacting selected text in body
Failure/Error:
page.execute_script <<-EOS
document.getElementById('notice_#{#name}').focus();
document.getElementById('notice_#{#name}').select();
EOS
Capybara::Webkit::InvalidResponseError:
Javascript failed to execute
# /Users/siaw23/.rvm/gems/ruby-2.1.9/gems/capybara-webkit-1.11.1/lib/capybara/webkit/browser.rb:305:in `check'
# /Users/siaw23/.rvm/gems/ruby-2.1.9/gems/capybara-webkit-1.11.1/lib/capybara/webkit/browser.rb:211:in `command'
# /Users/siaw23/.rvm/gems/ruby-2.1.9/gems/capybara-webkit-1.11.1/lib/capybara/webkit/browser.rb:232:in `execute_script'
# /Users/siaw23/.rvm/gems/ruby-2.1.9/gems/capybara-webkit-1.11.1/lib/capybara/webkit/driver.rb:80:in `execute_script'
# /Users/siaw23/.rvm/gems/ruby-2.1.9/gems/capybara-2.7.1/lib/capybara/session.rb:524:in `execute_script'
# ./spec/support/page_objects/redactable_field_on_page.rb:15:in `select'
# ./spec/support/page_objects/redactable_field_on_page.rb:22:in `select_and_redact'
# ./spec/integration/redaction_spec.rb:37:in `block (4 levels) in <top (required)>'
2) Redactable fields Manual redaction Restoring body from original
Failure/Error: page.find("#notice_#{#name}").click
Capybara::ElementNotFound:
Unable to find css "#notice_body"
# /Users/siaw23/.rvm/gems/ruby-2.1.9/gems/capybara-2.7.1/lib/capybara/node/finders.rb:44:in `block in find'
# /Users/siaw23/.rvm/gems/ruby-2.1.9/gems/capybara-2.7.1/lib/capybara/node/base.rb:85:in `synchronize'
# /Users/siaw23/.rvm/gems/ruby-2.1.9/gems/capybara-2.7.1/lib/capybara/node/finders.rb:33:in `find'
# /Users/siaw23/.rvm/gems/ruby-2.1.9/gems/capybara-2.7.1/lib/capybara/session.rb:699:in `block (2 levels) in <class:Session>'
# ./spec/support/page_objects/redactable_field_on_page.rb:9:in `unredact'
# ./spec/integration/redaction_spec.rb:46:in `block (4 levels) in <top (required)>'
The elements being searched for can be reached manually after loggin in. This means neither cancancan nor cancan successfully applies :admin permission to my user that's created in the test. I used You are not authorized to access this page. and I get "You are not authorized to access this page."
The spec itself looks like this:
require 'rails_helper'
require 'support/notice_actions'
#lots of irrelevant code
private
def visit_redact_notice
user = create(:user, :admin)
visit '/users/sign_in'
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_on "Log in"
notice = create(:dmca, :redactable)
visit "/admin/notice/#{notice.id}/redact_notice"
notice
end
end
end
end
The visit_redact_notice method in the above spec is where everything is happening. I need help :). This is my repo if it might help: https://github.com/siaw23/lumendatabase
ability.rb
class Ability
include CanCan::Ability
def initialize(user)
return unless user
if user.has_role?(Role.submitter)
can :submit, Notice
end
if user.has_role?(Role.redactor)
grant_admin_access
grant_redact
end
if user.has_role?(Role.publisher)
grant_admin_access
grant_redact
can :publish, Notice
end
if user.has_role?(Role.admin)
grant_admin_access
grant_redact
can :edit, :all
cannot :edit, [User, Role]
can :publish, Notice
can :rescind, Notice
can :pdf_requests, :all
end
if user.has_role?(Role.super_admin)
can :manage, :all
end
if user.has_role?(Role.researcher)
can :read, Notice
end
end
def grant_admin_access
can :read, :all
can :access, :rails_admin
can :dashboard
can :search, Entity
can :access, :original_files
end
def grant_redact
can :edit, Notice
can :redact_notice, Notice
can :redact_queue, Notice
end
end
A couple issues you might want to check out...
There's a new version of Devise out that allows authentication via feature / integration tests. I've always spent hours debugging actually getting step-by-step login functionality to work. With Devise 4.2, logging in via integration tests is now supported. You'll have to make some changes to the RSpec configuration for Devise, as noted in the documentation above for the sign_in method to work.
If at this point logging in as an admin still does not work, I would check to see if your RSpec configuration has use_transactional_fixtures set to true. If it does, I would strongly encourage you to let the database_cleaner gem manage your test database, and set use_transactional_fixtures to false.
One or both of those should get you around your authentication issue. My guess is that it's most likely an issue with the transactional fixtures, as Rails is likely cleaning up a transaction before you're fully authenticated (thus why I'd suspect #1 above won't work).
Related
I'm trying to implement an rspec functional test to check that a user is signed in with devise. I've looked through my code again and again and tried different solutions but so far nothing has worked.
I've included what I believe are the relevant files. Let me know if anything is missing. Any recommendations are appreciated.
spec/rails_helper.rb
# This file is copied to spec/ when you run 'rails generate rspec:install'
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
# Prevent database truncation if the environment is production
abort("The Rails environment is running in production mode!") if Rails.env.production?
# Add additional requires below this line. Rails is not loaded until this point!
require 'spec_helper'
require 'rspec/rails'
# note: require 'devise' after require 'rspec/rails'
require 'devise'
# Requires supporting ruby files with custom matchers and macros, etc, in
# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
# run as spec files by default. This means that files in spec/support that end
# in _spec.rb will both be required and run as specs, causing the specs to be
# run twice. It is recommended that you do not name files matching this glob to
# end with _spec.rb. You can configure this pattern with the --pattern
# option on the command line or in ~/.rspec, .rspec or `.rspec-local`.
#
# The following line is provided for convenience purposes. It has the downside
# of increasing the boot-up time by auto-requiring all files in the support
# directory. Alternatively, in the individual `*_spec.rb` files, manually
# require only the support files necessary.
#
# Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
# Checks for pending migration and applies them before tests are run.
# If you are not using ActiveRecord, you can remove this line.
ActiveRecord::Migration.maintain_test_schema!
RSpec.configure do |config|
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
config.fixture_path = "#{::Rails.root}/spec/fixtures"
# If you're not using ActiveRecord, or you'd prefer not to run each of your
# examples within a transaction, remove the following line or assign false
# instead of true.
config.use_transactional_fixtures = true
# RSpec Rails can automatically mix in different behaviours to your tests
# based on their file location, for example enabling you to call `get` and
# `post` in specs under `spec/controllers`.
#
# You can disable this behaviour by removing the line below, and instead
# explicitly tag your specs with their type, e.g.:
#
# RSpec.describe UsersController, :type => :controller do
# # ...
# end
#
# The different available types are documented in the features, such as in
# https://relishapp.com/rspec/rspec-rails/docs
config.infer_spec_type_from_file_location!
# Filter lines from Rails gems in backtraces.
config.filter_rails_from_backtrace!
# arbitrary gems may also be filtered via:
# config.filter_gems_from_backtrace("gem name")
#Added for Devise
config.include Devise::Test::ControllerHelpers, :type => :controller
end
spec/controllers/users_controller_spec.rb
require 'rails_helper'
describe UsersController, :type => :controller do
before do
#user = User.create!(email: 'example#example.com', password: 'example')
end
# let(:user) { User.create!(email: 'example#example.com', password: 'example') }
describe 'GET #show' do
context "when user is logged in" do
before do
sign_in #user
end
it "loads correct user details" do
get :show, id: #user.id
expect(response).to have_http_status(200)
expect(assigns(:user)).to eq #user
end
end
context "when no user is logged in" do
it "redirects to login" do
get :show, id: #user.id
expect(response).to redirect_to(root_path)
end
end
end
end
And my feedback from the terminal after running bundle exec rpsec
Failures:
1) UsersController GET #show when user is logged in loads correct user details
Failure/Error: elsif user?
NoMethodError:
undefined method `user?' for #<Ability:0x007fa1d784cb98>
# ./app/models/ability.rb:9:in `initialize'
# /Users/Admin/.rvm/gems/ruby-2.3.0/gems/cancancan-1.15.0/lib/cancan/controller_additions.rb:361:in `new'
# /Users/Admin/.rvm/gems/ruby-2.3.0/gems/cancancan-1.15.0/lib/cancan/controller_additions.rb:361:in `current_ability'
# /Users/Admin/.rvm/gems/ruby-2.3.0/gems/cancancan-1.15.0/lib/cancan/controller_additions.rb:342:in `authorize!'
# /Users/Admin/.rvm/gems/ruby-2.3.0/gems/cancancan-1.15.0/lib/cancan/controller_resource.rb:49:in `authorize_resource'
# /Users/Admin/.rvm/gems/ruby-2.3.0/gems/cancancan-1.15.0/lib/cancan/controller_resource.rb:34:in `load_and_authorize_resource'
# /Users/Admin/.rvm/gems/ruby-2.3.0/gems/cancancan-1.15.0/lib/cancan/controller_resource.rb:10:in `block in add_before_action'
# /Users/Admin/.rvm/gems/ruby-2.3.0/gems/rails-controller-testing-1.0.1/lib/rails/controller/testing/template_assertions.rb:61:in `process'
# /Users/Admin/.rvm/gems/ruby-2.3.0/gems/devise-4.2.0/lib/devise/test/controller_helpers.rb:33:in `block in process'
# /Users/Admin/.rvm/gems/ruby-2.3.0/gems/devise-4.2.0/lib/devise/test/controller_helpers.rb:100:in `catch'
# /Users/Admin/.rvm/gems/ruby-2.3.0/gems/devise-4.2.0/lib/devise/test/controller_helpers.rb:100:in `_catch_warden'
# /Users/Admin/.rvm/gems/ruby-2.3.0/gems/devise-4.2.0/lib/devise/test/controller_helpers.rb:33:in `process'
# /Users/Admin/.rvm/gems/ruby-2.3.0/gems/rails-controller-testing-1.0.1/lib/rails/controller/testing/integration.rb:12:in `block (2 levels) in <module:Integration>'
# ./spec/controllers/users_controller_spec.rb:19:in `block (4 levels) in <top (required)>'
2) UsersController GET #show when no user is logged in redirects to login
Failure/Error: expect(response).to redirect_to(root_path)
Expected response to be a redirect to <http://test.host/> but was a redirect to <http://test.host/users/sign_in>.
Expected "http://test.host/" to be === "http://test.host/users/sign_in".
# ./spec/controllers/users_controller_spec.rb:29:in `block (4 levels) in <top (required)>'
Finished in 0.90214 seconds (files took 11.05 seconds to load)
6 examples, 2 failures
Failed examples:
rspec ./spec/controllers/users_controller_spec.rb:18 # UsersController GET #show when user is logged in loads correct user details
rspec ./spec/controllers/users_controller_spec.rb:27 # UsersController GET #show when no user is logged in redirects to login
A friend actually helped me figure it out. Apparently there's some issue that involves the seperate gem cancancan. I had to go into the models/ability.rb file and change a line in it.
In the original code the line if !user.admin? was just user?
So if you run into this same issue, that's the fix!
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new #guest user (not logged in)
# can :manage, User, id: user.id
if user.admin?
can :manage, :all
elsif !user.admin?
can :manage, User, id: user.id
else
can :read, :all
end
end
end
I have a helper for ensuring that a user has permission to view a page, and redirects if the user doesn't have permission:
module PermissionsHelper
def require_permission(permission_attribute_name)
return if current_or_guest_user.role.send(permission_attribute_name)
redirect_to current_or_guest_user.role.landing_page,
notice: 'You do not have sufficient permissions'
end
end
The method current_or_guest_user is a method I use in another helper that returns current user, or creates and returns a guest if there is no current user.
My spec looks like this:
require 'rails_helper'
RSpec.describe PermissionsHelper, type: :helper do
describe 'requiring permissions' do
let(:test_user) { create :customer }
it "redirects the user to the user's landing page if the user doesn't have permission" do
allow(helper).to receive(:current_or_guest_user) { test_user }
require_permission(:view_admins)
expect(response).to redirect_to test_user.landing_page
end
end
end
And I am getting this error:
PermissionsHelper
requiring permissions
redirects the user to the user's langing page if the user doesn't have permission (FAILED - 1)
Failures:
1) PermissionsHelper requiring permissions redirects the user to the user's langing page if the user doesn't have permission
Failure/Error: require_permission(:view_admins)
NameError:
undefined local variable or method `current_or_guest_user' for #<RSpec::ExampleGroups::PermissionsHelper::RequiringPermissions:0x007fb7b430afe8>
# /Users/user/.rvm/gems/ruby-2.2.2#project/gems/actionpack-4.2.1/lib/action_dispatch/testing/assertions/routing.rb:171:in `method_missing'
# /Users/user/.rvm/gems/ruby-2.2.2#project/gems/actionview-4.2.1/lib/action_view/test_case.rb:271:in `method_missing'
# ./app/helpers/permissions_helper.rb:3:in `require_permission'
# ./spec/helpers/permissions_helper_spec.rb:11:in `block (3 levels) in <top (required)>'
# /Users/user/.rvm/gems/ruby-2.2.2#project/gems/rspec-retry-0.4.0/lib/rspec/retry.rb:43:in `block (3 levels) in apply'
# /Users/user/.rvm/gems/ruby-2.2.2#project/gems/rspec-retry-0.4.0/lib/rspec/retry.rb:34:in `times'
# /Users/user/.rvm/gems/ruby-2.2.2#project/gems/rspec-retry-0.4.0/lib/rspec/retry.rb:34:in `block (2 levels) in apply'
Finished in 0.01188 seconds (files took 1.88 seconds to load)
1 example, 1 failure
Failed examples:
rspec ./spec/helpers/permissions_helper_spec.rb:9 # PermissionsHelper requiring permissions redirects the user to the user's langing page if the user doesn't have permission
EDIT: I tried changing allow to allow_any_instance_of and now I get this error:
With this spec:
it "redirects the user to the user's langing page if the user doesn't have permission" do
allow_any_instance_of(helper).to receive(:current_or_guest_user) { test_user }
require_permission(:view_admins)
expect(response).to redirect_to test_user.landing_page
end
I get:
Failure/Error: allow_any_instance_of(helper).to receive(:current_or_guest_user) { test_user }
NoMethodError:
undefined method `ancestors' for #<#<Class:0x007fe5d19a9a98>:0x007fe5d19a1668>
allow
will only work with static methods. Since the method in the helper is an instance method, you instead will want to use
allow_any_instance_of
https://www.relishapp.com/rspec/rspec-mocks/v/2-14/docs/message-expectations/allow-a-message-on-any-instance-of-a-class
EDIT:
The below snippet fails in one of my projects (using the 'helper' variable)
allow_any_instance_of(helper).to receive(:paginate).and_return('asdf')
However, using the full class name works fine (for the purpose of testing method mocking at least)
allow_any_instance_of(PaginationHelper).to receive(:paginate).and_return('asdf)
EDIT Read my comment to this question
I'm very new to rails, so please bear with me.
I've been trying to configure a test for Devise using factory girl and rspec. This has taken me the best part of 2 hours, and scouring half the internet to no avail. Even though there is loads of thread on what seems to be my issue, I just cant figure it out.
This is how my /spec files looks like.
GET Home Gives the correct status code
Failure/Error: sign_in user
NoMethodError:
undefined method `sign_in' for #<RSpec::Core::ExampleGroup::Nested_2:0x00000106f32558>
# ./spec/models/user_spec.rb:6:in `block (2 levels) in <top (required)>
This is the error message I get, trying to achieve the following test:
user_spec.rb:
require 'spec_helper'
describe "GET Home" do
before do
##I have tried all sorts of things here. I have also tried to define a module in devise.rb (see note below*), and then call that module here instead of the 2 lines below. But I get the same error, no local variable or undefined method for ...
user = FactoryGirl.create(:user)
sign_in user
end
describe "GET /Home"
it "Gives the correct status code" do
get root_path
response.status.should be(200)
end
end
in spec/factories/users.rb:
FactoryGirl.define do
factory :user do
name "Christoffer"
email "test#test2.com"
password "testtest"
password_confirmation "testtest"
end
end
And the folling lines is included in spec_helpers.rb
config.include FactoryGirl::Syntax::Methods
config.include Devise::TestHelpers, :type => :controller
Now, by doing this, i get the error above. Can anyone possibly explain what I'm doing wrong here? It might be something really obvious, as I'm not really that well rehearsed in the ways of Rails.
*Note (module I tried to define in devise.rb and insert in the before do):
module ValidUserRequestHelper
# Define a method which signs in as a valid user.
def sign_in_as_a_valid_user_nommels
# ASk factory girl to generate a valid user for us.
#user ||= FactoryGirl.create :user
# We action the login request using the parameters before we begin.
# The login requests will match these to the user we just created in the factory, and authenticate us.
post_via_redirect user_session_path, 'user[email]' => #user.email, 'user[password]' => #user.password
end
end
The purpose of 'spec/requests' is for integration tests. You would test features of your app from the user's perspective (ie. fill in certain info, then click button, then so and so should happen if certain inputs are valid or invalid). Spec/models and spec/controllers are usually for unit tests where you test for smaller parts of your app (ie. what happens if the password and password_confirmation params passed to your user model don't match)
Loosely following Ryan Bates's How I Test Railscast to implement sending an email confirmation token to users when they sign up.
class User < ActiveRecord::Base
has_secure_password
strip_attributes except: [:password, :password_confirmation]
...
def send_email_confirmation
generate_token(:email_token)
self.email_token_sent_at = Time.zone.now
save!
UserMailer.email_confirmation(self).deliver
end
private
def generate_token(column)
begin
self[column] = SecureRandom.urlsafe_base64
end while User.exists?(column => self[column])
end
end
This is failing (in feature specs and when manually clicking through sign-up process) with:
Failures:
1) UserPages sign up with valid information should create a user
Failure/Error: expect { click_button submit }.to change(User, :count).by(1)
BCrypt::Errors::InvalidSalt:
invalid salt
# ./app/models/user.rb:70:in `send_email_confirmation'
# ./app/controllers/users_controller.rb:27:in `create'
# ./spec/features/user_pages_spec.rb:165:in `block (5 levels) in <top (required)>'
# ./spec/features/user_pages_spec.rb:165:in `block (4 levels) in <top (required)>'
I've tried reinstalling bcrypt gem (as suggested elsewhere even though it was Devise related and I'm not using Devise): gem uninstall bcrypt-ruby and then gem install bcrypt-ruby but to no avail. ideas?
Gem strip_attributes is responsible for such behavior.
Solution
Exclude :password_digest attribute from stripping like below:
strip_attributes except: [:password_digest]
We hit this error and it was because we were trying to perform a write operation in a k8s pod that only had read access to the database.
I'm sure this isn't the answer to the original question, but I came across this question when we were trying to figure out what was causing this error for us so I wanted to share.
I set up a controller which handles omniauth authentications which are worked into a custom built authentication system. i am trying to test the logic for how authentications are handled (ex: if user already has/does not have account, if user is/isn't currently logged in, etc.). as such i have a Authorization model and a authorizations controller. The action to create a authorization has this general outline:
class AuthorizationsController < ApplicationController
def create
omniauth = request.env['omniauth.auth']
authorization = Authorization.find_by_provider_and_uid(omniauth['provider'], omniauth['uid'])
if authorization
# Authorization already established, log in user
elsif current_user
# User is logged in but wants to add another omniauth authentication
else
# Create user and associate them with omniauth authentication
end
end
end
I am trying to test this logic in Rspec but have been having issues. Heres is what I am working with in my spec:
describe AuthorizationsController do
render_views
describe "POST 'create'" do
describe "with an already existing authorization" do
it "should log the user in" do
#authmock = mock_model(Authorization)
Authorization.should_receive(:find_by_provider_and_uid).and_return(#authmock)
post :create, :provider => 'twitter'
current_user?(#authmock.user).should == true
response.should redirect_to(root_path)
end
end
end
end
I am under the impression that this should assign my mocked Authorization model (#authmock) to the local variable authorization in my controller when the assignment call is made, thus making 'if authorization' return true. However whenever I true to run this spec I get this error:
Failures:
1) AuthorizationsController POST 'create' with an already existing authorization should log the user in
Failure/Error: post :create, :provider => 'twitter'
NoMethodError:
You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.[]
# ./app/controllers/authorizations_controller.rb:5:in `create'
# ./spec/controllers/authorizations_controller_spec.rb:16:in `block (4 levels) in <top (required)>'
Can anyone enlighten me as to what I am doing wrong here?
Edit:
since the question was raised as to whether or not the assignment of omniauth was causing issues, I commented out that line to see what would happen and got the following error:
1) AuthorizationsController POST 'create' with an already existing authorization should log the user in
Failure/Error: post :create, :provider => 'twitter'
NameError:
undefined local variable or method `omniauth' for #<AuthorizationsController:0xb41809c>
# ./app/controllers/authorizations_controller.rb:5:in `create'
# ./spec/controllers/authorizations_controller_spec.rb:16:in `block (4 levels) in <top (required)>'
which tells me that the problem is with the mock or stub as the find_by_provider_and_uid function is still being evaluated and is not stubbed when the test runs
Are you specing
current_user?(#authmock.user).should == true
or
response.should redirect_to(root_path)
I think that first expectation should not be tested here, because you've mocked 'if authorization' block, so you should spec what happens then!