# frozen_string_literal: true

require 'test_helper'

class AuthenticationSanityTest < Devise::IntegrationTest
  test 'sign in should not run model validations' do
    sign_in_as_user

    assert_not User.validations_performed
  end

  test 'home should be accessible without sign in' do
    visit '/'
    assert_response :success
    assert_template 'home/index'
  end

  test 'sign in as user should not authenticate admin scope' do
    sign_in_as_user
    assert warden.authenticated?(:user)
    assert_not warden.authenticated?(:admin)
  end

  test 'sign in as admin should not authenticate user scope' do
    sign_in_as_admin
    assert warden.authenticated?(:admin)
    assert_not warden.authenticated?(:user)
  end

  test 'sign in as both user and admin at same time' do
    sign_in_as_user
    sign_in_as_admin
    assert warden.authenticated?(:user)
    assert warden.authenticated?(:admin)
  end

  test 'sign out as user should not touch admin authentication if sign_out_all_scopes is false' do
    swap Devise, sign_out_all_scopes: false do
      sign_in_as_user
      sign_in_as_admin
      delete destroy_user_session_path
      assert_not warden.authenticated?(:user)
      assert warden.authenticated?(:admin)
    end
  end

  test 'sign out as admin should not touch user authentication if sign_out_all_scopes is false' do
    swap Devise, sign_out_all_scopes: false do
      sign_in_as_user
      sign_in_as_admin

      delete destroy_admin_session_path
      assert_not warden.authenticated?(:admin)
      assert warden.authenticated?(:user)
    end
  end

  test 'sign out as user should also sign out admin if sign_out_all_scopes is true' do
    swap Devise, sign_out_all_scopes: true do
      sign_in_as_user
      sign_in_as_admin

      delete destroy_user_session_path
      assert_not warden.authenticated?(:user)
      assert_not warden.authenticated?(:admin)
    end
  end

  test 'sign out as admin should also sign out user if sign_out_all_scopes is true' do
    swap Devise, sign_out_all_scopes: true do
      sign_in_as_user
      sign_in_as_admin

      delete destroy_admin_session_path
      assert_not warden.authenticated?(:admin)
      assert_not warden.authenticated?(:user)
    end
  end

  test 'not signed in as admin should not be able to access admins actions' do
    get admins_path
    assert_redirected_to new_admin_session_path
    assert_not warden.authenticated?(:admin)
  end

  test 'signed in as user should not be able to access admins actions' do
    sign_in_as_user
    assert warden.authenticated?(:user)
    assert_not warden.authenticated?(:admin)

    get admins_path
    assert_redirected_to new_admin_session_path
  end

  test 'signed in as admin should be able to access admin actions' do
    sign_in_as_admin
    assert warden.authenticated?(:admin)
    assert_not warden.authenticated?(:user)

    get admins_path

    assert_response :success
    assert_template 'admins/index'
    assert_contain 'Welcome Admin'
  end

  test 'authenticated admin should not be able to sign as admin again' do
    sign_in_as_admin
    get new_admin_session_path

    assert_response :redirect
    assert_redirected_to admin_root_path
    assert warden.authenticated?(:admin)
  end

  test 'authenticated admin should be able to sign out' do
    sign_in_as_admin
    assert warden.authenticated?(:admin)

    delete destroy_admin_session_path
    assert_response :redirect
    assert_redirected_to root_path

    get root_path
    assert_contain 'Signed out successfully'
    assert_not warden.authenticated?(:admin)
  end

  test 'unauthenticated admin set message on sign out' do
    delete destroy_admin_session_path
    assert_response :redirect
    assert_redirected_to root_path

    get root_path
    assert_contain 'Signed out successfully'
  end

  test 'scope uses custom failure app' do
    put "/en/accounts/management"
    assert_equal "Oops, not found", response.body
    assert_equal 404, response.status
  end
end

