require 'abstract_unit'
require 'fixtures/session_autoload_test/session_autoload_test/foo'

class CacheStoreTest < ActionDispatch::IntegrationTest
  class TestController < ActionController::Base
    def no_session_access
      head :ok
    end

    def set_session_value
      session[:foo] = "bar"
      head :ok
    end

    def set_deep_session_value
      session[:foo] = { bar: "baz" }
      head :ok
    end

    def set_serialized_session_value
      session[:foo] = SessionAutoloadTest::Foo.new
      head :ok
    end

    def get_session_value
      render plain: "foo: #{session[:foo].inspect}"
    end

    def get_deep_session_value_with_symbol
      render plain: "foo: { bar: #{session[:foo][:bar].inspect} }"
    end

    def get_deep_session_value_with_string
      render plain: "foo: { \"bar\" => #{session[:foo]["bar"].inspect} }"
    end

    def get_session_id
      render plain: "#{request.session.id}"
    end

    def call_reset_session
      session[:bar]
      reset_session
      session[:bar] = "baz"
      head :ok
    end
  end

  def test_setting_and_getting_session_value
    with_test_route_set do
      get '/set_session_value'
      assert_response :success
      assert cookies['_session_id']

      get '/get_session_value'
      assert_response :success
      assert_equal 'foo: "bar"', response.body
    end
  end

  def test_getting_nil_session_value
    with_test_route_set do
      get '/get_session_value'
      assert_response :success
      assert_equal 'foo: nil', response.body
    end
  end

  def test_getting_session_value_after_session_reset
    with_test_route_set do
      get '/set_session_value'
      assert_response :success
      assert cookies['_session_id']
      session_cookie = cookies.send(:hash_for)['_session_id']

      get '/call_reset_session'
      assert_response :success
      assert_not_equal [], headers['Set-Cookie']

      cookies << session_cookie # replace our new session_id with our old, pre-reset session_id

      get '/get_session_value'
      assert_response :success
      assert_equal 'foo: nil', response.body, "data for this session should have been obliterated from cache"
    end
  end

  def test_getting_from_nonexistent_session
    with_test_route_set do
      get '/get_session_value'
      assert_response :success
      assert_equal 'foo: nil', response.body
      assert_nil cookies['_session_id'], "should only create session on write, not read"
    end
  end

  def test_setting_session_value_after_session_reset
    with_test_route_set do
      get '/set_session_value'
      assert_response :success
      assert cookies['_session_id']
      session_id = cookies['_session_id']

      get '/call_reset_session'
      assert_response :success
      assert_not_equal [], headers['Set-Cookie']

      get '/get_session_value'
      assert_response :success
      assert_equal 'foo: nil', response.body

      get '/get_session_id'
      assert_response :success
      assert_not_equal session_id, response.body
    end
  end

  def test_getting_session_id
    with_test_route_set do
      get '/set_session_value'
      assert_response :success
      assert cookies['_session_id']
      session_id = cookies['_session_id']

      get '/get_session_id'
      assert_response :success
      assert_equal session_id, response.body, "should be able to read session id without accessing the session hash"
    end
  end

  def test_deserializes_unloaded_class
    with_test_route_set do
      with_autoload_path "session_autoload_test" do
        get '/set_serialized_session_value'
        assert_response :success
        assert cookies['_session_id']
      end
      with_autoload_path "session_autoload_test" do
        get '/get_session_id'
        assert_response :success
      end
      with_autoload_path "session_autoload_test" do
        get '/get_session_value'
        assert_response :success
        assert_equal 'foo: #<SessionAutoloadTest::Foo bar:"baz">', response.body, "should auto-load unloaded class"
      end
    end
  end

  def test_doesnt_write_session_cookie_if_session_id_is_already_exists
    with_test_route_set do
      get '/set_session_value'
      assert_response :success
      assert cookies['_session_id']

      get '/get_session_value'
      assert_response :success
      assert_equal nil, headers['Set-Cookie'], "should not resend the cookie again if session_id cookie is already exists"
    end
  end

  def test_prevents_session_fixation
    with_test_route_set do
      assert_equal nil, @cache.read('_session_id:0xhax')

      cookies['_session_id'] = '0xhax'
      get '/set_session_value'

      assert_response :success
      assert_not_equal '0xhax', cookies['_session_id']
      assert_equal nil, @cache.read('_session_id:0xhax')
      assert_equal({'foo' => 'bar'}, @cache.read("_session_id:#{cookies['_session_id']}"))
    end
  end

  def test_previous_session_has_indifferent_access
    with_test_route_set do
      get '/set_deep_session_value'
      assert_response :success
      assert cookies['_session_id']

      get '/get_deep_session_value_with_symbol'
      assert_response :success
      assert_equal 'foo: { bar: "baz" }', response.body

      get '/get_deep_session_value_with_string'
      assert_response :success
      assert_equal 'foo: { "bar" => "baz" }', response.body
    end
  end

  private
    def with_test_route_set
      with_routing do |set|
        set.draw do
          get ':action', :to => ::CacheStoreTest::TestController
        end

        @app = self.class.build_app(set) do |middleware|
          @cache = ActiveSupport::Cache::MemoryStore.new
          middleware.use ActionDispatch::Session::CacheStore, :key => '_session_id', :cache => @cache
          middleware.delete ActionDispatch::ShowExceptions
        end

        yield
      end
    end
end
