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