adhearsion 1.1.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. data/.gitignore +3 -0
  2. data/.rspec +3 -0
  3. data/CHANGELOG +12 -0
  4. data/README.markdown +1 -10
  5. data/Rakefile +6 -28
  6. data/adhearsion.gemspec +21 -38
  7. data/app_generators/ahn/ahn_generator.rb +3 -4
  8. data/app_generators/ahn/templates/config/environment.rb +4 -0
  9. data/app_generators/ahn/templates/config/startup.rb +11 -5
  10. data/app_generators/ahn/templates/script/ahn +8 -0
  11. data/lib/adhearsion/cli.rb +5 -298
  12. data/lib/adhearsion/commands.rb +308 -0
  13. data/lib/adhearsion/events_support.rb +0 -20
  14. data/lib/adhearsion/initializer/asterisk.rb +0 -1
  15. data/lib/adhearsion/initializer/configuration.rb +4 -1
  16. data/lib/adhearsion/logging.rb +18 -3
  17. data/lib/adhearsion/script_ahn_loader.rb +30 -0
  18. data/lib/adhearsion/tasks/testing.rb +39 -8
  19. data/lib/adhearsion/version.rb +2 -2
  20. data/lib/adhearsion/voip/asterisk/agi_server.rb +15 -8
  21. data/lib/adhearsion/voip/asterisk/commands.rb +340 -86
  22. data/lib/adhearsion/voip/asterisk/manager_interface.rb +0 -2
  23. data/lib/adhearsion/voip/call.rb +11 -3
  24. data/lib/adhearsion/voip/commands.rb +5 -1
  25. data/lib/adhearsion/voip/dial_plan.rb +4 -0
  26. data/lib/theatre/invocation.rb +3 -0
  27. data/spec/{ahn_command_spec.rb → adhearsion/cli_spec.rb} +11 -0
  28. data/spec/{component_manager_spec.rb → adhearsion/component_manager_spec.rb} +0 -0
  29. data/spec/{constants_spec.rb → adhearsion/constants_spec.rb} +0 -0
  30. data/spec/{drb_spec.rb → adhearsion/drb_spec.rb} +0 -0
  31. data/spec/{fixtures → adhearsion/fixtures}/dialplan.rb +0 -0
  32. data/spec/{foundation → adhearsion/foundation}/event_socket_spec.rb +0 -0
  33. data/spec/{host_definitions_spec.rb → adhearsion/host_definitions_spec.rb} +0 -0
  34. data/spec/{initializer → adhearsion/initializer}/configuration_spec.rb +21 -0
  35. data/spec/{initializer → adhearsion/initializer}/loading_spec.rb +0 -0
  36. data/spec/{initializer → adhearsion/initializer}/paths_spec.rb +0 -0
  37. data/spec/{initialization_spec.rb → adhearsion/initializer_spec.rb} +0 -0
  38. data/spec/{logging_spec.rb → adhearsion/logging_spec.rb} +6 -0
  39. data/spec/{relationship_properties_spec.rb → adhearsion/relationship_properties_spec.rb} +0 -0
  40. data/spec/{voip → adhearsion/voip}/asterisk/agi_server_spec.rb +0 -0
  41. data/spec/{voip → adhearsion/voip}/asterisk/ami/ami_spec.rb +1 -0
  42. data/spec/{voip → adhearsion/voip}/asterisk/ami/lexer/ami_fixtures.yml +0 -0
  43. data/spec/{voip → adhearsion/voip}/asterisk/ami/lexer/lexer_story +0 -0
  44. data/spec/{voip → adhearsion/voip}/asterisk/ami/lexer/lexer_story.rb +0 -0
  45. data/spec/{voip → adhearsion/voip}/asterisk/ami/lexer/story_helper.rb +0 -0
  46. data/spec/{voip → adhearsion/voip}/asterisk/commands_spec.rb +549 -47
  47. data/spec/{voip → adhearsion/voip}/asterisk/config_file_generators/agents_spec.rb +0 -0
  48. data/spec/{voip → adhearsion/voip}/asterisk/config_file_generators/queues_spec.rb +0 -0
  49. data/spec/{voip → adhearsion/voip}/asterisk/config_file_generators/voicemail_spec.rb +0 -0
  50. data/spec/{voip → adhearsion/voip}/asterisk/config_manager_spec.rb +0 -0
  51. data/spec/{voip → adhearsion/voip}/asterisk/menu_command/calculated_match_spec.rb +0 -0
  52. data/spec/{voip → adhearsion/voip}/asterisk/menu_command/matchers_spec.rb +0 -0
  53. data/spec/{voip → adhearsion/voip}/call_routing_spec.rb +0 -0
  54. data/spec/{voip → adhearsion/voip}/dialplan_manager_spec.rb +0 -0
  55. data/spec/{voip → adhearsion/voip}/dsl/dialing_dsl_spec.rb +1 -1
  56. data/spec/{voip → adhearsion/voip}/dsl/dispatcher_spec.rb +0 -0
  57. data/spec/{voip → adhearsion/voip}/dsl/dispatcher_spec_helper.rb +0 -0
  58. data/spec/{voip → adhearsion/voip}/dsl/parser_spec.rb +0 -0
  59. data/spec/{voip → adhearsion/voip}/freeswitch/basic_connection_manager_spec.rb +0 -0
  60. data/spec/{voip → adhearsion/voip}/freeswitch/inbound_connection_manager_spec.rb +0 -0
  61. data/spec/{voip → adhearsion/voip}/freeswitch/oes_server_spec.rb +0 -0
  62. data/spec/{voip → adhearsion/voip}/numerical_string_spec.rb +0 -0
  63. data/spec/{voip → adhearsion/voip}/phone_number_spec.rb +0 -0
  64. data/spec/spec_helper.rb +36 -89
  65. data/spec/support/initializer_stubs.rb +47 -0
  66. data/spec/support/the_following_code.rb +3 -0
  67. data/{theatre-spec → spec/theatre}/dsl_examples/simple_before_call.rb +0 -0
  68. data/{theatre-spec → spec/theatre}/dsl_spec.rb +26 -0
  69. data/{theatre-spec → spec/theatre}/invocation_spec.rb +6 -10
  70. data/{theatre-spec → spec/theatre}/namespace_spec.rb +0 -0
  71. data/{theatre-spec → spec/theatre}/spec_helper_spec.rb +0 -0
  72. data/{theatre-spec → spec/theatre}/theatre_class_spec.rb +2 -5
  73. metadata +254 -271
  74. data/app_generators/ahn/templates/components/disabled/sandbox/sandbox.rb +0 -104
  75. data/app_generators/ahn/templates/components/disabled/sandbox/sandbox.yml +0 -2
  76. data/lib/adhearsion/voip/asterisk/super_manager.rb +0 -19
  77. data/spec/silence.rb +0 -10
  78. data/spec/voip/asterisk/ami/old_tests.rb +0 -204
  79. data/spec/voip/asterisk/ami/super_manager/super_manager_story +0 -25
  80. data/spec/voip/asterisk/ami/super_manager/super_manager_story.rb +0 -15
  81. data/spec/voip/asterisk/ami/super_manager/super_manager_story_helper.rb +0 -5
  82. data/theatre-spec/dsl_examples/dynamic_stomp.rb +0 -7
  83. data/theatre-spec/spec_helper.rb +0 -37
@@ -25,8 +25,6 @@ module Adhearsion
25
25
  # ManagerInterfaceResponse, ManagerInterfaceError, etc.) are relatively user-friendly, they're designed to be a
26
26
  # building block on which to build higher-level abstractions of the Asterisk Manager Interface.
27
27
  #
28
- # For a higher-level abstraction of the Asterisk Manager Interface, see the SuperManager class.
29
- #
30
28
  class ManagerInterface
31
29
 
