adhearsion 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. data/.gitignore +10 -0
  2. data/CHANGELOG +8 -0
  3. data/README.markdown +33 -0
  4. data/Rakefile +28 -68
  5. data/adhearsion.gemspec +19 -133
  6. data/app_generators/ahn/templates/Gemfile +0 -4
  7. data/app_generators/ahn/templates/components/disabled/restful_rpc/spec/restful_rpc_spec.rb +4 -16
  8. data/lib/adhearsion/cli.rb +17 -0
  9. data/lib/adhearsion/component_manager/component_tester.rb +1 -3
  10. data/lib/adhearsion/component_manager/spec_framework.rb +4 -10
  11. data/lib/adhearsion/foundation/object.rb +10 -0
  12. data/lib/adhearsion/version.rb +1 -1
  13. data/spec/ahn_command_spec.rb +284 -0
  14. data/spec/component_manager_spec.rb +292 -0
  15. data/spec/constants_spec.rb +8 -0
  16. data/spec/drb_spec.rb +65 -0
  17. data/spec/fixtures/dialplan.rb +3 -0
  18. data/spec/foundation/event_socket_spec.rb +168 -0
  19. data/spec/host_definitions_spec.rb +79 -0
  20. data/spec/initialization_spec.rb +163 -0
  21. data/spec/initializer/configuration_spec.rb +270 -0
  22. data/spec/initializer/loading_spec.rb +149 -0
  23. data/spec/initializer/paths_spec.rb +74 -0
  24. data/spec/logging_spec.rb +86 -0
  25. data/spec/relationship_properties_spec.rb +54 -0
  26. data/spec/silence.rb +10 -0
  27. data/spec/spec_helper.rb +101 -0
  28. data/spec/voip/asterisk/agi_server_spec.rb +473 -0
  29. data/spec/voip/asterisk/ami/ami_spec.rb +549 -0
  30. data/spec/voip/asterisk/ami/lexer/ami_fixtures.yml +30 -0
  31. data/spec/voip/asterisk/ami/lexer/lexer_story +291 -0
  32. data/spec/voip/asterisk/ami/lexer/lexer_story.rb +241 -0
  33. data/spec/voip/asterisk/ami/lexer/story_helper.rb +124 -0
  34. data/spec/voip/asterisk/ami/old_tests.rb +204 -0
  35. data/spec/voip/asterisk/ami/super_manager/super_manager_story +25 -0
  36. data/spec/voip/asterisk/ami/super_manager/super_manager_story.rb +15 -0
  37. data/spec/voip/asterisk/ami/super_manager/super_manager_story_helper.rb +5 -0
  38. data/spec/voip/asterisk/commands_spec.rb +2179 -0
  39. data/spec/voip/asterisk/config_file_generators/agents_spec.rb +251 -0
  40. data/spec/voip/asterisk/config_file_generators/queues_spec.rb +323 -0
  41. data/spec/voip/asterisk/config_file_generators/voicemail_spec.rb +306 -0
  42. data/spec/voip/asterisk/config_manager_spec.rb +127 -0
  43. data/spec/voip/asterisk/menu_command/calculated_match_spec.rb +109 -0
  44. data/spec/voip/asterisk/menu_command/matchers_spec.rb +97 -0
  45. data/spec/voip/call_routing_spec.rb +125 -0
  46. data/spec/voip/dialplan_manager_spec.rb +468 -0
  47. data/spec/voip/dsl/dialing_dsl_spec.rb +270 -0
  48. data/spec/voip/dsl/dispatcher_spec.rb +82 -0
  49. data/spec/voip/dsl/dispatcher_spec_helper.rb +45 -0
  50. data/spec/voip/dsl/parser_spec.rb +69 -0
  51. data/spec/voip/freeswitch/basic_connection_manager_spec.rb +39 -0
  52. data/spec/voip/freeswitch/inbound_connection_manager_spec.rb +39 -0
  53. data/spec/voip/freeswitch/oes_server_spec.rb +9 -0
  54. data/spec/voip/numerical_string_spec.rb +61 -0
  55. data/spec/voip/phone_number_spec.rb +45 -0
  56. data/theatre-spec/dsl_examples/dynamic_stomp.rb +7 -0
  57. data/theatre-spec/dsl_examples/simple_before_call.rb +7 -0
  58. data/theatre-spec/dsl_spec.rb +43 -0
  59. data/theatre-spec/invocation_spec.rb +167 -0
  60. data/theatre-spec/namespace_spec.rb +125 -0
  61. data/theatre-spec/spec_helper.rb +37 -0
  62. data/theatre-spec/spec_helper_spec.rb +28 -0
  63. data/theatre-spec/theatre_class_spec.rb +150 -0
  64. metadata +171 -34
