adhearsion 1.0.1 → 1.0.2

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 (64) hide show
  1. data/.gitignore +10 -0
  2. data/CHANGELOG +8 -0
  3. data/README.markdown +33 -0
  4. data/Rakefile +28 -68
  5. data/adhearsion.gemspec +19 -133
  6. data/app_generators/ahn/templates/Gemfile +0 -4
  7. data/app_generators/ahn/templates/components/disabled/restful_rpc/spec/restful_rpc_spec.rb +4 -16
  8. data/lib/adhearsion/cli.rb +17 -0
  9. data/lib/adhearsion/component_manager/component_tester.rb +1 -3
  10. data/lib/adhearsion/component_manager/spec_framework.rb +4 -10
  11. data/lib/adhearsion/foundation/object.rb +10 -0
  12. data/lib/adhearsion/version.rb +1 -1
  13. data/spec/ahn_command_spec.rb +284 -0
  14. data/spec/component_manager_spec.rb +292 -0
  15. data/spec/constants_spec.rb +8 -0
  16. data/spec/drb_spec.rb +65 -0
  17. data/spec/fixtures/dialplan.rb +3 -0
  18. data/spec/foundation/event_socket_spec.rb +168 -0
  19. data/spec/host_definitions_spec.rb +79 -0
  20. data/spec/initialization_spec.rb +163 -0
  21. data/spec/initializer/configuration_spec.rb +270 -0
  22. data/spec/initializer/loading_spec.rb +149 -0
  23. data/spec/initializer/paths_spec.rb +74 -0
  24. data/spec/logging_spec.rb +86 -0
  25. data/spec/relationship_properties_spec.rb +54 -0
  26. data/spec/silence.rb +10 -0
  27. data/spec/spec_helper.rb +101 -0
  28. data/spec/voip/asterisk/agi_server_spec.rb +473 -0
  29. data/spec/voip/asterisk/ami/ami_spec.rb +549 -0
  30. data/spec/voip/asterisk/ami/lexer/ami_fixtures.yml +30 -0
  31. data/spec/voip/asterisk/ami/lexer/lexer_story +291 -0
  32. data/spec/voip/asterisk/ami/lexer/lexer_story.rb +241 -0
  33. data/spec/voip/asterisk/ami/lexer/story_helper.rb +124 -0
  34. data/spec/voip/asterisk/ami/old_tests.rb +204 -0
  35. data/spec/voip/asterisk/ami/super_manager/super_manager_story +25 -0
  36. data/spec/voip/asterisk/ami/super_manager/super_manager_story.rb +15 -0
  37. data/spec/voip/asterisk/ami/super_manager/super_manager_story_helper.rb +5 -0
  38. data/spec/voip/asterisk/commands_spec.rb +2179 -0
  39. data/spec/voip/asterisk/config_file_generators/agents_spec.rb +251 -0
  40. data/spec/voip/asterisk/config_file_generators/queues_spec.rb +323 -0
  41. data/spec/voip/asterisk/config_file_generators/voicemail_spec.rb +306 -0
  42. data/spec/voip/asterisk/config_manager_spec.rb +127 -0
  43. data/spec/voip/asterisk/menu_command/calculated_match_spec.rb +109 -0
  44. data/spec/voip/asterisk/menu_command/matchers_spec.rb +97 -0
  45. data/spec/voip/call_routing_spec.rb +125 -0
  46. data/spec/voip/dialplan_manager_spec.rb +468 -0
  47. data/spec/voip/dsl/dialing_dsl_spec.rb +270 -0
  48. data/spec/voip/dsl/dispatcher_spec.rb +82 -0
  49. data/spec/voip/dsl/dispatcher_spec_helper.rb +45 -0
  50. data/spec/voip/dsl/parser_spec.rb +69 -0
  51. data/spec/voip/freeswitch/basic_connection_manager_spec.rb +39 -0
  52. data/spec/voip/freeswitch/inbound_connection_manager_spec.rb +39 -0
  53. data/spec/voip/freeswitch/oes_server_spec.rb +9 -0
  54. data/spec/voip/numerical_string_spec.rb +61 -0
  55. data/spec/voip/phone_number_spec.rb +45 -0
  56. data/theatre-spec/dsl_examples/dynamic_stomp.rb +7 -0
  57. data/theatre-spec/dsl_examples/simple_before_call.rb +7 -0
  58. data/theatre-spec/dsl_spec.rb +43 -0
  59. data/theatre-spec/invocation_spec.rb +167 -0
  60. data/theatre-spec/namespace_spec.rb +125 -0
  61. data/theatre-spec/spec_helper.rb +37 -0
  62. data/theatre-spec/spec_helper_spec.rb +28 -0
  63. data/theatre-spec/theatre_class_spec.rb +150 -0
  64. metadata +171 -34
