adhearsion 2.0.0.alpha3 → 2.0.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 (34) hide show
  1. data/CHANGELOG.md +13 -1
  2. data/README.markdown +1 -1
  3. data/features/cli_daemon.feature +2 -3
  4. data/features/step_definitions/cli_steps.rb +0 -1
  5. data/lib/adhearsion/call.rb +50 -32
  6. data/lib/adhearsion/call_controller.rb +53 -4
  7. data/lib/adhearsion/call_controller/dial.rb +26 -6
  8. data/lib/adhearsion/call_controller/input.rb +1 -1
  9. data/lib/adhearsion/call_controller/menu.rb +2 -2
  10. data/lib/adhearsion/call_controller/output.rb +11 -11
  11. data/lib/adhearsion/call_controller/utility.rb +3 -3
  12. data/lib/adhearsion/calls.rb +0 -4
  13. data/lib/adhearsion/cli_commands.rb +2 -3
  14. data/lib/adhearsion/console.rb +42 -14
  15. data/lib/adhearsion/foundation/thread_safety.rb +8 -2
  16. data/lib/adhearsion/generators/app/templates/Rakefile +1 -4
  17. data/lib/adhearsion/initializer.rb +12 -5
  18. data/lib/adhearsion/logging.rb +23 -4
  19. data/lib/adhearsion/process.rb +11 -2
  20. data/lib/adhearsion/punchblock_plugin.rb +8 -0
  21. data/lib/adhearsion/punchblock_plugin/initializer.rb +11 -4
  22. data/lib/adhearsion/version.rb +1 -1
  23. data/spec/adhearsion/call_controller/dial_spec.rb +123 -85
  24. data/spec/adhearsion/call_controller/output_spec.rb +10 -0
  25. data/spec/adhearsion/call_controller_spec.rb +59 -52
  26. data/spec/adhearsion/call_spec.rb +47 -3
  27. data/spec/adhearsion/console_spec.rb +10 -1
  28. data/spec/adhearsion/initializer_spec.rb +5 -5
  29. data/spec/adhearsion/logging_spec.rb +17 -1
  30. data/spec/adhearsion/process_spec.rb +18 -0
  31. data/spec/adhearsion/punchblock_plugin/initializer_spec.rb +20 -8
  32. data/spec/adhearsion/punchblock_plugin_spec.rb +42 -0
  33. data/spec/spec_helper.rb +5 -1
  34. metadata +64 -64
@@ -7,7 +7,7 @@ module Adhearsion
7
7
  # @param [Integer] Number of digits to accept in the grammar.
8
8
  # @return [RubySpeech::GRXML::Grammar] A grammar suitable for use in SSML prompts
9
9
  #
10
- def grammar_digits(digits = 1)
10
+ def grammar_digits(digits = 1) # :nodoc:
11
11
  RubySpeech::GRXML.draw :mode => 'dtmf', :root => 'inputdigits' do
12
12
  rule id: 'inputdigits', scope: 'public' do
13
13
  item repeat: digits.to_s do
@@ -24,7 +24,7 @@ module Adhearsion
24
24
  # @param [String] String representing the digits to accept
25
25
  # @return [RubySpeech::GRXML::Grammar] A grammar suitable for use in SSML prompts
26
26
  #
27
- def grammar_accept(digits = '0123456789#*')
27
+ def grammar_accept(digits = '0123456789#*') # :nodoc:
28
28
  allowed_digits = '0123456789#*'
29
29
  gram_digits = digits.chars.select { |x| allowed_digits.include? x }
30
30
 
@@ -43,7 +43,7 @@ module Adhearsion
43
43
  # @param [String] the tone string to be parsed
44
44
  # @return [String] the digit in case input was 0-9, * or # if star or pound respectively
45
45
  #
46
- def parse_single_dtmf(result)
46
+ def parse_single_dtmf(result) # :nodoc:
47
47
  return if result.nil?
48
48
  case tone = result.split('-')[1]
49
49
  when 'star'
@@ -28,10 +28,6 @@ module Adhearsion
28
28
  atomically { !calls.empty? }
29
29
  end
30
30
 
