adhearsion 2.3.5 → 2.4.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +0 -1
  3. data/CHANGELOG.md +14 -0
  4. data/Gemfile +2 -0
  5. data/README.markdown +21 -2
  6. data/adhearsion.gemspec +5 -4
  7. data/features/cli_plugin.feature +41 -0
  8. data/features/cli_start.feature +12 -4
  9. data/features/step_definitions/cli_steps.rb +12 -0
  10. data/features/support/env.rb +1 -1
  11. data/features/support/utils.rb +0 -1
  12. data/lib/adhearsion.rb +4 -1
  13. data/lib/adhearsion/call.rb +92 -22
  14. data/lib/adhearsion/call_controller.rb +19 -15
  15. data/lib/adhearsion/call_controller/dial.rb +157 -25
  16. data/lib/adhearsion/call_controller/menu_dsl/menu_builder.rb +8 -0
  17. data/lib/adhearsion/call_controller/output/async_player.rb +1 -1
  18. data/lib/adhearsion/call_controller/output/formatter.rb +1 -1
  19. data/lib/adhearsion/call_controller/output/player.rb +1 -1
  20. data/lib/adhearsion/calls.rb +2 -0
  21. data/lib/adhearsion/cli_commands.rb +3 -163
  22. data/lib/adhearsion/cli_commands/ahn_command.rb +141 -0
  23. data/lib/adhearsion/cli_commands/plugin_command.rb +74 -0
  24. data/lib/adhearsion/cli_commands/thor_errors.rb +36 -0
  25. data/lib/adhearsion/console.rb +14 -6
  26. data/lib/adhearsion/generators/app/templates/spec/call_controllers/simon_game_spec.rb +36 -36
  27. data/lib/adhearsion/generators/controller/templates/spec/controller_spec.rb +1 -1
  28. data/lib/adhearsion/generators/plugin/templates/plugin-template.gemspec.tt +0 -1
  29. data/lib/adhearsion/generators/plugin/templates/spec/plugin-template/controller_methods_spec.rb.tt +1 -1
  30. data/lib/adhearsion/generators/plugin/templates/spec/spec_helper.rb.tt +0 -1
  31. data/lib/adhearsion/logging.rb +5 -1
  32. data/lib/adhearsion/outbound_call.rb +16 -0
  33. data/lib/adhearsion/punchblock_plugin.rb +0 -2
  34. data/lib/adhearsion/punchblock_plugin/initializer.rb +7 -12
  35. data/lib/adhearsion/version.rb +1 -1
  36. data/spec/adhearsion/call_controller/dial_spec.rb +785 -32
  37. data/spec/adhearsion/call_controller/menu_dsl/menu_builder_spec.rb +10 -0
  38. data/spec/adhearsion/call_controller/output/async_player_spec.rb +1 -1
  39. data/spec/adhearsion/call_controller/output/player_spec.rb +1 -1
  40. data/spec/adhearsion/call_controller/output_spec.rb +3 -3
  41. data/spec/adhearsion/call_controller/record_spec.rb +1 -1
  42. data/spec/adhearsion/call_controller_spec.rb +13 -9
  43. data/spec/adhearsion/call_spec.rb +216 -51
  44. data/spec/adhearsion/calls_spec.rb +1 -1
  45. data/spec/adhearsion/console_spec.rb +20 -9
  46. data/spec/adhearsion/outbound_call_spec.rb +40 -6
  47. data/spec/adhearsion/punchblock_plugin/initializer_spec.rb +9 -21
  48. data/spec/adhearsion/punchblock_plugin_spec.rb +1 -1
  49. data/spec/adhearsion/router_spec.rb +1 -1
  50. data/spec/spec_helper.rb +11 -15
  51. data/spec/support/call_controller_test_helpers.rb +2 -2
  52. data/spec/support/punchblock_mocks.rb +2 -2
  53. metadata +41 -16
@@ -4,7 +4,7 @@ require 'spec_helper'
4
4
 
5
5
  describe <%= @controller_name.camelcase %> do
6
6
 
7
- let(:mock_call) { mock 'Call', :to => '1112223333', :from => "2223334444" }
7
+ let(:mock_call) { double 'Call', :to => '1112223333', :from => "2223334444" }
8
8
  let(:metadata) { {} }
9
9
  subject { <%= @controller_name.camelcase %>.new(mock_call, metadata) }
10
10
 
@@ -26,6 +26,5 @@ Gem::Specification.new do |s|
26
26
  s.add_development_dependency %q<bundler>, ["~> 1.0"]
27
27
  s.add_development_dependency %q<rspec>, ["~> 2.5"]
28
28
  s.add_development_dependency %q<rake>, [">= 0"]
29
- s.add_development_dependency %q<mocha>, [">= 0"]
30
29
  s.add_development_dependency %q<guard-rspec>
31
30
  end
@@ -8,7 +8,7 @@ module <%= @plugin_name %>
8
8
  include <%= @plugin_name %>::ControllerMethods
9
9
  end
10
10
 
11
- let(:mock_call) { mock 'Call' }
11
+ let(:mock_call) { double 'Call' }
12
12
 
13
13
  subject do
14
14
  TestController.new mock_call
@@ -5,7 +5,6 @@ RSpec.configure do |config|
5
5
  config.color_enabled = true
6
6
  config.tty = true
7
7
 
8
- config.mock_with :mocha
9
8
  config.filter_run :focus => true
10
9
  config.run_all_when_everything_filtered = true
11
10
  end
@@ -7,6 +7,10 @@ module Adhearsion
7
7
 
8
8
  LOG_LEVELS = %w(TRACE DEBUG INFO WARN ERROR FATAL)
9
9
 
10
+ class ::Logging::Repository
11
+ def delete( key ) @h.delete(to_key(key)) end
12
+ end
13
+
10
14
  module HasLogger
