require 'spec_helper'
require 'support/example_classes'

describe Authority::Abilities do

  let(:user)                      { ExampleUser.new }
  let(:resource_class)            { ExampleResource }
  let(:namespaced_resource_class) { Namespaced::SampleResource }
  let(:other_resource_class)      { OtherResource }

  describe "instance methods" do

    describe "authorizer_name" do

      it "has a class attribute getter" do
        expect(resource_class).to respond_to(:authorizer_name)
      end

      it "has a class attribute setter" do
        expect(resource_class).to respond_to(:authorizer_name=)
      end

      describe "by default" do

        context "when there is an authorizer with a name like the resource's" do

          it "uses that authorizer" do
            expect(resource_class.authorizer_name).to eq("ExampleResourceAuthorizer")
          end

          it "respects namespaces when it's looking" do
            expect(namespaced_resource_class.authorizer_name).to eq("Namespaced::SampleResourceAuthorizer")
          end

        end

        context "when there is no authorizer with a name like the resource's" do

          it "uses 'ApplicationAuthorizer'" do
            expect(other_resource_class.authorizer_name).to eq("ApplicationAuthorizer")
          end

        end

      end

    end

    describe "authorizer=" do

      let(:test_class)  { Class.new {include Authority::Abilities} }

      it "has a class attribute setter" do
        expect(test_class).to respond_to(:authorizer=)
      end

      it "sets authorizer" do
        test_class.authorizer = ExampleResourceAuthorizer
        expect(test_class.authorizer).to eq(ExampleResourceAuthorizer)
      end

      it "also sets authorizer_name" do
        test_class.authorizer_name = 'FooAuthorizer'
        test_class.authorizer      = ExampleResourceAuthorizer
        expect(test_class.authorizer_name).to eq("ExampleResourceAuthorizer")
      end

    end

    describe "authorizer" do

      it "constantizes the authorizer name as the authorizer" do
        resource_class.instance_variable_set(:@authorizer, nil)
        expect(resource_class.authorizer_name).to receive(:constantize)
        resource_class.authorizer
      end

      it "memoizes the authorizer to avoid reconstantizing" do
        resource_class.authorizer
        expect(resource_class.authorizer_name).not_to receive(:constantize)
        resource_class.authorizer
      end

      it "raises a friendly error if the authorizer doesn't exist" do
        class NoAuthorizerModel < resource_class; end ;
        NoAuthorizerModel.instance_variable_set(:@authorizer, nil)
        NoAuthorizerModel.authorizer_name = 'NonExistentAuthorizer'
        expect { NoAuthorizerModel.authorizer }.to raise_error(Authority::NoAuthorizerError)
      end

    end

  end

  describe "class methods" do

    Authority.adjectives.each do |adjective|
      method_name = "#{adjective}_by?"

      it "responds to `#{method_name}`" do
        expect(resource_class).to respond_to(method_name)
      end

      describe "#{method_name}" do

        context "when given an options hash" do

          it "delegates `#{method_name}` to its authorizer class, passing the options" do
            expect(resource_class.authorizer).to receive(method_name).with(user, :lacking => 'nothing')
            resource_class.send(method_name, user, :lacking => 'nothing')
          end

        end

        context "when not given an options hash" do

          it "delegates `#{method_name}` to its authorizer class, passing no options" do
            expect(resource_class.authorizer).to receive(method_name).with(user)
            resource_class.send(method_name, user)
          end

        end

      end

    end

  end

  describe "instance methods" do

    let(:resource_instance) { resource_class.new }

    before :each do
      @authorizer = resource_class.authorizer.new(resource_instance)
    end

    Authority.adjectives.each do |adjective|
      method_name = "#{adjective}_by?"

      it "responds to `#{method_name}`" do
        expect(resource_instance).to respond_to(method_name)
      end

      describe "#{method_name}" do

        context "when given an options hash" do

          it "delegates `#{method_name}` to a new authorizer instance, passing the options" do
            allow(resource_class.authorizer).to receive(:new).and_return(@authorizer)
            expect(@authorizer).to receive(method_name).with(user, :with => 'mayo')
            resource_instance.send(method_name, user, :with => 'mayo')
          end

        end

        context "when not given an options hash" do

          it "delegates `#{method_name}` to a new authorizer instance, passing no options" do
            allow(resource_class.authorizer).to receive(:new).and_return(@authorizer)
            expect(@authorizer).to receive(method_name).with(user)
            resource_instance.send(method_name, user)
          end

        end

      end

    end

    it "provides an accessor for its authorizer" do
      expect(resource_instance).to respond_to(:authorizer)
    end

    # When checking instance methods, we want to ensure that every check uses a new
    # instance of the authorizer. Otherwise, you might check, make a change to the
    # model instance, check again, and get an outdated answer.
    it "always creates a new authorizer instance when accessing the authorizer" do
      expect(resource_instance.class.authorizer).to receive(:new).with(resource_instance).twice
      2.times { resource_instance.authorizer }
    end

  end

end
