adhearsion 2.0.0.alpha3 → 2.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
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