require 'spec_helper'

#require 'amqp-spec/rspec'
#require 'amqp'

module MockClientModule
  def my_mock_method
  end
end

class MockClient
  include AMQP::Client
end

describe AMQP, 'as a class' do

  context 'has class accessors, with default values' do
    subject { AMQP }

    its(:logging) { should be_false }
    its(:connection) { should be_nil }
    its(:conn) { should be_nil } # Alias for #connection
    its(:closing) { should be_false }
    its(:settings) { should == {:host=>"127.0.0.1",
                                :port=>5672,
                                :user=>"guest",
                                :pass=>"guest",
                                :vhost=>"/",
                                :timeout=>nil,
                                :logging=>false,
                                :ssl=>false} }
    its(:client) { should == AMQP::BasicClient }

  end

  describe '.client=' do
    after(:all) { AMQP.client = AMQP::BasicClient }

    it 'is used to change default client module' do
      AMQP.client = MockClientModule
      AMQP.client.should == MockClientModule
      MockClientModule.ancestors.should include AMQP
    end

    describe 'new default client module' do
      it 'sticks around after being assigned' do
        AMQP.client.should == MockClientModule
      end

      it 'extends any object that includes AMQP::Client' do
        @client = MockClient.new
        @client.should respond_to :my_mock_method
      end
    end
  end

  describe '.logging=' do
    it 'changes logging setting' do
      pending 'require "pp"; pp XX - not sure how to intercept'
    end
  end

  describe '.connect' do
    it 'delegates to Client.connect' do
      AMQP::Client.should_receive(:connect).with("args")
      AMQP.connect "args"
    end

    it 'raises error unless called inside EM event loop' do
      expect { AMQP.connect {} }.to raise_error RuntimeError, /eventmachine not initialized/
    end

    context 'when called inside EM event loop, it either' do
#      include AMQP::EMSpec

      it 'raises connection error (with wrong AMQP opts), or' do
        expect { @conn = AMQP.connect(host: 'wrong') }.
            to raise_error EventMachine::ConnectionError, /unable to resolve server address/
        done
      end

      it 'establishes connection to AMQP broker (with right AMQP options). Mind the delay!' do
        @conn = AMQP.connect AMQP_OPTS
#      p (@conn.methods - Object.methods).sort #class.ancestors.first.should == MockClientModule
        @conn.should be_kind_of AMQP::Client::EM_CONNECTION_CLASS
        @conn.should_not be_connected
        done(0.1) { @conn.should be_connected }
      end

    end
  end

  describe '.start' do
    it 'starts new EM loop and never returns from it' do
      EM.should_receive(:run).with no_args
      AMQP.start "connect_args"
    end

    it 'calls .connect inside block given to EM.run (inside EM loop, that is)' do
      EM.should_receive(:run) do |*args, &block|
        args.should be_empty
        AMQP.should_receive(:connect).with("connect_args")
        block.call
      end
      AMQP.start "connect_args"
    end

    context 'inside EM loop' do
#      include AMQP::EMSpec
      after { AMQP.cleanup_state; done }

      it 'raises connection error (with wrong AMQP opts)' do
        expect { AMQP.start(host: 'wrong') }.
            to raise_error EventMachine::ConnectionError, /unable to resolve server address/
        done
      end

      it 'sets AMQP.connection property with client instance returned by .connect' do
        AMQP.connection.should be_nil
        AMQP.start AMQP_OPTS
        AMQP.connection.should be_kind_of AMQP::Client::EM_CONNECTION_CLASS
        done
      end

      it 'yields to given block AFTER connection is established' do
        AMQP.start AMQP_OPTS do
          @block_fired = true
          AMQP.connection.should be_connected
        end
        done(0.1) { @block_fired.should be_true }
      end
    end
  end

  describe '.stop' do
    it 'is noop if connection is not established' do
      expect { @res = AMQP.stop }.to_not raise_error
      @res.should be_nil
    end

    context 'with established AMQP connection' do
#      include AMQP::Spec
      after { AMQP.cleanup_state; done }
#      default_options AMQP_OPTS

      it 'closes existing connection' do
        AMQP.connection.should_receive(:close).with(no_args)
        AMQP.stop
        done
      end

      it 'unsets AMQP.connection property. Mind the delay!' do
        AMQP.connection.should be_connected
        AMQP.stop
        AMQP.connection.should_not be_nil
        done(0.1) { AMQP.connection.should be_nil }
      end

      it 'yields to given block AFTER connection is disconnected (BUT before AMQP.conn is cleared!)' do
        AMQP.stop do
          @block_fired = true
          AMQP.connection.should_not be_nil
          AMQP.instance_variable_get(:@closing).should be_true
        end
        done(0.1) do
          @block_fired.should be_true
        end
      end
    end
  end

  describe '.fork' do

    it 'yields to given block in EM.fork (cleaning its own process-local properties first)' do
      pending 'Not implemented in ZM yet'
      EM.should_receive(:fork) do |workers, &block|
        workers.should == 'workers'
        block.call
        Thread.current[:mq].should be_nil
        AMQP.connection.should be_nil
      end
      AMQP.fork('workers') { |*args| p *args }

    end
  end
end