require 'spec_helper'
require 'cancan/matchers'

describe Ability do
  before do
    allow(Devise).to receive(:authentication_keys).and_return(['uid'])
  end
  describe "class methods" do
    subject { Ability }
    its(:read_group_field) { should == 'read_access_group_ssim'}
    its(:read_user_field) { should == 'read_access_person_ssim'}
    its(:edit_group_field) { should == 'edit_access_group_ssim'}
    its(:edit_user_field) { should == 'edit_access_person_ssim'}
    its(:discover_group_field) { should == 'discover_access_group_ssim'}
    its(:discover_user_field) { should == 'discover_access_person_ssim'}
  end

  subject { Ability.new(user) }

  context "for a not-signed in user" do
    before do
      allow_any_instance_of(User).to receive(:email).and_return(nil)
      allow_any_instance_of(User).to receive(:new_record?).and_return(true)
    end
    let(:user) { nil }
    it "calls custom_permissions" do
      expect_any_instance_of(Ability).to receive(:custom_permissions)
      subject.can?(:delete, 7)
    end
    it { should_not be_able_to(:create, ActiveFedora::Base) }
  end

  context "for a signed in user" do
    let(:user) { FactoryBot.build(:registered_user) }

    it { should_not be_able_to(:create, ActiveFedora::Base) }
  end


