adhearsion 1.1.1 → 1.2.0

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 (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