@@ -0,0 +1,86 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'The ahn_log command' do
4
+
5
+ it 'should add the ahn_log method to the global namespace' do
6
+ ahn_log.should be Adhearsion::Logging::DefaultAdhearsionLogger
7
+ end
8
+
9
+ it "should log to the standard Adhearsion logger when given arguments" do
10
+ message = "o hai. ur home erly."
11
+ flexmock(Log4r::Logger['ahn']).should_receive(:info).once.with(message)
12
+ ahn_log message
13
+ end
14
+
15
+ it 'should create a new logger when given method_missing' do
16
+ ahn_log.micromenus 'danger will robinson!'
17
+ Log4r::Logger['micromenus'].should_not be nil
18
+ end
19
+
20
+ it 'should define a singleton method on itself of any name found by method_missing' do
21
+ ahn_log.agi "SOMETHING IMPORTANT HAPPENED"
22
+ Adhearsion::Logging::AdhearsionLogger.instance_methods.map{|m| m.to_sym}.should include :agi
23
+ end
24
+
25
+ it "dynamically generated loggers should support logging with blocks" do
26
+ # I had to comment out this it because Flexmock makes it impossible to#
27
+ # set up an expectation for receiving blocks.
28
+
29
+ # proc_to_log = lambda { [1,2,3].reverse.join }
30
+ #
31
+ # info_catcher = flexmock "A logger that responds to info()"
32
+ # info_catcher.should_receive(:info).once.with(&proc_to_log)
33
+ #
34
+ # flexmock(Log4r::Logger).should_receive(:[]).with('log4r')
35
+ # flexmock(Log4r::Logger).should_receive(:[]).once.with('ami').and_return info_catcher
36
+ #
37
+ # ahn_log.ami(&proc_to_log)
38
+ end
39
+
40
+ it 'new loggers created by method_missing() should be instances of AdhearsionLogger' do
41
+ ahn_log.qwerty.should be_a_kind_of Adhearsion::Logging::AdhearsionLogger
42
+ end
43
+
44
+ end
45
+
46
+ # Essential for running the tests
47
+ describe 'Logger level changing' do
48
+
49
+ after :each do
50
+ Adhearsion::Logging.logging_level = :info
51
+ end
52
+
53
+ after :all do
54
+ Adhearsion::Logging.logging_level = :fatal # Silence them again
55
+ end
56
+
57
+ it 'changing the logging level should affect all loggers' do
58
+ loggers = [ahn_log.one, ahn_log.two, ahn_log.three]
59
+ loggers.map(&:level).should_not == [Log4r::WARN] * 3
60
+ Adhearsion::Logging.logging_level = :warn
61
+ loggers.map(&:level).should == [Log4r::WARN] * 3
62
+ end
63
+
64
+ it 'a new logger should have the global Adhearsion logging level' do
65
+ ahn_log.foo.level.should be Log4r::INFO
66
+ Adhearsion::Logging.logging_level = :fatal
67
+ ahn_log.brand_new.level.should be Log4r::FATAL
68
+ end
69
+
70
+ it '#silence!() should change the level to be FATAL' do
71
+ flexmock(Adhearsion::Logging::DefaultAdhearsionLogger).should_receive(:level=).once.with(Log4r::FATAL)
72
+ Adhearsion::Logging.silence!
73
+ # Verify and close manually here because the after-test hook breaks the expectation
74
+ flexmock_verify
75
+ flexmock_close
76
+ end
77
+
78
+ it '#unsilence!() should change the level to be INFO' do
79
+ flexmock(Adhearsion::Logging::DefaultAdhearsionLogger).should_receive(:level=).once.with(Log4r::INFO)
80
+ Adhearsion::Logging.unsilence!
81
+ # Verify and close manually here because the after-test hook breaks the expectation
82
+ flexmock_verify
83
+ flexmock_close
84
+ end
85
+
86
+ end
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+ require 'adhearsion/foundation/relationship_properties'
3
+
4
+ describe "Module#relationships" do
5
+
6
+ describe "Overriding relationships in subclasses" do
7
+
8
+ it "should be overridable in subclasses" do
9
+ super_class = Class.new do
10
+ relationships :storage_medium => Array
11
+ end
12
+ sub_class = Class.new(super_class) do
13
+ relationships :storage_medium => Hash
14
+ end
15
+ super_class.new.send(:storage_medium).should be Array
16
+ sub_class.new.send(:storage_medium).should be Hash
17
+ end
18
+
19
+ it "should not affect other defined relationships" do
20
+ super_class = Class.new do
21
+ relationships :io_class => TCPSocket, :error_class => StandardError
22
+ end
23
+ sub_class = Class.new(super_class) do
24
+ relationships :error_class => RuntimeError
25
+ end
26
+ super_class.new.send(:io_class).should be TCPSocket
27
+ sub_class.new.send(:io_class).should be TCPSocket
28
+ end
29
+
30
+ end
31
+
32
+ it "should be accessible within instance methods of that Class as another instance method" do
33
+ klass = Class.new do
34
+ relationships :struct => Struct
35
+ def new_struct
36
+ struct.new
37
+ end
38
+ end
39
+ end
40
+
41
+ it "should be accessible in subclasses" do
42
+ super_class = Class.new do
43
+ relationships :number_class => Bignum
44
+ end
45
+
46
+ Class.new(super_class) do
47
+ def number_class_name
48
+ number_class.name
49
+ end
50
+ end.new.number_class_name.should == "Bignum"
51
+
52
+ end
53
+
54
+ end
@@ -0,0 +1,10 @@
1
+ #
2
+ # Seeing the dump of pending specs every time you run them is distracting.
3
+ # This file lets you skip pending specs.
4
+ class Test::Unit::TestResult
5
+ def add_disabled(name)
6
+ #nothing!
7
+ end
8
+ end
9
+
10
+ Adhearsion::Logging.silence!
@@ -0,0 +1,101 @@
1
+ Dir.chdir File.join(File.dirname(__FILE__), '..')
2
+ $:.push('.')
3
+
4
+ require 'rubygems'
5
+ require 'rspec/core'
6
+ require 'bundler/setup'
7
+
8
+ def require_or_report_dependency(require_name, gem_name)
9
+ begin
10
+ require require_name
11
+ rescue LoadError
12
+ report_dependency!(gem_name)
13
+ end
14
+ end
15
+
16
+ def report_dependency!(name)
17
+ 2.times { puts }
18
+ puts "You need #{name} to run these tests: gem install #{name}"
19
+ 2.times { puts }
20
+ exit!
21
+ end
22
+
23
+ class Object
24
+ alias :the_following_code :lambda
25
+ end
26
+
27
+ require_or_report_dependency('flexmock/test_unit', 'flexmock')
28
+ require_or_report_dependency('active_support', 'activesupport')
29
+ # require_or_report_dependency('ruby-debug', 'ruby-debug')
30
+ require_or_report_dependency('rubigen', 'rubigen')
31
+
32
+ RSpec.configure do |config|
33
+ config.mock_framework = :flexmock
34
+ config.filter_run_excluding :ignore => true
35
+ #config.filter_run :focus => true
36
+ config.color_enabled = true
37
+ end
38
+
39
+ require 'pp'
40
+ require 'stringio'
41
+
42
+ $: << File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
43
+ $: << File.expand_path('lib')
44
+ $: << File.dirname(__FILE__)
45
+
46
+ require 'adhearsion'
47
+
48
+ module InitializerStubs
49
+
50
+ DEFAULT_AHNRC_DATA_STRUCTURE = YAML.load_file(
51
+ File.dirname(__FILE__) + "/../app_generators/ahn/templates/.ahnrc"
52
+ ) unless defined? DEFAULT_AHNRC_DATA_STRUCTURE
53
+
54
+ UNWANTED_BEHAVIOR = {
55
+ Adhearsion::Initializer => [:initialize_log_file, :switch_to_root_directory, :daemonize!, :load],
56
+ Adhearsion::Initializer.metaclass => { :get_rules_from => DEFAULT_AHNRC_DATA_STRUCTURE },
57
+ } unless defined? UNWANTED_BEHAVIOR
58
+
59
+ def stub_behavior_for_initializer_with_no_path_changing_behavior
60
+ stub_unwanted_behavior
61
+ yield if block_given?
62
+ ensure
63
+ unstub_directory_changing_behavior
64
+ end
65
+
66
+ def with_new_initializer_with_no_path_changing_behavior(&block)
67
+ stub_behavior_for_initializer_with_no_path_changing_behavior do
68
+ block.call Adhearsion::Initializer.start('path does not matter')
69
+ end
70
+ end
71
+
72
+ def stub_unwanted_behavior
73
+ UNWANTED_BEHAVIOR.each do |stub_victim_class, undesired_methods|
74
+ undesired_methods.each do |undesired_method_name_or_key_value_pair|
75
+ undesired_method_name, method_implementation = case undesired_method_name_or_key_value_pair
76
+ when Array
77
+ [undesired_method_name_or_key_value_pair.first, lambda { |*args| undesired_method_name_or_key_value_pair.last } ]
78
+ else
79
+ [undesired_method_name_or_key_value_pair, lambda{ |*args| }]
80
+ end
81
+ stub_victim_class.send(:alias_method, "pre_stubbed_#{undesired_method_name}", undesired_method_name)
82
+ stub_victim_class.send(:define_method, undesired_method_name, &method_implementation)
83
+ end
84
+ end
85
+ end
86
+
87
+ def unstub_directory_changing_behavior
88
+ UNWANTED_BEHAVIOR.each do |stub_victim_class, undesired_methods|
89
+ undesired_methods.each do |undesired_method_name|
90
+ undesired_method_name = undesired_method_name.first if undesired_method_name.kind_of? Array
91
+ stub_victim_class.send(:alias_method, undesired_method_name, "pre_stubbed_#{undesired_method_name}")
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ Adhearsion::Initializer.ahn_root = File.dirname(__FILE__) + '/fixtures'
98
+ require 'spec/silence' unless ENV['SHOW_DISABLED']
99
+
100
+ require 'adhearsion/voip/asterisk'
101
+ require 'adhearsion/component_manager'
@@ -0,0 +1,473 @@
1
+ require 'spec_helper'
2
+ require 'adhearsion/voip/asterisk'
3
+
4
+ module CallVariableTestHelper
5
+ def parsed_call_variables_from(io)
6
+ Adhearsion::Call::Variables::Parser.parse(io).variables
7
+ end
8
+
9
+ def coerce_call_variables(variables)
10
+ Adhearsion::Call::Variables::Parser.coerce_variables(variables)
11
+ end
12
+
13
+ def merged_hash_with_call_variables(new_hash)
14
+ call_variables_with_new_query = typical_call_variables_hash.merge new_hash
15
+ coerce_call_variables call_variables_with_new_query
16
+ end
17
+
18
+ def parsed_call_variables_with_query(query_string)
19
+ request_string = "agi://10.0.0.0/#{query_string}"
20
+ merged_hash_with_call_variables({:request => request_string})[:query]
21
+ end
22
+
23
+ def typical_call_variable_io
24
+ StringIO.new(typical_call_variable_section)
25
+ end
26
+
27
+ def typical_call_variable_section
28
+ <<-VARIABLES
29
+ agi_network: yes
30
+ agi_request: agi://10.0.0.152/monkey?foo=bar&qaz=qwerty
31
+ agi_channel: SIP/marcel-b58046e0
32
+ agi_language: en
33
+ agi_type: SIP
34
+ agi_uniqueid: 1191245124.16
35
+ agi_callerid: 011441234567899
36
+ agi_calleridname: unknown
37
+ agi_callingpres: 0
38
+ agi_callingani2: 0
39
+ agi_callington: 0
40
+ agi_callingtns: 0
41
+ agi_dnid: 911
42
+ agi_rdnis: unknown
43
+ agi_context: adhearsion
44
+ agi_extension: 911
45
+ agi_priority: 1
46
+ agi_enhanced: 0.0
47
+ agi_accountcode:
48
+
49
+ VARIABLES
50
+ end
51
+
52
+ # TODO:
53
+ # - "unknown" should be converted to nil
54
+ # - "yes" or "no" should be converted to true or false
55
+ # - numbers beginning with a 0 MUST be converted to a NumericalString
56
+ # - Look up why there are so many zeroes. They're likely reprentative of some PRI definition.
57
+
58
+ def typical_call_variables_hash
59
+ uncoerced_variable_map = expected_uncoerced_variable_map
60
+
61
+ uncoerced_variable_map[:request] = URI.parse(uncoerced_variable_map[:request])
62
+ uncoerced_variable_map[:extension] = 911 # Adhearsion::VoIP::DSL::PhoneNumber.new(uncoerced_variable_map[:extension]) #
63
+ uncoerced_variable_map[:callerid] = Adhearsion::VoIP::DSL::NumericalString.new(uncoerced_variable_map[:callerid])
64
+
65
+ uncoerced_variable_map[:network] = true
66
+ uncoerced_variable_map[:calleridname] = nil
67
+ uncoerced_variable_map[:callingpres] = 0
68
+ uncoerced_variable_map[:callingani2] = 0
69
+ uncoerced_variable_map[:callingtns] = 0
70
+ uncoerced_variable_map[:dnid] = 911
71
+ uncoerced_variable_map[:rdnis] = nil
72
+ uncoerced_variable_map[:priority] = 1
73
+ uncoerced_variable_map[:enhanced] = 0.0
74
+ uncoerced_variable_map[:uniqueid] = "1191245124.16"
75
+
76
+ uncoerced_variable_map[:type_of_calling_number] = Adhearsion::VoIP::Constants::Q931_TYPE_OF_NUMBER[uncoerced_variable_map.delete(:callington).to_i]
77
+
78
+ coerced_variable_map = uncoerced_variable_map
79
+ coerced_variable_map[:query] = {"foo" => "bar", "qaz" => "qwerty"}
80
+ coerced_variable_map[:foo] = 'bar'
81
+ coerced_variable_map[:qaz] = 'qwerty'
82
+ coerced_variable_map
83
+ end
84
+
85
+ def expected_uncoerced_variable_map
86
+ {:network => 'yes',
87
+ :request => 'agi://10.0.0.152/monkey?foo=bar&qaz=qwerty',
88
+ :channel => 'SIP/marcel-b58046e0',
89
+ :language => 'en',
90
+ :type => 'SIP',
91
+ :uniqueid => '1191245124.16',
92
+ :callerid => '011441234567899',
93
+ :calleridname => 'unknown',
94
+ :callingpres => '0',
95
+ :callingani2 => '0',
96
+ :callington => '0',
97
+ :callingtns => '0',
98
+ :dnid => '911',
99
+ :rdnis => 'unknown',
100
+ :context => 'adhearsion',
101
+ :extension => '911',
102
+ :priority => '1',
103
+ :enhanced => '0.0',
104
+ :accountcode => ''}
105
+ end
106
+ end
107
+
108
+ module AgiServerTestHelper
109
+ def stub_before_call_hooks!
110
+ flexstub(Adhearsion::Events).should_receive(:trigger).with([:asterisk, :before_call], Proc).and_return
111
+ end
112
+
113
+ def stub_confirmation_manager!
114
+ flexstub(Adhearsion::DialPlan::ConfirmationManager).should_receive(:confirmation_call?).and_return false
115
+ end
116
+ end
117
+
118
+ describe "The AGI server's serve() method" do
119
+
120
+ include AgiServerTestHelper
121
+
122
+ attr_reader :server_class, :server
123
+ before :each do
124
+ @server_class = Adhearsion::VoIP::Asterisk::AGI::Server::RubyServer
125
+ @server = @server_class.new(:port,:host)
126
+ end
127
+
128
+ before :each do
129
+ Adhearsion::Events.reinitialize_theatre!
130
+ end
131
+
132
+ it 'should instantiate a new Call with the IO object it receives' do
133
+ stub_before_call_hooks!
134
+ io_mock = flexmock "Mock IO object that's passed to the serve() method"
135
+ call_mock = flexmock "A Call mock that's returned by Adhearsion#receive_call_from", :variable => {}
136
+ flexstub(server_class).should_receive(:ahn_log)
137
+ the_following_code {
138
+ flexmock(Adhearsion).should_receive(:receive_call_from).once.with(io_mock).and_throw :created_call!
139
+ server.serve(io_mock)
140
+ }.should throw_symbol :created_call!
141
+ end
142
+
143
+ it 'should hand the call off to a new Manager if the request is agi://IP_ADDRESS_HERE' do
144
+ stub_before_call_hooks!
145
+ call_mock = flexmock 'A new mock call that will be passed to the manager', :variables => {}, :unique_identifier => "X"
146
+
147
+ flexmock(Adhearsion).should_receive(:receive_call_from).once.and_return call_mock
148
+ manager_mock = flexmock 'a mock dialplan manager'
149
+ manager_mock.should_receive(:handle).once.with(call_mock)
150
+ flexmock(Adhearsion::DialPlan::Manager).should_receive(:new).once.and_return manager_mock
151
+ server.serve(nil)
152
+ end
153
+
154
+ it 'should hand off a call to a ConfirmationManager if the request begins with confirm!' do
155
+ confirm_options = Adhearsion::DialPlan::ConfirmationManager.encode_hash_for_dial_macro_argument :timeout => 20, :key => "#"
156
+ call_mock = flexmock "a call that has network_script as a variable", :variables => {:network_script => "confirm!#{confirm_options[/^M\(\^?(.+)\)$/,1]}"}, :unique_identifier => "X"
157
+ manager_mock = flexmock 'a mock ConfirmationManager'
158
+
159
+ the_following_code {
160
+ flexstub(Adhearsion).should_receive(:receive_call_from).once.and_return(call_mock)
161
+ flexmock(Adhearsion::DialPlan::ConfirmationManager).should_receive(:confirmation_call?).once.with(call_mock).and_return true
162
+ flexmock(Adhearsion::DialPlan::ConfirmationManager).should_receive(:handle).once.with(call_mock).and_throw :handled_call!
163
+ server.serve(nil)
164
+ }.should throw_symbol :handled_call!
165
+ end
166
+
167
+ it 'calling the serve() method invokes the before_call event' do
168
+ mock_io = flexmock "mock IO object given to AGIServer#serve"
169
+ mock_call = flexmock "mock Call"
170
+ flexmock(Adhearsion).should_receive(:receive_call_from).once.with(mock_io).and_return mock_call
171
+
172
+ flexmock(Adhearsion::Events).should_receive(:trigger_immediately).once.with([:asterisk, :before_call], mock_call).
173
+ and_throw :triggered
174
+
175
+ the_following_code {
176
+ server.serve mock_io
177
+ }.should throw_symbol :triggered
178
+ end
179
+
180
+ it 'should execute the hungup_call event when a HungupExtensionCallException is raised' do
181
+ call_mock = flexmock 'a bogus call', :hungup_call? => true, :variables => {:extension => "h"}, :unique_identifier => "X"
182
+ mock_env = flexmock "A mock execution environment which gets passed along in the HungupExtensionCallException"
183
+
184
+ stub_confirmation_manager!
185
+ flexstub(Adhearsion).should_receive(:receive_call_from).once.and_return(call_mock)
186
+ flexmock(Adhearsion::DialPlan::Manager).should_receive(:handle).once.and_raise Adhearsion::HungupExtensionCallException.new(mock_env)
187
+ flexmock(Adhearsion::Events).should_receive(:trigger).once.with([:asterisk, :hungup_call], mock_env).and_throw :hungup_call
188
+
189
+ the_following_code { server.serve nil }.should throw_symbol :hungup_call
190
+ end
191
+
192
+ it 'should execute the OnFailedCall hooks when a FailedExtensionCallException is raised' do
193
+ call_mock = flexmock 'a bogus call', :failed_call? => true, :variables => {:extension => "failed"}, :unique_identifier => "X"
194
+ mock_env = flexmock "A mock execution environment which gets passed along in the HungupExtensionCallException", :failed_reason => "does not matter"
195
+
196
+ server = Adhearsion::VoIP::Asterisk::AGI::Server::RubyServer.new :port, :host
197
+
198
+ flexmock(Adhearsion).should_receive(:receive_call_from).once.and_return(call_mock)
199
+ flexmock(Adhearsion::DialPlan::Manager).should_receive(:handle).once.and_raise Adhearsion::FailedExtensionCallException.new(mock_env)
200
+ flexmock(Adhearsion::Events).should_receive(:trigger).once.with([:asterisk, :failed_call], mock_env).and_throw :failed_call
201
+ the_following_code { server.serve nil }.should throw_symbol :failed_call
202
+ end
203
+
204
+ end
205
+
206
+ describe "Active Calls" do
207
+ include CallVariableTestHelper
208
+ attr_accessor :typical_call
209
+
210
+ before do
211
+ @mock_io = StringIO.new
212
+ @typical_call = Adhearsion::Call.new(@mock_io, typical_call_variables_hash)
213
+ end
214
+
215
+ after do
216
+ Adhearsion::active_calls.clear!
217
+ end
218
+
219
+ it 'A Call object can be instantiated with a hash of attributes' do
220
+ the_following_code {
221
+ Adhearsion::Call.new(@mock_io, {})
222
+ }.should_not raise_error
223
+ end
224
+
225
+ it 'Attributes passed into initialization of call object are accessible as attributes on the object' do
226
+ Adhearsion::Call.new(@mock_io, {:channel => typical_call_variables_hash[:channel]}).channel.should == typical_call_variables_hash[:channel]
227
+ end
228
+
229
+ it 'Attributes passed into initialization of call object are accessible in the variables() Hash' do
230
+ variables = Adhearsion::Call.new(@mock_io, typical_call_variables_hash).variables
231
+ typical_call_variables_hash.each{|key, value|
232
+ value.should == variables[key]
233
+ }
234
+ end
235
+
236
+ it 'Can add a call to the active calls list' do
237
+ Adhearsion.active_calls.any?.should be false
238
+ Adhearsion.active_calls << typical_call
239
+ Adhearsion.active_calls.size.should == 1
240
+ end
241
+
242
+ it 'A hungup call removes itself from the active calls' do
243
+ mock_io = flexmock typical_call_variable_io
244
+ mock_io.should_receive(:close).once
245
+
246
+ size_before = Adhearsion.active_calls.size
247
+
248
+ call = Adhearsion.receive_call_from mock_io
249
+ Adhearsion.active_calls.size.should > size_before
250
+ call.hangup!
251
+ Adhearsion.active_calls.size.should == size_before
252
+ end
253
+
254
+ it 'Can find active call by unique ID' do
255
+ Adhearsion.active_calls << typical_call
256
+ Adhearsion.active_calls.find(typical_call_variables_hash[:channel]).should_not be nil
257
+ end
258
+
259
+ it 'A call can store the IO associated with the PBX/switch connection' do
260
+ Adhearsion::Call.new(@mock_io, {}).should respond_to(:io)
261
+ end
262
+
263
+ it 'A call can be instantiated given a PBX/switch IO' do
264
+ call = Adhearsion::Call.receive_from(typical_call_variable_io)
265
+ call.should be_a_kind_of(Adhearsion::Call)
266
+ call.channel.should == typical_call_variables_hash[:channel]
267
+ end
268
+
269
+ it 'A call with an extension of "t" raises a UselessCallException' do
270
+ the_following_code {
271
+ Adhearsion::Call.new(@mock_io, typical_call_variables_hash.merge(:extension => 't'))
272
+ }.should raise_error(Adhearsion::UselessCallException)
273
+ end
274
+
275
+ it 'Can create a call and add it via a top-level method on the Adhearsion module' do
276
+ Adhearsion.active_calls.any?.should be false
277
+ call = Adhearsion.receive_call_from(typical_call_variable_io)
278
+ call.should be_a_kind_of(Adhearsion::Call)
279
+ Adhearsion.active_calls.size.should == 1
280
+ end
281
+
282
+ it 'A call can identify its originating voip platform' do
283
+ call = Adhearsion::receive_call_from(typical_call_variable_io)
284
+ call.originating_voip_platform.should be(:asterisk)
285
+ end
286
+
287
+ end
288
+
289
+ describe 'A new Call object' do
290
+
291
+ include CallVariableTestHelper
292
+
293
+ it "it should have an @inbox object that's a synchronized Queue" do
294
+ new_call = Adhearsion::Call.new(nil, {})
295
+ new_call.inbox.should be_a_kind_of Queue
296
+ new_call.should respond_to :<<
297
+ end
298
+
299
+ it 'the unique_identifier() method should return the :channel variable for :asterisk calls' do
300
+ variables = typical_call_variables_hash
301
+ new_call = Adhearsion::Call.new(nil, typical_call_variables_hash)
302
+ flexmock(new_call).should_receive(:originating_voip_platform).and_return :asterisk
303
+ new_call.unique_identifier.should == variables[:channel]
304
+ end
305
+
306
+ it "Call#define_singleton_accessor_with_pair should define a singleton method, not a class method" do
307
+ control = Adhearsion::Call.new(nil, {})
308
+ experiment = Adhearsion::Call.new(nil, {})
309
+
310
+ experiment.send(:define_singleton_accessor_with_pair, "ohai", 123)
311
+ experiment.should respond_to "ohai"
312
+ control.should_not respond_to "ohai"
313
+ end
314
+
315
+ end
316
+
317
+ describe 'the Calls collection' do
318
+
319
+ include CallVariableTestHelper
320
+
321
+ it 'the #<< method should add a Call to the Hash with its unique_id' do
322
+ id = rand
323
+ collection = Adhearsion::Calls.new
324
+ call = Adhearsion::Call.new(nil, {})
325
+ flexmock(call).should_receive(:unique_identifier).and_return id
326
+ collection << call
327
+ hash = collection.instance_variable_get("@calls")
328
+ hash.empty?.should_not be true
329
+ hash[id].should be call
330
+ end
331
+
332
+ it '#size should return the size of the Hash' do
333
+ collection = Adhearsion::Calls.new
334
+ collection.size.should be 0
335
+ collection << Adhearsion::Call.new(nil, {})
336
+ collection.size.should be 1
337
+ end
338
+
339
+ it '#remove_inactive_call should delete the call in the Hash' do
340
+ collection = Adhearsion::Calls.new
341
+
342
+ number_of_calls = 10
343
+ unique_ids = Array.new(number_of_calls) { rand }
344
+ calls = unique_ids.map { |id| Adhearsion::Call.new(nil, {:uniqueid => id}) }
345
+ calls.each { |call| collection << call }
346
+
347
+ deleted_call = calls[number_of_calls / 2]
348
+ collection.remove_inactive_call deleted_call
349
+ collection.size.should be number_of_calls - 1
350
+ end
351
+
352
+ it '#find_by_unique_id should pull the Call from the Hash using the unique_id' do
353
+ id = rand
354
+ call_database = flexmock "a mock Hash in which calls are stored"
355
+ call_database.should_receive(:[]).once.with(id)
356
+ collection = Adhearsion::Calls.new
357
+ flexmock(collection).should_receive(:calls).once.and_return(call_database)
358
+ collection.find(id)
359
+ end
360
+
361
+ end
362
+
363
+ describe 'Typical call variable parsing with typical data that has no special treatment' do
364
+ include CallVariableTestHelper
365
+ attr_reader :variables, :io
366
+
367
+ before(:each) do
368
+ @io = typical_call_variable_io
369
+ @variables = parsed_call_variables_from(io)
370
+ end
371
+
372
+ # it "listing all variable names" do
373
+ # parser.variable_names.map(&:to_s).sort.should == typical_call_variables_hash.keys.map(&:to_s).sort
374
+ # end
375
+
376
+ it "extracts call variables into a Hash" do
377
+ variables.kind_of?(Hash).should be true
378
+ end
379
+
380
+ it "extracting and converting variables and their values to a hash" do
381
+ typical_call_variables_hash.each{|key, value|
382
+ value.should == variables[key]
383
+ }
384
+ end
385
+ end
386
+
387
+ describe 'Call variable parsing with data that is treated specially' do
388
+ include CallVariableTestHelper
389
+ it "callington is renamed to type_of_calling_number" do
390
+ variables = parsed_call_variables_from typical_call_variable_io
391
+ variables[:type_of_calling_number].should == :unknown
392
+ variables.has_key?(:callington).should == false
393
+ end
394
+ it "normalizes the context to be a valid Ruby method name"
395
+ it "value of 'no' converts to false" do
396
+ merged_hash_with_call_variables(:no_var => "no")[:no_var].should be(false)
397
+ end
398
+ it "value of 'yes' converts to true"
399
+ it "value of 'unknown' converts to nil"
400
+ it 'separate_line_into_key_value_pair parses values with spaces in them' do
401
+ key, value = Adhearsion::Call::Variables::Parser.separate_line_into_key_value_pair("foo: My Name")
402
+ value.should == 'My Name'
403
+ end
404
+
405
+ it "Uniqueid remains a String, not a Float" do
406
+ uniqueid = "123456.12831"
407
+ variables = merged_hash_with_call_variables :uniqueid => uniqueid
408
+ variables[:uniqueid].should ==uniqueid
409
+ end
410
+
411
+ end
412
+
413
+ describe 'Typical call variable line parsing with a typical line that is not treated specially' do
414
+ include CallVariableTestHelper
415
+ attr_reader :parser, :line
416
+
417
+ before(:each) do
418
+ @line = 'agi_channel: SIP/marcel-b58046e0'
419
+ @key, @value = Adhearsion::Call::Variables::Parser.separate_line_into_key_value_pair line
420
+ end
421
+
422
+ it "raw name is extracted correctly" do
423
+ @key.should == 'agi_channel'
424
+ end
425
+
426
+ it "raw value is extracted correctly" do
427
+ @value.should == 'SIP/marcel-b58046e0'
428
+ end
429
+ end
430
+
431
+ describe 'Call variable line parsing with a line that is treated specially' do
432
+ include CallVariableTestHelper
433
+ attr_reader :key, :value, :line
434
+
435
+ before(:each) do
436
+ @line = 'agi_request: agi://10.0.0.152'
437
+ @key, @value = Adhearsion::Call::Variables::Parser.separate_line_into_key_value_pair line
438
+ end
439
+
440
+ it "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
441
+ @key.should == 'agi_request'
442
+ @value.should == 'agi://10.0.0.152'
443
+ end
444
+
445
+ end
446
+
447
+ describe "Extracting the query from the request URI" do
448
+ include CallVariableTestHelper
449
+ attr_reader :parser
450
+
451
+ before(:each) do
452
+ # We don't want this Call::Variables::Parser to call parse because we only care about handling the request
453
+ # variable which we mock here.
454
+ @parser = Adhearsion::Call::Variables::Parser.new(StringIO.new('This argument does not matter'))
455
+ end
456
+
457
+ it "an empty hash is returned if there is no query string" do
458
+ parsed_call_variables_with_query('').should == {}
459
+ end
460
+
461
+ it "a key and value for parameter is returned when there is one query string parameter" do
462
+ parsed_call_variables_with_query('?foo=bar').should == {'foo' => 'bar'}
463
+ end
464
+
465
+ it "both key/value pairs are returned when there are a pair of query string parameters" do
466
+ parsed_call_variables_with_query('?foo=bar&baz=quux').should == {'foo' => 'bar', 'baz' => 'quux'}
467
+ end
468
+
469
+ it "all key/value pairs are returned when there are more than a pair of query string parameters" do
470
+ parsed_call_variables_with_query('?foo=bar&baz=quux&name=marcel').should == {'foo' => 'bar', 'baz' => 'quux', 'name' => 'marcel'}
471
+ end
472
+
473
+ end