adhearsion 2.3.5 → 2.4.0.beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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