class AuthenticationRoutesRestrictions < Devise::IntegrationTest
  test 'not signed in should not be able to access private route (authenticate denied)' do
    get private_path
    assert_redirected_to new_admin_session_path
    assert_not warden.authenticated?(:admin)
  end

  test 'signed in as user should not be able to access private route restricted to admins (authenticate denied)' do
    sign_in_as_user
    assert warden.authenticated?(:user)
    assert_not warden.authenticated?(:admin)
    get private_path
    assert_redirected_to new_admin_session_path
  end

  test 'signed in as admin should be able to access private route restricted to admins (authenticate accepted)' do
    sign_in_as_admin
    assert warden.authenticated?(:admin)
    assert_not warden.authenticated?(:user)

    get private_path

    assert_response :success
    assert_template 'home/private'
    assert_contain 'Private!'
  end

  test 'signed in as inactive admin should not be able to access private/active route restricted to active admins (authenticate denied)' do
    sign_in_as_admin(active: false)
    assert warden.authenticated?(:admin)
    assert_not warden.authenticated?(:user)

    assert_raises ActionController::RoutingError do
      get "/private/active"
    end
  end

  test 'signed in as active admin should be able to access private/active route restricted to active admins (authenticate accepted)' do
    sign_in_as_admin(active: true)
    assert warden.authenticated?(:admin)
    assert_not warden.authenticated?(:user)

    get private_active_path

    assert_response :success
    assert_template 'home/private'
    assert_contain 'Private!'
  end

  test 'signed in as admin should get admin dashboard (authenticated accepted)' do
    sign_in_as_admin
    assert warden.authenticated?(:admin)
    assert_not warden.authenticated?(:user)

    get dashboard_path

    assert_response :success
    assert_template 'home/admin_dashboard'
    assert_contain 'Admin dashboard'
  end

  test 'signed in as user should get user dashboard (authenticated accepted)' do
    sign_in_as_user
    assert warden.authenticated?(:user)
    assert_not warden.authenticated?(:admin)

    get dashboard_path

    assert_response :success
    assert_template 'home/user_dashboard'
    assert_contain 'User dashboard'
  end

  test 'not signed in should get no dashboard (authenticated denied)' do
    assert_raises ActionController::RoutingError do
      get dashboard_path
    end
  end

  test 'signed in as inactive admin should not be able to access dashboard/active route restricted to active admins (authenticated denied)' do
    sign_in_as_admin(active: false)
    assert warden.authenticated?(:admin)
    assert_not warden.authenticated?(:user)

    assert_raises ActionController::RoutingError do
      get "/dashboard/active"
    end
  end

  test 'signed in as active admin should be able to access dashboard/active route restricted to active admins (authenticated accepted)' do
    sign_in_as_admin(active: true)
    assert warden.authenticated?(:admin)
    assert_not warden.authenticated?(:user)

    get dashboard_active_path

    assert_response :success
    assert_template 'home/admin_dashboard'
    assert_contain 'Admin dashboard'
  end

  test 'signed in user should not see unauthenticated page (unauthenticated denied)' do
    sign_in_as_user
    assert warden.authenticated?(:user)
    assert_not warden.authenticated?(:admin)

    assert_raises ActionController::RoutingError do
      get join_path
    end
  end

  test 'not signed in users should see unauthenticated page (unauthenticated accepted)' do
    get join_path

    assert_response :success
    assert_template 'home/join'
    assert_contain 'Join'
  end
end