31
- def size
32
- atomically { calls.size }
33
- end
34
-
35
31
  def remove_inactive_call(call)
36
32
  atomically { calls.delete call.id }
37
33
  end
@@ -68,13 +68,12 @@ module Adhearsion
68
68
  def stop(path = nil)
69
69
  execute_from_app_dir! path
70
70
 
71
- path ||= '.'
72
-
73
71
  pid_file = if options[:pidfile]
74
72
  File.exists?(File.expand_path(options[:pidfile])) ?
75
73
  options[:pidfile] :
76
74
  File.join(path, options[:pidfile])
77
75
  else
76
+ path = Dir.pwd
78
77
  File.join path, Adhearsion::Initializer::DEFAULT_PID_FILE_NAME
79
78
  end
80
79
  pid_file = File.expand_path pid_file
@@ -118,7 +117,7 @@ module Adhearsion
118
117
  def execute_from_app_dir!(path)
119
118
  return if in_app? and running_script_ahn?
120
119
 
121
- path ||= '.' if in_app?
120
+ path ||= Dir.pwd if in_app?
122
121
 
123
122
  raise PathRequired, ARGV[0] if path.nil? or path.empty?
124
123
  raise PathInvalid, path unless ScriptAhnLoader.in_ahn_application?(path)
@@ -28,23 +28,15 @@ module Adhearsion
28
28
  # Start the Adhearsion console
29
29
  #
30
30
  def run
31
- Pry.prompt = [
32
- proc do |*args|
33
- obj, nest_level, pry_instance = args
34
- "AHN#{' ' * nest_level}> "
35
- end,
36
- proc do |*args|
37
- obj, nest_level, pry_instance = args
38
- "AHN#{' ' * nest_level}? "
39
- end
40
- ]
31
+ set_prompt
41
32
  Pry.config.command_prefix = "%"
42
33
  if libedit?
43
34
  logger.error "Cannot start. You are running Adhearsion on Ruby with libedit. You must use readline for the console to work."
44
35
  else
45
36
  logger.info "Starting up..."
46
37
  @pry_thread = Thread.current
47
- binding.pry
38
+ pry
39
+ logger.info "Console exiting"
48
40
  end
49
41
  end
50
42
 
@@ -82,7 +74,10 @@ module Adhearsion
82
74
  logger.error "An active call with that ID does not exist"
83
75
  end
84
76
  when nil
85
- if calls.size == 1
77
+ case calls.size
78
+ when 0
79
+ logger.warn "No calls exist for control"
80
+ when 1
86
81
  interact_with_call calls.values.first
87
82
  else
88
83
  puts "Please choose a call:"
@@ -97,6 +92,9 @@ module Adhearsion
97
92
  else
98
93
  raise ArgumentError
99
94
  end
95
+ ensure
96
+ set_prompt
97
+ pry
100
98
  end
101
99
 
102
100
  def libedit?
@@ -111,16 +109,46 @@ module Adhearsion
111
109
 
112
110
  private
113
111
 
112
+ def set_prompt
113
+ Pry.prompt = [
114
+ proc do |*args|
115
+ obj, nest_level, pry_instance = args
116
+ "AHN#{' ' * nest_level}> "
117
+ end,
118
+ proc do |*args|
119
+ obj, nest_level, pry_instance = args
120
+ "AHN#{' ' * nest_level}? "
121
+ end
122
+ ]
123
+ end
124
+
114
125
  def interact_with_call(call)
115
126
  Pry.prompt = [ proc { "AHN<#{call.id}> " },
116
127
  proc { "AHN<#{call.id}? " } ]
117
128
 
118
- CallController.exec InteractiveController.new(call)
129
+ begin
130
+ call.pause_controllers
131
+ CallController.exec InteractiveController.new(call)
132
+ ensure
133
+ logger.debug "Resuming call's controllers"
134
+ call.resume_controllers
135
+ end
119
136
  end
120
137
 
121
138
  class InteractiveController < CallController
122
139
  def run
123
- pry
140
+ logger.debug "Starting interactive controller."
141
+ begin
142
+ pry
143
+ rescue => e
144
+ logger.error e
145
+ end
146
+ logger.debug "Interactive controller finished."
147
+ end
148
+
149
+ def hangup(*args)
150
+ super
151
+ exit
124
152
  end
