require 'spec_helpers/boot'

if defined?(ActiveRecord)
  load 'spec_helpers/adapters/active_record.rb'

  describe Protector::Adapters::ActiveRecord do
    before(:all) do
      load 'migrations/active_record.rb'

      module ProtectionCase
        extend ActiveSupport::Concern

        included do |klass|
          protect do |x|
            if x == '-'
              scope{ where('1=0') } 
            elsif x == '+'
              scope{ where(klass.table_name => {number: 999}) }
            elsif !Protector.config.paranoid && Protector::Adapters::ActiveRecord.modern?
              scope { all }
            end

            can :view, :dummy_id unless x == '-'
          end
        end
      end

      [Dummy, Fluffy].each{|c| c.send :include, ProtectionCase}

      Dummy.create! string: 'zomgstring', number: 999, text: 'zomgtext'
      Dummy.create! string: 'zomgstring', number: 999, text: 'zomgtext'
      Dummy.create! string: 'zomgstring', number: 777, text: 'zomgtext'
      Dummy.create! string: 'zomgstring', number: 777, text: 'zomgtext'

      [Fluffy, Bobby].each do |m|
        m.create! string: 'zomgstring', number: 999, text: 'zomgtext', dummy_id: 1
        m.create! string: 'zomgstring', number: 777, text: 'zomgtext', dummy_id: 1
        m.create! string: 'zomgstring', number: 999, text: 'zomgtext', dummy_id: 2
        m.create! string: 'zomgstring', number: 777, text: 'zomgtext', dummy_id: 2
      end

      Fluffy.all.each{|f| Loony.create! fluffy_id: f.id, string: 'zomgstring' }
    end

    describe Protector::Adapters::ActiveRecord do
      it "finds out whether object is AR relation" do
        Protector::Adapters::ActiveRecord.is?(Dummy).should == true
        Protector::Adapters::ActiveRecord.is?(Dummy.every).should == true
      end

      it "sets the adapter" do
        Dummy.restrict!('!').protector_meta.adapter.should == Protector::Adapters::ActiveRecord
      end
    end

    #
    # Model instance
    #
    describe Protector::Adapters::ActiveRecord::Base do
      let(:dummy) do
        Class.new(ActiveRecord::Base) do
          def self.model_name; ActiveModel::Name.new(self, nil, "dummy"); end
          self.table_name = "dummies"
          scope :none, where('1 = 0') unless respond_to?(:none)
        end
      end

      it "includes" do
        Dummy.ancestors.should include(Protector::Adapters::ActiveRecord::Base)
      end

      it "scopes" do
        scope = Dummy.restrict!('!')
        scope.should be_a_kind_of ActiveRecord::Relation
        scope.protector_subject.should == '!'
      end

      it_behaves_like "a model"

      it "validates on create" do
        dummy.instance_eval do
          protect do; end
        end

        instance = dummy.restrict!('!').create(string: 'test')
        instance.errors[:base].should == ["Access denied to 'string'"]
        instance.delete
      end

      it "validates on create!" do
        dummy.instance_eval do
          protect do; end
        end

        expect { dummy.restrict!('!').create!(string: 'test').delete }.to raise_error
      end
    end

    #
    # Model scope
    #
    describe Protector::Adapters::ActiveRecord::Relation do
      it "includes" do
        Dummy.none.ancestors.should include(Protector::Adapters::ActiveRecord::Base)
      end

      it "saves subject" do
        Dummy.restrict!('!').where(number: 999).protector_subject.should == '!'
        Dummy.restrict!('!').except(:order).protector_subject.should == '!'
        Dummy.restrict!('!').only(:order).protector_subject.should == '!'
      end

      it "forwards subject" do
        Dummy.restrict!('!').where(number: 999).first.protector_subject.should == '!'
        Dummy.restrict!('!').where(number: 999).to_a.first.protector_subject.should == '!'
        Dummy.restrict!('!').new.protector_subject.should == '!'
      end

      it "checks creatability" do
        Dummy.restrict!('!').creatable?.should == false
        Dummy.restrict!('!').where(number: 999).creatable?.should == false
      end

      context "with open relation" do
        context "adequate", paranoid: false do
          it "checks existence" do
            Dummy.any?.should == true
            Dummy.restrict!('!').any?.should == true
          end

          it "counts" do
            Dummy.count.should == 4
            Dummy.restrict!('!').count.should == 4
          end

          it "fetches" do
            fetched = Dummy.restrict!('!').to_a

            Dummy.count.should == 4
            fetched.length.should == 4
          end
        end

        context "paranoid", paranoid: true do
          it "checks existence" do
            Dummy.any?.should == true
            Dummy.restrict!('!').any?.should == false
          end

          it "counts" do
            Dummy.count.should == 4
            Dummy.restrict!('!').count.should == 0
          end

          it "fetches" do
            fetched = Dummy.restrict!('!').to_a

            Dummy.count.should == 4
            fetched.length.should == 0
          end
        end
      end

      context "with null relation" do
        it "checks existence" do
          Dummy.any?.should == true
          Dummy.restrict!('-').any?.should == false
        end

        it "counts" do
          Dummy.count.should == 4
          Dummy.restrict!('-').count.should == 0
        end

        it "fetches" do
          fetched = Dummy.restrict!('-').to_a

          Dummy.count.should == 4
          fetched.length.should == 0
        end

        it "keeps security scope when unscoped" do
          Dummy.unscoped.restrict!('-').count.should == 0
          Dummy.restrict!('-').unscoped.count.should == 0
        end
      end

      context "with active relation" do
        it "checks existence" do
          Dummy.any?.should == true
          Dummy.restrict!('+').any?.should == true
        end

        it "counts" do
          Dummy.count.should == 4
          Dummy.restrict!('+').count.should == 2
        end

        it "fetches" do
          fetched = Dummy.restrict!('+').to_a

          Dummy.count.should == 4
          fetched.length.should == 2
        end

        it "keeps security scope when unscoped" do
          Dummy.unscoped.restrict!('+').count.should == 2
          Dummy.restrict!('+').unscoped.count.should == 2
        end
      end
    end

    #
    # Eager loading
    #
    describe Protector::Adapters::ActiveRecord::Preloader do
      describe "eager loading" do
        it "scopes" do
          d = Dummy.restrict!('+').includes(:fluffies)
          d.length.should == 2
          d.first.fluffies.length.should == 1
        end

        context "joined to filtered association" do
          it "scopes" do
            d = Dummy.restrict!('+').includes(:fluffies).where(fluffies: {string: 'zomgstring'})
            d.length.should == 2
            d.first.fluffies.length.should == 1
          end
        end

        context "joined to plain association" do
          it "scopes" do
            d = Dummy.restrict!('+').includes(:bobbies, :fluffies).where(
              bobbies: {string: 'zomgstring'}, fluffies: {string: 'zomgstring'}
            )
            d.length.should == 2
            d.first.fluffies.length.should == 1
            d.first.bobbies.length.should == 2
          end
        end

        context "with complex include" do
          it "scopes" do
            d = Dummy.restrict!('+').includes(fluffies: :loony).where(
              fluffies: {string: 'zomgstring'},
              loonies: {string: 'zomgstring'}
            )
            d.length.should == 2
            d.first.fluffies.length.should == 1
            d.first.fluffies.first.loony.should be_a_kind_of(Loony)
          end
        end
      end

      context "complicated features" do
        # https://github.com/inossidabile/protector/commit/7ce072aa2074e0f3b48e293b952810f720bc143d
        it "handles scopes with includes" do
          fluffy = Class.new(ActiveRecord::Base) do
            def self.name; 'Fluffy'; end
            def self.model_name; ActiveModel::Name.new(self, nil, "fluffy"); end
            self.table_name = "fluffies"
            scope :none, where('1 = 0') unless respond_to?(:none)
            belongs_to :dummy, class_name: 'Dummy'

            protect do
              scope { includes(:dummy).where(dummies: {id: 1}) }
            end
          end

          expect { fluffy.restrict!('!').to_a }.to_not raise_error
        end
      end
    end
  end

end