class AuthenticationRedirectTest < Devise::IntegrationTest
  test 'redirect from warden shows sign in or sign up message' do
    get admins_path

    warden_path = new_admin_session_path
    assert_redirected_to warden_path

    get warden_path
    assert_contain 'You need to sign in or sign up before continuing.'
  end

  test 'redirect from warden respects i18n locale set at the controller' do
    get admins_path(locale: "pt-BR")

    assert_redirected_to new_admin_session_path
    follow_redirect!

    assert_contain 'Para continuar, faça login ou registre-se.'
  end

  test 'redirect to default url if no other was configured' do
    sign_in_as_user
    assert_template 'home/index'
    assert_nil session[:"user_return_to"]
  end

  test 'redirect to requested url after sign in' do
    get users_path
    assert_redirected_to new_user_session_path
    assert_equal users_path, session[:"user_return_to"]

    follow_redirect!
    sign_in_as_user visit: false

    assert_current_url '/users'
    assert_nil session[:"user_return_to"]
  end

  test 'redirect to last requested url overwriting the stored return_to option' do
    get expire_user_path(create_user)
    assert_redirected_to new_user_session_path
    assert_equal expire_user_path(create_user), session[:"user_return_to"]

    get users_path
    assert_redirected_to new_user_session_path
    assert_equal users_path, session[:"user_return_to"]

    follow_redirect!
    sign_in_as_user visit: false

    assert_current_url '/users'
    assert_nil session[:"user_return_to"]
  end

  test 'xml http requests does not store urls for redirect' do
    get users_path, headers: { 'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest' }
    assert_equal 401, response.status
    assert_nil session[:"user_return_to"]
  end

  test 'redirect to configured home path for a given scope after sign in' do
    sign_in_as_admin
    assert_equal "/admin_area/home", @request.path
  end

  test 'require_no_authentication should set the already_authenticated flash message' do
    sign_in_as_user
    visit new_user_session_path
    assert_equal I18n.t("devise.failure.already_authenticated"), flash[:alert]
  end

  test 'require_no_authentication should set the already_authenticated flash message as admin' do
    store_translations :en, devise: { failure: { admin: { already_authenticated: 'You are already signed in as admin.' } } } do
      sign_in_as_admin
      visit new_admin_session_path
      assert_equal "You are already signed in as admin.", flash[:alert]
    end
  end
end

class AuthenticationSessionTest < Devise::IntegrationTest
  test 'destroyed account is signed out' do
    sign_in_as_user
    get '/users'

    User.destroy_all
    get '/users'
    assert_redirected_to new_user_session_path
  end

  test 'refreshes _csrf_token' do
    swap ApplicationController, allow_forgery_protection: true do
      get new_user_session_path
      token_from_session = request.session[:_csrf_token]

      if Devise::Test.rails71_and_up?
        token_from_env = request.env["action_controller.csrf_token"]
      end

      sign_in_as_user
      assert_not_equal request.session[:_csrf_token], token_from_session

      if Devise::Test.rails71_and_up?
        assert_not_equal request.env["action_controller.csrf_token"], token_from_env
      end
    end
  end

  test 'allows session to be set for a given scope' do
    sign_in_as_user
    get '/users'
    assert_equal "Cart", @controller.user_session[:cart]
  end

  test 'session id is changed on sign in' do
    get '/users'
    session_id = request.session["session_id"]

    get '/users'
    assert_equal session_id, request.session["session_id"]

    sign_in_as_user
    assert_not_equal session_id, request.session["session_id"]
  end
end

class AuthenticationWithScopedViewsTest < Devise::IntegrationTest
  test 'renders the scoped view if turned on and view is available' do
    swap Devise, scoped_views: true do
      assert_raise Webrat::NotFoundError do
        sign_in_as_user
      end
      assert_match %r{Special user view}, response.body
    end
  end

  test 'renders the scoped view if turned on in a specific controller' do
    begin
      Devise::SessionsController.scoped_views = true
      assert_raise Webrat::NotFoundError do
        sign_in_as_user
      end

      assert_match %r{Special user view}, response.body
      assert_not Devise::PasswordsController.scoped_views?
    ensure
      Devise::SessionsController.send :remove_instance_variable, :@scoped_views
    end
  end

  test 'does not render the scoped view if turned off' do
    swap Devise, scoped_views: false do
      assert_nothing_raised do
        sign_in_as_user
      end
    end
  end

  test 'does not render the scoped view if not available' do
    swap Devise, scoped_views: true do
      assert_nothing_raised do
        sign_in_as_admin
      end
    end
  end
end

