spec/adhearsion/call_controller/dial_spec.rb in adhearsion-2.4.0.beta1 vs spec/adhearsion/call_controller/dial_spec.rb in adhearsion-2.4.0.beta2

- old
+ new

@@ -106,11 +106,11 @@ other_mock_call << mock_end latch.wait(1).should be_true end - it "hangs up the new call when the dial unblocks" do + 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(:hangup).once dial_in_thread @@ -174,11 +174,10 @@ latch.wait(1).should be_true t.join status = t.value status.result.should be == :answer - status.joined_call.should eq(other_mock_call) joined_status = status.joins[status.calls.first] joined_status.result.should == :joined end @@ -204,15 +203,397 @@ latch.wait(1).should be_true t.join status = t.value status.result.should be == :answer - status.joined_call.should eq(other_mock_call) joined_status = status.joins[status.calls.first] joined_status.duration.should == 37.0 end end + + context "when a dial is split" do + before do + call.should_receive(:answer).once + other_mock_call.should_receive(:join).once.with(call) + 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 + 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 + + waiter_thread = Thread.new do + dial.await_completion + latch.countdown! + end + + sleep 0.5 + + other_mock_call << mock_answered + + dial.split + other_mock_call << mock_end + + latch.wait(1).should be_true + + waiter_thread.join + dial.status.result.should be == :answer + end + + it "should not unblock immediately" do + dial = Dial::Dial.new to, options, call + dial.run + + waiter_thread = Thread.new do + dial.await_completion + latch.countdown! + end + + sleep 0.5 + + other_mock_call << mock_answered + + dial.split + + latch.wait(1).should be_false + + other_mock_call << mock_end + + latch.wait(1).should be_true + + waiter_thread.join + dial.status.result.should be == :answer + end + + it "should set end time" do + dial = Dial::Dial.new to, options, call + dial.run + + waiter_thread = Thread.new do + dial.await_completion + latch.countdown! + end + + sleep 0.5 + + base_time = Time.local(2008, 9, 1, 12, 0, 0) + Timecop.freeze base_time + + other_mock_call << mock_answered + + base_time = Time.local(2008, 9, 1, 12, 0, 37) + Timecop.freeze base_time + dial.split + + base_time = Time.local(2008, 9, 1, 12, 0, 54) + Timecop.freeze base_time + other_mock_call << mock_end + + latch.wait(1).should be_true + + waiter_thread.join + status = dial.status + status.result.should be == :answer + joined_status = status.joins[status.calls.first] + joined_status.duration.should == 37.0 + end + + context "with new controllers specified" do + let(:split_latch) { CountDownLatch.new 2 } + + let(:split_controller) do + latch = split_latch + Class.new(Adhearsion::CallController) do + @@split_latch = latch + + def run + call['hit_split_controller'] = self.class + call['split_controller_metadata'] = metadata + @@split_latch.countdown! + end + end + end + + 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 + + waiter_thread = Thread.new do + dial.await_completion + latch.countdown! + end + + sleep 0.5 + + other_mock_call << mock_answered + + should_receive(:callback).once.with(call) + should_receive(:callback).once.with(other_mock_call) + + dial.split main: main_split_controller, others: others_split_controller, main_callback: ->(call) { self.callback(call) }, others_callback: ->(call) { self.callback(call) } + + latch.wait(1).should be_false + split_latch.wait(1).should be_true + + call['hit_split_controller'].should == main_split_controller + call['split_controller_metadata']['current_dial'].should be dial + + other_mock_call['hit_split_controller'].should == others_split_controller + other_mock_call['split_controller_metadata']['current_dial'].should be dial + + other_mock_call << mock_end + + latch.wait(1).should be_true + + waiter_thread.join + dial.status.result.should be == :answer + 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 + 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 + + waiter_thread = Thread.new do + dial.await_completion + latch.countdown! + end + + sleep 0.5 + + other_mock_call << mock_answered + + dial.split + + 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 "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 + 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 + + 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 << 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 } + let(:second_root_call) { Adhearsion::Call.new } + let(:mixer) { SecureRandom.uuid } + + let(:dial) { Dial::Dial.new to, options, call } + 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(:dial).once.with(second_to, options) + second_root_call.should_receive(:answer).once + + SecureRandom.stub uuid: mixer + + dial.run + other_dial.run + + 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 + 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_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) + + 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 + latch.countdown! + end + + sleep 0.5 + + other_mock_call << mock_end + second_root_call << mock_end + second_other_mock_call << mock_end + + latch.wait(1).should be_true + + waiter_thread.join + dial.status.result.should be == :answer + 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 + dial.await_completion + latch.countdown! + end + + sleep 0.5 + + other_mock_call << mock_end + second_root_call << mock_end + second_other_mock_call << mock_end + + latch.wait(1).should be_true + + waiter_thread.join + dial.status.result.should be == :answer + dial.status.calls.should include(second_root_call, second_other_mock_call) + end + + it "should not unblock until all joined calls end" 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 + + second_other_mock_call << mock_end + latch.wait(1).should be_false + + second_root_call << mock_end + latch.wait(1).should be_true + + waiter_thread.join + dial.status.result.should be == :answer + end + + it "should 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, second_root_call, second_other_mock_call].each do |c| + c.should_receive(:hangup).once + end + + dial.merge other_dial + + 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 + + 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) + + 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 + + waiter_thread = Thread.new do + dial.await_completion + latch.countdown! + end + + sleep 0.5 + + other_mock_call << mock_end + second_root_call << mock_end + second_other_mock_call << mock_end + + latch.wait(1).should be_true + + waiter_thread.join + dial.status.result.should be == :answer + end + end + end + end end describe "when the caller has already hung up" do before do call << mock_end @@ -723,11 +1104,11 @@ other_mock_call << mock_end latch.wait(1).should be_true end - it "hangs up the new call when the dial unblocks" do + 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) dial_in_thread @@ -791,11 +1172,10 @@ latch.wait(1).should be_true t.join status = t.value status.result.should be == :answer - status.joined_call.should eq(other_mock_call) joined_status = status.joins[status.calls.first] joined_status.result.should == :joined end @@ -821,15 +1201,397 @@ latch.wait(1).should be_true t.join status = t.value status.result.should be == :answer - status.joined_call.should eq(other_mock_call) joined_status = status.joins[status.calls.first] joined_status.duration.should == 37.0 end end + + context "when a dial is split" do + before do + call.should_receive(:answer).once + other_mock_call.should_receive(:join).once.with(call) + 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 + 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 + + waiter_thread = Thread.new do + dial.await_completion + latch.countdown! + end + + sleep 0.5 + + other_mock_call << mock_answered + + dial.split + other_mock_call << mock_end + + latch.wait(1).should be_true + + waiter_thread.join + dial.status.result.should be == :answer + end + + it "should not unblock immediately" do + dial = Dial::ParallelConfirmationDial.new to, options, call + dial.run + + waiter_thread = Thread.new do + dial.await_completion + latch.countdown! + end + + sleep 0.5 + + other_mock_call << mock_answered + + dial.split + + latch.wait(1).should be_false + + other_mock_call << mock_end + + latch.wait(1).should be_true + + waiter_thread.join + dial.status.result.should be == :answer + end + + it "should set end time" do + dial = Dial::ParallelConfirmationDial.new to, options, call + dial.run + + waiter_thread = Thread.new do + dial.await_completion + latch.countdown! + end + + sleep 0.5 + + base_time = Time.local(2008, 9, 1, 12, 0, 0) + Timecop.freeze base_time + + other_mock_call << mock_answered + + base_time = Time.local(2008, 9, 1, 12, 0, 37) + Timecop.freeze base_time + dial.split + + base_time = Time.local(2008, 9, 1, 12, 0, 54) + Timecop.freeze base_time + other_mock_call << mock_end + + latch.wait(1).should be_true + + waiter_thread.join + status = dial.status + status.result.should be == :answer + joined_status = status.joins[status.calls.first] + joined_status.duration.should == 37.0 + end + + context "with new controllers specified" do + let(:split_latch) { CountDownLatch.new 2 } + + let(:split_controller) do + latch = split_latch + Class.new(Adhearsion::CallController) do + @@split_latch = latch + + def run + call['hit_split_controller'] = self.class + call['split_controller_metadata'] = metadata + @@split_latch.countdown! + end + end + end + + 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 + + waiter_thread = Thread.new do + dial.await_completion + latch.countdown! + end + + sleep 0.5 + + other_mock_call << mock_answered + + should_receive(:callback).once.with(call) + should_receive(:callback).once.with(other_mock_call) + + dial.split main: main_split_controller, others: others_split_controller, main_callback: ->(call) { self.callback(call) }, others_callback: ->(call) { self.callback(call) } + + latch.wait(1).should be_false + split_latch.wait(1).should be_true + + call['hit_split_controller'].should == main_split_controller + call['split_controller_metadata']['current_dial'].should be dial + + other_mock_call['hit_split_controller'].should == others_split_controller + other_mock_call['split_controller_metadata']['current_dial'].should be dial + + other_mock_call << mock_end + + latch.wait(1).should be_true + + waiter_thread.join + dial.status.result.should be == :answer + 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 + 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 + + waiter_thread = Thread.new do + dial.await_completion + latch.countdown! + end + + sleep 0.5 + + other_mock_call << mock_answered + + dial.split + + 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 "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 + 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 + + 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 << 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 } + let(:second_root_call) { Adhearsion::Call.new } + let(:mixer) { SecureRandom.uuid } + + let(:dial) { Dial::ParallelConfirmationDial.new to, options, call } + 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(:dial).once.with(second_to, options) + second_root_call.should_receive(:answer).once + + SecureRandom.stub uuid: mixer + + dial.run + other_dial.run + + 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 + 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_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) + + 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 + latch.countdown! + end + + sleep 0.5 + + other_mock_call << mock_end + second_root_call << mock_end + second_other_mock_call << mock_end + + latch.wait(1).should be_true + + waiter_thread.join + dial.status.result.should be == :answer + 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 + dial.await_completion + latch.countdown! + end + + sleep 0.5 + + other_mock_call << mock_end + second_root_call << mock_end + second_other_mock_call << mock_end + + latch.wait(1).should be_true + + waiter_thread.join + dial.status.result.should be == :answer + dial.status.calls.should include(second_root_call, second_other_mock_call) + end + + it "should not unblock until all joined calls end" 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 + + second_other_mock_call << mock_end + latch.wait(1).should be_false + + second_root_call << mock_end + latch.wait(1).should be_true + + waiter_thread.join + dial.status.result.should be == :answer + end + + it "should 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, second_root_call, second_other_mock_call].each do |c| + c.should_receive(:hangup).once + end + + dial.merge other_dial + + 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 + + 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) + + 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 + + waiter_thread = Thread.new do + dial.await_completion + latch.countdown! + end + + sleep 0.5 + + other_mock_call << mock_end + second_root_call << mock_end + second_other_mock_call << mock_end + + latch.wait(1).should be_true + + waiter_thread.join + dial.status.result.should be == :answer + end + end + end + end end describe "when the caller has already hung up" do before do call << mock_end @@ -1284,9 +2046,49 @@ status.result.should be == :answer status.joins[other_mock_call].result.should == :joined status.joins[second_other_mock_call].result.should == :lost_confirmation end end + end + end + end + + describe Dial::Dial do + subject { Dial::Dial.new to, {}, call } + + describe "#prep_calls" do + it "yields all calls to the passed block" do + OutboundCall.should_receive(:new).and_return other_mock_call + + gathered_calls = [] + subject.prep_calls { |call| gathered_calls << call } + + expect(gathered_calls).to include(other_mock_call) + end + end + + context "#skip_cleanup" do + it "allows the new call to continue after the root call ends" do + OutboundCall.should_receive(:new).and_return other_mock_call + + call.stub answer: true + other_mock_call.stub dial: true, join: true + other_mock_call.should_receive(:hangup).never + + subject.run + + subject.skip_cleanup + + Thread.new do + subject.await_completion + subject.cleanup_calls + latch.countdown! + end + + other_mock_call << mock_answered + call << mock_end + + latch.wait(1).should be_true end end end end end