# NOTES:
#   See spec/requests/... for test coverage describing WHAT should appear on a page based on access permissions
#   Test coverage for discover permission is in spec/requests/gated_discovery_spec.rb

  describe "Given an asset that has been made publicly discoverable" do
    let(:asset) { FactoryBot.create(:asset) }
    before do
      asset.permissions_attributes = [{ name: "public", access: "discover", type: "group" }, { name: "joe_creator", access: "edit", type: "person" }, { name: "calvin_collaborator", access: "edit", type: "person" }]
      asset.save
    end

    context "Then a not-signed-in user" do
      let(:user) { nil }
      it { should     be_able_to(:discover, asset) }
      it { should_not be_able_to(:read, asset) }
      it { should_not be_able_to(:edit, asset) }
      it { should_not be_able_to(:update, asset) }
      it { should_not be_able_to(:destroy, asset) }
    end

    context "Then a registered user" do
      let(:user) { FactoryBot.build(:registered_user) }
      it { should     be_able_to(:discover, asset) }
      it { should_not be_able_to(:read, asset) }
      it { should_not be_able_to(:edit, asset) }
      it { should_not be_able_to(:update, asset) }
      it { should_not be_able_to(:destroy, asset) }
    end
  end

  describe "Given an asset that has been made publicly available (ie. open access)" do
    #let(:asset) { FactoryBot.create(:open_access_asset) }
    let(:asset) { FactoryBot.create(:asset) }
    before do
      asset.permissions_attributes = [{ name: "public", access: "read", type: "group" }, { name: "joe_creator", access: "edit", type: "person" }, { name: "calvin_collaborator", access: "edit", type: "person" }]
      asset.save
    end

    context "Then a not-signed-in user" do
      let(:user) { nil }
      it { should     be_able_to(:discover, asset) }
      it { should     be_able_to(:read, asset) }
      it { should_not be_able_to(:edit, asset) }
      it { should_not be_able_to(:update, asset) }
      it { should_not be_able_to(:destroy, asset) }
    end

    context "Then a registered user" do
      let(:user) { FactoryBot.build(:registered_user) }
      it { should     be_able_to(:discover, asset) }
      it { should     be_able_to(:read, asset) }
      it { should_not be_able_to(:edit, asset) }
      it { should_not be_able_to(:update, asset) }
      it { should_not be_able_to(:destroy, asset) }
    end
  end

  describe "Given an asset with no custom access set" do
    let(:asset) { FactoryBot.create(:asset) }
    before do
      asset.permissions_attributes = [{ name: "joe_creator", access: "edit", type: "person" }]
      asset.save
    end
    let(:solr_doc) { SolrDocument.new(asset.to_solr.merge(id: asset.id)) }
    context "Then a not-signed-in user" do
      let(:user) { User.new }
      it { should_not be_able_to(:discover, asset) }
      it { should_not be_able_to(:read, asset) }
      it { should_not be_able_to(:edit, asset) }
      it { should_not be_able_to(:update, asset) }
      it { should_not be_able_to(:destroy, asset) }
    end
    context "Then a registered user" do
      let(:user) { FactoryBot.build(:registered_user) }
      it { should_not be_able_to(:discover, asset) }
      it { should_not be_able_to(:read, asset) }
      it { should_not be_able_to(:edit, asset) }
      it { should_not be_able_to(:update, asset) }
      it { should_not be_able_to(:destroy, asset) }
    end
    context "Then the Creator" do
      let(:user) { FactoryBot.build(:joe_creator) }
      it { should     be_able_to(:discover, asset) }
      it { should     be_able_to(:read, asset) }
      it { should     be_able_to(:edit, asset) }
      it { should     be_able_to(:edit, solr_doc) }
      it { should     be_able_to(:update, asset) }
      it { should     be_able_to(:update, solr_doc) }
      it { should     be_able_to(:destroy, asset) }
      it { should     be_able_to(:destroy, solr_doc) }
      it { should_not be_able_to(:admin, asset) }
    end
  end

  describe "Given an asset which registered users have read access to" do
    # let(:asset) { FactoryBot.create(:org_read_access_asset) }
    let(:asset) { FactoryBot.create(:asset) }
    before do
      asset.permissions_attributes = [{ name: "registered", access: "read", type: "group" }, { name: "joe_creator", access: "edit", type: "person" }, { name: "calvin_collaborator", access: "edit", type: "person" }]
      asset.save
    end
    context "The a registered user" do
      let(:user) { FactoryBot.build(:registered_user) }
      before do
        allow(user).to receive(:new_record?).and_return(false)
      end

      it { should     be_able_to(:discover, asset) }
      it { should     be_able_to(:read, asset) }
      it { should_not be_able_to(:edit, asset) }
      it { should_not be_able_to(:update, asset) }
      it { should_not be_able_to(:destroy, asset) }
      it { should_not be_able_to(:admin, asset) }
    end
  end

  describe "Given an asset with collaborator" do
    let(:asset) { FactoryBot.create(:asset) }
    before do
      asset.permissions_attributes = [{ name:"africana-faculty", access: "edit", type: "group" }, {name: "calvin_collaborator", access: "edit", type: "person"}]
      asset.save
    end
    after { asset.destroy }

    context "Then a collaborator with edit access (user permision)" do
      let(:user) { FactoryBot.build(:calvin_collaborator) }

      it { should     be_able_to(:discover, asset) }
      it { should     be_able_to(:read, asset) }
      it { should     be_able_to(:edit, asset) }
      it { should     be_able_to(:update, asset) }
      it { should     be_able_to(:destroy, asset) }
      it { should_not be_able_to(:admin, asset) }
    end

    context "Then a collaborator with edit access (group permision)" do
      let(:user) { FactoryBot.build(:martia_morocco) }
      before do
        allow(user).to receive(:groups).and_return(["faculty", "africana-faculty"])
      end

      it { should be_able_to(:read, asset) }
    end
  end

  describe "Given an asset where dept can read & registered users can discover" do
    # let(:asset) { FactoryBot.create(:dept_access_asset) }
    let(:asset) { FactoryBot.create(:asset) }
    before do
      asset.permissions_attributes = [{ name: "africana-faculty", access: "read", type: "group" }, { name: "joe_creator", access: "edit", type: "person" }]
      asset.save
    end
    context "Then a registered user" do
      let(:user) { FactoryBot.build(:registered_user) }

      it { should_not be_able_to(:discover, asset) }
      it { should_not be_able_to(:read, asset) }
      it { should_not be_able_to(:edit, asset) }
      it { should_not be_able_to(:update, asset) }
      it { should_not be_able_to(:destroy, asset) }
      it { should_not be_able_to(:admin, asset) }
    end

    context "Then someone whose role/group has read access" do
      let(:user) { FactoryBot.build(:martia_morocco) }
      before do
        allow(user).to receive(:groups).and_return(["faculty", "africana-faculty"])
      end

      it { should     be_able_to(:discover, asset) }
      it { should     be_able_to(:read, asset) }
      it { should_not be_able_to(:edit, asset) }
      it { should_not be_able_to(:update, asset) }
      it { should_not be_able_to(:destroy, asset) }
      it { should_not be_able_to(:admin, asset) }
    end
  end


  describe "custom method" do
    before do
      class MyAbility
        include Blacklight::AccessControls::Ability
        include Hydra::Ability
        self.ability_logic +=[:setup_my_permissions]

        def setup_my_permissions
          can :accept, ActiveFedora::Base
        end
      end
    end
    let(:user) { FactoryBot.build(:staff) }

    after do
      Object.send(:remove_const, :MyAbility)
    end

    subject { MyAbility.new(user) }

    it { should be_able_to(:accept, ActiveFedora::Base) }

  end

  describe "calling ability on two separate objects" do
    let(:asset1) { FactoryBot.create(:asset) }
    let(:asset2) { FactoryBot.create(:asset) }
    before do
      asset1.permissions_attributes = [{ name: "registered", access: "read", type: "group" }, { name: "joe_creator", access: "edit", type: "person" }, { name: "calvin_collaborator", access: "edit", type: "person" }]
      asset1.save
    end
    let(:user) { FactoryBot.build(:calvin_collaborator) } # has access to @asset1, but not @asset2
    after do
      asset1.destroy
      asset2.destroy
    end

    it "is readable in the first instance and not in the second instance" do
      # We had a bug around this where it keeps returning the access for the first object queried
      expect(subject).to be_able_to(:edit, asset1)
      expect(subject).to_not be_able_to(:edit, asset2)
    end
  end

  describe "download permissions" do
    let(:asset) { FactoryBot.create(:asset) }
    let(:user) { FactoryBot.build(:user) }
    let(:file) { ActiveFedora::File.new() }

    before { allow(file).to receive(:uri).and_return(uri) }
    after { asset.destroy }

    context "in AF < 9.2" do
      let(:uri) { "#{asset.uri}/ds1" }

      context "user has read permission on the object" do
        before do
          asset.read_users = [user.user_key]
          asset.save!
        end

        it { should be_able_to(:read, asset.id) }
        it { should be_able_to(:download, file) }
      end

      context "user lacks read permission on the object and file" do
        it { should_not be_able_to(:read, asset) }
        it { should_not be_able_to(:download, file) }
      end
    end

    context "in AF >= 9.2" do
      let(:uri) { RDF::URI("#{asset.uri}/ds1") }

      context "user has read permission on the object" do
        before do
          asset.read_users = [user.user_key]
          asset.save!
        end

        it { should be_able_to(:read, asset.id) }
        it { should be_able_to(:download, file) }
      end
    end
  end
end
