# encoding: utf-8

require 'spec_helper'

module Adhearsion
  class Router
    describe Route do
      describe 'a new route' do
        let(:name) { 'catchall' }
        let(:guards) do
          [
            {:to => /foobar/},
            [{:from => 'fred'}, {:from => 'paul'}]
          ]
        end

        subject { Route.new name }

        it { is_expected.not_to be_evented }
        it { is_expected.to be_accepting }

        describe "with a class target and guards" do
          let(:target) { CallController }

          subject { Route.new name, target, *guards }

          describe '#name' do
            subject { super().name }
            it { is_expected.to eq(name) }
          end

          describe '#target' do
            subject { super().target }
            it { is_expected.to eq(target) }
          end

          describe '#guards' do
            subject { super().guards }
            it { is_expected.to eq(guards) }
          end
        end

        describe "with a block target and guards" do
          subject { Route.new(name, *guards) { :foo } }

          describe '#name' do
            subject { super().name }
            it { is_expected.to eq(name) }
          end

          describe '#target' do
            subject { super().target }
            it { is_expected.to be_a Proc }
          end

          describe '#guards' do
            subject { super().guards }
            it { is_expected.to eq(guards) }
          end
        end
      end

      describe "a guarded route" do
        subject { Route.new 'foobar', CallController, *guards }

        def should_match_the_call
          expect(subject.match?(call)).to be true
        end

        def should_not_match_the_call
          expect(subject.match?(call)).to be false
        end

        describe "matching calls from fred to paul" do
          let :guards do
            [
              {:from => 'fred', :to => 'paul'}
            ]
          end

          let :offer do
            Adhearsion::Event::Offer.new :to => to, :from => from
          end

          let(:call) { Adhearsion::Call.new offer }

          context "with a call from fred to paul" do
            let(:from)  { 'fred' }
            let(:to)    { 'paul' }
            it { should_match_the_call }
          end

          context "with a call from fred to frank" do
            let(:from)  { 'fred' }
            let(:to)    { 'frank' }
            it { should_not_match_the_call }
          end

          context "with a call from frank to paul" do
            let(:from)  { 'frank' }
            let(:to)    { 'paul' }
            it { should_not_match_the_call }
          end
        end

        describe "matching calls with the variable :foo=:bar" do
          let :guards do
            [[:[], :foo] => :bar]
          end

          let(:call) { Adhearsion::Call.new }

          context "with :foo=:bar" do
            before { call[:foo] = :bar }
            it { should_match_the_call }
          end

          context "with :foo=:baz" do
            before { call[:foo] = :baz }
            it { should_not_match_the_call }
          end

          context "with :foo unset" do
            it { should_not_match_the_call }
          end
        end
      end

      describe "dispatching a call" do
        let(:call) { Call.new }

        let(:latch) { CountDownLatch.new 1 }

        before { allow(call.wrapped_object).to receive :write_and_await_response }

        context "via a call controller" do
          let(:controller)  { CallController }
          let(:route)       { Route.new 'foobar', controller }

          it "should immediately fire the :call_routed event giving the call and route" do
            expect(Adhearsion::Events).to receive(:trigger).once.with(:call_routed, call: call, route: route)
            expect(call).to receive(:hangup).once
            route.dispatch call, lambda { latch.countdown! }
            expect(latch.wait(2)).to be true
          end

          it "should accept the call" do
            expect(call).to receive(:accept).once
            route.dispatch call, lambda { latch.countdown! }
            expect(latch.wait(2)).to be true
          end

          it "should instruct the call to use an instance of the controller" do
            expect(call).to receive(:execute_controller).once.with kind_of(controller), kind_of(Proc)
            route.dispatch call
          end

          it "should hangup the call after all controllers have executed" do
            expect(call).to receive(:hangup).once
            route.dispatch call, lambda { latch.countdown! }
            expect(latch.wait(2)).to be true
          end

          context "when the call has already ended before routing can begin" do
            before { Celluloid::Actor.kill call }

            it "should raise a hangup exception" do
              expect { route.dispatch call }.to raise_error(Call::ExpiredError)
            end
          end

          context "when the CallController mutates its metadata" do
            let :controller do
              Class.new CallController do
                def run
                  metadata[:foo] = 'bar'
                end
              end
            end

            before do
              route.dispatch call
            end

            it "gives the next call fresh metadata" do
              expected_controller = controller.new call, nil
              expect(call).to receive(:execute_controller).once.with expected_controller, kind_of(Proc)
              route.dispatch call
            end
          end

          context 'with the Call#auto_hangup set to false' do
            before { call.auto_hangup = false }

            it "should not hangup the call after controller execution" do
              expect(call).to receive(:hangup).never
              route.dispatch call, lambda { latch.countdown! }
              expect(latch.wait(2)).to be true
            end
          end

          context "if hangup raises a Call::Hangup" do
            before { expect(call).to receive(:hangup).once.and_raise Call::Hangup }

            it "should not raise an exception" do
              expect do
                route.dispatch call, lambda { latch.countdown! }
                expect(latch.wait(2)).to be true
              end.not_to raise_error
            end
          end

          context "if the call is dead when trying to clear it up" do
            let :controller do
              Class.new CallController do
                def run
                  call.terminate
                end
              end
            end

            it "should not raise an exception" do
              expect do
                route.dispatch call, lambda { latch.countdown! }
                expect(latch.wait(2)).to be true
              end.not_to raise_error
            end
          end
        end

        context "via a block" do
          let :route do
            Route.new 'foobar' do
              :foobar
            end
          end

          it "should instruct the call to use a CallController with the correct block" do
            expect(call).to receive(:execute_controller).once.with(kind_of(CallController), kind_of(Proc)) do |controller|
              expect(controller.block.call).to eq(:foobar)
            end
            route.dispatch call
          end
        end
      end
    end
  end
end