125
153
  end
126
154
  end
@@ -8,8 +8,6 @@ class Object
8
8
  end
9
9
 
10
10
  class ThreadSafeArray
11
- include Enumerable
12
-
13
11
  def initialize
14
12
  @mutex = Mutex.new
15
13
  @array = []
@@ -20,4 +18,12 @@ class ThreadSafeArray
20
18
  @array.send method, *args, &block
21
19
  end
22
20
  end
21
+
22
+ def inspect
23
+ @mutex.synchronize { @array.inspect }
24
+ end
25
+
26
+ def to_s
27
+ @mutex.synchronize { @array.to_s }
28
+ end
23
29
  end
@@ -1,8 +1,5 @@
1
1
  #!/usr/bin/env rake
2
2
 
3
- require 'rubygems'
4
- require 'bundler'
5
- Bundler.setup
6
- Bundler.require
3
+ require File.expand_path('../config/environment', __FILE__)
7
4
 
8
5
  require 'adhearsion/tasks'
@@ -111,14 +111,19 @@ module Adhearsion
111
111
  def catch_termination_signal
112
112
  %w'INT TERM'.each do |process_signal|
113
113
  trap process_signal do
114
- logger.info "Received #{process_signal} signal. Shutting down."
114
+ logger.info "Received SIG#{process_signal}. Shutting down."
115
115
  Adhearsion::Process.shutdown
116
116
  end
117
117
  end
118
118
 
119
- trap 'QUIT' do
120
- logger.info "Received QUIT signal. Hard shutting down."
121
- Adhearsion::Process.hard_shutdown
119
+ trap 'HUP' do
120
+ logger.debug "Received SIGHUP. Reopening logfiles."
121
+ Adhearsion::Logging.reopen_logs
122
+ end
123
+
124
+ trap 'ALRM' do
125
+ logger.debug "Received SIGALRM. Toggling trace logging."
126
+ Adhearsion::Logging.toggle_trace!
122
127
  end
123
128
 
124
129
  trap 'ABRT' do
@@ -163,7 +168,9 @@ module Adhearsion
163
168
  ::Logging.appenders.file(f,
164
169
  :layout => ::Logging.layouts.pattern(
165
170
  :pattern => Adhearsion::Logging.adhearsion_pattern
166
- )
171
+ ),
172
+ :auto_flushing => 2,
173
+ :flush_period => 2
167
174
  )
168
175
  else
169
176
  a
@@ -24,16 +24,33 @@ module Adhearsion
24
24
  '[%d] %-5l %c: %m\n'
25
25
  end
26
26
 
27
+ # Silence Adhearsion's logging, printing only FATAL messages
27
28
  def silence!
28
29
  self.logging_level = :fatal
29
30
  end
30
31
 
32
+ # Restore the default configured logging level
31
33
  def unsilence!
32
- self.logging_level = :info
34
+ self.logging_level = Adhearsion.config.platform.logging['level']
33
35
  end
34
36
 
35
- def reset
36
- ::Logging.reset
37
+ # Toggle between the configured log level and :trace
38
+ # Useful for debugging a live Adhearsion instance
39
+ def toggle_trace!
40
+ if level == ::Logging.level_num(Adhearsion.config.platform.logging['level'])
41
+ logger.warn "Turning TRACE logging ON."
42
+ self.level = :trace
43
+ else
44
+ logger.warn "Turning TRACE logging OFF."
45
+ self.level = Adhearsion.config.platform.logging['level']
46
+ end
47
+ end
48
+
49
+ # Close logfiles and reopen them. Useful for log rotation.
50
+ def reopen_logs
51
+ logger.info "Closing logfiles."
52
+ ::Logging.reopen
53
+ logger.info "Logfiles reopened."
37
54
  end
38
55
 
39
56
  def init
@@ -58,7 +75,9 @@ module Adhearsion
58
75
  :layout => ::Logging.layouts.pattern(
59
76
  :pattern => adhearsion_pattern,
60
77
  :color_scheme => 'bright'
61
- )
78
+ ),
79
+ :auto_flushing => 2,
80
+ :flush_period => 2
62
81
  )]
