# frozen_string_literal: true

require_relative "test_case/rails_request_adapter"
require_relative "test_case/mock_api_controller"
require_relative "test_case/mock_cookie_jar"
require_relative "test_case/mock_controller"
require_relative "test_case/mock_logger"
require_relative "test_case/mock_request"

# :nodoc:
module Authlogic
  # This module is a collection of methods and classes that help you easily test
  # Authlogic. In fact, I use these same tools to test the internals of
  # Authlogic.
  #
  # === The quick and dirty
  #
  #   require "authlogic/test_case" # include at the top of test_helper.rb
  #   setup :activate_authlogic # run before tests are executed
  #   UserSession.create(users(:whomever)) # logs a user in
  #
  # For a more detailed explanation, see below.
  #
  # === Setting up
  #
  # Authlogic comes with some simple testing tools. To get these, you need to
  # first require Authlogic's TestCase. If you are doing this in a rails app,
  # you would require this file at the top of your test_helper.rb file:
  #
  #   require "authlogic/test_case"
  #
  # If you are using Test::Unit::TestCase, the standard testing library that
  # comes with ruby, then you can skip this next part. If you are not, you need
  # to include the Authlogic::TestCase into your testing suite as follows:
  #
  #   include Authlogic::TestCase
  #
  # Now that everything is ready to go, let's move onto actually testing. Here
  # is the basic idea behind testing:
  #
  # Authlogic requires a "connection" to your controller to activate it. In the
  # same manner that ActiveRecord requires a connection to your database. It
  # can't do anything until it gets connected. That being said, Authlogic will
  # raise an Authlogic::Session::Activation::NotActivatedError any time you try
  # to instantiate an object without a "connection". So before you do anything
  # with Authlogic, you need to activate / connect Authlogic. Let's walk through
  # how to do this in tests:
  #
  # === Fixtures / Factories
  #
  # Creating users via fixtures / factories is easy. Here's an example of a
  # fixture:
  #
  #   ben:
  #     email: whatever@whatever.com
  #     password_salt: <%= salt = Authlogic::Random.hex_token %>
  #     crypted_password: <%= Authlogic::CryptoProviders::SCrypt.encrypt("benrocks" + salt) %>
  #     persistence_token: <%= Authlogic::Random.hex_token %>
  #     single_access_token: <%= Authlogic::Random.friendly_token %>
  #     perishable_token: <%= Authlogic::Random.friendly_token %>
  #
  # Notice the crypted_password value. Just supplement that with whatever crypto
  # provider you are using, if you are not using the default.
  #
  # === Functional tests
  #
  # Activating Authlogic isn't a problem here, because making a request will
  # activate Authlogic for you. The problem is logging users in so they can
  # access restricted areas. Solving this is simple, just do this:
  #
  #   setup :activate_authlogic
  #
  # For those of you unfamiliar with TestUnit, the setup method basically just
  # executes a method before any test is ran. It is essentially "setting up"
  # your tests.
  #
  # Once you have done this, just log users in like usual:
  #
  #   UserSession.create(users(:whomever))
  #   # access my restricted area here
  #
  # Do this before you make your request and it will act as if that user is
  # logged in.
  #
  # === Integration tests
  #
  # Again, just like functional tests, you don't have to do anything. As soon as
  # you make a request, Authlogic will be connected. If you want to activate
  # Authlogic before making a request follow the same steps described in the
  # "functional tests" section above. It works in the same manner.
  #
  # === Unit tests
  #
  # The only time you need to do any trickiness here is if you want to test
  # Authlogic models. Maybe you added some custom code or methods in your
  # Authlogic models. Maybe you are writing a plugin or a library that extends
  # Authlogic.
  #
  # That being said, in this environment there is no controller. So you need to
  # use a "mock" controller. Something that looks like a controller, acts like a
  # controller, but isn't a "real" controller. You are essentially connecting
  # Authlogic to your "mock" controller, then you can test off of the mock
  # controller to make sure everything is functioning properly.
  #
  # I use a mock controller to test Authlogic myself. It's part of the Authlogic
  # library that you can easily use. It's as simple as functional and
  # integration tests. Just do the following:
  #
  #   setup :activate_authlogic
  #
  # You also get a controller method that you can test off of. For example:
  #
  #   ben = users(:ben)
  #   assert_nil controller.session["user_credentials"]
  #   assert UserSession.create(ben)
  #   assert_equal controller.session["user_credentials"], ben.persistence_token
  #
  # See how I am checking that Authlogic is interacting with the controller
  # properly? That's the idea here.
  #
  # === Testing with Rails 5
  #
  # Rails 5 has [deprecated classic controller tests](https://goo.gl/4zmt6y).
  # Controller tests now inherit from `ActionDispatch::IntegrationTest` making
  # them plain old integration tests now. You have two options for testing
  # AuthLogic in Rails 5:
  #
  # * Add the `rails-controller-testing` gem to bring back the original
  #   controller testing usage
  # * Go full steam ahead with integration testing and actually log a user in
  #   by submitting a form in the integration test.
  #
  # Naturally DHH recommends the second method and this is
  # [what he does in his own tests](https://goo.gl/Ar6p0u). This is useful
  # for testing not only AuthLogic itself (submitting login credentials to a
  # UserSessionsController, for example) but any controller action that is
  # behind a login wall. Add a helper method and use that before testing your
  # actual controller action:
  #
  #   # test/test_helper.rb
  #   def login(user)
  #     post user_sessions_url, :params => { :email => user.email, :password => 'password' }
  #   end
  #
  #   # test/controllers/posts_controller_test.rb
  #   test "#create requires a user to be logged in
  #     post posts_url, :params => { :body => 'Lorem ipsum' }
  #
  #     assert_redirected_to new_user_session_url
  #   end
  #
  #   test "#create lets a logged in user create a new post" do
  #     login(users(:admin))
  #
  #     assert_difference 'Posts.count' do
  #       post posts_url, :params => { :body => 'Lorem ipsum' }
  #     end
  #
  #     assert_redirected_to posts_url
  #   end
  #
  # You still have access to the `session` helper in an integration test and so
  # you can still test to see if a user is logged in. A couple of helper methods
  # might look like:
  #
  #   # test/test_helper.rb
  #   def assert_logged_in
  #     assert session[:user_credentials].present?
  #   end
  #
  #   def assert_not_logged_in
  #     assert session[:user_credentials].blank?
  #   end
  #
  #   # test/user_sessions_controller_test.rb
  #   test "#create logs in a user" do
  #     login(users(:admin))
  #
  #     assert_logged_in
  #   end
  module TestCase
    def initialize(*args)
      @request = nil
      super
    end

    # Activates authlogic so that you can use it in your tests. You should call
    # this method in your test's setup. Ex:
    #
    #   setup :activate_authlogic
    def activate_authlogic
      if @request && !@request.respond_to?(:params)
        class <<@request
          alias_method :params, :parameters
        end
      end

      Authlogic::Session::Base.controller = @request &&
        Authlogic::TestCase::RailsRequestAdapter.new(@request) ||
        controller
    end

    # The Authlogic::TestCase::MockController object passed to Authlogic to
    # activate it. You can access this in your test. See the module description
    # for an example.
    def controller
      @controller ||= Authlogic::TestCase::MockController.new
    end
  end

  # TODO: Why are these lines inside the `Authlogic` module? Should be outside?
  ::Test::Unit::TestCase.send(:include, TestCase) if defined?(::Test::Unit::TestCase)
  ::MiniTest::Unit::TestCase.send(:include, TestCase) if defined?(::MiniTest::Unit::TestCase)
  ::MiniTest::Test.send(:include, TestCase) if defined?(::MiniTest::Test)
end
