# encoding: utf-8 require 'spec_helper' module Adhearsion class PunchblockPlugin describe Initializer do def reset_default_config Adhearsion.config.punchblock do |config| config.platform = :xmpp config.username = "usera@127.0.0.1" config.password = "1" config.host = nil config.port = nil config.certs_directory = nil config.root_domain = nil config.connection_timeout = 60 config.reconnect_attempts = 1.0/0.0 config.reconnect_timer = 5 end end def initialize_punchblock(options = {}) reset_default_config allow(Initializer).to receive(:connect) Adhearsion.config.punchblock do |config| config.platform = options[:platform] if options.has_key?(:platform) config.username = options[:username] if options.has_key?(:username) config.password = options[:password] if options.has_key?(:password) config.host = options[:host] if options.has_key?(:host) config.port = options[:port] if options.has_key?(:port) config.certs_directory = options[:certs_directory] if options.has_key?(:certs_directory) config.root_domain = options[:root_domain] if options.has_key?(:root_domain) config.connection_timeout = options[:connection_timeout] if options.has_key?(:connection_timeout) config.reconnect_attempts = options[:reconnect_attempts] if options.has_key?(:reconnect_attempts) config.reconnect_timer = options[:reconnect_timer] if options.has_key?(:reconnect_timer) end Initializer.init Adhearsion.config[:punchblock] end let(:call_id) { rand } let(:offer) { Punchblock::Event::Offer.new :target_call_id => call_id } let(:mock_call) { Call.new } let(:mock_client) { double 'Client' } before do allow(mock_call).to receive_messages :id => call_id mock_client.as_null_object allow(mock_client).to receive_messages :event_handler= => true Events.refresh! allow(Adhearsion::Process).to receive_messages :fqdn => 'hostname' allow(::Process).to receive_messages :pid => 1234 end describe "starts the client with the default values" do subject { initialize_punchblock } it "should set properly the username value" do expect(subject.username).to eq('usera@127.0.0.1') end it "should set properly the password value" do expect(subject.password).to eq('1') end it "should set properly the host value" do expect(subject.host).to be_nil end it "should set properly the port value" do expect(subject.port).to be_nil end it "should set properly the certs_directory value" do expect(subject.certs_directory).to be_nil end it "should set properly the root_domain value" do expect(subject.root_domain).to be_nil end it "should properly set the reconnect_attempts value" do expect(subject.reconnect_attempts).to eq(1.0/0.0) end it "should properly set the reconnect_timer value" do expect(subject.reconnect_timer).to eq(5) end end it "starts the client with the correct resource" do username = "usera@127.0.0.1/hostname-1234" expect(Punchblock::Connection::XMPP).to receive(:new).once.with(hash_including :username => username).and_return mock_client initialize_punchblock end context "when the fqdn is not available" do it "should use the local hostname instead" do allow(Adhearsion::Process).to receive(:fqdn).and_raise SocketError allow(Socket).to receive(:gethostname).and_return 'local_hostname' username = "usera@127.0.0.1/local_hostname-1234" expect(Punchblock::Connection::XMPP).to receive(:new).once.with(hash_including :username => username).and_return mock_client initialize_punchblock end end it "starts the client with any overridden settings" do expect(Punchblock::Connection::XMPP).to receive(:new).once.with(username: 'userb@127.0.0.1/foo', password: '123', host: 'foo.bar.com', port: 200, certs: '/foo/bar', connection_timeout: 20, root_domain: 'foo.com').and_return mock_client initialize_punchblock username: 'userb@127.0.0.1/foo', password: '123', host: 'foo.bar.com', port: 200, certs_directory: '/foo/bar', connection_timeout: 20, root_domain: 'foo.com' end describe "#connect" do it 'should block until the connection is established' do reset_default_config mock_connection = double :mock_connection expect(mock_connection).to receive(:register_event_handler).once expect(Punchblock::Client).to receive(:new).once.and_return mock_connection expect(mock_connection).to receive(:run).once t = Thread.new { Initializer.init; Initializer.run } t.join 5 expect(t.status).to eq("sleep") Events.trigger_immediately :punchblock, Punchblock::Connection::Connected.new t.join end end describe '#connect_to_server' do before :each do Adhearsion::Process.reset Initializer.config = reset_default_config Initializer.config.reconnect_attempts = 1 expect(Adhearsion::Logging.get_logger(Initializer)).to receive(:fatal).at_most(:once) allow(Initializer).to receive(:client).and_return mock_client end after :each do Adhearsion::Process.reset end it 'should reset the Adhearsion process state to "booting"' do Adhearsion::Process.booted expect(Adhearsion::Process.state_name).to eq(:running) allow(mock_client).to receive(:run).and_raise Punchblock::DisconnectedError expect(Adhearsion::Process).to receive(:reset).at_least(:once) Initializer.connect_to_server end it 'should retry the connection the specified number of times' do Initializer.config.reconnect_attempts = 3 allow(mock_client).to receive(:run).and_raise Punchblock::DisconnectedError Initializer.connect_to_server expect(Initializer.attempts).to eq(3) end it 'should preserve a Punchblock::ProtocolError exception and give up' do allow(mock_client).to receive(:run).and_raise Punchblock::ProtocolError expect { Initializer.connect_to_server }.to raise_error Punchblock::ProtocolError end it 'should not attempt to reconnect if Adhearsion is shutting down' do Adhearsion::Process.booted Adhearsion::Process.shutdown allow(mock_client).to receive(:run).and_raise Punchblock::DisconnectedError expect { Initializer.connect_to_server }.not_to raise_error end end describe 'using Asterisk' do let(:overrides) { {:username => 'test', :password => '123', :host => 'foo.bar.com', :port => 200, :certs => nil, :connection_timeout => 20, :root_domain => 'foo.com'} } it 'should start an Asterisk PB connection' do expect(Punchblock::Connection::Asterisk).to receive(:new).once.with(overrides).and_return mock_client initialize_punchblock overrides.merge(:platform => :asterisk) end end describe 'using FreeSWITCH' do let(:overrides) { {:username => 'test', :password => '123', :host => 'foo.bar.com', :port => 200, :certs => nil, :connection_timeout => 20, :root_domain => 'foo.com'} } it 'should start a FreeSWITCH PB connection' do expect(Punchblock::Connection::Freeswitch).to receive(:new).once.with(overrides).and_return mock_client initialize_punchblock overrides.merge(:platform => :freeswitch) end end it 'should place events from Punchblock into the event handler' do expect(Events.instance).to receive(:trigger).once.with(:punchblock, offer) initialize_punchblock Initializer.client.handle_event offer end describe "dispatching an offer" do before do initialize_punchblock expect(Adhearsion::Process).to receive(:state_name).once.and_return process_state expect(Adhearsion::Call).to receive(:new).once.and_return mock_call end context "when the Adhearsion::Process is :booting" do let(:process_state) { :booting } it 'should reject a call with cause :declined' do expect(mock_call).to receive(:reject).once.with(:decline) end end [ :running, :stopping ].each do |state| context "when when Adhearsion::Process is in :#{state}" do let(:process_state) { state } it "should dispatch via the router" do Adhearsion.router do route 'foobar', Class.new end expect(Adhearsion.router).to receive(:handle).once.with mock_call end end end context "when when Adhearsion::Process is in :rejecting" do let(:process_state) { :rejecting } it 'should reject a call with cause :declined' do expect(mock_call).to receive(:reject).once.with(:decline) end end context "when when Adhearsion::Process is not :running, :stopping or :rejecting" do let(:process_state) { :foobar } it 'should reject a call with cause :error' do expect(mock_call).to receive(:reject).once.with(:error) end end after { Events.trigger_immediately :punchblock, offer } end describe "dispatching a component event" do let(:component) { double 'ComponentNode' } let(:mock_event) { double 'Event' } before { allow(mock_event).to receive_messages target_call_id: call_id, source: component } before do initialize_punchblock end it "should place the event in the call's inbox" do expect(component).to receive(:trigger_event_handler).once.with mock_event Events.trigger_immediately :punchblock, mock_event end end describe "dispatching a call event" do let(:mock_event) { double 'Event' } before { allow(mock_event).to receive_messages target_call_id: call_id } describe "with an active call" do before do initialize_punchblock Adhearsion.active_calls << mock_call end it "should forward the event to the call actor" do events = [] mock_call.register_event_handler do |event| events << event end Initializer.dispatch_call_event mock_event sleep 0.5 expect(events).to eql([mock_event]) end it "should not block on the call handling the event" do mock_call.register_event_handler do |event| sleep 5 end start_time = Time.now Initializer.dispatch_call_event mock_event sleep 0.5 expect(Time.now - start_time).to be < 1 end end describe "with an inactive call" do it "should log a warning" do expect(Adhearsion::Logging.get_logger(Initializer)).to receive(:warn).once.with("Event received for inactive call #{call_id}: #{mock_event.inspect}") Initializer.dispatch_call_event mock_event end end describe "when the registry contains a dead call" do before do mock_call.terminate Adhearsion.active_calls[mock_call.id] = mock_call end it "should log a warning" do expect(Adhearsion::Logging.get_logger(Initializer)).to receive(:warn).once.with("Event received for inactive call #{call_id}: #{mock_event.inspect}") Initializer.dispatch_call_event mock_event end end end context "Punchblock configuration" do describe "with config specified" do before do Adhearsion.config.punchblock do |config| config.username = 'userb@127.0.0.1' config.password = 'abc123' end end subject do Adhearsion.config[:punchblock] end it "should set properly the username value" do expect(subject.username).to eq('userb@127.0.0.1') end it "should set properly the password value" do expect(subject.password).to eq('abc123') end end end it "should allow easily registering handlers for AMI events" do result = nil ami_event = Punchblock::Event::Asterisk::AMI::Event.new :name => 'foobar' latch = CountDownLatch.new 1 Events.draw do ami :name => 'foobar' do |event| result = event latch.countdown! end end Initializer.handle_event ami_event expect(latch.wait(1)).to be true expect(result).to be ami_event end end end end