63
82
  end
64
83
 
@@ -1,5 +1,6 @@
1
1
  require 'state_machine'
2
2
  require 'singleton'
3
+ require 'socket'
3
4
 
4
5
  module Adhearsion
5
6
  class Process
@@ -9,7 +10,7 @@ module Adhearsion
9
10
  before_transition :log_state_change
10
11
  after_transition :on => :shutdown, :do => :request_stop
11
12
  after_transition any => :stopped, :do => :final_shutdown
12
- after_transition :on => :force_stop, :do => :die_now!
13
+ before_transition any => :force_stopped, :do => :die_now!
13
14
 
14
15
  event :booted do
15
16
  transition :booting => :running
@@ -27,8 +28,12 @@ module Adhearsion
27
28
  # This corresponds to the admin pressing CTRL+C three times.
28
29
  transition :rejecting => :stopped
29
30
 
31
+ # On the fourth shutdown request, we are probably hung.
32
+ # Attempt no more graceful shutdown and exit as quickly as possible.
33
+ transition :stopped => :force_stopped
34
+
30
35
  # If we are still booting, transition directly to stopped
31
- transition :booting => :force_stop
36
+ transition :booting => :force_stopped
32
37
  end
33
38
 
34
39
  event :hard_shutdown do
@@ -90,6 +95,10 @@ module Adhearsion
90
95
  ::Process.exit 1
91
96
  end
92
97
 
98
+ def fqdn
99
+ Socket.gethostbyname(Socket.gethostname).first
100
+ end
101
+
93
102
  def self.method_missing(method_name, *args, &block)
94
103
  instance.send method_name, *args, &block
95
104
  end
@@ -20,6 +20,7 @@ module Adhearsion
20
20
  connection_timeout 60 , :transform => Proc.new { |v| PunchblockPlugin.validate_number v }, :desc => "The amount of time to wait for a connection"
21
21
  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"
22
22
  reconnect_timer 5 , :transform => Proc.new { |v| PunchblockPlugin.validate_number v }, :desc => "Delay between connection attempts"
23
+ media_engine nil , :transform => Proc.new { |v| v.to_sym }, :desc => "The media engine to use. Defaults to platform default."
23
24
  end
24
25
 
25
26
  init :punchblock do
@@ -38,6 +39,13 @@ module Adhearsion
38
39
  return 1.0/0.0 if ["Infinity", 1.0/0.0].include? value
39
40
  value.to_i
40
41
  end
42
+
43
+ def execute_component(command, timeout = 60)
44
+ client.execute_command command
45
+ response = command.response timeout
46
+ raise response if response.is_a? Exception
47
+ command
48
+ end
41
49
  end
42
50
  end
43
51
  end
@@ -10,19 +10,23 @@ module Adhearsion
10
10
  self.config = Adhearsion.config[:punchblock]
11
11
  connection_class = case (self.config.platform || :xmpp)
12
12
  when :xmpp
13
+ username = [self.config.username, resource].join('/')
13
14
  ::Punchblock::Connection::XMPP
14
15
  when :asterisk
16
+ username = self.config.username
15
17
  ::Punchblock::Connection::Asterisk
16
18
  end
19
+
17
20
  connection_options = {
18
- :username => self.config.username,
21
+ :username => username,
19
22
  :password => self.config.password,
20
23
  :connection_timeout => self.config.connection_timeout,
21
24
  :host => self.config.host,
22
25
  :port => self.config.port,
23
26
  :root_domain => self.config.root_domain,
24
27
  :calls_domain => self.config.calls_domain,
25
- :mixers_domain => self.config.mixers_domain
28
+ :mixers_domain => self.config.mixers_domain,
29
+ :media_engine => self.config.media_engine
26
30
  }
27
31
 
28
32
  self.connection = connection_class.new connection_options
@@ -130,14 +134,17 @@ module Adhearsion
130
134
  end
131
135
  end
132
136
 
133
- def dispatch_call_event(event, latch = nil)
137
+ def dispatch_call_event(event)
134
138
  if call = Adhearsion.active_calls.find(event.call_id)