11
15
  def logger
12
16
  ::Logging.logger[logger_id]
@@ -76,7 +80,7 @@ module Adhearsion
76
80
 
77
81
  ::Logging.logger.root.level = level
78
82
 
79
- formatter = formatter if formatter
83
+ self.formatter = formatter if formatter
80
84
  end
81
85
 
82
86
  def default_appenders
@@ -36,6 +36,10 @@ module Adhearsion
36
36
  dial_command.target_call_id if dial_command
37
37
  end
38
38
 
39
+ def domain
40
+ dial_command.domain if dial_command
41
+ end
42
+
39
43
  def client
40
44
  PunchblockPlugin::Initializer.client
41
45
  end
@@ -76,6 +80,12 @@ module Adhearsion
76
80
  end
77
81
  end
78
82
 
83
+ # @private
84
+ def register_initial_handlers
85
+ super
86
+ on_answer { |event| @start_time = Time.now }
87
+ end
88
+
79
89
  def run_router
80
90
  catching_standard_errors do
81
91
  Adhearsion.router.handle current_actor
@@ -105,5 +115,11 @@ module Adhearsion
105
115
  run_router_on_answer
106
116
  end
107
117
  end
118
+
119
+ private
120
+
121
+ def transport
122
+ dial_command.transport if dial_command
123
+ end
108
124
  end
109
125
  end
@@ -20,8 +20,6 @@ module Adhearsion
20
20
  port Proc.new { PunchblockPlugin.default_port_for_platform platform }, :transform => Proc.new { |v| PunchblockPlugin.validate_number v }, :desc => "Port punchblock needs to connect"
21
21
  certs_directory nil , :desc => "Directory containing certificates for securing the connection."
22
22
  root_domain nil , :desc => "The root domain at which to address the server"
23
- calls_domain nil , :desc => "The domain at which to address calls"
24
- mixers_domain nil , :desc => "The domain at which to address mixers"
25
23
  connection_timeout 60 , :transform => Proc.new { |v| PunchblockPlugin.validate_number v }, :desc => "The amount of time to wait for a connection"
26
24
  reconnect_attempts 1.0/0.0 , :transform => Proc.new { |v| PunchblockPlugin.validate_number v }, :desc => "The number of times to (re)attempt connection to the server"
27
25
  reconnect_timer 5 , :transform => Proc.new { |v| PunchblockPlugin.validate_number v }, :desc => "Delay between connection attempts"
@@ -5,7 +5,7 @@ require 'blather'
5
5
  module Adhearsion
6
6
  class PunchblockPlugin
7
7
  class Initializer
8
- cattr_accessor :config, :client, :connection, :dispatcher, :attempts
8
+ cattr_accessor :config, :client, :dispatcher, :attempts
9
9
 
10
10
  self.attempts = 0
11
11
 
@@ -14,16 +14,10 @@ module Adhearsion
14
14
  self.config = Adhearsion.config[:punchblock]
15
15
 
16
16
  username = self.config.username
17
- connection_class = case (self.config.platform || :xmpp)
18
- when :xmpp
17
+ if (self.config.platform || :xmpp) == :xmpp
19
18
  username = Blather::JID.new username
20
19
  username = Blather::JID.new username.node, username.domain, resource unless username.resource
21
20
  username = username.to_s
22
- Punchblock::Connection::XMPP
23
- when :asterisk
24
- Punchblock::Connection::Asterisk
25
- when :freeswitch
26
- Punchblock::Connection::Freeswitch
27
21
  end
28
22
 
29
23
  connection_options = {
@@ -34,14 +28,11 @@ module Adhearsion
34
28
  :port => self.config.port,
35
29
  :certs => self.config.certs_directory,
36
30
  :root_domain => self.config.root_domain,
37
- :calls_domain => self.config.calls_domain,
38
- :mixers_domain => self.config.mixers_domain,
39
31
  :media_engine => self.config.media_engine,
40
32
  :default_voice => self.config.default_voice
41
33
  }
42
34
 
43
- self.connection = connection_class.new connection_options
44
- self.client = Punchblock::Client.new :connection => connection
35
+ self.client = Punchblock.client_with_connection self.config.platform, connection_options
45
36
 
46
37
  # Tell the Punchblock connection that we are ready to process calls.
47
38
  Events.register_callback :after_initialized do
@@ -170,6 +161,10 @@ module Adhearsion
170
161
  def resource
171
162
  [Adhearsion::Process.fqdn, ::Process.pid].join '-'
172
163
  end
164
+
165
+ def connection
166
+ client.connection
167
+ end
173
168
  end
174
169
  end # Punchblock
175
170
  end # Plugin
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module Adhearsion
4
- VERSION = '2.3.5'
4
+ VERSION = '2.4.0.beta1'
5
5
  end
@@ -20,11 +20,11 @@ module Adhearsion
20
20
  let(:latch) { CountDownLatch.new 1 }
21
21
 
22
22
  before do
23
- other_mock_call.stub id: other_call_id, write_command: true
24
- second_other_mock_call.stub id: second_other_call_id, write_command: true
23
+ other_mock_call.wrapped_object.stub id: other_call_id, write_command: true
24
+ second_other_mock_call.wrapped_object.stub id: second_other_call_id, write_command: true
25
25
  end
26
26
 
27
- def mock_end(reason = :hangup)
27
+ def mock_end(reason = :hangup_command)
28
28
  Punchblock::Event::End.new.tap { |event| event.stub reason: reason }
29
29
  end
30
30
 
@@ -33,7 +33,12 @@ module Adhearsion
33
33
  OutboundCall.should_receive(:new).and_return other_mock_call
34
34
  other_mock_call.should_receive(:dial).with(to, :from => 'foo').once
35
35
  dial_thread = Thread.new do