32
30
  CAUSAL_EVENT_NAMES = %w[queuestatus sippeers iaxpeers parkedcalls
@@ -55,7 +55,7 @@ module Adhearsion
55
55
  end
56
56
  end
57
57
 
58
- # Searches all active calls by their unique_identifier. See Call#unique_identifier.
58
+ # Searches all active calls by their unique_identifier. See Call#unique_identifier.
59
59
  # Is this actually by channel?
60
60
  def find(id)
61
61
  atomically do
@@ -90,6 +90,12 @@ module Adhearsion
90
90
  calls
91
91
  end
92
92
 
93
+ def method_missing(m, *args)
94
+ atomically do
95
+ calls.send(m.to_sym, *args)
96
+ end
97
+ end
98
+
93
99
  private
94
100
 
95
101
  def atomically(&block)
@@ -146,8 +152,6 @@ module Adhearsion
146
152
  raise Adhearsion::Call::CallMessageQueue::InboxClosedException, "The message queue for this call has aleady been disabled." if !@open
147
153
  super
148
154
  end
149
-
150
-
151
155
  end
152
156
 
153
157
 
@@ -294,6 +298,10 @@ module Adhearsion
294
298
  end
295
299
  end
296
300
 
301
+ def ahn_log(*args)
302
+ Adhearsion::Logging::DefaultAdhearsionLogger.send Adhearsion::Logging::AdhearsionLogger.sanitized_logger_name(unique_identifier), *args
303
+ end
304
+
297
305
  def define_variable_accessors(recipient=self)
298
306
  variables.each do |key, value|
299
307
  define_singleton_accessor_with_pair(key, value, recipient)
@@ -9,5 +9,9 @@ module Adhearsion
9
9
  class PlaybackError < StandardError
10
10
  # Represents failure to play audio, such as when the sound file cannot be found
11
11
  end
12
+
13
+ class RecordError < StandardError
14
+ # Represents failure to record such as when a file cannot be written.
15
+ end
12
16
  end
13
- end
17
+ end
@@ -93,6 +93,10 @@ module Adhearsion
93
93
  Components.component_manager.extend_object_with(self, :dialplan) if Components.component_manager
94
94
  end
95
95
 
96
+ def ahn_log(*args)
97
+ @call.ahn_log *args
98
+ end
99
+
96
100
  end
97
101
 
98
102
  class Manager
@@ -62,6 +62,9 @@ module Theatre
62
62
  @callback.call @payload
63
63
  end
64
64
  with_state_lock { @current_state = :success }
65
+ rescue => e
66
+ @error = e
67
+ with_state_lock { @current_state = :error }
65
68
  ensure
66
69
  @finished_time = Time.now.freeze
67
70
  end
@@ -66,6 +66,10 @@ describe "A simulated use of the 'ahn' command" do
66
66
  Adhearsion::CLI::AhnCommand.const_defined?('USAGE').should be true
67
67
  end
68
68
 
69
+ before do
70
+ flexmock Adhearsion::ScriptAhnLoader, :in_ahn_application? => true
71
+ end
72
+
69
73
  it "arguments to 'create' are executed properly" do
70
74
  some_path = "/path/somewhere"
71
75
  simulate_args "create", some_path
@@ -113,6 +117,13 @@ describe "A simulated use of the 'ahn' command" do
113
117
  Adhearsion::CLI::AhnCommand.parse_arguments(arguments).should == [:start, project_path, :daemon, pid_file_path]
114
118
  end
115
119
 
120
+ it 'should recognize start without daemon and with pid file properly' do
121
+ project_path = '/second/star/on/the/right'
122
+ pid_file_path = '/straight/on/til/morning'
123
+ arguments = ["start", project_path, "--pid-file=#{pid_file_path}"]
124
+ Adhearsion::CLI::AhnCommand.parse_arguments(arguments).should == [:start, project_path, :foreground, pid_file_path]
125
+ end
126
+
116
127
  it 'parse_arguments should recognize start without daemon properly' do
117
128
  path = '/path/to/somewhere'
118
129
  arguments = ['start', path]
@@ -87,6 +87,8 @@ describe 'Logging configuration' do
87
87
 
88
88
  after :each do
89
89
  Adhearsion::Logging.logging_level = :fatal
90
+ Adhearsion::Logging::AdhearsionLogger.outputters = [Log4r::Outputter.stdout]
91
+ Adhearsion::Logging::AdhearsionLogger.formatters = [Log4r::DefaultFormatter]
90
92
  end
91
93
 
92
94
  it 'the logging level should translate from symbols into Log4r constants' do
@@ -95,6 +97,25 @@ describe 'Logging configuration' do
95
97
  Adhearsion::Logging.logging_level.should be Log4r::WARN
96
98
  end
97
99
 
100
+ it 'outputters should be settable' do
101
+ Adhearsion::Logging::AdhearsionLogger.outputters.should == [Log4r::Outputter.stdout]
102
+ config.logging :outputters => Log4r::Outputter.stderr
103
+ Adhearsion::Logging::AdhearsionLogger.outputters.should == [Log4r::Outputter.stderr]
104
+ end
105
+
106
+ it 'formatters should be settable' do
107
+ Adhearsion::Logging::AdhearsionLogger.formatters.map(&:class).should == [Log4r::DefaultFormatter]
108
+ config.logging :formatters => Log4r::ObjectFormatter
109
+ Adhearsion::Logging::AdhearsionLogger.formatters.map(&:class).should == [Log4r::ObjectFormatter]
110
+ end
111
+
112
+ it 'a global formatter should be settable' do
113
+ Adhearsion::Logging::AdhearsionLogger.outputters << Log4r::Outputter.stdout
114
+ Adhearsion::Logging::AdhearsionLogger.formatters.map(&:class).should == [Log4r::DefaultFormatter, Log4r::DefaultFormatter]
115
+ config.logging :formatter => Log4r::ObjectFormatter
116
+ Adhearsion::Logging::AdhearsionLogger.formatters.map(&:class).should == [Log4r::ObjectFormatter, Log4r::ObjectFormatter]
117
+ end
118
+
98
119
  end
99
120
 
100
121
  describe "AMI configuration defaults" do
@@ -41,6 +41,12 @@ describe 'The ahn_log command' do
41
41
  ahn_log.qwerty.should be_a_kind_of Adhearsion::Logging::AdhearsionLogger
42
42
  end
43
43
 
44
+ it "handles crazy logger names" do
45
+ ahn_log.send :'locals@DEMO_ca.ll&', "hey"
46
+ Log4r::Logger['locals@DEMO_ca.ll&'].should_not be nil
47
+ ahn_log.send(:'localsdemo_call').should == Log4r::Logger['locals@DEMO_ca.ll&']
48
+ end
49
+
44
50
  end
45
51
 
46
52
  # Essential for running the tests
@@ -252,6 +252,7 @@ describe "ManagerInterface" do
252
252
  write_queue_mock = mocked_queue
253
253
 
254
254
  manager = new_manager_with_events
255
+ flexmock(manager).should_receive(:start_actions_writer_loop).and_return true
255
256
  manager.connect!
256
257
 
257
258
  write_queue_mock.actions.first.name.should == "login"
@@ -20,9 +20,19 @@ module DialplanCommandTestHelpers
20
20
  call.should_receive(:to_pbx).and_return(output)
21
21
  end
22
22
  end
23
+
24
+ test_case.after do
25
+ pbx_output_should_be_empty
26
+ end
23
27
  end
24
28
 
25
29
  class MockCall
30
+ attr_accessor :variables
31
+
32
+ def initialize
33
+ @variables = {}
34
+ end
35
+
26
36
  def with_command_lock
27
37
  yield
28
38
  end
@@ -30,6 +40,10 @@ module DialplanCommandTestHelpers
30
40
 
31
41
  class MockSocket
32
42
 
43
+ def empty?
44
+ messages.empty?
45
+ end
46
+
33
47
  def print(message)
34
48
  messages << message
35
49
  end
@@ -98,7 +112,7 @@ module DialplanCommandTestHelpers
98
112
  end
99
113
 
100
114
  def pbx_should_respond_with_value(value)
101
- pbx_should_respond_with "200 result=1 (#{value})"
115
+ pbx_should_respond_with pbx_value_response value
102
116
  end
103
117
 
104
118
  def pbx_should_respond_with_success(success_code = nil)
@@ -148,6 +162,14 @@ module DialplanCommandTestHelpers
148
162
  "200 result=#{code || default_code} endpos=#{endpos || default_code}\n"
149
163
  end
150
164
 
165
+ def pbx_value_response(value)
166
+ "200 result=1 (#{value})"
167
+ end
168
+
169
+ def pbx_result_response(value)
170
+ "200 result=#{value.ord}"
171
+ end
172
+
151
173
  def default_success_code
152
174
  '1'
153
175
  end
@@ -192,6 +214,10 @@ module DialplanCommandTestHelpers
192
214
  def pbx_was_asked_to_execute(application, *options)
193
215
  output_stream_matches(/exec saydigits "#{options.join('|')}"/i)
194
216
  end
217
+
218
+ def pbx_output_should_be_empty
219
+ output.messages.should be_empty, output.messages.inspect
220
+ end
195
221
  end
196
222
  include OutputStreamMatchers
197
223
 
@@ -248,7 +274,17 @@ describe 'hangup command' do
248
274
  end
249
275
  end
250
276
 
251
- describe 'interruptible_play command' do
277
+ describe "writing a command" do
278
+ include DialplanCommandTestHelpers
279
+
280
+ it "should strip out excess whitespace" do
281
+ pbx_should_respond_with_success
282
+ mock_call.should_receive(:write).with "EXEC Ringing"
283
+ mock_call.raw_response "EXEC \nRinging\n\n"
284
+ end
285
+ end
286
+
287
+ describe 'The #interruptible_play method' do
252
288
 
253
289
  include DialplanCommandTestHelpers
254
290
 
@@ -257,7 +293,7 @@ describe 'interruptible_play command' do
257
293
  file = "file_doesnt_matter"
258
294
  digits.each { |digit| pbx_should_respond_with_stream_file_success digit }
259
295
  digits.map { |digit| mock_call.interruptible_play file }.should == digits.map(&:chr)
260
- pbx_was_asked_to_stream file
296
+ digits.size.times { pbx_was_asked_to_stream file }
261
297
  end
262
298
 
263
299
  it "should return nil if no digit was pressed" do
@@ -297,10 +333,9 @@ describe 'interruptible_play command' do
297
333
  mock_call.interruptible_play(*play_files).should == '9'
298
334
  pbx_was_asked_to_stream played_files
299
335
  end
300
-
301
336
  end
302
337
 
303
- describe 'interruptible_play! command' do
338
+ describe 'The #interruptible_play! method' do
304
339
  include DialplanCommandTestHelpers
305
340
 
306
341
  it 'should return a string for the digit that was pressed' do
@@ -308,7 +343,7 @@ describe 'interruptible_play! command' do
308
343
  file = "file_doesnt_matter"
309
344
  digits.each { |digit| pbx_should_respond_with_stream_file_success digit }
310
345
  digits.map { |digit| mock_call.interruptible_play! file }.should == digits.map(&:chr)
311
- pbx_was_asked_to_stream file
346
+ digits.size.times { pbx_was_asked_to_stream file }
312
347
  end
313
348
 
314
349
  it "should return nil if no digit was pressed" do
@@ -372,7 +407,7 @@ describe 'interruptible_play! command' do
372
407
  end
373
408
  end
374
409
 
375
- describe 'wait_for_digit command' do
410
+ describe 'The #wait_for_digit method' do
376
411
 
377
412
  include DialplanCommandTestHelpers
378
413
 
@@ -380,16 +415,17 @@ describe 'wait_for_digit command' do
380
415
  digits = %w{0 1 # * 9}.map{|c| c.ord}
381
416
  digits.each { |digit| pbx_should_respond_with_success digit }
382
417
  digits.map { |digit| mock_call.send(:wait_for_digit) }.should == digits.map(&:chr)
418
+ digits.size.times { pbx_should_have_been_sent 'WAIT FOR DIGIT "-1"' }
383
419
  end
384
420
 
385
421
  it "the timeout given must be converted to milliseconds" do
386
422
  pbx_should_respond_with_success 0
387
423
  mock_call.send(:wait_for_digit, 1)
388
- output.messages.first.chomp.ends_with?('"1000"').should be true
424
+ pbx_should_have_been_sent 'WAIT FOR DIGIT "1000"'
389
425
  end
390
426
  end
391
427
 
392
- describe 'answer' do
428
+ describe 'The #answer method' do
393
429
  include DialplanCommandTestHelpers
394
430
 
395
431
  it 'should send ANSWER over the AGI socket' do
@@ -400,18 +436,19 @@ describe 'answer' do
400
436
 
401
437
  end
402
438
 
403
- describe 'execute' do
439
+ describe 'The #execute method' do
404
440
  include DialplanCommandTestHelpers
405
441
 
406
442
  it 'execute writes exec and app name to the PBX' do
407
443
  pbx_should_respond_with_success
408
444
  assert_success mock_call.execute(:foo)
409
- pbx_should_have_been_sent 'EXEC foo ""'
445
+ pbx_should_have_been_sent 'EXEC foo'
410
446
  end
411
447
 
412
448
  it 'execute returns false if the command was not executed successfully by the PBX' do
413
449
  pbx_should_respond_with_failure
414
450
  mock_call.execute(:foo).should_not be true
451
+ pbx_should_have_been_sent 'EXEC foo'
415
452
  end
416
453
 
417
454
  it 'execute can accept arguments after the app name which get translated into pipe-delimited arguments to the PBX' do
@@ -425,6 +462,7 @@ describe 'execute' do
425
462
  the_following_code {
426
463
  mock_call.execute :foo, "bar"
427
464
  }.should raise_error Adhearsion::Hangup
465
+ pbx_should_have_been_sent 'EXEC foo "bar"'
428
466
  end
429
467
 
430
468
  it "should raise a ArgumentError if given a null byte in the arguments" do
@@ -432,10 +470,121 @@ describe 'execute' do
432
470
  mock_call.execute :foo, "bar\0"
433
471
  }.should raise_error ArgumentError
434
472
  end
473
+ end
474
+
475
+ describe 'The #inline_return_value method' do
476
+ include DialplanCommandTestHelpers
435
477
 
478
+ it 'should return nil when given false or nil' do
479
+ mock_call.inline_return_value(false).should be nil
480
+ mock_call.inline_return_value(nil).should be nil
481
+ end
482
+
483
+ it 'should return nil when given an empty AGI value (0)' do
484
+ mock_call.inline_return_value(pbx_result_response(0)).should be nil
485
+ end
486
+
487
+
488
+ it 'should raise AGIProtocolError with an invalid response' do
489
+ expect {
490
+ mock_call.inline_return_value("500 result=foo\n")
491
+ }.to raise_error Adhearsion::VoIP::Asterisk::AGIProtocolError
492
+
493
+ expect {
494
+ mock_call.inline_return_value('Hey man, not so loud!')
495
+ }.to raise_error Adhearsion::VoIP::Asterisk::AGIProtocolError
496
+ end
497
+
498
+ it 'should parse the return value' do
499
+ mock_call.inline_return_value(pbx_result_response(5)).should == '5'
500
+ end
501
+ end
502
+
503
+ describe 'The #inline_result_with_return_value method' do
504
+ include DialplanCommandTestHelpers
505
+
506
+ it 'should return nil when given false or nil' do
507
+ mock_call.inline_result_with_return_value(false).should be nil
508
+ mock_call.inline_result_with_return_value(nil).should be nil
509
+ end
510
+
511
+ it 'should return nil when given an empty AGI value (0)' do
512
+ mock_call.inline_result_with_return_value(pbx_result_response(0)).should be nil
513
+ end
514
+
515
+ it 'should raise AGIProtocolError with an invalid response' do
516
+ expect {
517
+ mock_call.inline_result_with_return_value("500 result=1 (foo)\n")
518
+ }.to raise_error Adhearsion::VoIP::Asterisk::AGIProtocolError
519
+
520
+ expect {
521
+ mock_call.inline_result_with_return_value('Hey man, not so loud!')
522
+ }.to raise_error Adhearsion::VoIP::Asterisk::AGIProtocolError
523
+ end
524
+
525
+ it 'should parse the return value' do
526
+ mock_call.inline_result_with_return_value(pbx_value_response(5)).should == '5'
527
+ end
436
528
  end
437
529
 
438
- describe 'play command' do
530
+ describe 'The #play_or_speak method' do
531
+ include DialplanCommandTestHelpers
532
+
533
+ it 'should play a sound file if one exists' do
534
+ pbx_should_respond_with_playback_success
535
+ audio_file = "cents-per-minute"
536
+ mock_call.play_or_speak({audio_file => {}}).should be nil
537
+ pbx_was_asked_to_play audio_file
538
+ end
539
+
540
+ it 'should play a sound file via interruptible_play if file exists and interrupbible set and return key pressed and return the key press value' do
541
+ audio_file = "cents-per-minute"
542
+ mock_call.should_receive(:interruptible_play!).with(audio_file).once.and_return '#'
543
+ mock_call.play_or_speak({audio_file => {:interruptible => true}}).should == '#'
544
+ end
545
+
546
+ it 'should play a sound file via interruptible_play if file exists and interrupbible set' do
547
+ audio_file = "cents-per-minute"
548
+ mock_call.should_receive(:interruptible_play!).with(audio_file).once.and_return nil
549
+ mock_call.play_or_speak({audio_file => {:interruptible => true}}).should == nil
550
+ end
551
+
552
+ it 'should raise and error if a sound file does not exist and there is not text specified to fall back to' do
553
+ audio_file = "nixon tapes"
554
+ mock_call.should_receive(:play!).with(audio_file).and_raise Adhearsion::VoIP::PlaybackError
555
+ the_following_code {
556
+ mock_call.play_or_speak({audio_file => { :engine => :unimrcp}})
557
+ }.should raise_error ArgumentError
558
+ end
559
+
560
+ it 'should not send the command to play if the audio file is blank' do
561
+ mock_call.should_receive(:speak).with('hello', {:engine=>:unimrcp}).once.and_return nil
562
+ mock_call.play_or_speak({'' => { :text => 'hello', :engine => :unimrcp }}).should be nil
563
+ end
564
+
565
+ it 'should not send the command to play if the audio file is nil' do
566
+ mock_call.should_receive(:speak).with('hello', {:engine=>:unimrcp}).once.and_return nil
567
+ mock_call.play_or_speak({nil => { :text => 'hello', :engine => :unimrcp }}).should be nil
568
+ end
569
+
570
+ it 'should speak the text if a sound file does not exist' do
571
+ audio_file = "nixon tapes"
572
+ mock_call.should_receive(:play!).with(audio_file).and_raise Adhearsion::VoIP::PlaybackError
573
+ mock_call.should_receive(:speak).with('hello', {:engine=>:unimrcp}).once.and_return nil
574
+ mock_call.play_or_speak({audio_file => { :text => 'hello', :engine => :unimrcp }}).should be nil
575
+ end
576
+
577
+ it 'should speak the text if a sound file does not exist and pass back the entered text if a key is pressed' do
578
+ audio_file = "nixon tapes"
579
+ mock_call.should_receive(:interruptible_play!).with(audio_file).and_raise Adhearsion::VoIP::PlaybackError
580
+ mock_call.should_receive(:speak).with('hello', {:engine=>:unimrcp, :interruptible => true}).once.and_return '5'
581
+ mock_call.play_or_speak({audio_file => { :text => 'hello', :engine => :unimrcp, :interruptible => true
582
+ }}).should == '5'
583
+ end
584
+
585
+ end
586
+
587
+ describe 'The #play method' do
439
588
  include DialplanCommandTestHelpers
440
589
 
441
590
  it 'passing a single string to play results in the playback application being executed with that file name on the PBX' do
@@ -537,7 +686,7 @@ describe 'play command' do
537
686
  end
538
687
  end
539
688
 
540
- describe 'play! command' do
689
+ describe 'The #play! method' do
541
690
  include DialplanCommandTestHelpers
542
691
 
543
692
  it 'should accept multiple strings to play, causing multiple playback commands to be issued' do
@@ -570,12 +719,149 @@ describe 'play! command' do
570
719
  end
571
720
  end
572
721
 
573
- describe 'input command' do
722
+ describe 'the #record method' do
723
+ include DialplanCommandTestHelpers
724
+
725
+ it 'should return the recorded file name if the user hangs up during the recording' do
726
+ mock_call.should_receive(:response).once.with('RECORD FILE', 'foo', 'gsm', '#', -1, 0, 'BEEP').and_return("200 result=-1 (hangup) endpos=167840\n")
727
+ mock_call.record('foo').should == 'foo.gsm'
728
+ end
729
+
730
+ it 'create a default filename if no file is specifed and icrement it on subsequent calls' do
731
+ mock_call.call.variables.delete :recording_counter
732
+ mock_call.should_receive(:response).once.with('RECORD FILE', '/tmp/recording_0', 'gsm', '26', -1, 0).and_return("200 result=0 (timeout) endpos=21600\n")
733
+ mock_call.should_receive(:response).once.with('RECORD FILE', '/tmp/recording_1', 'gsm', '26', -1, 0).and_return("200 result=0 (timeout) endpos=21600\n")
734
+ mock_call.record(:beep => nil, :escapedigits => '26').should == '/tmp/recording_0.gsm'
735
+ mock_call.record(:beep => nil, :escapedigits => '26').should == '/tmp/recording_1.gsm'
736
+ end
737
+
738
+ it 'determine the format from the filename' do
739
+ mock_call.should_receive(:response).once.with('RECORD FILE', 'foo', 'wav', '26', -1, 0).and_return("200 result=0 (timeout) endpos=21600\n")
740
+ mock_call.record('foo.wav', :beep => nil, :escapedigits => '26').should == 'foo.wav'
741
+ end
742
+
743
+ it 'set the format of a file via the :format option' do
744
+ mock_call.should_receive(:response).once.with("RECORD FILE", "foo", "wav", "#", 2000, 0).and_return("200 result=0 (timeout) endpos=21600\n")
745
+ mock_call.record('foo', :beep => nil, :maxduration => 2, :format => 'wav').should == 'foo.wav'
746
+ end
747
+
748
+ it 'set the format of a file via the :format option over-riding a implicit format' do
749
+ mock_call.should_receive(:response).once.with("RECORD FILE", "foo.wav", "mpeg", "#", 2000, 0).and_return("200 result=0 (timeout) endpos=21600\n")
750
+ mock_call.record('foo.wav', :beep => nil, :maxduration => 2, :format => 'mpeg').should == 'foo.wav.mpeg'
751
+ end
752
+ end
753
+
754
+ describe 'the #record_to_file method' do
755
+ include DialplanCommandTestHelpers
756
+
757
+ it 'should return :hangup if the user hangs up during the recording' do
758
+ mock_call.should_receive(:response).once.with("RECORD FILE", "foo", "gsm", "#", -1, 0, "BEEP").and_return("200 result=-1 (hangup) endpos=167840\n")
759
+ mock_call.record_to_file('foo').should == :hangup
760
+ end
761
+
762
+ it 'should return :write error if the recording had a problem writing the file' do
763
+ mock_call.should_receive(:response).once.with("RECORD FILE", "foo", "gsm", "#", -1, 0, "BEEP").and_return("200 result=-1 (writefile) endpos=167840\n")
764
+ mock_call.record_to_file('foo').should == :write_error
765
+ end
766
+
767
+ it 'should return :success_dtmf if the recording was completed successfully with a dtmf tone to end' do
768
+ mock_call.should_receive(:response).once.with("RECORD FILE", "foo", "gsm", "#", -1, 0, "BEEP").and_return("200 result=35 (dtmf) endpos=29120\n")
769
+ mock_call.record_to_file('foo').should == :success_dtmf
770
+ end
771
+
772
+ it 'should return :success_timeout if the recording was completed successfully by timing out with silence' do
773
+ mock_call.should_receive(:response).once.with("RECORD FILE", "foo", "gsm", "#", -1, 0, "BEEP").and_return("200 result=0 (timeout) endpos=21600\n")
774
+ mock_call.record_to_file('foo').should == :success_timeout
775
+ end
776
+
777
+ it 'not send a beep if a :beep=>nil is passed in' do
778
+ mock_call.should_receive(:response).once.with("RECORD FILE", "foo", "gsm", "#", -1, 0).and_return("200 result=0 (timeout) endpos=21600\n")
779
+ mock_call.record_to_file('foo', :beep => nil).should == :success_timeout
780
+ end
781
+
782
+ it 'set the silence if it is passed in' do
783
+ mock_call.should_receive(:response).once.with("RECORD FILE", "foo", "gsm", "#", -1, 0, 's=2').and_return("200 result=0 (timeout) endpos=21600\n")
784
+ mock_call.record_to_file('foo', :beep => nil, :silence => 2).should == :success_timeout
785
+ end
786
+
787
+ it 'set the maxduration if it is passed in' do
788
+ mock_call.should_receive(:response).once.with("RECORD FILE", "foo", "gsm", "#", 2000, 0).and_return("200 result=0 (timeout) endpos=21600\n")
789
+ mock_call.record_to_file('foo', :beep => nil, :maxduration => 2).should == :success_timeout
790
+ end
791
+
792
+ it 'set the format of a file via the :format option' do
793
+ mock_call.should_receive(:response).once.with("RECORD FILE", "foo", "wav", "#", 2000, 0).and_return("200 result=0 (timeout) endpos=21600\n")
794
+ mock_call.record_to_file('foo', :beep => nil, :maxduration => 2, :format => 'wav').should == :success_timeout
795
+ end
796
+
797
+ it 'set the format of a file via the :format option over-riding a implicit format' do
798
+ mock_call.should_receive(:response).once.with("RECORD FILE", "foo.wav", "mpeg", "#", 2000, 0).and_return("200 result=0 (timeout) endpos=21600\n")
799
+ mock_call.record_to_file('foo.wav', :beep => nil, :maxduration => 2, :format => 'mpeg').should == :success_timeout
800
+ end
801
+
802
+ it 'set the escapedigits if it is passed in' do
803
+ mock_call.should_receive(:response).once.with("RECORD FILE", "foo", "gsm", "26", -1, 0).and_return("200 result=0 (timeout) endpos=21600\n")
804
+ mock_call.record_to_file('foo', :beep => nil, :escapedigits => '26').should == :success_timeout
805
+ end
806
+
807
+ it 'play a passed in beep file if it is passed in' do
808
+ mock_call.should_receive(:execute).once.with(:playback, 'my_awesome_beep.wav').and_return(true)
809
+ pbx_should_respond_with_playback_success
810
+ pbx_should_respond_with_playback_success
811
+ mock_call.should_receive(:response).once.with("RECORD FILE", "foo", "gsm", "26", -1, 0).and_return("200 result=0 (timeout) endpos=21600\n")
812
+ mock_call.record_to_file('foo', :beep => 'my_awesome_beep.wav', :escapedigits => '26').should == :success_timeout
813
+ end
814
+
815
+ it "should silently fail if the beep file passed in can't be played" do
816
+ mock_call.should_receive(:execute).once.with(:playback, 'my_awesome_beep.wav').and_return(true)
817
+ pbx_should_respond_with_playback_failure
818
+ pbx_should_respond_with_playback_failure
819
+ mock_call.should_receive(:response).once.with("RECORD FILE", "foo", "gsm", "26", -1, 0).and_return("200 result=0 (timeout) endpos=21600\n")
820
+ mock_call.record_to_file('foo', :beep => 'my_awesome_beep.wav', :escapedigits => '26').should == :success_timeout
821
+ end
822
+
823
+ it 'determine the format from the filename' do
824
+ mock_call.should_receive(:response).once.with("RECORD FILE", "foo", "wav", "26", -1, 0).and_return("200 result=0 (timeout) endpos=21600\n")
825
+ mock_call.record_to_file('foo.wav', :beep => nil, :escapedigits => '26').should == :success_timeout
826
+ end
827
+
828
+ it 'create a default filename if no file is specifed and icrement it on subsequent calls' do
829
+ mock_call.should_receive(:response).once.with("RECORD FILE", "/tmp/recording_0", "gsm", "26", -1, 0).and_return("200 result=0 (timeout) endpos=21600\n")
830
+ mock_call.should_receive(:response).once.with("RECORD FILE", "/tmp/recording_1", "gsm", "26", -1, 0).and_return("200 result=0 (timeout) endpos=21600\n")
831
+ mock_call.record_to_file(:beep => nil, :escapedigits => '26').should == :success_timeout
832
+ mock_call.record_to_file(:beep => nil, :escapedigits => '26').should == :success_timeout
833
+ end
834
+ end
574
835
 
836
+ describe 'The #record_to_file! method' do
575
837
  include DialplanCommandTestHelpers
576
838
 
577
- # pbx_should_respond_with_successful_background_response
578
- # pbx_should_respond_with_a_wait_for_digit_timeout
839
+ it "should throw an exception the beep file passed in can't be played" do
840
+ mock_call.should_receive(:execute).once.with(:playback, 'my_awesome_beep.wav').and_return(false)
841
+ pbx_should_respond_with_playback_failure
842
+ pbx_should_respond_with_playback_failure
843
+ mock_call.should_receive(:response).once.with("RECORD FILE", "foo", "gsm", "26", -1, 0).and_return("200 result=0 (timeout) endpos=21600\n")
844
+ the_following_code {
845
+ mock_call.record_to_file!('foo', :beep => 'my_awesome_beep.wav', :escapedigits => '26').should == :success_timeout
846
+ }.should raise_error Adhearsion::VoIP::PlaybackError
847
+ end
848
+
849
+ it 'should throw RecordError if the recording had a problem writing the file' do
850
+ mock_call.should_receive(:response).once.with("RECORD FILE", "foo", "gsm", "#", -1, 0, "BEEP").and_return("200 result=-1 (writefile) endpos=167840\n")
851
+ the_following_code {
852
+ mock_call.record_to_file!('foo').should == :write_error
853
+ }.should raise_error Adhearsion::VoIP::RecordError
854
+ end
855
+
856
+ it 'should be able get a response from a successfull call' do
857
+ mock_call.should_receive(:response).once.with("RECORD FILE", "foo", "wav", "26", -1, 0).and_return("200 result=0 (timeout) endpos=21600\n")
858
+ mock_call.record_to_file!('foo.wav', :beep => nil, :escapedigits => '26').should == :success_timeout
859
+ end
860
+ end
861
+
862
+ describe 'The #input method' do
863
+
864
+ include DialplanCommandTestHelpers
579
865
 
580
866
  it 'should raise an error when the number of digits expected is -1 (this is deprecated behavior)' do
581
867
  the_following_code {
@@ -608,6 +894,7 @@ describe 'input command' do
608
894
  the_following_code {
609
895
  mock_call.input(:accept_key => false)
610
896
  }.should raise_error ArgumentError
897
+ pbx_should_have_been_sent 'WAIT FOR DIGIT "-1"'
611
898
  end
612
899
 
613
900
  it 'when :accept_key is false and input() is collecting a finite number of digits, it should allow all DTMFs' do
@@ -618,6 +905,18 @@ describe 'input command' do
618
905
  }.should_not raise_error ArgumentError
619
906
  end
620
907
 
908
+ it 'should terminate early when the passed block returns something truthy' do
909
+ three_digits = %w[9 3 0]
910
+ mock_call.should_receive(:wait_for_digit).times(2).and_return(*three_digits)
911
+ mock_call.input(3, :accept_key => false) { |buffer| buffer.size == 2 }.should == '93'
912
+ end
913
+
914
+ it "Input timing out when digits are pressed returns only the collected digits" do
915
+ timeout = 1.day
916
+ mock_call.should_receive(:wait_for_digit).twice.with(timeout).and_return '5', nil
917
+ mock_call.input(9, :timeout => timeout).should == '5'
918
+ end
919
+
621
920
  it 'passes wait_for_digit the :timeout option when one is given' do
622
921
  mock_call.should_receive(:interruptible_play!).never
623
922
  mock_call.should_receive(:wait_for_digit).twice.and_return '1', '2'
@@ -633,6 +932,15 @@ describe 'input command' do
633
932
  mock_call.input(2, :play => sound_files).should == '#*'
634
933
  end
635
934
 
935
+ it 'executes #play! when :interruptible is set to false' do
936
+ sound_files = %w[foo bar qaz]
937
+ mock_call.should_receive(:play!).once.with('foo').and_return true
938
+ mock_call.should_receive(:play!).once.with('bar').and_return true
939
+ mock_call.should_receive(:play!).once.with('qaz').and_return true
940
+ mock_call.should_receive(:wait_for_digit).once.and_return '*'
941
+ mock_call.input(1, :play => sound_files, :interruptible => false).should == '*'
942
+ end
943
+
636
944
  it 'pressing the terminating key before any other digits returns an empty string' do
637
945
  mock_call.should_receive(:wait_for_digit).once.and_return '*'
638
946
  mock_call.input(:accept_key => '*').should == ''
@@ -650,7 +958,7 @@ describe 'input command' do
650
958
  mock_call.input(9, :timeout => timeout).should == '5'
651
959
  end
652
960
 
653
- it 'should execute wait_for_digit, even if some sound files are not found' do
961
+ it 'should execute wait_for_digit, even if some interruptible sound files are not found' do
654
962
  pbx_should_respond_with_stream_file_failure_on_open
655
963
  file = 'foobar'
656
964
  timeout = 1.hour
@@ -659,6 +967,15 @@ describe 'input command' do
659
967
  pbx_was_asked_to_stream file
660
968
  end
661
969
 
970
+ it 'should execute wait_for_digit with, even if some uninterruptible sound files are not found' do
971
+ pbx_should_respond_with_playback_failure
972
+ file = 'foobar'
973
+ timeout = 1.hour
974
+ mock_call.should_receive(:wait_for_digit).twice.with(timeout).and_return '8', '9'
975
+ mock_call.input(2, :timeout => timeout, :play => file, :interruptible => false).should == '89'
976
+ pbx_was_asked_to_play file
977
+ end
978
+
662
979
  it 'should return an empty string if no keys are pressed, even if the sound file is not found' do
663
980
  pbx_should_respond_with_stream_file_failure_on_open
664
981
  file = 'foobar'
@@ -682,7 +999,7 @@ describe 'input command' do
682
999
  pbx_was_asked_to_stream played_files
683
1000
  end
684
1001
 
685
- it 'should play a series of 4 files, collecting digits even if some of the sound files cannot be found' do
1002
+ it 'should play a series of 4 interruptible sounds, collecting digits even if some of the sound files cannot be found' do
686
1003
  pbx_should_respond_with_stream_file_success 0
687
1004
  pbx_should_respond_with_stream_file_success 0
688
1005
  pbx_should_respond_with_stream_file_failure_on_open
@@ -697,9 +1014,57 @@ describe 'input command' do
697
1014
  pbx_was_asked_to_stream played_files
698
1015
  end
699
1016
 
1017
+ it 'should not raise an exception if the sound file is unplayable' do
1018
+ pbx_should_respond_with_stream_file_failure_on_open
1019
+ file = 'foobar'
1020
+ mock_call.should_receive(:wait_for_digit).once
1021
+ the_following_code {
1022
+ mock_call.input 1, :play => file
1023
+ }.should_not raise_error
1024
+ pbx_was_asked_to_stream file
1025
+ end
1026
+
1027
+ it 'should default to playing interruptible prompts' do
1028
+ mock_call.should_receive(:interruptible_play!).once.with('does_not_matter')
1029
+ mock_call.should_receive(:wait_for_digit).once
1030
+ mock_call.input(1, :play => 'does_not_matter')
1031
+ end
1032
+
1033
+ it 'should render uninterruptible prompts' do
1034
+ mock_call.should_receive(:play!).once.with('does_not_matter')
1035
+ mock_call.should_receive(:wait_for_digit).once
1036
+ mock_call.input(1, :play => 'does_not_matter', :interruptible => false)
1037
+ end
1038
+
1039
+ it 'should fall back to speaking TTS if sound file is unplayable' do
1040
+ pbx_should_respond_with_stream_file_failure_on_open
1041
+ mock_call.should_receive(:speak).once.with("The sound file was not available", :interruptible => true)
1042
+ mock_call.should_receive(:wait_for_digit).once
1043
+ mock_call.input(1, :play => 'unavailable sound file', :speak => {:text => "The sound file was not available"})
1044
+ @output.read.should == "STREAM FILE \"unavailable sound file\" \"1234567890*#\"\n"
1045
+ end
1046
+
1047
+ it 'should allow uninterruptible TTS prompts' do
1048
+ mock_call.should_receive(:speak).once.with("The sound file was not available", :interruptible => false)
1049
+ mock_call.should_receive(:wait_for_digit).once
1050
+ mock_call.input(1, :speak => {:text => "The sound file was not available"}, :interruptible => false)
1051
+ end
1052
+
1053
+ it 'should play a series of 4 uninterruptible sounds, collecting digits even if some of the sound files cannot be found' do
1054
+ pbx_should_respond_with_playback_success
1055
+ pbx_should_respond_with_playback_failure
1056
+ pbx_should_respond_with_playback_success
1057
+
1058
+ files = ('sound1'..'sound3').map &:to_s
1059
+ timeout = 1.second
1060
+ mock_call.should_receive(:wait_for_digit).twice.with(timeout).and_return '6', '7'
1061
+ mock_call.input(2, :timeout => timeout, :play => files, :interruptible => false).should == '67'
1062
+ pbx_was_asked_to_play files
1063
+ end
1064
+
700
1065
  end
701
1066
 
702
- describe 'input! command' do
1067
+ describe 'The #input! method' do
703
1068
 
704
1069
  include DialplanCommandTestHelpers
705
1070
 
@@ -727,6 +1092,15 @@ describe 'input! command' do
727
1092
  mock_call.input!(2, :play => sound_files).should == '#*'
728
1093
  end
729
1094
 
1095
+ it 'executes play!() with all of the files given to :play' do
1096
+ sound_files = %w[foo bar qaz]
1097
+ mock_call.should_receive(:play!).once.with('foo').and_return true
1098
+ mock_call.should_receive(:play!).once.with('bar').and_return true
1099
+ mock_call.should_receive(:play!).once.with('qaz').and_return true
1100
+ mock_call.should_receive(:wait_for_digit).once.and_return '*'
1101
+ mock_call.input!(1, :play => sound_files, :interruptible => false).should == '*'
1102
+ end
1103
+
730
1104
  it 'should execute wait_for_digit first if no sound files are given' do
731
1105
  mock_call.should_receive(:interruptible_play!).never
732
1106
  mock_call.should_receive(:wait_for_digit).once.and_throw :digit_request
@@ -743,7 +1117,7 @@ describe 'input! command' do
743
1117
  pbx_was_asked_to_stream file
744
1118
  end
745
1119
 
746
- it 'should play a series of files, raising an error if a sound file cannot be found' do
1120
+ it 'should play a series of interruptible files, raising an error if a sound file cannot be found' do
747
1121
  pbx_should_respond_with_stream_file_success 0
748
1122
  pbx_should_respond_with_stream_file_failure_on_open
749
1123
  mock_call.should_receive(:wait_for_digit).never
@@ -756,9 +1130,21 @@ describe 'input! command' do
756
1130
  pbx_was_asked_to_stream played_files
757
1131
  end
758
1132
 
1133
+ it 'should play a series of uninterruptible files, raising an error if a sound file cannot be found' do
1134
+ pbx_should_respond_with_playback_success
1135
+ pbx_should_respond_with_playback_failure
1136
+
1137
+ play_files = ('sound1'..'sound6').map &:to_s
1138
+ played_files = ('sound1'..'sound2').map &:to_s
1139
+ the_following_code {
1140
+ mock_call.input! 10, :play => play_files, :timeout => 5.seconds, :interruptible => false
1141
+ }.should raise_error Adhearsion::VoIP::PlaybackError
1142
+ pbx_was_asked_to_play played_files
1143
+ end
1144
+
759
1145
  end
760
1146
 
761
- describe "The variable() command" do
1147
+ describe "The #variable method" do
762
1148
 
763
1149
  include DialplanCommandTestHelpers
764
1150
 
@@ -795,7 +1181,7 @@ describe "The variable() command" do
795
1181
 
796
1182
  end
797
1183
 
798
- describe "the set_variable method" do
1184
+ describe "The #set_variable method" do
799
1185
 
800
1186
  include DialplanCommandTestHelpers
801
1187
 
@@ -811,7 +1197,7 @@ describe "the set_variable method" do
811
1197
 
812
1198
  end
813
1199
 
814
- describe "the sip_add_header method" do
1200
+ describe "The #sip_add_header method" do
815
1201
  include DialplanCommandTestHelpers
816
1202
 
817
1203
  it "values are properly quoted" do
@@ -820,21 +1206,23 @@ describe "the sip_add_header method" do
820
1206
  end
821
1207
  end
822
1208
 
823
- describe "the sip_get_header method" do
1209
+ describe "The #sip_get_header method" do
824
1210
  include DialplanCommandTestHelpers
825
1211
 
826
- it "values are properly quoted" do
827
- mock_call.should_receive(:raw_response).once.with 'GET VARIABLE "SIP_HEADER(x-ahn-header)"'
828
- mock_call.sip_get_header "x-ahn-header"
1212
+ it "properly formats the AGI request" do
1213
+ value = 'jason-was-here'
1214
+ mock_call.should_receive(:raw_response).once.with('GET VARIABLE "SIP_HEADER(x-ahn-header)"').and_return "200 result=1 (#{value})"
1215
+ mock_call.sip_get_header("x-ahn-header").should == value
829
1216
  end
830
1217
 
831
- it "values are properly quoted with aliased method" do
832
- mock_call.should_receive(:raw_response).once.with 'GET VARIABLE "SIP_HEADER(x-ahn-header)"'
833
- mock_call.sip_header "x-ahn-header"
1218
+ it "properly formats the AGI request using the method alias" do
1219
+ value = 'jason-was-here'
1220
+ mock_call.should_receive(:raw_response).once.with('GET VARIABLE "SIP_HEADER(x-ahn-header)"').and_return "200 result=1 (#{value})"
1221
+ mock_call.sip_header("x-ahn-header").should == value
834
1222
  end
835
1223
  end
836
1224
 
837
- describe 'the voicemail command' do
1225
+ describe 'The #voicemail command' do
838
1226
 
839
1227
  include DialplanCommandTestHelpers
840
1228
 
@@ -857,12 +1245,13 @@ describe 'the voicemail command' do
857
1245
  end
858
1246
 
859
1247
  it 'should combine mailbox numbers with the context name given when both are given' do
860
- does_not_read_data_back
1248
+ pbx_should_respond_with_value 'SUCCESS'
861
1249
  context = "lolcats"
862
1250
  mailboxes = [1,2,3,4,5]
863
1251
  mailboxes_with_context = mailboxes.map { |mailbox| "#{mailbox}@#{context}"}
864
1252
  mock_call.should_receive(:execute).once.with('voicemail', mailboxes_with_context.join('&'), '')
865
1253
  mock_call.voicemail context => mailboxes
1254
+ pbx_should_have_been_sent 'GET VARIABLE "VMSTATUS"'
866
1255
  end
867
1256
 
868
1257
  it 'should raise an argument error if the mailbox number is not numerical' do
@@ -1047,12 +1436,14 @@ describe "The queue management abstractions" do
1047
1436
  does_not_read_data_back
1048
1437
  mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return "TIMEOUT"
1049
1438
  mock_call.queue('monkey').join!.should be :timeout
1439
+ pbx_should_have_been_sent 'EXEC queue "monkey"|""|""|""|""|""'
1050
1440
  end
1051
1441
 
1052
1442
  it 'should return :completed after joining the queue and being connected' do
1053
1443
  does_not_read_data_back
1054
1444
  mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return nil
1055
1445
  mock_call.queue('monkey').join!.should be :completed
1446
+ pbx_should_have_been_sent 'EXEC queue "monkey"|""|""|""|""|""'
1056
1447
  end
1057
1448
 
1058
1449
  it 'should join a queue with a timeout properly' do
@@ -1204,6 +1595,7 @@ describe "The queue management abstractions" do
1204
1595
  the_following_code {
1205
1596
  mock_call.queue('this_should_not_exist').agents.new 'Agent/911'
1206
1597
  }.should raise_error Adhearsion::VoIP::Asterisk::Commands::QueueProxy::QueueDoesNotExistError
1598
+ pbx_should_have_been_sent 'EXEC AddQueueMember "this_should_not_exist"|"Agent/911"|""|""|""|""'
1207
1599
  end
1208
1600
 
1209
1601
  it 'when a queue agent is dynamiaclly added and the adding was successful, an AgentProxy should be returned' do
@@ -1284,6 +1676,7 @@ describe "The queue management abstractions" do
1284
1676
 
1285
1677
  agents = mock_call.queue('lolcats').agents :cache => true
1286
1678
  agents.last.pause!.should be true
1679
+ pbx_should_have_been_sent 'EXEC PauseQueueMember "lolcats"|"Agent/008"'
1287
1680
  end
1288
1681
 
1289
1682
  it 'should pause an agent properly from a certain queue and return false when the agent did not exist' do
@@ -1442,6 +1835,7 @@ describe 'the menu() method' do
1442
1835
  link.other 543
1443
1836
  end
1444
1837
  end
1838
+ 3.times { pbx_should_have_been_sent 'WAIT FOR DIGIT "5000"' }
1445
1839
  end
1446
1840
 
1447
1841
  it "when the 'extension' variable is changed, it should be an instance of PhoneNumber" do
@@ -1455,6 +1849,7 @@ describe 'the menu() method' do
1455
1849
  end
1456
1850
  5.should === mock_call.extension
1457
1851
  mock_call.extension.__real_string.should == "5"
1852
+ pbx_should_have_been_sent 'WAIT FOR DIGIT "5000"'
1458
1853
  end
1459
1854
 
1460
1855
  end
@@ -1511,9 +1906,6 @@ describe 'the Menu class' do
1511
1906
  end
1512
1907
 
1513
1908
  it "should default the timeout to five seconds" do
1514
- pbx_should_respond_with_successful_background_response ?2.ord
1515
- pbx_should_respond_with_a_wait_for_digit_timeout
1516
-
1517
1909
  mock_call.should_receive(:wait_for_digit).once.with(5).and_return nil
1518
1910
  mock_call.menu { |link| link.foobar 22 }
1519
1911
  end
@@ -1536,6 +1928,7 @@ describe 'the Menu class' do
1536
1928
  end
1537
1929
  end
1538
1930
  times_timed_out.should be tries
1931
+ 30.times { pbx_should_have_been_sent 'WAIT FOR DIGIT "5000"' }
1539
1932
  end
1540
1933
 
1541
1934
  it "when matches fail due to invalid input, the menu should repeat :tries times" do
@@ -1555,6 +1948,7 @@ describe 'the Menu class' do
1555
1948
  end
1556
1949
  end
1557
1950
  times_invalid.should be tries
1951
+ 10.times { pbx_should_have_been_sent 'WAIT FOR DIGIT "5000"' }
1558
1952
  end
1559
1953
 
1560
1954
  it "invoke on_invalid callback when an invalid extension was entered" do
@@ -1567,6 +1961,7 @@ describe 'the Menu class' do
1567
1961
  link.on_invalid { throw :inside_invalid_callback }
1568
1962
  end
1569
1963
  end
1964
+ pbx_should_have_been_sent 'WAIT FOR DIGIT "5000"'
1570
1965
  end
1571
1966
 
1572
1967
  it "invoke on_premature_timeout when a timeout is encountered" do
@@ -1579,6 +1974,7 @@ describe 'the Menu class' do
1579
1974
  link.on_premature_timeout { throw :inside_timeout }
1580
1975
  end
1581
1976
  end
1977
+ 2.times { pbx_should_have_been_sent 'WAIT FOR DIGIT "1000"' }
1582
1978
  end
1583
1979
 
1584
1980
  end
@@ -1603,6 +1999,7 @@ describe "the Menu class's high-level judgment" do
1603
1999
  end
1604
2000
  end
1605
2001
  111.should === mock_call.extension
2002
+ 4.times { pbx_should_have_been_sent 'WAIT FOR DIGIT "5000"' }
1606
2003
  end
1607
2004
 
1608
2005
  it 'should match things in a range when there are many other non-matching patterns' do
@@ -1621,6 +2018,7 @@ describe "the Menu class's high-level judgment" do
1621
2018
  link.conferences 900..999
1622
2019
  end
1623
2020
  end
2021
+ 3.times { pbx_should_have_been_sent 'WAIT FOR DIGIT "5000"' }
1624
2022
  end
1625
2023
 
1626
2024
  end
@@ -1938,11 +2336,13 @@ describe "get variable command" do
1938
2336
  it "Getting a variable that isn't set returns nothing" do
1939
2337
  pbx_should_respond_with "200 result=0"
1940
2338
  mock_call.get_variable('OMGURFACE').should be nil
2339
+ pbx_should_have_been_sent 'GET VARIABLE "OMGURFACE"'
1941
2340
  end
1942
2341
 
1943
2342
  it 'An empty variable should return an empty String' do
1944
2343
  pbx_should_respond_with_value ""
1945
2344
  mock_call.get_variable('kablamm').should == ""
2345
+ pbx_should_have_been_sent 'GET VARIABLE "kablamm"'
1946
2346
  end
1947
2347
 
1948
2348
  it "Getting a variable that is set returns its value" do
@@ -1950,6 +2350,7 @@ describe "get variable command" do
1950
2350
  pbx_should_respond_with_value unique_id
1951
2351
  variable_value_returned = mock_call.get_variable('UNIQUEID')
1952
2352
  variable_value_returned.should == unique_id
2353
+ pbx_should_have_been_sent 'GET VARIABLE "UNIQUEID"'
1953
2354
  end
1954
2355
  end
1955
2356
 
@@ -1991,6 +2392,7 @@ describe "Dial command" do
1991
2392
  does_not_read_data_back
1992
2393
  mock_call.should_receive(:set_caller_id_number).once
1993
2394
  mock_call.dial 123, :caller_id => "1234678901"
2395
+ pbx_should_have_been_sent 'EXEC Dial "123"|""|""'
1994
2396
  end
1995
2397
 
1996
2398
  it 'should raise an exception when unknown hash key arguments are given to it' do
@@ -2004,6 +2406,7 @@ describe "Dial command" do
2004
2406
  name = "Jay Phillips"
2005
2407
  mock_call.should_receive(:set_caller_id_name).once.with(name)
2006
2408
  mock_call.dial "BlahBlahBlah", :name => name
2409
+ pbx_should_have_been_sent 'EXEC Dial "BlahBlahBlah"|""|""'
2007
2410
  end
2008
2411
 
2009
2412
  it "should raise an exception when a non-numerical caller_id is specified" do
@@ -2012,11 +2415,19 @@ describe "Dial command" do
2012
2415
  }.should raise_error ArgumentError
