require 'spec_helper'

RSpec.describe RailsAdmin::Config do
  describe '.included_models' do
    it 'only uses included models' do
      RailsAdmin.config.included_models = [Team, League]
      expect(RailsAdmin::AbstractModel.all.collect(&:model)).to eq([League, Team]) # it gets sorted
    end

    it 'does not restrict models if included_models is left empty' do
      RailsAdmin.config.included_models = []
      expect(RailsAdmin::AbstractModel.all.collect(&:model)).to include(Team, League)
    end

    it 'removes excluded models (whitelist - blacklist)' do
      RailsAdmin.config.excluded_models = [Team]
      RailsAdmin.config.included_models = [Team, League]
      expect(RailsAdmin::AbstractModel.all.collect(&:model)).to eq([League])
    end

    it 'excluded? returns true for any model not on the list' do
      RailsAdmin.config.included_models = [Team, League]

      team_config = RailsAdmin::AbstractModel.new('Team').config
      fan_config = RailsAdmin::AbstractModel.new('Fan').config

      expect(fan_config).to be_excluded
      expect(team_config).not_to be_excluded
    end
  end

  describe '.add_extension' do
    before do
      silence_warnings do
        RailsAdmin.const_set('EXTENSIONS', [])
      end
    end

    it 'registers the extension with RailsAdmin' do
      RailsAdmin.add_extension(:example, ExampleModule)
      expect(RailsAdmin::EXTENSIONS.count { |name| name == :example }).to eq(1)
    end

    context 'given an extension with an authorization adapter' do
      it 'registers the adapter' do
        RailsAdmin.add_extension(:example, ExampleModule, authorization: true)
        expect(RailsAdmin::AUTHORIZATION_ADAPTERS[:example]).to eq(ExampleModule::AuthorizationAdapter)
      end
    end

    context 'given an extension with an auditing adapter' do
      it 'registers the adapter' do
        RailsAdmin.add_extension(:example, ExampleModule, auditing: true)
        expect(RailsAdmin::AUDITING_ADAPTERS[:example]).to eq(ExampleModule::AuditingAdapter)
      end
    end

    context 'given an extension with a configuration adapter' do
      it 'registers the adapter' do
        RailsAdmin.add_extension(:example, ExampleModule, configuration: true)
        expect(RailsAdmin::CONFIGURATION_ADAPTERS[:example]).to eq(ExampleModule::ConfigurationAdapter)
      end
    end
  end

  describe '.main_app_name' do
    it 'as a default meaningful dynamic value' do
      expect(RailsAdmin.config.main_app_name.call).to eq(['Dummy App', 'Admin'])
    end

    it 'can be configured' do
      RailsAdmin.config do |config|
        config.main_app_name = %w[stati c value]
      end
      expect(RailsAdmin.config.main_app_name).to eq(%w[stati c value])
    end
  end

  describe '.authorize_with' do
    context 'given a key for a extension with authorization' do
      before do
        RailsAdmin.add_extension(:example, ExampleModule, authorization: true)
      end

      it 'initializes the authorization adapter' do
        expect(ExampleModule::AuthorizationAdapter).to receive(:new).with(RailsAdmin::Config)
        RailsAdmin.config do |config|
          config.authorize_with(:example)
        end
        RailsAdmin.config.authorize_with.call
      end

      it 'passes through any additional arguments to the initializer' do
        options = {option: true}
        expect(ExampleModule::AuthorizationAdapter).to receive(:new).with(RailsAdmin::Config, options)
        RailsAdmin.config do |config|
          config.authorize_with(:example, options)
        end
        RailsAdmin.config.authorize_with.call
      end
    end
  end

  describe '.audit_with' do
    context 'given a key for a extension with auditing' do
      before do
        RailsAdmin.add_extension(:example, ExampleModule, auditing: true)
      end

      it 'initializes the auditing adapter' do
        expect(ExampleModule::AuditingAdapter).to receive(:new).with(RailsAdmin::Config)
        RailsAdmin.config do |config|
          config.audit_with(:example)
        end
        RailsAdmin.config.audit_with.call
      end

      it 'passes through any additional arguments to the initializer' do
        options = {option: true}
        expect(ExampleModule::AuditingAdapter).to receive(:new).with(RailsAdmin::Config, options)
        RailsAdmin.config do |config|
          config.audit_with(:example, options)
        end
        RailsAdmin.config.audit_with.call
      end
    end

    context 'given paper_trail as the extension for auditing', active_record: true do
      before do
        class ControllerMock
          def set_paper_trail_whodunnit; end
        end

        module PaperTrail; end

        class Version; end
        RailsAdmin.add_extension(:example, RailsAdmin::Extensions::PaperTrail, auditing: true)
      end

      it 'initializes the auditing adapter' do
        RailsAdmin.config do |config|
          config.audit_with(:example)
        end
        expect { ControllerMock.new.instance_eval(&RailsAdmin.config.audit_with) }.not_to raise_error
      end
    end
  end

  describe '.configure_with' do
    context 'given a key for a extension with configuration' do
      before do
        RailsAdmin.add_extension(:example, ExampleModule, configuration: true)
      end

      it 'initializes configuration adapter' do
        expect(ExampleModule::ConfigurationAdapter).to receive(:new)
        RailsAdmin.config do |config|
          config.configure_with(:example)
        end
      end

      it 'yields the (optionally) provided block, passing the initialized adapter' do
        configurator = nil
        RailsAdmin.config do |config|
          config.configure_with(:example) do |configuration_adapter|
            configurator = configuration_adapter
          end
        end
        expect(configurator).to be_a(ExampleModule::ConfigurationAdapter)
      end
    end
  end

  describe '.config' do
    context '.default_search_operator' do
      it 'sets the default_search_operator' do
        RailsAdmin.config do |config|
          config.default_search_operator = 'starts_with'
        end
        expect(RailsAdmin::Config.default_search_operator).to eq('starts_with')
      end

      it 'errors on unrecognized search operator' do
        expect do
          RailsAdmin.config do |config|
            config.default_search_operator = 'random'
          end
        end.to raise_error(ArgumentError, "Search operator 'random' not supported")
      end

      it "defaults to 'default'" do
        expect(RailsAdmin::Config.default_search_operator).to eq('default')
      end
    end
  end

  describe '.visible_models' do
    it 'passes controller bindings, find visible models, order them' do
      RailsAdmin.config do |config|
        config.included_models = [Player, Fan, Comment, Team]

        config.model Player do
          hide
        end
        config.model Fan do
          weight(-1)
          show
        end
        config.model Comment do
          visible do
            bindings[:controller]._current_user.role == :admin
          end
        end
        config.model Team do
          visible do
            bindings[:controller]._current_user.role != :admin
          end
        end
      end

      expect(RailsAdmin.config.visible_models(controller: double(_current_user: double(role: :admin), authorized?: true)).collect(&:abstract_model).collect(&:model)).to match_array [Fan, Comment]
    end

    it 'hides unallowed models' do
      RailsAdmin.config do |config|
        config.included_models = [Comment]
      end
      expect(RailsAdmin.config.visible_models(controller: double(authorization_adapter: double(authorized?: true))).collect(&:abstract_model).collect(&:model)).to eq([Comment])
      expect(RailsAdmin.config.visible_models(controller: double(authorization_adapter: double(authorized?: false))).collect(&:abstract_model).collect(&:model)).to eq([])
    end

    it 'does not contain embedded model', mongoid: true do
      RailsAdmin.config do |config|
        config.included_models = [FieldTest, Comment, Embed]
      end

      expect(RailsAdmin.config.visible_models(controller: double(_current_user: double(role: :admin), authorized?: true)).collect(&:abstract_model).collect(&:model)).to match_array [FieldTest, Comment]
    end

    it 'basically does not contain embedded model except model using recursively_embeds_many or recursively_embeds_one', mongoid: true do
      class RecursivelyEmbedsOne
        include Mongoid::Document
        recursively_embeds_one
      end

      class RecursivelyEmbedsMany
        include Mongoid::Document
        recursively_embeds_many
      end
      RailsAdmin.config do |config|
        config.included_models = [FieldTest, Comment, Embed, RecursivelyEmbedsMany, RecursivelyEmbedsOne]
      end
      expect(RailsAdmin.config.visible_models(controller: double(_current_user: double(role: :admin), authorized?: true)).collect(&:abstract_model).collect(&:model)).to match_array [FieldTest, Comment, RecursivelyEmbedsMany, RecursivelyEmbedsOne]
    end
  end

  describe '.models_pool' do
    it 'should not include classnames start with Concerns::' do
      expect(RailsAdmin::Config.models_pool.select { |m| m.match(/^Concerns::/) }).to be_empty
    end

    it 'includes models in the directory added by config.eager_load_paths' do
      expect(RailsAdmin::Config.models_pool).to include('Basketball')
    end
  end

  describe '.parent_controller' do
    before do
      class TestController < ActionController::Base; end
    end

    it 'uses default class' do
      expect(RailsAdmin.config.parent_controller).to eq '::ActionController::Base'
    end

    it 'uses other class' do
      RailsAdmin.config do |config|
        config.parent_controller = 'TestController'
      end
      expect(RailsAdmin.config.parent_controller).to eq 'TestController'
    end
  end

  describe '.parent_controller=' do
    context 'if RailsAdmin::ApplicationController is already loaded' do
      after do
        RailsAdmin::Config.reset
        RailsAdmin.send(:remove_const, :ApplicationController)
        load RailsAdmin::Engine.root.join('app/controllers/rails_admin/application_controller.rb')
      end

      it 'can be changed' do
        RailsAdmin.config.parent_controller = 'ApplicationController'
        expect(RailsAdmin::ApplicationController.superclass).to eq ApplicationController
      end
    end
  end

  describe '.forgery_protection_settings' do
    it 'uses with: :exception by default' do
      expect(RailsAdmin.config.forgery_protection_settings).to eq(with: :exception)
    end

    it 'allows to customize settings' do
      RailsAdmin.config do |config|
        config.forgery_protection_settings = {with: :null_session}
      end
      expect(RailsAdmin.config.forgery_protection_settings).to eq(with: :null_session)
    end
  end

  describe '.model' do
    let(:fields) { described_class.model(Team).fields }
    before do
      described_class.model Team do
        field :players do
          visible false
        end
      end
    end

    context 'when model expanded' do
      before do
        described_class.model(Team) do
          field :fans
        end
      end
      it 'execute all passed blocks' do
        expect(fields.map(&:name)).to match_array %i[players fans]
      end
    end

    context 'when expand redefine behavior' do
      before do
        described_class.model Team do
          field :players
        end
      end
      it 'execute all passed blocks' do
        expect(fields.find { |f| f.name == :players }.visible).to be true
      end
    end

    context 'when model has no table yet', active_record: true do
      it 'does not try to apply the configuration block' do
        described_class.model(WithoutTable) do
          include_all_fields
        end
      end
    end
  end

  describe '.reset' do
    before do
      RailsAdmin.config do |config|
        config.included_models = %w[Player Team]
      end
      RailsAdmin::AbstractModel.all
      RailsAdmin::Config.reset
      RailsAdmin.config do |config|
        config.excluded_models = ['Player']
      end
    end
    subject { RailsAdmin::AbstractModel.all.map { |am| am.model.name } }

    it 'refreshes the result of RailsAdmin::AbstractModel.all' do
      expect(subject).not_to include 'Player'
      expect(subject).to include 'Team'
    end
  end

  describe '.apply' do
    subject { RailsAdmin::Config.apply(&block) }
    let(:block) do
      proc do |config|
        config.model Team do
          register_instance_option('parameter') # an arbitrary instance method we can spy on
        end
      end
    end
    before { RailsAdmin::Config.instance_variable_set(:@initialized, false) }
    after do
      RailsAdmin::Config.instance_variable_set(:@initialized, true)
      RailsAdmin::Config.instance_variable_set(:@deferred_blocks, [])
    end

    it "doesn't evaluate the block immediately" do
      expect_any_instance_of(RailsAdmin::Config::Model).not_to receive(:register_instance_option)
      subject
    end

    it 'evaluates block when initialize! is finished' do
      expect_any_instance_of(RailsAdmin::Config::Model).to receive(:register_instance_option).with('parameter')
      subject
      RailsAdmin::Config.initialize!
    end

    it 'evaluates config block only once' do
      expect_any_instance_of(RailsAdmin::Config::Model).to receive(:register_instance_option).once.with('parameter')
      subject
      RailsAdmin::Config.initialize!
      RailsAdmin::Config.initialize!
    end

    context 'with a non-existent class' do
      let(:block) do
        proc do |config|
          config.model UnknownClass do
            field :name
          end
        end
      end

      it "doesn't break immediately" do
        subject
      end
    end
  end

  describe '.reload!' do
    before do
      RailsAdmin.config Player do
        field :name
      end
      RailsAdmin.config Team do
        field :color, :hidden
      end
    end

    it 'clears current configuration' do
      RailsAdmin::Config.reload!
      expect(RailsAdmin::Config.model(Player).fields.map(&:name)).to include :number
    end

    it 're-applies the configuration from the initializer' do
      RailsAdmin::Config.reload!
      expect(RailsAdmin::Config.model(Team).fields.find { |f| f.name == :color }.type).to eq :color
    end

    it "applies the initializer's configuration first, then models' configurations" do
      # simulate the situation that Team model is loaded in the middle of processing RailsAdmin initializer
      allow_any_instance_of(RailsAdmin::Config::Model).to receive(:include_all_fields).and_wrap_original do |method|
        Team.rails_admin do
          field :color, :integer
        end
        method.call
      end

      RailsAdmin::Config.reload!
      expect(RailsAdmin::Config.model(Team).fields.find { |f| f.name == :color }.type).to eq :integer
    end
  end
end

module ExampleModule
  class AuthorizationAdapter; end

  class ConfigurationAdapter; end

  class AuditingAdapter; end
end