36
- subject.dial(to, :from => 'foo').should be_a Dial::DialStatus
36
+ status = subject.dial(to, :from => 'foo')
37
+
38
+ status.should be_a Dial::DialStatus
39
+ joined_status = status.joins[status.calls.first]
40
+ joined_status.duration.should == 0.0
41
+ joined_status.result.should == :no_answer
37
42
  end
38
43
  sleep 0.1
39
44
  other_mock_call << mock_end
@@ -65,7 +70,6 @@ module Adhearsion
65
70
  describe "without a block" do
66
71
  before do
67
72
  other_mock_call.should_receive(:dial).once.with(to, options)
68
- other_mock_call.should_receive(:hangup).once
69
73
  OutboundCall.should_receive(:new).and_return other_mock_call
70
74
  end
71
75
 
@@ -80,6 +84,7 @@ module Adhearsion
80
84
  end
81
85
 
82
86
  it "unblocks the original controller if the original call ends" do
87
+ other_mock_call.should_receive(:hangup).once
83
88
  dial_in_thread
84
89
 
85
90
  latch.wait(1).should be_false
@@ -106,6 +111,7 @@ module Adhearsion
106
111
  it "hangs up the new call when the dial unblocks" do
107
112
  call.should_receive(:answer).once
108
113
  other_mock_call.should_receive(:join).once.with(call)
114
+ other_mock_call.should_receive(:hangup).once
109
115
 
110
116
  dial_in_thread
111
117
 
@@ -146,6 +152,10 @@ module Adhearsion
146
152
  t.join
147
153
  status = t.value
148
154
  status.result.should be == :error
155
+
156
+ joined_status = status.joins[status.calls.first]
157
+ joined_status.duration.should == 0.0
158
+ joined_status.result.should == :error
149
159
  end
150
160
  end
151
161
 
@@ -166,6 +176,39 @@ module Adhearsion
166
176
  t.join
167
177
  status = t.value
168
178
  status.result.should be == :answer
179
+ status.joined_call.should eq(other_mock_call)
180
+
181
+ joined_status = status.joins[status.calls.first]
182
+ joined_status.result.should == :joined
183
+ end
184
+
185
+ it "records the duration of the join" do
186
+ call.should_receive(:answer).once
187
+ other_mock_call.should_receive(:join).once.with(call)
188
+ other_mock_call.stub hangup: true
189
+
190
+ t = dial_in_thread
191
+
192
+ sleep 0.5
193
+
194
+ base_time = Time.local(2008, 9, 1, 12, 0, 0)
195
+ Timecop.freeze base_time
196
+
197
+ other_mock_call << mock_answered
198
+
199
+ base_time = Time.local(2008, 9, 1, 12, 0, 37)
200
+ Timecop.freeze base_time
201
+ other_mock_call << Punchblock::Event::Unjoined.new(call_uri: call.id)
202
+ other_mock_call << mock_end
203
+
204
+ latch.wait(1).should be_true
205
+
206
+ t.join
207
+ status = t.value
208
+ status.result.should be == :answer
209
+ status.joined_call.should eq(other_mock_call)
210
+ joined_status = status.joins[status.calls.first]
211
+ joined_status.duration.should == 37.0
169
212
  end
170
213
  end
171
214
  end
@@ -194,11 +237,9 @@ module Adhearsion
194
237
  OutboundCall.should_receive(:new).and_return other_mock_call, second_other_mock_call
195
238
 
196
239
  other_mock_call.should_receive(:dial).once.with(to, other_options)
197
- other_mock_call.should_receive(:hangup).once
198
240
 
199
241
  second_other_mock_call.should_receive(:dial).once.with(second_to, second_other_options)
200
242
  second_other_mock_call.should_receive(:join).never
201
- second_other_mock_call.should_receive(:hangup).once
202
243
  end
203
244
 
204
245
  def dial_in_thread
@@ -212,7 +253,9 @@ module Adhearsion
212
253
  it "dials all parties and joins the first one to answer, hanging up the rest" do
213
254
  call.should_receive(:answer).once
214
255
  other_mock_call.should_receive(:join).once.with(call)
215
- second_other_mock_call.should_receive(:hangup).once
256
+ second_other_mock_call.should_receive(:hangup).once.and_return do
257
+ second_other_mock_call << mock_end
258
+ end
216
259
 
217
260
  t = dial_in_thread
218
261
 
@@ -221,10 +264,6 @@ module Adhearsion
221
264
  other_mock_call << mock_answered
222
265
  other_mock_call << mock_end
223
266
 
224
- latch.wait(1).should be_false
225
-
226
- second_other_mock_call << mock_end
227
-
228
267
  latch.wait(2).should be_true
229
268
 
230
269
  t.join
@@ -237,18 +276,17 @@ module Adhearsion
237
276
  it "unblocks when the joined call unjoins, allowing it to proceed further" do
238
277
  call.should_receive(:answer).once
239
278
  other_mock_call.should_receive(:join).once.with(call)
240
- second_other_mock_call.should_receive(:hangup).once
279
+ other_mock_call.should_receive(:hangup).once
280
+ second_other_mock_call.should_receive(:hangup).once.and_return do
281
+ second_other_mock_call << mock_end
282
+ end
241
283
 
242
284
  t = dial_in_thread
243
285
 
244
286
  latch.wait(1).should be_false
245
287
 
246
288
  other_mock_call << mock_answered
247
- other_mock_call << Punchblock::Event::Unjoined.new(:call_id => call.id)
248
-
249
- latch.wait(1).should be_false
250
-
251
- second_other_mock_call << mock_end
289
+ other_mock_call << Punchblock::Event::Unjoined.new(call_uri: call.id)
252
290
 
253
291
  latch.wait(2).should be_true
254
292
 
@@ -350,7 +388,9 @@ module Adhearsion
350
388
  it "has an overall dial status of :answer" do