2013
2416
  end
2014
2417
 
2418
+ it "should not raise an exception when a caller_id is specified in E.164 format (with '+' sign)" do
2419
+ the_following_code {
2420
+ mock_call.dial 911, :caller_id => "+123456789"
2421
+ }.should_not raise_error ArgumentError
2422
+ pbx_should_have_been_sent 'SET VARIABLE "CALLERID(num)" "+123456789"'
2423
+ end
2424
+
2015
2425
  it 'should pass the value of the :confirm key to dial_macro_option_compiler()' do
2016
2426
  does_not_read_data_back
2017
2427
  value_of_confirm_key = {:play => "ohai", :timeout => 30}
2018
2428
  mock_call.should_receive(:dial_macro_option_compiler).once.with value_of_confirm_key
2019
2429
  mock_call.dial 123, :confirm => value_of_confirm_key
2430
+ pbx_should_have_been_sent 'EXEC Dial "123"|""|""'
2020
2431
  end
2021
2432
 
2022
2433
  it "should add the return value of dial_macro_option_compiler to the :options key's value given to the dial command" do
@@ -2248,33 +2659,124 @@ describe 'set_caller_id_name command' do
2248
2659
  end
2249
2660
  end
2250
2661
 
2251
- describe "record command" do
2662
+ describe "speak command" do
2252
2663
  include DialplanCommandTestHelpers
