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,451 @@
1
+ require File.dirname(__FILE__) + "/../../test_helper"
2
+ require 'adhearsion/voip/asterisk'
3
+
4
+ context "The AGI server's serve() method" do
5
+
6
+ include AgiServerTestHelper
7
+
8
+ attr_reader :server_class, :server
9
+ before :each do
10
+ @server_class = Adhearsion::VoIP::Asterisk::AGI::Server::RubyServer
11
+ @server = @server_class.new(:port,:host)
12
+ end
13
+
14
+ test 'should instantiate a new Call with the IO object it receives' do
15
+ stub_before_call_hooks!
16
+ io_mock = flexmock "Mock IO object that's passed to the serve() method"
17
+ call_mock = flexmock "A Call mock that's returned by Adhearsion#receive_call_from", :variable => {}
18
+ flexstub(server_class).should_receive(:ahn_log)
19
+ the_following_code {
20
+ flexmock(Adhearsion).should_receive(:receive_call_from).once.with(io_mock).and_throw :created_call!
21
+ server.serve(io_mock)
22
+ }.should.throw :created_call!
23
+ end
24
+
25
+ test 'should hand the call off to a new Manager if the request is agi://IP_ADDRESS_HERE' do
26
+ stub_before_call_hooks!
27
+ call_mock = flexmock 'A new mock call that will be passed to the manager', :variables => {}
28
+
29
+ flexmock(Adhearsion).should_receive(:receive_call_from).once.and_return call_mock
30
+ manager_mock = flexmock 'a mock dialplan manager'
31
+ manager_mock.should_receive(:handle).once.with(call_mock)
32
+ flexmock(Adhearsion::DialPlan::Manager).should_receive(:new).once.and_return manager_mock
33
+ server.serve(nil)
34
+ end
35
+
36
+ test 'should hand off a call to a ConfirmationManager if the request begins with confirm!' do
37
+ confirm_options = Adhearsion::DialPlan::ConfirmationManager.encode_hash_for_dial_macro_argument :timeout => 20, :key => "#"
38
+ call_mock = flexmock "a call that has network_script as a variable", :variables => {:network_script => "confirm!#{confirm_options[/^M\(\^?(.+)\)$/,1]}"}
39
+ manager_mock = flexmock 'a mock ConfirmationManager'
40
+
41
+ the_following_code {
42
+ flexstub(Adhearsion).should_receive(:receive_call_from).once.and_return(call_mock)
43
+ flexmock(Adhearsion::DialPlan::ConfirmationManager).should_receive(:confirmation_call?).once.with(call_mock).and_return true
44
+ flexmock(Adhearsion::DialPlan::ConfirmationManager).should_receive(:handle).once.with(call_mock).and_throw :handled_call!
45
+ server.serve(nil)
46
+ }.should.throw :handled_call!
47
+ end
48
+
49
+ test 'calling the serve() method invokes any BeforeCall hooks' do
50
+ flexmock(Adhearsion::Hooks::BeforeCall).should_receive(:trigger_hooks).once.and_throw :before_call_hooks_executed
51
+ assert_throws :before_call_hooks_executed do
52
+ server.serve nil
53
+ end
54
+ end
55
+
56
+ test 'should execute the OnHungupCall hooks when a HungupExtensionCallException is raised' do
57
+ call_mock = flexmock 'a bogus call', :hungup_call? => true, :variables => {:extension => "h"}
58
+ mock_env = flexmock "A mock execution environment which gets passed along in the HungupExtensionCallException"
59
+
60
+ stub_confirmation_manager!
61
+ flexstub(Adhearsion).should_receive(:receive_call_from).once.and_return(call_mock)
62
+ flexmock(Adhearsion::DialPlan::Manager).should_receive(:handle).once.and_raise Adhearsion::HungupExtensionCallException.new(mock_env)
63
+ flexmock(Adhearsion::Hooks::OnHungupCall).should_receive(:trigger_hooks).once.with(mock_env).and_throw :hungup_call
64
+
65
+ the_following_code { server.serve nil }.should.throw :hungup_call
66
+ end
67
+
68
+ test 'should execute the OnFailedCall hooks when a FailedExtensionCallException is raised' do
69
+ call_mock = flexmock 'a bogus call', :failed_call? => true, :variables => {:extension => "failed"}
70
+ mock_env = flexmock "A mock execution environment which gets passed along in the HungupExtensionCallException", :failed_reason => "does not matter"
71
+
72
+ server = Adhearsion::VoIP::Asterisk::AGI::Server::RubyServer.new :port, :host
73
+
74
+ flexmock(Adhearsion).should_receive(:receive_call_from).once.and_return(call_mock)
75
+ flexmock(Adhearsion::DialPlan::Manager).should_receive(:handle).once.and_raise Adhearsion::FailedExtensionCallException.new(mock_env)
76
+ flexmock(Adhearsion::Hooks::OnFailedCall).should_receive(:trigger_hooks).once.with(mock_env).and_throw :failed_call
77
+ the_following_code { server.serve nil }.should.throw :failed_call
78
+ end
79
+
80
+ end
81
+
82
+ context "Active Calls" do
83
+ include CallVariableTestHelper
84
+ attr_accessor :typical_call
85
+
86
+ before do
87
+ @mock_io = StringIO.new
88
+ @typical_call = Adhearsion::Call.new(@mock_io, typical_call_variables_hash)
89
+ end
90
+
91
+ after do
92
+ Adhearsion::active_calls.clear!
93
+ end
94
+
95
+ test 'A Call object can be instantiated with a hash of attributes' do
96
+ the_following_code {
97
+ Adhearsion::Call.new(@mock_io, {})
98
+ }.should.not.raise
99
+ end
100
+
101
+ test 'Attributes passed into initialization of call object are accessible as attributes on the object' do
102
+ Adhearsion::Call.new(@mock_io, {:channel => typical_call_variables_hash[:channel]}).channel.should == typical_call_variables_hash[:channel]
103
+ end
104
+
105
+ test 'Attributes passed into initialization of call object are accessible in the variables() Hash' do
106
+ Adhearsion::Call.new(@mock_io, typical_call_variables_hash).variables.should.equal typical_call_variables_hash
107
+ end
108
+
109
+ test 'Can add a call to the active calls list' do
110
+ Adhearsion.active_calls.should.not.be.any
111
+ Adhearsion.active_calls << typical_call
112
+ Adhearsion.active_calls.size.should == 1
113
+ end
114
+
115
+ test 'A hungup call removes itself from the active calls' do
116
+ mock_io = flexmock typical_call_variable_io
117
+ mock_io.should_receive(:close).once
118
+
119
+ size_before = Adhearsion.active_calls.size
120
+
121
+ call = Adhearsion.receive_call_from mock_io
122
+ Adhearsion.active_calls.size.should.be > size_before
123
+ call.hangup!
124
+ Adhearsion.active_calls.size.should == size_before
125
+ end
126
+
127
+ test 'Can find active call by unique ID' do
128
+ Adhearsion.active_calls << typical_call
129
+ assert_not_nil Adhearsion.active_calls.find(typical_call_variables_hash[:channel])
130
+ end
131
+
132
+ test 'A call can store the IO associated with the PBX/switch connection' do
133
+ Adhearsion::Call.new(@mock_io, {}).should.respond_to(:io)
134
+ end
135
+
136
+ test 'A call can be instantiated given a PBX/switch IO' do
137
+ call = Adhearsion::Call.receive_from(typical_call_variable_io)
138
+ call.should.be.kind_of(Adhearsion::Call)
139
+ call.channel.should == typical_call_variables_hash[:channel]
140
+ end
141
+
142
+ test 'A call with an extension of "t" raises a UselessCallException' do
143
+ the_following_code {
144
+ Adhearsion::Call.new(@mock_io, typical_call_variables_hash.merge(:extension => 't'))
145
+ }.should.raise(Adhearsion::UselessCallException)
146
+ end
147
+
148
+ test 'Can create a call and add it via a top-level method on the Adhearsion module' do
149
+ assert !Adhearsion.active_calls.any?
150
+ call = Adhearsion.receive_call_from(typical_call_variable_io)
151
+ call.should.be.kind_of(Adhearsion::Call)
152
+ Adhearsion.active_calls.size.should == 1
153
+ end
154
+
155
+ test 'A call can identify its originating voip platform' do
156
+ call = Adhearsion::receive_call_from(typical_call_variable_io)
157
+ call.originating_voip_platform.should.equal(:asterisk)
158
+ end
159
+
160
+ end
161
+
162
+ context 'A new Call object' do
163
+
164
+ include CallVariableTestHelper
165
+
166
+ test "it should have an @inbox object that's a synchronized Queue" do
167
+ new_call = Adhearsion::Call.new(nil, {})
168
+ new_call.inbox.should.be.kind_of Queue
169
+ new_call.should.respond_to :<<
170
+ end
171
+
172
+ test 'the unique_identifier() method should return the :channel variable for :asterisk calls' do
173
+ variables = typical_call_variables_hash
174
+ new_call = Adhearsion::Call.new(nil, typical_call_variables_hash)
175
+ flexmock(new_call).should_receive(:originating_voip_platform).and_return :asterisk
176
+ new_call.unique_identifier.should == variables[:channel]
177
+ end
178
+
179
+ test "Call#define_singleton_accessor_with_pair should define a singleton method, not a class method" do
180
+ control = Adhearsion::Call.new(nil, {})
181
+ experiment = Adhearsion::Call.new(nil, {})
182
+
183
+ experiment.send(:define_singleton_accessor_with_pair, "ohai", 123)
184
+ experiment.should.respond_to "ohai"
185
+ control.should.not.respond_to "ohai"
186
+ end
187
+
188
+ end
189
+
190
+ context 'the Calls collection' do
191
+
192
+ include CallVariableTestHelper
193
+
194
+ test 'the #<< method should add a Call to the Hash with its unique_id' do
195
+ id = rand
196
+ collection = Adhearsion::Calls.new
197
+ call = Adhearsion::Call.new(nil, {})
198
+ flexmock(call).should_receive(:unique_identifier).and_return id
199
+ collection << call
200
+ hash = collection.instance_variable_get("@calls")
201
+ hash.should.not.be.empty
202
+ hash[id].should.equal call
203
+ end
204
+
205
+ test '#size should return the size of the Hash' do
206
+ collection = Adhearsion::Calls.new
207
+ collection.size.should.equal 0
208
+ collection << Adhearsion::Call.new(nil, {})
209
+ collection.size.should.equal 1
210
+ end
211
+
212
+ test '#remove_inactive_call should delete the call in the Hash' do
213
+ collection = Adhearsion::Calls.new
214
+
215
+ number_of_calls = 10
216
+ unique_ids = Array.new(number_of_calls) { rand }
217
+ calls = unique_ids.map { |id| Adhearsion::Call.new(nil, {:uniqueid => id}) }
218
+ calls.each { |call| collection << call }
219
+
220
+ deleted_call = calls[number_of_calls / 2]
221
+ collection.remove_inactive_call deleted_call
222
+ collection.size.should.equal number_of_calls - 1
223
+ end
224
+
225
+ test '#find_by_unique_id should pull the Call from the Hash using the unique_id' do
226
+ id = rand
227
+ call_database = flexmock "a mock Hash in which calls are stored"
228
+ call_database.should_receive(:[]).once.with(id)
229
+ collection = Adhearsion::Calls.new
230
+ flexmock(collection).should_receive(:calls).once.and_return(call_database)
231
+ collection.find(id)
232
+ end
233
+
234
+ end
235
+
236
+ context 'Typical call variable parsing with typical data that has no special treatment' do
237
+ include CallVariableTestHelper
238
+ attr_reader :variables, :io
239
+
240
+ setup do
241
+ @io = typical_call_variable_io
242
+ @variables = parsed_call_variables_from(io)
243
+ end
244
+
245
+ # test "listing all variable names" do
246
+ # parser.variable_names.map(&:to_s).sort.should == typical_call_variables_hash.keys.map(&:to_s).sort
247
+ # end
248
+
249
+ test "extracts call variables into a Hash" do
250
+ assert variables.kind_of?(Hash)
251
+ end
252
+
253
+ test "extracting and converting variables and their values to a hash" do
254
+ variables.should == typical_call_variables_hash
255
+ end
256
+ end
257
+
258
+ context 'Call variable parsing with data that is treated specially' do
259
+ include CallVariableTestHelper
260
+ test "callington is renamed to type_of_calling_number" do
261
+ variables = parsed_call_variables_from typical_call_variable_io
262
+ variables[:type_of_calling_number].should == :unknown
263
+ variables.has_key?(:callington).should == false
264
+ end
265
+ test "normalizes the context to be a valid Ruby method name"
266
+ test "value of 'no' converts to false" do
267
+ merged_hash_with_call_variables(:foo => "no")[:foo].should.equal(false)
268
+ end
269
+ test "value of 'yes' converts to true"
270
+ test "value of 'unknown' converts to nil"
271
+ test 'separate_line_into_key_value_pair parses values with spaces in them' do
272
+ key, value = Adhearsion::Call::Variables::Parser.separate_line_into_key_value_pair("foo: My Name")
273
+ value.should == 'My Name'
274
+ end
275
+ end
276
+
277
+ context 'Typical call variable line parsing with a typical line that is not treated specially' do
278
+ include CallVariableTestHelper
279
+ attr_reader :parser, :line
280
+
281
+ setup do
282
+ @line = 'agi_channel: SIP/marcel-b58046e0'
283
+ @key, @value = Adhearsion::Call::Variables::Parser.separate_line_into_key_value_pair line
284
+ end
285
+
286
+ test "raw name is extracted correctly" do
287
+ @key.should == 'agi_channel'
288
+ end
289
+
290
+ test "raw value is extracted correctly" do
291
+ @value.should == 'SIP/marcel-b58046e0'
292
+ end
293
+ end
294
+
295
+ context 'Call variable line parsing with a line that is treated specially' do
296
+ include CallVariableTestHelper
297
+ attr_reader :key, :value, :line
298
+
299
+ setup do
300
+ @line = 'agi_request: agi://10.0.0.152'
301
+ @key, @value = Adhearsion::Call::Variables::Parser.separate_line_into_key_value_pair line
302
+ end
303
+
304
+ test "splits out name and value correctly even if the value contains a semicolon (i.e. the same character that is used as the name/value separators)" do
305
+ @key.should == 'agi_request'
306
+ @value.should == 'agi://10.0.0.152'
307
+ end
308
+
309
+ end
310
+
311
+ context "Extracting the query from the request URI" do
312
+ include CallVariableTestHelper
313
+ attr_reader :parser
314
+
315
+ setup do
316
+ # We don't want this Call::Variables::Parser to call parse because we only care about handling the request
317
+ # variable which we mock here.
318
+ @parser = Adhearsion::Call::Variables::Parser.new(StringIO.new('This argument does not matter'))
319
+ end
320
+
321
+ test "an empty hash is returned if there is no query string" do
322
+ parsed_call_variables_with_query('').should == {}
323
+ end
324
+
325
+ test "a key and value for parameter is returned when there is one query string parameter" do
326
+ parsed_call_variables_with_query('?foo=bar').should == {'foo' => 'bar'}
327
+ end
328
+
329
+ test "both key/value pairs are returned when there are a pair of query string parameters" do
330
+ parsed_call_variables_with_query('?foo=bar&baz=quux').should == {'foo' => 'bar', 'baz' => 'quux'}
331
+ end
332
+
333
+ test "all key/value pairs are returned when there are more than a pair of query string parameters" do
334
+ parsed_call_variables_with_query('?foo=bar&baz=quux&name=marcel').should == {'foo' => 'bar', 'baz' => 'quux', 'name' => 'marcel'}
335
+ end
336
+
337
+ end
338
+
339
+ BEGIN {
340
+ module CallVariableTestHelper
341
+ def parsed_call_variables_from(io)
342
+ Adhearsion::Call::Variables::Parser.parse(io).variables
343
+ end
344
+
345
+ def coerce_call_variables(variables)
346
+ Adhearsion::Call::Variables::Parser.coerce_variables(variables)
347
+ end
348
+
349
+ def merged_hash_with_call_variables(new_hash)
350
+ call_variables_with_new_query = typical_call_variables_hash.merge new_hash
351
+ coerce_call_variables call_variables_with_new_query
352
+ end
353
+
354
+ def parsed_call_variables_with_query(query_string)
355
+ request_string = "agi://10.0.0.0/#{query_string}"
356
+ merged_hash_with_call_variables({:request => request_string})[:query]
357
+ end
358
+
359
+ def typical_call_variable_io
360
+ StringIO.new(typical_call_variable_section)
361
+ end
362
+
363
+ def typical_call_variable_section
364
+ <<-VARIABLES
365
+ agi_network: yes
366
+ agi_request: agi://10.0.0.152/monkey?foo=bar&qaz=qwerty
367
+ agi_channel: SIP/marcel-b58046e0
368
+ agi_language: en
369
+ agi_type: SIP
370
+ agi_uniqueid: 1191245124.16
371
+ agi_callerid: 011441234567899
372
+ agi_calleridname: unknown
373
+ agi_callingpres: 0
374
+ agi_callingani2: 0
375
+ agi_callington: 0
376
+ agi_callingtns: 0
377
+ agi_dnid: 911
378
+ agi_rdnis: unknown
379
+ agi_context: adhearsion
380
+ agi_extension: 911
381
+ agi_priority: 1
382
+ agi_enhanced: 0.0
383
+ agi_accountcode:
384
+
385
+ VARIABLES
386
+ end
387
+
388
+ # TODO:
389
+ # - "unknown" should be converted to nil
390
+ # - "yes" or "no" should be converted to true or false
391
+ # - numbers beginning with a 0 MUST be converted to a NumericalString
392
+ # - Look up why there are so many zeroes. They're likely reprentative of some PRI definition.
393
+
394
+ def typical_call_variables_hash
395
+ uncoerced_variable_map = expected_uncoerced_variable_map
396
+
397
+ uncoerced_variable_map[:request] = URI.parse(uncoerced_variable_map[:request])
398
+ uncoerced_variable_map[:extension] = 911 # Adhearsion::VoIP::DSL::PhoneNumber.new(uncoerced_variable_map[:extension]) #
399
+ uncoerced_variable_map[:callerid] = Adhearsion::VoIP::DSL::NumericalString.new(uncoerced_variable_map[:callerid])
400
+
401
+ uncoerced_variable_map[:network] = true
402
+ uncoerced_variable_map[:calleridname] = nil
403
+ uncoerced_variable_map[:callingpres] = 0
404
+ uncoerced_variable_map[:callingani2] = 0
405
+ uncoerced_variable_map[:callingtns] = 0
406
+ uncoerced_variable_map[:dnid] = 911
407
+ uncoerced_variable_map[:rdnis] = nil
408
+ uncoerced_variable_map[:priority] = 1
409
+ uncoerced_variable_map[:enhanced] = 0.0
410
+ uncoerced_variable_map[:uniqueid] = 1191245124.16
411
+
412
+ uncoerced_variable_map[:type_of_calling_number] = Adhearsion::VoIP::Constants::Q931_TYPE_OF_NUMBER[uncoerced_variable_map.delete(:callington).to_i]
413
+
414
+ coerced_variable_map = uncoerced_variable_map
415
+ coerced_variable_map[:query] = {"foo" => "bar", "qaz" => "qwerty"}
416
+ coerced_variable_map
417
+ end
418
+
419
+ def expected_uncoerced_variable_map
420
+ {:network => 'yes',
421
+ :request => 'agi://10.0.0.152/monkey?foo=bar&qaz=qwerty',
422
+ :channel => 'SIP/marcel-b58046e0',
423
+ :language => 'en',
424
+ :type => 'SIP',
425
+ :uniqueid => '1191245124.16',
426
+ :callerid => '011441234567899',
427
+ :calleridname => 'unknown',
428
+ :callingpres => '0',
429
+ :callingani2 => '0',
430
+ :callington => '0',
431
+ :callingtns => '0',
432
+ :dnid => '911',
433
+ :rdnis => 'unknown',
434
+ :context => 'adhearsion',
435
+ :extension => '911',
436
+ :priority => '1',
437
+ :enhanced => '0.0',
438
+ :accountcode => ''}
439
+ end
440
+ end
441
+
442
+ module AgiServerTestHelper
443
+ def stub_before_call_hooks!
444
+ flexstub(Adhearsion::Hooks::BeforeCall).should_receive :trigger_hooks
445
+ end
446
+
447
+ def stub_confirmation_manager!
448
+ flexstub(Adhearsion::DialPlan::ConfirmationManager).should_receive(:confirmation_call?).and_return false
449
+ end
450
+ end
451
+ }
@@ -0,0 +1,227 @@
1
+ require File.dirname(__FILE__) + "/../../test_helper"
2
+ require File.dirname(__FILE__) + "/mock_ami_server"
3
+
4
+ require 'adhearsion'
5
+ require 'adhearsion/voip/asterisk/ami'
6
+
7
+ context "Connecting via AMI" do
8
+ test "should raise an exception if the password was invalid" do
9
+ host, port = "localhost", 5038
10
+ ami = Adhearsion::VoIP::Asterisk::AMI.new "admin", "bad_password", "localhost", :port => port
11
+
12
+ ami_server = AmiServer.new
13
+ flexmock(TCPSocket).should_receive(:new).once.with(host, port).and_return(ami_server)
14
+ flexmock(IO).should_receive(:select).at_least.once.with([ami_server], nil, nil, 1.0).and_return(true)
15
+
16
+ the_following_code do
17
+ ami.connect!
18
+ end.should.raise Adhearsion::VoIP::Asterisk::AMI::AuthenticationFailedException
19
+ ami.disconnect!
20
+ end
21
+
22
+ test "should discover its own permissions and make them available as connection attributes"
23
+
24
+ # TODO: DRY up the following two specs
25
+ test "should start a new thread if events are enabled" do
26
+ host, port = "localhost", 5038
27
+ ami = Adhearsion::VoIP::Asterisk::AMI.new "admin", "password", "localhost", :port => port, :events => true
28
+
29
+ ami_server = AmiServer.new
30
+ flexmock(TCPSocket).should_receive(:new).once.with(host, port).and_return(ami_server)
31
+ flexmock(IO).should_receive(:select).at_least.once.with([ami_server], nil, nil, 1.0).and_return(true)
32
+
33
+ ami.connect!
34
+ ami.instance_eval { meta_eval { attr_accessor :event_thread } }
35
+ ami.event_thread.should.be.a.kind_of Thread
36
+ ami.disconnect!
37
+ end
38
+
39
+ test "should find the Asterisk version when connecting" do
40
+ host, port = "localhost", 5038
41
+ ami = Adhearsion::VoIP::Asterisk::AMI.new "admin", "password", "localhost", :port => port
42
+
43
+ ami_server = AmiServer.new
44
+ flexmock(TCPSocket).should_receive(:new).once.with(host, port).and_return(ami_server)
45
+ flexmock(IO).should_receive(:select).at_least.once.with([ami_server], nil, nil, 1.0).and_return(true)
46
+
47
+ ami.connect!
48
+ ami.version.should == "1.0"
49
+ ami.disconnect!
50
+ end
51
+ end
52
+
53
+ context "The AMI command interface" do
54
+ before do
55
+ host, port = "localhost", 5038
56
+ @ami = Adhearsion::VoIP::Asterisk::AMI.new "admin", "password", host, :port => port, :events => false
57
+
58
+ ami_server = AmiServer.new
59
+ flexmock(TCPSocket).should_receive(:new).once.with(host, port).and_return(ami_server)
60
+ flexmock(IO).should_receive(:select).at_least.once.with([ami_server], nil, nil, 1.0).and_return(true)
61
+
62
+ @ami.connect!
63
+ end
64
+
65
+ after do
66
+ @ami.disconnect!
67
+ end
68
+
69
+ test "should respond to an immediate command" do
70
+ resp = @ami.queues
71
+ resp[0][:raw].should.be.a.kind_of String
72
+ end
73
+
74
+ test "should respond to a follows command" do
75
+ resp = @ami.command :Command => "show channels"
76
+ resp[0][:raw].should.be.a.kind_of String
77
+ end
78
+
79
+ test "should respond to a DBGet for a non-existent key with an exception" do
80
+ the_following_code do
81
+ resp = @ami.dbget :Family => "none", :Key => "somekey"
82
+ end.should.raise Adhearsion::VoIP::Asterisk::AMI::ActionError
83
+ end
84
+
85
+ test "should respond to a DBGet for a key with an event" do
86
+ resp = @ami.dbput :Family => "none", :Key => "somekey", :Val => 5
87
+ resp = @ami.dbget :Family => "none", :Key => "somekey"
88
+ resp[0]['Val'].should == "5"
89
+ end
90
+
91
+ test "should respond to a command that generates follows event(s)" do
92
+ resp = @ami.queuestatus
93
+ resp[0]['Queue'].should == "default"
94
+ end
95
+
96
+ test "should show usage for an improper follows command" do
97
+ resp = @ami.command :Command => "meetme list"
98
+ resp[0][:raw].should.be.a.kind_of String
99
+ end
100
+
101
+ test "should respond to a synchronous originate"
102
+ test "should respond to an asynchronous originate"
103
+
104
+ test "should define events() as a private method to prevent turning events on or off" do
105
+ @ami.private_methods.include?("events").should.equal true
106
+ end
107
+
108
+ test "should raise an exception when Asterisk doesn't recognize a command" do
109
+ the_following_code {
110
+ @ami.this_command_does_not_exist_kthx
111
+ }.should.raise Adhearsion::VoIP::Asterisk::AMI::ActionError
112
+
113
+ end
114
+
115
+ end
116
+
117
+ context 'AMI#originate' do
118
+ include AmiCommandTestHelper
119
+ test "should pass the arguments to execute_ami_command! with the options given" do
120
+ ami = new_ami_instance
121
+ options = { :channel => "ohai_lolz", :application => "Echo" }
122
+ flexmock(ami).should_receive(:execute_ami_command!).with(:originate, options).once
123
+ ami.originate options
124
+ end
125
+
126
+ test "should rename the :caller_id Hash key to :callerid" do
127
+ ami, caller_id = new_ami_instance, "Jay"
128
+ options = { :channel => "ohai_lolz", :application => "Echo"}
129
+ flexmock(ami).should_receive(:execute_ami_command!).with(:originate, options.merge(:callerid => caller_id)).once
130
+ ami.originate options.merge(:caller_id => caller_id)
131
+ end
132
+
133
+ end
134
+
135
+ context 'AMI#call_and_exec' do
136
+ include AmiCommandTestHelper
137
+ test "should execute originate properly with the minimum arguments" do
138
+ number, app = "12224446666", "Echo"
139
+
140
+ ami = flexmock new_ami_instance
141
+ ami.should_receive(:originate).once.with(:channel => number, :application => app).and_return true
142
+ ami.call_and_exec number, app
143
+ end
144
+
145
+ end
146
+
147
+ context 'AMI#introduce' do
148
+
149
+ include AmiCommandTestHelper
150
+
151
+ test "should execute origiante properly (when :caller_id and :options aren't specified)" do
152
+ caller, callee, caller_id = "SIP/12224446666@trunk", "SIP/12224447777@trunk", "Jay Phillips"
153
+
154
+ correct_args = {:application => "Dial", :channel => caller, :data => callee, :caller_id => "Jay"}
155
+ ami = flexmock new_ami_instance
156
+ ami.should_receive(:originate).once.with(correct_args).and_return(true)
157
+ ami.introduce caller, callee, :caller_id => "Jay"
158
+ end
159
+
160
+ end
161
+
162
+ context "The manager proxy" do
163
+ before do
164
+ host, port = "localhost", 5038
165
+ @ami = Adhearsion::VoIP::Asterisk::AMI.new "admin", "password", "localhost", :port => port, :events => false
166
+
167
+ ami_server = AmiServer.new
168
+ flexmock(TCPSocket).should_receive(:new).once.with(host, port).and_return(ami_server)
169
+ flexmock(IO).should_receive(:select).at_least.once.with([ami_server], nil, nil, 1.0).and_return(true)
170
+
171
+ @ami.connect!
172
+ @door = DRb.start_service "druby://127.0.0.1:9050", Adhearsion::DrbDoor.instance
173
+ end
174
+
175
+ test "should accept a command" do
176
+ client = DRbObject.new nil, DRb.uri
177
+ client.proxy.ping
178
+ end
179
+
180
+ after do
181
+ DRb.stop_service
182
+ @ami.disconnect!
183
+ end
184
+ end
185
+
186
+ context "The command-sending interface" do
187
+ test "should raise an exception if permission was denied"
188
+ test "should allow variables to be specified as a Hash"
189
+ end
190
+
191
+ context "Sent arbitrary AMI commands" do
192
+
193
+ test "should allow a convenient way of parsing by event name"
194
+ test "should return Hash arguments"
195
+
196
+ test "should recognize its subclasses"
197
+ test "should send events to all of its subclasses"
198
+ test "should catch action names with method_missing() and format them properly"
199
+
200
+ test "should raise an exception if permission was denied"
201
+ end
202
+
203
+ context "AMI Packets" do
204
+ test "A Packet should not be an error" do
205
+ Adhearsion::VoIP::Asterisk::AMI::Packet.new.error?.should.be false
206
+ end
207
+ test "An ErrorPacket should be an error" do
208
+ Adhearsion::VoIP::Asterisk::AMI::ErrorPacket.new.error?.should.be true
209
+ end
210
+ end
211
+
212
+ context "The event parser" do
213
+ test "should parse the the YAML-like format "
214
+ test "should allow a Hash to specify multiple matches"
215
+ end
216
+
217
+ BEGIN {
218
+ module AmiCommandTestHelper
219
+ def new_ami_instance
220
+ # TODO. mock everything out here
221
+ returning Adhearsion::VoIP::Asterisk::AMI.new("user","pass") do |ami|
222
+ flexmock(ami).should_receive(:connect!).and_return(true)
223
+ end
224
+ end
225
+
226
+ end
227
+ }