Testing Email Confirmation Implementation in Devise
This test suite validates the email confirmation functionality in Devise, covering user registration confirmation flows and email change verification processes. It ensures robust handling of confirmation tokens, expiration periods, and various edge cases in authentication workflows.
Test Coverage Overview
Implementation Analysis
Technical Details
Best Practices Demonstrated
heartcombo/devise
test/integration/confirmable_test.rb
# frozen_string_literal: true
require 'test_helper'
class ConfirmationTest < Devise::IntegrationTest
def visit_user_confirmation_with_token(confirmation_token)
visit user_confirmation_path(confirmation_token: confirmation_token)
end
def resend_confirmation
user = create_user(confirm: false)
ActionMailer::Base.deliveries.clear
visit new_user_session_path
click_link "Didn't receive confirmation instructions?"
fill_in 'email', with: user.email
click_button 'Resend confirmation instructions'
end
test 'user should be able to request a new confirmation' do
resend_confirmation
assert_current_url '/users/sign_in'
assert_contain 'You will receive an email with instructions for how to confirm your email address in a few minutes'
assert_equal 1, ActionMailer::Base.deliveries.size
assert_equal ['[email protected]'], ActionMailer::Base.deliveries.first.from
end
test 'user should receive a confirmation from a custom mailer' do
User.any_instance.stubs(:devise_mailer).returns(Users::Mailer)
resend_confirmation
assert_equal ['[email protected]'], ActionMailer::Base.deliveries.first.from
end
test 'user with invalid confirmation token should not be able to confirm an account' do
visit_user_confirmation_with_token('invalid_confirmation')
assert_have_selector '#error_explanation'
assert_contain %r{Confirmation token(.*)invalid}
end
test 'user with valid confirmation token should not be able to confirm an account after the token has expired' do
swap Devise, confirm_within: 3.days do
user = create_user(confirm: false, confirmation_sent_at: 4.days.ago)
assert_not user.confirmed?
visit_user_confirmation_with_token(user.raw_confirmation_token)
assert_have_selector '#error_explanation'
assert_contain %r{needs to be confirmed within 3 days}
assert_not user.reload.confirmed?
assert_current_url "/users/confirmation?confirmation_token=#{user.raw_confirmation_token}"
end
end
test 'user with valid confirmation token where the token has expired and with application router_name set to a different engine it should raise an error' do
user = create_user(confirm: false, confirmation_sent_at: 4.days.ago)
swap Devise, confirm_within: 3.days, router_name: :fake_engine do
assert_raise ActionView::Template::Error do
visit_user_confirmation_with_token(user.raw_confirmation_token)
end
end
end
test 'user with valid confirmation token where the token has expired and with application router_name set to a different engine and route overrides back to main it shows the path' do
user = create_user(confirm: false, confirmation_sent_at: 4.days.ago)
swap Devise, confirm_within: 3.days, router_name: :fake_engine do
visit user_on_main_app_confirmation_path(confirmation_token: user.raw_confirmation_token)
assert_current_url "/user_on_main_apps/confirmation?confirmation_token=#{user.raw_confirmation_token}"
end
end
test 'user with valid confirmation token where the token has expired with router overrides different engine it shows the path' do
user = create_user(confirm: false, confirmation_sent_at: 4.days.ago)
swap Devise, confirm_within: 3.days do
visit user_on_engine_confirmation_path(confirmation_token: user.raw_confirmation_token)
assert_current_url "/user_on_engines/confirmation?confirmation_token=#{user.raw_confirmation_token}"
end
end
test 'user with valid confirmation token should be able to confirm an account before the token has expired' do
swap Devise, confirm_within: 3.days do
user = create_user(confirm: false, confirmation_sent_at: 2.days.ago)
assert_not user.confirmed?
visit_user_confirmation_with_token(user.raw_confirmation_token)
assert_contain 'Your email address has been successfully confirmed.'
assert_current_url '/users/sign_in'
assert user.reload.confirmed?
end
end
test 'user should be redirected to a custom path after confirmation' do
Devise::ConfirmationsController.any_instance.stubs(:after_confirmation_path_for).returns("/?custom=1")
user = create_user(confirm: false)
visit_user_confirmation_with_token(user.raw_confirmation_token)
assert_current_url "/?custom=1"
end
test 'already confirmed user should not be able to confirm the account again' do
user = create_user(confirm: false)
user.confirmed_at = Time.now
user.save
visit_user_confirmation_with_token(user.raw_confirmation_token)
assert_have_selector '#error_explanation'
assert_contain 'already confirmed'
end
test 'already confirmed user should not be able to confirm the account again neither request confirmation' do
user = create_user(confirm: false)
user.confirmed_at = Time.now
user.save
visit_user_confirmation_with_token(user.raw_confirmation_token)
assert_contain 'already confirmed'
fill_in 'email', with: user.email
click_button 'Resend confirmation instructions'
assert_contain 'already confirmed'
end
test 'not confirmed user with setup to block without confirmation should not be able to sign in' do
swap Devise, allow_unconfirmed_access_for: 0.days do
sign_in_as_user(confirm: false)
assert_contain 'You have to confirm your email address before continuing'
assert_not warden.authenticated?(:user)
end
end
test 'not confirmed user should not see confirmation message if invalid credentials are given' do
swap Devise, allow_unconfirmed_access_for: 0.days do
sign_in_as_user(confirm: false) do
fill_in 'password', with: 'invalid'
end
assert_contain 'Invalid Email or password'
assert_not warden.authenticated?(:user)
end
end
test 'not confirmed user but configured with some days to confirm should be able to sign in' do
swap Devise, allow_unconfirmed_access_for: 1.day do
sign_in_as_user(confirm: false)
assert_response :success
assert warden.authenticated?(:user)
end
end
test 'unconfirmed but signed in user should be redirected to their root path' do
swap Devise, allow_unconfirmed_access_for: 1.day do
user = sign_in_as_user(confirm: false)
visit_user_confirmation_with_token(user.raw_confirmation_token)
assert_contain 'Your email address has been successfully confirmed.'
assert_current_url '/'
end
end
test 'user should be redirected to sign in page whenever signed in as another resource at same session already' do
sign_in_as_admin
user = create_user(confirm: false)
visit_user_confirmation_with_token(user.raw_confirmation_token)
assert_current_url '/users/sign_in'
end
test "should not be able to confirm an email with a blank confirmation token" do
visit_user_confirmation_with_token("")
assert_contain %r{Confirmation token can['’]t be blank}
end
test "should not be able to confirm an email with a nil confirmation token" do
visit_user_confirmation_with_token(nil)
assert_contain %r{Confirmation token can['’]t be blank}
end
test "should not be able to confirm user with blank confirmation token" do
user = create_user(confirm: false)
user.update_attribute(:confirmation_token, "")
visit_user_confirmation_with_token("")
assert_contain %r{Confirmation token can['’]t be blank}
end
test "should not be able to confirm user with nil confirmation token" do
user = create_user(confirm: false)
user.update_attribute(:confirmation_token, nil)
visit_user_confirmation_with_token(nil)
assert_contain %r{Confirmation token can['’]t be blank}
end
test 'error message is configurable by resource name' do
store_translations :en, devise: {
failure: { user: { unconfirmed: "Not confirmed user" } }
} do
sign_in_as_user(confirm: false)
assert_contain 'Not confirmed user'
end
end
test 'resent confirmation token with valid e-mail in JSON format should return empty and valid response' do
user = create_user(confirm: false)
post user_confirmation_path(format: 'json'), params: { user: { email: user.email } }
assert_response :success
assert_equal({}.to_json, response.body)
end
test 'resent confirmation token with invalid e-mail in JSON format should return invalid response' do
create_user(confirm: false)
post user_confirmation_path(format: 'json'), params: { user: { email: '[email protected]' } }
assert_response :unprocessable_entity
assert_includes response.body, '{"errors":{'
end
test 'confirm account with valid confirmation token in JSON format should return valid response' do
user = create_user(confirm: false)
get user_confirmation_path(confirmation_token: user.raw_confirmation_token, format: 'json')
assert_response :success
assert_includes response.body, '{"user":{'
end
test 'confirm account with invalid confirmation token in JSON format should return invalid response' do
create_user(confirm: false)
get user_confirmation_path(confirmation_token: 'invalid_confirmation', format: 'json')
assert_response :unprocessable_entity
assert_includes response.body, '{"confirmation_token":['
end
test "when in paranoid mode and with a valid e-mail, should not say that the e-mail is valid" do
swap Devise, paranoid: true do
user = create_user(confirm: false)
visit new_user_session_path
click_link "Didn't receive confirmation instructions?"
fill_in 'email', with: user.email
click_button 'Resend confirmation instructions'
assert_contain "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes."
assert_current_url "/users/sign_in"
end
end
test "when in paranoid mode and with a invalid e-mail, should not say that the e-mail is invalid" do
swap Devise, paranoid: true do
visit new_user_session_path
click_link "Didn't receive confirmation instructions?"
fill_in 'email', with: "[email protected]"
click_button 'Resend confirmation instructions'
assert_not_contain "1 error prohibited this user from being saved:"
assert_not_contain "Email not found"
assert_contain "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes."
assert_current_url "/users/sign_in"
end
end
end
class ConfirmationOnChangeTest < Devise::IntegrationTest
def create_second_admin(options = {})
@admin = nil
create_admin(options)
end
def visit_admin_confirmation_with_token(confirmation_token)
visit admin_confirmation_path(confirmation_token: confirmation_token)
end
test 'admin should be able to request a new confirmation after email changed' do
admin = create_admin
admin.update(email: '[email protected]')
visit new_admin_session_path
click_link "Didn't receive confirmation instructions?"
fill_in 'email', with: admin.unconfirmed_email
assert_difference "ActionMailer::Base.deliveries.size" do
click_button 'Resend confirmation instructions'
end
assert_current_url '/admin_area/sign_in'
assert_contain 'You will receive an email with instructions for how to confirm your email address in a few minutes'
end
test 'admin with valid confirmation token should be able to confirm email after email changed' do
admin = create_admin
admin.update(email: '[email protected]')
assert_equal '[email protected]', admin.unconfirmed_email
visit_admin_confirmation_with_token(admin.raw_confirmation_token)
assert_contain 'Your email address has been successfully confirmed.'
assert_current_url '/admin_area/sign_in'
assert admin.reload.confirmed?
assert_not admin.reload.pending_reconfirmation?
end
test 'admin with previously valid confirmation token should not be able to confirm email after email changed again' do
admin = create_admin
admin.update(email: '[email protected]')
assert_equal '[email protected]', admin.unconfirmed_email
raw_confirmation_token = admin.raw_confirmation_token
admin = Admin.find(admin.id)
admin.update(email: '[email protected]')
assert_equal '[email protected]', admin.unconfirmed_email
visit_admin_confirmation_with_token(raw_confirmation_token)
assert_have_selector '#error_explanation'
assert_contain(/Confirmation token(.*)invalid/)
visit_admin_confirmation_with_token(admin.raw_confirmation_token)
assert_contain 'Your email address has been successfully confirmed.'
assert_current_url '/admin_area/sign_in'
assert admin.reload.confirmed?
assert_not admin.reload.pending_reconfirmation?
end
test 'admin email should be unique also within unconfirmed_email' do
admin = create_admin
admin.update(email: '[email protected]')
assert_equal '[email protected]', admin.unconfirmed_email
create_second_admin(email: "[email protected]")
visit_admin_confirmation_with_token(admin.raw_confirmation_token)
assert_have_selector '#error_explanation'
assert_contain(/Email.*already.*taken/)
assert admin.reload.pending_reconfirmation?
end
end