eric-adhearsion 0.7.999

Sign up to get free protection for your applications and to get access to all the features.
Files changed (150) hide show
  1. data/CHANGELOG +3 -0
  2. data/LICENSE +456 -0
  3. data/Manifest.txt +149 -0
  4. data/README.txt +6 -0
  5. data/Rakefile +48 -0
  6. data/ahn_generators/component/USAGE +5 -0
  7. data/ahn_generators/component/component_generator.rb +57 -0
  8. data/ahn_generators/component/templates/configuration.rb +0 -0
  9. data/ahn_generators/component/templates/lib/lib.rb.erb +3 -0
  10. data/ahn_generators/component/templates/test/test.rb.erb +12 -0
  11. data/ahn_generators/component/templates/test/test_helper.rb +14 -0
  12. data/app_generators/ahn/USAGE +5 -0
  13. data/app_generators/ahn/ahn_generator.rb +76 -0
  14. data/app_generators/ahn/templates/.ahnrc +12 -0
  15. data/app_generators/ahn/templates/README +8 -0
  16. data/app_generators/ahn/templates/Rakefile +3 -0
  17. data/app_generators/ahn/templates/components/simon_game/configuration.rb +0 -0
  18. data/app_generators/ahn/templates/components/simon_game/lib/simon_game.rb +61 -0
  19. data/app_generators/ahn/templates/components/simon_game/test/test_helper.rb +14 -0
  20. data/app_generators/ahn/templates/components/simon_game/test/test_simon_game.rb +31 -0
  21. data/app_generators/ahn/templates/config/startup.rb +53 -0
  22. data/app_generators/ahn/templates/dialplan.rb +4 -0
  23. data/bin/ahn +28 -0
  24. data/bin/ahnctl +68 -0
  25. data/bin/jahn +32 -0
  26. data/lib/adhearsion/blank_slate.rb +5 -0
  27. data/lib/adhearsion/cli.rb +106 -0
  28. data/lib/adhearsion/component_manager.rb +277 -0
  29. data/lib/adhearsion/core_extensions/all.rb +9 -0
  30. data/lib/adhearsion/core_extensions/array.rb +0 -0
  31. data/lib/adhearsion/core_extensions/custom_daemonizer.rb +45 -0
  32. data/lib/adhearsion/core_extensions/global.rb +1 -0
  33. data/lib/adhearsion/core_extensions/guid.rb +5 -0
  34. data/lib/adhearsion/core_extensions/hash.rb +0 -0
  35. data/lib/adhearsion/core_extensions/metaprogramming.rb +17 -0
  36. data/lib/adhearsion/core_extensions/numeric.rb +4 -0
  37. data/lib/adhearsion/core_extensions/proc.rb +0 -0
  38. data/lib/adhearsion/core_extensions/pseudo_uuid.rb +11 -0
  39. data/lib/adhearsion/core_extensions/publishable.rb +73 -0
  40. data/lib/adhearsion/core_extensions/relationship_properties.rb +40 -0
  41. data/lib/adhearsion/core_extensions/string.rb +26 -0
  42. data/lib/adhearsion/core_extensions/thread.rb +13 -0
  43. data/lib/adhearsion/core_extensions/thread_safety.rb +7 -0
  44. data/lib/adhearsion/core_extensions/time.rb +0 -0
  45. data/lib/adhearsion/distributed/gateways/dbus_gateway.rb +0 -0
  46. data/lib/adhearsion/distributed/gateways/osa_gateway.rb +0 -0
  47. data/lib/adhearsion/distributed/gateways/rest_gateway.rb +9 -0
  48. data/lib/adhearsion/distributed/gateways/soap_gateway.rb +9 -0
  49. data/lib/adhearsion/distributed/gateways/xmlrpc_gateway.rb +9 -0
  50. data/lib/adhearsion/distributed/peer_finder.rb +0 -0
  51. data/lib/adhearsion/distributed/remote_cli.rb +0 -0
  52. data/lib/adhearsion/hooks.rb +57 -0
  53. data/lib/adhearsion/host_definitions.rb +63 -0
  54. data/lib/adhearsion/initializer/asterisk.rb +59 -0
  55. data/lib/adhearsion/initializer/configuration.rb +202 -0
  56. data/lib/adhearsion/initializer/database.rb +92 -0
  57. data/lib/adhearsion/initializer/drb.rb +25 -0
  58. data/lib/adhearsion/initializer/freeswitch.rb +22 -0
  59. data/lib/adhearsion/initializer/paths.rb +55 -0
  60. data/lib/adhearsion/initializer/rails.rb +40 -0
  61. data/lib/adhearsion/initializer.rb +217 -0
  62. data/lib/adhearsion/logging.rb +92 -0
  63. data/lib/adhearsion/services/scheduler.rb +5 -0
  64. data/lib/adhearsion/tasks/database.rb +5 -0
  65. data/lib/adhearsion/tasks/generating.rb +20 -0
  66. data/lib/adhearsion/tasks/lint.rb +4 -0
  67. data/lib/adhearsion/tasks/testing.rb +37 -0
  68. data/lib/adhearsion/tasks.rb +15 -0
  69. data/lib/adhearsion/version.rb +9 -0
  70. data/lib/adhearsion/voip/asterisk/agi_server.rb +78 -0
  71. data/lib/adhearsion/voip/asterisk/ami/actions.rb +238 -0
  72. data/lib/adhearsion/voip/asterisk/ami/machine.rb +871 -0
  73. data/lib/adhearsion/voip/asterisk/ami/machine.rl +109 -0
  74. data/lib/adhearsion/voip/asterisk/ami/parser.rb +262 -0
  75. data/lib/adhearsion/voip/asterisk/ami.rb +147 -0
  76. data/lib/adhearsion/voip/asterisk/commands.rb +1182 -0
  77. data/lib/adhearsion/voip/asterisk/config_generators/agents.conf.rb +140 -0
  78. data/lib/adhearsion/voip/asterisk/config_generators/config_generator.rb +101 -0
  79. data/lib/adhearsion/voip/asterisk/config_generators/queues.conf.rb +250 -0
  80. data/lib/adhearsion/voip/asterisk/config_generators/voicemail.conf.rb +240 -0
  81. data/lib/adhearsion/voip/asterisk/config_manager.rb +71 -0
  82. data/lib/adhearsion/voip/asterisk/special_dial_plan_managers.rb +80 -0
  83. data/lib/adhearsion/voip/asterisk.rb +4 -0
  84. data/lib/adhearsion/voip/call.rb +391 -0
  85. data/lib/adhearsion/voip/call_routing.rb +64 -0
  86. data/lib/adhearsion/voip/commands.rb +9 -0
  87. data/lib/adhearsion/voip/constants.rb +39 -0
  88. data/lib/adhearsion/voip/conveniences.rb +18 -0
  89. data/lib/adhearsion/voip/dial_plan.rb +205 -0
  90. data/lib/adhearsion/voip/dsl/dialing_dsl/dialing_dsl_monkey_patches.rb +37 -0
  91. data/lib/adhearsion/voip/dsl/dialing_dsl.rb +151 -0
  92. data/lib/adhearsion/voip/dsl/dialplan/control_passing_exception.rb +27 -0
  93. data/lib/adhearsion/voip/dsl/dialplan/dispatcher.rb +124 -0
  94. data/lib/adhearsion/voip/dsl/dialplan/parser.rb +75 -0
  95. data/lib/adhearsion/voip/dsl/dialplan/thread_mixin.rb +16 -0
  96. data/lib/adhearsion/voip/dsl/numerical_string.rb +117 -0
  97. data/lib/adhearsion/voip/freeswitch/basic_connection_manager.rb +48 -0
  98. data/lib/adhearsion/voip/freeswitch/event_handler.rb +58 -0
  99. data/lib/adhearsion/voip/freeswitch/freeswitch_dialplan_command_factory.rb +129 -0
  100. data/lib/adhearsion/voip/freeswitch/inbound_connection_manager.rb +38 -0
  101. data/lib/adhearsion/voip/freeswitch/oes_server.rb +195 -0
  102. data/lib/adhearsion/voip/menu_state_machine/calculated_match.rb +80 -0
  103. data/lib/adhearsion/voip/menu_state_machine/matchers.rb +123 -0
  104. data/lib/adhearsion/voip/menu_state_machine/menu_builder.rb +58 -0
  105. data/lib/adhearsion/voip/menu_state_machine/menu_class.rb +149 -0
  106. data/lib/adhearsion.rb +31 -0
  107. data/script/destroy +14 -0
  108. data/script/generate +14 -0
  109. data/spec/fixtures/dialplan.rb +3 -0
  110. data/spec/initializer/test_configuration.rb +267 -0
  111. data/spec/initializer/test_loading.rb +162 -0
  112. data/spec/initializer/test_paths.rb +43 -0
  113. data/spec/silence.rb +10 -0
  114. data/spec/test_ahn_command.rb +149 -0
  115. data/spec/test_code_quality.rb +87 -0
  116. data/spec/test_component_manager.rb +97 -0
  117. data/spec/test_constants.rb +8 -0
  118. data/spec/test_drb.rb +104 -0
  119. data/spec/test_helper.rb +94 -0
  120. data/spec/test_hooks.rb +37 -0
  121. data/spec/test_host_definitions.rb +79 -0
  122. data/spec/test_initialization.rb +105 -0
  123. data/spec/test_logging.rb +80 -0
  124. data/spec/test_relationship_properties.rb +54 -0
  125. data/spec/voip/asterisk/ami_response_definitions.rb +23 -0
  126. data/spec/voip/asterisk/config_file_generators/test_agents.rb +253 -0
  127. data/spec/voip/asterisk/config_file_generators/test_queues.rb +325 -0
  128. data/spec/voip/asterisk/config_file_generators/test_voicemail.rb +306 -0
  129. data/spec/voip/asterisk/menu_command/test_calculated_match.rb +111 -0
  130. data/spec/voip/asterisk/menu_command/test_matchers.rb +98 -0
  131. data/spec/voip/asterisk/mock_ami_server.rb +176 -0
  132. data/spec/voip/asterisk/test_agi_server.rb +451 -0
  133. data/spec/voip/asterisk/test_ami.rb +227 -0
  134. data/spec/voip/asterisk/test_commands.rb +2006 -0
  135. data/spec/voip/asterisk/test_config_manager.rb +129 -0
  136. data/spec/voip/dsl/dispatcher_spec_helper.rb +45 -0
  137. data/spec/voip/dsl/test_dialing_dsl.rb +268 -0
  138. data/spec/voip/dsl/test_dispatcher.rb +82 -0
  139. data/spec/voip/dsl/test_parser.rb +87 -0
  140. data/spec/voip/freeswitch/test_basic_connection_manager.rb +39 -0
  141. data/spec/voip/freeswitch/test_inbound_connection_manager.rb +39 -0
  142. data/spec/voip/freeswitch/test_oes_server.rb +9 -0
  143. data/spec/voip/test_call_routing.rb +127 -0
  144. data/spec/voip/test_dialplan_manager.rb +372 -0
  145. data/spec/voip/test_numerical_string.rb +48 -0
  146. data/spec/voip/test_phone_number.rb +36 -0
  147. data/test/test_ahn_generator.rb +59 -0
  148. data/test/test_component_generator.rb +52 -0
  149. data/test/test_generator_helper.rb +20 -0
  150. 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
+ }