sevenscale-adhearsion 0.7.1000
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +3 -0
- data/LICENSE +456 -0
- data/Manifest.txt +149 -0
- data/README.txt +6 -0
- data/Rakefile +48 -0
- data/ahn_generators/component/USAGE +5 -0
- data/ahn_generators/component/component_generator.rb +57 -0
- data/ahn_generators/component/templates/configuration.rb +0 -0
- data/ahn_generators/component/templates/lib/lib.rb.erb +3 -0
- data/ahn_generators/component/templates/test/test.rb.erb +12 -0
- data/ahn_generators/component/templates/test/test_helper.rb +14 -0
- data/app_generators/ahn/USAGE +5 -0
- data/app_generators/ahn/ahn_generator.rb +76 -0
- data/app_generators/ahn/templates/.ahnrc +12 -0
- data/app_generators/ahn/templates/README +8 -0
- data/app_generators/ahn/templates/Rakefile +3 -0
- data/app_generators/ahn/templates/components/simon_game/configuration.rb +0 -0
- data/app_generators/ahn/templates/components/simon_game/lib/simon_game.rb +61 -0
- data/app_generators/ahn/templates/components/simon_game/test/test_helper.rb +14 -0
- data/app_generators/ahn/templates/components/simon_game/test/test_simon_game.rb +31 -0
- data/app_generators/ahn/templates/config/startup.rb +53 -0
- data/app_generators/ahn/templates/dialplan.rb +4 -0
- data/bin/ahn +28 -0
- data/bin/ahnctl +68 -0
- data/bin/jahn +32 -0
- data/lib/adhearsion/blank_slate.rb +5 -0
- data/lib/adhearsion/cli.rb +106 -0
- data/lib/adhearsion/component_manager.rb +277 -0
- data/lib/adhearsion/core_extensions/all.rb +9 -0
- data/lib/adhearsion/core_extensions/array.rb +0 -0
- data/lib/adhearsion/core_extensions/custom_daemonizer.rb +45 -0
- data/lib/adhearsion/core_extensions/global.rb +1 -0
- data/lib/adhearsion/core_extensions/guid.rb +5 -0
- data/lib/adhearsion/core_extensions/hash.rb +0 -0
- data/lib/adhearsion/core_extensions/metaprogramming.rb +17 -0
- data/lib/adhearsion/core_extensions/numeric.rb +4 -0
- data/lib/adhearsion/core_extensions/proc.rb +0 -0
- data/lib/adhearsion/core_extensions/pseudo_uuid.rb +11 -0
- data/lib/adhearsion/core_extensions/publishable.rb +73 -0
- data/lib/adhearsion/core_extensions/relationship_properties.rb +40 -0
- data/lib/adhearsion/core_extensions/string.rb +26 -0
- data/lib/adhearsion/core_extensions/thread.rb +13 -0
- data/lib/adhearsion/core_extensions/thread_safety.rb +7 -0
- data/lib/adhearsion/core_extensions/time.rb +0 -0
- data/lib/adhearsion/distributed/gateways/dbus_gateway.rb +0 -0
- data/lib/adhearsion/distributed/gateways/osa_gateway.rb +0 -0
- data/lib/adhearsion/distributed/gateways/rest_gateway.rb +9 -0
- data/lib/adhearsion/distributed/gateways/soap_gateway.rb +9 -0
- data/lib/adhearsion/distributed/gateways/xmlrpc_gateway.rb +9 -0
- data/lib/adhearsion/distributed/peer_finder.rb +0 -0
- data/lib/adhearsion/distributed/remote_cli.rb +0 -0
- data/lib/adhearsion/hooks.rb +57 -0
- data/lib/adhearsion/host_definitions.rb +63 -0
- data/lib/adhearsion/initializer/asterisk.rb +59 -0
- data/lib/adhearsion/initializer/configuration.rb +202 -0
- data/lib/adhearsion/initializer/database.rb +92 -0
- data/lib/adhearsion/initializer/drb.rb +25 -0
- data/lib/adhearsion/initializer/freeswitch.rb +22 -0
- data/lib/adhearsion/initializer/paths.rb +55 -0
- data/lib/adhearsion/initializer/rails.rb +40 -0
- data/lib/adhearsion/initializer.rb +217 -0
- data/lib/adhearsion/logging.rb +92 -0
- data/lib/adhearsion/services/scheduler.rb +5 -0
- data/lib/adhearsion/tasks/database.rb +5 -0
- data/lib/adhearsion/tasks/generating.rb +20 -0
- data/lib/adhearsion/tasks/lint.rb +4 -0
- data/lib/adhearsion/tasks/testing.rb +37 -0
- data/lib/adhearsion/tasks.rb +15 -0
- data/lib/adhearsion/version.rb +9 -0
- data/lib/adhearsion/voip/asterisk/agi_server.rb +78 -0
- data/lib/adhearsion/voip/asterisk/ami/actions.rb +238 -0
- data/lib/adhearsion/voip/asterisk/ami/machine.rb +871 -0
- data/lib/adhearsion/voip/asterisk/ami/machine.rl +109 -0
- data/lib/adhearsion/voip/asterisk/ami/parser.rb +262 -0
- data/lib/adhearsion/voip/asterisk/ami.rb +147 -0
- data/lib/adhearsion/voip/asterisk/commands.rb +1186 -0
- data/lib/adhearsion/voip/asterisk/config_generators/agents.conf.rb +140 -0
- data/lib/adhearsion/voip/asterisk/config_generators/config_generator.rb +101 -0
- data/lib/adhearsion/voip/asterisk/config_generators/queues.conf.rb +250 -0
- data/lib/adhearsion/voip/asterisk/config_generators/voicemail.conf.rb +240 -0
- data/lib/adhearsion/voip/asterisk/config_manager.rb +71 -0
- data/lib/adhearsion/voip/asterisk/special_dial_plan_managers.rb +80 -0
- data/lib/adhearsion/voip/asterisk.rb +4 -0
- data/lib/adhearsion/voip/call.rb +402 -0
- data/lib/adhearsion/voip/call_routing.rb +64 -0
- data/lib/adhearsion/voip/commands.rb +9 -0
- data/lib/adhearsion/voip/constants.rb +39 -0
- data/lib/adhearsion/voip/conveniences.rb +18 -0
- data/lib/adhearsion/voip/dial_plan.rb +205 -0
- data/lib/adhearsion/voip/dsl/dialing_dsl/dialing_dsl_monkey_patches.rb +37 -0
- data/lib/adhearsion/voip/dsl/dialing_dsl.rb +151 -0
- data/lib/adhearsion/voip/dsl/dialplan/control_passing_exception.rb +27 -0
- data/lib/adhearsion/voip/dsl/dialplan/dispatcher.rb +124 -0
- data/lib/adhearsion/voip/dsl/dialplan/parser.rb +75 -0
- data/lib/adhearsion/voip/dsl/dialplan/thread_mixin.rb +16 -0
- data/lib/adhearsion/voip/dsl/numerical_string.rb +117 -0
- data/lib/adhearsion/voip/freeswitch/basic_connection_manager.rb +48 -0
- data/lib/adhearsion/voip/freeswitch/event_handler.rb +58 -0
- data/lib/adhearsion/voip/freeswitch/freeswitch_dialplan_command_factory.rb +129 -0
- data/lib/adhearsion/voip/freeswitch/inbound_connection_manager.rb +38 -0
- data/lib/adhearsion/voip/freeswitch/oes_server.rb +195 -0
- data/lib/adhearsion/voip/menu_state_machine/calculated_match.rb +80 -0
- data/lib/adhearsion/voip/menu_state_machine/matchers.rb +123 -0
- data/lib/adhearsion/voip/menu_state_machine/menu_builder.rb +58 -0
- data/lib/adhearsion/voip/menu_state_machine/menu_class.rb +149 -0
- data/lib/adhearsion.rb +31 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/spec/fixtures/dialplan.rb +3 -0
- data/spec/initializer/test_configuration.rb +267 -0
- data/spec/initializer/test_loading.rb +162 -0
- data/spec/initializer/test_paths.rb +43 -0
- data/spec/silence.rb +10 -0
- data/spec/test_ahn_command.rb +149 -0
- data/spec/test_code_quality.rb +87 -0
- data/spec/test_component_manager.rb +97 -0
- data/spec/test_constants.rb +8 -0
- data/spec/test_drb.rb +104 -0
- data/spec/test_helper.rb +94 -0
- data/spec/test_hooks.rb +37 -0
- data/spec/test_host_definitions.rb +79 -0
- data/spec/test_initialization.rb +105 -0
- data/spec/test_logging.rb +80 -0
- data/spec/test_relationship_properties.rb +54 -0
- data/spec/voip/asterisk/ami_response_definitions.rb +23 -0
- data/spec/voip/asterisk/config_file_generators/test_agents.rb +253 -0
- data/spec/voip/asterisk/config_file_generators/test_queues.rb +325 -0
- data/spec/voip/asterisk/config_file_generators/test_voicemail.rb +306 -0
- data/spec/voip/asterisk/menu_command/test_calculated_match.rb +111 -0
- data/spec/voip/asterisk/menu_command/test_matchers.rb +98 -0
- data/spec/voip/asterisk/mock_ami_server.rb +176 -0
- data/spec/voip/asterisk/test_agi_server.rb +453 -0
- data/spec/voip/asterisk/test_ami.rb +227 -0
- data/spec/voip/asterisk/test_commands.rb +2006 -0
- data/spec/voip/asterisk/test_config_manager.rb +129 -0
- data/spec/voip/dsl/dispatcher_spec_helper.rb +45 -0
- data/spec/voip/dsl/test_dialing_dsl.rb +268 -0
- data/spec/voip/dsl/test_dispatcher.rb +82 -0
- data/spec/voip/dsl/test_parser.rb +87 -0
- data/spec/voip/freeswitch/test_basic_connection_manager.rb +39 -0
- data/spec/voip/freeswitch/test_inbound_connection_manager.rb +39 -0
- data/spec/voip/freeswitch/test_oes_server.rb +9 -0
- data/spec/voip/test_call_routing.rb +127 -0
- data/spec/voip/test_dialplan_manager.rb +372 -0
- data/spec/voip/test_numerical_string.rb +48 -0
- data/spec/voip/test_phone_number.rb +36 -0
- data/test/test_ahn_generator.rb +59 -0
- data/test/test_component_generator.rb +52 -0
- data/test/test_generator_helper.rb +20 -0
- metadata +254 -0
@@ -0,0 +1,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
|