135
139
  call.deliver_message! event
136
- latch.countdown! if latch
137
140
  else
138
141
  logger.error "Event received for inactive call #{event.call_id}: #{event.inspect}"
139
142
  end
140
143
  end
144
+
145
+ def resource
146
+ [Adhearsion::Process.fqdn, ::Process.pid].join '-'
147
+ end
141
148
  end
142
149
  end # Punchblock
143
150
  end # Plugin
@@ -1,5 +1,5 @@
1
1
  module Adhearsion #:nodoc:
2
- VERSION = '2.0.0.alpha3'
2
+ VERSION = '2.0.0.beta1'
3
3
 
4
4
  class PkgVersion
5
5
  include Comparable
@@ -19,12 +19,15 @@ module Adhearsion
19
19
  let(:latch) { CountDownLatch.new 1 }
20
20
 
21
21
  describe "#dial" do
22
- it "should dial the call to the correct endpoint and return it" do
22
+ before do
23
23
  other_mock_call
24
+ end
25
+
26
+ it "should dial the call to the correct endpoint and return a dial status object" do
24
27
  flexmock(OutboundCall).should_receive(:new).and_return other_mock_call
25
28
  flexmock(other_mock_call).should_receive(:dial).with(to, :from => 'foo').once
26
29
  dial_thread = Thread.new do
27
- subject.dial(to, :from => 'foo').should be_a OutboundCall
30
+ subject.dial(to, :from => 'foo').should be_a Dial::DialStatus
28
31
  end
29
32
  sleep 0.1
30
33
  other_mock_call << mock_end
@@ -32,7 +35,6 @@ module Adhearsion
32
35
  end
33
36
 
34
37
  it "should default the caller ID to that of the original call" do
35
- other_mock_call
36
38
  flexmock call, :from => 'sip:foo@bar.com'
37
39
  flexmock(OutboundCall).should_receive(:new).and_return other_mock_call
38
40
  flexmock(other_mock_call).should_receive(:dial).with(to, :from => 'sip:foo@bar.com').once
@@ -45,19 +47,22 @@ module Adhearsion
45
47
  end
46
48
 
47
49
  describe "without a block" do
48
- it "blocks the original controller until the new call ends" do
49
- other_mock_call
50
-
50
+ before do
51
51
  flexmock(other_mock_call).should_receive(:dial).once
52
52
  flexmock(other_mock_call).should_receive(:hangup).once
53
53
  flexmock(OutboundCall).should_receive(:new).and_return other_mock_call
54
+ end
54
55
 
55
- latch = CountDownLatch.new 1
56
-
56
+ def dial_in_thread
57
57
  Thread.new do
58
- subject.dial to
58
+ status = subject.dial to
59
59
  latch.countdown!
60
+ status
60
61
  end
62
+ end
63
+
64
+ it "blocks the original controller until the new call ends" do
65
+ dial_in_thread
61
66
 
62
67
  latch.wait(1).should be_false
63
68
 
@@ -67,18 +72,7 @@ module Adhearsion
67
72
  end
68
73
 
69
74
  it "unblocks the original controller if the original call ends" do
70
- other_mock_call
71
-
72
- flexmock(other_mock_call).should_receive(:dial).once
73
- flexmock(other_mock_call).should_receive(:hangup).once
74
- flexmock(OutboundCall).should_receive(:new).and_return other_mock_call
75
-
76
- latch = CountDownLatch.new 1
77
-
78
- Thread.new do
79
- subject.dial to
80
- latch.countdown!
81
- end
75
+ dial_in_thread
82
76
 
83
77
  latch.wait(1).should be_false
84
78
 
@@ -88,19 +82,9 @@ module Adhearsion
88
82
  end
89
83
 
90
84
  it "joins the new call to the existing one on answer" do
91
- other_mock_call
92
-
93
- flexmock(other_mock_call).should_receive(:dial).once
94
85
  flexmock(other_mock_call).should_receive(:join).once.with(call)
95
- flexmock(other_mock_call).should_receive(:hangup).once
96
- flexmock(OutboundCall).should_receive(:new).and_return other_mock_call
97
86
 