351
389
  call.should_receive(:answer).once
352
390
  other_mock_call.should_receive(:join).once.with(call)
353
- second_other_mock_call.should_receive(:hangup).once
391
+ second_other_mock_call.should_receive(:hangup).once.and_return do
392
+ second_other_mock_call << mock_end(:error)
393
+ end
354
394
 
355
395
  t = dial_in_thread
356
396
 
@@ -359,8 +399,6 @@ module Adhearsion
359
399
  other_mock_call << mock_answered
360
400
  other_mock_call << mock_end
361
401
 
362
- second_other_mock_call << mock_end(:error)
363
-
364
402
  latch.wait(1).should be_true
365
403
 
366
404
  t.join
@@ -388,7 +426,7 @@ module Adhearsion
388
426
 
389
427
  latch.wait
390
428
  time = Time.now - time
391
- time.to_i.should be == timeout
429
+ time.round.should be == timeout
392
430
  t.join
393
431
  status = t.value
394
432
  status.result.should be == :timeout
@@ -399,7 +437,6 @@ module Adhearsion
399
437
  other_mock_call.should_receive(:dial).once.with(to, hash_including(:timeout => timeout))
400
438
  call.should_receive(:answer).once
401
439
  other_mock_call.should_receive(:join).once.with(call)
402
- other_mock_call.should_receive(:hangup).once
403
440
  OutboundCall.should_receive(:new).and_return other_mock_call
404
441
 
405
442
  time = Time.now
@@ -449,16 +486,16 @@ module Adhearsion
449
486
 
450
487
  let(:options) { {:confirm => confirmation_controller} }
451
488
 
452
- before do
453
- other_mock_call.should_receive(:dial).once
454
- OutboundCall.should_receive(:new).and_return other_mock_call
455
- end
456
-
457
489
  context "with confirmation controller metadata specified" do
458
490
  let(:options) { {:confirm => confirmation_controller, :confirm_metadata => {:foo => 'bar'}} }
459
491
 
492
+ before do
493
+ other_mock_call.should_receive(:dial).once
494
+ OutboundCall.should_receive(:new).and_return other_mock_call
495
+ end
496
+
460
497
  it "should set the metadata on the controller" do
461
- other_mock_call.should_receive(:hangup).twice.and_return do
498
+ other_mock_call.should_receive(:hangup).once.and_return do
462
499
  other_mock_call << mock_end
463
500
  end
464
501
  other_mock_call['confirm'] = false
@@ -477,8 +514,13 @@ module Adhearsion
477
514
  end
478
515
 
479
516
  context "when an outbound call is answered" do
517
+ before do
518
+ other_mock_call.should_receive(:dial).once
519
+ OutboundCall.should_receive(:new).and_return other_mock_call
520
+ end
521
+
480
522
  it "should execute the specified confirmation controller" do
481
- other_mock_call.should_receive(:hangup).twice.and_return do
523
+ other_mock_call.should_receive(:hangup).once.and_return do
482
524
  other_mock_call << mock_end
483
525
  end
484
526
  other_mock_call['confirm'] = false
@@ -503,7 +545,14 @@ module Adhearsion
503
545
 
504
546
  latch.wait(1).should be_false
505
547
 
548
+ base_time = Time.local(2008, 9, 1, 12, 0, 0)
549
+ Timecop.freeze base_time
550
+
506
551
  other_mock_call << mock_answered
552
+
553
+ base_time = Time.local(2008, 9, 1, 12, 0, 42)
554
+ Timecop.freeze base_time
555
+ other_mock_call << Punchblock::Event::Unjoined.new(call_uri: call.id)
507
556
  other_mock_call << mock_end
508
557
 
509
558
  latch.wait(1).should be_true
@@ -511,10 +560,14 @@ module Adhearsion
511
560
  t.join
512
561
  status = t.value
513
562
  status.result.should be == :answer
563
+
564
+ joined_status = status.joins[status.calls.first]
565
+ joined_status.duration.should == 42.0
566
+ joined_status.result.should == :joined
514
567
  end
515
568
 
516
569
  it "should not join the calls if the call is not active after execution of the call controller" do
517
- other_mock_call.should_receive(:hangup).twice.and_return do
570
+ other_mock_call.should_receive(:hangup).once.and_return do
518
571
  other_mock_call << mock_end
519
572
  end
520
573
  other_mock_call['confirm'] = false
@@ -532,10 +585,710 @@ module Adhearsion
532
585
  t.join
533
586
  status = t.value
534
587
  status.result.should be == :unconfirmed