2253
2664
 
2665
+ before :all do
2666
+ @speech_engines = Adhearsion::VoIP::Asterisk::Commands::SpeechEngines
2667
+ end
2668
+
2254
2669
  it "executes the command SpeechEngine gives it based on the engine name" do
2255
- mock_speak_command = "returned command doesn't matter"
2256
- flexmock(Adhearsion::VoIP::Asterisk::Commands::SpeechEngines).should_receive(:cepstral).
2257
- once.and_return(mock_speak_command)
2258
- mock_call.should_receive(:execute).once.with(mock_speak_command)
2259
- mock_call.speak "Spoken text doesn't matter", :cepstral
2670
+ pbx_should_respond_with_success
2671
+ flexmock(@speech_engines).should_receive(:cepstral).once
2672
+ mock_call.speak "Spoken text doesn't matter", :engine => :cepstral
2260
2673
  end
2261
2674
 
2262
2675
  it "raises an InvalidSpeechEngine exception when the engine is 'none'" do
2263
2676
  the_following_code {
2264
- mock_call.speak("o hai!", :none)
2265
- }.should raise_error Adhearsion::VoIP::Asterisk::Commands::SpeechEngines::InvalidSpeechEngine
2677
+ mock_call.speak("o hai!", :engine => :none)
2678
+ }.should raise_error @speech_engines::InvalidSpeechEngine
2266
2679
  end
