Back to Repositories

Testing OmniAuth Authentication Workflows in Devise

This integration test suite validates OmniAuth authentication functionality in Devise, focusing on Facebook authentication implementation. It covers session management, user authentication flows, and error handling scenarios for OAuth integration.

Test Coverage Overview

The test suite provides comprehensive coverage of OmniAuth integration with Devise, specifically for Facebook authentication.

Key areas tested include:
  • OAuth authentication flow validation
  • Session management and cleanup
  • User sign-in and registration processes
  • Remember token functionality
  • Error handling scenarios
  • Request method validation

Implementation Analysis

The testing approach uses Devise’s IntegrationTest framework with mock OmniAuth configurations for Facebook authentication. It implements stub actions to test different authentication scenarios and validates both successful and error paths.

Notable patterns include:
  • OmniAuth test mode configuration
  • Controller action stubbing
  • Session state verification
  • Authentication flow validation

Technical Details

Testing tools and configuration:
  • Minitest framework
  • Capybara for integration testing
  • OmniAuth test mode
  • Mock authentication data
  • Custom controller action aliases
  • Session management verification

Best Practices Demonstrated

The test suite exemplifies high-quality testing practices through comprehensive setup and teardown procedures, isolation of test scenarios, and thorough validation of edge cases.

Notable practices include:
  • Proper test isolation
  • Comprehensive error handling
  • Mock data management
  • Session state verification
  • Authentication flow validation

heartcombo/devise

test/integration/omniauthable_test.rb

            
# frozen_string_literal: true

require 'test_helper'


class OmniauthableIntegrationTest < Devise::IntegrationTest
  FACEBOOK_INFO = {
    "id" => '12345',
    "link" => 'http://facebook.com/josevalim',
    "email" => '[email protected]',
    "first_name" => 'Jose',
    "last_name" => 'Valim',
    "website" => 'http://blog.plataformatec.com.br'
  }

  setup do
    OmniAuth.config.test_mode = true
    OmniAuth.config.mock_auth[:facebook] = {
      "uid" => '12345',
      "provider" => 'facebook',
      "user_info" => {"nickname" => 'josevalim'},
      "credentials" => {"token" => 'plataformatec'},
      "extra" => {"user_hash" => FACEBOOK_INFO}
    }
    OmniAuth.config.add_camelization 'facebook', 'FaceBook'
    if OmniAuth.config.respond_to?(:request_validation_phase)
      OmniAuth.config.request_validation_phase = ->(env) {}
    end
  end

  teardown do
    OmniAuth.config.camelizations.delete('facebook')
    OmniAuth.config.test_mode = false
  end

  def stub_action!(name)
    Users::OmniauthCallbacksController.class_eval do
      alias_method :__old_facebook, :facebook
      alias_method :facebook, name
    end
    yield
  ensure
    Users::OmniauthCallbacksController.class_eval do
      alias_method :facebook, :__old_facebook
    end
  end

  test "omniauth sign in should not run model validations" do
    stub_action!(:sign_in_facebook) do
      create_user
      post "/users/auth/facebook"
      follow_redirect!
      assert warden.authenticated?(:user)

      assert_not User.validations_performed
    end
  end

  test "can access omniauth.auth in the env hash" do
    post "/users/auth/facebook"
    follow_redirect!

    json = ActiveSupport::JSON.decode(response.body)

    assert_equal "12345",         json["uid"]
    assert_equal "facebook",      json["provider"]
    assert_equal "josevalim",     json["user_info"]["nickname"]
    assert_equal FACEBOOK_INFO,   json["extra"]["user_hash"]
    assert_equal "plataformatec", json["credentials"]["token"]
  end

  test "cleans up session on sign up" do
    assert_no_difference "User.count" do
      post "/users/auth/facebook"
      follow_redirect!
    end

    assert session["devise.facebook_data"]

    assert_difference "User.count" do
      visit "/users/sign_up"
      fill_in "Password", with: "12345678"
      fill_in "Password confirmation", with: "12345678"
      click_button "Sign up"
    end

    assert_current_url "/"
    assert_contain "You have signed up successfully."
    assert_contain "Hello User [email protected]"
    assert_not session["devise.facebook_data"]
  end

  test "cleans up session on cancel" do
    assert_no_difference "User.count" do
      post "/users/auth/facebook"
      follow_redirect!
    end

    assert session["devise.facebook_data"]
    visit "/users/cancel"
    assert_not session["devise.facebook_data"]
  end

  test "cleans up session on sign in" do
    assert_no_difference "User.count" do
      post "/users/auth/facebook"
      follow_redirect!
    end

    assert session["devise.facebook_data"]
    sign_in_as_user
    assert_not session["devise.facebook_data"]
  end

  test "sign in and send remember token if configured" do
    post "/users/auth/facebook"
    follow_redirect!
    assert_nil warden.cookies["remember_user_token"]

    stub_action!(:sign_in_facebook) do
      create_user
      post "/users/auth/facebook"
      follow_redirect!
      assert warden.authenticated?(:user)
      assert warden.cookies["remember_user_token"]
    end
  end

  test "authorization path via GET when Omniauth allowed_request_methods includes GET" do
    original_allowed = OmniAuth.config.allowed_request_methods
    OmniAuth.config.allowed_request_methods = [:get, :post]

    get "/users/auth/facebook"

    assert_response(:redirect)
  ensure
    OmniAuth.config.allowed_request_methods = original_allowed
  end

  test "authorization path via GET when Omniauth allowed_request_methods doesn't include GET" do
    original_allowed = OmniAuth.config.allowed_request_methods
    OmniAuth.config.allowed_request_methods = [:post]

    assert_raises(ActionController::RoutingError) do
      get "/users/auth/facebook"
    end
  ensure
    OmniAuth.config.allowed_request_methods = original_allowed
  end

  test "generates a link to authenticate with provider" do
    visit "/users/sign_in"
    assert_select "form[action=?][method=post]", "/users/auth/facebook" do
      assert_select "input[type=submit][value=?]", "Sign in with FaceBook"
    end
  end

  test "generates a proper link when SCRIPT_NAME is set" do
    header 'SCRIPT_NAME', '/q'
    visit "/users/sign_in"
    assert_select "form[action=?][method=post]", "/q/users/auth/facebook" do
      assert_select "input[type=submit][value=?]", "Sign in with FaceBook"
    end
  end

  test "handles callback error parameter according to the specification" do
    OmniAuth.config.mock_auth[:facebook] = :access_denied
    visit "/users/auth/facebook/callback?error=access_denied"
    assert_current_url "/users/sign_in"
    assert_contain 'Could not authenticate you from FaceBook because "Access denied".'
  end

  test "handles other exceptions from OmniAuth" do
    OmniAuth.config.mock_auth[:facebook] = :invalid_credentials

    post "/users/auth/facebook"
    follow_redirect!
    follow_redirect!

    assert_contain 'Could not authenticate you from FaceBook because "Invalid credentials".'
  end
end