588
+
589
+ joined_status = status.joins[status.calls.first]
590
+ joined_status.duration.should == 0.0
591
+ joined_status.result.should == :unconfirmed
592
+ end
593
+ end
594
+
595
+ context "when multiple calls are made" do
596
+ before do
597
+ OutboundCall.should_receive(:new).and_return other_mock_call, second_other_mock_call
598
+ end
599
+
600
+ def dial_in_thread
601
+ Thread.new do
602
+ status = subject.dial [to, second_to], options
603
+ latch.countdown!
604
+ status
605
+ end
606
+ end
607
+
608
+ context "when one answers" do
609
+ it "should only execute the confirmation controller on the first call to answer, immediately hanging up all others" do
610
+ other_mock_call['confirm'] = true
611
+ call.should_receive(:answer).once
612
+
613
+ other_mock_call.should_receive(:dial).once.with(to, from: nil)
614
+ other_mock_call.should_receive(:join).once.with(call)
615
+ other_mock_call.should_receive(:hangup).once
616
+
617
+ second_other_mock_call.should_receive(:dial).once.with(second_to, from: nil)
618
+ second_other_mock_call.should_receive(:join).never
619
+ second_other_mock_call.should_receive(:execute_controller).never
620
+ second_other_mock_call.should_receive(:hangup).once.and_return do
621
+ second_other_mock_call << mock_end(:foo)
622
+ end
623
+
624
+ t = dial_in_thread
625
+
626
+ latch.wait(1).should be_false
627
+
628
+ other_mock_call << mock_answered
629
+ confirmation_latch.wait(1).should be_true
630
+
631
+ other_mock_call << Punchblock::Event::Unjoined.new(call_uri: call.id)
632
+ other_mock_call << mock_end
633
+
634
+ latch.wait(2).should be_true
635
+
636
+ t.join
637
+ status = t.value
638
+ status.should be_a Dial::DialStatus
639
+ status.should have(2).calls
640
+ status.calls.each { |c| c.should be_a OutboundCall }
641
+ status.result.should be == :answer
642
+ end
643
+ end
644
+ end
645
+ end
646
+ end
647
+
648
+ describe "#dial_and_confirm" do
649
+ it "should dial the call to the correct endpoint and return a dial status object" do
650
+ OutboundCall.should_receive(:new).and_return other_mock_call
651
+ other_mock_call.should_receive(:dial).with(to, :from => 'foo').once
652
+ dial_thread = Thread.new do
653
+ status = subject.dial_and_confirm(to, :from => 'foo')
654
+
655
+ status.should be_a Dial::DialStatus
656
+ joined_status = status.joins[status.calls.first]
657
+ joined_status.duration.should == 0.0
658
+ joined_status.result.should == :no_answer
659
+ end
660
+ sleep 0.1
661
+ other_mock_call << mock_end
662
+ dial_thread.join.should be_true
663
+ end
664
+
665
+ it "should default the caller ID to that of the original call" do
666
+ call.stub :from => 'sip:foo@bar.com'
667
+ OutboundCall.should_receive(:new).and_return other_mock_call
668
+ other_mock_call.should_receive(:dial).with(to, :from => 'sip:foo@bar.com').once
669
+ dial_thread = Thread.new do
670
+ subject.dial_and_confirm to
671
+ end
672
+ sleep 0.1
673
+ other_mock_call << mock_end
674
+ dial_thread.join.should be_true
675
+ end
676
+
677
+ let(:options) { { :foo => :bar } }
678
+
679
+ def dial_in_thread
680
+ Thread.new do
681
+ status = subject.dial_and_confirm to, options
682
+ latch.countdown!
683
+ status
684
+ end
685
+ end
686
+
687
+ describe "without a block" do
688
+ before do
689
+ other_mock_call.should_receive(:dial).once.with(to, options)
690
+ OutboundCall.should_receive(:new).and_return other_mock_call
691
+ end
692
+
693
+ it "blocks the original controller until the new call ends" do
694
+ dial_in_thread
695
+
696
+ latch.wait(1).should be_false
697
+
698
+ other_mock_call << mock_end
699
+
700
+ latch.wait(1).should be_true
701
+ end
702
+
703
+ it "unblocks the original controller if the original call ends" do
704
+ other_mock_call.should_receive(:hangup).once
705
+ dial_in_thread
706
+
707
+ latch.wait(1).should be_false
708
+
709
+ call << mock_end
710
+
711
+ latch.wait(1).should be_true
712
+ end
713
+
714
+ it "joins the new call to the existing one on answer" do
715
+ call.should_receive(:answer).once
716
+ other_mock_call.should_receive(:join).once.with(call)
717
+
718
+ dial_in_thread
719
+
720
+ latch.wait(1).should be_false
721
+
722
+ other_mock_call << mock_answered
723
+ other_mock_call << mock_end
724
+
725
+ latch.wait(1).should be_true
726
+ end
727
+
728
+ it "hangs up the new call when the dial unblocks" do
729
+ other_mock_call.should_receive(:hangup).once
730
+ call.should_receive(:answer).once
731
+ other_mock_call.should_receive(:join).once.with(call)
732
+
733
+ dial_in_thread
734
+
735
+ latch.wait(1).should be_false
736
+
737
+ other_mock_call << mock_answered
738
+ call << mock_end
739
+
740
+ latch.wait(1).should be_true
741
+ end
742
+
743
+ context "when the call is rejected" do
744
+ it "has an overall dial status of :no_answer" do
745
+ t = dial_in_thread
746
+
747
+ sleep 0.5
748
+
749
+ other_mock_call << mock_end(:reject)
750
+
751
+ latch.wait(2).should be_true
752
+
753
+ t.join
754
+ status = t.value
755
+ status.result.should be == :no_answer
756
+ end
757
+ end
758
+
759
+ context "when the call ends with an error" do
760
+ it "has an overall dial status of :error" do
761
+ t = dial_in_thread
762
+
763
+ sleep 0.5
764
+
765
+ other_mock_call << mock_end(:error)
766
+
767
+ latch.wait(2).should be_true
768
+
769
+ t.join
770
+ status = t.value
771
+ status.result.should be == :error
772
+
773
+ joined_status = status.joins[status.calls.first]
774
+ joined_status.duration.should == 0.0
775
+ joined_status.result.should == :error
776
+ end
777
+ end
778
+
779
+ context "when the call is answered and joined" do
780
+ it "has an overall dial status of :answer" do
781
+ call.should_receive(:answer).once
782
+ other_mock_call.should_receive(:join).once.with(call)
783
+
784
+ t = dial_in_thread
785
+
786
+ sleep 0.5
787
+
788
+ other_mock_call << mock_answered
789
+ other_mock_call << mock_end
790
+
791
+ latch.wait(1).should be_true
792
+
793
+ t.join
794
+ status = t.value
795
+ status.result.should be == :answer
796
+ status.joined_call.should eq(other_mock_call)
797
+
798
+ joined_status = status.joins[status.calls.first]
799
+ joined_status.result.should == :joined
800
+ end
801
+
802
+ it "records the duration of the join" do
803
+ call.should_receive(:answer).once
804
+ other_mock_call.should_receive(:join).once.with(call)
805
+ other_mock_call.stub hangup: true
806
+
807
+ t = dial_in_thread
808
+
809
+ sleep 0.5
810
+
811
+ base_time = Time.local(2008, 9, 1, 12, 0, 0)
812
+ Timecop.freeze base_time
813
+
814
+ other_mock_call << mock_answered
815
+
816
+ base_time = Time.local(2008, 9, 1, 12, 0, 37)
817
+ Timecop.freeze base_time
818
+ other_mock_call << Punchblock::Event::Unjoined.new(call_uri: call.id)
819
+ other_mock_call << mock_end
820
+
821
+ latch.wait(1).should be_true
822
+
823
+ t.join
824
+ status = t.value
825
+ status.result.should be == :answer
826
+ status.joined_call.should eq(other_mock_call)
827
+ joined_status = status.joins[status.calls.first]
828
+ joined_status.duration.should == 37.0
535
829
  end