class AuthenticationOthersTest < Devise::IntegrationTest
  test 'handles unverified requests gets rid of caches' do
    swap ApplicationController, allow_forgery_protection: true do
      post exhibit_user_url(1)
      assert_not warden.authenticated?(:user)

      sign_in_as_user
      assert warden.authenticated?(:user)

      post exhibit_user_url(1)
      assert_not warden.authenticated?(:user)
      assert_equal "User is not authenticated", response.body
    end
  end

  test 'uses the custom controller with the custom controller view' do
    get '/admin_area/sign_in'
    assert_contain 'Log in'
    assert_contain 'Welcome to "admins/sessions" controller!'
    assert_contain 'Welcome to "sessions/new" view!'
  end

  test 'render 404 on roles without routes' do
    assert_raise ActionController::RoutingError do
      get '/admin_area/password/new'
    end
  end

  test 'does not intercept Rails 401 responses' do
    get '/unauthenticated'
    assert_equal 401, response.status
  end

  test 'render 404 on roles without mapping' do
    assert_raise AbstractController::ActionNotFound do
      get '/sign_in'
    end
  end

  test 'sign in with script name' do
    assert_nothing_raised do
      get new_user_session_path, headers: { "SCRIPT_NAME" => "/omg" }
      fill_in "email", with: "user@test.com"
    end
  end

  test 'sign in stub in json format' do
    get new_user_session_path(format: 'json')
    assert_match '{"user":{', response.body
    assert_match '"email":""', response.body
    assert_match '"password":null', response.body
  end

  test 'sign in stub in json with non attribute key' do
    swap Devise, authentication_keys: [:other_key] do
      get new_user_session_path(format: 'json')
      assert_match '{"user":{', response.body
      assert_match '"other_key":null', response.body
      assert_match '"password":null', response.body
    end
  end

  test 'uses the mapping from router' do
    sign_in_as_user visit: "/as/sign_in"
    assert warden.authenticated?(:user)
    assert_not warden.authenticated?(:admin)
  end

  test 'sign in with json format returns json response' do
    create_user
    post user_session_path(format: 'json'), params: { user: {email: "user@test.com", password: '12345678'} }
    assert_response :success
    assert_includes response.body, '{"user":{'
  end

  test 'sign in with json format is idempotent' do
    get new_user_session_path(format: 'json')
    assert_response :success

    create_user
    post user_session_path(format: 'json'), params: { user: {email: "user@test.com", password: '12345678'} }
    assert_response :success

    get new_user_session_path(format: 'json')
    assert_response :success

    post user_session_path(format: 'json'), params: { user: {email: "user@test.com", password: '12345678'} }
    assert_response :success
    assert_includes response.body, '{"user":{'
  end

  test 'sign out with html redirects' do
    sign_in_as_user
    delete destroy_user_session_path
    assert_response :redirect
    assert_current_url '/'

    sign_in_as_user
    delete destroy_user_session_path(format: 'html')
    assert_response :redirect
    assert_current_url '/'
  end

  test 'sign out with json format returns no content' do
    sign_in_as_user
    delete destroy_user_session_path(format: 'json')
    assert_response :no_content
    assert_not warden.authenticated?(:user)
  end

  test 'sign out with non-navigational format via XHR does not redirect' do
    swap Devise, navigational_formats: ['*/*', :html] do
      sign_in_as_admin
      get destroy_sign_out_via_get_session_path, xhr: true, headers: { "HTTP_ACCEPT" => "application/json,text/javascript,*/*" } # NOTE: Bug is triggered by combination of XHR and */*.
      assert_response :no_content
      assert_not warden.authenticated?(:user)
    end
  end

  # Belt and braces ... Perhaps this test is not necessary?
  test 'sign out with navigational format via XHR does redirect' do
    swap Devise, navigational_formats: ['*/*', :html] do
      sign_in_as_user
      delete destroy_user_session_path, xhr: true, headers: { "HTTP_ACCEPT" => "text/html,*/*" }
      assert_response :redirect
      assert_not warden.authenticated?(:user)
    end
  end
end

class AuthenticationKeysTest < Devise::IntegrationTest
  test 'missing authentication keys cause authentication to abort' do
    swap Devise, authentication_keys: [:subdomain] do
      sign_in_as_user
      assert_contain "Invalid Subdomain or password."
      assert_not warden.authenticated?(:user)
    end
  end

  test 'missing authentication keys cause authentication to abort unless marked as not required' do
    swap Devise, authentication_keys: { email: true, subdomain: false } do
      sign_in_as_user
      assert warden.authenticated?(:user)
    end
  end
end

