sevenscale-adhearsion 0.7.1000

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +1186 -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 +402 -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 +453 -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,306 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. .. .. test_helper])
2
+ require 'adhearsion/voip/asterisk/config_generators/voicemail.conf'
3
+
4
+ context 'Basic requirements of the Voicemail config generator' do
5
+ attr_reader :config
6
+ before :each do
7
+ @config = Adhearsion::VoIP::Asterisk::ConfigFileGenerators::Voicemail.new
8
+ end
9
+
10
+ test 'should have a [general] context' do
11
+ config.to_sanitary_hash.has_key?('[general]').should.equal true
12
+ end
13
+
14
+ test 'should set the format to "wav" by default in the general section' do
15
+ config.to_sanitary_hash['[general]'].should.include 'format=wav'
16
+ end
17
+
18
+ test 'an exception should be raised if the context name is "general"' do
19
+ the_following_code {
20
+ config.context(:general) {|_|}
21
+ }.should.raise ArgumentError
22
+ end
23
+
24
+ end
25
+
26
+ context 'Defining recording-related settings of the Voicemail config file' do
27
+
28
+ attr_reader :recordings
29
+ before :each do
30
+ @recordings = Adhearsion::VoIP::Asterisk::ConfigFileGenerators::Voicemail::RecordingDefinition.new
31
+ end
32
+
33
+ test 'the recordings setting setter' do
34
+ Adhearsion::VoIP::Asterisk::ConfigFileGenerators::Voicemail.new.recordings.should.be.kind_of? recordings.class
35
+ end
36
+
37
+ test 'recordings format should only allow a few options' do
38
+ the_following_code {
39
+ recordings.format :wav
40
+ recordings.format :wav49
41
+ recordings.format :gsm
42
+ }.should.not.raise
43
+
44
+ the_following_code {
45
+ recordings.format :lolcats
46
+ }.should.raise ArgumentError
47
+ end
48
+
49
+ end
50
+
51
+ context 'Defining email-related Voicemail settings' do
52
+
53
+ attr_reader :email
54
+ before :each do
55
+ @email = Adhearsion::VoIP::Asterisk::ConfigFileGenerators::Voicemail::EmailDefinition.new
56
+ end
57
+
58
+ test 'the [] operator is overloaded to return conveniences for the body() and subject() methods' do
59
+ variables = %{#{email[:name]} #{email[:mailbox]} #{email[:date]} #{email[:duration]} } +
60
+ %{#{email[:message_number]} #{email[:caller_id]} #{email[:caller_id_number]} } +
61
+ %{#{email[:caller_id_name]}}
62
+ formatted = %{${VM_NAME} ${VM_MAILBOX} ${VM_DATE} ${VM_DUR} ${VM_MSGNUM} ${VM_CALLERID} ${VM_CIDNUM} ${VM_CIDNAME}}
63
+ email.body variables
64
+ email.subject variables
65
+ email.properties[:emailbody].should == formatted
66
+ email.properties[:emailsubject].should == formatted
67
+ end
68
+
69
+ test 'when defining a body, newlines should be escaped and carriage returns removed' do
70
+ unescaped, escaped = "one\ntwo\n\r\r\nthree\n\n\n", 'one\ntwo\n\nthree\n\n\n'
71
+ email.body unescaped
72
+ email.properties[:emailbody].should == escaped
73
+ end
74
+
75
+ test 'the body must not be allowed to exceed 512 characters' do
76
+ the_following_code {
77
+ email.body "X" * 512
78
+ }.should.not.raise ArgumentError
79
+
80
+ the_following_code {
81
+ email.body "X" * 513
82
+ }.should.raise ArgumentError
83
+
84
+ the_following_code {
85
+ email.body "X" * 1000
86
+ }.should.raise ArgumentError
87
+ end
88
+
89
+ test 'should store away the email command properly' do
90
+ mail_command = "/usr/sbin/sendmail -f alice@wonderland.com -t"
91
+ email.command mail_command
92
+ email.properties[:mailcmd].should == mail_command
93
+ end
94
+
95
+ end
96
+
97
+ context 'A mailbox definition' do
98
+ attr_reader :mailbox
99
+ before :each do
100
+ @mailbox = Adhearsion::VoIP::Asterisk::ConfigFileGenerators::Voicemail::ContextDefinition::MailboxDefinition.new("123")
101
+ end
102
+
103
+ test 'setting the name should be reflected in the to_hash form of the definition' do
104
+ mailbox.name "Foobar"
105
+ mailbox.to_hash[:name].should == "Foobar"
106
+ end
107
+
108
+ test 'setting the pin_number should be reflected in the to_hash form of the definition' do
109
+ mailbox.pin_number 555
110
+ mailbox.to_hash[:pin_number].should.equal 555
111
+ end
112
+
113
+ test 'the mailbox number should be available in the mailbox_number getter' do
114
+ Adhearsion::VoIP::Asterisk::ConfigFileGenerators::Voicemail::ContextDefinition::MailboxDefinition.new '123'
115
+ mailbox.mailbox_number.should.equal '123'
116
+ end
117
+
118
+ test 'an ArgumentError should be raised if the mailbox_number is not numeric' do
119
+ the_following_code {
120
+ Adhearsion::VoIP::Asterisk::ConfigFileGenerators::Voicemail::ContextDefinition::MailboxDefinition.new("this is not numeric")
121
+ }.should.raise ArgumentError
122
+ end
123
+
124
+ test 'an ArgumentError should be raised if the pin_number is not numeric' do
125
+ the_following_code {
126
+ mailbox.pin_number "this is not numeric"
127
+ }.should.raise ArgumentError
128
+ end
129
+
130
+ test "the string representation should be valid" do
131
+ expected = "123 => 1337,Jay Phillips,ahn@adhearsion.com"
132
+ mailbox.pin_number 1337
133
+ mailbox.name "Jay Phillips"
134
+ mailbox.email "ahn@adhearsion.com"
135
+ mailbox.to_s.should == expected
136
+ end
137
+
138
+ test 'should not add a trailing comma when the email is left out' do
139
+ mailbox.pin_number 1337
140
+ mailbox.name "Jay Phillips"
141
+ mailbox.to_s.ends_with?(',').should.equal false
142
+ end
143
+
144
+ test 'should not add a trailing comma when the email and name is left out' do
145
+ mailbox.pin_number 1337
146
+ mailbox.to_s.ends_with?(',').should.equal false
147
+ end
148
+
149
+ end
150
+
151
+ context "A Voicemail context definition" do
152
+
153
+ test "should ultimately add a [] context definition to the string output" do
154
+ voicemail = Adhearsion::VoIP::Asterisk::ConfigFileGenerators::Voicemail.new
155
+ voicemail.context "monkeys" do |config|
156
+ config.should.be.kind_of voicemail.class::ContextDefinition
157
+ config.mailbox 1234 do |mailbox|
158
+ mailbox.pin_number 3333
159
+ mailbox.email "alice@wonderland.com"
160
+ mailbox.name "Alice Little"
161
+ end
162
+ end
163
+ voicemail.to_sanitary_hash.has_key?('[monkeys]').should.equal true
164
+ end
165
+
166
+ test 'should raise a LocalJumpError if no block is given' do
167
+ the_following_code {
168
+ Adhearsion::VoIP::Asterisk::ConfigFileGenerators::Voicemail.new.context('lols')
169
+ }.should.raise LocalJumpError
170
+ end
171
+
172
+ test 'its string representation should begin with a context declaration' do
173
+ vm = Adhearsion::VoIP::Asterisk::ConfigFileGenerators::Voicemail.new
174
+ vm.context("jay") {|_|}.to_s.starts_with?("[jay]").should.equal true
175
+ end
176
+
177
+ end
178
+
179
+ context 'Defining Voicemail contexts with mailbox definitions' do
180
+
181
+ end
182
+
183
+ context 'An expansive example of the Voicemail config generator' do
184
+
185
+ before :each do
186
+ @employees = [
187
+ {:name => "Tango", :pin_number => 7777, :mailbox_number => 10},
188
+ {:name => "Echo", :pin_number => 7777, :mailbox_number => 20},
189
+ {:name => "Sierra", :pin_number => 7777, :mailbox_number => 30},
190
+ {:name => "Tango2", :pin_number => 7777, :mailbox_number => 40}
191
+ ].map { |hash| OpenStruct.new(hash) }
192
+
193
+ @groups = [
194
+ {:name => "Brand New Cadillac", :pin_number => 1111, :mailbox_number => 1},
195
+ {:name => "Jimmy Jazz", :pin_number => 2222, :mailbox_number => 2},
196
+ {:name => "Death or Glory", :pin_number => 3333, :mailbox_number => 3},
197
+ {:name => "Rudie Can't Fail", :pin_number => 4444, :mailbox_number => 4},
198
+ {:name => "Spanish Bombs", :pin_number => 5555, :mailbox_number => 5}
199
+ ].map { |hash| OpenStruct.new(hash) }
200
+ end
201
+
202
+ test 'a huge, brittle integration test' do
203
+ vm = Adhearsion::VoIP::Asterisk::ConfigFileGenerators::Voicemail.new do |voicemail|
204
+ voicemail.context :default do |context|
205
+ context.mailbox 123 do |mailbox|
206
+ mailbox.name "Administrator"
207
+ mailbox.email "jabberwocky@wonderland.com"
208
+ mailbox.pin_number 9876
209
+ end
210
+ end
211
+
212
+ voicemail.context :employees do |context|
213
+ @employees.each do |employee|
214
+ context.mailbox employee.mailbox_number do |mailbox|
215
+ mailbox.pin_number 1337
216
+ mailbox.name employee.name
217
+ end
218
+ end
219
+ end
220
+
221
+ voicemail.context :groups do |context|
222
+ @groups.each do |group|
223
+ context.mailbox group.mailbox_number do |mailbox|
224
+ mailbox.pin_number 1337
225
+ mailbox.name group.name
226
+ mailbox.email "foo@qaz.org"
227
+ end
228
+ end
229
+ end
230
+
231
+ voicemail.execute_on_pin_change "/path/to/my/changer_script.rb"
232
+ ############################################################################
233
+ ############################################################################
234
+
235
+ signature = "Your Friendly Phone System"
236
+
237
+ # execute_after_email "netcat 192.168.1.2 12345"
238
+ # greeting_maximum 1.minute
239
+ # time_jumped_with_skip_key 3.seconds # milliseconds!
240
+ # logging_in do |config|
241
+ # config.maximum_attempts 3
242
+ # end
243
+
244
+ voicemail.recordings do |config|
245
+ config.format :wav # ONCE YOU PICK A FORMAT, NEVER CHANGE IT UNLESS YOU KNOW THE CONSEQUENCES!
246
+ config.allowed_length 3.seconds..5.minutes
247
+ config.maximum_silence 10.seconds
248
+ # config.silence_threshold 128 # wtf?
249
+ end
250
+
251
+ voicemail.emails do |config|
252
+ config.from :name => signature, :email => "noreply@adhearsion.com"
253
+ config.attach_recordings true
254
+ config.command '/usr/sbin/sendmail -f alice@wonderland.com -t'
255
+ config.subject "New voicemail for #{config[:name]}"
256
+ config.body <<-BODY.unindent
257
+ Dear #{config[:name]}:
258
+
259
+ The caller #{config[:caller_id]} left you a #{config[:duration]} long voicemail
260
+ (number #{config[:message_number]}) on #{config[:date]} in mailbox #{config[:mailbox]}.
261
+
262
+ #{ "The recording is attached to this email.\n" if config.attach_recordings? }
263
+ - #{signature}
264
+ BODY
265
+ end
266
+ end
267
+ internalized = vm.to_sanitary_hash
268
+ internalized.size.should.equal 5 # general, zonemessages, default, employees, groups
269
+
270
+ target_config = <<-CONFIG
271
+ [general]
272
+ attach=yes
273
+ emailbody=Dear ${VM_NAME}:\\nThe caller ${VM_CALLERID} left you a ${VM_DUR} long voicemail\\n(number ${VM_MSGNUM}) on ${VM_DATE} in mailbox ${VM_MAILBOX}.\\nThe recording is attached to this email.\\n- Your Friendly Phone System\\n
274
+ emailsubject=New voicemail for ${VM_NAME}
275
+ externpass=/path/to/my/changer_script.rb
276
+ format=wav
277
+ fromstring=Your Friendly Phone System
278
+ mailcmd=/usr/sbin/sendmail -f alice@wonderland.com -t
279
+ serveremail=noreply@adhearsion.com
280
+
281
+ [zonemessages]
282
+ eastern=America/New_York|'vm-received' Q 'digits/at' IMp
283
+ central=America/Chicago|'vm-received' Q 'digits/at' IMp
284
+ central24=America/Chicago|'vm-received' q 'digits/at' H N 'hours'
285
+ military=Zulu|'vm-received' q 'digits/at' H N 'hours' 'phonetic/z_p'
286
+ european=Europe/Copenhagen|'vm-received' a d b 'digits/at' HM
287
+ [default]
288
+ 123 => 9876,Administrator,jabberwocky@wonderland.com
289
+
290
+ [employees]
291
+ 10 => 1337,Tango
292
+ 20 => 1337,Echo
293
+ 30 => 1337,Sierra
294
+ 40 => 1337,Tango2
295
+
296
+ [groups]
297
+ 1 => 1337,Brand New Cadillac,foo@qaz.org
298
+ 2 => 1337,Jimmy Jazz,foo@qaz.org
299
+ 3 => 1337,Death or Glory,foo@qaz.org
300
+ 4 => 1337,Rudie Can't Fail,foo@qaz.org
301
+ 5 => 1337,Spanish Bombs,foo@qaz.org
302
+ CONFIG
303
+ vm.to_s.grep(/^$|^[^;]/).join.strip.should == target_config.strip
304
+
305
+ end
306
+ end
@@ -0,0 +1,111 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. .. .. test_helper])
2
+ require 'adhearsion/voip/menu_state_machine/calculated_match'
3
+
4
+ context 'CalculatedMatch' do
5
+ test "should make accessible the context name" do
6
+ Adhearsion::VoIP::CalculatedMatch.new(:match_payload => :foobar).match_payload.should.equal :foobar
7
+ end
8
+ test "should make accessible the original pattern" do
9
+ Adhearsion::VoIP::CalculatedMatch.new(:pattern => :something).pattern.should.equal :something
10
+ end
11
+ test "should make accessible the matched query" do
12
+ Adhearsion::VoIP::CalculatedMatch.new(:query => 123).query.should.equal 123
13
+ end
14
+ test '#type_of_match should return :exact, :potential, or nil' do
15
+ Adhearsion::VoIP::CalculatedMatch.new(:potential_matches => [1]).type_of_match.should.equal :potential
16
+ Adhearsion::VoIP::CalculatedMatch.new(:exact_matches => [3,3]).type_of_match.should.equal :exact
17
+ Adhearsion::VoIP::CalculatedMatch.new(:exact_matches => [8,3], :potential_matches => [0,9]).type_of_match.should.equal :exact
18
+ Adhearsion::VoIP::CalculatedMatch.new.type_of_match.should.equal nil
19
+ end
20
+ test '#exact_match? should return true if the match was exact' do
21
+ Adhearsion::VoIP::CalculatedMatch.new(:exact_matches => [0,3,5]).should.be.exact_match
22
+ end
23
+
24
+ test '#potential_match? should return true if the match was exact' do
25
+ Adhearsion::VoIP::CalculatedMatch.new(:potential_matches => [88,99,77]).should.be.potential_match
26
+ end
27
+
28
+ test '#exact_matches should return an array of exact matches' do
29
+ Adhearsion::VoIP::CalculatedMatch.new(:exact_matches => [0,3,5]).exact_matches.should == [0,3,5]
30
+ end
31
+
32
+ test '#potential_matches should return an array of potential matches' do
33
+ Adhearsion::VoIP::CalculatedMatch.new(:potential_matches => [88,99,77]).potential_matches.should == [88,99,77]
34
+ end
35
+
36
+ test '::failed_match! should return a match that *really* failed' do
37
+ failure = Adhearsion::VoIP::CalculatedMatch.failed_match! 10..20, 30, :match_payload_does_not_matter
38
+ failure.should.not.be.exact_match
39
+ failure.should.not.be.potential_match
40
+ failure.type_of_match.should.be nil
41
+
42
+ failure.match_payload.should.equal :match_payload_does_not_matter
43
+ failure.pattern.should == (10..20)
44
+ failure.query.should == 30
45
+ end
46
+
47
+ end
48
+
49
+ context 'CalculatedMatchCollection' do
50
+
51
+ include CalculatedMatchCollectionTestHelper
52
+
53
+ attr_reader :collection
54
+ before:each do
55
+ @collection = Adhearsion::VoIP::CalculatedMatchCollection.new
56
+ end
57
+
58
+ test 'the <<() method should collect the potential matches into the actual_potential_matches Array' do
59
+ mock_matches_array_1 = [:foo, :bar, :qaz],
60
+ mock_matches_array_2 = [10,20,30]
61
+ mock_matches_1 = mock_with_potential_matches mock_matches_array_1
62
+ mock_matches_2 = mock_with_potential_matches mock_matches_array_2
63
+
64
+ collection << mock_matches_1
65
+ collection.actual_potential_matches.should == mock_matches_array_1
66
+
67
+ collection << mock_matches_2
68
+ collection.actual_potential_matches.should == mock_matches_array_1 + mock_matches_array_2
69
+ end
70
+
71
+ test 'the <<() method should collect the exact matches into the actual_exact_matches Array' do
72
+ mock_matches_array_1 = [:blam, :blargh],
73
+ mock_matches_array_2 = [5,4,3,2,1]
74
+ mock_matches_1 = mock_with_exact_matches mock_matches_array_1
75
+ mock_matches_2 = mock_with_exact_matches mock_matches_array_2
76
+
77
+ collection << mock_matches_1
78
+ collection.actual_exact_matches.should == mock_matches_array_1
79
+
80
+ collection << mock_matches_2
81
+ collection.actual_exact_matches.should == mock_matches_array_1 + mock_matches_array_2
82
+ end
83
+
84
+ test "if any exact matches exist, the exact_match?() method should return true" do
85
+ collection << mock_with_exact_matches([1,2,3])
86
+ collection.should.be.exact_match
87
+ end
88
+
89
+ test "if any potential matches exist, the potential_match?() method should return true" do
90
+ collection << mock_with_potential_matches([1,2,3])
91
+ collection.should.be.potential_match
92
+ end
93
+ end
94
+
95
+ BEGIN {
96
+ module CalculatedMatchCollectionTestHelper
97
+ def mock_with_potential_matches(potential_matches)
98
+ Adhearsion::VoIP::CalculatedMatch.new :potential_matches => potential_matches
99
+ end
100
+
101
+ def mock_with_exact_matches(exact_matches)
102
+ Adhearsion::VoIP::CalculatedMatch.new :exact_matches => exact_matches
103
+ end
104
+
105
+ def mock_with_potential_and_exact_matches(potential_matches, exact_matches)
106
+ Adhearsion::VoIP::CalculatedMatch.new :potential_matches => potential_matches,
107
+ :exact_matches => exact_matches
108
+ end
109
+
110
+ end
111
+ }
@@ -0,0 +1,98 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. .. .. test_helper])
2
+ require 'adhearsion/voip/menu_state_machine/menu_builder'
3
+ require 'adhearsion/voip/menu_state_machine/matchers'
4
+
5
+ context "MatchCalculator" do
6
+ test "the build_with_pattern() method should return an appropriate subclass instance based on the pattern's class" do
7
+ Adhearsion::VoIP::MatchCalculator.build_with_pattern(1..2, :main).should.be.instance_of Adhearsion::VoIP::RangeMatchCalculator
8
+ end
9
+
10
+ end
11
+
12
+ context "The RangeMatchCalculator" do
13
+
14
+ test "matching with a Range should handle the case of two potential matches in the range" do
15
+ digits_that_begin_with_eleven = [110..119, 1100..1111].map { |x| Array(x) }.flatten
16
+
17
+ calculator = Adhearsion::VoIP::RangeMatchCalculator.new 11..1111, :match_payload_doesnt_matter
18
+ match = calculator.match 11
19
+ match.exact_matches.should == [11]
20
+ match.potential_matches.should == digits_that_begin_with_eleven
21
+ end
22
+
23
+ test 'return values of #match should be an instance of CalculatedMatch' do
24
+ calculator = Adhearsion::VoIP::RangeMatchCalculator.new 1..9, :match_payload_doesnt_matter
25
+ calculator.match(0).should.be.instance_of Adhearsion::VoIP::CalculatedMatch
26
+ calculator.match(1000).should.be.instance_of Adhearsion::VoIP::CalculatedMatch
27
+ end
28
+
29
+ end
30
+
31
+ context "FixnumMatchCalculator" do
32
+ attr_reader :match_payload
33
+ before:each do
34
+ @match_payload = :main
35
+ end
36
+
37
+ test "a potential match scenario" do
38
+ calculator = Adhearsion::VoIP::FixnumMatchCalculator.new(444, match_payload)
39
+ match = calculator.match 4
40
+ match.should.be.potential_match
41
+ match.should.not.be.exact_match
42
+ match.potential_matches.should == [444]
43
+ end
44
+
45
+ test 'a multi-digit exact match scenario' do
46
+ calculator = Adhearsion::VoIP::FixnumMatchCalculator.new(5555, match_payload)
47
+ calculator.match(5555).should.be.exact_match
48
+ end
49
+
50
+ test 'a single-digit exact match scenario' do
51
+ calculator = Adhearsion::VoIP::FixnumMatchCalculator.new(1, match_payload)
52
+ calculator.match(1).should.be.exact_match
53
+ end
54
+
55
+ test 'the context name given to the calculator should be passed on to the CalculatedMatch' do
56
+ match_payload = :icanhascheezburger
57
+ calculator = Adhearsion::VoIP::FixnumMatchCalculator.new(1337, match_payload)
58
+ calculator.match(1337).match_payload.should.equal match_payload
59
+ end
60
+
61
+ end
62
+
63
+ context "StringMatchCalculator" do
64
+
65
+ attr_reader :match_payload
66
+ before:each do
67
+ @match_payload = :doesnt_matter
68
+ end
69
+
70
+ test "numerical digits mixed with special digits" do
71
+
72
+ %w[5*11#3 5*** ###].each do |str|
73
+ calculator = Adhearsion::VoIP::StringMatchCalculator.new(str, match_payload)
74
+
75
+ match_case = calculator.match str[0,2]
76
+
77
+ match_case.should.not.be.exact_match
78
+ match_case.should.be.potential_match
79
+ match_case.potential_matches.should == [str]
80
+
81
+ match_case = calculator.match str
82
+ match_case.should.be.exact_match
83
+ match_case.should.not.be.potential_match
84
+ match_case.exact_matches.should == [str]
85
+ end
86
+ end
87
+
88
+ test "matching the special DTMF characters such as * and #" do
89
+ %w[* #].each do |special_digit|
90
+ calculator = Adhearsion::VoIP::StringMatchCalculator.new(special_digit, match_payload)
91
+ match_case = calculator.match special_digit
92
+ match_case.should.not.be.potential_match
93
+ match_case.should.be.exact_match
94
+ match_case.exact_matches.first.should == special_digit
95
+ end
96
+ end
97
+
98
+ end
@@ -0,0 +1,176 @@
1
+ class AmiServer
2
+ class AMIResponseHelper
3
+ class << self
4
+ def bad_login_response
5
+ { "Response" => "Error", "Message" => "Authentication failed" }
6
+ end
7
+
8
+ def good_login_response
9
+ { "Response" => "Success", "Message" => "Authentication accepted" }
10
+ end
11
+
12
+ def follows_response
13
+ { "Response" => "Follows", "Privilege" => "Command" }
14
+ end
15
+
16
+ def bad_dbget_response
17
+ { "Response" => "Error", "Message" => "Database entry not found" }
18
+ end
19
+
20
+ def good_dbget_response
21
+ { "Response" => "Success", "Message" => "Result will follow" }
22
+ end
23
+
24
+ def dbput_response
25
+ { "Response" => "Success", "Message" => "Updated database successfully" }
26
+ end
27
+
28
+ def dbget_event_response
29
+ { "Event" => "DBGetResponse" }
30
+ end
31
+
32
+ def queue_status_response
33
+ { "Response" => "Success", "Message" => "Queue status will follow" }
34
+ end
35
+
36
+ def queue_status_event_complete_response
37
+ { "Event" => "QueueStatusComplete" }
38
+ end
39
+
40
+ def queue_status_event_params_response
41
+ { "Event" => "QueueParams", "Queue" => "default", "Max" => 0, "Min" => 0, "Holdtime" => 0,
42
+ "Completed" => 0, "Abandoned" => 0, "ServiceLevel" => 0, "ServiceLevelPerf" => 0 }
43
+ end
44
+
45
+ def events_on_response
46
+ { "Response" => "Events On" }
47
+ end
48
+
49
+ def events_off_response
50
+ { "Response" => "Events Off" }
51
+ end
52
+
53
+ def ping_response
54
+ { "Response" => "Pong" }
55
+ end
56
+
57
+ def unrecognized_action
58
+ { "Response" => "Error", "Message" => "Invalid/unknown command" }
59
+ end
60
+ end
61
+ end
62
+
63
+ def initialize(*args)
64
+ @closed = false
65
+ @buffer = ""
66
+ @db = {}
67
+ extend MonitorMixin
68
+ fill(prompt, 1)
69
+ end
70
+
71
+ def closed?
72
+ @closed
73
+ end
74
+
75
+ def close
76
+ @closed = true
77
+ end
78
+
79
+ def read_nonblock(count)
80
+ str = @buffer.slice!(0..count-1)
81
+ raise Errno::EAGAIN if str == ""
82
+ str
83
+ end
84
+
85
+ def gets
86
+ @buffer.slice!(0..-1)
87
+ end
88
+
89
+ def write(str)
90
+ raise ArgumentError, "malformed request" if not str.ends_with?("\r\n\r\n")
91
+ args = str.split("\r\n").inject({}) { |accum, str| accum.merge!(YAML.load(str)) }
92
+ actionid = args['actionid']
93
+ case args['action']
94
+ when "login"
95
+ if args['username'] == username and args['secret'] == password
96
+ fill(response(actionid, AMIResponseHelper.good_login_response))
97
+ else
98
+ fill(response(actionid, AMIResponseHelper.bad_login_response))
99
+ end
100
+ when "queues"
101
+ fill("No Members\nNo Callers\r\n")
102
+ when "command"
103
+ case args['command']
104
+ when "show channels"
105
+ fill(response(actionid, AMIResponseHelper.follows_response), 1)
106
+ fill("Channel (Context Extension Pri ) State Appl. Data\n0 active channel(s)\n--END COMMAND--")
107
+ when "meetme list"
108
+ fill(response(actionid, AMIResponseHelper.follows_response), 1)
109
+ str = []
110
+ str << "Usage: meetme (un)lock|(un)mute|kick|list [concise] "
111
+ str << " Executes a command for the conference or on a conferee"
112
+ str << "--END COMMAND--"
113
+ fill(str.join("\n"))
114
+ else
115
+ fill(response(actionid, AMIResponseHelper.follows_response), 1)
116
+ fill("No such command '#{args['command']}' (type 'help' for help)\n--END COMMAND--")
117
+ end
118
+ when "dbget"
119
+ if not @db.has_key?(args['family']) or not @db[args['family']].has_key?(args['key'])
120
+ fill(response(actionid, AMIResponseHelper.bad_dbget_response))
121
+ else
122
+ val = @db[args['family']][args['key']]
123
+ fill(response(actionid, AMIResponseHelper.good_dbget_response))
124
+ fill(response(actionid, {
125
+ :Family => args['family'],
126
+ :Key => args['key'],
127
+ :Val => val }.merge!(AMIResponseHelper.dbget_event_response)))
128
+ end
129
+ when "dbput"
130
+ @db[args['family']] ||= {}
131
+ @db[args['family']][args['key']] = args['val']
132
+ fill(response(actionid, AMIResponseHelper.dbput_response))
133
+ when "queuestatus"
134
+ fill(response(actionid, AMIResponseHelper.queue_status_response))
135
+ fill(response(actionid, AMIResponseHelper.queue_status_event_params_response))
136
+ fill(response(actionid, AMIResponseHelper.queue_status_event_complete_response))
137
+ when "events"
138
+ if args['eventmask'] !~ /^on$/i
139
+ if args['eventmask'] =~ /^off$/i
140
+ fill(response(actionid, AMIResponseHelper.events_off_response))
141
+ else
142
+ fill(response(actionid, AMIResponseHelper.events_on_response))
143
+ end
144
+ end
145
+ when "ping"
146
+ fill(response(actionid, AMIResponseHelper.ping_response))
147
+ else
148
+ # raise ArgumentError, "Unknown action #{args['action']}"
149
+ fill(response(actionid, AMIResponseHelper.unrecognized_action))
150
+ end
151
+ end
152
+
153
+ private
154
+
155
+ def response(action_id, args = {})
156
+ resp = []
157
+ resp << "Response: #{args.delete('Response')}" if args['Response']
158
+ resp << "Privilege: #{args.delete('Privilege')}" if args['Privilege']
159
+ resp << "Event: #{args.delete('Event')}" if args['Event']
160
+ resp << "ActionID: #{action_id}"
161
+ resp << "Message: #{args.delete('Message')}" if args['Message']
162
+ args.inject(resp) do |accum, pair|
163
+ accum << "#{pair[0]}: #{pair[1]}"
164
+ end
165
+ resp.join("\r\n")
166
+ end
167
+
168
+ def fill(line, blanks=2)
169
+ @buffer += line
170
+ @buffer += "\r\n" * blanks
171
+ end
172
+
173
+ def prompt; "Asterisk Call Manager/1.0" end
174
+ def username; "admin" end
175
+ def password; "password" end
176
+ end