536
830
  end
537
831
  end
538
- end#describe #dial
832
+
833
+ describe "when the caller has already hung up" do
834
+ before do
835
+ call << mock_end
836
+ end
837
+
838
+ it "should raise Call::Hangup" do
839
+ expect { subject.dial_and_confirm to, options }.to raise_error(Call::Hangup)
840
+ end
841
+
842
+ it "should not make any outbound calls" do
843
+ OutboundCall.should_receive(:new).never
844
+ expect { subject.dial_and_confirm to, options }.to raise_error
845
+ end
846
+ end
847
+
848
+ describe "with multiple third parties specified" do
849
+ let(:options) { {} }
850
+ let(:other_options) { options }
851
+ let(:second_other_options) { options }
852
+
853
+ before do
854
+ OutboundCall.should_receive(:new).and_return other_mock_call, second_other_mock_call
855
+
856
+ other_mock_call.should_receive(:dial).once.with(to, other_options)
857
+
858
+ second_other_mock_call.should_receive(:dial).once.with(second_to, second_other_options)
859
+ second_other_mock_call.should_receive(:join).never
860
+ end
861
+
862
+ def dial_in_thread
863
+ Thread.new do
864
+ status = subject.dial_and_confirm [to, second_to], options
865
+ latch.countdown!
866
+ status
867
+ end
868
+ end
869
+
870
+ it "dials all parties and joins the first one to answer, hanging up the rest" do
871
+ call.should_receive(:answer).once
872
+ other_mock_call.should_receive(:join).once.with(call)
873
+ second_other_mock_call.should_receive(:hangup).once.and_return do
874
+ second_other_mock_call << mock_end
875
+ end
876
+
877
+ t = dial_in_thread
878
+
879
+ latch.wait(1).should be_false
880
+
881
+ other_mock_call << mock_answered
882
+ other_mock_call << mock_end
883
+
884
+ latch.wait(2).should be_true
885
+
886
+ t.join
887
+ status = t.value
888
+ status.should be_a Dial::DialStatus
889
+ status.should have(2).calls
890
+ status.calls.each { |c| c.should be_a OutboundCall }
891
+ end
892
+
893
+ it "unblocks when the joined call unjoins, allowing it to proceed further" do
894
+ call.should_receive(:answer).once
895
+ other_mock_call.should_receive(:join).once.with(call)
896
+ other_mock_call.should_receive(:hangup).once
897
+ second_other_mock_call.should_receive(:hangup).once.and_return do
898
+ second_other_mock_call << mock_end
899
+ end
900
+
901
+ t = dial_in_thread
902
+
903
+ latch.wait(1).should be_false
904
+
905
+ other_mock_call << mock_answered
906
+ other_mock_call << Punchblock::Event::Unjoined.new(call_uri: call.id)
907
+
908
+ latch.wait(2).should be_true
909
+
910
+ t.join
911
+ status = t.value
912
+ status.should be_a Dial::DialStatus
913
+ status.should have(2).calls
914
+ status.calls.each { |c| c.should be_a OutboundCall }
915
+ end
916
+
917
+ describe "with options overrides" do
918
+ let(:options) do
919
+ {
920
+ :from => 'foo',
921
+ :timeout => 3000,
922
+ :headers => {
923
+ :x_foo => 'bar'
924
+ }
925
+ }
926
+ end
927
+
928
+ let(:dial_other_options) do
929
+ {
930
+ :foo => 'bar',
931
+ :headers => {
932
+ :x_foo => 'buzz'
933
+ }
934
+ }
935
+ end
936
+
937
+ let(:other_options) do
938
+ {
939
+ :from => 'foo',
940
+ :timeout => 3000,
941
+ :foo => 'bar',
942
+ :headers => {
943
+ :x_foo => 'buzz'
944
+ }
945
+
946
+ }
947
+ end
948
+
949
+ let(:dial_second_other_options) do
950
+ {
951
+ :timeout => 5000,
952
+ :headers => {
953
+ :x_bar => 'barbuzz'
954
+ }
955
+ }
956
+ end
957
+
958
+ let(:second_other_options) do
959
+ {
960
+ :from => 'foo',
961
+ :timeout => 5000,
962
+ :headers => {
963
+ :x_foo => 'bar',
964
+ :x_bar => 'barbuzz'
965
+ }
966
+ }
967
+ end
968
+
969
+ it "with multiple destinations as an hash, with overrides for each, and an options hash, it dials each call with specified options" do
970
+ t = Thread.new do
971
+ subject.dial_and_confirm({
972
+ to => dial_other_options,
973
+ second_to => dial_second_other_options
974
+ }, options)
975
+ latch.countdown!
976
+ end
977
+
978
+ latch.wait(1).should be_false
979
+ other_mock_call << mock_end
980
+ latch.wait(1).should be_false
981
+ second_other_mock_call << mock_end
982
+ latch.wait(2).should be_true
983
+ t.join
984
+ end
985
+ end
986
+
987
+ context "when all calls are rejected" do
988
+ it "has an overall dial status of :no_answer" do
989
+ t = dial_in_thread
990
+
991
+ sleep 0.5
992
+
993
+ other_mock_call << mock_end(:reject)
994
+ second_other_mock_call << mock_end(:reject)
995
+
996
+ latch.wait(2).should be_true
997
+
998
+ t.join
999
+ status = t.value
1000
+ status.result.should be == :no_answer
1001
+ end
1002
+ end
1003
+
1004
+ context "when a call is answered and joined, and the other ends with an error" do
1005
+ it "has an overall dial status of :answer" do
1006
+ call.should_receive(:answer).once
1007
+ other_mock_call.should_receive(:join).once.with(call)
1008
+ second_other_mock_call.should_receive(:hangup).once.and_return do
1009
+ second_other_mock_call << mock_end(:error)
1010
+ end
1011
+
1012
+ t = dial_in_thread
1013
+
1014
+ sleep 0.5
1015
+
1016
+ other_mock_call << mock_answered
1017
+ other_mock_call << mock_end
1018
+
1019
+ latch.wait(1).should be_true
1020
+
1021
+ t.join
1022
+ status = t.value
1023
+ status.result.should be == :answer
1024
+ end
1025
+ end
1026
+ end
1027
+
1028
+ describe "with a timeout specified" do
1029
+ let(:timeout) { 3 }
1030
+
1031
+ it "should abort the dial after the specified timeout" do
1032
+ other_mock_call.should_receive(:dial).once
1033
+ other_mock_call.should_receive(:hangup).once
1034
+ OutboundCall.should_receive(:new).and_return other_mock_call
1035
+
1036
+ time = Time.now
1037
+
1038
+ t = Thread.new do
1039
+ status = subject.dial_and_confirm to, :timeout => timeout
1040
+ latch.countdown!
1041
+ status
1042
+ end
1043
+
1044
+ latch.wait
1045
+ time = Time.now - time
1046
+ time.round.should be == timeout
1047
+ t.join
1048
+ status = t.value
1049
+ status.result.should be == :timeout
1050
+ end
1051
+
1052
+ describe "if someone answers before the timeout elapses" do
1053
+ it "should not abort until the far end hangs up" do
1054
+ other_mock_call.should_receive(:dial).once.with(to, hash_including(:timeout => timeout))
1055
+ call.should_receive(:answer).once
1056
+ other_mock_call.should_receive(:join).once.with(call)
1057
+ OutboundCall.should_receive(:new).and_return other_mock_call
1058
+
1059
+ time = Time.now
1060
+
1061
+ t = Thread.new do
1062
+ status = subject.dial_and_confirm to, :timeout => timeout
1063
+ latch.countdown!
1064
+ status
1065
+ end
1066
+
1067
+ latch.wait(2).should be_false
1068
+
1069
+ other_mock_call << mock_answered
1070
+
1071
+ latch.wait(2).should be_false
1072
+
1073
+ other_mock_call << mock_end
1074
+
1075
+ latch.wait(0.1).should be_true
1076
+ time = Time.now - time
1077
+ time.to_i.should be > timeout
1078
+ t.join
1079
+ status = t.value
1080
+ status.result.should be == :answer
1081
+ end
1082
+ end
1083
+ end
1084
+
1085
+ describe "with a confirmation controller" do
1086
+ let(:confirmation_controller) do
1087
+ latch = confirmation_latch
1088
+ Class.new(Adhearsion::CallController) do
1089
+ @@confirmation_latch = latch
1090
+
1091
+ def run
1092
+ # Copy metadata onto call variables so we can assert it later. Ugly hack
1093
+ metadata.each_pair do |key, value|
1094
+ call[key] = value
1095
+ end
1096
+ @@confirmation_latch.countdown!
1097
+ if delay = call['confirmation_delay']
1098
+ sleep delay
1099
+ end
1100
+ call['confirm'] || hangup
1101
+ end
1102
+ end
1103
+ end
1104
+
1105
+ let(:confirmation_latch) { CountDownLatch.new 1 }
1106
+
1107
+ let(:options) { {:confirm => confirmation_controller} }
1108
+
1109
+ context "with confirmation controller metadata specified" do
1110
+ let(:options) { {:confirm => confirmation_controller, :confirm_metadata => {:foo => 'bar'}} }
1111
+
1112
+ before do
1113
+ other_mock_call.should_receive(:dial).once
1114
+ OutboundCall.should_receive(:new).and_return other_mock_call
1115
+ end
1116
+
1117
+ it "should set the metadata on the controller" do
1118
+ other_mock_call.should_receive(:hangup).once.and_return do
1119
+ other_mock_call << mock_end
1120
+ end
1121
+ other_mock_call['confirm'] = false
1122
+
1123
+ dial_in_thread
1124
+
1125
+ latch.wait(0.1).should be_false
1126
+
1127
+ other_mock_call << mock_answered
1128
+
1129
+ confirmation_latch.wait(1).should be_true
1130
+ latch.wait(2).should be_true
1131
+
1132
+ other_mock_call[:foo].should == 'bar'
1133
+ end
1134
+ end
1135
+
1136
+ context "when an outbound call is answered" do
1137
+ before do
1138
+ other_mock_call.should_receive(:dial).once
1139
+ OutboundCall.should_receive(:new).and_return other_mock_call
1140
+ end
1141
+
1142
+ it "should execute the specified confirmation controller" do
1143
+ other_mock_call.should_receive(:hangup).once.and_return do
1144
+ other_mock_call << mock_end
1145
+ end
1146
+ other_mock_call['confirm'] = false
1147
+
1148
+ dial_in_thread
1149
+
1150
+ latch.wait(0.1).should be_false
1151
+
1152
+ other_mock_call << mock_answered
1153
+
1154
+ confirmation_latch.wait(1).should be_true
1155
+ latch.wait(2).should be_true
1156
+ end
1157
+
1158
+ it "should join the calls if the call is still active after execution of the call controller" do
1159
+ other_mock_call.should_receive(:hangup).once.and_return do
1160
+ other_mock_call << mock_end
1161
+ end
1162
+ other_mock_call['confirm'] = true
1163
+ call.should_receive(:answer).once
1164
+ other_mock_call.should_receive(:join).once.with(call)
1165
+
1166
+ t = dial_in_thread
1167
+
1168
+ latch.wait(1).should be_false
1169
+
1170
+ base_time = Time.local(2008, 9, 1, 12, 0, 0)
1171
+ Timecop.freeze base_time
1172
+
1173
+ other_mock_call << mock_answered
1174
+
1175
+ base_time = Time.local(2008, 9, 1, 12, 0, 42)
1176
+ Timecop.freeze base_time
1177
+ other_mock_call << Punchblock::Event::Unjoined.new(call_uri: call.id)
1178
+
1179
+ latch.wait(1).should be_true
1180
+
1181
+ t.join
1182
+ status = t.value
1183
+ status.result.should be == :answer
1184
+
1185
+ joined_status = status.joins[status.calls.first]
1186
+ joined_status.duration.should == 42.0
1187
+ joined_status.result.should == :joined
1188
+ end
1189
+
1190
+ it "should not join the calls if the call is not active after execution of the call controller" do
1191
+ other_mock_call.should_receive(:hangup).once.and_return do
1192
+ other_mock_call << mock_end
1193
+ end
1194
+ other_mock_call['confirm'] = false
1195
+ call.should_receive(:answer).never
1196
+ other_mock_call.should_receive(:join).never.with(call)
1197
+
1198
+ t = dial_in_thread
1199
+
1200
+ latch.wait(1).should be_false
1201
+
1202
+ other_mock_call << mock_answered
1203
+
1204
+ latch.wait(1).should be_true
1205
+
1206
+ t.join
1207
+ status = t.value
1208
+ status.result.should be == :unconfirmed
1209
+
1210
+ joined_status = status.joins[status.calls.first]
1211
+ joined_status.duration.should == 0.0
1212
+ joined_status.result.should == :unconfirmed
1213
+ end
1214
+ end
1215
+
1216
+ context "when multiple calls are made" do
1217
+ let(:confirmation_latch) { CountDownLatch.new 2 }
1218
+ let(:apology_controller) do
1219
+ Class.new(Adhearsion::CallController) do
1220
+ def run
1221
+ logger.info "Apologising..."
1222
+ call['apology_metadata'] = metadata
1223
+ call['apology_done'] = true
1224
+ end
1225
+ end
1226
+ end
1227
+ let(:options) { {confirm: confirmation_controller, confirm_metadata: {'foo' => 'bar'}, apology: apology_controller} }
1228
+
1229
+ before do
1230
+ OutboundCall.should_receive(:new).and_return other_mock_call, second_other_mock_call
1231
+ end
1232
+
1233
+ def dial_in_thread
1234
+ Thread.new do
1235
+ status = subject.dial_and_confirm [to, second_to], options
1236
+ latch.countdown!
1237
+ status
1238
+ end
1239
+ end
1240
+
1241
+ context "when two answer" do
1242
+ it "should execute the confirmation controller on both, joining the first call to confirm" do
1243
+ other_mock_call['confirm'] = true
1244
+ other_mock_call['confirmation_delay'] = 1
1245
+ second_other_mock_call['confirm'] = true
1246
+ second_other_mock_call['confirmation_delay'] = 1.3
1247
+
1248
+ call.should_receive(:answer).once
1249
+
1250
+ other_mock_call.should_receive(:dial).once.with(to, from: nil)
1251
+ other_mock_call.should_receive(:join).once.with(call)
1252
+ other_mock_call.should_receive(:hangup).once.and_return do
1253
+ other_mock_call.async.deliver_message mock_end
1254
+ end
1255
+
1256
+ second_other_mock_call.should_receive(:dial).once.with(second_to, from: nil)
1257
+ second_other_mock_call.should_receive(:join).never
1258
+ second_other_mock_call.should_receive(:hangup).once.and_return do
1259
+ second_other_mock_call.async.deliver_message mock_end
1260
+ end
1261
+
1262
+ t = dial_in_thread
1263
+
1264
+ latch.wait(1).should be_false
1265
+
1266
+ other_mock_call.async.deliver_message mock_answered
1267
+ second_other_mock_call.async.deliver_message mock_answered
1268
+ confirmation_latch.wait(1).should be_true
1269
+
1270
+ sleep 2
1271
+
1272
+ other_mock_call.async.deliver_message Punchblock::Event::Unjoined.new(call_uri: call.id)
1273
+
1274
+ latch.wait(2).should be_true
1275
+
1276
+ second_other_mock_call['apology_done'].should be_true
1277
+ second_other_mock_call['apology_metadata'].should == {'foo' => 'bar'}
1278
+
1279
+ t.join
1280
+ status = t.value
1281
+ status.should be_a Dial::DialStatus
1282
+ status.should have(2).calls
1283
+ status.calls.each { |c| c.should be_a OutboundCall }
1284
+ status.result.should be == :answer
1285
+ status.joins[other_mock_call].result.should == :joined
1286
+ status.joins[second_other_mock_call].result.should == :lost_confirmation
1287
+ end
1288
+ end
1289
+ end
1290
+ end
1291
+ end
539
1292
  end
540
1293
  end
541
1294
  end