class AuthenticationRequestKeysTest < Devise::IntegrationTest
  test 'request keys are used on authentication' do
    host! 'foo.bar.baz'

    swap Devise, request_keys: [:subdomain] do
      User.expects(:find_for_authentication).with({ subdomain: 'foo', email: 'user@test.com' }).returns(create_user)
      sign_in_as_user
      assert warden.authenticated?(:user)
    end
  end

  test 'invalid request keys raises NoMethodError' do
    swap Devise, request_keys: [:unknown_method] do
      assert_raise NoMethodError do
        sign_in_as_user
      end

      assert_not warden.authenticated?(:user)
    end
  end

  test 'blank request keys cause authentication to abort' do
    host! 'test.com'

    swap Devise, request_keys: [:subdomain] do
      sign_in_as_user
      assert_contain "Invalid Email or password."
      assert_not warden.authenticated?(:user)
    end
  end

  test 'blank request keys cause authentication to abort unless if marked as not required' do
    host! 'test.com'

    swap Devise, request_keys: { subdomain: false } do
      sign_in_as_user
      assert warden.authenticated?(:user)
    end
  end
end

class AuthenticationSignOutViaTest < Devise::IntegrationTest
  def sign_in!(scope)
    sign_in_as_admin(visit: send("new_#{scope}_session_path"))
    assert warden.authenticated?(scope)
  end

  test 'allow sign out via delete when sign_out_via provides only delete' do
    sign_in!(:sign_out_via_delete)
    delete destroy_sign_out_via_delete_session_path
    assert_not warden.authenticated?(:sign_out_via_delete)
  end

  test 'do not allow sign out via get when sign_out_via provides only delete' do
    sign_in!(:sign_out_via_delete)
    assert_raise ActionController::RoutingError do
      get destroy_sign_out_via_delete_session_path
    end
    assert warden.authenticated?(:sign_out_via_delete)
  end

  test 'allow sign out via post when sign_out_via provides only post' do
    sign_in!(:sign_out_via_post)
    post destroy_sign_out_via_post_session_path
    assert_not warden.authenticated?(:sign_out_via_post)
  end

  test 'do not allow sign out via get when sign_out_via provides only post' do
    sign_in!(:sign_out_via_post)
    assert_raise ActionController::RoutingError do
      get destroy_sign_out_via_delete_session_path
    end
    assert warden.authenticated?(:sign_out_via_post)
  end

  test 'allow sign out via delete when sign_out_via provides delete and post' do
    sign_in!(:sign_out_via_delete_or_post)
    delete destroy_sign_out_via_delete_or_post_session_path
    assert_not warden.authenticated?(:sign_out_via_delete_or_post)
  end

  test 'allow sign out via post when sign_out_via provides delete and post' do
    sign_in!(:sign_out_via_delete_or_post)
    post destroy_sign_out_via_delete_or_post_session_path
    assert_not warden.authenticated?(:sign_out_via_delete_or_post)
  end

  test 'do not allow sign out via get when sign_out_via provides delete and post' do
    sign_in!(:sign_out_via_delete_or_post)
    assert_raise ActionController::RoutingError do
      get destroy_sign_out_via_delete_or_post_session_path
    end
    assert warden.authenticated?(:sign_out_via_delete_or_post)
  end
end

class DoubleAuthenticationRedirectTest < Devise::IntegrationTest
  test 'signed in as user redirects when visiting user sign in page' do
    sign_in_as_user
    get new_user_session_path(format: :html)
    assert_redirected_to '/'
  end

  test 'signed in as admin redirects when visiting admin sign in page' do
    sign_in_as_admin
    get new_admin_session_path(format: :html)
    assert_redirected_to '/admin_area/home'
  end

  test 'signed in as both user and admin redirects when visiting admin sign in page' do
    sign_in_as_user
    sign_in_as_admin
    get new_user_session_path(format: :html)
    assert_redirected_to '/'
    get new_admin_session_path(format: :html)
    assert_redirected_to '/admin_area/home'
  end
end

class DoubleSignOutRedirectTest < Devise::IntegrationTest
  test 'sign out after already having signed out redirects to sign in' do
    sign_in_as_user

    post destroy_sign_out_via_delete_or_post_session_path

    get root_path
    assert_contain 'Signed out successfully.'

    post destroy_sign_out_via_delete_or_post_session_path

    get root_path
    assert_contain 'Signed out successfully.'
  end
end
