require 'spec_helper'
require 'pact/provider/state/provider_state'

module Pact
  module Provider::State
    describe ProviderStates do

      module ProviderStatesHelper
        def help_me_set_up
          "set up helped"
        end

        def help_me_tear_down
          "tear down helped"
        end
      end

      MESSAGES = []

      let(:params) { { "foo" => "bar" } }

      before do
        MESSAGES.clear
        Pact.configuration.include ProviderStatesHelper
      end

      describe 'with an included module' do
        Pact.provider_state :alligators_with_helper_module do
          set_up do
            MESSAGES << help_me_set_up
          end
          tear_down do
            MESSAGES << help_me_tear_down
          end
        end

        subject { ProviderStates.get('alligators_with_helper_module') }

        it 'makes the helper module available to the set up block' do
          subject.set_up
          expect(MESSAGES).to eq ["set up helped"]
        end

        it 'makes the helper module available to the tear down block' do
          subject.tear_down
          expect(MESSAGES).to eq ["tear down helped"]
        end
      end

      describe 'with params' do
        Pact.provider_state :alligators_with_params do
          set_up do | params |
            MESSAGES << { set_up: params }
          end
          tear_down do | params |
            MESSAGES << { tear_down: params }
          end
        end

        subject { ProviderStates.get('alligators_with_params') }

        it 'calls the block passed to set_up with the params as an argument' do
          subject.set_up(params)
          expect(MESSAGES).to eq [{ set_up: params }]
        end

        it 'calls the block passed to tear_down with the params as an argument' do
          subject.tear_down(params)
          expect(MESSAGES).to eq [{ tear_down: params }]
        end
      end

      describe 'with a variable delcared outside the block' do

        variable_declared_outside = "foo"

        Pact.provider_state :alligators_with_variable_declared_out_of_scope do
          set_up do | params |
            MESSAGES << variable_declared_outside
          end
        end

        subject { ProviderStates.get('alligators_with_variable_declared_out_of_scope') }

        it 'calls the block passed to set_up with the params as an argument' do
          subject.set_up(params)
          expect(MESSAGES).to eq ["foo"]
        end
      end

      describe 'global ProviderState' do

        Pact.provider_state :no_alligators do
          set_up do
            MESSAGES << 'set_up'
          end
          tear_down do
            MESSAGES << 'tear_down'
          end
        end

        Pact.provider_state 'some alligators' do
          no_op
        end

        subject { ProviderStates.get('no_alligators') }

        describe 'set_up' do
          it 'should call the block passed to set_up' do
            subject.set_up
            expect(MESSAGES).to eq ['set_up']
          end
        end

        describe 'tear_down' do
          it 'should call the block passed to set_up' do
            subject.tear_down
            expect(MESSAGES).to eq ['tear_down']
          end
        end

        describe '.get' do
          context 'when the name is a matching symbol' do
            it 'will return the ProviderState' do
              expect(ProviderStates.get('no_alligators')).to_not be_nil
            end
          end

          context 'when the name is a matching string' do
            it 'will return the ProviderState' do
              expect(ProviderStates.get('some alligators')).to_not be_nil
            end
          end
        end
      end

      describe 'no_op' do
        context "when a no_op is defined instead of a set_up or tear_down" do
          it "treats set_up and tear_down as empty blocks" do
            Pact.provider_state 'with_no_op' do
              no_op
            end
            ProviderStates.get('with_no_op').set_up
            ProviderStates.get('with_no_op').tear_down
          end
        end

        context "when a no_op is defined with a set_up" do
          it "raises an error" do
            expect do
              Pact.provider_state 'with_no_op_and_set_up' do
                no_op
                set_up do

                end
              end.to raise_error(/Provider state \"with_no_op_and_set_up\" has been defined as a no_op but it also has a set_up block. Please remove one or the other./)
            end
          end
        end

        context "when a no_op is defined with a tear_down" do
          it "raises an error" do
            expect do
              Pact.provider_state 'with_no_op_and_set_up' do
                no_op
                tear_down do

                end
              end.to raise_error(/Provider state \"with_no_op_and_set_up\" has been defined as a no_op but it also has a tear_down block. Please remove one or the other./)
            end
          end
        end

      end

      describe 'namespaced ProviderStates' do

        NAMESPACED_MESSAGES = []

        Pact.provider_states_for 'a consumer' do
          provider_state 'the weather is sunny' do
            set_up do
              NAMESPACED_MESSAGES << 'sunny!'
            end
          end
        end

        Pact.provider_state 'the weather is cloudy' do
          set_up do
            NAMESPACED_MESSAGES << 'cloudy :('
          end
        end

        before do
          NAMESPACED_MESSAGES.clear
        end

        describe '.get' do
          context 'for a consumer' do
            it 'has a namespaced name' do
              expect(ProviderStates.get('the weather is sunny', for: 'a consumer')).to_not be_nil
            end

            it 'falls back to a global state of the same name if one is not found for the specified consumer' do
              expect(ProviderStates.get('the weather is cloudy', for: 'a consumer')).to_not be_nil
            end
          end

        end

        describe 'set_up' do
          context 'for a consumer' do
            it 'runs its own setup' do
              ProviderStates.get('the weather is sunny', for: 'a consumer').set_up
              expect(NAMESPACED_MESSAGES).to eq ['sunny!']
            end
          end
        end
      end

      describe "base_provider_state" do
        Pact.provider_states_for "a consumer with base state" do
          set_up do
            MESSAGES << "setting up base provider state"
          end
        end

        context "when the base state has been declared" do
          it "creates a base state for the provider" do
            ProviderStates.get_base(for: "a consumer with base state").set_up
            expect(MESSAGES).to eq ["setting up base provider state"]
          end
        end

        context "when a base state has not been declared" do
          it "returns a no op state" do
            ProviderStates.get_base(for: "a consumer that does not exist").set_up
            ProviderStates.get_base(for: "a consumer that does not exist").tear_down
          end
        end

      end

      describe "base_provider_state with a teardown and no setup" do
        Pact.provider_states_for "a consumer with base state with only a tear down" do
          tear_down do
            MESSAGES << "tearing down without a set up"
          end
        end

        it "creates a base state for the provider" do
          ProviderStates.get_base(for: "a consumer with base state with only a tear down").tear_down
          expect(MESSAGES).to eq ["tearing down without a set up"]
        end
      end

      describe "global base_provider_state" do

        before(:all) do
          Pact.set_up do
            MESSAGES << "setting up global base provider state"
          end
        end

        context "when the base state has been declared" do
          it "creates a base state for the provider" do
            ProviderStates.get_base.set_up
            expect(MESSAGES).to eq ["setting up global base provider state"]
          end

        end

        context "when a base state has not been declared" do
          it "returns a no op state" do
            ProviderStates.get_base.set_up
            ProviderStates.get_base.tear_down
          end
        end
      end

      describe "invalid provider state" do
        context "when no set_up or tear_down is provided" do
          it "raises an error to prevent someone forgetting about the set_up and putting the set_up code directly in the provider_state block and wasting 20 minutes trying to work out why their provider states aren't working properly" do
            expect do
              Pact.provider_state 'invalid' do
              end
            end.to raise_error(/Please provide a set_up or tear_down block for provider state \"invalid\"/)
          end
        end

        context "when a no_op is defined" do
          it "does not raise an error" do
            expect do
              Pact.provider_state 'valid' do
                no_op
              end
            end.not_to raise_error
          end
        end
      end
    end
  end
end