98
- latch = CountDownLatch.new 1
99
-
100
- Thread.new do
101
- subject.dial to
102
- latch.countdown!
103
- end
87
+ dial_in_thread
104
88
 
105
89
  latch.wait(1).should be_false
106
90
 
@@ -111,19 +95,9 @@ module Adhearsion
111
95
  end
112
96
 
113
97
  it "hangs up the new call when the dial unblocks" do
114
- other_mock_call
115
-
116
- flexmock(other_mock_call).should_receive(:dial).once
117
98
  flexmock(other_mock_call).should_receive(:join).once.with(call)
118
- flexmock(other_mock_call).should_receive(:hangup).once
119
- flexmock(OutboundCall).should_receive(:new).and_return other_mock_call
120
99
 
121
- latch = CountDownLatch.new 1
122
-
123
- Thread.new do
124
- subject.dial to
125
- latch.countdown!
126
- end
100
+ dial_in_thread
127
101
 
128
102
  latch.wait(1).should be_false
129
103
 
@@ -132,29 +106,70 @@ module Adhearsion
132
106
 
133
107
  latch.wait(1).should be_true
134
108
  end
109
+
110
+ context "when the call is rejected" do
111
+ it "has an overall dial status of :no_answer" do
112
+ t = dial_in_thread
113
+
114
+ sleep 0.5
115
+
116
+ other_mock_call << mock_end
117
+
118
+ latch.wait(2).should be_true
119
+
120
+ t.join
121
+ status = t.value
122
+ status.result.should == :no_answer
123
+ end
124
+ end
125
+
126
+ context "when the call is answered and joined" do
127
+ it "has an overall dial status of :answer" do
128
+ flexmock(other_mock_call).should_receive(:join).once.with(call)
129
+
130
+ t = dial_in_thread
131
+
132
+ sleep 0.5
133
+
134
+ other_mock_call << mock_answered
135
+ other_mock_call << mock_end
136
+
137
+ latch.wait(1).should be_true
138
+
139
+ t.join
140
+ status = t.value
141
+ status.result.should == :answer
142
+ end
143
+ end
135
144
  end
136
145
 
137
146
  describe "with multiple third parties specified" do
138
- it "dials all parties and joins the first one to answer, hanging up the rest" do
139
- other_mock_call
147
+ before do
140
148
  second_other_mock_call
141
149
 
150
+ flexmock(OutboundCall).should_receive(:new).and_return other_mock_call, second_other_mock_call
151
+
142
152
  flexmock(other_mock_call).should_receive(:dial).once
143
- flexmock(other_mock_call).should_receive(:join).once.with(call)
144
153
  flexmock(other_mock_call).should_receive(:hangup).once
145
154
 
146
155
  flexmock(second_other_mock_call).should_receive(:dial).once
147
156
  flexmock(second_other_mock_call).should_receive(:join).never
148
- flexmock(second_other_mock_call).should_receive(:hangup).twice
149
-
150
- flexmock(OutboundCall).should_receive(:new).and_return other_mock_call, second_other_mock_call
151
- latch = CountDownLatch.new 1
157
+ flexmock(second_other_mock_call).should_receive(:hangup).once
158
+ end
152
159
 
153
- t = Thread.new do
154
- calls = subject.dial [to, second_to]
160
+ def dial_in_thread
161
+ Thread.new do
162
+ status = subject.dial [to, second_to]
155
163
  latch.countdown!
156
- calls
164
+ status
157
165
  end
166
+ end
167
+
168
+ it "dials all parties and joins the first one to answer, hanging up the rest" do
169
+ flexmock(other_mock_call).should_receive(:join).once.with(call)
170
+ flexmock(second_other_mock_call).should_receive(:hangup).once
171
+
172
+ t = dial_in_thread
158
173
 
159
174
  latch.wait(1).should be_false
160
175
 
@@ -168,37 +183,22 @@ module Adhearsion
168
183
  latch.wait(2).should be_true
169
184
 
170
185
  t.join
171
- calls = t.value
172
- calls.should have(2).calls
173
- calls.each { |c| c.should be_a OutboundCall }
186
+ status = t.value
187
+ status.should be_a Dial::DialStatus
188
+ status.should have(2).calls
189
+ status.calls.each { |c| c.should be_a OutboundCall }
174
190
  end