2267
2680
 
2268
2681
  it "should default its engine to :none" do
2269
2682
  the_following_code {
2270
- flexmock(Adhearsion::VoIP::Asterisk::Commands::SpeechEngines).should_receive(:none).once.
2271
- and_raise(Adhearsion::VoIP::Asterisk::Commands::SpeechEngines::InvalidSpeechEngine)
2683
+ flexmock(@speech_engines).should_receive(:none).once.
2684
+ and_raise(@speech_engines::InvalidSpeechEngine)
2272
2685
  mock_call.speak "ruby ruby ruby ruby!"
2273
- }.should raise_error Adhearsion::VoIP::Asterisk::Commands::SpeechEngines::InvalidSpeechEngine
2686
+ }.should raise_error @speech_engines::InvalidSpeechEngine
2687
+ end
2688
+
2689
+ it 'should default to a configured TTS engine' do
2690
+ Adhearsion::Configuration.configure {|c| c.asterisk.speech_engine = :unimrcp }
2691
+ flexmock(@speech_engines).should_receive(:unimrcp).once
2692
+ mock_call.speak 'What say you, sir?'
2693
+ end
2694
+
2695
+ it 'should allow the caller to override the default configured TTS engine' do
2696
+ Adhearsion::Configuration.configure {|c| c.asterisk.speech_engine = :unimrcp }
2697
+ flexmock(@speech_engines).should_receive(:cepstral).once
2698
+ mock_call.speak 'What say you now, sir?', :engine => :cepstral
2699
+ end
2700
+
2701
+ it 'should default to uninterruptible TTS rendering' do
2702
+ flexmock(@speech_engines).should_receive(:cepstral).once.with(mock_call, 'hello', {:interruptible => false})
2703
+ mock_call.speak 'hello', :engine => :cepstral
2704
+ end
2705
+
2706
+ it 'should allow setting TTS rendering interruptible' do
2707
+ flexmock(@speech_engines).should_receive(:cepstral).once.with(mock_call, 'hello', {:interruptible => true})
2708
+ mock_call.speak 'hello', :engine => :cepstral, :interruptible => true
2274
2709
  end
