spec/adhearsion/call_controller/dial_spec.rb in adhearsion-2.4.0 vs spec/adhearsion/call_controller/dial_spec.rb in adhearsion-2.5.0
- old
+ new
@@ -17,10 +17,12 @@
let(:mock_answered) { Punchblock::Event::Answered.new }
let(:latch) { CountDownLatch.new 1 }
+ let(:join_options) { options[:join_options] || {} }
+
before do
other_mock_call.wrapped_object.stub id: other_call_id, write_command: true
second_other_mock_call.wrapped_object.stub id: second_other_call_id, write_command: true
end
@@ -94,11 +96,11 @@
latch.wait(1).should be_true
end
it "joins the new call to the existing one on answer" do
call.should_receive(:answer).once
- other_mock_call.should_receive(:join).once.with(call)
+ other_mock_call.should_receive(:join).once.with(call, {})
dial_in_thread
latch.wait(1).should be_false
@@ -106,13 +108,114 @@
other_mock_call << mock_end
latch.wait(1).should be_true
end
+ context "with a join target specified" do
+ let(:options) { { join_target: {mixer_name: 'foobar'} } }
+
+ it "joins the calls to the specified target on answer" do
+ call.should_receive(:answer).once
+ call.should_receive(:join).once.with({mixer_name: 'foobar'}, {})
+ other_mock_call.should_receive(:join).once.with({mixer_name: 'foobar'}, {})
+
+ dial_in_thread
+
+ latch.wait(1).should be_false
+
+ other_mock_call << mock_answered
+ other_mock_call << mock_end
+
+ latch.wait(1).should be_true
+ end
+ end
+
+ context "with a pre-join callback specified" do
+ let(:foo) { double }
+ let(:options) { { pre_join: ->(call) { foo.bar call } } }
+
+ it "executes the callback prior to joining" do
+ foo.should_receive(:bar).once.with(other_mock_call).ordered
+ call.should_receive(:answer).once.ordered
+ other_mock_call.should_receive(:join).once.with(call, {}).ordered
+
+ dial_in_thread
+
+ latch.wait(1).should be_false
+
+ other_mock_call << mock_answered
+ other_mock_call << mock_end
+
+ latch.wait(1).should be_true
+ end
+ end
+
+ context "with ringback specified" do
+ let(:component) { Punchblock::Component::Output.new }
+ let(:options) { { ringback: ['file://tt-monkeys'] } }
+
+ before do
+ component.request!
+ component.execute!
+ end
+
+ it "plays the ringback asynchronously, terminating prior to joining" do
+ subject.should_receive(:play!).once.with(['file://tt-monkeys'], repeat_times: 0).and_return(component)
+ component.should_receive(:stop!).twice
+ call.should_receive(:answer).once.ordered
+ other_mock_call.should_receive(:join).once.with(call, {}).ordered
+
+ dial_in_thread
+
+ latch.wait(1).should be_false
+
+ other_mock_call << mock_answered
+ other_mock_call << mock_end
+
+ latch.wait(1).should be_true
+ end
+
+ context "as a callback" do
+ let(:foo) { double }
+ let(:options) { { ringback: -> { foo.bar; component } } }
+
+ it "calls the callback to start, and uses the return value of the callback to stop the ringback" do
+ foo.should_receive(:bar).once.ordered
+ component.should_receive(:stop!).twice
+ call.should_receive(:answer).once.ordered
+ other_mock_call.should_receive(:join).once.with(call, {}).ordered
+
+ dial_in_thread
+
+ latch.wait(1).should be_false
+
+ other_mock_call << mock_answered
+ other_mock_call << mock_end
+
+ latch.wait(1).should be_true
+ end
+ end
+
+ context "when the call is rejected" do
+ it "terminates the ringback before returning" do
+ subject.should_receive(:play!).once.with(['file://tt-monkeys'], repeat_times: 0).and_return(component)
+ component.should_receive(:stop!).once
+
+ t = dial_in_thread
+
+ latch.wait(1).should be_false
+
+ other_mock_call << mock_end(:reject)
+
+ latch.wait(1).should be_true
+ end
+ end
+ end
+
it "hangs up the new call when the root call ends" do
call.should_receive(:answer).once
- other_mock_call.should_receive(:join).once.with(call)
+ other_mock_call.should_receive(:join).once.with(call, {})
other_mock_call.should_receive(:hangup).once
dial_in_thread
latch.wait(1).should be_false
@@ -160,11 +263,11 @@
end
context "when the call is answered and joined" do
it "has an overall dial status of :answer" do
call.should_receive(:answer).once
- other_mock_call.should_receive(:join).once.with(call)
+ other_mock_call.should_receive(:join).once.with(call, {})
t = dial_in_thread
sleep 0.5
@@ -181,11 +284,11 @@
joined_status.result.should == :joined
end
it "records the duration of the join" do
call.should_receive(:answer).once
- other_mock_call.should_receive(:join).once.with(call)
+ other_mock_call.should_receive(:join).once.with(call, {})
other_mock_call.stub hangup: true
t = dial_in_thread
sleep 0.5
@@ -206,30 +309,55 @@
status = t.value
status.result.should be == :answer
joined_status = status.joins[status.calls.first]
joined_status.duration.should == 37.0
end
+
+ context "when join options are specified" do
+ let(:options) { { join_options: {media: :direct} } }
+
+ it "joins the calls with those options" do
+ call.should_receive(:answer).once
+ other_mock_call.should_receive(:join).once.with(call, media: :direct)
+ other_mock_call.stub hangup: true
+
+ t = dial_in_thread
+
+ sleep 0.5
+
+ other_mock_call << mock_answered
+
+ other_mock_call << Punchblock::Event::Unjoined.new(call_uri: call.id)
+ other_mock_call << mock_end
+
+ latch.wait(1).should be_true
+
+ t.join
+ end
+ end
end
context "when a dial is split" do
+ let(:join_target) { call }
+
before do
call.should_receive(:answer).once
- other_mock_call.should_receive(:join).once.with(call)
- call.stub(:unjoin).and_return do
+ other_mock_call.should_receive(:join).once.with(join_target, join_options)
+ other_mock_call.stub(:unjoin).and_return do
call << Punchblock::Event::Unjoined.new(call_uri: other_mock_call.id)
other_mock_call << Punchblock::Event::Unjoined.new(call_uri: call.id)
end
end
it "should unjoin the calls" do
- call.should_receive(:unjoin).once.ordered.with(other_mock_call.id).and_return do
+ other_mock_call.should_receive(:unjoin).once.ordered.with(call).and_return do
call << Punchblock::Event::Unjoined.new(call_uri: other_mock_call.id)
other_mock_call << Punchblock::Event::Unjoined.new(call_uri: call.id)
end
dial = Dial::Dial.new to, options, call
- dial.run
+ dial.run subject
waiter_thread = Thread.new do
dial.await_completion
latch.countdown!
end
@@ -247,11 +375,11 @@
dial.status.result.should be == :answer
end
it "should not unblock immediately" do
dial = Dial::Dial.new to, options, call
- dial.run
+ dial.run subject
waiter_thread = Thread.new do
dial.await_completion
latch.countdown!
end
@@ -272,11 +400,11 @@
dial.status.result.should be == :answer
end
it "should set end time" do
dial = Dial::Dial.new to, options, call
- dial.run
+ dial.run subject
waiter_thread = Thread.new do
dial.await_completion
latch.countdown!
end
@@ -324,11 +452,11 @@
let(:main_split_controller) { Class.new(split_controller) }
let(:others_split_controller) { Class.new(split_controller) }
it "should execute the :main controller on the originating call and :others on the outbound calls" do
dial = Dial::Dial.new to, options, call
- dial.run
+ dial.run subject
waiter_thread = Thread.new do
dial.await_completion
latch.countdown!
end
@@ -360,17 +488,17 @@
end
end
context "when rejoining" do
it "should rejoin the calls" do
- call.should_receive(:unjoin).once.ordered.with(other_mock_call.id).and_return do
+ other_mock_call.should_receive(:unjoin).once.ordered.with(call).and_return do
call << Punchblock::Event::Unjoined.new(call_uri: other_mock_call.id)
other_mock_call << Punchblock::Event::Unjoined.new(call_uri: call.id)
end
dial = Dial::Dial.new to, options, call
- dial.run
+ dial.run subject
waiter_thread = Thread.new do
dial.await_completion
latch.countdown!
end
@@ -379,32 +507,105 @@
other_mock_call << mock_answered
dial.split
- other_mock_call.should_receive(:join).once.ordered.with(call)
+ other_mock_call.should_receive(:join).once.ordered.with(call, {})
dial.rejoin
other_mock_call << mock_end
latch.wait(1).should be_true
waiter_thread.join
dial.status.result.should be == :answer
end
+ context "when join options were set originally" do
+ let(:options) { { join_options: {media: :direct} } }
+
+ it "should rejoin with the same parameters" do
+ other_mock_call.stub(:unjoin)
+
+ dial = Dial::Dial.new to, options, call
+ dial.run subject
+
+ other_mock_call << mock_answered
+
+ dial.split
+
+ other_mock_call.should_receive(:join).once.ordered.with(call, media: :direct)
+ dial.rejoin
+ end
+ end
+
+ context "when join options are passed to rejoin" do
+ it "should rejoin with those parameters" do
+ other_mock_call.stub(:unjoin)
+
+ dial = Dial::Dial.new to, options, call
+ dial.run subject
+
+ other_mock_call << mock_answered
+
+ dial.split
+
+ other_mock_call.should_receive(:join).once.ordered.with(call, media: :direct)
+ dial.rejoin nil, media: :direct
+ end
+ end
+
+ context "when a join target was originally specified" do
+ let(:join_target) { {mixer_name: 'foobar'} }
+ let(:options) { { join_target: join_target } }
+
+ it "joins the calls to the specified target on answer" do
+ call.should_receive(:join).once.with(join_target, {})
+ other_mock_call.should_receive(:unjoin).once.ordered.with(join_target)
+ call.should_receive(:unjoin).once.ordered.with(join_target).and_return do
+ call << Punchblock::Event::Unjoined.new(join_target)
+ other_mock_call << Punchblock::Event::Unjoined.new(join_target)
+ end
+
+ dial = Dial::Dial.new to, options, call
+ dial.run subject
+
+ waiter_thread = Thread.new do
+ dial.await_completion
+ latch.countdown!
+ end
+
+ sleep 0.5
+
+ other_mock_call << mock_answered
+
+ dial.split
+
+ call.should_receive(:join).once.ordered.with({mixer_name: 'foobar'}, {})
+ other_mock_call.should_receive(:join).once.ordered.with({mixer_name: 'foobar'}, {})
+ dial.rejoin
+
+ other_mock_call << mock_end
+
+ latch.wait(1).should be_true
+
+ waiter_thread.join
+ dial.status.result.should be == :answer
+ end
+ end
+
context "to a specified mixer" do
let(:mixer) { SecureRandom.uuid }
it "should join all calls to the mixer" do
- call.should_receive(:unjoin).once.ordered.with(other_mock_call.id).and_return do
+ other_mock_call.should_receive(:unjoin).once.ordered.with(call).and_return do
call << Punchblock::Event::Unjoined.new(call_uri: other_mock_call.id)
other_mock_call << Punchblock::Event::Unjoined.new(call_uri: call.id)
end
dial = Dial::Dial.new to, options, call
- dial.run
+ dial.run subject
waiter_thread = Thread.new do
dial.await_completion
latch.countdown!
end
@@ -413,21 +614,61 @@
other_mock_call << mock_answered
dial.split
- call.should_receive(:join).once.ordered.with(mixer_name: mixer)
- other_mock_call.should_receive(:join).once.ordered.with(mixer_name: mixer)
+ call.should_receive(:join).once.ordered.with({mixer_name: mixer}, {})
+ other_mock_call.should_receive(:join).once.ordered.with({mixer_name: mixer}, {})
dial.rejoin mixer_name: mixer
other_mock_call << mock_end
latch.wait(1).should be_true
waiter_thread.join
dial.status.result.should be == :answer
end
+
+ it "#split should then unjoin calls from the mixer" do
+ other_mock_call.should_receive(:unjoin).once.ordered.with(call).and_return do
+ call << Punchblock::Event::Unjoined.new(call_uri: other_mock_call.id)
+ other_mock_call << Punchblock::Event::Unjoined.new(call_uri: call.id)
+ end
+
+ dial = Dial::Dial.new to, options, call
+ dial.run subject
+
+ waiter_thread = Thread.new do
+ dial.await_completion
+ latch.countdown!
+ end
+
+ sleep 0.5
+
+ other_mock_call << mock_answered
+
+ dial.split
+
+ call.should_receive(:join).once.ordered.with({mixer_name: mixer}, {})
+ other_mock_call.should_receive(:join).once.ordered.with({mixer_name: mixer}, {})
+ dial.rejoin mixer_name: mixer
+
+ other_mock_call.should_receive(:unjoin).once.ordered.with(mixer_name: mixer).and_return do
+ other_mock_call << Punchblock::Event::Unjoined.new(mixer_name: mixer)
+ end
+ call.should_receive(:unjoin).once.ordered.with(mixer_name: mixer).and_return do
+ call << Punchblock::Event::Unjoined.new(mixer_name: mixer)
+ end
+ dial.split
+
+ other_mock_call << mock_end
+
+ latch.wait(1).should be_true
+
+ waiter_thread.join
+ dial.status.result.should be == :answer
+ end
end
end
context "when another dial is merged in" do
let(:second_root_call_id) { new_uuid }
@@ -438,38 +679,38 @@
let(:other_dial) { Dial::Dial.new second_to, options, second_root_call }
before do
second_root_call.stub write_command: true, id: second_root_call_id
OutboundCall.should_receive(:new).and_return second_other_mock_call
- second_other_mock_call.should_receive(:join).once.with(second_root_call)
+ second_other_mock_call.should_receive(:join).once.with(second_root_call, {})
second_other_mock_call.should_receive(:dial).once.with(second_to, options)
second_root_call.should_receive(:answer).once
SecureRandom.stub uuid: mixer
- dial.run
- other_dial.run
+ dial.run subject
+ other_dial.run subject
other_mock_call << mock_answered
second_other_mock_call << mock_answered
end
it "should split calls, rejoin to a mixer, and rejoin other calls to mixer" do
- call.should_receive(:unjoin).once.ordered.with(other_mock_call.id).and_return do
+ other_mock_call.should_receive(:unjoin).once.ordered.with(call).and_return do
call << Punchblock::Event::Unjoined.new(call_uri: other_mock_call.id)
other_mock_call << Punchblock::Event::Unjoined.new(call_uri: call.id)
end
- second_root_call.should_receive(:unjoin).once.ordered.with(second_other_mock_call.id).and_return do
+ second_other_mock_call.should_receive(:unjoin).once.ordered.with(second_root_call).and_return do
second_root_call << Punchblock::Event::Unjoined.new(call_uri: second_other_mock_call.id)
second_other_mock_call << Punchblock::Event::Unjoined.new(call_uri: second_root_call.id)
end
- call.should_receive(:join).once.ordered.with(mixer_name: mixer)
- other_mock_call.should_receive(:join).once.ordered.with(mixer_name: mixer)
+ call.should_receive(:join).once.ordered.with({mixer_name: mixer}, {})
+ other_mock_call.should_receive(:join).once.ordered.with({mixer_name: mixer}, {})
- second_root_call.should_receive(:join).once.ordered.with(mixer_name: mixer)
- second_other_mock_call.should_receive(:join).once.ordered.with(mixer_name: mixer)
+ second_root_call.should_receive(:join).once.ordered.with({mixer_name: mixer}, {})
+ second_other_mock_call.should_receive(:join).once.ordered.with({mixer_name: mixer}, {})
dial.merge other_dial
waiter_thread = Thread.new do
dial.await_completion
@@ -486,10 +727,27 @@
waiter_thread.join
dial.status.result.should be == :answer
end
+ context "when join options were specified originally" do
+ let(:options) { { join_options: {media: :direct} } }
+
+ it "should rejoin with default options" do
+ other_mock_call.stub(:unjoin)
+ second_other_mock_call.stub(:unjoin)
+
+ call.should_receive(:join).once.ordered.with({mixer_name: mixer}, {})
+ other_mock_call.should_receive(:join).once.ordered.with({mixer_name: mixer}, {})
+
+ second_root_call.should_receive(:join).once.ordered.with({mixer_name: mixer}, {})
+ second_other_mock_call.should_receive(:join).once.ordered.with({mixer_name: mixer}, {})
+
+ dial.merge other_dial
+ end
+ end
+
it "should add the merged calls to the returned status" do
[call, other_mock_call, second_root_call, second_other_mock_call].each { |c| c.stub join: true, unjoin: true }
dial.merge other_dial
waiter_thread = Thread.new do
@@ -558,20 +816,168 @@
waiter_thread.join
dial.status.result.should be == :answer
end
+ it "should subsequently rejoin to a mixer" do
+ [call, other_mock_call, second_root_call, second_other_mock_call].each { |c| c.stub join: true, unjoin: true }
+
+ dial.merge other_dial
+
+ waiter_thread = Thread.new do
+ dial.await_completion
+ latch.countdown!
+ end
+
+ sleep 0.5
+
+ other_mock_call << mock_end
+ latch.wait(1).should be_false
+
+ [call, second_root_call, second_other_mock_call].each do |call|
+ call.should_receive(:unjoin).once.with(mixer_name: mixer).and_return do
+ call << Punchblock::Event::Unjoined.new(mixer_name: mixer)
+ end
+ end
+
+ dial.split
+
+ [call, second_root_call, second_other_mock_call].each do |call|
+ call.should_receive(:join).once.with({mixer_name: mixer}, {}).and_return do
+ call << Punchblock::Event::Joined.new(mixer_name: mixer)
+ end
+ end
+
+ dial.rejoin
+ end
+
+ describe "if splitting fails" do
+ it "should not add the merged calls to the returned status" do
+ [call, other_mock_call, second_root_call, second_other_mock_call].each { |c| c.stub join: true, unjoin: true }
+ other_dial.should_receive(:split).and_raise StandardError
+ expect { dial.merge other_dial }.to raise_error(StandardError)
+
+ waiter_thread = Thread.new do
+ dial.await_completion
+ latch.countdown!
+ end
+
+ sleep 0.5
+
+ other_mock_call.async << mock_end
+ second_root_call.async << mock_end
+ second_other_mock_call.async << mock_end
+
+ latch.wait(1).should be_true
+
+ waiter_thread.join
+ dial.status.result.should be == :answer
+ dial.status.calls.should_not include(second_root_call, second_other_mock_call)
+ end
+
+ it "should unblock before all joined calls end" do
+ [call, other_mock_call, second_root_call, second_other_mock_call].each { |c| c.stub join: true, unjoin: true }
+
+ other_dial.should_receive(:split).and_raise StandardError
+ expect { dial.merge other_dial }.to raise_error(StandardError)
+
+ waiter_thread = Thread.new do
+ dial.await_completion
+ latch.countdown!
+ end
+
+ sleep 0.5
+
+ other_mock_call << mock_end
+ latch.wait(1).should be_true
+
+ second_other_mock_call << mock_end
+ latch.wait(1).should be_true
+
+ second_root_call << mock_end
+ latch.wait(1).should be_true
+
+ waiter_thread.join
+ dial.status.result.should be == :answer
+ end
+
+ it "should not cleanup merged calls when the root call ends" do
+ [call, other_mock_call, second_root_call, second_other_mock_call].each do |c|
+ c.stub join: true, unjoin: true
+ end
+ other_mock_call.should_receive(:hangup).once
+ [second_root_call, second_other_mock_call].each do |c|
+ c.should_receive(:hangup).never
+ end
+
+ other_dial.should_receive(:split).and_raise StandardError
+ expect { dial.merge other_dial }.to raise_error(StandardError)
+
+ waiter_thread = Thread.new do
+ dial.await_completion
+ dial.cleanup_calls
+ latch.countdown!
+ end
+
+ sleep 0.5
+
+ call << mock_end
+ latch.wait(1).should be_true
+
+ waiter_thread.join
+ dial.status.result.should be == :answer
+ end
+ end
+
+ context "if a call hangs up" do
+ it "should still allow splitting and rejoining" do
+ [call, other_mock_call, second_root_call, second_other_mock_call].each { |c| c.stub join: true, unjoin: true }
+
+ dial.merge other_dial
+
+ waiter_thread = Thread.new do
+ dial.await_completion
+ latch.countdown!
+ end
+
+ sleep 0.5
+
+ [call, second_root_call, second_other_mock_call].each do |call|
+ call.should_receive(:unjoin).once.with(mixer_name: mixer).and_return do
+ call << Punchblock::Event::Unjoined.new(mixer_name: mixer)
+ end
+ end
+
+ other_mock_call.should_receive(:unjoin).and_raise Adhearsion::Call::Hangup
+
+ dial.split
+
+ other_mock_call << mock_end
+ latch.wait(1).should be_false
+
+ [call, second_root_call, second_other_mock_call].each do |call|
+ call.should_receive(:join).once.with({mixer_name: mixer}, {}).and_return do
+ call << Punchblock::Event::Joined.new(mixer_name: mixer)
+ end
+ end
+
+ other_mock_call.should_receive(:join).and_raise Adhearsion::Call::ExpiredError
+
+ dial.rejoin
+ end
+ end
+
context "if the calls were not joined" do
it "should still join to mixer" do
- call.should_receive(:unjoin).once.ordered.with(other_mock_call.id).and_raise Punchblock::ProtocolError.new.setup(:service_unavailable)
- second_root_call.should_receive(:unjoin).once.ordered.with(second_other_mock_call.id).and_raise Punchblock::ProtocolError.new.setup(:service_unavailable)
+ other_mock_call.should_receive(:unjoin).once.ordered.with(call).and_raise Punchblock::ProtocolError.new.setup(:service_unavailable)
+ second_other_mock_call.should_receive(:unjoin).once.ordered.with(second_root_call).and_raise Punchblock::ProtocolError.new.setup(:service_unavailable)
- call.should_receive(:join).once.ordered.with(mixer_name: mixer)
- other_mock_call.should_receive(:join).once.ordered.with(mixer_name: mixer)
+ call.should_receive(:join).once.ordered.with({mixer_name: mixer}, {})
+ other_mock_call.should_receive(:join).once.ordered.with({mixer_name: mixer}, {})
- second_root_call.should_receive(:join).once.ordered.with(mixer_name: mixer)
- second_other_mock_call.should_receive(:join).once.ordered.with(mixer_name: mixer)
+ second_root_call.should_receive(:join).once.ordered.with({mixer_name: mixer}, {})
+ second_other_mock_call.should_receive(:join).once.ordered.with({mixer_name: mixer}, {})
dial.merge other_dial
waiter_thread = Thread.new do
dial.await_completion
@@ -631,11 +1037,11 @@
end
end
it "dials all parties and joins the first one to answer, hanging up the rest" do
call.should_receive(:answer).once
- other_mock_call.should_receive(:join).once.with(call)
+ other_mock_call.should_receive(:join).once.with(call, {})
second_other_mock_call.should_receive(:hangup).once.and_return do
second_other_mock_call << mock_end
end
t = dial_in_thread
@@ -654,11 +1060,11 @@
status.calls.each { |c| c.should be_a OutboundCall }
end
it "unblocks when the joined call unjoins, allowing it to proceed further" do
call.should_receive(:answer).once
- other_mock_call.should_receive(:join).once.with(call)
+ other_mock_call.should_receive(:join).once.with(call, {})
other_mock_call.should_receive(:hangup).once
second_other_mock_call.should_receive(:hangup).once.and_return do
second_other_mock_call << mock_end
end
@@ -766,11 +1172,11 @@
end
context "when a call is answered and joined, and the other ends with an error" do
it "has an overall dial status of :answer" do
call.should_receive(:answer).once
- other_mock_call.should_receive(:join).once.with(call)
+ other_mock_call.should_receive(:join).once.with(call, {})
second_other_mock_call.should_receive(:hangup).once.and_return do
second_other_mock_call << mock_end(:error)
end
t = dial_in_thread
@@ -815,11 +1221,11 @@
describe "if someone answers before the timeout elapses" do
it "should not abort until the far end hangs up" do
other_mock_call.should_receive(:dial).once.with(to, hash_including(:timeout => timeout))
call.should_receive(:answer).once
- other_mock_call.should_receive(:join).once.with(call)
+ other_mock_call.should_receive(:join).once.with(call, {})
OutboundCall.should_receive(:new).and_return other_mock_call
time = Time.now
t = Thread.new do
@@ -918,11 +1324,11 @@
it "should join the calls if the call is still active after execution of the call controller" do
other_mock_call.should_receive(:hangup).once
other_mock_call['confirm'] = true
call.should_receive(:answer).once
- other_mock_call.should_receive(:join).once.with(call)
+ other_mock_call.should_receive(:join).once.with(call, {})
t = dial_in_thread
latch.wait(1).should be_false
@@ -990,11 +1396,11 @@
it "should only execute the confirmation controller on the first call to answer, immediately hanging up all others" do
other_mock_call['confirm'] = true
call.should_receive(:answer).once
other_mock_call.should_receive(:dial).once.with(to, from: nil)
- other_mock_call.should_receive(:join).once.with(call)
+ other_mock_call.should_receive(:join).once.with(call, {})
other_mock_call.should_receive(:hangup).once.and_return do
other_mock_call << mock_end
end
second_other_mock_call.should_receive(:dial).once.with(second_to, from: nil)
@@ -1093,11 +1499,11 @@
latch.wait(1).should be_true
end
it "joins the new call to the existing one on answer" do
call.should_receive(:answer).once
- other_mock_call.should_receive(:join).once.with(call)
+ other_mock_call.should_receive(:join).once.with(call, {})
dial_in_thread
latch.wait(1).should be_false
@@ -1105,14 +1511,115 @@
other_mock_call << mock_end
latch.wait(1).should be_true
end
+ context "with a join target specified" do
+ let(:options) { { join_target: {mixer_name: 'foobar'} } }
+
+ it "joins the calls to the specified target on answer" do
+ call.should_receive(:answer).once
+ call.should_receive(:join).once.with({mixer_name: 'foobar'}, {})
+ other_mock_call.should_receive(:join).once.with({mixer_name: 'foobar'}, {})
+
+ dial_in_thread
+
+ latch.wait(1).should be_false
+
+ other_mock_call << mock_answered
+ other_mock_call << mock_end
+
+ latch.wait(1).should be_true
+ end
+ end
+
+ context "with a pre-join callback specified" do
+ let(:foo) { double }
+ let(:options) { { pre_join: ->(call) { foo.bar call } } }
+
+ it "executes the callback prior to joining" do
+ foo.should_receive(:bar).once.with(other_mock_call).ordered
+ call.should_receive(:answer).once.ordered
+ other_mock_call.should_receive(:join).once.with(call, {}).ordered
+
+ dial_in_thread
+
+ latch.wait(1).should be_false
+
+ other_mock_call << mock_answered
+ other_mock_call << mock_end
+
+ latch.wait(1).should be_true
+ end
+ end
+
+ context "with ringback specified" do
+ let(:component) { Punchblock::Component::Output.new }
+ let(:options) { { ringback: ['file://tt-monkeys'] } }
+
+ before do
+ component.request!
+ component.execute!
+ end
+
+ it "plays the ringback asynchronously, terminating prior to joining" do
+ subject.should_receive(:play!).once.with(['file://tt-monkeys'], repeat_times: 0).and_return(component)
+ component.should_receive(:stop!).twice
+ call.should_receive(:answer).once.ordered
+ other_mock_call.should_receive(:join).once.with(call, {}).ordered
+
+ dial_in_thread
+
+ latch.wait(1).should be_false
+
+ other_mock_call << mock_answered
+ other_mock_call << mock_end
+
+ latch.wait(1).should be_true
+ end
+
+ context "as a callback" do
+ let(:foo) { double }
+ let(:options) { { ringback: -> { foo.bar; component } } }
+
+ it "calls the callback to start, and uses the return value of the callback to stop the ringback" do
+ foo.should_receive(:bar).once.ordered
+ component.should_receive(:stop!).twice
+ call.should_receive(:answer).once.ordered
+ other_mock_call.should_receive(:join).once.with(call, {}).ordered
+
+ dial_in_thread
+
+ latch.wait(1).should be_false
+
+ other_mock_call << mock_answered
+ other_mock_call << mock_end
+
+ latch.wait(1).should be_true
+ end
+ end
+
+ context "when the call is rejected" do
+ it "terminates the ringback before returning" do
+ subject.should_receive(:play!).once.with(['file://tt-monkeys'], repeat_times: 0).and_return(component)
+ component.should_receive(:stop!).once
+
+ t = dial_in_thread
+
+ latch.wait(1).should be_false
+
+ other_mock_call << mock_end(:reject)
+
+ latch.wait(1).should be_true
+ end
+ end
+ end
+
it "hangs up the new call when the root call ends" do
other_mock_call.should_receive(:hangup).once
call.should_receive(:answer).once
- other_mock_call.should_receive(:join).once.with(call)
+ other_mock_call.should_receive(:join).once.with(call, {})
dial_in_thread
latch.wait(1).should be_false
@@ -1159,11 +1666,11 @@
end
context "when the call is answered and joined" do
it "has an overall dial status of :answer" do
call.should_receive(:answer).once
- other_mock_call.should_receive(:join).once.with(call)
+ other_mock_call.should_receive(:join).once.with(call, {})
t = dial_in_thread
sleep 0.5
@@ -1180,11 +1687,11 @@
joined_status.result.should == :joined
end
it "records the duration of the join" do
call.should_receive(:answer).once
- other_mock_call.should_receive(:join).once.with(call)
+ other_mock_call.should_receive(:join).once.with(call, {})
other_mock_call.stub hangup: true
t = dial_in_thread
sleep 0.5
@@ -1205,30 +1712,55 @@
status = t.value
status.result.should be == :answer
joined_status = status.joins[status.calls.first]
joined_status.duration.should == 37.0
end
+
+ context "when join options are specified" do
+ let(:options) { { join_options: {media: :direct} } }
+
+ it "joins the calls with those options" do
+ call.should_receive(:answer).once
+ other_mock_call.should_receive(:join).once.with(call, media: :direct)
+ other_mock_call.stub hangup: true
+
+ t = dial_in_thread
+
+ sleep 0.5
+
+ other_mock_call << mock_answered
+
+ other_mock_call << Punchblock::Event::Unjoined.new(call_uri: call.id)
+ other_mock_call << mock_end
+
+ latch.wait(1).should be_true
+
+ t.join
+ end
+ end
end
context "when a dial is split" do
+ let(:join_target) { call }
+
before do
call.should_receive(:answer).once
- other_mock_call.should_receive(:join).once.with(call)
- call.stub(:unjoin).and_return do
+ other_mock_call.should_receive(:join).once.with(join_target, join_options)
+ other_mock_call.stub(:unjoin).and_return do
call << Punchblock::Event::Unjoined.new(call_uri: other_mock_call.id)
other_mock_call << Punchblock::Event::Unjoined.new(call_uri: call.id)
end
end
it "should unjoin the calls" do
- call.should_receive(:unjoin).once.ordered.with(other_mock_call.id).and_return do
+ other_mock_call.should_receive(:unjoin).once.ordered.with(call).and_return do
call << Punchblock::Event::Unjoined.new(call_uri: other_mock_call.id)
other_mock_call << Punchblock::Event::Unjoined.new(call_uri: call.id)
end
dial = Dial::ParallelConfirmationDial.new to, options, call
- dial.run
+ dial.run subject
waiter_thread = Thread.new do
dial.await_completion
latch.countdown!
end
@@ -1246,11 +1778,11 @@
dial.status.result.should be == :answer
end
it "should not unblock immediately" do
dial = Dial::ParallelConfirmationDial.new to, options, call
- dial.run
+ dial.run subject
waiter_thread = Thread.new do
dial.await_completion
latch.countdown!
end
@@ -1271,11 +1803,11 @@
dial.status.result.should be == :answer
end
it "should set end time" do
dial = Dial::ParallelConfirmationDial.new to, options, call
- dial.run
+ dial.run subject
waiter_thread = Thread.new do
dial.await_completion
latch.countdown!
end
@@ -1323,11 +1855,11 @@
let(:main_split_controller) { Class.new(split_controller) }
let(:others_split_controller) { Class.new(split_controller) }
it "should execute the :main controller on the originating call and :others on the outbound calls" do
dial = Dial::ParallelConfirmationDial.new to, options, call
- dial.run
+ dial.run subject
waiter_thread = Thread.new do
dial.await_completion
latch.countdown!
end
@@ -1359,17 +1891,17 @@
end
end
context "when rejoining" do
it "should rejoin the calls" do
- call.should_receive(:unjoin).once.ordered.with(other_mock_call.id).and_return do
+ other_mock_call.should_receive(:unjoin).once.ordered.with(call).and_return do
call << Punchblock::Event::Unjoined.new(call_uri: other_mock_call.id)
other_mock_call << Punchblock::Event::Unjoined.new(call_uri: call.id)
end
dial = Dial::ParallelConfirmationDial.new to, options, call
- dial.run
+ dial.run subject
waiter_thread = Thread.new do
dial.await_completion
latch.countdown!
end
@@ -1378,32 +1910,105 @@
other_mock_call << mock_answered
dial.split
- other_mock_call.should_receive(:join).once.ordered.with(call)
+ other_mock_call.should_receive(:join).once.ordered.with(call, {})
dial.rejoin
other_mock_call << mock_end
latch.wait(1).should be_true
waiter_thread.join
dial.status.result.should be == :answer
end
+ context "when join options were set originally" do
+ let(:options) { { join_options: {media: :direct} } }
+
+ it "should rejoin with the same parameters" do
+ other_mock_call.stub(:unjoin)
+
+ dial = Dial::ParallelConfirmationDial.new to, options, call
+ dial.run subject
+
+ other_mock_call << mock_answered
+
+ dial.split
+
+ other_mock_call.should_receive(:join).once.ordered.with(call, media: :direct)
+ dial.rejoin
+ end
+ end
+
+ context "when join options are passed to rejoin" do
+ it "should rejoin with those parameters" do
+ other_mock_call.stub(:unjoin)
+
+ dial = Dial::ParallelConfirmationDial.new to, options, call
+ dial.run subject
+
+ other_mock_call << mock_answered
+
+ dial.split
+
+ other_mock_call.should_receive(:join).once.ordered.with(call, media: :direct)
+ dial.rejoin nil, media: :direct
+ end
+ end
+
+ context "when a join target was originally specified" do
+ let(:join_target) { {mixer_name: 'foobar'} }
+ let(:options) { { join_target: join_target } }
+
+ it "joins the calls to the specified target on answer" do
+ call.should_receive(:join).once.with(join_target, {})
+ other_mock_call.should_receive(:unjoin).once.ordered.with(join_target)
+ call.should_receive(:unjoin).once.ordered.with(join_target).and_return do
+ call << Punchblock::Event::Unjoined.new(join_target)
+ other_mock_call << Punchblock::Event::Unjoined.new(join_target)
+ end
+
+ dial = Dial::ParallelConfirmationDial.new to, options, call
+ dial.run subject
+
+ waiter_thread = Thread.new do
+ dial.await_completion
+ latch.countdown!
+ end
+
+ sleep 0.5
+
+ other_mock_call << mock_answered
+
+ dial.split
+
+ call.should_receive(:join).once.ordered.with({mixer_name: 'foobar'}, {})
+ other_mock_call.should_receive(:join).once.ordered.with({mixer_name: 'foobar'}, {})
+ dial.rejoin
+
+ other_mock_call << mock_end
+
+ latch.wait(1).should be_true
+
+ waiter_thread.join
+ dial.status.result.should be == :answer
+ end
+ end
+
context "to a specified mixer" do
let(:mixer) { SecureRandom.uuid }
it "should join all calls to the mixer" do
- call.should_receive(:unjoin).once.ordered.with(other_mock_call.id).and_return do
+ other_mock_call.should_receive(:unjoin).once.ordered.with(call).and_return do
call << Punchblock::Event::Unjoined.new(call_uri: other_mock_call.id)
other_mock_call << Punchblock::Event::Unjoined.new(call_uri: call.id)
end
dial = Dial::ParallelConfirmationDial.new to, options, call
- dial.run
+ dial.run subject
waiter_thread = Thread.new do
dial.await_completion
latch.countdown!
end
@@ -1412,21 +2017,61 @@
other_mock_call << mock_answered
dial.split
- call.should_receive(:join).once.ordered.with(mixer_name: mixer)
- other_mock_call.should_receive(:join).once.ordered.with(mixer_name: mixer)
+ call.should_receive(:join).once.ordered.with({mixer_name: mixer}, {})
+ other_mock_call.should_receive(:join).once.ordered.with({mixer_name: mixer}, {})
dial.rejoin mixer_name: mixer
other_mock_call << mock_end
latch.wait(1).should be_true
waiter_thread.join
dial.status.result.should be == :answer
end
+
+ it "#split should then unjoin calls from the mixer" do
+ other_mock_call.should_receive(:unjoin).once.ordered.with(call).and_return do
+ call << Punchblock::Event::Unjoined.new(call_uri: other_mock_call.id)
+ other_mock_call << Punchblock::Event::Unjoined.new(call_uri: call.id)
+ end
+
+ dial = Dial::ParallelConfirmationDial.new to, options, call
+ dial.run subject
+
+ waiter_thread = Thread.new do
+ dial.await_completion
+ latch.countdown!
+ end
+
+ sleep 0.5
+
+ other_mock_call << mock_answered
+
+ dial.split
+
+ call.should_receive(:join).once.ordered.with({mixer_name: mixer}, {})
+ other_mock_call.should_receive(:join).once.ordered.with({mixer_name: mixer}, {})
+ dial.rejoin mixer_name: mixer
+
+ other_mock_call.should_receive(:unjoin).once.ordered.with(mixer_name: mixer).and_return do
+ other_mock_call << Punchblock::Event::Unjoined.new(mixer_name: mixer)
+ end
+ call.should_receive(:unjoin).once.ordered.with(mixer_name: mixer).and_return do
+ call << Punchblock::Event::Unjoined.new(mixer_name: mixer)
+ end
+ dial.split
+
+ other_mock_call << mock_end
+
+ latch.wait(1).should be_true
+
+ waiter_thread.join
+ dial.status.result.should be == :answer
+ end
end
end
context "when another dial is merged in" do
let(:second_root_call_id) { new_uuid }
@@ -1437,38 +2082,38 @@
let(:other_dial) { Dial::ParallelConfirmationDial.new second_to, options, second_root_call }
before do
second_root_call.stub write_command: true, id: second_root_call_id
OutboundCall.should_receive(:new).and_return second_other_mock_call
- second_other_mock_call.should_receive(:join).once.with(second_root_call)
+ second_other_mock_call.should_receive(:join).once.with(second_root_call, {})
second_other_mock_call.should_receive(:dial).once.with(second_to, options)
second_root_call.should_receive(:answer).once
SecureRandom.stub uuid: mixer
- dial.run
- other_dial.run
+ dial.run subject
+ other_dial.run subject
other_mock_call << mock_answered
second_other_mock_call << mock_answered
end
it "should split calls, rejoin to a mixer, and rejoin other calls to mixer" do
- call.should_receive(:unjoin).once.ordered.with(other_mock_call.id).and_return do
+ other_mock_call.should_receive(:unjoin).once.ordered.with(call).and_return do
call << Punchblock::Event::Unjoined.new(call_uri: other_mock_call.id)
other_mock_call << Punchblock::Event::Unjoined.new(call_uri: call.id)
end
- second_root_call.should_receive(:unjoin).once.ordered.with(second_other_mock_call.id).and_return do
+ second_other_mock_call.should_receive(:unjoin).once.ordered.with(second_root_call).and_return do
second_root_call << Punchblock::Event::Unjoined.new(call_uri: second_other_mock_call.id)
second_other_mock_call << Punchblock::Event::Unjoined.new(call_uri: second_root_call.id)
end
- call.should_receive(:join).once.ordered.with(mixer_name: mixer)
- other_mock_call.should_receive(:join).once.ordered.with(mixer_name: mixer)
+ call.should_receive(:join).once.ordered.with({mixer_name: mixer}, {})
+ other_mock_call.should_receive(:join).once.ordered.with({mixer_name: mixer}, {})
- second_root_call.should_receive(:join).once.ordered.with(mixer_name: mixer)
- second_other_mock_call.should_receive(:join).once.ordered.with(mixer_name: mixer)
+ second_root_call.should_receive(:join).once.ordered.with({mixer_name: mixer}, {})
+ second_other_mock_call.should_receive(:join).once.ordered.with({mixer_name: mixer}, {})
dial.merge other_dial
waiter_thread = Thread.new do
dial.await_completion
@@ -1485,10 +2130,27 @@
waiter_thread.join
dial.status.result.should be == :answer
end
+ context "when join options were specified originally" do
+ let(:options) { { join_options: {media: :direct} } }
+
+ it "should rejoin with default options" do
+ other_mock_call.stub(:unjoin)
+ second_other_mock_call.stub(:unjoin)
+
+ call.should_receive(:join).once.ordered.with({mixer_name: mixer}, {})
+ other_mock_call.should_receive(:join).once.ordered.with({mixer_name: mixer}, {})
+
+ second_root_call.should_receive(:join).once.ordered.with({mixer_name: mixer}, {})
+ second_other_mock_call.should_receive(:join).once.ordered.with({mixer_name: mixer}, {})
+
+ dial.merge other_dial
+ end
+ end
+
it "should add the merged calls to the returned status" do
[call, other_mock_call, second_root_call, second_other_mock_call].each { |c| c.stub join: true, unjoin: true }
dial.merge other_dial
waiter_thread = Thread.new do
@@ -1557,20 +2219,90 @@
waiter_thread.join
dial.status.result.should be == :answer
end
+ it "should subsequently rejoin to a mixer" do
+ [call, other_mock_call, second_root_call, second_other_mock_call].each { |c| c.stub join: true, unjoin: true }
+
+ dial.merge other_dial
+
+ waiter_thread = Thread.new do
+ dial.await_completion
+ latch.countdown!
+ end
+
+ sleep 0.5
+
+ other_mock_call << mock_end
+ latch.wait(1).should be_false
+
+ [call, second_root_call, second_other_mock_call].each do |call|
+ call.should_receive(:unjoin).once.with(mixer_name: mixer).and_return do
+ call << Punchblock::Event::Unjoined.new(mixer_name: mixer)
+ end
+ end
+
+ dial.split
+
+ [call, other_mock_call, second_root_call, second_other_mock_call].each do |call|
+ call.should_receive(:join).once.with({mixer_name: mixer}, {}).and_return do
+ call << Punchblock::Event::Joined.new(mixer_name: mixer)
+ end
+ end
+
+ dial.rejoin
+ end
+
+ context "if a call hangs up" do
+ it "should still allow splitting and rejoining" do
+ [call, other_mock_call, second_root_call, second_other_mock_call].each { |c| c.stub join: true, unjoin: true }
+
+ dial.merge other_dial
+
+ waiter_thread = Thread.new do
+ dial.await_completion
+ latch.countdown!
+ end
+
+ sleep 0.5
+
+ [call, second_root_call, second_other_mock_call].each do |call|
+ call.should_receive(:unjoin).once.with(mixer_name: mixer).and_return do
+ call << Punchblock::Event::Unjoined.new(mixer_name: mixer)
+ end
+ end
+
+ other_mock_call.should_receive(:unjoin).and_raise Adhearsion::Call::Hangup
+
+ dial.split
+
+ other_mock_call << mock_end
+ latch.wait(1).should be_false
+
+ [call, second_root_call, second_other_mock_call].each do |call|
+ call.should_receive(:join).once.with({mixer_name: mixer}, {}).and_return do
+ call << Punchblock::Event::Joined.new(mixer_name: mixer)
+ end
+ end
+
+ other_mock_call.should_receive(:join).and_raise Adhearsion::Call::ExpiredError
+
+ dial.rejoin
+ end
+ end
+
context "if the calls were not joined" do
it "should still join to mixer" do
- call.should_receive(:unjoin).once.ordered.with(other_mock_call.id).and_raise Punchblock::ProtocolError.new.setup(:service_unavailable)
- second_root_call.should_receive(:unjoin).once.ordered.with(second_other_mock_call.id).and_raise Punchblock::ProtocolError.new.setup(:service_unavailable)
+ other_mock_call.should_receive(:unjoin).once.ordered.with(call).and_raise Punchblock::ProtocolError.new.setup(:service_unavailable)
+ second_other_mock_call.should_receive(:unjoin).once.ordered.with(second_root_call).and_raise Punchblock::ProtocolError.new.setup(:service_unavailable)
- call.should_receive(:join).once.ordered.with(mixer_name: mixer)
- other_mock_call.should_receive(:join).once.ordered.with(mixer_name: mixer)
+ call.should_receive(:join).once.ordered.with({mixer_name: mixer}, {})
+ other_mock_call.should_receive(:join).once.ordered.with({mixer_name: mixer}, {})
- second_root_call.should_receive(:join).once.ordered.with(mixer_name: mixer)
- second_other_mock_call.should_receive(:join).once.ordered.with(mixer_name: mixer)
+ second_root_call.should_receive(:join).once.ordered.with({mixer_name: mixer}, {})
+ second_other_mock_call.should_receive(:join).once.ordered.with({mixer_name: mixer}, {})
dial.merge other_dial
waiter_thread = Thread.new do
dial.await_completion
@@ -1630,11 +2362,11 @@
end
end
it "dials all parties and joins the first one to answer, hanging up the rest" do
call.should_receive(:answer).once
- other_mock_call.should_receive(:join).once.with(call)
+ other_mock_call.should_receive(:join).once.with(call, {})
second_other_mock_call.should_receive(:hangup).once.and_return do
second_other_mock_call << mock_end
end
t = dial_in_thread
@@ -1653,11 +2385,11 @@
status.calls.each { |c| c.should be_a OutboundCall }
end
it "unblocks when the joined call unjoins, allowing it to proceed further" do
call.should_receive(:answer).once
- other_mock_call.should_receive(:join).once.with(call)
+ other_mock_call.should_receive(:join).once.with(call, {})
other_mock_call.should_receive(:hangup).once
second_other_mock_call.should_receive(:hangup).once.and_return do
second_other_mock_call << mock_end
end
@@ -1765,11 +2497,11 @@
end
context "when a call is answered and joined, and the other ends with an error" do
it "has an overall dial status of :answer" do
call.should_receive(:answer).once
- other_mock_call.should_receive(:join).once.with(call)
+ other_mock_call.should_receive(:join).once.with(call, {})
second_other_mock_call.should_receive(:hangup).once.and_return do
second_other_mock_call << mock_end(:error)
end
t = dial_in_thread
@@ -1814,11 +2546,11 @@
describe "if someone answers before the timeout elapses" do
it "should not abort until the far end hangs up" do
other_mock_call.should_receive(:dial).once.with(to, hash_including(:timeout => timeout))
call.should_receive(:answer).once
- other_mock_call.should_receive(:join).once.with(call)
+ other_mock_call.should_receive(:join).once.with(call, {})
OutboundCall.should_receive(:new).and_return other_mock_call
time = Time.now
t = Thread.new do
@@ -1922,11 +2654,11 @@
other_mock_call.should_receive(:hangup).once.and_return do
other_mock_call << mock_end
end
other_mock_call['confirm'] = true
call.should_receive(:answer).once
- other_mock_call.should_receive(:join).once.with(call)
+ other_mock_call.should_receive(:join).once.with(call, {})
t = dial_in_thread
latch.wait(1).should be_false
@@ -2009,11 +2741,11 @@
second_other_mock_call['confirmation_delay'] = 1.3
call.should_receive(:answer).once
other_mock_call.should_receive(:dial).once.with(to, from: nil)
- other_mock_call.should_receive(:join).once.with(call)
+ other_mock_call.should_receive(:join).once.with(call, {})
other_mock_call.should_receive(:hangup).once.and_return do
other_mock_call.async.deliver_message mock_end
end
second_other_mock_call.should_receive(:dial).once.with(second_to, from: nil)
@@ -2073,10 +2805,10 @@
call.stub answer: true
other_mock_call.stub dial: true, join: true
other_mock_call.should_receive(:hangup).never
- subject.run
+ subject.run double('controller')
subject.skip_cleanup
Thread.new do
subject.await_completion