175
191
 
176
192
  it "unblocks when the joined call unjoins, allowing it to proceed further" do
177
- other_mock_call
178
- second_other_mock_call
179
-
180
- flexmock(other_mock_call).should_receive(:dial).once
181
193
  flexmock(other_mock_call).should_receive(:join).once.with(call)
182
- flexmock(other_mock_call).should_receive(:hangup).once
194
+ flexmock(second_other_mock_call).should_receive(:hangup).once
183
195
 
184
- flexmock(second_other_mock_call).should_receive(:dial).once
185
- flexmock(second_other_mock_call).should_receive(:join).never
186
- flexmock(second_other_mock_call).should_receive(:hangup).twice
187
-
188
- flexmock(OutboundCall).should_receive(:new).and_return other_mock_call, second_other_mock_call
189
- latch = CountDownLatch.new 1
190
-
191
- t = Thread.new do
192
- calls = subject.dial [to, second_to]
193
- latch.countdown!
194
- calls
195
- end
196
+ t = dial_in_thread
196
197
 
197
198
  latch.wait(1).should be_false
198
199
 
199
200
  other_mock_call << mock_answered
200
201
  other_mock_call << Punchblock::Event::Unjoined.new(:other_call_id => call.id)
201
- other_mock_call << mock_end
202
202
 
203
203
  latch.wait(1).should be_false
204
204
 
@@ -207,9 +207,49 @@ module Adhearsion
207
207
  latch.wait(2).should be_true
208
208
 
209
209
  t.join
210
- calls = t.value
211
- calls.should have(2).calls
212
- calls.each { |c| c.should be_a OutboundCall }
210
+ status = t.value
211
+ status.should be_a Dial::DialStatus
212
+ status.should have(2).calls
213
+ status.calls.each { |c| c.should be_a OutboundCall }
214
+ end
215
+
216
+ context "when all calls are rejected" do
217
+ it "has an overall dial status of :no_answer" do
218
+ t = dial_in_thread
219
+
220
+ sleep 0.5
221
+
222
+ other_mock_call << mock_end
223
+ second_other_mock_call << mock_end
224
+
225
+ latch.wait(2).should be_true
226
+
227
+ t.join
228
+ status = t.value
229
+ status.result.should == :no_answer
230
+ end
231
+ end
232
+
233
+ context "when a call is answered and joined" do
234
+ it "has an overall dial status of :answer" do
235
+ flexmock(other_mock_call).should_receive(:join).once.with(call)
236
+ flexmock(second_other_mock_call).should_receive(:hangup).once
237
+
238
+ t = dial_in_thread
239
+
240
+ sleep 0.5
241
+
242
+ other_mock_call << mock_answered
243
+ other_mock_call << mock_end
244
+
245
+ second_other_mock_call << mock_end
246
+
247
+ latch.wait(1).should be_true
248
+
249
+ t.join
250
+ status = t.value
251
+ status.result.should == :answer
252
+ end
213
253
  end
214
254
  end
215
255
 
@@ -217,26 +257,24 @@ module Adhearsion
217
257
  let(:timeout) { 3 }
218
258
 
219
259
  it "should abort the dial after the specified timeout" do
220
- other_mock_call
221
-
222
260
  flexmock(other_mock_call).should_receive(:dial).once
223
261
  flexmock(other_mock_call).should_receive(:hangup).once
224
262
  flexmock(OutboundCall).should_receive(:new).and_return other_mock_call
225
263
 
226
- latch = CountDownLatch.new 1
227
-
228
- value = nil
229
264
  time = Time.now
230
265
 
231
- Thread.new do
232
- value = subject.dial to, :timeout => timeout
266
+ t = Thread.new do
267
+ status = subject.dial to, :timeout => timeout
233
268
  latch.countdown!
269
+ status
234
270
  end
235
271
 
236
272
  latch.wait
237
273
  time = Time.now - time
238
274
  time.to_i.should == timeout
239
- value.should == false
275
+ t.join
276
+ status = t.value
277
+ status.result.should == :timeout
240
278
  end
241
279
  end
242
280