2275
2710
 
2276
- #it "Properly escapes spoken text" # TODO: What are the escaping needs?
2711
+ it "should stringify the text" do
2712
+ flexmock(@speech_engines).should_receive(:cepstral).once.with(mock_call, 'hello', {:interruptible => false})
2713
+ mock_call.speak :hello, :engine => :cepstral
2714
+ end
2715
+
2716
+ context "with the engine :cepstral" do
2717
+ it "should execute Swift"do
2718
+ pbx_should_respond_with_value 0
2719
+ mock_call.should_receive(:execute).with('Swift', 'hello')
2720
+ @speech_engines.cepstral(mock_call, 'hello')
2721
+ @output.read.should == "GET VARIABLE \"SWIFT_DTMF\"\n"
2722
+ end
2723
+
2724
+ it "should properly escape commas in the TTS string" do
2725
+ pbx_should_respond_with_value 0
2726
+ mock_call.should_receive(:execute).with('Swift', 'Once\\\\, a long\\\\, long time ago\\\\, ...')
2727
+ @speech_engines.cepstral(mock_call, 'Once, a long, long time ago, ...')
2728
+ @output.read.should == "GET VARIABLE \"SWIFT_DTMF\"\n"
2729
+ end
2730
+
2731
+ it "should properly escape double-quotes (for XML) in the TTS string" do
2732
+ mock_call.should_receive(:raw_response).once.with('EXEC MRCPSynth "<speak xmlns=\\\\\"http://www.w3.org/2001/10/synthesis\\\\\" version=\\\\\"1.0\\\\\" xml:lang=\\\\\"en-US\\\\\"> <voice name=\\\\\"Paul\\\\\"> <prosody rate=\\\\\"1.0\\\\\">Howdy, stranger. How are you today?</prosody> </voice> </speak>"').and_return pbx_success_response
2733
+ @speech_engines.unimrcp(mock_call, '<speak xmlns="http://www.w3.org/2001/10/synthesis" version="1.0" xml:lang="en-US"> <voice name="Paul"> <prosody rate="1.0">Howdy, stranger. How are you today?</prosody> </voice> </speak>')
2734
+ end
2735
+
2736
+ context "with barge in digits set" do
2737
+ it "should return the digit when :interruptible = true" do
2738
+ mock_call.should_receive(:execute).once.with('Swift', 'hello', 1, 1).and_return pbx_success_response
2739
+ mock_call.should_receive(:get_variable).once.with('SWIFT_DTMF').and_return ?1
2740
+ @speech_engines.cepstral(mock_call, 'hello', :interruptible => true).should == ?1
2741
+ end
2742
+ end
2743
+ end
2744
+
2745
+ context "with the engine :unimrcp" do
2746
+ it "should execute MRCPSynth" do
2747
+ pbx_should_respond_with_success
2748
+ mock_call.should_receive(:execute).with('MRCPSynth', 'hello').once.and_return pbx_success_response
2749
+ @speech_engines.unimrcp(mock_call, 'hello')
2750
+ end
2751
+
2752
+ context "with barge in digits set" do
2753
+ it "should pass the i option for MRCPSynth" do
2754
+ mock_call.should_receive(:execute).with('MRCPSynth', 'hello', 'i=any').once.and_return pbx_result_response 0
2755
+ @speech_engines.unimrcp(mock_call, 'hello', :interrupt_digits => 'any')
2756
+ end
2757
+ end
2758
+ end
2759
+
2760
+ context "with the engine :tropo" do
2761
+ it "should execute tropo" do
2762
+ pbx_should_respond_with_success
2763
+ response = '200 result=' + {:interpretation => '1'}.to_json
2764
+ mock_call.should_receive(:raw_response).with(/Ask/i, 'hello').once.and_return response
2765
+ @speech_engines.tropo(mock_call, 'hello').should == "1"
2766
+ end
2767
+
2768
+ context "with :interruptible set to false"do
2769
+ it "should pass the :bargein => false option for Tropo Ask" do
2770
+ response = '200 result=' + {:interpretation => '1'}.to_json
2771
+ mock_call.should_receive(:raw_response).with(/Ask/i, 'hello', {:bargein => false}.to_json).once.and_return response
2772
+ @speech_engines.tropo(mock_call, 'hello', :interruptible => false)
2773
+ end
2774
+ end
2775
+ end
2277
2776
 
2777
+ it "properly escapes spoken text" do
2778
+ pending 'What are the escaping needs?'
2779
+ end
2278
2780
  end
2279
2781
 
2280
2782
  describe 'The join command' do