@@ -0,0 +1,124 @@
1
+ require 'rubygems'
2
+ gem 'rspec', "= 1.1.4"
3
+ require 'spec/story'
4
+ require File.dirname(__FILE__) + "/../../../../../lib/adhearsion"
5
+
6
+ RAGEL_FILES = %w[lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rl.rb]
7
+
8
+ def regenerate_ragel
9
+ # Check Ragel version:
10
+ ragel_version_match = `ragel --version`.match(/(\d)\.(\d)+/)
11
+ abort "Could not get Ragel version! Is it installed? You must have at least version 6.3" unless ragel_version_match
12
+ big, small = ragel_version_match.captures.map { |n| n.to_i }
13
+ if big < 6 || (big == 6 && small < 3)
14
+ abort "Please upgrade Ragel! You're on version #{ragel_version_match[0]} and must be on 6.3 or later"
15
+ end
16
+
17
+ RAGEL_FILES.each do |ragel_file|
18
+ ruby_file = ragel_file.sub(".rl.rb", ".rb")
19
+ puts `ragel -n -R #{ragel_file} -o #{ruby_file} 2>&1`
20
+ raise "Failed generating code from Ragel file #{ragel_file}" if $?.to_i.nonzero?
21
+ end
22
+ end
23
+
24
+ Dir.chdir(File.dirname(__FILE__) + "/../../../../..") { regenerate_ragel }
25
+
26
+ require 'adhearsion/voip/asterisk/manager_interface/ami_lexer'
27
+
28
+ FIXTURES = YAML.load_file File.dirname(__FILE__) + "/ami_fixtures.yml"
29
+
30
+ def fixture(path, overrides={})
31
+ path_segments = path.split '/'
32
+ selected_event = path_segments.inject(FIXTURES.clone) do |hash, segment|
33
+ raise ArgumentError, path + " not found!" unless hash
34
+ hash[segment.to_sym]
35
+ end
36
+
37
+ # Downcase all keys in the event and the overrides
38
+ selected_event = selected_event.inject({}) do |downcased_hash,(key,value)|
39
+ downcased_hash[key.to_s.downcase] = value
40
+ downcased_hash
41
+ end
42
+ overrides = overrides.inject({}) do |downcased_hash,(key,value)|
43
+ downcased_hash[key.to_s.downcase] = value
44
+ downcased_hash
45
+ end
46
+
47
+ # Replace variables in the selected_event with any overrides, ignoring case of the key
48
+ keys_with_variables = selected_event.select { |(key, value)| value.kind_of?(Symbol) || value.kind_of?(Hash) }
49
+
50
+ keys_with_variables.each do |original_key, variable_type|
51
+ # Does an override an exist in the supplied list?
52
+ if overriden_pair = overrides.find { |(key, value)| key == original_key }
53
+ # We have an override! Let's replace the template value in the event with the overriden value
54
+ selected_event[original_key] = overriden_pair.last
55
+ else
56
+ # Based on the type, let's generate a placeholder.
57
+ selected_event[original_key] = case variable_type
58
+ when :string
59
+ rand(100000).to_s
60
+ when Hash
61
+ if variable_type.has_key? "one_of"
62
+ # Choose a random possibility
63
+ possibilities = variable_type['one_of']
64
+ possibilities[rand(possibilities.size)]
65
+ else
66
+ raise "Unrecognized Hash fixture property! ##{variable_type.keys.to_sentence}"
67
+ end
68
+ else
69
+ raise "Unrecognized fixture variable type #{variable_type}!"
70
+ end
71
+ end
72
+
73
+ end
74
+ hash_to_stanza(selected_event).tap do |event|
75
+ selected_event.each_pair do |key, value|
76
+ event.meta_def(key) { value }
77
+ end
78
+ end
79
+ end
80
+
81
+ def hash_to_stanza(hash)
82
+ ordered_hash = hash.to_a
83
+ starter = hash.find { |(key, value)| key.strip =~ /^(Response|Action)$/i }
84
+ ordered_hash.unshift ordered_hash.delete(starter) if starter
85
+ ordered_hash.inject(String.new) do |stanza,(key, value)|
86
+ stanza + "#{key}: #{value}\r\n"
87
+ end + "\r\n"
88
+ end
89
+
90
+ def format_newlines(string)
91
+ # HOLY FUCK THIS IS UGLY
92
+ tmp_replacement = random_string
93
+ string.gsub("\r\n", tmp_replacement).
94
+ gsub("\n", "\r\n").
95
+ gsub(tmp_replacement, "\r\n")
96
+ end
97
+
98
+ def random_string
99
+ (rand(1_000_000_000_000) + 1_000_000_000).to_s
100
+ end
101
+
102
+ def follows_body_text(name)
103
+ case name
104
+ when "ragel_description"
105
+ "Ragel is a software development tool that allows user actions to
106
+ be embedded into the transitions of a regular expression's corresponding state machine,
107
+ eliminating the need to switch from the regular expression engine and user code execution
108
+ environment and back again."
109
+ when "with_colon_after_first_line"
110
+ "Host Username Refresh State Reg.Time \r\nlax.teliax.net:5060 jicksta 105 Registered Tue, 11 Nov 2008 02:29:55"
111
+ when "show_channels_from_wayne"
112
+ "Channel Location State Application(Data)\r\n0 active channels\r\n0 active calls"
113
+ when "empty_string"
114
+ ""
115
+ end
116
+
117
+ end
118
+
119
+ def syntax_error_data(name)
120
+ case name
121
+ when "immediate_packet_with_colon"
122
+ "!IJ@MHY:!&@B*!B @ ! @^! @ !@ !\r!@ ! @ !@ ! !!m, \n\\n\n"
123
+ end
124
+ end
@@ -0,0 +1,204 @@
1
+ =begin
2
+ describe "Connecting via AMI" do
3
+ it "should raise an exception if the password was invalid" do
4
+ host, port = "localhost", 5038
5
+ ami = Adhearsion::VoIP::Asterisk::AMI.new "admin", "bad_password", "localhost", :port => port
6
+
7
+ ami_server = AmiServer.new
8
+ flexmock(TCPSocket).should_receive(:new).once.with(host, port).and_return(ami_server)
9
+ flexmock(IO).should_receive(:select).at_least.once.with([ami_server], nil, nil, 1.0).and_return(true)
10
+
11
+ the_following_code do
12
+ ami.connect!
13
+ end.should raise_error Adhearsion::VoIP::Asterisk::AMI::AuthenticationFailedException
14
+ ami.disconnect!
15
+ end
16
+
17
+ it "should discover its own permissions and make them available as connection attributes"
18
+
19
+ it "should find the Asterisk version when connecting" do
20
+ host, port = "localhost", 5038
21
+ ami = Adhearsion::VoIP::Asterisk::AMI.new "admin", "password", "localhost", :port => port
22
+
23
+ ami_server = AmiServer.new
24
+ flexmock(TCPSocket).should_receive(:new).once.with(host, port).and_return(ami_server)
25
+ flexmock(IO).should_receive(:select).at_least.once.with([ami_server], nil, nil, 1.0).and_return(true)
26
+
27
+ ami.connect!
28
+ ami.version.should == "1.0"
29
+ ami.disconnect!
30
+ end
31
+ end
32
+
33
+ describe "The AMI command interface" do
34
+
35
+ before do
36
+ host, port = "localhost", 5038
37
+ @ami = Adhearsion::VoIP::Asterisk::AMI.new "admin", "password", host, :port => port, :events => false
38
+
39
+ ami_server = AmiServer.new
40
+ flexmock(TCPSocket).should_receive(:new).once.with(host, port).and_return(ami_server)
41
+ flexmock(IO).should_receive(:select).at_least.once.with([ami_server], nil, nil, 1.0).and_return(true)
42
+
43
+ @ami.connect!
44
+ end
45
+
46
+ after do
47
+ @ami.disconnect!
48
+ end
49
+
50
+ it "should respond to an immediate command" do
51
+ resp = @ami.queues
52
+ resp[0][:raw].should be_a_kind_of String
53
+ end
54
+
55
+ it "should respond to a follows command" do
56
+ resp = @ami.command :Command => "show channels"
57
+ resp[0][:raw].should be_a_kind_of String
58
+ end
59
+
60
+ it "should respond to a DBGet for a non-existent key with an exception" do
61
+ the_following_code do
62
+ resp = @ami.dbget :Family => "none", :Key => "somekey"
63
+ end.should raise_error Adhearsion::VoIP::Asterisk::AMI::ActionError
64
+ end
65
+
66
+ it "should respond to a DBGet for a key with an event" do
67
+ resp = @ami.dbput :Family => "none", :Key => "somekey", :Val => 5
68
+ resp = @ami.dbget :Family => "none", :Key => "somekey"
69
+ resp[0]['Val'].should == "5"
70
+ end
71
+
72
+ it "should respond to a command that generates follows event(s)" do
73
+ resp = @ami.queuestatus
74
+ resp[0]['Queue'].should == "default"
75
+ end
76
+
77
+ it "should show usage for an improper follows command" do
78
+ resp = @ami.command :Command => "meetme list"
79
+ resp[0][:raw].should be_a_kind_of String
80
+ end
81
+
82
+ it "should respond to a synchronous originate"
83
+ it "should respond to an asynchronous originate"
84
+
85
+ it "should define events() as a private method to prevent turning events on or off" do
86
+ @ami.private_methods.include?("events").should be true
87
+ end
88
+
89
+ it "should raise an exception when Asterisk doesn't recognize a command" do
90
+ the_following_code {
91
+ @ami.this_command_does_not_exist_kthx
92
+ }.should raise_error Adhearsion::VoIP::Asterisk::AMI::ActionError
93
+
94
+ end
95
+
96
+ end
97
+
98
+ describe 'AMI#originate' do
99
+ include AmiCommandTestHelper
100
+ it "should pass the arguments to execute_ami_command! with the options given" do
101
+ ami = new_ami_instance
102
+ options = { :channel => "ohai_lolz", :application => "Echo" }
103
+ flexmock(ami).should_receive(:execute_ami_command!).with(:originate, options).once
104
+ ami.originate options
105
+ end
106
+
107
+ it "should rename the :caller_id Hash key to :callerid" do
108
+ ami, caller_id = new_ami_instance, "Jay"
109
+ options = { :channel => "ohai_lolz", :application => "Echo"}
110
+ flexmock(ami).should_receive(:execute_ami_command!).with(:originate, options.merge(:callerid => caller_id)).once
111
+ ami.originate options.merge(:caller_id => caller_id)
112
+ end
113
+
114
+ end
115
+
116
+ describe 'AMI#call_and_exec' do
117
+ include AmiCommandTestHelper
118
+ it "should execute originate properly with the minimum arguments" do
119
+ number, app = "12224446666", "Echo"
120
+
121
+ ami = flexmock new_ami_instance
122
+ ami.should_receive(:originate).once.with(:channel => number, :application => app).and_return true
123
+ ami.call_and_exec number, app
124
+ end
125
+
126
+ end
127
+
128
+ describe 'AMI#introduce' do
129
+
130
+ include AmiCommandTestHelper
131
+
132
+ it "should execute origiante properly (when :caller_id and :options aren't specified)" do
133
+ caller, callee, caller_id = "SIP/12224446666@trunk", "SIP/12224447777@trunk", "Jay Phillips"
134
+
135
+ correct_args = {:application => "Dial", :channel => caller, :data => callee, :caller_id => "Jay"}
136
+ ami = flexmock new_ami_instance
137
+ ami.should_receive(:originate).once.with(correct_args).and_return(true)
138
+ ami.introduce caller, callee, :caller_id => "Jay"
139
+ end
140
+
141
+ end
142
+
143
+ describe "The manager proxy" do
144
+ before do
145
+ host, port = "localhost", 5038
146
+ @ami = Adhearsion::VoIP::Asterisk::AMI.new "admin", "password", "localhost", :port => port, :events => false
147
+
148
+ ami_server = AmiServer.new
149
+ flexmock(TCPSocket).should_receive(:new).once.with(host, port).and_return(ami_server)
150
+ flexmock(IO).should_receive(:select).at_least.once.with([ami_server], nil, nil, 1.0).and_return(true)
151
+
152
+ @ami.connect!
153
+ @door = DRb.start_service "druby://127.0.0.1:9050", Adhearsion::DrbDoor.instance
154
+ end
155
+
156
+ it "should accept a command" do
157
+ client = DRbObject.new nil, DRb.uri
158
+ client.proxy.ping
159
+ end
160
+
161
+ after do
162
+ DRb.stop_service
163
+ @ami.disconnect!
164
+ end
165
+ end
166
+
167
+ describe "The command-sending interface" do
168
+ it "should raise an exception if permission was denied"
169
+ it "should allow variables to be specified as a Hash"
170
+ end
171
+
172
+ describe "Sent arbitrary AMI commands" do
173
+
174
+ it "should allow a convenient way of parsing by event name"
175
+ it "should return Hash arguments"
176
+
177
+ it "should recognize its subclasses"
178
+ it "should send events to all of its subclasses"
179
+ it "should catch action names with method_missing() and format them properly"
180
+
181
+ it "should raise an exception if permission was denied"
182
+ end
183
+
184
+ describe "AMI Packets" do
185
+ it "A Packet should not be an error" do
186
+ Adhearsion::VoIP::Asterisk::AMI::Packet.new.error?.should.be false
187
+ end
188
+ it "An ErrorPacket should be an error" do
189
+ Adhearsion::VoIP::Asterisk::AMI::ErrorPacket.new.error?.should.be true
190
+ end
191
+ end
192
+
193
+ BEGIN {
194
+ module AmiCommandTestHelper
195
+ def new_ami_instance
196
+ # TODO. mock everything out here
197
+ Adhearsion::VoIP::Asterisk::AMI.new("user","pass").tap do |ami|
198
+ flexmock(ami).should_receive(:connect!).and_return(true)
199
+ end
200
+ end
201
+
202
+ end
203
+ }
204
+ =end
@@ -0,0 +1,25 @@
1
+ Story: Building and sending high-level AMI actions with SuperManager
2
+
3
+ As an Adhearsion user
4
+ I want higher-level, intuitive abstractions of the AMI protocol
5
+ So that I can control Asterisk better
6
+
7
+ TODO:
8
+ - Migrate to cucumber
9
+
10
+ Scenario: Sending a Ping
11
+
12
+ Given a new SuperManager
13
+ And a "Ping" action
14
+
15
+ When the data it sent
16
+ And the server responds with
17
+
18
+ Then the data sent should be well-formed
19
+ And the 1st action should have the action name "Ping"
20
+ And the 1st action should have an ActionID
21
+ And the 1st action should have exactly 2 headers
22
+
23
+ Scenario: An action with many
24
+
25
+ Scenario: A Channel object
@@ -0,0 +1,15 @@
1
+ require 'rubygems'
2
+ require 'cucumber'
3
+
4
+ Given "etwas" do
5
+
6
+ end
7
+
8
+ When "etwas2" do
9
+
10
+ end
11
+
12
+ Then "etwas3" do
13
+
14
+ end
15
+
@@ -0,0 +1,5 @@
1
+ require 'rubygems'
2
+ gem 'rspec', "= 1.1.4"
3
+ require 'spec/story'
4
+
5
+ require File.dirname(__FILE__) + "/super_manager_story"
@@ -0,0 +1,2179 @@
1
+ require 'spec_helper'
2
+ require 'adhearsion/voip/menu_state_machine/menu_class'
3
+ require 'adhearsion/voip/menu_state_machine/menu_builder'
4
+
5
+ module DialplanCommandTestHelpers
6
+ def self.included(test_case)
7
+ test_case.send(:attr_reader, :mock_call, :input, :output)
8
+
9
+ test_case.before do
10
+ Adhearsion::Configuration.configure { |config| config.enable_asterisk() }
11
+
12
+ @input = MockSocket.new
13
+ @output = MockSocket.new
14
+ @mock_call = Object.new
15
+ @mock_call.metaclass.send(:attr_reader, :call)
16
+ mock_call.extend(Adhearsion::VoIP::Asterisk::Commands)
17
+ flexmock(mock_call) do |call|
18
+ call.should_receive(:from_pbx).and_return(input)
19
+ call.should_receive(:to_pbx).and_return(output)
20
+ end
21
+ end
22
+ end
23
+
24
+ class MockSocket
25
+
26
+ def print(message)
27
+ messages << message
28
+ end
29
+
30
+ def puts(message)
31
+ messages << message.chomp + "\n"
32
+ end
33
+
34
+ def read
35
+ messages.shift
36
+ end
37
+
38
+ def gets
39
+ read
40
+ end
41
+
42
+ def messages
43
+ @messages ||= []
44
+ end
45
+ end
46
+
47
+
48
+ private
49
+
50
+ def should_pass_control_to_a_context_that_throws(symbol, &block)
51
+ did_the_rescue_block_get_executed = false
52
+ begin
53
+ yield
54
+ rescue Adhearsion::VoIP::DSL::Dialplan::ControlPassingException => cpe
55
+ did_the_rescue_block_get_executed = true
56
+ cpe.target.should throw_symbol symbol
57
+ rescue => e
58
+ did_the_rescue_block_get_executed = true
59
+ raise e
60
+ ensure
61
+ did_the_rescue_block_get_executed.should be true
62
+ end
63
+ end
64
+
65
+ def should_throw(sym=nil,&block)
66
+ block.should throw_symbol(*[sym].compact)
67
+ end
68
+
69
+ def mock_route_calculation_with(*definitions)
70
+ flexmock(Adhearsion::VoIP::DSL::DialingDSL).should_receive(:calculate_routes_for).and_return(definitions)
71
+ end
72
+
73
+ def pbx_should_have_been_sent(message)
74
+ output.gets.chomp.should == message
75
+ end
76
+
77
+ def pbx_should_respond_with(message)
78
+ input.print message
79
+ end
80
+
81
+ def pbx_should_respond_with_digits(string_of_digits)
82
+ pbx_should_respond_with "200 result=#{string_of_digits}"
83
+ end
84
+
85
+ def pbx_should_respond_with_digits_and_timeout(string_of_digits)
86
+ pbx_should_respond_with "200 result=#{string_of_digits} (timeout)"
87
+ end
88
+
89
+ def pbx_should_respond_to_timeout(timeout)
90
+ pbx_should_respond_with "200 result=#{timeout}"
91
+ end
92
+
93
+ def pbx_should_respond_with_value(value)
94
+ pbx_should_respond_with "200 result=1 (#{value})"
95
+ end
96
+
97
+ def pbx_should_respond_with_success(success_code = nil)
98
+ pbx_should_respond_with pbx_success_response(success_code)
99
+ end
100
+ alias does_not_read_data_back pbx_should_respond_with_success
101
+
102
+ def pbx_should_respond_with_failure(failure_code = nil)
103
+ pbx_should_respond_with(pbx_failure_response(failure_code))
104
+ end
105
+
106
+ def pbx_should_respond_with_successful_background_response(digit=0)
107
+ pbx_should_respond_with_success digit.kind_of?(String) ? digit[0] : digit
108
+ end
109
+
110
+ def pbx_should_respond_with_a_wait_for_digit_timeout
111
+ pbx_should_respond_with_successful_background_response 0
112
+ end
113
+
114
+ def pbx_success_response(success_code = nil)
115
+ "200 result=#{success_code || default_success_code}"
116
+ end
117
+
118
+ def default_success_code
119
+ '1'
120
+ end
121
+
122
+ def pbx_failure_response(failure_code = nil)
123
+ "200 result=#{failure_code || default_failure_code}"
124
+ end
125
+
126
+ def default_failure_code
127
+ '-1'
128
+ end
129
+
130
+ def output_stream_matches(pattern)
131
+ output.gets.should match pattern
132
+ end
133
+
134
+ module OutputStreamMatchers
135
+ def pbx_was_asked_to_play(*audio_files)
136
+ audio_files.each do |audio_file|
137
+ output_stream_matches(/playback "#{audio_file}"/)
138
+ end
139
+ end
140
+
141
+ def pbx_was_asked_to_play_number(number)
142
+ output_stream_matches(/saynumber "#{number}"/)
143
+ end
144
+
145
+ def pbx_was_asked_to_play_time(number)
146
+ output_stream_matches(/sayunixtime "#{number}"/)
147
+ end
148
+
149
+ def pbx_was_asked_to_execute(application, *options)
150
+ output_stream_matches(/exec saydigits "#{options.join('|')}"/i)
151
+ end
152
+ end
153
+ include OutputStreamMatchers
154
+
155
+ def assert_success(response)
156
+ response.should == pbx_success_response
157
+ end
158
+
159
+ end
160
+
161
+
162
+ module MenuBuilderTestHelper
163
+ def builder_should_match_with_these_quantities_of_calculated_matches(checks)
164
+ checks.each do |check,hash|
165
+ hash.each_pair do |method_name,intended_quantity|
166
+ builder.calculate_matches_for(check).send(method_name).should == intended_quantity
167
+ end
168
+ end
169
+ end
170
+ end
171
+
172
+ module MenuTestHelper
173
+
174
+ def pbx_should_send_digits(*digits)
175
+ digits.each do |digit|
176
+ digit = nil if digit == :timeout
177
+ mock_call.should_receive(:interruptible_play).once.and_return(digit)
178
+ end
179
+ end
180
+ end
181
+
182
+ module ConfirmationManagerTestHelper
183
+ def encode_hash(hash)
184
+ Adhearsion::DialPlan::ConfirmationManager.encode_hash_for_dial_macro_argument(hash)
185
+ end
186
+ end
187
+
188
+ describe 'Asterisk VoIP Commands' do
189
+ include DialplanCommandTestHelpers
190
+
191
+ it "a call can write back to the PBX" do
192
+ message = 'oh hai'
193
+ mock_call.write message
194
+ pbx_should_have_been_sent message
195
+ end
196
+ end
197
+ describe 'hangup command' do
198
+ include DialplanCommandTestHelpers
199
+
200
+ it "hanging up a call succesfully writes HANGUP back to the PBX and a success resopnse is returned" do
201
+ pbx_should_respond_with_success
202
+ response = mock_call.hangup
203
+ pbx_should_have_been_sent 'HANGUP'
204
+ response.should == pbx_success_response
205
+ end
206
+ end
207
+
208
+ describe 'interruptible_play command' do
209
+
210
+ include DialplanCommandTestHelpers
211
+
212
+ it 'should return a string for the digit that was pressed' do
213
+ digits = %w{0 1 # * 9}.map{|c| c.ord}
214
+ file = "file_doesnt_matter"
215
+ digits.each { |digit| pbx_should_respond_with_success digit }
216
+ digits.map { |digit| mock_call.send(:interruptible_play, file) }.should == digits.map(&:chr)
217
+ end
218
+
219
+ it "should return nil if no digit was pressed" do
220
+ pbx_should_respond_with_success 0
221
+ mock_call.send(:interruptible_play, 'foobar').should be nil
222
+ end
223
+
224
+ it "should play a series of files, stopping the series when a digit is played" do
225
+ stubbed_keypad_input = [0, 0, ?3.ord]
226
+ stubbed_keypad_input.each do |digit|
227
+ pbx_should_respond_with_success digit
228
+ end
229
+
230
+ files = (100..105).map(&:to_s)
231
+ mock_call.send(:interruptible_play, *files).should == '3'
232
+ end
233
+
234
+ end
235
+
236
+ describe 'wait_for_digit command' do
237
+
238
+ include DialplanCommandTestHelpers
239
+
240
+ it 'should return a string for the digit that was pressed' do
241
+ digits = %w{0 1 # * 9}.map{|c| c.ord}
242
+ digits.each { |digit| pbx_should_respond_with_success digit }
243
+ digits.map { |digit| mock_call.send(:wait_for_digit) }.should == digits.map(&:chr)
244
+ end
245
+
246
+ it "the timeout given must be converted to milliseconds" do
247
+ pbx_should_respond_with_success 0
248
+ mock_call.send(:wait_for_digit, 1)
249
+ output.messages.first.chomp.ends_with?('"1000"').should be true
250
+ end
251
+ end
252
+
253
+ describe 'answer' do
254
+ include DialplanCommandTestHelpers
255
+
256
+ it 'should send ANSWER over the AGI socket' do
257
+ does_not_read_data_back
258
+ mock_call.answer
259
+ pbx_should_have_been_sent 'ANSWER'
260
+ end
261
+
262
+ end
263
+
264
+ describe 'execute' do
265
+ include DialplanCommandTestHelpers
266
+
267
+ it 'execute writes exec and app name to the PBX' do
268
+ pbx_should_respond_with_success
269
+ assert_success mock_call.execute(:foo)
270
+ pbx_should_have_been_sent 'EXEC foo ""'
271
+ end
272
+
273
+ it 'execute returns false if the command was not executed successfully by the PBX' do
274
+ pbx_should_respond_with_failure
275
+ mock_call.execute(:foo).should_not be true
276
+ end
277
+
278
+ it 'execute can accept arguments after the app name which get translated into pipe-delimited arguments to the PBX' do
279
+ pbx_should_respond_with_success
280
+ mock_call.execute :foo, 'bar', 'baz', 'hi'
281
+ pbx_should_have_been_sent 'EXEC foo "bar"|"baz"|"hi"'
282
+ end
283
+
284
+ it "should raise a Hangup exception when nil is returned when reading a command from Asterisk" do
285
+ flexmock(input).should_receive(:gets).once.and_return nil
286
+ the_following_code {
287
+ mock_call.execute :foo, "bar"
288
+ }.should raise_error Adhearsion::Hangup
289
+ end
290
+
291
+ it "should raise a ArgumentError if given a null byte in the arguments" do
292
+ the_following_code {
293
+ mock_call.execute :foo, "bar\0"
294
+ }.should raise_error ArgumentError
295
+ end
296
+
297
+ end
298
+
299
+ describe 'play command' do
300
+ include DialplanCommandTestHelpers
301
+
302
+ it 'passing a single string to play results in the playback application being executed with that file name on the PBX' do
303
+ pbx_should_respond_with_success
304
+ audio_file = "cents-per-minute"
305
+ mock_call.play audio_file
306
+ pbx_was_asked_to_play audio_file
307
+ end
308
+
309
+ it 'multiple strings can be passed to play, causing multiple playback commands to be issued' do
310
+ 2.times do
311
+ pbx_should_respond_with_success
312
+ end
313
+ audio_files = ["cents-per-minute", 'o-hai']
314
+ mock_call.play(*audio_files)
315
+ pbx_was_asked_to_play(*audio_files)
316
+ end
317
+
318
+ it 'If a number is passed to play(), the saynumber application is executed with the number as an argument' do
319
+ pbx_should_respond_with_success
320
+ mock_call.play 123
321
+ pbx_was_asked_to_play_number(123)
322
+ end
323
+
324
+ it 'if a string representation of a number is passed to play(), the saynumber application is executed with the number as an argument' do
325
+ pbx_should_respond_with_success
326
+ mock_call.play '123'
327
+ pbx_was_asked_to_play_number(123)
328
+ end
329
+
330
+ it 'If a Time is passed to play(), the SayUnixTime application will be executed with the time since the UNIX epoch in seconds as an argument' do
331
+ time = Time.parse("12/5/2000")
332
+ pbx_should_respond_with_success
333
+ mock_call.play time
334
+ pbx_was_asked_to_play_time(time.to_i)
335
+ end
336
+
337
+ it 'If a Date is passed to play(), the SayUnixTime application will be executed with the date passed in' do
338
+ date = Date.parse('2011-01-23')
339
+ mock_call.should_receive(:execute).once.with(:sayunixtime, date.to_time.to_i, "",'BdY').and_return('200')
340
+ mock_call.play date
341
+ end
342
+
343
+ it 'If a Date or Time is passed to play_time(), the SayUnixTime application will be executed with the date and format passed in' do
344
+ date, format = Date.parse('2011-01-23'), 'ABdY'
345
+ mock_call.should_receive(:execute).once.with(:sayunixtime, date.to_time.to_i, "",format).and_return('200')
346
+ mock_call.play_time date, :format => format
347
+
348
+ time, format = Time.at(875121313), 'BdY \'digits/at\' IMp'
349
+ mock_call.should_receive(:execute).once.with(:sayunixtime, time.to_i, "",format).and_return('200')
350
+ mock_call.play_time time, :format => format
351
+ end
352
+
353
+ it 'If a Time object is passed to play_time, the SayUnixTime application will be executed with the default parameters' do
354
+ time = Time.at(875121313)
355
+ mock_call.should_receive(:execute).once.with(:sayunixtime, time.to_i, "",'').and_return('200')
356
+ mock_call.play_time time
357
+ end
358
+
359
+ it 'If an object other than Time, DateTime, or Date is passed to play_time false will be returned' do
360
+ non_time = 'blah'
361
+ mock_call.play_time(non_time).should be false
362
+ end
363
+
364
+ it 'If an array containing a Date/DateTime/Time object and a hash is passed to play(), the SayUnixTime application will be executed with the object passed in with the specified format and timezone' do
365
+ date, format = Date.parse('2011-01-23'), 'ABdY'
366
+ mock_call.should_receive(:execute).once.with(:sayunixtime, date.to_time.to_i, "",format).and_return('200')
367
+ mock_call.play [date, {:format => format}]
368
+
369
+ time, timezone = Time.at(1295843084), 'US/Eastern'
370
+ mock_call.should_receive(:execute).once.with(:sayunixtime, time.to_i, timezone,'').and_return('200')
371
+ mock_call.play [time, {:timezone => timezone}]
372
+
373
+ time, timezone, format = Time.at(1295843084), 'US/Eastern', 'ABdY \'digits/at\' IMp'
374
+ mock_call.should_receive(:execute).once.with(:sayunixtime, time.to_i, timezone,format).and_return('200')
375
+ mock_call.play [time, {:timezone => timezone, :format => format}]
376
+ end
377
+
378
+ it 'If a string matching dollars and (optionally) cents is passed to play(), a series of command will be executed to read the dollar amount', :ignore => true do
379
+ #TODO: I think we should not have this be part of play(). Too much functionality in one method. Too much overloading. When we want to support multiple
380
+ # currencies, it'll be completely unwieldy. I'd suggest play_currency as a separate method. - Chad
381
+ end
382
+ end
383
+
384
+ describe 'input command' do
385
+
386
+ include DialplanCommandTestHelpers
387
+
388
+ # pbx_should_respond_with_successful_background_response
389
+ # pbx_should_respond_with_a_wait_for_digit_timeout
390
+
391
+ it 'should raise an error when the number of digits expected is -1 (this is deprecated behavior)' do
392
+ the_following_code {
393
+ mock_call.input(-1)
394
+ }.should raise_error ArgumentError
395
+ end
396
+
397
+ it 'input() calls wait_for_digit the specified number of times (when no sound files are given)' do
398
+ # mock_call.should_receive(:interruptible_play).never
399
+ mock_call.should_receive(:wait_for_digit).times(4).and_return('1', '2', '3', '4')
400
+ mock_call.input(4).should == '1234'
401
+ end
402
+
403
+ it 'should execute wait_for_digit if no digit is pressed during interruptible_play' do
404
+ sound_files = %w[one two three]
405
+ mock_call.should_receive(:interruptible_play).once.with(*sound_files).and_return nil
406
+ mock_call.should_receive(:wait_for_digit).once.and_throw :digit_request
407
+ should_throw(:digit_request) { mock_call.input(10, :play => sound_files) }
408
+ end
409
+
410
+ it 'should default the :accept_key to "#" when unlimited digits are to be collected' do
411
+ mock_call.should_receive(:wait_for_digit).times(2).and_return '*', '#'
412
+ mock_call.input.should == '*'
413
+ end
414
+
415
+ it 'should raise an exception when unlimited digits are to be collected and :accept_key => false' do
416
+ flexstub(mock_call).should_receive(:read).and_return
417
+ the_following_code {
418
+ mock_call.input(:accept_key => false)
419
+ }.should raise_error ArgumentError
420
+ end
421
+
422
+ it 'when :accept_key is false and input() is collecting a finite number of digits, it should allow all DTMFs' do
423
+ all_digits = %w[0 1 2 3 # * 4 5 6 7 8 9]
424
+ mock_call.should_receive(:wait_for_digit).times(all_digits.size).and_return(*all_digits)
425
+ the_following_code {
426
+ mock_call.input(all_digits.size, :accept_key => false)
427
+ }.should_not raise_error ArgumentError
428
+ end
429
+
430
+ it 'passes wait_for_digit the :timeout option when one is given' do
431
+ mock_call.should_receive(:interruptible_play).never
432
+ mock_call.should_receive(:wait_for_digit).twice.and_return '1', '2'
433
+ mock_call.input(2, :timeout => 1.minute).should == '12'
434
+ end
435
+
436
+ it 'executes interruptible_play() with all of the files given to :play' do
437
+ sound_files = %w[foo bar qaz]
438
+ mock_call.should_receive(:interruptible_play).once.with(*sound_files).and_return '#'
439
+ mock_call.should_receive(:wait_for_digit).once.and_return '*'
440
+ mock_call.input(2, :play => sound_files).should == '#*'
441
+ end
442
+
443
+ it 'pressing the terminating key before any other digits returns an empty string' do
444
+ mock_call.should_receive(:wait_for_digit).once.and_return '*'
445
+ mock_call.input(:accept_key => '*').should == ''
446
+ end
447
+
448
+ it 'should execute wait_for_digit first if no sound files are given' do
449
+ mock_call.should_receive(:interruptible_play).never
450
+ mock_call.should_receive(:wait_for_digit).once.and_throw :digit_request
451
+ should_throw(:digit_request) { mock_call.input(1) }
452
+ end
453
+
454
+ it "Input timing out when digits are pressed returns only the collected digits" do
455
+ mock_call.should_receive(:wait_for_digit).twice.and_return '5', nil
456
+ mock_call.input(9, :timeout => 1.day).should == '5'
457
+ end
458
+
459
+ end
460
+
461
+ describe "The variable() command" do
462
+
463
+ include DialplanCommandTestHelpers
464
+
465
+ it "should call set_variable for every Hash-key argument given" do
466
+ args = [:ohai, "ur_home_erly"]
467
+ mock_call.should_receive(:set_variable).once.with(*args)
468
+ mock_call.variable Hash[*args]
469
+ end
470
+
471
+ it "should call set_variable for every Hash-key argument given" do
472
+ many_args = { :a => :b, :c => :d, :e => :f, :g => :h}
473
+ mock_call.should_receive(:set_variable).times(many_args.size)
474
+ mock_call.variable many_args
475
+ end
476
+
477
+ it "should call get_variable for every String given" do
478
+ variables = ["foo", "bar", :qaz, :qwerty, :baz]
479
+ variables.each do |var|
480
+ mock_call.should_receive(:get_variable).once.with(var).and_return("X")
481
+ end
482
+ mock_call.variable(*variables)
483
+ end
484
+
485
+ it "should NOT return an Array when just one arg is given" do
486
+ mock_call.should_receive(:get_variable).once.and_return "lol"
487
+ mock_call.variable(:foo).should_not be_a_kind_of Array
488
+ end
489
+
490
+ it "should raise an ArgumentError when a Hash and normal args are given" do
491
+ the_following_code {
492
+ mock_call.variable 5,4,3,2,1, :foo => :bar
493
+ }.should raise_error ArgumentError
494
+ end
495
+
496
+ end
497
+
498
+ describe "the set_variable method" do
499
+
500
+ include DialplanCommandTestHelpers
501
+
502
+ it "variables and values are properly quoted" do
503
+ mock_call.should_receive(:raw_response).once.with 'SET VARIABLE "foo" "i can \\" has ruby?"'
504
+ mock_call.set_variable 'foo', 'i can " has ruby?'
505
+ end
506
+
507
+ it "to_s() is effectively called on both the key and the value" do
508
+ mock_call.should_receive(:raw_response).once.with 'SET VARIABLE "QAZ" "QWERTY"'
509
+ mock_call.set_variable :QAZ, :QWERTY
510
+ end
511
+
512
+ end
513
+
514
+ describe "the sip_add_header method" do
515
+ include DialplanCommandTestHelpers
516
+
517
+ it "values are properly quoted" do
518
+ mock_call.should_receive(:raw_response).once.with 'EXEC SIPAddHeader "x-ahn-header: rubyrox"'
519
+ mock_call.sip_add_header "x-ahn-header", "rubyrox"
520
+ end
521
+ end
522
+
523
+ describe "the sip_get_header method" do
524
+ include DialplanCommandTestHelpers
525
+
526
+ it "values are properly quoted" do
527
+ mock_call.should_receive(:raw_response).once.with 'GET VARIABLE "SIP_HEADER(x-ahn-header)"'
528
+ mock_call.sip_get_header "x-ahn-header"
529
+ end
530
+
531
+ it "values are properly quoted with aliased method" do
532
+ mock_call.should_receive(:raw_response).once.with 'GET VARIABLE "SIP_HEADER(x-ahn-header)"'
533
+ mock_call.sip_header "x-ahn-header"
534
+ end
535
+ end
536
+
537
+ describe 'the voicemail command' do
538
+
539
+ include DialplanCommandTestHelpers
540
+
541
+ it 'should not send the context name when none is given' do
542
+ mailbox_number = 123
543
+ mock_call.should_receive(:execute).once.with('voicemail', 123, '').and_throw :sent_voicemail!
544
+ should_throw(:sent_voicemail!) { mock_call.voicemail 123 }
545
+ end
546
+
547
+ it 'should send the context name when one is given' do
548
+ mailbox_number, context_name = 333, 'doesntmatter'
549
+ mock_call.should_receive(:execute).once.with('voicemail', "#{mailbox_number}@#{context_name}", '').and_throw :sent_voicemail!
550
+ should_throw(:sent_voicemail!) { mock_call.voicemail(context_name => mailbox_number) }
551
+ end
552
+
553
+ it 'should pass in the s option if :skip => true' do
554
+ mailbox_number = '012'
555
+ mock_call.should_receive(:execute).once.with('voicemail', mailbox_number, 's').and_throw :sent_voicemail!
556
+ should_throw(:sent_voicemail!) { mock_call.voicemail(mailbox_number, :skip => true) }
557
+ end
558
+
559
+ it 'should combine mailbox numbers with the context name given when both are given' do
560
+ does_not_read_data_back
561
+ context = "lolcats"
562
+ mailboxes = [1,2,3,4,5]
563
+ mailboxes_with_context = mailboxes.map { |mailbox| "#{mailbox}@#{context}"}
564
+ mock_call.should_receive(:execute).once.with('voicemail', mailboxes_with_context.join('&'), '')
565
+ mock_call.voicemail context => mailboxes
566
+ end
567
+
568
+ it 'should raise an argument error if the mailbox number is not numerical' do
569
+ the_following_code {
570
+ mock_call.voicemail :foo => "bar"
571
+ }.should raise_error ArgumentError
572
+ end
573
+
574
+ it 'should raise an argument error if too many arguments are supplied' do
575
+ the_following_code {
576
+ mock_call.voicemail "wtfisthisargument", :context_name => 123, :greeting => :busy
577
+ }.should raise_error ArgumentError
578
+ end
579
+
580
+ it 'should raise an ArgumentError if multiple context names are given' do
581
+ the_following_code {
582
+ mock_call.voicemail :one => [1,2,3], :two => [11,22,33]
583
+ }.should raise_error ArgumentError
584
+ end
585
+
586
+ it "should raise an ArgumentError when the :greeting value isn't recognized" do
587
+ the_following_code {
588
+ mock_call.voicemail :context_name => 123, :greeting => :zomgz
589
+ }.should raise_error ArgumentError
590
+ end
591
+
592
+ it 'should pass in the u option if :greeting => :unavailable' do
593
+ mailbox_number = '776'
594
+ mock_call.should_receive(:execute).once.with('voicemail', mailbox_number, 'u').and_throw :sent_voicemail!
595
+ should_throw(:sent_voicemail!) { mock_call.voicemail(mailbox_number, :greeting => :unavailable) }
596
+ end
597
+
598
+ it 'should pass in both the skip and greeting options if both are supplied' do
599
+ mailbox_number = '4'
600
+ mock_call.should_receive(:execute).once.with('voicemail', mailbox_number, 'u').and_throw :sent_voicemail!
601
+ should_throw(:sent_voicemail!) { mock_call.voicemail(mailbox_number, :greeting => :unavailable) }
602
+ end
603
+
604
+ it 'should raise an ArgumentError if mailbox_number is blank?()' do
605
+ the_following_code {
606
+ mock_call.voicemail ''
607
+ }.should raise_error ArgumentError
608
+
609
+ the_following_code {
610
+ mock_call.voicemail nil
611
+ }.should raise_error ArgumentError
612
+ end
613
+
614
+ it 'should pass in the b option if :gretting => :busy' do
615
+ mailbox_number = '1'
616
+ mock_call.should_receive(:execute).once.with('voicemail', mailbox_number, 'b').and_throw :sent_voicemail!
617
+ should_throw(:sent_voicemail!) { mock_call.voicemail(mailbox_number, :greeting => :busy) }
618
+ end
619
+
620
+ it 'should return true if VMSTATUS == "SUCCESS"' do
621
+ mock_call.should_receive(:execute).once
622
+ mock_call.should_receive(:variable).once.with('VMSTATUS').and_return "SUCCESS"
623
+ mock_call.voicemail(3).should be true
624
+ end
625
+
626
+ it 'should return false if VMSTATUS == "USEREXIT"' do
627
+ mock_call.should_receive(:execute).once
628
+ mock_call.should_receive(:variable).once.with('VMSTATUS').and_return "USEREXIT"
629
+ mock_call.voicemail(2).should be false
630
+ end
631
+
632
+ it 'should return nil if VMSTATUS == "FAILED"' do
633
+ mock_call.should_receive(:execute).once
634
+ mock_call.should_receive(:variable).once.with('VMSTATUS').and_return "FAILED"
635
+ mock_call.voicemail(2).should be nil
636
+ end
637
+
638
+ end
639
+
640
+ describe 'The voicemail_main command' do
641
+
642
+ include DialplanCommandTestHelpers
643
+
644
+ #it "should not pass in the context or the delimiting @ sign if you don't supply one"
645
+
646
+ it "the :folder Hash key argument should wrap the value in a()" do
647
+ folder = "foobar"
648
+ mailbox = 81
649
+ mock_call.should_receive(:execute).once.with("VoiceMailMain", "#{mailbox}","a(#{folder})")
650
+ mock_call.voicemail_main :mailbox => mailbox, :folder => folder
651
+ end
652
+
653
+ it ':authenticate should pass in the "s" option if given false' do
654
+ mailbox = 333
655
+ mock_call.should_receive(:execute).once.with("VoiceMailMain", "#{mailbox}","s")
656
+ mock_call.voicemail_main :mailbox => mailbox, :authenticate => false
657
+ end
658
+
659
+ it ':authenticate should pass in the s option if given false' do
660
+ mailbox = 55
661
+ mock_call.should_receive(:execute).once.with("VoiceMailMain", "#{mailbox}")
662
+ mock_call.voicemail_main :mailbox => mailbox, :authenticate => true
663
+ end
664
+
665
+ it 'should not pass any flags only a mailbox is given' do
666
+ mailbox = "1"
667
+ mock_call.should_receive(:execute).once.with("VoiceMailMain", "#{mailbox}")
668
+ mock_call.voicemail_main :mailbox => mailbox
669
+ end
670
+
671
+ it 'when given no mailbox or context an empty string should be passed to execute as the first argument' do
672
+ mock_call.should_receive(:execute).once.with("VoiceMailMain", "", "s")
673
+ mock_call.voicemail_main :authenticate => false
674
+ end
675
+
676
+ it 'should properly concatenate the options when given multiple ones' do
677
+ folder = "ohai"
678
+ mailbox = 9999
679
+ mock_call.should_receive(:execute).once.with("VoiceMailMain", "#{mailbox}", "sa(#{folder})")
680
+ mock_call.voicemail_main :mailbox => mailbox, :authenticate => false, :folder => folder
681
+ end
682
+
683
+ it 'should not require any arguments' do
684
+ mock_call.should_receive(:execute).once.with("VoiceMailMain")
685
+ mock_call.voicemail_main
686
+ end
687
+
688
+ it 'should pass in the "@context_name" part in if a :context is given and no mailbox is given' do
689
+ context_name = "icanhascheezburger"
690
+ mock_call.should_receive(:execute).once.with("VoiceMailMain", "@#{context_name}")
691
+ mock_call.voicemail_main :context => context_name
692
+ end
693
+
694
+ it "should raise an exception if the folder has a space or malformed characters in it" do
695
+ ["i has a space", "exclaim!", ",", ""].each do |bad_folder_name|
696
+ the_following_code {
697
+ mock_call.voicemail_main :mailbox => 123, :folder => bad_folder_name
698
+ }.should raise_error ArgumentError
699
+ end
700
+ end
701
+
702
+ end
703
+
704
+ describe 'the check_voicemail command' do
705
+
706
+ include DialplanCommandTestHelpers
707
+
708
+ it "should simply execute voicemail_main with no arguments after warning" do
709
+ flexmock(ahn_log.agi).should_receive(:warn).once.with(String)
710
+ mock_call.should_receive(:voicemail_main).once.and_return :mocked_out
711
+ mock_call.check_voicemail.should be :mocked_out
712
+ end
713
+
714
+ end
715
+
716
+
717
+ describe "The queue management abstractions" do
718
+
719
+ include DialplanCommandTestHelpers
720
+
721
+ it 'should not create separate objects for queues with basically the same name' do
722
+ mock_call.queue('foo').should be mock_call.queue('foo')
723
+ mock_call.queue('bar').should be mock_call.queue(:bar)
724
+ end
725
+
726
+ it "queue() should return an instance of QueueProxy" do
727
+ mock_call.queue("foobar").should be_a_kind_of Adhearsion::VoIP::Asterisk::Commands::QueueProxy
728
+ end
729
+
730
+ it "a QueueProxy should respond to join!(), members()" do
731
+ %w[join! agents].each do |method|
732
+ mock_call.queue('foobar').should respond_to(method)
733
+ end
734
+ end
735
+
736
+ it 'a QueueProxy should return a QueueAgentsListProxy when members() is called' do
737
+ mock_call.queue('foobar').agents.should be_a_kind_of(Adhearsion::VoIP::Asterisk::Commands::QueueProxy::QueueAgentsListProxy)
738
+ end
739
+
740
+ it 'join! should properly join a queue' do
741
+ mock_call.should_receive(:execute).once.with("queue", "foobaz", "", '', '', '', '')
742
+ mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return "FULL"
743
+ mock_call.queue("foobaz").join!
744
+ end
745
+
746
+ it 'should return a symbol representing the result of joining the queue' do
747
+ does_not_read_data_back
748
+ mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return "TIMEOUT"
749
+ mock_call.queue('monkey').join!.should be :timeout
750
+ end
751
+
752
+ it 'should return :completed after joining the queue and being connected' do
753
+ does_not_read_data_back
754
+ mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return nil
755
+ mock_call.queue('monkey').join!.should be :completed
756
+ end
757
+
758
+ it 'should join a queue with a timeout properly' do
759
+ mock_call.should_receive(:execute).once.with("queue", "foobaz", "", '', '', '60', '')
760
+ mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return "JOINEMPTY"
761
+ mock_call.queue("foobaz").join! :timeout => 1.minute
762
+ end
763
+
764
+ it 'should join a queue with an announcement file properly' do
765
+ mock_call.should_receive(:execute).once.with("queue", "roflcopter", "", '', 'custom_announcement_file_here', '', '')
766
+ mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return "JOINEMPTY"
767
+ mock_call.queue("roflcopter").join! :announce => 'custom_announcement_file_here'
768
+ end
769
+
770
+ it 'should join a queue with an agi script properly' do
771
+ mock_call.should_receive(:execute).once.with("queue", 'support', '', '', '', '','agi://localhost/queue_agi_test')
772
+ mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return "JOINUNAVAIL"
773
+ mock_call.queue("support").join! :agi => 'agi://localhost/queue_agi_test'
774
+ end
775
+
776
+ it 'should join a queue with allow_transfer properly' do
777
+ mock_call.should_receive(:execute).once.with("queue", "roflcopter", "Tt", '', '', '', '')
778
+ mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return "JOINEMPTY"
779
+ mock_call.queue("roflcopter").join! :allow_transfer => :everyone
780
+
781
+ mock_call.should_receive(:execute).once.with("queue", "roflcopter", "T", '', '', '', '')
782
+ mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return "JOINEMPTY"
783
+ mock_call.queue("roflcopter").join! :allow_transfer => :caller
784
+
785
+ mock_call.should_receive(:execute).once.with("queue", "roflcopter", "t", '', '', '', '')
786
+ mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return "JOINEMPTY"
787
+ mock_call.queue("roflcopter").join! :allow_transfer => :agent
788
+ end
789
+
790
+ it 'should join a queue with allow_hangup properly' do
791
+ mock_call.should_receive(:execute).once.with("queue", "roflcopter", "Hh", '', '', '', '')
792
+ mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return "JOINEMPTY"
793
+ mock_call.queue("roflcopter").join! :allow_hangup => :everyone
794
+
795
+ mock_call.should_receive(:execute).once.with("queue", "roflcopter", "H", '', '', '', '')
796
+ mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return "JOINEMPTY"
797
+ mock_call.queue("roflcopter").join! :allow_hangup => :caller
798
+
799
+ mock_call.should_receive(:execute).once.with("queue", "roflcopter", "h", '', '', '', '')
800
+ mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return "JOINEMPTY"
801
+ mock_call.queue("roflcopter").join! :allow_hangup => :agent
802
+ end
803
+
804
+ it 'should join a queue properly with the :play argument' do
805
+ mock_call.should_receive(:execute).once.with("queue", "roflcopter", "r", '', '', '', '')
806
+ mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return "JOINEMPTY"
807
+ mock_call.queue("roflcopter").join! :play => :ringing
808
+
809
+ mock_call.should_receive(:execute).once.with("queue", "roflcopter", "", '', '', '', '')
810
+ mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return "JOINEMPTY"
811
+ mock_call.queue("roflcopter").join! :play => :music
812
+ end
813
+
814
+ it 'joining a queue with many options specified' do
815
+ mock_call.should_receive(:execute).once.with("queue", "q", "rtHh", '', '', '120', '')
816
+ mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return "JOINEMPTY"
817
+ mock_call.queue('q').join! :allow_transfer => :agent, :timeout => 2.minutes,
818
+ :play => :ringing, :allow_hangup => :everyone
819
+ end
820
+
821
+ it 'join!() should raise an ArgumentError when unrecognized Hash key arguments are given' do
822
+ the_following_code {
823
+ mock_call.queue('iwearmysunglassesatnight').join! :misspelled => true
824
+ }.should raise_error ArgumentError
825
+ end
826
+
827
+ it 'should fetch the members with the name given to queue()' do
828
+ mock_call.should_receive(:variable).once.with("QUEUE_MEMBER_COUNT(jay)").and_return 5
829
+ mock_call.queue('jay').agents.size.should be 5
830
+ end
831
+
832
+ it 'should not fetch a QUEUE_MEMBER_COUNT each time count() is called when caching is enabled' do
833
+ mock_call.should_receive(:variable).once.with("QUEUE_MEMBER_COUNT(sales)").and_return 0
834
+ 10.times do
835
+ mock_call.queue('sales').agents(:cache => true).size
836
+ end
837
+ end
838
+
839
+ it 'should raise an argument error if the members() method receives an unrecognized symbol' do
840
+ the_following_code {
841
+ mock_call.queue('foobarz').agents(:cached => true) # common typo
842
+ }.should raise_error ArgumentError
843
+ end
844
+
845
+ it 'when fetching agents, it should properly split by the supported delimiters' do
846
+ queue_name = "doesnt_matter"
847
+ mock_call.should_receive(:get_variable).with("QUEUE_MEMBER_LIST(#{queue_name})").and_return('Agent/007,Agent/003,Zap/2')
848
+ mock_call.queue(queue_name).agents(:cache => true).to_a.size.should be 3
849
+ end
850
+
851
+ it 'when fetching agents, each array index should be an instance of AgentProxy' do
852
+ queue_name = 'doesnt_matter'
853
+ mock_call.should_receive(:get_variable).with("QUEUE_MEMBER_LIST(#{queue_name})").and_return('Agent/007,Agent/003,Zap/2')
854
+ agents = mock_call.queue(queue_name).agents(:cache => true).to_a
855
+ agents.size.should > 0
856
+ agents.each do |agent|
857
+ agent.should be_a_kind_of Adhearsion::VoIP::Asterisk::Commands::QueueProxy::AgentProxy
858
+ end
859
+ end
860
+
861
+ it 'should properly retrieve metadata for an AgentProxy instance' do
862
+ agent_id, metadata_name = '22', 'status'
863
+ mock_env = flexmock "a mock ExecutionEnvironment"
864
+ mock_queue = flexmock "a queue that references our mock ExecutionEnvironment", :environment => mock_env, :name => "doesntmatter"
865
+ mock_env.should_receive(:variable).once.with("AGENT(#{agent_id}:#{metadata_name})")
866
+ agent = Adhearsion::VoIP::Asterisk::Commands::QueueProxy::AgentProxy.new("Agent/#{agent_id}", mock_queue)
867
+ agent.send(:agent_metadata, metadata_name)
868
+ end
869
+
870
+ it 'AgentProxy#logged_in? should return true if the "state" of an agent == LOGGEDIN' do
871
+ mock_env = flexmock "a mock ExecutionEnvironment"
872
+ mock_queue = flexmock "a queue that references our mock ExecutionEnvironment", :environment => mock_env, :name => "doesntmatter"
873
+
874
+ agent = Adhearsion::VoIP::Asterisk::Commands::QueueProxy::AgentProxy.new('Agent/123', mock_queue)
875
+ flexmock(agent).should_receive(:agent_metadata).once.with('status').and_return 'LOGGEDIN'
876
+ agent.logged_in?.should be true
877
+
878
+ flexmock(agent).should_receive(:agent_metadata).once.with('status').and_return 'LOGGEDOUT'
879
+ agent.logged_in?.should_not be true
880
+ end
881
+
882
+ it 'the AgentProxy should populate its own "id" property to the numerical ID of the "interface" with which it was constructed' do
883
+ mock_queue = flexmock :name => "doesntmatter"
884
+ id = '123'
885
+
886
+ agent = Adhearsion::VoIP::Asterisk::Commands::QueueProxy::AgentProxy.new("Agent/#{id}", mock_queue)
887
+ agent.id.should == id
888
+
889
+ agent = Adhearsion::VoIP::Asterisk::Commands::QueueProxy::AgentProxy.new(id, mock_queue)
890
+ agent.id.should == id
891
+ end
892
+
893
+ it 'QueueAgentsListProxy#<<() should new the channel driver given as the argument to the system' do
894
+ queue_name, agent_channel = "metasyntacticvariablesftw", "Agent/123"
895
+ mock_call.should_receive('execute').once.with("AddQueueMember", queue_name, agent_channel, "", "", "", "")
896
+ mock_call.should_receive(:get_variable).once.with('AQMSTATUS').and_return('ADDED')
897
+ mock_call.should_receive(:get_variable).once.with("QUEUE_MEMBER_LIST(#{queue_name})").and_return "Agent/007,SIP/2302,Local/2510@from-internal"
898
+ mock_call.queue(queue_name).agents.new agent_channel
899
+ end
900
+
901
+ it 'when a queue agent is dynamically added and the queue does not exist, a QueueDoesNotExistError should be raised' do
902
+ does_not_read_data_back
903
+ mock_call.should_receive(:get_variable).once.with('AQMSTATUS').and_return('NOSUCHQUEUE')
904
+ the_following_code {
905
+ mock_call.queue('this_should_not_exist').agents.new 'Agent/911'
906
+ }.should raise_error Adhearsion::VoIP::Asterisk::Commands::QueueProxy::QueueDoesNotExistError
907
+ end
908
+
909
+ it 'when a queue agent is dynamiaclly added and the adding was successful, an AgentProxy should be returned' do
910
+ mock_call.should_receive(:get_variable).once.with("AQMSTATUS").and_return("ADDED")
911
+ mock_call.should_receive(:execute).once.with("AddQueueMember", "lalala", "Agent/007", "", "", "", "")
912
+ mock_call.should_receive(:get_variable).once.with("QUEUE_MEMBER_LIST(lalala)").and_return "Agent/007,SIP/2302,Local/2510@from-internal"
913
+ return_value = mock_call.queue('lalala').agents.new "Agent/007"
914
+ return_value.kind_of?(Adhearsion::VoIP::Asterisk::Commands::QueueProxy::AgentProxy).should be true
915
+ end
916
+
917
+ it 'when a queue agent is dynamiaclly added and the adding was unsuccessful, a false should be returned' do
918
+ mock_call.should_receive(:get_variable).once.with("AQMSTATUS").and_return("MEMBERALREADY")
919
+ mock_call.should_receive(:execute).once.with("AddQueueMember", "lalala", "Agent/007", "", "", "", "")
920
+ return_value = mock_call.queue('lalala').agents.new "Agent/007"
921
+ return_value.should be false
922
+ end
923
+
924
+ it 'should raise an argument when an unrecognized key is given to add()' do
925
+ the_following_code {
926
+ mock_call.queue('q').agents.new :foo => "bar"
927
+ }.should raise_error ArgumentError
928
+ end
929
+
930
+ it 'should execute AddQueueMember with the penalty properly' do
931
+ queue_name = 'name_does_not_matter'
932
+ mock_call.should_receive(:execute).once.with('AddQueueMember', queue_name, 'Agent/007', 10, '', '','')
933
+ mock_call.should_receive(:get_variable).once.with('AQMSTATUS').and_return('ADDED')
934
+ mock_call.should_receive(:get_variable).once.with("QUEUE_MEMBER_LIST(#{queue_name})").and_return "Agent/007,SIP/2302,Local/2510@from-internal"
935
+ mock_call.queue(queue_name).agents.new 'Agent/007', :penalty => 10
936
+ end
937
+
938
+ it 'should execute AddQueueMember with the state_interface properly' do
939
+ queue_name = 'name_does_not_matter'
940
+ mock_call.should_receive(:execute).once.with('AddQueueMember', queue_name, 'Agent/007', '', '', '','SIP/2302')
941
+ mock_call.should_receive(:get_variable).once.with('AQMSTATUS').and_return('ADDED')
942
+ mock_call.should_receive(:get_variable).once.with("QUEUE_MEMBER_LIST(#{queue_name})").and_return "Agent/007,SIP/2302,Local/2510@from-internal"
943
+ mock_call.queue(queue_name).agents.new 'Agent/007', :state_interface => 'SIP/2302'
944
+ end
945
+
946
+ it 'should execute AddQueueMember properly when the name is given' do
947
+ queue_name, agent_name = 'name_does_not_matter', 'Jay Phillips'
948
+ mock_call.should_receive(:execute).once.with('AddQueueMember', queue_name, 'Agents/007', '', '', agent_name,'')
949
+ mock_call.should_receive(:get_variable).once.with('AQMSTATUS').and_return('ADDED')
950
+ mock_call.should_receive(:get_variable).once.with("QUEUE_MEMBER_LIST(#{queue_name})").and_return "Agent/007,SIP/2302,Local/2510@from-internal"
951
+ mock_call.queue(queue_name).agents.new 'Agents/007', :name => agent_name
952
+ end
953
+
954
+ it 'should execute AddQueueMember properly when the name, penalty, and interface is given' do
955
+ queue_name, agent_name, interface, penalty = 'name_does_not_matter', 'Jay Phillips', 'Agent/007', 4
956
+ mock_call.should_receive(:execute).once.with('AddQueueMember', queue_name, interface, penalty, '', agent_name,'')
957
+ mock_call.should_receive(:get_variable).once.with('AQMSTATUS').and_return('ADDED')
958
+ mock_call.should_receive(:get_variable).once.with("QUEUE_MEMBER_LIST(#{queue_name})").and_return "Agent/007,SIP/2302,Local/2510@from-internal"
959
+ mock_call.queue(queue_name).agents.new interface, :name => agent_name, :penalty => penalty
960
+ end
961
+
962
+ it 'should execute AddQueueMember properly when the name, penalty, interface, and state_interface is given' do
963
+ queue_name, agent_name, interface, penalty, state_interface = 'name_does_not_matter', 'Jay Phillips', 'Agent/007', 4, 'SIP/2302'
964
+ mock_call.should_receive(:execute).once.with('AddQueueMember', queue_name, interface, penalty, '', agent_name, state_interface)
965
+ mock_call.should_receive(:get_variable).once.with('AQMSTATUS').and_return('ADDED')
966
+ mock_call.should_receive(:get_variable).once.with("QUEUE_MEMBER_LIST(#{queue_name})").and_return "Agent/007,SIP/2302,Local/2510@from-internal"
967
+ mock_call.queue(queue_name).agents.new interface, :name => agent_name, :penalty => penalty, :state_interface => state_interface
968
+ end
969
+
970
+ it 'should return a correct boolean for exists?()' do
971
+ mock_call.should_receive(:execute).once.with("RemoveQueueMember", "kablamm", "SIP/AdhearsionQueueExistenceCheck")
972
+ mock_call.should_receive(:get_variable).once.with("RQMSTATUS").and_return "NOTINQUEUE"
973
+ mock_call.queue("kablamm").exists?.should be true
974
+
975
+ mock_call.should_receive(:execute).once.with("RemoveQueueMember", "monkey", "SIP/AdhearsionQueueExistenceCheck")
976
+ mock_call.should_receive(:get_variable).once.with("RQMSTATUS").and_return "NOSUCHQUEUE"
977
+ mock_call.queue("monkey").exists?.should be false
978
+ end
979
+
980
+ it 'should pause an agent properly from a certain queue' do
981
+ does_not_read_data_back
982
+ mock_call.should_receive(:get_variable).once.with("QUEUE_MEMBER_LIST(lolcats)").and_return "Agent/007,Agent/008"
983
+ mock_call.should_receive(:get_variable).once.with("PQMSTATUS").and_return "PAUSED"
984
+
985
+ agents = mock_call.queue('lolcats').agents :cache => true
986
+ agents.last.pause!.should be true
987
+ end
988
+
989
+ it 'should pause an agent properly from a certain queue and return false when the agent did not exist' do
990
+ mock_call.should_receive(:get_variable).once.with("QUEUE_MEMBER_LIST(lolcats)").and_return "Agent/007,Agent/008"
991
+ mock_call.should_receive(:get_variable).once.with("PQMSTATUS").and_return "NOTFOUND"
992
+ mock_call.should_receive(:execute).once.with("PauseQueueMember", 'lolcats', "Agent/008")
993
+
994
+ agents = mock_call.queue('lolcats').agents :cache => true
995
+ agents.last.pause!.should be false
996
+ end
997
+
998
+ it 'should pause an agent globally properly' do
999
+ mock_call.should_receive(:get_variable).once.with("QUEUE_MEMBER_LIST(family)").and_return "Agent/Jay"
1000
+ mock_call.should_receive(:get_variable).once.with("PQMSTATUS").and_return "PAUSED"
1001
+ mock_call.should_receive(:execute).once.with("PauseQueueMember", nil, "Agent/Jay")
1002
+
1003
+ mock_call.queue('family').agents.first.pause! :everywhere => true
1004
+ end
1005
+
1006
+ it 'should unpause an agent properly' do
1007
+ queue_name = "name with spaces"
1008
+ mock_call.should_receive(:get_variable).once.with("QUEUE_MEMBER_LIST(#{queue_name})").and_return "Agent/Jay"
1009
+ mock_call.should_receive(:get_variable).once.with("UPQMSTATUS").and_return "UNPAUSED"
1010
+ mock_call.should_receive(:execute).once.with("UnpauseQueueMember", queue_name, "Agent/Jay")
1011
+
1012
+ mock_call.queue(queue_name).agents.first.unpause!.should be true
1013
+ end
1014
+
1015
+ it 'should unpause an agent globally properly' do
1016
+ mock_call.should_receive(:get_variable).once.with("QUEUE_MEMBER_LIST(FOO)").and_return "Agent/Tom"
1017
+ mock_call.should_receive(:get_variable).once.with("UPQMSTATUS").and_return "UNPAUSED"
1018
+ mock_call.should_receive(:execute).once.with("UnpauseQueueMember", nil, "Agent/Tom")
1019
+
1020
+ mock_call.queue('FOO').agents.first.unpause!(:everywhere => true).should be true
1021
+ end
1022
+
1023
+ it 'waiting_count for a queue that does exist' do
1024
+ mock_call.should_receive(:get_variable).once.with("QUEUE_WAITING_COUNT(q)").and_return "50"
1025
+ flexmock(mock_call.queue('q')).should_receive(:exists?).once.and_return true
1026
+ mock_call.queue('q').waiting_count.should be 50
1027
+ end
1028
+
1029
+ it 'waiting_count for a queue that does not exist' do
1030
+ the_following_code {
1031
+ flexmock(mock_call.queue('q')).should_receive(:exists?).once.and_return false
1032
+ mock_call.queue('q').waiting_count
1033
+ }.should raise_error Adhearsion::VoIP::Asterisk::Commands::QueueProxy::QueueDoesNotExistError
1034
+ end
1035
+
1036
+ it 'empty? should call waiting_count' do
1037
+ queue = mock_call.queue 'testing_empty'
1038
+ flexmock(queue).should_receive(:waiting_count).once.and_return 0
1039
+ queue.empty?.should be true
1040
+
1041
+ queue = mock_call.queue 'testing_empty'
1042
+ flexmock(queue).should_receive(:waiting_count).once.and_return 99
1043
+ queue.empty?.should_not be true
1044
+ end
1045
+
1046
+ it 'any? should call waiting_count' do
1047
+ queue = mock_call.queue 'testing_empty'
1048
+ flexmock(queue).should_receive(:waiting_count).once.and_return 0
1049
+ queue.any?.should be false
1050
+
1051
+ queue = mock_call.queue 'testing_empty'
1052
+ flexmock(queue).should_receive(:waiting_count).once.and_return 99
1053
+ queue.any?.should be true
1054
+ end
1055
+
1056
+ it 'should remove an agent properly' do
1057
+ mock_call.should_receive(:get_variable).once.with("QUEUE_MEMBER_LIST(FOO)").and_return "Agent/Tom"
1058
+ mock_call.should_receive(:execute).once.with('RemoveQueueMember', 'FOO', 'Agent/Tom')
1059
+ mock_call.should_receive(:get_variable).once.with("RQMSTATUS").and_return "REMOVED"
1060
+ mock_call.queue('FOO').agents.first.remove!.should be true
1061
+ end
1062
+
1063
+ it 'should remove an agent properly' do
1064
+ mock_call.should_receive(:get_variable).once.with("QUEUE_MEMBER_LIST(FOO)").and_return "Agent/Tom"
1065
+ mock_call.should_receive(:execute).once.with('RemoveQueueMember', 'FOO', 'Agent/Tom')
1066
+ mock_call.should_receive(:get_variable).once.with("RQMSTATUS").and_return "NOTINQUEUE"
1067
+ mock_call.queue('FOO').agents.first.remove!.should be false
1068
+ end
1069
+
1070
+ it "should raise a QueueDoesNotExistError when removing an agent from a queue that doesn't exist" do
1071
+ mock_call.should_receive(:get_variable).once.with("QUEUE_MEMBER_LIST(cool_people)").and_return "Agent/ZeroCool"
1072
+ mock_call.should_receive(:execute).once.with("RemoveQueueMember", "cool_people", "Agent/ZeroCool")
1073
+ mock_call.should_receive(:get_variable).once.with("RQMSTATUS").and_return "NOSUCHQUEUE"
1074
+ the_following_code {
1075
+ mock_call.queue("cool_people").agents.first.remove!
1076
+ }.should raise_error Adhearsion::VoIP::Asterisk::Commands::QueueProxy::QueueDoesNotExistError
1077
+ end
1078
+
1079
+ it "should log an agent in properly with no agent id given" do
1080
+ mock_call.should_receive(:execute).once.with('AgentLogin', nil, 's')
1081
+ mock_call.queue('barrel_o_agents').agents.login!
1082
+ end
1083
+
1084
+ it 'should remove "Agent/" before the agent ID given if necessary when logging an agent in' do
1085
+ mock_call.should_receive(:execute).once.with('AgentLogin', '007', 's')
1086
+ mock_call.queue('barrel_o_agents').agents.login! 'Agent/007'
1087
+
1088
+ mock_call.should_receive(:execute).once.with('AgentLogin', '007', 's')
1089
+ mock_call.queue('barrel_o_agents').agents.login! '007'
1090
+ end
1091
+
1092
+ it 'should add an agent silently properly' do
1093
+ mock_call.should_receive(:execute).once.with('AgentLogin', '007', '')
1094
+ mock_call.queue('barrel_o_agents').agents.login! 'Agent/007', :silent => false
1095
+
1096
+ mock_call.should_receive(:execute).once.with('AgentLogin', '008', 's')
1097
+ mock_call.queue('barrel_o_agents').agents.login! 'Agent/008', :silent => true
1098
+ end
1099
+
1100
+ it 'logging an agent in should raise an ArgumentError is unrecognized arguments are given' do
1101
+ the_following_code {
1102
+ mock_call.queue('ohai').agents.login! 1,2,3,4,5
1103
+ }.should raise_error ArgumentError
1104
+
1105
+ the_following_code {
1106
+ mock_call.queue('lols').agents.login! 1337, :sssssilent => false
1107
+ }.should raise_error ArgumentError
1108
+
1109
+ the_following_code {
1110
+ mock_call.queue('qwerty').agents.login! 777, 6,5,4,3,2,1, :wee => :wee
1111
+ }.should raise_error ArgumentError
1112
+ end
1113
+
1114
+ end
1115
+
1116
+ describe 'the menu() method' do
1117
+
1118
+ include DialplanCommandTestHelpers
1119
+
1120
+ it "should instantiate a new Menu object with only the Hash given as menu() options" do
1121
+ args = [1,2,3,4,5, {:timeout => 1.year, :tries => (1.0/0.0)}]
1122
+
1123
+ flexmock(Adhearsion::VoIP::Menu).should_receive(:new).once.
1124
+ with(args.last).and_throw(:instantiating_menu!)
1125
+
1126
+ should_throw(:instantiating_menu!) { mock_call.menu(*args) }
1127
+ end
1128
+
1129
+ it "should jump to a context when a timeout is encountered and there is at least one exact match" do
1130
+ pbx_should_respond_with_successful_background_response ?5.ord
1131
+ pbx_should_respond_with_successful_background_response ?4.ord
1132
+ pbx_should_respond_with_a_wait_for_digit_timeout
1133
+
1134
+ context_named_main = Adhearsion::DialPlan::DialplanContextProc.new(:main) { throw :inside_main! }
1135
+ context_named_other = Adhearsion::DialPlan::DialplanContextProc.new(:other) { throw :inside_other! }
1136
+ flexmock(mock_call).should_receive(:main).once.and_return(context_named_main)
1137
+ flexmock(mock_call).should_receive(:other).never
1138
+
1139
+ should_pass_control_to_a_context_that_throws :inside_main! do
1140
+ mock_call.menu do |link|
1141
+ link.main 54
1142
+ link.other 543
1143
+ end
1144
+ end
1145
+ end
1146
+
1147
+ it "when the 'extension' variable is changed, it should be an instance of PhoneNumber" do
1148
+ pbx_should_respond_with_successful_background_response ?5.ord
1149
+ foobar_context = Adhearsion::DialPlan::DialplanContextProc.new(:foobar) { throw :foobar! }
1150
+ mock_call.should_receive(:foobar).once.and_return foobar_context
1151
+ should_pass_control_to_a_context_that_throws :foobar! do
1152
+ mock_call.menu do |link|
1153
+ link.foobar 5
1154
+ end
1155
+ end
1156
+ 5.should === mock_call.extension
1157
+ mock_call.extension.__real_string.should == "5"
1158
+ end
1159
+
1160
+ end
1161
+
1162
+ describe 'the Menu class' do
1163
+
1164
+ include DialplanCommandTestHelpers
1165
+
1166
+ it "should yield a MenuBuilder when instantiated" do
1167
+ lambda {
1168
+ Adhearsion::VoIP::Menu.new do |block_argument|
1169
+ block_argument.should be_a_kind_of Adhearsion::VoIP::MenuBuilder
1170
+ throw :inside_block
1171
+ end
1172
+ }.should throw_symbol :inside_block
1173
+ end
1174
+
1175
+ it "should invoke wait_for_digit instead of interruptible_play when no sound files are given" do
1176
+ mock_call.should_receive(:wait_for_digit).once.with(5).and_return '#'
1177
+ mock_call.menu { |link| link.does_not_match 3 }
1178
+ end
1179
+
1180
+ it 'should invoke interruptible_play when sound files are given only for the first digit' do
1181
+ sound_files = %w[i like big butts and i cannot lie]
1182
+ timeout = 1337
1183
+
1184
+ mock_call.should_receive(:interruptible_play).once.with(*sound_files).and_return nil
1185
+ mock_call.should_receive(:wait_for_digit).once.with(timeout).and_return nil
1186
+
1187
+ mock_call.menu(sound_files, :timeout => timeout) { |link| link.qwerty 12345 }
1188
+ end
1189
+
1190
+ it 'if the call to interruptible_play receives a timeout, it should execute wait_for_digit with the timeout given' do
1191
+ sound_files = %w[i like big butts and i cannot lie]
1192
+ timeout = 987
1193
+
1194
+ mock_call.should_receive(:interruptible_play).once.with(*sound_files).and_return nil
1195
+ mock_call.should_receive(:wait_for_digit).with(timeout).and_return
1196
+
1197
+ mock_call.menu(sound_files, :timeout => timeout) { |link| link.foobar 911 }
1198
+ end
1199
+
1200
+ it "should work when no files are given to be played and a timeout is reached on the first digit" do
1201
+ timeout = 12
1202
+ [:on_premature_timeout, :on_failure].each do |usage_case|
1203
+ should_throw :got_here! do
1204
+ mock_call.should_receive(:wait_for_digit).once.with(timeout).and_return nil # Simulates timeout
1205
+ mock_call.menu :timeout => timeout do |link|
1206
+ link.foobar 0
1207
+ link.__send__(usage_case) { throw :got_here! }
1208
+ end
1209
+ end
1210
+ end
1211
+ end
1212
+
1213
+ it "should default the timeout to five seconds" do
1214
+ pbx_should_respond_with_successful_background_response ?2.ord
1215
+ pbx_should_respond_with_a_wait_for_digit_timeout
1216
+
1217
+ mock_call.should_receive(:wait_for_digit).once.with(5).and_return nil
1218
+ mock_call.menu { |link| link.foobar 22 }
1219
+ end
1220
+
1221
+ it "when matches fail due to timeouts, the menu should repeat :tries times" do
1222
+ tries, times_timed_out = 10, 0
1223
+
1224
+ tries.times do
1225
+ pbx_should_respond_with_successful_background_response ?4.ord
1226
+ pbx_should_respond_with_successful_background_response ?0.ord
1227
+ pbx_should_respond_with_a_wait_for_digit_timeout
1228
+ end
1229
+
1230
+ should_throw :inside_failure_callback do
1231
+ mock_call.menu :tries => tries do |link|
1232
+ link.pattern_longer_than_our_test_input 400
1233
+ link.on_premature_timeout { times_timed_out += 1 }
1234
+ link.on_invalid { raise "should never get here!" }
1235
+ link.on_failure { throw :inside_failure_callback }
1236
+ end
1237
+ end
1238
+ times_timed_out.should be tries
1239
+ end
1240
+
1241
+ it "when matches fail due to invalid input, the menu should repeat :tries times" do
1242
+ tries = 10
1243
+ times_invalid = 0
1244
+
1245
+ tries.times do
1246
+ pbx_should_respond_with_successful_background_response ?0.ord
1247
+ end
1248
+
1249
+ should_throw :inside_failure_callback do
1250
+ mock_call.menu :tries => tries do |link|
1251
+ link.be_leet 1337
1252
+ link.on_premature_timeout { raise "should never get here!" }
1253
+ link.on_invalid { times_invalid += 1 }
1254
+ link.on_failure { throw :inside_failure_callback }
1255
+ end
1256
+ end
1257
+ times_invalid.should be tries
1258
+ end
1259
+
1260
+ it "invoke on_invalid callback when an invalid extension was entered" do
1261
+ pbx_should_respond_with_successful_background_response ?5.ord
1262
+ pbx_should_respond_with_successful_background_response ?5.ord
1263
+ pbx_should_respond_with_successful_background_response ?5.ord
1264
+ should_throw :inside_invalid_callback do
1265
+ mock_call.menu do |link|
1266
+ link.onetwothree 123
1267
+ link.on_invalid { throw :inside_invalid_callback }
1268
+ end
1269
+ end
1270
+ end
1271
+
1272
+ it "invoke on_premature_timeout when a timeout is encountered" do
1273
+ pbx_should_respond_with_successful_background_response ?9.ord
1274
+ pbx_should_respond_with_a_wait_for_digit_timeout
1275
+
1276
+ should_throw :inside_timeout do
1277
+ mock_call.menu :timeout => 1 do |link|
1278
+ link.something 999
1279
+ link.on_premature_timeout { throw :inside_timeout }
1280
+ end
1281
+ end
1282
+ end
1283
+
1284
+ end
1285
+
1286
+ describe "the Menu class's high-level judgment" do
1287
+
1288
+ include DialplanCommandTestHelpers
1289
+
1290
+ it "should match things in ambiguous ranges properly" do
1291
+ pbx_should_respond_with_successful_background_response ?1.ord
1292
+ pbx_should_respond_with_successful_background_response ?1.ord
1293
+ pbx_should_respond_with_successful_background_response ?1.ord
1294
+ pbx_should_respond_with_a_wait_for_digit_timeout
1295
+
1296
+ main_context = Adhearsion::DialPlan::DialplanContextProc.new(:main) { throw :got_here! }
1297
+ mock_call.should_receive(:main).and_return main_context
1298
+
1299
+ should_pass_control_to_a_context_that_throws :got_here! do
1300
+ mock_call.menu do |link|
1301
+ link.blah 1
1302
+ link.main 11..11111
1303
+ end
1304
+ end
1305
+ 111.should === mock_call.extension
1306
+ end
1307
+
1308
+ it 'should match things in a range when there are many other non-matching patterns' do
1309
+ pbx_should_respond_with_successful_background_response ?9.ord
1310
+ pbx_should_respond_with_successful_background_response ?9.ord
1311
+ pbx_should_respond_with_successful_background_response ?5.ord
1312
+
1313
+ conferences_context = Adhearsion::DialPlan::DialplanContextProc.new(:conferences) { throw :got_here! }
1314
+ mock_call.should_receive(:conferences).and_return conferences_context
1315
+
1316
+ should_pass_control_to_a_context_that_throws :got_here! do
1317
+ mock_call.menu do |link|
1318
+ link.sales 1
1319
+ link.tech_support 2
1320
+ link.finance 3
1321
+ link.conferences 900..999
1322
+ end
1323
+ end
1324
+ end
1325
+
1326
+ end
1327
+
1328
+ describe 'the MenuBuilder' do
1329
+
1330
+ include MenuBuilderTestHelper
1331
+
1332
+ attr_reader :builder
1333
+ before(:each) do
1334
+ @builder = Adhearsion::VoIP::MenuBuilder.new
1335
+ end
1336
+
1337
+ it "should convert each pattern given to it into a MatchCalculator instance" do
1338
+ builder.tap do |link|
1339
+ link.foo 1,2,3
1340
+ link.bar "4", "5", 6
1341
+ end
1342
+
1343
+ builder.weighted_match_calculators.size.should be 6
1344
+ builder.weighted_match_calculators.each do |match_calculator|
1345
+ match_calculator.should be_a_kind_of Adhearsion::VoIP::MatchCalculator
1346
+ end
1347
+ end
1348
+
1349
+ it "conflicting ranges" do
1350
+ builder.tap do |link|
1351
+ link.hundreds 100...200
1352
+ link.thousands 1_000...2_000
1353
+ link.tenthousands 10_000...20_000
1354
+ end
1355
+
1356
+ builder_should_match_with_these_quantities_of_calculated_matches \
1357
+ 1 => { :exact_match_count => 0, :potential_match_count => 11100 },
1358
+ 10 => { :exact_match_count => 0, :potential_match_count => 1110 },
1359
+ 100 => { :exact_match_count => 1, :potential_match_count => 110 },
1360
+ 1_000 => { :exact_match_count => 1, :potential_match_count => 10 },
1361
+ 10_000 => { :exact_match_count => 1, :potential_match_count => 0 },
1362
+ 100_000 => { :exact_match_count => 0, :potential_match_count => 0 }
1363
+
1364
+ end
1365
+
1366
+ it 'a String query ran against multiple Numeric patterns and a range' do
1367
+ builder.tap do |link|
1368
+ link.sales 1
1369
+ link.tech_support 2
1370
+ link.finance 3
1371
+ link.conferences 900..999
1372
+ end
1373
+ match = builder.calculate_matches_for "995"
1374
+ require 'pp'
1375
+ # pp match
1376
+ match.potential_match?.should_not be true
1377
+ match.exact_match?.should be true
1378
+ match.actual_exact_matches.should == ["995"]
1379
+ end
1380
+
1381
+ it "multiple patterns given at once" do
1382
+ builder.tap do |link|
1383
+ link.multiple_patterns 1,2,3,4,5,6,7,8,9
1384
+ link.multiple_patterns 100..199, 200..299, 300..399, 400..499, 500..599,
1385
+ 600..699, 700..799, 800..899, 900..999
1386
+ end
1387
+ 1.upto 9 do |num|
1388
+ builder.calculate_matches_for(num).tap do |matches_of_num|
1389
+ matches_of_num.potential_match_count.should be 100
1390
+ matches_of_num.exact_match_count.should be 1
1391
+ end
1392
+ builder.calculate_matches_for((num * 100) + 5).tap do |matches_of_num|
1393
+ matches_of_num.potential_match_count.should be 0
1394
+ matches_of_num.exact_match_count.should be 1
1395
+ end
1396
+ end
1397
+ end
1398
+
1399
+ it "numeric literals that don't match but ultimately would" do
1400
+ builder.tap do |link|
1401
+ link.nineninenine 999
1402
+ link.shouldnt_match 4444
1403
+ end
1404
+ builder.calculate_matches_for(9).potential_match_count.should be 1
1405
+ end
1406
+
1407
+ it "three fixnums that obviously don't conflict" do
1408
+ builder.tap do |link|
1409
+ link.one 1
1410
+ link.two 2
1411
+ link.three 3
1412
+ end
1413
+ [[1,2,3,4,'#'], [1,1,1,0,0]].transpose.each do |(input,expected_matches)|
1414
+ matches = builder.calculate_matches_for input
1415
+ matches.exact_match_count.should be expected_matches
1416
+ end
1417
+ end
1418
+
1419
+ it "numerical digits mixed with special digits" do
1420
+ builder.tap do |link|
1421
+ link.one '5*11#3'
1422
+ link.two '5***'
1423
+ link.three '###'
1424
+ end
1425
+
1426
+ builder_should_match_with_these_quantities_of_calculated_matches \
1427
+ '5' => { :potential_match_count => 2, :exact_match_count => 0 },
1428
+ '*' => { :potential_match_count => 0, :exact_match_count => 0 },
1429
+ '5**' => { :potential_match_count => 1, :exact_match_count => 0 },
1430
+ '5*1' => { :potential_match_count => 1, :exact_match_count => 0 },
1431
+ '5*11#3' => { :potential_match_count => 0, :exact_match_count => 1 },
1432
+ '5*11#4' => { :potential_match_count => 0, :exact_match_count => 0 },
1433
+ '5***' => { :potential_match_count => 0, :exact_match_count => 1 },
1434
+ '###' => { :potential_match_count => 0, :exact_match_count => 1 },
1435
+ '##*' => { :potential_match_count => 0, :exact_match_count => 0 }
1436
+
1437
+ end
1438
+
1439
+ it 'a Fixnum exact match conflicting with a Range that would ultimately match' do
1440
+ builder.tap do |link|
1441
+ link.single_digit 1
1442
+ link.range 100..200
1443
+ end
1444
+ matches = builder.calculate_matches_for 1
1445
+ matches.potential_match_count.should be 100
1446
+ end
1447
+
1448
+ end
1449
+
1450
+ describe 'say_digits command' do
1451
+ include DialplanCommandTestHelpers
1452
+ it 'Can execute the saydigits application using say_digits' do
1453
+ digits = "12345"
1454
+ pbx_should_respond_with_success
1455
+ mock_call.say_digits digits
1456
+ pbx_was_asked_to_execute "saydigits", digits
1457
+ end
1458
+
1459
+ it 'Digits that include pound, star, and minus are considered valid' do
1460
+ digits = "1#2*3-4"
1461
+ mock_call.should_receive(:execute).once.with("saydigits", digits)
1462
+ mock_call.say_digits digits
1463
+ end
1464
+
1465
+ it 'Cannot pass non-integers into say_digits. Will raise an ArgumentError' do
1466
+ the_following_code {
1467
+ mock_call.say_digits 'abc'
1468
+ }.should raise_error(ArgumentError)
1469
+
1470
+ the_following_code {
1471
+ mock_call.say_digits '1.20'
1472
+ }.should raise_error(ArgumentError)
1473
+ end
1474
+
1475
+ it 'Digits that start with a 0 are considered valid and parsed properly' do
1476
+ digits = "0123"
1477
+ mock_call.should_receive(:execute).once.with("saydigits", digits)
1478
+ mock_call.say_digits digits
1479
+ end
1480
+
1481
+ end
1482
+
1483
+ describe 'the enable_feature command' do
1484
+
1485
+ include DialplanCommandTestHelpers
1486
+
1487
+ it 'it should fetch the variable for DYNAMIC_FEATURES at first' do
1488
+ mock_call.should_receive(:variable).once.with("DYNAMIC_FEATURES").and_throw :got_variable
1489
+ should_throw :got_variable do
1490
+ mock_call.enable_feature :foobar
1491
+ end
1492
+ end
1493
+
1494
+ it 'should check Adhearsion::VoIP::Asterisk::Commands::DYNAMIC_FEATURE_EXTENSIONS mapping for configuration setters' do
1495
+ feature_name = :attended_transfer
1496
+
1497
+ assertion = lambda do |arg|
1498
+ arg.should == :this_is_the_right_arg
1499
+ throw :inside_assertion!
1500
+ end
1501
+
1502
+ # I had to do this ugly hack because of a bug in Flexmock which prevented me from mocking out Hash#[] :(
1503
+ old_hash_feature_extension = Adhearsion::VoIP::Asterisk::Commands::DYNAMIC_FEATURE_EXTENSIONS[feature_name]
1504
+ begin
1505
+ Adhearsion::VoIP::Asterisk::Commands::DYNAMIC_FEATURE_EXTENSIONS[feature_name] = assertion
1506
+ # </uglyhack>
1507
+ should_throw :inside_assertion! do
1508
+ mock_call.enable_feature(feature_name, :this_is_the_right_arg)
1509
+ end
1510
+ ensure
1511
+ Adhearsion::VoIP::Asterisk::Commands::DYNAMIC_FEATURE_EXTENSIONS[feature_name] = old_hash_feature_extension
1512
+ end
1513
+ end
1514
+
1515
+ it 'should separate enabled features with a "#"' do
1516
+ mock_call.should_receive(:variable).once.with("DYNAMIC_FEATURES").and_return("one")
1517
+ mock_call.should_receive(:variable).once.with("DYNAMIC_FEATURES" => 'one#bar')
1518
+ mock_call.enable_feature "bar"
1519
+ end
1520
+
1521
+ it 'should not add duplicate enabled dynamic features' do
1522
+ mock_call.should_receive(:variable).once.and_return('eins#zwei')
1523
+ mock_call.enable_feature "eins"
1524
+ end
1525
+
1526
+ it 'should raise an ArgumentError if optional options are given when DYNAMIC_FEATURE_EXTENSIONS does not have a key for the feature name' do
1527
+ the_following_code {
1528
+ mock_call.enable_feature :this_is_not_recognized, :these_features => "are not going to be recognized"
1529
+ }.should raise_error ArgumentError
1530
+ end
1531
+
1532
+ it 'enabling :attended_transfer should actually enable the atxfer feature' do
1533
+ mock_call.should_receive(:variable).once.with("DYNAMIC_FEATURES").and_return ''
1534
+ mock_call.should_receive(:variable).once.with("DYNAMIC_FEATURES" => 'atxfer')
1535
+ mock_call.enable_feature :attended_transfer
1536
+ end
1537
+
1538
+ it 'the :context optional option when enabling :attended_transfer should set the TRANSFER_CONTEXT variable to the String supplied as a Hash value' do
1539
+ context_name = "direct_dial"
1540
+ mock_call.should_receive(:variable).once.with("DYNAMIC_FEATURES").and_return ''
1541
+ mock_call.should_receive(:variable).once.with("DYNAMIC_FEATURES" => 'atxfer')
1542
+ mock_call.should_receive(:variable).once.with("TRANSFER_CONTEXT" => context_name)
1543
+ mock_call.enable_feature :attended_transfer, :context => context_name
1544
+ end
1545
+
1546
+ it 'enabling :attended_transfer should not add a duplicate if atxfer has been enabled, but it should still set the TRANSFER_CONTEXT variable' do
1547
+ context_name = 'blah'
1548
+ mock_call.should_receive(:variable).once.with('DYNAMIC_FEATURES').and_return 'atxfer'
1549
+ mock_call.should_receive(:variable).once.with('TRANSFER_CONTEXT' => context_name)
1550
+ mock_call.enable_feature :attended_transfer, :context => context_name
1551
+ end
1552
+
1553
+ end
1554
+
1555
+ describe 'the disable_feature command' do
1556
+
1557
+ include DialplanCommandTestHelpers
1558
+
1559
+ it "should properly remove the feature from the DYNAMIC_FEATURES variable" do
1560
+ mock_call.should_receive(:variable).once.with('DYNAMIC_FEATURES').and_return 'foobar#qaz'
1561
+ mock_call.should_receive(:variable).once.with('DYNAMIC_FEATURES' => 'qaz')
1562
+ mock_call.disable_feature "foobar"
1563
+ end
1564
+
1565
+ it "should not re-set the variable if the feature wasn't enabled in the first place" do
1566
+ mock_call.should_receive(:variable).once.with('DYNAMIC_FEATURES').and_return 'atxfer'
1567
+ mock_call.should_receive(:variable).never
1568
+ mock_call.disable_feature "jay"
1569
+ end
1570
+
1571
+ end
1572
+
1573
+ describe 'jump_to command' do
1574
+
1575
+ include DialplanCommandTestHelpers
1576
+
1577
+ it 'when given a DialplanContextProc as the only argument, it should raise a ControlPassingException with that as the target' do
1578
+ # Having to do this ugly hack because I can't do anything with the exception once I set up an expectation with it normally.
1579
+ dialplan_context = Adhearsion::DialPlan::DialplanContextProc.new("my_context") {}
1580
+ should_throw :finishing_the_rescue_block do
1581
+ begin
1582
+ mock_call.jump_to dialplan_context
1583
+ rescue Adhearsion::VoIP::DSL::Dialplan::ControlPassingException => cpe
1584
+ cpe.target.should be dialplan_context
1585
+ throw :finishing_the_rescue_block
1586
+ end
1587
+ end
1588
+ end
1589
+
1590
+ it 'when given a String, it should perform a lookup of the context name' do
1591
+ context_name = 'cool_context'
1592
+ mock_call.should_receive(context_name).once.and_throw :found_context!
1593
+ should_throw :found_context! do
1594
+ mock_call.jump_to context_name
1595
+ end
1596
+ end
1597
+
1598
+ it 'when given a Symbol, it should perform a lookup of the context name' do
1599
+ context_name = :cool_context
1600
+ mock_call.should_receive(context_name).once.and_throw :found_context!
1601
+ should_throw :found_context! do
1602
+ mock_call.jump_to context_name
1603
+ end
1604
+ end
1605
+
1606
+ it "a clearly invalid context name should raise a ContextNotFoundException" do
1607
+ bad_context_name = ' ZOMGS this is A REALLY! STUPID context name . wtf were you thinking?!'
1608
+ the_following_code {
1609
+ mock_call.jump_to bad_context_name
1610
+ }.should raise_error Adhearsion::VoIP::DSL::Dialplan::ContextNotFoundException
1611
+ end
1612
+
1613
+ it 'when given an :extension override, the new value should be boxed in a PhoneNumber' do
1614
+ my_context = Adhearsion::DialPlan::DialplanContextProc.new("my_context") {}
1615
+ begin
1616
+ mock_call.jump_to my_context, :extension => 1337
1617
+ rescue Adhearsion::VoIP::DSL::Dialplan::ControlPassingException
1618
+ # Eating this exception
1619
+ end
1620
+ mock_call.extension.__real_num.should be 1337
1621
+ end
1622
+
1623
+ it 'other overrides should be simply metadef()d' do
1624
+ test_context = Adhearsion::DialPlan::DialplanContextProc.new("test_context") {}
1625
+ begin
1626
+ mock_call.jump_to test_context, :caller_id => 1_444_555_6666
1627
+ rescue Adhearsion::VoIP::DSL::Dialplan::ControlPassingException
1628
+ # Eating this exception
1629
+ end
1630
+ mock_call.caller_id.should == 1_444_555_6666
1631
+ end
1632
+
1633
+ end
1634
+
1635
+ describe "get variable command" do
1636
+ include DialplanCommandTestHelpers
1637
+
1638
+ it "Getting a variable that isn't set returns nothing" do
1639
+ pbx_should_respond_with "200 result=0"
1640
+ mock_call.get_variable('OMGURFACE').should be nil
1641
+ end
1642
+
1643
+ it 'An empty variable should return an empty String' do
1644
+ pbx_should_respond_with_value ""
1645
+ mock_call.get_variable('kablamm').should == ""
1646
+ end
1647
+
1648
+ it "Getting a variable that is set returns its value" do
1649
+ unique_id = "1192470850.1"
1650
+ pbx_should_respond_with_value unique_id
1651
+ variable_value_returned = mock_call.get_variable('UNIQUEID')
1652
+ variable_value_returned.should == unique_id
1653
+ end
1654
+ end
1655
+
1656
+ describe "duration_of command" do
1657
+ include DialplanCommandTestHelpers
1658
+
1659
+ it "Duration of must take a block" do
1660
+ the_following_code {
1661
+ mock_call.duration_of
1662
+ }.should raise_error(LocalJumpError)
1663
+ end
1664
+
1665
+ it "Passed block to duration of is actually executed" do
1666
+ the_following_code {
1667
+ mock_call.duration_of {
1668
+ throw :inside_duration_of
1669
+ }
1670
+ }.should throw_symbol :inside_duration_of
1671
+ end
1672
+
1673
+ it "Duration of block is returned" do
1674
+ start_time = Time.parse('9:25:00')
1675
+ end_time = Time.parse('9:25:05')
1676
+ expected_duration = end_time - start_time
1677
+
1678
+ flexmock(Time).should_receive(:now).twice.and_return(start_time, end_time)
1679
+ duration = mock_call.duration_of {
1680
+ # This doesn't matter
1681
+ }
1682
+
1683
+ duration.should == expected_duration
1684
+ end
1685
+ end
1686
+
1687
+ describe "Dial command" do
1688
+ include DialplanCommandTestHelpers
1689
+
1690
+ it "should set the caller id if the caller_id option is specified" do
1691
+ does_not_read_data_back
1692
+ mock_call.should_receive(:set_caller_id_number).once
1693
+ mock_call.dial 123, :caller_id => "1234678901"
1694
+ end
1695
+
1696
+ it 'should raise an exception when unknown hash key arguments are given to it' do
1697
+ the_following_code {
1698
+ mock_call.dial 123, :asjndfhasndfahsbdfbhasbdfhabsd => "asbdfhabshdfbajhshfbajsf"
1699
+ }.should raise_error ArgumentError
1700
+ end
1701
+
1702
+ it 'should set the caller ID name when given the :name hash key argument' do
1703
+ does_not_read_data_back
1704
+ name = "Jay Phillips"
1705
+ mock_call.should_receive(:set_caller_id_name).once.with(name)
1706
+ mock_call.dial "BlahBlahBlah", :name => name
1707
+ end
1708
+
1709
+ it "should raise an exception when a non-numerical caller_id is specified" do
1710
+ the_following_code {
1711
+ mock_call.dial 911, :caller_id => "zomgz"
1712
+ }.should raise_error ArgumentError
1713
+ end
1714
+
1715
+ it 'should pass the value of the :confirm key to dial_macro_option_compiler()' do
1716
+ does_not_read_data_back
1717
+ value_of_confirm_key = {:play => "ohai", :timeout => 30}
1718
+ mock_call.should_receive(:dial_macro_option_compiler).once.with value_of_confirm_key
1719
+ mock_call.dial 123, :confirm => value_of_confirm_key
1720
+ end
1721
+
1722
+ it "should add the return value of dial_macro_option_compiler to the :options key's value given to the dial command" do
1723
+ channel = "SIP/1337"
1724
+ macro_arg = "THISSHOULDGETPASSEDTOASTERISK"
1725
+ timeout = 10
1726
+ options = 'hH'
1727
+
1728
+ mock_call.should_receive(:dial_macro_option_compiler).once.and_return macro_arg
1729
+ mock_call.should_receive(:execute).with('Dial', channel, timeout, options + macro_arg)
1730
+ mock_call.dial channel, :for => timeout, :confirm => true, :options => options
1731
+ end
1732
+
1733
+ it 'should add the return value of dial_macro_option_compiler to the options field when NO :options are given' do
1734
+ channel = "SIP/1337"
1735
+ macro_arg = "THISSHOULDGETPASSEDTOASTERISK"
1736
+ timeout = 10
1737
+ options = 'hH'
1738
+
1739
+ mock_call.should_receive(:dial_macro_option_compiler).once.and_return macro_arg
1740
+ mock_call.should_receive(:execute).with('Dial', channel, timeout, options + macro_arg)
1741
+ mock_call.dial channel, :for => timeout, :confirm => true, :options => options
1742
+ end
1743
+
1744
+ end
1745
+
1746
+ describe "The Dial command's :confirm option setting builder" do
1747
+
1748
+ include DialplanCommandTestHelpers
1749
+
1750
+ attr_reader :formatter
1751
+ before :each do
1752
+ @formatter = mock_call.method :dial_macro_option_compiler
1753
+ end
1754
+
1755
+ it 'should allow passing in the :confirm named argument with true' do
1756
+ the_following_code {
1757
+ formatter.call true
1758
+ }.should_not raise_error ArgumentError
1759
+ end
1760
+
1761
+ it 'should separate :play options with "++"' do
1762
+ sound_files = *1..10
1763
+ formatter.call(:play => sound_files).should include sound_files.join('++')
1764
+ end
1765
+
1766
+ it 'should raise an ArgumentError if an invalid Hash key is given' do
1767
+ the_following_code {
1768
+ formatter.call :this_symbol_is_not_valid => 123
1769
+ }.should raise_error ArgumentError
1770
+ end
1771
+
1772
+ it "should raise an ArgumentError if the argument's class is not recognized" do
1773
+ the_following_code {
1774
+ formatter.call Time.now # Time is an example strange case
1775
+ }.should raise_error ArgumentError
1776
+ end
1777
+
1778
+ it 'should return the contents within a M() Dial argument' do
1779
+ formatter.call(true).should =~ /^M\(.+\)$/
1780
+ end
1781
+
1782
+ it 'should replace the default macro name when given the :macro options' do
1783
+ macro_name = "ivegotalovelybunchofcoconuts"
1784
+ formatter.call(:macro => macro_name).starts_with?("M(#{macro_name}").should be true
1785
+ end
1786
+
1787
+ it 'should allow a symbol macro name' do
1788
+ the_following_code {
1789
+ formatter.call(:macro => :foo)
1790
+ }.should_not raise_error ArgumentError
1791
+ end
1792
+
1793
+ it 'should only allow alphanumeric and underscores in the macro name' do
1794
+ bad_options = ["this has a space", "foo,bar", 'exists?', 'x^z', '', "!&@&*^!@"]
1795
+ bad_options.each do |bad_option|
1796
+ the_following_code {
1797
+ formatter.call(:macro => bad_option)
1798
+ }.should raise_error ArgumentError
1799
+ end
1800
+ end
1801
+
1802
+ it 'should confirm :timeout => :none to 0' do
1803
+ formatter.call(:timeout => :none).should include "timeout:0"
1804
+ end
1805
+
1806
+ it 'should separate the macro name and the arguments with a caret (^)' do
1807
+ formatter.call(:macro => "jay").should =~ /M\(jay\^.+/
1808
+ end
1809
+
1810
+ it 'should raise an ArgumentError if a caret existed anywhere in the resulting String' do
1811
+ # FIXME: Duplicate hash key
1812
+ bad_options = [{:play => "foo^bar", :key => "^", :play => ["hello-world", 'lol^cats']}]
1813
+ bad_options.each do |bad_option|
1814
+ the_following_code {
1815
+ formatter.call(bad_option)
1816
+ }.should raise_error ArgumentError
1817
+ end
1818
+ end
1819
+
1820
+ it 'should raise an ArgumentError if the :key is not [0-9#*]' do
1821
+ bad_options = %w[& A $ @ . )]
1822
+ bad_options.each do |bad_option|
1823
+ the_following_code {
1824
+ formatter.call :key => bad_option
1825
+ }.should raise_error ArgumentError
1826
+ end
1827
+ end
1828
+
1829
+ it 'should raise an ArgumentError if the key is longer than one digit' do
1830
+ the_following_code {
1831
+ formatter.call :key => "55"
1832
+ }.should raise_error ArgumentError
1833
+ end
1834
+
1835
+ it 'should raise an ArgumentError if the timeout is not numeric and not :none' do
1836
+ bad_options = [:nonee, Time.now, method(:inspect)]
1837
+ bad_options.each do |bad_option|
1838
+ the_following_code {
1839
+ formatter.call bad_option
1840
+ }.should raise_error ArgumentError
1841
+ end
1842
+ end
1843
+
1844
+ it 'should support passing a String argument as a timeout' do
1845
+ the_following_code {
1846
+ formatter.call :timeout => "123"
1847
+ }.should_not raise_error ArgumentError
1848
+ end
1849
+
1850
+ it 'should raise an ArgumentError if given a Float' do
1851
+ the_following_code {
1852
+ formatter.call :timeout => 100.0012
1853
+ }.should raise_error ArgumentError
1854
+ end
1855
+
1856
+ it 'should allow passing a ActiveSupport::Duration to :timeout' do
1857
+ the_following_code {
1858
+ formatter.call :timeout => 3.minutes
1859
+ }.should_not raise_error ArgumentError
1860
+ end
1861
+
1862
+ end
1863
+
1864
+ describe 'the dtmf command' do
1865
+
1866
+ include DialplanCommandTestHelpers
1867
+
1868
+ it 'should send the proper AGI command' do
1869
+ does_not_read_data_back
1870
+ digits = '8404#4*'
1871
+ mock_call.dtmf digits
1872
+ pbx_should_have_been_sent "EXEC SendDTMF \"#{digits}\""
1873
+ end
1874
+ end
1875
+
1876
+ describe "the last_dial_status command and family" do
1877
+
1878
+ include DialplanCommandTestHelpers
1879
+
1880
+ it 'should convert common DIALSTATUS variables to their appropriate symbols' do
1881
+ mock_call.should_receive(:variable).with("DIALSTATUS").once.and_return('ANSWER')
1882
+ mock_call.last_dial_status.should be :answered
1883
+
1884
+ mock_call.should_receive(:variable).with("DIALSTATUS").once.and_return('CONGESTION')
1885
+ mock_call.last_dial_status.should be :congested
1886
+
1887
+ mock_call.should_receive(:variable).once.with("DIALSTATUS").and_return("BUSY")
1888
+ mock_call.last_dial_status.should be :busy
1889
+
1890
+ mock_call.should_receive(:variable).once.with("DIALSTATUS").and_return("CANCEL")
1891
+ mock_call.last_dial_status.should be :cancelled
1892
+
1893
+ mock_call.should_receive(:variable).once.with("DIALSTATUS").and_return("NOANSWER")
1894
+ mock_call.last_dial_status.should be :unanswered
1895
+
1896
+ mock_call.should_receive(:variable).once.with("DIALSTATUS").and_return("CHANUNAVAIL")
1897
+ mock_call.last_dial_status.should be :channel_unavailable
1898
+
1899
+ mock_call.should_receive(:variable).once.with("DIALSTATUS").and_return("THISISNOTVALID")
1900
+ mock_call.last_dial_status.should be :unknown
1901
+ end
1902
+
1903
+ it 'last_dial_successful? should return true if last_dial_status == :answered' do
1904
+ mock_call.should_receive(:variable).with("DIALSTATUS").once.and_return('ANSWER')
1905
+ mock_call.last_dial_successful?.should be true
1906
+
1907
+ mock_call.should_receive(:variable).with("DIALSTATUS").once.and_return('CHANUNAVAIL')
1908
+ mock_call.last_dial_successful?.should be false
1909
+ end
1910
+
1911
+ it 'last_dial_unsuccessful? should be the opposite of last_dial_successful?' do
1912
+ mock_call.should_receive(:variable).with("DIALSTATUS").once.and_return('ANSWER')
1913
+ mock_call.last_dial_unsuccessful?.should be false
1914
+
1915
+ mock_call.should_receive(:variable).with("DIALSTATUS").once.and_return('CHANUNAVAIL')
1916
+ mock_call.last_dial_unsuccessful?.should be true
1917
+ end
1918
+
1919
+ it 'last_dial_status should not blow up if variable() returns nil. it should return :cancelled' do
1920
+ the_following_code {
1921
+ mock_call.should_receive(:variable).once.with("DIALSTATUS").and_return nil
1922
+ mock_call.last_dial_status.should be :cancelled
1923
+
1924
+ mock_call.should_receive(:variable).once.with("DIALSTATUS").and_return nil
1925
+ mock_call.last_dial_successful?.should be false
1926
+ }.should_not raise_error
1927
+ end
1928
+
1929
+ end
1930
+
1931
+ describe "set_caller_id_number command" do
1932
+ include DialplanCommandTestHelpers
1933
+
1934
+ it "should encapsulate the number with quotes" do
1935
+ caller_id = "14445556666"
1936
+ mock_call.should_receive(:raw_response).once.with(%(SET VARIABLE "CALLERID(num)" "#{caller_id}")).and_return true
1937
+ mock_call.send(:set_caller_id_number, caller_id)
1938
+ end
1939
+ end
1940
+
1941
+ describe 'set_caller_id_name command' do
1942
+ include DialplanCommandTestHelpers
1943
+
1944
+ it "should wrap the name in quotes" do
1945
+ name = "Jay Phillips"
1946
+ mock_call.should_receive(:raw_response).once.with(%(SET VARIABLE "CALLERID(name)" "#{name}")).and_return true
1947
+ mock_call.send(:set_caller_id_name, name)
1948
+ end
1949
+ end
1950
+
1951
+ describe "record command" do
1952
+ include DialplanCommandTestHelpers
1953
+
1954
+ it "executes the command SpeechEngine gives it based on the engine name" do
1955
+ mock_speak_command = "returned command doesn't matter"
1956
+ flexmock(Adhearsion::VoIP::Asterisk::Commands::SpeechEngines).should_receive(:cepstral).
1957
+ once.and_return(mock_speak_command)
1958
+ mock_call.should_receive(:execute).once.with(mock_speak_command)
1959
+ mock_call.speak "Spoken text doesn't matter", :cepstral
1960
+ end
1961
+
1962
+ it "raises an InvalidSpeechEngine exception when the engine is 'none'" do
1963
+ the_following_code {
1964
+ mock_call.speak("o hai!", :none)
1965
+ }.should raise_error Adhearsion::VoIP::Asterisk::Commands::SpeechEngines::InvalidSpeechEngine
1966
+ end
1967
+
1968
+ it "should default its engine to :none" do
1969
+ the_following_code {
1970
+ flexmock(Adhearsion::VoIP::Asterisk::Commands::SpeechEngines).should_receive(:none).once.
1971
+ and_raise(Adhearsion::VoIP::Asterisk::Commands::SpeechEngines::InvalidSpeechEngine)
1972
+ mock_call.speak "ruby ruby ruby ruby!"
1973
+ }.should raise_error Adhearsion::VoIP::Asterisk::Commands::SpeechEngines::InvalidSpeechEngine
1974
+ end
1975
+
1976
+ #it "Properly escapes spoken text" # TODO: What are the escaping needs?
1977
+
1978
+ end
1979
+
1980
+ describe 'The join command' do
1981
+
1982
+ include DialplanCommandTestHelpers
1983
+
1984
+ it "should pass the 'd' flag when no options are given" do
1985
+ conference_id = "123"
1986
+ mock_call.should_receive(:execute).once.with("MeetMe", conference_id, "d", nil)
1987
+ mock_call.join conference_id
1988
+ end
1989
+
1990
+ it "should pass through any given flags with 'd' appended to it if necessary" do
1991
+ conference_id, flags = "1000", "zomgs"
1992
+ mock_call.should_receive(:execute).once.with("MeetMe", conference_id, flags + "d", nil)
1993
+ mock_call.join conference_id, :options => flags
1994
+ end
1995
+
1996
+ it "should NOT pass the 'd' flag when requiring static conferences" do
1997
+ conference_id, options = "1000", {:use_static_conf => true}
1998
+ mock_call.should_receive(:execute).once.with("MeetMe", conference_id, "", nil)
1999
+ mock_call.join conference_id, options
2000
+ end
2001
+
2002
+ it "should raise an ArgumentError when the pin is not numerical" do
2003
+ the_following_code {
2004
+ mock_call.should_receive(:execute).never
2005
+ mock_call.join 3333, :pin => "letters are bad, mkay?!1"
2006
+ }.should raise_error ArgumentError
2007
+ end
2008
+
2009
+ it "should strip out illegal characters from a conference name" do
2010
+ bizarre_conference_name = "a- bc!d&&e--`"
2011
+ normal_conference_name = "abcde"
2012
+ mock_call.should_receive(:execute).twice.with("MeetMe", normal_conference_name, "d", nil)
2013
+
2014
+ mock_call.join bizarre_conference_name
2015
+ mock_call.join normal_conference_name
2016
+ end
2017
+
2018
+ it "should allow textual conference names" do
2019
+ the_following_code {
2020
+ mock_call.should_receive(:execute).once.with_any_args
2021
+ mock_call.join "david bowie's pants"
2022
+ }.should_not raise_error
2023
+ end
2024
+
2025
+ end
2026
+
2027
+
2028
+ describe 'the DialPlan::ConfirmationManager' do
2029
+
2030
+ include ConfirmationManagerTestHelper
2031
+ include DialplanCommandTestHelpers
2032
+
2033
+ attr_reader :example_encoded_hash, :example_encoded_hash_without_macro_name
2034
+ before :each do
2035
+ @example_encoded_hash_without_macro_name = 'timeout:20!play:foo-bar++qaz_qwerty.gsm!key:#'
2036
+ @example_encoded_hash = 'confirm!' + @example_encoded_hash_without_macro_name
2037
+ end
2038
+
2039
+ it '::decode_hash() should convert the String of key/value escaped pairs into a Hash with Symbol keys when the macro name is not given' do
2040
+ Adhearsion::DialPlan::ConfirmationManager.decode_hash(example_encoded_hash).should ==
2041
+ {:timeout => 20, :play => ['foo-bar', 'qaz_qwerty.gsm'], :key => '#'}
2042
+ end
2043
+
2044
+ it '::decode_hash() should convert the String of key/value escaped pairs into a Hash with Symbol keys when the macro name is not given' do
2045
+ Adhearsion::DialPlan::ConfirmationManager.decode_hash(example_encoded_hash_without_macro_name).should ==
2046
+ {:timeout => 20, :play => ['foo-bar', 'qaz_qwerty.gsm'], :key => '#'}
2047
+ end
2048
+
2049
+ it '::decode_hash() should split the sound files in the :play key to an array by splitting by "++"' do
2050
+ decoded_sound_files = Adhearsion::DialPlan::ConfirmationManager.decode_hash(example_encoded_hash)[:play]
2051
+ decoded_sound_files.should be_a_kind_of Array
2052
+ decoded_sound_files.size.should be 2
2053
+ end
2054
+
2055
+ it 'a call to a party which is acknowledged with the proper key during the call to interruptible_play' do
2056
+ variables = {:timeout => 20, :play => ['foo-bar', 'qaz_qwerty.gsm'], :key => '#', :macro => 'confirmer'}
2057
+ encoded_variables = {:network_script => encode_hash(variables)}
2058
+ io_mock = StringIO.new
2059
+
2060
+ mock_call.should_receive(:originating_voip_platform).once.and_return :asterisk
2061
+ mock_call.should_receive(:variables).once.and_return encoded_variables
2062
+
2063
+ sound_files = variables[:play]
2064
+
2065
+ manager = Adhearsion::DialPlan::ConfirmationManager.new(mock_call)
2066
+
2067
+ flexstub(manager).should_receive(:result_digit_from).and_return ?0.ord
2068
+ flexstub(manager).should_receive(:raw_response).and_return nil
2069
+
2070
+ flexmock(manager).should_receive(:answer).once
2071
+ flexmock(manager).should_receive(:interruptible_play).once.with(*sound_files).and_return '#'
2072
+
2073
+ manager.handle
2074
+ end
2075
+
2076
+ it 'when an timeout is encountered, it should set the MACRO_RESULT variable to CONTINUE' do
2077
+ variables = {:timeout => 20, :play => ['foo-bar', 'qaz_qwerty.gsm'], :key => '#', :macro => 'confirmer'}
2078
+ encoded_variables = {:network_script => encode_hash(variables)}
2079
+ io_mock = StringIO.new
2080
+
2081
+ mock_call.should_receive(:originating_voip_platform).once.and_return :asterisk
2082
+ mock_call.should_receive(:variables).once.and_return encoded_variables
2083
+
2084
+ sound_files = variables[:play]
2085
+
2086
+ manager = Adhearsion::DialPlan::ConfirmationManager.new(mock_call)
2087
+
2088
+ flexstub(manager).should_receive(:result_digit_from).and_return ?0.ord
2089
+ flexstub(manager).should_receive(:raw_response).and_return nil
2090
+
2091
+ flexmock(manager).should_receive(:answer).once
2092
+ flexmock(manager).should_receive(:interruptible_play).once.with(*sound_files).and_return nil
2093
+ flexmock(manager).should_receive(:wait_for_digit).once.with(20).and_return nil
2094
+
2095
+ flexmock(manager).should_receive(:variable).once.with("MACRO_RESULT" => 'CONTINUE')
2096
+
2097
+ manager.handle
2098
+ end
2099
+
2100
+ it 'should wait the :timeout number of seconds if no digit was received when playing the files and continue when the right key is pressed' do
2101
+ variables = {:timeout => 20, :play => ['foo-bar', 'qaz_qwerty.gsm'], :key => '#', :macro => 'confirmer'}
2102
+ encoded_variables = {:network_script => encode_hash(variables)}
2103
+ io_mock = StringIO.new
2104
+
2105
+ mock_call.should_receive(:originating_voip_platform).once.and_return :asterisk
2106
+ mock_call.should_receive(:variables).once.and_return encoded_variables
2107
+
2108
+ sound_files = variables[:play]
2109
+
2110
+ manager = Adhearsion::DialPlan::ConfirmationManager.new(mock_call)
2111
+
2112
+ flexstub(manager).should_receive(:result_digit_from).and_return ?0.ord
2113
+ flexstub(manager).should_receive(:raw_response).and_return nil
2114
+
2115
+ flexmock(manager).should_receive(:answer).once
2116
+ flexmock(manager).should_receive(:interruptible_play).once.with(*sound_files).and_return nil
2117
+ flexmock(manager).should_receive(:wait_for_digit).once.with(20).and_return '#'
2118
+
2119
+ manager.handle
2120
+ end
2121
+
2122
+ it 'should restart playback if the key received was not recognized' do
2123
+ variables = {:timeout => 20, :play => ['foo-bar', 'qaz_qwerty.gsm'], :key => '2', :macro => 'confirmer'}
2124
+ encoded_variables = {:network_script => encode_hash(variables)}
2125
+ io_mock = StringIO.new
2126
+
2127
+ mock_call.should_receive(:originating_voip_platform).once.and_return :asterisk
2128
+ mock_call.should_receive(:variables).once.and_return encoded_variables
2129
+
2130
+ sound_files = variables[:play]
2131
+
2132
+ manager = Adhearsion::DialPlan::ConfirmationManager.new(mock_call)
2133
+
2134
+ flexstub(manager).should_receive(:result_digit_from).and_return ?0.ord
2135
+ flexstub(manager).should_receive(:raw_response).and_return nil
2136
+
2137
+ flexmock(manager).should_receive(:answer).once
2138
+ flexmock(manager).should_receive(:interruptible_play).once.with(*sound_files).and_return '3' # not :key
2139
+ flexmock(manager).should_receive(:interruptible_play).once.with(*sound_files).and_return '#' # not :key
2140
+ flexmock(manager).should_receive(:interruptible_play).once.with(*sound_files).and_return '1' # not :key
2141
+ flexmock(manager).should_receive(:interruptible_play).once.with(*sound_files).and_return '2' # matches :key
2142
+
2143
+ flexmock(manager).should_receive(:wait_for_digit).never # We never let it get to the point where it may timeout
2144
+ flexmock(manager).should_receive(:variable).never # We succeed by not setting the MACRO_RESULT variable
2145
+
2146
+ manager.handle
2147
+ end
2148
+
2149
+ end
2150
+
2151
+ describe 'say_phonetic command' do
2152
+ include DialplanCommandTestHelpers
2153
+ it 'Can execute the sayphonetic application using say_phonetic' do
2154
+ text = 'Say This'
2155
+ mock_call.should_receive(:execute).once.with("sayphonetic", text)
2156
+ mock_call.say_phonetic text
2157
+ end
2158
+
2159
+ it 'Can use special characters with say_phonetic' do
2160
+ text = '*Say This!*'
2161
+ mock_call.should_receive(:execute).once.with("sayphonetic", text)
2162
+ mock_call.say_phonetic text
2163
+ end
2164
+ end
2165
+
2166
+ describe 'say_chars command' do
2167
+ include DialplanCommandTestHelpers
2168
+ it 'Can execute the sayalpha application using say_chars' do
2169
+ text = 'ha124d9'
2170
+ mock_call.should_receive(:execute).once.with("sayalpha", text)
2171
+ mock_call.say_chars text
2172
+ end
2173
+
2174
+ it 'Can use special characters with say_chars' do
2175
+ text = "1a2.#"
2176
+ mock_call.should_receive(:execute).once.with("sayalpha", text)
2177
+ mock_call.say_chars text
2178
+ end
2179
+ end