adhearsion 2.6.4 → 3.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (242) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +6 -9
  3. data/CHANGELOG.md +22 -7
  4. data/Gemfile +2 -0
  5. data/README.markdown +4 -5
  6. data/Rakefile +1 -2
  7. data/adhearsion.gemspec +19 -8
  8. data/features/cli_create.feature +19 -3
  9. data/features/step_definitions/cli_steps.rb +0 -11
  10. data/features/support/env.rb +3 -4
  11. data/lib/adhearsion.rb +48 -27
  12. data/lib/adhearsion/call.rb +34 -50
  13. data/lib/adhearsion/call_controller.rb +6 -12
  14. data/lib/adhearsion/call_controller/dial.rb +15 -53
  15. data/lib/adhearsion/call_controller/input.rb +39 -162
  16. data/lib/adhearsion/call_controller/input/ask_grammar_builder.rb +44 -0
  17. data/lib/adhearsion/call_controller/input/menu_builder.rb +136 -0
  18. data/lib/adhearsion/call_controller/input/prompt_builder.rb +78 -0
  19. data/lib/adhearsion/call_controller/input/result.rb +46 -0
  20. data/lib/adhearsion/call_controller/output.rb +48 -67
  21. data/lib/adhearsion/call_controller/output/abstract_player.rb +3 -3
  22. data/lib/adhearsion/call_controller/output/async_player.rb +3 -3
  23. data/lib/adhearsion/call_controller/output/player.rb +1 -1
  24. data/lib/adhearsion/call_controller/record.rb +23 -8
  25. data/lib/adhearsion/calls.rb +1 -1
  26. data/lib/adhearsion/cli_commands/ahn_command.rb +2 -65
  27. data/lib/adhearsion/cli_commands/thor_errors.rb +0 -6
  28. data/lib/adhearsion/configuration.rb +91 -39
  29. data/lib/adhearsion/core_ext/blather/stanza.rb +41 -0
  30. data/lib/adhearsion/core_ext/blather/stanza/presence.rb +13 -0
  31. data/lib/adhearsion/error.rb +5 -0
  32. data/lib/adhearsion/event.rb +21 -0
  33. data/lib/adhearsion/event/active_speaker.rb +11 -0
  34. data/lib/adhearsion/event/answered.rb +11 -0
  35. data/lib/adhearsion/event/asterisk.rb +10 -0
  36. data/lib/adhearsion/event/asterisk/ami.rb +34 -0
  37. data/lib/adhearsion/event/complete.rb +75 -0
  38. data/lib/adhearsion/event/dtmf.rb +11 -0
  39. data/lib/adhearsion/event/end.rb +22 -0
  40. data/lib/adhearsion/event/input_timers_started.rb +9 -0
  41. data/lib/adhearsion/event/joined.rb +17 -0
  42. data/lib/adhearsion/event/offer.rb +14 -0
  43. data/lib/adhearsion/event/ringing.rb +11 -0
  44. data/lib/adhearsion/event/started_speaking.rb +13 -0
  45. data/lib/adhearsion/event/stopped_speaking.rb +13 -0
  46. data/lib/adhearsion/event/unjoined.rb +17 -0
  47. data/lib/adhearsion/events.rb +47 -66
  48. data/lib/adhearsion/foundation.rb +0 -1
  49. data/lib/adhearsion/foundation/object.rb +0 -5
  50. data/lib/adhearsion/generators/app/app_generator.rb +4 -1
  51. data/lib/adhearsion/generators/app/templates/Gemfile.erb +2 -10
  52. data/lib/adhearsion/generators/app/templates/adhearsion.erb +9 -9
  53. data/lib/adhearsion/generators/app/templates/config.ru +7 -0
  54. data/lib/adhearsion/generators/app/templates/en.yml +4 -0
  55. data/lib/adhearsion/generators/app/templates/events.erb +2 -2
  56. data/lib/adhearsion/generators/app/templates/hello_world.wav +0 -0
  57. data/lib/adhearsion/generators/app/templates/simon_game.rb +2 -1
  58. data/lib/adhearsion/generators/app/templates/simon_game_spec.rb +2 -2
  59. data/lib/adhearsion/has_headers.rb +34 -0
  60. data/lib/adhearsion/http_server.rb +37 -0
  61. data/lib/adhearsion/initializer.rb +19 -153
  62. data/lib/adhearsion/logging.rb +6 -25
  63. data/lib/adhearsion/outbound_call.rb +5 -5
  64. data/lib/adhearsion/plugin.rb +1 -0
  65. data/lib/adhearsion/protocol_error.rb +26 -0
  66. data/lib/adhearsion/rayo.rb +30 -0
  67. data/lib/adhearsion/rayo/client.rb +62 -0
  68. data/lib/adhearsion/rayo/client/component_registry.rb +33 -0
  69. data/lib/adhearsion/rayo/command.rb +21 -0
  70. data/lib/adhearsion/rayo/command/accept.rb +16 -0
  71. data/lib/adhearsion/rayo/command/answer.rb +16 -0
  72. data/lib/adhearsion/rayo/command/dial.rb +57 -0
  73. data/lib/adhearsion/rayo/command/hangup.rb +16 -0
  74. data/lib/adhearsion/rayo/command/join.rb +43 -0
  75. data/lib/adhearsion/rayo/command/mute.rb +13 -0
  76. data/lib/adhearsion/rayo/command/redirect.rb +23 -0
  77. data/lib/adhearsion/rayo/command/reject.rb +40 -0
  78. data/lib/adhearsion/rayo/command/unjoin.rb +24 -0
  79. data/lib/adhearsion/rayo/command/unmute.rb +13 -0
  80. data/lib/adhearsion/rayo/command_node.rb +47 -0
  81. data/lib/adhearsion/rayo/component.rb +21 -0
  82. data/lib/adhearsion/rayo/component/asterisk.rb +13 -0
  83. data/lib/adhearsion/rayo/component/asterisk/agi.rb +14 -0
  84. data/lib/adhearsion/rayo/component/asterisk/agi/command.rb +46 -0
  85. data/lib/adhearsion/rayo/component/asterisk/ami.rb +14 -0
  86. data/lib/adhearsion/rayo/component/asterisk/ami/action.rb +61 -0
  87. data/lib/adhearsion/rayo/component/component_node.rb +90 -0
  88. data/lib/adhearsion/rayo/component/input.rb +186 -0
  89. data/lib/adhearsion/rayo/component/output.rb +471 -0
  90. data/lib/adhearsion/rayo/component/prompt.rb +53 -0
  91. data/lib/adhearsion/rayo/component/receive_fax.rb +26 -0
  92. data/lib/adhearsion/rayo/component/record.rb +165 -0
  93. data/lib/adhearsion/rayo/component/send_fax.rb +64 -0
  94. data/lib/adhearsion/rayo/component/stop.rb +11 -0
  95. data/lib/adhearsion/rayo/connection.rb +12 -0
  96. data/lib/adhearsion/rayo/connection/asterisk.rb +74 -0
  97. data/lib/adhearsion/rayo/connection/connected.rb +22 -0
  98. data/lib/adhearsion/rayo/connection/generic_connection.rb +22 -0
  99. data/lib/adhearsion/rayo/connection/xmpp.rb +198 -0
  100. data/lib/adhearsion/rayo/disconnected_error.rb +22 -0
  101. data/lib/adhearsion/{punchblock_plugin → rayo}/initializer.rb +19 -19
  102. data/lib/adhearsion/rayo/rayo_node.rb +127 -0
  103. data/lib/adhearsion/rayo/ref.rb +57 -0
  104. data/lib/adhearsion/statistics.rb +1 -1
  105. data/lib/adhearsion/tasks.rb +1 -2
  106. data/lib/adhearsion/tasks/configuration.rb +1 -1
  107. data/lib/adhearsion/tasks/environment.rb +0 -2
  108. data/lib/adhearsion/tasks/i18n.rb +49 -0
  109. data/lib/adhearsion/translator.rb +11 -0
  110. data/lib/adhearsion/translator/asterisk.rb +234 -0
  111. data/lib/adhearsion/translator/asterisk/agi_app.rb +17 -0
  112. data/lib/adhearsion/translator/asterisk/agi_command.rb +45 -0
  113. data/lib/adhearsion/translator/asterisk/ami_error_converter.rb +20 -0
  114. data/lib/adhearsion/translator/asterisk/call.rb +416 -0
  115. data/lib/adhearsion/translator/asterisk/channel.rb +43 -0
  116. data/lib/adhearsion/translator/asterisk/component.rb +88 -0
  117. data/lib/adhearsion/translator/asterisk/component/asterisk.rb +15 -0
  118. data/lib/adhearsion/translator/asterisk/component/asterisk/agi_command.rb +42 -0
  119. data/lib/adhearsion/translator/asterisk/component/asterisk/ami_action.rb +68 -0
  120. data/lib/adhearsion/translator/asterisk/component/composed_prompt.rb +76 -0
  121. data/lib/adhearsion/translator/asterisk/component/dtmf_recognizer.rb +137 -0
  122. data/lib/adhearsion/translator/asterisk/component/input.rb +34 -0
  123. data/lib/adhearsion/translator/asterisk/component/input_component.rb +90 -0
  124. data/lib/adhearsion/translator/asterisk/component/mrcp_native_prompt.rb +71 -0
  125. data/lib/adhearsion/translator/asterisk/component/mrcp_prompt.rb +55 -0
  126. data/lib/adhearsion/translator/asterisk/component/mrcp_recog_prompt.rb +165 -0
  127. data/lib/adhearsion/translator/asterisk/component/output.rb +233 -0
  128. data/lib/adhearsion/translator/asterisk/component/record.rb +101 -0
  129. data/lib/adhearsion/translator/asterisk/component/stop_by_redirect.rb +30 -0
  130. data/lib/adhearsion/translator/asterisk/unimrcp_app.rb +28 -0
  131. data/lib/adhearsion/uri_list.rb +21 -0
  132. data/lib/adhearsion/version.rb +1 -1
  133. data/spec/adhearsion/call_controller/dial_spec.rb +79 -1420
  134. data/spec/adhearsion/call_controller/input_spec.rb +1141 -237
  135. data/spec/adhearsion/call_controller/output/async_player_spec.rb +10 -10
  136. data/spec/adhearsion/call_controller/output/player_spec.rb +8 -8
  137. data/spec/adhearsion/call_controller/output_spec.rb +162 -215
  138. data/spec/adhearsion/call_controller/record_spec.rb +15 -16
  139. data/spec/adhearsion/call_controller_spec.rb +23 -40
  140. data/spec/adhearsion/call_spec.rb +123 -129
  141. data/spec/adhearsion/calls_spec.rb +3 -3
  142. data/spec/adhearsion/configuration_spec.rb +94 -108
  143. data/spec/adhearsion/event/answered_spec.rb +50 -0
  144. data/spec/adhearsion/event/asterisk/Find Results +402 -0
  145. data/spec/adhearsion/event/asterisk/ami_spec.rb +81 -0
  146. data/spec/adhearsion/event/complete_spec.rb +176 -0
  147. data/spec/adhearsion/event/dtmf_spec.rb +35 -0
  148. data/spec/adhearsion/event/end_spec.rb +85 -0
  149. data/spec/adhearsion/event/input_timers_started_spec.rb +19 -0
  150. data/spec/adhearsion/event/joined_spec.rb +53 -0
  151. data/spec/adhearsion/event/offer_spec.rb +106 -0
  152. data/spec/adhearsion/event/ringing_spec.rb +50 -0
  153. data/spec/adhearsion/event/started_speaking_spec.rb +37 -0
  154. data/spec/adhearsion/event/stopped_speaking_spec.rb +37 -0
  155. data/spec/adhearsion/event/unjoined_spec.rb +48 -0
  156. data/spec/adhearsion/event/untitled +0 -0
  157. data/spec/adhearsion/events_spec.rb +19 -45
  158. data/spec/adhearsion/initializer_spec.rb +12 -184
  159. data/spec/adhearsion/logging_spec.rb +5 -20
  160. data/spec/adhearsion/outbound_call_spec.rb +13 -13
  161. data/spec/adhearsion/plugin_spec.rb +3 -4
  162. data/spec/adhearsion/protocol_error_spec.rb +91 -0
  163. data/spec/adhearsion/rayo/client/component_registry_spec.rb +26 -0
  164. data/spec/adhearsion/rayo/client_spec.rb +134 -0
  165. data/spec/adhearsion/rayo/command/accept_spec.rb +63 -0
  166. data/spec/adhearsion/rayo/command/answer_spec.rb +73 -0
  167. data/spec/adhearsion/rayo/command/dial_spec.rb +156 -0
  168. data/spec/adhearsion/rayo/command/hangup_spec.rb +63 -0
  169. data/spec/adhearsion/rayo/command/join_spec.rb +158 -0
  170. data/spec/adhearsion/rayo/command/mute_spec.rb +32 -0
  171. data/spec/adhearsion/rayo/command/redirect_spec.rb +89 -0
  172. data/spec/adhearsion/rayo/command/reject_spec.rb +117 -0
  173. data/spec/adhearsion/rayo/command/unjoin_spec.rb +82 -0
  174. data/spec/adhearsion/rayo/command/unmute_spec.rb +32 -0
  175. data/spec/adhearsion/rayo/command_node_spec.rb +101 -0
  176. data/spec/adhearsion/rayo/component/asterisk/agi/command_spec.rb +111 -0
  177. data/spec/adhearsion/rayo/component/asterisk/ami/action_spec.rb +173 -0
  178. data/spec/adhearsion/rayo/component/component_node_spec.rb +110 -0
  179. data/spec/adhearsion/rayo/component/input_spec.rb +715 -0
  180. data/spec/adhearsion/rayo/component/output_spec.rb +1030 -0
  181. data/spec/adhearsion/rayo/component/prompt_spec.rb +171 -0
  182. data/spec/adhearsion/rayo/component/receive_fax_spec.rb +136 -0
  183. data/spec/adhearsion/rayo/component/record_spec.rb +497 -0
  184. data/spec/adhearsion/rayo/component/send_fax_spec.rb +144 -0
  185. data/spec/adhearsion/rayo/connection/asterisk_spec.rb +118 -0
  186. data/spec/adhearsion/rayo/connection/xmpp_spec.rb +449 -0
  187. data/spec/adhearsion/rayo/initializer_spec.rb +353 -0
  188. data/spec/adhearsion/rayo/ref_spec.rb +168 -0
  189. data/spec/adhearsion/rayo_spec.rb +7 -0
  190. data/spec/adhearsion/router/route_spec.rb +1 -1
  191. data/spec/adhearsion/statistics_spec.rb +2 -5
  192. data/spec/adhearsion/translator/asterisk/call_spec.rb +2047 -0
  193. data/spec/adhearsion/translator/asterisk/component/asterisk/agi_command_spec.rb +256 -0
  194. data/spec/adhearsion/translator/asterisk/component/asterisk/ami_action_spec.rb +151 -0
  195. data/spec/adhearsion/translator/asterisk/component/composed_prompt_spec.rb +257 -0
  196. data/spec/adhearsion/translator/asterisk/component/input_spec.rb +571 -0
  197. data/spec/adhearsion/translator/asterisk/component/mrcp_native_prompt_spec.rb +774 -0
  198. data/spec/adhearsion/translator/asterisk/component/mrcp_prompt_spec.rb +1244 -0
  199. data/spec/adhearsion/translator/asterisk/component/output_spec.rb +1850 -0
  200. data/spec/adhearsion/translator/asterisk/component/record_spec.rb +426 -0
  201. data/spec/adhearsion/translator/asterisk/component/stop_by_redirect_spec.rb +62 -0
  202. data/spec/adhearsion/translator/asterisk/component_spec.rb +83 -0
  203. data/spec/adhearsion/translator/asterisk_spec.rb +685 -0
  204. data/spec/adhearsion/uri_list_spec.rb +88 -0
  205. data/spec/adhearsion_spec.rb +89 -14
  206. data/spec/fixtures/locale/en.yml +13 -0
  207. data/spec/fixtures/locale/it.yml +13 -0
  208. data/spec/spec_helper.rb +18 -5
  209. data/spec/support/call_controller_test_helpers.rb +3 -2
  210. data/spec/support/initializer_stubs.rb +3 -1
  211. data/spec/support/punchblock_examples.rb +65 -0
  212. data/spec/support/punchblock_mocks.rb +12 -0
  213. metadata +412 -70
  214. data/features/cli_daemon.feature +0 -20
  215. data/features/cli_restart.feature +0 -52
  216. data/features/cli_stop.feature +0 -50
  217. data/lib/adhearsion/call_controller/menu_dsl.rb +0 -21
  218. data/lib/adhearsion/call_controller/menu_dsl/array_match_calculator.rb +0 -26
  219. data/lib/adhearsion/call_controller/menu_dsl/calculated_match.rb +0 -43
  220. data/lib/adhearsion/call_controller/menu_dsl/calculated_match_collection.rb +0 -45
  221. data/lib/adhearsion/call_controller/menu_dsl/fixnum_match_calculator.rb +0 -11
  222. data/lib/adhearsion/call_controller/menu_dsl/match_calculator.rb +0 -40
  223. data/lib/adhearsion/call_controller/menu_dsl/menu.rb +0 -207
  224. data/lib/adhearsion/call_controller/menu_dsl/menu_builder.rb +0 -92
  225. data/lib/adhearsion/call_controller/menu_dsl/range_match_calculator.rb +0 -60
  226. data/lib/adhearsion/call_controller/menu_dsl/string_match_calculator.rb +0 -25
  227. data/lib/adhearsion/call_controller/utility.rb +0 -77
  228. data/lib/adhearsion/foundation/custom_daemonizer.rb +0 -52
  229. data/lib/adhearsion/punchblock_plugin.rb +0 -63
  230. data/scripts/cloc-1.64.pl +0 -10483
  231. data/spec/adhearsion/call_controller/menu_dsl/array_match_calculator_spec.rb +0 -76
  232. data/spec/adhearsion/call_controller/menu_dsl/calculated_match_collection_spec.rb +0 -60
  233. data/spec/adhearsion/call_controller/menu_dsl/calculated_match_spec.rb +0 -61
  234. data/spec/adhearsion/call_controller/menu_dsl/fixnum_match_calculator_spec.rb +0 -39
  235. data/spec/adhearsion/call_controller/menu_dsl/match_calculator_spec.rb +0 -17
  236. data/spec/adhearsion/call_controller/menu_dsl/menu_builder_spec.rb +0 -165
  237. data/spec/adhearsion/call_controller/menu_dsl/menu_spec.rb +0 -420
  238. data/spec/adhearsion/call_controller/menu_dsl/range_match_calculator_spec.rb +0 -32
  239. data/spec/adhearsion/call_controller/menu_dsl/string_match_calculator_spec.rb +0 -40
  240. data/spec/adhearsion/call_controller/utility_spec.rb +0 -90
  241. data/spec/adhearsion/punchblock_plugin/initializer_spec.rb +0 -356
  242. data/spec/adhearsion/punchblock_plugin_spec.rb +0 -59
@@ -0,0 +1,7 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Adhearsion::Rayo do
6
+
7
+ end
@@ -79,7 +79,7 @@ module Adhearsion
79
79
  end
80
80
 
81
81
  let :offer do
82
- Punchblock::Event::Offer.new :to => to, :from => from
82
+ Adhearsion::Event::Offer.new :to => to, :from => from
83
83
  end
84
84
 
85
85
  let(:call) { Adhearsion::Call.new offer }
@@ -3,14 +3,11 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  describe Adhearsion::Statistics do
6
- before(:all) do
7
- Adhearsion::Statistics.setup_event_handlers
8
- end
9
-
10
6
  subject { Celluloid::Actor[:statistics] }
11
7
 
12
8
  before do
13
9
  Celluloid::Actor[:statistics] = described_class.new
10
+ Adhearsion::Statistics.setup_event_handlers
14
11
  allow(Adhearsion.active_calls).to receive_messages count: 0
15
12
  end
16
13
 
@@ -38,7 +35,7 @@ describe Adhearsion::Statistics do
38
35
  end
39
36
 
40
37
  it "should listen for call offer events and increment the offered call count" do
41
- Adhearsion::Events.trigger_immediately :punchblock, Punchblock::Event::Offer.new
38
+ Adhearsion::Events.trigger_immediately :rayo, Adhearsion::Event::Offer.new
42
39
  expect(subject.dump.call_counts).to eq({dialed: 0, offered: 1, routed: 0, rejected: 0, active: 0})
43
40
  end
44
41
 
@@ -0,0 +1,2047 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ module Adhearsion
6
+ module Translator
7
+ class Asterisk
8
+ describe Call do
9
+ let(:channel) { 'SIP/foo' }
10
+ let(:ami_client) { double('AMI Client').as_null_object }
11
+ let(:connection) { double('connection').as_null_object }
12
+ let(:translator) { Asterisk.new ami_client, connection }
13
+ let(:agi_env) do
14
+ {
15
+ :agi_request => 'async',
16
+ :agi_channel => 'SIP/1234-00000000',
17
+ :agi_language => 'en',
18
+ :agi_type => 'SIP',
19
+ :agi_uniqueid => '1320835995.0',
20
+ :agi_version => '1.8.4.1',
21
+ :agi_callerid => '5678',
22
+ :agi_calleridname => 'Jane Smith',
23
+ :agi_callingpres => '0',
24
+ :agi_callingani2 => '0',
25
+ :agi_callington => '0',
26
+ :agi_callingtns => '0',
27
+ :agi_dnid => 'unknown',
28
+ :agi_rdnis => 'unknown',
29
+ :agi_context => 'default',
30
+ :agi_extension => '1000',
31
+ :agi_priority => '1',
32
+ :agi_enhanced => '0.0',
33
+ :agi_accountcode => '',
34
+ :agi_threadid => '4366221312'
35
+ }
36
+ end
37
+
38
+ let :sip_headers do
39
+ {
40
+ 'X-agi_request' => 'async',
41
+ 'X-agi_channel' => 'SIP/1234-00000000',
42
+ 'X-agi_language' => 'en',
43
+ 'X-agi_type' => 'SIP',
44
+ 'X-agi_uniqueid' => '1320835995.0',
45
+ 'X-agi_version' => '1.8.4.1',
46
+ 'X-agi_callerid' => '5678',
47
+ 'X-agi_calleridname' => 'Jane Smith',
48
+ 'X-agi_callingpres' => '0',
49
+ 'X-agi_callingani2' => '0',
50
+ 'X-agi_callington' => '0',
51
+ 'X-agi_callingtns' => '0',
52
+ 'X-agi_dnid' => 'unknown',
53
+ 'X-agi_rdnis' => 'unknown',
54
+ 'X-agi_context' => 'default',
55
+ 'X-agi_extension' => '1000',
56
+ 'X-agi_priority' => '1',
57
+ 'X-agi_enhanced' => '0.0',
58
+ 'X-agi_accountcode' => '',
59
+ 'X-agi_threadid' => '4366221312'
60
+ }
61
+ end
62
+
63
+ subject { Call.new channel, translator, ami_client, connection, agi_env }
64
+
65
+ describe '#id' do
66
+ subject { super().id }
67
+ it { is_expected.to be_a String }
68
+ end
69
+
70
+ describe '#channel' do
71
+ subject { super().channel }
72
+ it { is_expected.to eq(channel) }
73
+ end
74
+
75
+ describe '#translator' do
76
+ subject { super().translator }
77
+ it { is_expected.to be translator }
78
+ end
79
+
80
+ describe '#agi_env' do
81
+ subject { super().agi_env }
82
+ it { is_expected.to eq(agi_env) }
83
+ end
84
+
85
+ before { allow(translator).to receive :handle_pb_event }
86
+
87
+ describe '#register_component' do
88
+ it 'should make the component accessible by ID' do
89
+ component_id = 'abc123'
90
+ component = double 'Translator::Asterisk::Component', :id => component_id
91
+ subject.register_component component
92
+ expect(subject.component_with_id(component_id)).to be component
93
+ end
94
+ end
95
+
96
+ describe "getting channel vars" do
97
+ it "should do a GetVar when we don't have a cached value" do
98
+ response = RubyAMI::Response.new 'Value' => 'thevalue'
99
+ expect(ami_client).to receive(:send_action).once.with('GetVar', 'Channel' => channel, 'Variable' => 'somevariable').and_return response
100
+ expect(subject.channel_var('somevariable')).to eq('thevalue')
101
+ end
102
+
103
+ context "when the value comes back from GetVar as '(null)'" do
104
+ it "should return nil" do
105
+ response = RubyAMI::Response.new 'Value' => '(null)'
106
+ expect(ami_client).to receive(:send_action).once.with('GetVar', 'Channel' => channel, 'Variable' => 'somevariable').and_return response
107
+ expect(subject.channel_var('somevariable')).to be_nil
108
+ end
109
+ end
110
+ end
111
+
112
+ describe '#send_offer' do
113
+ it 'sends an offer to the translator' do
114
+ expected_offer = Adhearsion::Event::Offer.new :target_call_id => subject.id,
115
+ :to => '1000',
116
+ :from => 'Jane Smith <SIP/5678>',
117
+ :headers => sip_headers
118
+ expect(translator).to receive(:handle_pb_event).with expected_offer
119
+ subject.send_offer
120
+ end
121
+
122
+ it 'should make the call identify as inbound' do
123
+ subject.send_offer
124
+ expect(subject.direction).to eq(:inbound)
125
+ expect(subject.inbound?).to be true
126
+ expect(subject.outbound?).to be false
127
+ end
128
+ end
129
+
130
+ describe '#send_progress' do
131
+ context "with a call that is already answered" do
132
+ it 'should not send the EXEC Progress command' do
133
+ expect(subject).to receive(:'answered?').and_return true
134
+ expect(subject).to receive(:execute_agi_command).with("EXEC Progress").never
135
+ subject.send_progress
136
+ end
137
+ end
138
+
139
+ context "with an unanswered call" do
140
+ before do
141
+ expect(subject).to receive(:'answered?').at_least(:once).and_return(false)
142
+ end
143
+
144
+ context "with a call that is outbound" do
145
+ let(:dial_command) { Adhearsion::Rayo::Command::Dial.new }
146
+
147
+ before do
148
+ dial_command.request!
149
+ subject.dial dial_command
150
+ end
151
+
152
+ it 'should not send the EXEC Progress command' do
153
+ expect(subject).to receive(:execute_agi_command).with("EXEC Progress").never
154
+ subject.send_progress
155
+ end
156
+ end
157
+
158
+ context "with a call that is inbound" do
159
+ before do
160
+ subject.send_offer
161
+ end
162
+
163
+ it 'should send the EXEC Progress command to a call that is inbound and not answered' do
164
+ expect(subject).to receive(:execute_agi_command).with("EXEC Progress").and_return code: 200, result: 0
165
+ subject.send_progress
166
+ end
167
+
168
+ it 'should send the EXEC Progress command only once if called twice' do
169
+ expect(subject).to receive(:execute_agi_command).with("EXEC Progress").once.and_return code: 200, result: 0
170
+ subject.send_progress
171
+ subject.send_progress
172
+ end
173
+ end
174
+ end
175
+ end
176
+
177
+ describe '#dial' do
178
+ let(:dial_command_options) { {} }
179
+
180
+ let(:to) { 'SIP/1234' }
181
+
182
+ let :dial_command do
183
+ Adhearsion::Rayo::Command::Dial.new({:to => to, :from => 'sip:foo@bar.com'}.merge(dial_command_options))
184
+ end
185
+
186
+ before { dial_command.request! }
187
+
188
+ it 'sends an Originate AMI action' do
189
+ expect(ami_client).to receive(:send_action).once.with('Originate',
190
+ 'Async' => true,
191
+ 'Context' => REDIRECT_CONTEXT,
192
+ 'Exten' => REDIRECT_EXTENSION,
193
+ 'Priority' => REDIRECT_PRIORITY,
194
+ 'Channel' => 'SIP/1234',
195
+ 'Callerid' => 'sip:foo@bar.com',
196
+ 'Variable' => "adhearsion_call_id=#{subject.id}"
197
+ ).and_return RubyAMI::Response.new
198
+
199
+ subject.dial dial_command
200
+ sleep 0.1
201
+ end
202
+
203
+ context 'with a name and channel in the to field' do
204
+ let(:to) { 'Jane Smith <SIP/5678>' }
205
+
206
+ it 'sends an Originate AMI action with only the channel' do
207
+ expect(ami_client).to receive(:send_action).once.with('Originate',
208
+ 'Async' => true,
209
+ 'Context' => REDIRECT_CONTEXT,
210
+ 'Exten' => REDIRECT_EXTENSION,
211
+ 'Priority' => REDIRECT_PRIORITY,
212
+ 'Channel' => 'SIP/5678',
213
+ 'Callerid' => 'sip:foo@bar.com',
214
+ 'Variable' => "adhearsion_call_id=#{subject.id}"
215
+ ).and_return RubyAMI::Response.new
216
+
217
+ subject.dial dial_command
218
+ sleep 0.1
219
+ end
220
+ end
221
+
222
+ context 'with a timeout specified' do
223
+ let :dial_command_options do
224
+ { :timeout => 10000 }
225
+ end
226
+
227
+ it 'includes the timeout in the Originate AMI action' do
228
+ expect(ami_client).to receive(:send_action).once.with('Originate',
229
+ 'Async' => true,
230
+ 'Context' => REDIRECT_CONTEXT,
231
+ 'Exten' => REDIRECT_EXTENSION,
232
+ 'Priority' => REDIRECT_PRIORITY,
233
+ 'Channel' => 'SIP/1234',
234
+ 'Callerid' => 'sip:foo@bar.com',
235
+ 'Variable' => "adhearsion_call_id=#{subject.id}",
236
+ 'Timeout' => 10000
237
+ ).and_return RubyAMI::Response.new
238
+
239
+ subject.dial dial_command
240
+ sleep 0.1
241
+ end
242
+ end
243
+
244
+ context 'with headers specified' do
245
+ let :dial_command_options do
246
+ { :headers => {'X-foo' => 'bar', 'X-doo' => 'dah'} }
247
+ end
248
+
249
+ it 'includes the headers in the Originate AMI action' do
250
+ expect(ami_client).to receive(:send_action).once.with('Originate',
251
+ 'Async' => true,
252
+ 'Context' => REDIRECT_CONTEXT,
253
+ 'Exten' => REDIRECT_EXTENSION,
254
+ 'Priority' => REDIRECT_PRIORITY,
255
+ 'Channel' => 'SIP/1234',
256
+ 'Callerid' => 'sip:foo@bar.com',
257
+ 'Variable' => "adhearsion_call_id=#{subject.id},SIPADDHEADER51=\"X-foo: bar\",SIPADDHEADER52=\"X-doo: dah\""
258
+ ).and_return RubyAMI::Response.new
259
+
260
+ subject.dial dial_command
261
+ sleep 0.1
262
+ end
263
+ end
264
+
265
+ it 'sends the call ID as a response to the Dial' do
266
+ subject.dial dial_command
267
+ dial_command.response
268
+ expect(dial_command.target_call_id).to eq(subject.id)
269
+ end
270
+
271
+ it 'should make the call identify as outbound' do
272
+ subject.dial dial_command
273
+ expect(subject.direction).to eq(:outbound)
274
+ expect(subject.outbound?).to be true
275
+ expect(subject.inbound?).to be false
276
+ end
277
+
278
+ it 'causes accepting the call to be a null operation' do
279
+ subject.dial dial_command
280
+ accept_command = Adhearsion::Rayo::Command::Accept.new
281
+ accept_command.request!
282
+ expect(subject).to receive(:execute_agi_command).never
283
+ subject.execute_command accept_command
284
+ expect(accept_command.response(0.5)).to be true
285
+ end
286
+ end
287
+
288
+ describe '#process_ami_event' do
289
+ context 'with a Hangup event' do
290
+ let :ami_event do
291
+ RubyAMI::Event.new 'Hangup',
292
+ 'Uniqueid' => "1320842458.8",
293
+ 'Calleridnum' => "5678",
294
+ 'Calleridname' => "Jane Smith",
295
+ 'Cause' => cause,
296
+ 'Cause-txt' => cause_txt,
297
+ 'Channel' => "SIP/1234-00000000"
298
+ end
299
+
300
+ let(:cause) { '16' }
301
+ let(:cause_txt) { 'Normal Clearing' }
302
+
303
+ it "de-registers the call from the translator" do
304
+ allow(translator).to receive :handle_pb_event
305
+ expect(translator).to receive(:deregister_call).once.with(subject.id, subject.channel)
306
+ subject.process_ami_event ami_event
307
+ end
308
+
309
+ it "should cause all components to send complete events before sending end event" do
310
+ allow(subject).to receive :send_progress
311
+ comp_command = Adhearsion::Rayo::Component::Input.new :grammar => {:value => RubySpeech::GRXML.draw(root: 'foo') { rule id: 'foo' }}, :mode => :dtmf
312
+ comp_command.request!
313
+ component = subject.execute_command comp_command
314
+ expect(comp_command.response(0.1)).to be_a Adhearsion::Rayo::Ref
315
+ expected_complete_event = Adhearsion::Event::Complete.new :target_call_id => subject.id, :component_id => component.id, source_uri: component.id
316
+ expected_complete_event.reason = Adhearsion::Event::Complete::Hangup.new
317
+ expected_end_event = Adhearsion::Event::End.new :reason => :hungup, platform_code: cause, :target_call_id => subject.id
318
+
319
+ expect(translator).to receive(:handle_pb_event).with(expected_complete_event).once.ordered
320
+ expect(translator).to receive(:handle_pb_event).with(expected_end_event).once.ordered
321
+ subject.process_ami_event ami_event
322
+ end
323
+
324
+ it "should not allow commands to be executed while components are shutting down" do
325
+ call_id = subject.id
326
+
327
+ allow(subject).to receive :send_progress
328
+ comp_command = Adhearsion::Rayo::Component::Input.new :grammar => {:value => RubySpeech::GRXML.draw(root: 'foo') { rule id: 'foo' }}, :mode => :dtmf
329
+ comp_command.request!
330
+ component = subject.execute_command comp_command
331
+ expect(comp_command.response(0.1)).to be_a Adhearsion::Rayo::Ref
332
+
333
+ subject.process_ami_event ami_event
334
+
335
+ comp_command = Adhearsion::Rayo::Component::Input.new :grammar => {:value => '<grammar root="foo"><rule id="foo"/></grammar>'}, :mode => :dtmf
336
+ comp_command.request!
337
+ subject.execute_command comp_command
338
+ expect(comp_command.response(0.1)).to eq(Adhearsion::ProtocolError.new.setup(:item_not_found, "Could not find a call with ID #{call_id}", call_id))
339
+ end
340
+
341
+ context "when the AMI event has a timestamp" do
342
+ let :ami_event do
343
+ RubyAMI::Event.new 'Hangup',
344
+ 'Uniqueid' => "1320842458.8",
345
+ 'Cause' => cause,
346
+ 'Cause-txt' => cause_txt,
347
+ 'Channel' => "SIP/1234-00000000",
348
+ 'Timestamp' => '1393368380.572575'
349
+ end
350
+
351
+ it "should use the AMI timestamp for the Rayo event" do
352
+ expected_end_event = Adhearsion::Event::End.new reason: :hungup,
353
+ platform_code: cause,
354
+ target_call_id: subject.id,
355
+ timestamp: DateTime.new(2014, 2, 25, 22, 46, 20)
356
+ expect(translator).to receive(:handle_pb_event).with expected_end_event
357
+
358
+ subject.process_ami_event ami_event
359
+ end
360
+ end
361
+
362
+ context "after processing a hangup command" do
363
+ let(:command) { Adhearsion::Rayo::Command::Hangup.new }
364
+
365
+ before do
366
+ command.request!
367
+ subject.execute_command command
368
+ end
369
+
370
+ it 'should send an end (hangup_command) event to the translator' do
371
+ expected_end_event = Adhearsion::Event::End.new :reason => :hangup_command,
372
+ platform_code: cause,
373
+ :target_call_id => subject.id
374
+ expect(translator).to receive(:handle_pb_event).with expected_end_event
375
+
376
+ subject.process_ami_event ami_event
377
+ end
378
+ end
379
+
380
+ context "with an undefined cause" do
381
+ let(:cause) { '0' }
382
+ let(:cause_txt) { 'Undefined' }
383
+
384
+ it 'should send an end (hungup) event to the translator' do
385
+ expected_end_event = Adhearsion::Event::End.new :reason => :hungup,
386
+ platform_code: cause,
387
+ :target_call_id => subject.id
388
+ expect(translator).to receive(:handle_pb_event).with expected_end_event
389
+ subject.process_ami_event ami_event
390
+ end
391
+ end
392
+
393
+ context "with a normal clearing cause" do
394
+ let(:cause) { '16' }
395
+ let(:cause_txt) { 'Normal Clearing' }
396
+
397
+ it 'should send an end (hungup) event to the translator' do
398
+ expected_end_event = Adhearsion::Event::End.new :reason => :hungup,
399
+ platform_code: cause,
400
+ :target_call_id => subject.id
401
+ expect(translator).to receive(:handle_pb_event).with expected_end_event
402
+ subject.process_ami_event ami_event
403
+ end
404
+ end
405
+
406
+ context "with a user busy cause" do
407
+ let(:cause) { '17' }
408
+ let(:cause_txt) { 'User Busy' }
409
+
410
+ it 'should send an end (busy) event to the translator' do
411
+ expected_end_event = Adhearsion::Event::End.new :reason => :busy,
412
+ platform_code: cause,
413
+ :target_call_id => subject.id
414
+ expect(translator).to receive(:handle_pb_event).with expected_end_event
415
+ subject.process_ami_event ami_event
416
+ end
417
+ end
418
+
419
+ {
420
+ 18 => 'No user response',
421
+ 102 => 'Recovery on timer expire'
422
+ }.each_pair do |cause, cause_txt|
423
+ context "with a #{cause_txt} cause" do
424
+ let(:cause) { cause.to_s }
425
+ let(:cause_txt) { cause_txt }
426
+
427
+ it 'should send an end (timeout) event to the translator' do
428
+ expected_end_event = Adhearsion::Event::End.new :reason => :timeout,
429
+ platform_code: cause,
430
+ :target_call_id => subject.id
431
+ expect(translator).to receive(:handle_pb_event).with expected_end_event
432
+ subject.process_ami_event ami_event
433
+ end
434
+ end
435
+ end
436
+
437
+ {
438
+ 19 => 'No Answer',
439
+ 21 => 'Call Rejected',
440
+ 22 => 'Number Changed'
441
+ }.each_pair do |cause, cause_txt|
442
+ context "with a #{cause_txt} cause" do
443
+ let(:cause) { cause.to_s }
444
+ let(:cause_txt) { cause_txt }
445
+
446
+ it 'should send an end (reject) event to the translator' do
447
+ expected_end_event = Adhearsion::Event::End.new :reason => :reject,
448
+ platform_code: cause,
449
+ :target_call_id => subject.id
450
+ expect(translator).to receive(:handle_pb_event).with expected_end_event
451
+ subject.process_ami_event ami_event
452
+ end
453
+ end
454
+ end
455
+
456
+ {
457
+ 1 => 'AST_CAUSE_UNALLOCATED',
458
+ 2 => 'NO_ROUTE_TRANSIT_NET',
459
+ 3 => 'NO_ROUTE_DESTINATION',
460
+ 6 => 'CHANNEL_UNACCEPTABLE',
461
+ 7 => 'CALL_AWARDED_DELIVERED',
462
+ 27 => 'DESTINATION_OUT_OF_ORDER',
463
+ 28 => 'INVALID_NUMBER_FORMAT',
464
+ 29 => 'FACILITY_REJECTED',
465
+ 30 => 'RESPONSE_TO_STATUS_ENQUIRY',
466
+ 31 => 'NORMAL_UNSPECIFIED',
467
+ 34 => 'NORMAL_CIRCUIT_CONGESTION',
468
+ 38 => 'NETWORK_OUT_OF_ORDER',
469
+ 41 => 'NORMAL_TEMPORARY_FAILURE',
470
+ 42 => 'SWITCH_CONGESTION',
471
+ 43 => 'ACCESS_INFO_DISCARDED',
472
+ 44 => 'REQUESTED_CHAN_UNAVAIL',
473
+ 45 => 'PRE_EMPTED',
474
+ 50 => 'FACILITY_NOT_SUBSCRIBED',
475
+ 52 => 'OUTGOING_CALL_BARRED',
476
+ 54 => 'INCOMING_CALL_BARRED',
477
+ 57 => 'BEARERCAPABILITY_NOTAUTH',
478
+ 58 => 'BEARERCAPABILITY_NOTAVAIL',
479
+ 65 => 'BEARERCAPABILITY_NOTIMPL',
480
+ 66 => 'CHAN_NOT_IMPLEMENTED',
481
+ 69 => 'FACILITY_NOT_IMPLEMENTED',
482
+ 81 => 'INVALID_CALL_REFERENCE',
483
+ 88 => 'INCOMPATIBLE_DESTINATION',
484
+ 95 => 'INVALID_MSG_UNSPECIFIED',
485
+ 96 => 'MANDATORY_IE_MISSING',
486
+ 97 => 'MESSAGE_TYPE_NONEXIST',
487
+ 98 => 'WRONG_MESSAGE',
488
+ 99 => 'IE_NONEXIST',
489
+ 100 => 'INVALID_IE_CONTENTS',
490
+ 101 => 'WRONG_CALL_STATE',
491
+ 103 => 'MANDATORY_IE_LENGTH_ERROR',
492
+ 111 => 'PROTOCOL_ERROR',
493
+ 127 => 'INTERWORKING'
494
+ }.each_pair do |cause, cause_txt|
495
+ context "with a #{cause_txt} cause" do
496
+ let(:cause) { cause.to_s }
497
+ let(:cause_txt) { cause_txt }
498
+
499
+ it 'should send an end (error) event to the translator' do
500
+ expected_end_event = Adhearsion::Event::End.new :reason => :error,
501
+ platform_code: cause,
502
+ :target_call_id => subject.id
503
+ expect(translator).to receive(:handle_pb_event).with expected_end_event
504
+ subject.process_ami_event ami_event
505
+ end
506
+ end
507
+ end
508
+ end
509
+
510
+ context 'with an event for a known AGI command component' do
511
+ let(:mock_component_node) { Adhearsion::Rayo::Component::Asterisk::AGI::Command.new name: 'EXEC ANSWER', params: [] }
512
+ let :component do
513
+ Component::Asterisk::AGICommand.new mock_component_node, subject
514
+ end
515
+ before do
516
+ subject.register_component component
517
+ end
518
+
519
+ context 'with Asterisk 11 AsyncAGI SubEvent' do
520
+ let(:ami_event) do
521
+ RubyAMI::Event.new "AsyncAGI",
522
+ "SubEvent" => "End",
523
+ "Channel" => "SIP/1234-00000000",
524
+ "CommandID" => component.id,
525
+ "Command" => "EXEC ANSWER",
526
+ "Result" => "200%20result=123%20(timeout)%0A"
527
+ end
528
+
529
+ it 'should send the event to the component' do
530
+ expect(component).to receive(:handle_ami_event).once.with ami_event
531
+ subject.process_ami_event ami_event
532
+ end
533
+
534
+ it 'should not send an answered event' do
535
+ expect(translator).to receive(:handle_pb_event).with(kind_of(Adhearsion::Event::Answered)).never
536
+ subject.process_ami_event ami_event
537
+ end
538
+ end
539
+
540
+ context 'with Asterisk 13 AsyncAGIEnd and CommandId with a lowercase d' do
541
+ let(:ami_event) do
542
+ RubyAMI::Event.new "AsyncAGIEnd",
543
+ "Channel" => "SIP/1234-00000000",
544
+ "CommandId" => component.id,
545
+ "Command" => "EXEC ANSWER",
546
+ "Result" => "200%20result=123%20(timeout)%0A"
547
+ end
548
+
549
+ it 'should send the event to the component' do
550
+ expect(component).to receive(:handle_ami_event).once.with ami_event
551
+ subject.process_ami_event ami_event
552
+ end
553
+
554
+ it 'should not send an answered event' do
555
+ expect(translator).to receive(:handle_pb_event).with(kind_of(Adhearsion::Event::Answered)).never
556
+ subject.process_ami_event ami_event
557
+ end
558
+ end
559
+ end
560
+
561
+ def should_send_answered_event
562
+ expected_answered = Adhearsion::Event::Answered.new
563
+ expected_answered.target_call_id = subject.id
564
+ expect(translator).to receive(:handle_pb_event).with expected_answered
565
+ subject.process_ami_event ami_event
566
+ end
567
+
568
+ def answered_should_be_true
569
+ subject.process_ami_event ami_event
570
+ expect(subject.answered?).to be_truthy
571
+ end
572
+
573
+ def should_only_send_one_answered_event
574
+ expected_answered = Adhearsion::Event::Answered.new
575
+ expected_answered.target_call_id = subject.id
576
+ expect(translator).to receive(:handle_pb_event).with(expected_answered).once
577
+ subject.process_ami_event ami_event
578
+ subject.process_ami_event ami_event
579
+ end
580
+
581
+ def should_use_ami_timestamp_for_rayo_event
582
+ expected_answered = Adhearsion::Event::Answered.new target_call_id: subject.id,
583
+ timestamp: DateTime.new(2014, 2, 25, 22, 46, 20)
584
+ expect(translator).to receive(:handle_pb_event).with expected_answered
585
+
586
+ subject.process_ami_event ami_event
587
+ end
588
+
589
+ context 'with an AsyncAGI Start event' do
590
+ let(:ami_event) do
591
+ RubyAMI::Event.new "AsyncAGI",
592
+ "SubEvent" => "Start",
593
+ "Channel" => "SIP/1234-00000000",
594
+ "Env" => "agi_request%3A%20async%0Aagi_channel%3A%20SIP%2Fuserb-00000006%0Aagi_language%3A%20en%0Aagi_type%3A%20SIP%0Aagi_uniqueid%3A%201390303636.6%0Aagi_version%3A%2011.7.0%0Aagi_callerid%3A%20userb%0Aagi_calleridname%3A%20User%20B%0Aagi_callingpres%3A%200%0Aagi_callingani2%3A%200%0Aagi_callington%3A%200%0Aagi_callingtns%3A%200%0Aagi_dnid%3A%20unknown%0Aagi_rdnis%3A%20unknown%0Aagi_context%3A%20adhearsion-redirect%0Aagi_extension%3A%201%0Aagi_priority%3A%201%0Aagi_enhanced%3A%200.0%0Aagi_accountcode%3A%20%0Aagi_threadid%3A%20139696536876800%0A%0A"
595
+ end
596
+
597
+ it 'should send an answered event' do
598
+ should_send_answered_event
599
+ end
600
+
601
+ it '#answered? should be true' do
602
+ answered_should_be_true
603
+ end
604
+
605
+ context "for a second time" do
606
+ it 'should only send one answered event' do
607
+ should_only_send_one_answered_event
608
+ end
609
+ end
610
+
611
+ context "when the AMI event has a timestamp" do
612
+ let :ami_event do
613
+ RubyAMI::Event.new "AsyncAGI",
614
+ "SubEvent" => "Start",
615
+ "Channel" => "SIP/1234-00000000",
616
+ "Env" => "agi_request%3A%20async%0Aagi_channel%3A%20SIP%2Fuserb-00000006%0Aagi_language%3A%20en%0Aagi_type%3A%20SIP%0Aagi_uniqueid%3A%201390303636.6%0Aagi_version%3A%2011.7.0%0Aagi_callerid%3A%20userb%0Aagi_calleridname%3A%20User%20B%0Aagi_callingpres%3A%200%0Aagi_callingani2%3A%200%0Aagi_callington%3A%200%0Aagi_callingtns%3A%200%0Aagi_dnid%3A%20unknown%0Aagi_rdnis%3A%20unknown%0Aagi_context%3A%20adhearsion-redirect%0Aagi_extension%3A%201%0Aagi_priority%3A%201%0Aagi_enhanced%3A%200.0%0Aagi_accountcode%3A%20%0Aagi_threadid%3A%20139696536876800%0A%0A",
617
+ 'Timestamp' => '1393368380.572575'
618
+ end
619
+
620
+ it "should use the AMI timestamp for the Rayo event" do
621
+ should_use_ami_timestamp_for_rayo_event
622
+ end
623
+ end
624
+ end
625
+
626
+ context 'with an Asterisk 13 AsyncAGIStart event' do
627
+ let(:ami_event) do
628
+ RubyAMI::Event.new "AsyncAGIStart",
629
+ "Channel" => "SIP/1234-00000000",
630
+ "Env" => "agi_request%3A%20async%0Aagi_channel%3A%20SIP%2Fuserb-00000006%0Aagi_language%3A%20en%0Aagi_type%3A%20SIP%0Aagi_uniqueid%3A%201390303636.6%0Aagi_version%3A%2011.7.0%0Aagi_callerid%3A%20userb%0Aagi_calleridname%3A%20User%20B%0Aagi_callingpres%3A%200%0Aagi_callingani2%3A%200%0Aagi_callington%3A%200%0Aagi_callingtns%3A%200%0Aagi_dnid%3A%20unknown%0Aagi_rdnis%3A%20unknown%0Aagi_context%3A%20adhearsion-redirect%0Aagi_extension%3A%201%0Aagi_priority%3A%201%0Aagi_enhanced%3A%200.0%0Aagi_accountcode%3A%20%0Aagi_threadid%3A%20139696536876800%0A%0A"
631
+ end
632
+
633
+ it 'should send an answered event' do
634
+ should_send_answered_event
635
+ end
636
+
637
+ it '#answered? should be true' do
638
+ answered_should_be_true
639
+ end
640
+
641
+ context "for a second time" do
642
+ it 'should only send one answered event' do
643
+ should_only_send_one_answered_event
644
+ end
645
+ end
646
+
647
+ context "when the AMI event has a timestamp" do
648
+ let :ami_event do
649
+ RubyAMI::Event.new "AsyncAGIStart",
650
+ "Channel" => "SIP/1234-00000000",
651
+ "Env" => "agi_request%3A%20async%0Aagi_channel%3A%20SIP%2Fuserb-00000006%0Aagi_language%3A%20en%0Aagi_type%3A%20SIP%0Aagi_uniqueid%3A%201390303636.6%0Aagi_version%3A%2011.7.0%0Aagi_callerid%3A%20userb%0Aagi_calleridname%3A%20User%20B%0Aagi_callingpres%3A%200%0Aagi_callingani2%3A%200%0Aagi_callington%3A%200%0Aagi_callingtns%3A%200%0Aagi_dnid%3A%20unknown%0Aagi_rdnis%3A%20unknown%0Aagi_context%3A%20adhearsion-redirect%0Aagi_extension%3A%201%0Aagi_priority%3A%201%0Aagi_enhanced%3A%200.0%0Aagi_accountcode%3A%20%0Aagi_threadid%3A%20139696536876800%0A%0A",
652
+ 'Timestamp' => '1393368380.572575'
653
+ end
654
+
655
+ it "should use the AMI timestamp for the Rayo event" do
656
+ should_use_ami_timestamp_for_rayo_event
657
+ end
658
+ end
659
+ end
660
+
661
+ context 'with a Newstate event' do
662
+ let :ami_event do
663
+ RubyAMI::Event.new 'Newstate',
664
+ 'Privilege' => 'call,all',
665
+ 'Channel' => 'SIP/1234-00000000',
666
+ 'ChannelState' => channel_state,
667
+ 'ChannelStateDesc' => channel_state_desc,
668
+ 'CallerIDNum' => '',
669
+ 'CallerIDName' => '',
670
+ 'ConnectedLineNum' => '',
671
+ 'ConnectedLineName' => '',
672
+ 'Uniqueid' => '1326194671.0'
673
+ end
674
+
675
+ context 'ringing' do
676
+ let(:channel_state) { '5' }
677
+ let(:channel_state_desc) { 'Ringing' }
678
+
679
+ it 'should send a ringing event' do
680
+ expected_ringing = Adhearsion::Event::Ringing.new
681
+ expected_ringing.target_call_id = subject.id
682
+ expect(translator).to receive(:handle_pb_event).with expected_ringing
683
+ subject.process_ami_event ami_event
684
+ end
685
+
686
+ it '#answered? should return false' do
687
+ subject.process_ami_event ami_event
688
+ expect(subject.answered?).to be_falsey
689
+ end
690
+
691
+ context "when the AMI event has a timestamp" do
692
+ let :ami_event do
693
+ RubyAMI::Event.new 'Newstate',
694
+ 'Channel' => 'SIP/1234-00000000',
695
+ 'ChannelState' => channel_state,
696
+ 'ChannelStateDesc' => channel_state_desc,
697
+ 'Uniqueid' => '1326194671.0',
698
+ 'Timestamp' => '1393368380.572575'
699
+ end
700
+
701
+ it "should use the AMI timestamp for the Rayo event" do
702
+ expected_ringing = Adhearsion::Event::Ringing.new target_call_id: subject.id,
703
+ timestamp: DateTime.new(2014, 2, 25, 22, 46, 20)
704
+ expect(translator).to receive(:handle_pb_event).with expected_ringing
705
+
706
+ subject.process_ami_event ami_event
707
+ end
708
+ end
709
+ end
710
+ end
711
+
712
+ context 'with an OriginateResponse event' do
713
+ let :ami_event do
714
+ RubyAMI::Event.new 'OriginateResponse',
715
+ 'Privilege' => 'call,all',
716
+ 'ActionID' => '9d0c1aa4-5e3b-4cae-8aef-76a6119e2909',
717
+ 'Response' => response,
718
+ 'Channel' => 'SIP/15557654321',
719
+ 'Context' => '',
720
+ 'Exten' => '',
721
+ 'Reason' => '0',
722
+ 'Uniqueid' => uniqueid,
723
+ 'CallerIDNum' => 'sip:5551234567',
724
+ 'CallerIDName' => 'Bryan 100'
725
+ end
726
+
727
+ context 'sucessful' do
728
+ let(:response) { 'Success' }
729
+ let(:uniqueid) { '<null>' }
730
+
731
+ it 'should not send an end event' do
732
+ expect(translator).to receive(:handle_pb_event).once.with an_instance_of(Adhearsion::Event::Asterisk::AMI)
733
+ subject.process_ami_event ami_event
734
+ end
735
+ end
736
+
737
+ context 'failed after being connected' do
738
+ let(:response) { 'Failure' }
739
+ let(:uniqueid) { '1235' }
740
+
741
+ it 'should not send an end event' do
742
+ expect(translator).to receive(:handle_pb_event).once.with an_instance_of(Adhearsion::Event::Asterisk::AMI)
743
+ subject.process_ami_event ami_event
744
+ end
745
+ end
746
+
747
+ context 'failed without ever having connected' do
748
+ let(:response) { 'Failure' }
749
+ let(:uniqueid) { '<null>' }
750
+
751
+ it 'should send an error end event' do
752
+ expected_end_event = Adhearsion::Event::End.new :reason => :error,
753
+ :target_call_id => subject.id
754
+ expect(translator).to receive(:handle_pb_event).with expected_end_event
755
+ subject.process_ami_event ami_event
756
+ end
757
+
758
+ context "when the AMI event has a timestamp" do
759
+ let :ami_event do
760
+ RubyAMI::Event.new 'OriginateResponse',
761
+ 'Privilege' => 'call,all',
762
+ 'ActionID' => '9d0c1aa4-5e3b-4cae-8aef-76a6119e2909',
763
+ 'Response' => response,
764
+ 'Channel' => 'SIP/15557654321',
765
+ 'Context' => '',
766
+ 'Exten' => '',
767
+ 'Reason' => '0',
768
+ 'Uniqueid' => uniqueid,
769
+ 'CallerIDNum' => 'sip:5551234567',
770
+ 'CallerIDName' => 'Bryan 100',
771
+ 'Timestamp' => '1393368380.572575'
772
+ end
773
+
774
+ it "should use the AMI timestamp for the Rayo event" do
775
+ expected_end_event = Adhearsion::Event::End.new reason: :error,
776
+ target_call_id: subject.id,
777
+ timestamp: DateTime.new(2014, 2, 25, 22, 46, 20)
778
+ expect(translator).to receive(:handle_pb_event).with expected_end_event
779
+
780
+ subject.process_ami_event ami_event
781
+ end
782
+ end
783
+ end
784
+ end
785
+
786
+ context 'with a handler registered for a matching event' do
787
+ let :ami_event do
788
+ RubyAMI::Event.new 'DTMF',
789
+ 'Digit' => '4',
790
+ 'Start' => 'Yes',
791
+ 'End' => 'No',
792
+ 'Uniqueid' => "1320842458.8",
793
+ 'Channel' => "SIP/1234-00000000"
794
+ end
795
+
796
+ let(:response) { double 'Response' }
797
+
798
+ it 'should execute the handler' do
799
+ expect(response).to receive(:call).once.with ami_event
800
+ subject.register_handler :ami, :name => 'DTMF' do |event|
801
+ response.call event
802
+ end
803
+ subject.process_ami_event ami_event
804
+ end
805
+ end
806
+
807
+ context 'with a BridgeEnter event' do
808
+ let(:bridge_uniqueid) { "1234-5678" }
809
+ let(:call_channel) { "SIP/foo" }
810
+ let :ami_event do
811
+ RubyAMI::Event.new 'BridgeEnter',
812
+ 'Privilege' => "call,all",
813
+ 'BridgeUniqueid' => bridge_uniqueid,
814
+ 'Channel' => call_channel
815
+ end
816
+
817
+ context 'when the event is received the first time' do
818
+ it 'sets an entry in translator.bridges' do
819
+ subject.process_ami_event ami_event
820
+ expect(translator.bridges[bridge_uniqueid]).to eq call_channel
821
+ end
822
+ end
823
+
824
+ context 'when the event is received a second time for the same BridgeUniqueid' do
825
+ let(:other_channel) { 'SIP/5678-00000000' }
826
+ let :other_call do
827
+ Call.new other_channel, translator, ami_client, connection
828
+ end
829
+ let(:other_call_id) { other_call.id }
830
+
831
+ let :command do
832
+ Adhearsion::Rayo::Command::Join.new call_uri: other_call_id
833
+ end
834
+
835
+ let :ami_event do
836
+ RubyAMI::Event.new 'BridgeEnter',
837
+ 'Privilege' => "call,all",
838
+ 'BridgeUniqueid' => bridge_uniqueid,
839
+ 'Channel' => call_channel
840
+ end
841
+
842
+ let :expected_joined do
843
+ Adhearsion::Event::Joined.new target_call_id: subject.id,
844
+ call_uri: other_call_id
845
+ end
846
+
847
+ let :expected_joined_other do
848
+ Adhearsion::Event::Joined.new target_call_id: other_call_id,
849
+ call_uri: subject.id
850
+ end
851
+
852
+ before do
853
+ translator.bridges[bridge_uniqueid] = other_channel
854
+ translator.register_call other_call
855
+
856
+ other_call.pending_joins[channel] = command
857
+ command.request!
858
+ expect(subject).to receive(:execute_agi_command).and_return code: 200
859
+ subject.execute_command command
860
+ end
861
+
862
+ it 'sends the correct Joined events' do
863
+ expect(translator).to receive(:handle_pb_event).with expected_joined
864
+ expect(translator).to receive(:handle_pb_event).with expected_joined_other
865
+ subject.process_ami_event ami_event
866
+ expect(command.response(0.5)).to eq(true)
867
+ end
868
+ end
869
+ end
870
+
871
+ context 'with a BridgeLeave event' do
872
+ let(:bridge_uniqueid) { "1234-5678" }
873
+ let(:call_channel) { "SIP/foo-1234" }
874
+ let :ami_event do
875
+ RubyAMI::Event.new 'BridgeLeave',
876
+ 'Privilege' => "call,all",
877
+ 'BridgeUniqueid' => bridge_uniqueid,
878
+ 'Channel' => call_channel
879
+ end
880
+
881
+ context 'when the event is received the first time' do
882
+ it 'sets an entry in translator.bridges' do
883
+ subject.process_ami_event ami_event
884
+ expect(translator.bridges[bridge_uniqueid + '_leave']).to eq call_channel
885
+ end
886
+ end
887
+
888
+ context 'when the event is received a second time for the same BridgeUniqueid' do
889
+ let(:other_channel) { 'SIP/5678-00000000' }
890
+ let :other_call do
891
+ Call.new other_channel, translator, ami_client, connection
892
+ end
893
+ let(:other_call_id) { other_call.id }
894
+
895
+ let :ami_event do
896
+ RubyAMI::Event.new 'BridgeLeave',
897
+ 'Privilege' => "call,all",
898
+ 'BridgeUniqueid' => bridge_uniqueid,
899
+ 'Channel' => call_channel
900
+ end
901
+
902
+ let :expected_unjoined do
903
+ Adhearsion::Event::Unjoined.new target_call_id: subject.id,
904
+ call_uri: other_call_id
905
+ end
906
+
907
+ let :expected_unjoined_other do
908
+ Adhearsion::Event::Unjoined.new target_call_id: other_call_id,
909
+ call_uri: subject.id
910
+ end
911
+
912
+ before do
913
+ translator.bridges[bridge_uniqueid + '_leave'] = other_channel
914
+ translator.register_call other_call
915
+ end
916
+
917
+ it 'sends the correct Unjoined events' do
918
+ expect(translator).to receive(:handle_pb_event).with expected_unjoined
919
+ expect(translator).to receive(:handle_pb_event).with expected_unjoined_other
920
+ subject.process_ami_event ami_event
921
+ end
922
+ end
923
+ end
924
+
925
+ context 'with a BridgeExec event' do
926
+ let :ami_event do
927
+ RubyAMI::Event.new 'BridgeExec',
928
+ 'Privilege' => "call,all",
929
+ 'Response' => "Success",
930
+ 'Channel1' => "SIP/foo",
931
+ 'Channel2' => other_channel
932
+ end
933
+
934
+ let(:other_channel) { 'SIP/5678-00000000' }
935
+
936
+ context "when a join has been executed against another call" do
937
+ let :other_call do
938
+ Call.new other_channel, translator, ami_client, connection
939
+ end
940
+
941
+ let(:other_call_id) { other_call.id }
942
+ let :command do
943
+ Adhearsion::Rayo::Command::Join.new call_uri: other_call_id
944
+ end
945
+
946
+ before do
947
+ translator.register_call other_call
948
+ command.request!
949
+ expect(subject).to receive(:execute_agi_command).and_return code: 200
950
+ subject.execute_command command
951
+ end
952
+
953
+ it 'retrieves and sets success on the correct Join' do
954
+ subject.process_ami_event ami_event
955
+ expect(command.response(0.5)).to eq(true)
956
+ end
957
+
958
+ context "with the channel names reversed" do
959
+ let :ami_event do
960
+ RubyAMI::Event.new 'BridgeExec',
961
+ 'Privilege' => "call,all",
962
+ 'Response' => "Success",
963
+ 'Channel1' => other_channel,
964
+ 'Channel2' => "SIP/foo"
965
+ end
966
+
967
+ it 'retrieves and sets success on the correct Join' do
968
+ subject.process_ami_event ami_event
969
+ expect(command.response(0.5)).to eq(true)
970
+ end
971
+ end
972
+ end
973
+
974
+ context "with no matching join command" do
975
+ it "should do nothing" do
976
+ expect { subject.process_ami_event ami_event }.not_to raise_error
977
+ end
978
+ end
979
+ end
980
+
981
+ context 'with a Bridge event' do
982
+ let(:other_channel) { 'SIP/5678-00000000' }
983
+ let :other_call do
984
+ Call.new other_channel, translator, ami_client, connection
985
+ end
986
+ let(:other_call_id) { other_call.id }
987
+
988
+ let :ami_event do
989
+ RubyAMI::Event.new 'Bridge',
990
+ 'Privilege' => "call,all",
991
+ 'Bridgestate' => state,
992
+ 'Bridgetype' => "core",
993
+ 'Channel1' => channel,
994
+ 'Channel2' => other_channel,
995
+ 'Uniqueid1' => "1319717537.11",
996
+ 'Uniqueid2' => "1319717537.10",
997
+ 'CallerID1' => "1234",
998
+ 'CallerID2' => "5678"
999
+ end
1000
+
1001
+ let :switched_ami_event do
1002
+ RubyAMI::Event.new 'Bridge',
1003
+ 'Privilege' => "call,all",
1004
+ 'Bridgestate' => state,
1005
+ 'Bridgetype' => "core",
1006
+ 'Channel1' => other_channel,
1007
+ 'Channel2' => channel,
1008
+ 'Uniqueid1' => "1319717537.11",
1009
+ 'Uniqueid2' => "1319717537.10",
1010
+ 'CallerID1' => "1234",
1011
+ 'CallerID2' => "5678"
1012
+ end
1013
+
1014
+ before do
1015
+ translator.register_call other_call
1016
+ expect(translator).to receive(:call_for_channel).with(other_channel).and_return(other_call)
1017
+ end
1018
+
1019
+ context "of state 'Link'" do
1020
+ let(:state) { 'Link' }
1021
+
1022
+ let :expected_joined do
1023
+ Adhearsion::Event::Joined.new target_call_id: subject.id,
1024
+ call_uri: other_call_id
1025
+ end
1026
+
1027
+ it 'sends the Joined event when the call is the first channel' do
1028
+ expect(translator).to receive(:handle_pb_event).with expected_joined
1029
+ subject.process_ami_event ami_event
1030
+ end
1031
+
1032
+ it 'sends the Joined event when the call is the second channel' do
1033
+ expect(translator).to receive(:handle_pb_event).with expected_joined
1034
+ subject.process_ami_event switched_ami_event
1035
+ end
1036
+
1037
+ context "when the AMI event has a timestamp" do
1038
+ let :ami_event do
1039
+ RubyAMI::Event.new 'Bridge',
1040
+ 'Privilege' => "call,all",
1041
+ 'Bridgestate' => state,
1042
+ 'Bridgetype' => "core",
1043
+ 'Channel1' => channel,
1044
+ 'Channel2' => other_channel,
1045
+ 'Uniqueid1' => "1319717537.11",
1046
+ 'Uniqueid2' => "1319717537.10",
1047
+ 'CallerID1' => "1234",
1048
+ 'CallerID2' => "5678",
1049
+ 'Timestamp' => '1393368380.572575'
1050
+ end
1051
+
1052
+ let :switched_ami_event do
1053
+ RubyAMI::Event.new 'Bridge',
1054
+ 'Privilege' => "call,all",
1055
+ 'Bridgestate' => state,
1056
+ 'Bridgetype' => "core",
1057
+ 'Channel1' => other_channel,
1058
+ 'Channel2' => channel,
1059
+ 'Uniqueid1' => "1319717537.11",
1060
+ 'Uniqueid2' => "1319717537.10",
1061
+ 'CallerID1' => "1234",
1062
+ 'CallerID2' => "5678",
1063
+ 'Timestamp' => '1393368380.572575'
1064
+ end
1065
+
1066
+ before { expected_joined.timestamp = DateTime.new(2014, 2, 25, 22, 46, 20) }
1067
+
1068
+ context "when the call is the first channel" do
1069
+ it "should use the AMI timestamp for the Rayo event" do
1070
+ expect(translator).to receive(:handle_pb_event).with expected_joined
1071
+
1072
+ subject.process_ami_event ami_event
1073
+ end
1074
+ end
1075
+
1076
+ context "when the call is the second channel" do
1077
+ it "should use the AMI timestamp for the Rayo event" do
1078
+ expect(translator).to receive(:handle_pb_event).with expected_joined
1079
+
1080
+ subject.process_ami_event switched_ami_event
1081
+ end
1082
+ end
1083
+ end
1084
+ end
1085
+
1086
+ context "of state 'Unlink'" do
1087
+ let(:state) { 'Unlink' }
1088
+
1089
+ let :expected_unjoined do
1090
+ Adhearsion::Event::Unjoined.new target_call_id: subject.id,
1091
+ call_uri: other_call_id
1092
+ end
1093
+
1094
+ it 'sends the Unjoined event when the call is the first channel' do
1095
+ expect(translator).to receive(:handle_pb_event).with expected_unjoined
1096
+ subject.process_ami_event ami_event
1097
+ end
1098
+
1099
+ it 'sends the Unjoined event when the call is the second channel' do
1100
+ expect(translator).to receive(:handle_pb_event).with expected_unjoined
1101
+ subject.process_ami_event switched_ami_event
1102
+ end
1103
+
1104
+ context "when the AMI event has a timestamp" do
1105
+ let :ami_event do
1106
+ RubyAMI::Event.new 'Bridge',
1107
+ 'Privilege' => "call,all",
1108
+ 'Bridgestate' => state,
1109
+ 'Bridgetype' => "core",
1110
+ 'Channel1' => channel,
1111
+ 'Channel2' => other_channel,
1112
+ 'Uniqueid1' => "1319717537.11",
1113
+ 'Uniqueid2' => "1319717537.10",
1114
+ 'CallerID1' => "1234",
1115
+ 'CallerID2' => "5678",
1116
+ 'Timestamp' => '1393368380.572575'
1117
+ end
1118
+
1119
+ let :switched_ami_event do
1120
+ RubyAMI::Event.new 'Bridge',
1121
+ 'Privilege' => "call,all",
1122
+ 'Bridgestate' => state,
1123
+ 'Bridgetype' => "core",
1124
+ 'Channel1' => other_channel,
1125
+ 'Channel2' => channel,
1126
+ 'Uniqueid1' => "1319717537.11",
1127
+ 'Uniqueid2' => "1319717537.10",
1128
+ 'CallerID1' => "1234",
1129
+ 'CallerID2' => "5678",
1130
+ 'Timestamp' => '1393368380.572575'
1131
+ end
1132
+
1133
+ before { expected_unjoined.timestamp = DateTime.new(2014, 2, 25, 22, 46, 20) }
1134
+
1135
+ context "when the call is the first channel" do
1136
+ it "should use the AMI timestamp for the Rayo event" do
1137
+ expect(translator).to receive(:handle_pb_event).with expected_unjoined
1138
+
1139
+ subject.process_ami_event ami_event
1140
+ end
1141
+ end
1142
+
1143
+ context "when the call is the second channel" do
1144
+ it "should use the AMI timestamp for the Rayo event" do
1145
+ expect(translator).to receive(:handle_pb_event).with expected_unjoined
1146
+
1147
+ subject.process_ami_event switched_ami_event
1148
+ end
1149
+ end
1150
+ end
1151
+ end
1152
+ end
1153
+
1154
+ context 'with an Unlink event' do
1155
+ let(:other_channel) { 'SIP/5678-00000000' }
1156
+ let(:other_call_id) { 'def567' }
1157
+ let :other_call do
1158
+ Call.new other_channel, translator, ami_client, connection
1159
+ end
1160
+
1161
+ let :ami_event do
1162
+ RubyAMI::Event.new 'Unlink',
1163
+ 'Privilege' => "call,all",
1164
+ 'Channel1' => channel,
1165
+ 'Channel2' => other_channel,
1166
+ 'Uniqueid1' => "1319717537.11",
1167
+ 'Uniqueid2' => "1319717537.10",
1168
+ 'CallerID1' => "1234",
1169
+ 'CallerID2' => "5678"
1170
+ end
1171
+
1172
+ let :switched_ami_event do
1173
+ RubyAMI::Event.new 'Unlink',
1174
+ 'Privilege' => "call,all",
1175
+ 'Channel1' => other_channel,
1176
+ 'Channel2' => channel,
1177
+ 'Uniqueid1' => "1319717537.11",
1178
+ 'Uniqueid2' => "1319717537.10",
1179
+ 'CallerID1' => "1234",
1180
+ 'CallerID2' => "5678"
1181
+ end
1182
+
1183
+ before do
1184
+ translator.register_call other_call
1185
+ expect(translator).to receive(:call_for_channel).with(other_channel).and_return(other_call)
1186
+ expect(other_call).to receive(:id).and_return other_call_id
1187
+ end
1188
+
1189
+ let :expected_unjoined do
1190
+ Adhearsion::Event::Unjoined.new target_call_id: subject.id,
1191
+ call_uri: other_call_id
1192
+ end
1193
+
1194
+ it 'sends the Unjoined event when the call is the first channel' do
1195
+ expect(translator).to receive(:handle_pb_event).with expected_unjoined
1196
+ subject.process_ami_event ami_event
1197
+ end
1198
+
1199
+ it 'sends the Unjoined event when the call is the second channel' do
1200
+ expect(translator).to receive(:handle_pb_event).with expected_unjoined
1201
+ subject.process_ami_event switched_ami_event
1202
+ end
1203
+
1204
+ context "when the AMI event has a timestamp" do
1205
+ let :ami_event do
1206
+ RubyAMI::Event.new 'Unlink',
1207
+ 'Privilege' => "call,all",
1208
+ 'Channel1' => channel,
1209
+ 'Channel2' => other_channel,
1210
+ 'Uniqueid1' => "1319717537.11",
1211
+ 'Uniqueid2' => "1319717537.10",
1212
+ 'CallerID1' => "1234",
1213
+ 'CallerID2' => "5678",
1214
+ 'Timestamp' => '1393368380.572575'
1215
+ end
1216
+
1217
+ let :switched_ami_event do
1218
+ RubyAMI::Event.new 'Unlink',
1219
+ 'Privilege' => "call,all",
1220
+ 'Channel1' => other_channel,
1221
+ 'Channel2' => channel,
1222
+ 'Uniqueid1' => "1319717537.11",
1223
+ 'Uniqueid2' => "1319717537.10",
1224
+ 'CallerID1' => "1234",
1225
+ 'CallerID2' => "5678",
1226
+ 'Timestamp' => '1393368380.572575'
1227
+ end
1228
+
1229
+ before { expected_unjoined.timestamp = DateTime.new(2014, 2, 25, 22, 46, 20) }
1230
+
1231
+ context "when the call is the first channel" do
1232
+ it "should use the AMI timestamp for the Rayo event" do
1233
+ expect(translator).to receive(:handle_pb_event).with expected_unjoined
1234
+
1235
+ subject.process_ami_event ami_event
1236
+ end
1237
+ end
1238
+
1239
+ context "when the call is the second channel" do
1240
+ it "should use the AMI timestamp for the Rayo event" do
1241
+ expect(translator).to receive(:handle_pb_event).with expected_unjoined
1242
+
1243
+ subject.process_ami_event switched_ami_event
1244
+ end
1245
+ end
1246
+ end
1247
+ end
1248
+
1249
+ context 'with a VarSet event' do
1250
+ let :ami_event do
1251
+ RubyAMI::Event.new 'VarSet',
1252
+ "Privilege" => "dialplan,all",
1253
+ "Channel" => "SIP/1234-00000000",
1254
+ "Variable" => "foobar",
1255
+ "Value" => 'abc123',
1256
+ "Uniqueid" => "1326210224.0"
1257
+ end
1258
+
1259
+ it 'makes the variable accessible on the call' do
1260
+ subject.process_ami_event ami_event
1261
+ expect(subject.channel_var('foobar')).to eq('abc123')
1262
+ end
1263
+ end
1264
+
1265
+ let :ami_event do
1266
+ RubyAMI::Event.new 'Foo',
1267
+ 'Uniqueid' => "1320842458.8",
1268
+ 'Calleridnum' => "5678",
1269
+ 'Calleridname' => "Jane Smith",
1270
+ 'Cause' => "0",
1271
+ 'Cause-txt' => "Unknown",
1272
+ 'Channel' => channel
1273
+ end
1274
+
1275
+ let :expected_pb_event do
1276
+ Adhearsion::Event::Asterisk::AMI.new name: 'Foo',
1277
+ headers: { 'Channel' => channel,
1278
+ 'Uniqueid' => "1320842458.8",
1279
+ 'Calleridnum' => "5678",
1280
+ 'Calleridname' => "Jane Smith",
1281
+ 'Cause' => "0",
1282
+ 'Cause-txt' => "Unknown"},
1283
+ target_call_id: subject.id
1284
+ end
1285
+
1286
+ it 'sends the AMI event to the connection as a PB event' do
1287
+ expect(translator).to receive(:handle_pb_event).with expected_pb_event
1288
+ subject.process_ami_event ami_event
1289
+ end
1290
+
1291
+ context "when the event doesn't pass the filter" do
1292
+ before { Asterisk.event_filter = ->(event) { false } }
1293
+ after { Asterisk.event_filter = nil }
1294
+
1295
+ it 'does not send the AMI event to the connection as a PB event' do
1296
+ expect(translator).to receive(:handle_pb_event).never
1297
+ subject.process_ami_event ami_event
1298
+ end
1299
+ end
1300
+ end
1301
+
1302
+ describe '#send_message' do
1303
+ let(:body) { 'Hello world' }
1304
+
1305
+ it "should invoke SendText" do
1306
+ expect(subject).to receive(:execute_agi_command).with('EXEC SendText', body).and_return code: 200
1307
+ subject.send_message body
1308
+ end
1309
+
1310
+ context "when an AMI error is received" do
1311
+ it "is silently ignored" do
1312
+ expect(subject).to receive(:execute_agi_command).with('EXEC SendText', body).and_raise RubyAMI::Error.new.tap { |e| e.message = 'Call not found' }
1313
+ subject.send_message body
1314
+ end
1315
+ end
1316
+ end
1317
+
1318
+ describe '#execute_command' do
1319
+ before do
1320
+ command.request!
1321
+ end
1322
+
1323
+ context 'with an accept command' do
1324
+ let(:command) { Adhearsion::Rayo::Command::Accept.new }
1325
+
1326
+ it "should send an EXEC RINGING AGI command and set the command's response" do
1327
+ expect(subject).to receive(:execute_agi_command).with('EXEC RINGING').and_return code: 200
1328
+ subject.execute_command command
1329
+ expect(command.response(0.5)).to be true
1330
+ end
1331
+
1332
+ context "when the AMI commannd raises an error" do
1333
+ let(:message) { 'Some error' }
1334
+ let(:error) { RubyAMI::Error.new.tap { |e| e.message = message } }
1335
+
1336
+ before { expect(subject).to receive(:execute_agi_command).and_raise error }
1337
+
1338
+ it "should return an error with the message" do
1339
+ subject.execute_command command
1340
+ expect(command.response(0.5)).to eq(Adhearsion::ProtocolError.new.setup('error', message, subject.id))
1341
+ end
1342
+
1343
+ context "because the channel is gone" do
1344
+ let(:error) { ChannelGoneError }
1345
+
1346
+ it "should return an :item_not_found event for the call" do
1347
+ subject.execute_command command
1348
+ expect(command.response(0.5)).to eq(Adhearsion::ProtocolError.new.setup(:item_not_found, "Could not find a call with ID #{subject.id}", subject.id))
1349
+ end
1350
+ end
1351
+ end
1352
+ end
1353
+
1354
+ context 'with a redirect command' do
1355
+ let(:command) { Adhearsion::Rayo::Command::Redirect.new to: 'other@place.com' }
1356
+
1357
+ let(:transferstatus) { 'SUCCESS' }
1358
+ let :status_event do
1359
+ RubyAMI::Event.new 'VarSet',
1360
+ "Privilege" => "dialplan,all",
1361
+ "Channel" => "SIP/1234-00000000",
1362
+ "Variable" => "TRANSFERSTATUS",
1363
+ "Value" => transferstatus,
1364
+ "Uniqueid" => "1326210224.0"
1365
+ end
1366
+
1367
+ before do
1368
+ subject.process_ami_event status_event
1369
+ end
1370
+
1371
+ it "should send an EXEC Transfer AGI command" do
1372
+ expect(subject).to receive(:execute_agi_command).with('EXEC Transfer', 'other@place.com').and_return code: 200
1373
+ subject.execute_command command
1374
+ expect(command.response(0.5)).to be true
1375
+ end
1376
+
1377
+ context "when TRANSFERSTATUS is 'FAILURE'" do
1378
+ let(:transferstatus) { 'FAILURE' }
1379
+
1380
+ it "should return an error" do
1381
+ expect(subject).to receive(:execute_agi_command).with('EXEC Transfer', 'other@place.com').and_return code: 200
1382
+ subject.execute_command command
1383
+ expect(command.response(0.5)).to eq(Adhearsion::ProtocolError.new.setup('error', 'TRANSFERSTATUS was FAILURE', subject.id))
1384
+ end
1385
+ end
1386
+
1387
+ context "when TRANSFERSTATUS is 'UNSUPPORTED'" do
1388
+ let(:transferstatus) { 'UNSUPPORTED' }
1389
+
1390
+ it "should return an error" do
1391
+ expect(subject).to receive(:execute_agi_command).with('EXEC Transfer', 'other@place.com').and_return code: 200
1392
+ subject.execute_command command
1393
+ expect(command.response(0.5)).to eq(Adhearsion::ProtocolError.new.setup('error', 'TRANSFERSTATUS was UNSUPPORTED', subject.id))
1394
+ end
1395
+ end
1396
+
1397
+ context "when the AMI commannd raises an error" do
1398
+ let(:message) { 'Some error' }
1399
+ let(:error) { RubyAMI::Error.new.tap { |e| e.message = message } }
1400
+
1401
+ before { expect(subject).to receive(:execute_agi_command).and_raise error }
1402
+
1403
+ it "should return an error with the message" do
1404
+ subject.execute_command command
1405
+ expect(command.response(0.5)).to eq(Adhearsion::ProtocolError.new.setup('error', message, subject.id))
1406
+ end
1407
+
1408
+ context "because the channel is gone" do
1409
+ let(:error) { ChannelGoneError }
1410
+
1411
+ it "should return an :item_not_found event for the call" do
1412
+ subject.execute_command command
1413
+ expect(command.response(0.5)).to eq(Adhearsion::ProtocolError.new.setup(:item_not_found, "Could not find a call with ID #{subject.id}", subject.id))
1414
+ end
1415
+ end
1416
+ end
1417
+ end
1418
+
1419
+ context 'with a reject command' do
1420
+ let(:command) { Adhearsion::Rayo::Command::Reject.new }
1421
+
1422
+ it "with a :busy reason should send an EXEC Busy AGI command and set the command's response" do
1423
+ command.reason = :busy
1424
+ expect(subject).to receive(:execute_agi_command).with('EXEC Busy').and_return code: 200
1425
+ subject.execute_command command
1426
+ expect(command.response(0.5)).to be true
1427
+ end
1428
+
1429
+ it "with a :decline reason should send a Hangup AMI command (cause 21) and set the command's response" do
1430
+ command.reason = :decline
1431
+ expect(ami_client).to receive(:send_action).once.with('Hangup', 'Channel' => channel, 'Cause' => 21).and_return RubyAMI::Response.new
1432
+ subject.execute_command command
1433
+ expect(command.response(0.5)).to be true
1434
+ end
1435
+
1436
+ it "with an :error reason should send an EXEC Congestion AGI command and set the command's response" do
1437
+ command.reason = :error
1438
+ expect(subject).to receive(:execute_agi_command).with('EXEC Congestion').and_return code: 200
1439
+ subject.execute_command command
1440
+ expect(command.response(0.5)).to be true
1441
+ end
1442
+
1443
+ context "when the AMI commannd raises an error" do
1444
+ let(:message) { 'Some error' }
1445
+ let(:error) { RubyAMI::Error.new.tap { |e| e.message = message } }
1446
+
1447
+ before { expect(subject).to receive(:execute_agi_command).and_raise error }
1448
+
1449
+ it "should return an error with the message" do
1450
+ subject.execute_command command
1451
+ expect(command.response(0.5)).to eq(Adhearsion::ProtocolError.new.setup('error', message, subject.id))
1452
+ end
1453
+
1454
+ context "because the channel is gone" do
1455
+ let(:error) { ChannelGoneError }
1456
+
1457
+ it "should return an :item_not_found event for the call" do
1458
+ subject.execute_command command
1459
+ expect(command.response(0.5)).to eq(Adhearsion::ProtocolError.new.setup(:item_not_found, "Could not find a call with ID #{subject.id}", subject.id))
1460
+ end
1461
+ end
1462
+ end
1463
+ end
1464
+
1465
+ context 'with an answer command' do
1466
+ let(:command) { Adhearsion::Rayo::Command::Answer.new }
1467
+
1468
+ it "should send an ANSWER AGI command and set the command's response" do
1469
+ expect(subject).to receive(:execute_agi_command).with('ANSWER').and_return code: 200
1470
+ subject.execute_command command
1471
+ expect(command.response(0.5)).to be true
1472
+ end
1473
+
1474
+ it "should be answered" do
1475
+ expect(subject).to receive(:execute_agi_command)
1476
+ subject.execute_command command
1477
+ expect(subject).to be_answered
1478
+ end
1479
+
1480
+ context "when the AMI command raises an error" do
1481
+ let(:message) { 'Some error' }
1482
+ let(:error) { RubyAMI::Error.new.tap { |e| e.message = message } }
1483
+
1484
+ before { expect(subject).to receive(:execute_agi_command).and_raise error }
1485
+
1486
+ it "should return an error with the message" do
1487
+ subject.execute_command command
1488
+ expect(command.response(0.5)).to eq(Adhearsion::ProtocolError.new.setup('error', message, subject.id))
1489
+ end
1490
+
1491
+ it "should not be answered" do
1492
+ subject.execute_command command
1493
+ expect(subject).not_to be_answered
1494
+ end
1495
+
1496
+ context "because the channel is gone" do
1497
+ let(:error) { ChannelGoneError }
1498
+
1499
+ it "should return an :item_not_found event for the call" do
1500
+ subject.execute_command command
1501
+ expect(command.response(0.5)).to eq(Adhearsion::ProtocolError.new.setup(:item_not_found, "Could not find a call with ID #{subject.id}", subject.id))
1502
+ end
1503
+ end
1504
+ end
1505
+ end
1506
+
1507
+ context 'with a hangup command' do
1508
+ let(:command) { Adhearsion::Rayo::Command::Hangup.new }
1509
+
1510
+ it "should send a Hangup AMI command and set the command's response" do
1511
+ expect(ami_client).to receive(:send_action).once.with('Hangup', 'Channel' => channel, 'Cause' => 16).and_return RubyAMI::Response.new
1512
+ subject.execute_command command
1513
+ expect(command.response(0.5)).to be true
1514
+ end
1515
+
1516
+ context "when the AMI commannd raises an error" do
1517
+ let(:message) { 'Some error' }
1518
+ let(:error) { RubyAMI::Error.new.tap { |e| e.message = message } }
1519
+
1520
+ before { expect(ami_client).to receive(:send_action).and_raise error }
1521
+
1522
+ it "should return an error with the message" do
1523
+ subject.execute_command command
1524
+ expect(command.response(0.5)).to eq(Adhearsion::ProtocolError.new.setup('error', message, subject.id))
1525
+ end
1526
+
1527
+ context "which is 'No such channel'" do
1528
+ let(:message) { 'No such channel' }
1529
+
1530
+ it "should return an :item_not_found event for the call" do
1531
+ subject.execute_command command
1532
+ expect(command.response(0.5)).to eq(Adhearsion::ProtocolError.new.setup(:item_not_found, "Could not find a call with ID #{subject.id}", subject.id))
1533
+ end
1534
+ end
1535
+
1536
+ context "which is 'Channel SIP/nosuchchannel does not exist.'" do
1537
+ let(:message) { 'Channel SIP/nosuchchannel does not exist.' }
1538
+
1539
+ it "should return an :item_not_found event for the call" do
1540
+ subject.execute_command command
1541
+ expect(command.response(0.5)).to eq(Adhearsion::ProtocolError.new.setup(:item_not_found, "Could not find a call with ID #{subject.id}", subject.id))
1542
+ end
1543
+ end
1544
+ end
1545
+ end
1546
+
1547
+ context "with a join command" do
1548
+ let(:other_call_id) { "abc123" }
1549
+ let(:other_channel) { 'SIP/bar' }
1550
+ let(:other_translator) { double('Translator::Asterisk').as_null_object }
1551
+
1552
+ let :other_call do
1553
+ Call.new other_channel, other_translator, ami_client, connection
1554
+ end
1555
+
1556
+ let :command do
1557
+ Adhearsion::Rayo::Command::Join.new call_uri: other_call_id
1558
+ end
1559
+
1560
+ before { expect(translator).to receive(:call_with_id).with(other_call_id).and_return(other_call) }
1561
+
1562
+ it "executes the proper dialplan Bridge application" do
1563
+ expect(subject).to receive(:execute_agi_command).with('EXEC Bridge', "#{other_channel},F(#{REDIRECT_CONTEXT},#{REDIRECT_EXTENSION},#{REDIRECT_PRIORITY})").and_return code: 200
1564
+ subject.execute_command command
1565
+ end
1566
+
1567
+ context "when the other call doesn't exist" do
1568
+ let(:other_call) { nil }
1569
+
1570
+ it "returns an error" do
1571
+ subject.execute_command command
1572
+ expect(command.response(0.5)).to eq(Adhearsion::ProtocolError.new.setup(:service_unavailable, "Could not find join party with address #{other_call_id}", subject.id))
1573
+ end
1574
+ end
1575
+
1576
+ context "when the AMI command raises an error" do
1577
+ let(:message) { 'Some error' }
1578
+ let(:error) { RubyAMI::Error.new.tap { |e| e.message = message } }
1579
+
1580
+ before { expect(subject).to receive(:execute_agi_command).and_raise error }
1581
+
1582
+ it "should return an error with the message" do
1583
+ subject.execute_command command
1584
+ expect(command.response(0.5)).to eq(Adhearsion::ProtocolError.new.setup('error', message, subject.id))
1585
+ end
1586
+
1587
+ it "should not be answered" do
1588
+ subject.execute_command command
1589
+ expect(subject).not_to be_answered
1590
+ end
1591
+
1592
+ context "because the channel is gone" do
1593
+ let(:error) { ChannelGoneError }
1594
+
1595
+ it "should return an :item_not_found event for the call" do
1596
+ subject.execute_command command
1597
+ expect(command.response(0.5)).to eq(Adhearsion::ProtocolError.new.setup(:item_not_found, "Could not find a call with ID #{subject.id}", subject.id))
1598
+ end
1599
+ end
1600
+ end
1601
+ end
1602
+
1603
+ context "with an unjoin command" do
1604
+ let(:other_call_id) { "abc123" }
1605
+ let(:other_channel) { 'SIP/bar' }
1606
+
1607
+ let :other_call do
1608
+ Call.new other_channel, translator, ami_client, connection
1609
+ end
1610
+
1611
+ let :command do
1612
+ Adhearsion::Rayo::Command::Unjoin.new call_uri: other_call_id
1613
+ end
1614
+
1615
+ it "executes the unjoin through redirection" do
1616
+ expect(translator).to receive(:call_with_id).with(other_call_id).and_return(nil)
1617
+
1618
+ expect(ami_client).to receive(:send_action).once.with("Redirect",
1619
+ 'Channel' => channel,
1620
+ 'Exten' => Translator::Asterisk::REDIRECT_EXTENSION,
1621
+ 'Priority' => Translator::Asterisk::REDIRECT_PRIORITY,
1622
+ 'Context' => Translator::Asterisk::REDIRECT_CONTEXT,
1623
+ ).and_return RubyAMI::Response.new
1624
+
1625
+ subject.execute_command command
1626
+
1627
+ expect(command.response(1)).to be_truthy
1628
+ end
1629
+
1630
+ it "executes the unjoin through redirection, on the subject call and the other call" do
1631
+ expect(translator).to receive(:call_with_id).with(other_call_id).and_return(other_call)
1632
+
1633
+ expect(ami_client).to receive(:send_action).once.with("Redirect",
1634
+ 'Channel' => channel,
1635
+ 'Exten' => Translator::Asterisk::REDIRECT_EXTENSION,
1636
+ 'Priority' => Translator::Asterisk::REDIRECT_PRIORITY,
1637
+ 'Context' => Translator::Asterisk::REDIRECT_CONTEXT,
1638
+ 'ExtraChannel' => other_channel,
1639
+ 'ExtraExten' => Translator::Asterisk::REDIRECT_EXTENSION,
1640
+ 'ExtraPriority' => Translator::Asterisk::REDIRECT_PRIORITY,
1641
+ 'ExtraContext' => Translator::Asterisk::REDIRECT_CONTEXT
1642
+ ).and_return RubyAMI::Response.new
1643
+
1644
+ subject.execute_command command
1645
+ end
1646
+
1647
+ context "when the AMI commannd raises an error" do
1648
+ let(:message) { 'Some error' }
1649
+ let(:error) { RubyAMI::Error.new.tap { |e| e.message = message } }
1650
+
1651
+ before do
1652
+ expect(translator).to receive(:call_with_id).with(other_call_id).and_return(nil)
1653
+ expect(ami_client).to receive(:send_action).and_raise error
1654
+ end
1655
+
1656
+ it "should return an error with the message" do
1657
+ subject.execute_command command
1658
+ expect(command.response(0.5)).to eq(Adhearsion::ProtocolError.new.setup('error', message, subject.id))
1659
+ end
1660
+
1661
+ context "which is 'No such channel'" do
1662
+ let(:message) { 'No such channel' }
1663
+
1664
+ it "should return an :item_not_found event for the call" do
1665
+ subject.execute_command command
1666
+ expect(command.response(0.5)).to eq(Adhearsion::ProtocolError.new.setup(:item_not_found, "Could not find a call with ID #{subject.id}", subject.id))
1667
+ end
1668
+ end
1669
+
1670
+ context "which is 'Channel SIP/nosuchchannel does not exist.'" do
1671
+ let(:message) { 'Channel SIP/nosuchchannel does not exist.' }
1672
+
1673
+ it "should return an :item_not_found event for the call" do
1674
+ subject.execute_command command
1675
+ expect(command.response(0.5)).to eq(Adhearsion::ProtocolError.new.setup(:item_not_found, "Could not find a call with ID #{subject.id}", subject.id))
1676
+ end
1677
+ end
1678
+ end
1679
+ end
1680
+
1681
+ context 'with an AGI command component' do
1682
+ let :command do
1683
+ Adhearsion::Rayo::Component::Asterisk::AGI::Command.new :name => 'Answer'
1684
+ end
1685
+
1686
+ it 'should create an AGI command component actor and execute it asynchronously' do
1687
+ mock_action = Translator::Asterisk::Component::Asterisk::AGICommand.new(command, subject)
1688
+ expect(Component::Asterisk::AGICommand).to receive(:new).once.with(command, subject).and_return mock_action
1689
+ expect(mock_action).to receive(:execute).once
1690
+ subject.execute_command command
1691
+ end
1692
+ end
1693
+
1694
+ context 'with an Output component' do
1695
+ let :command do
1696
+ Adhearsion::Rayo::Component::Output.new
1697
+ end
1698
+
1699
+ it 'should create an Output component and execute it asynchronously' do
1700
+ mock_action = Translator::Asterisk::Component::Output.new(command, subject)
1701
+ expect(Component::Output).to receive(:new).once.with(command, subject).and_return mock_action
1702
+ expect(mock_action).to receive(:execute).once
1703
+ subject.execute_command command
1704
+ end
1705
+ end
1706
+
1707
+ context 'with an Input component' do
1708
+ let :command do
1709
+ Adhearsion::Rayo::Component::Input.new
1710
+ end
1711
+
1712
+ it 'should create an Input component and execute it asynchronously' do
1713
+ mock_action = Translator::Asterisk::Component::Input.new(command, subject)
1714
+ expect(Component::Input).to receive(:new).once.with(command, subject).and_return mock_action
1715
+ expect(mock_action).to receive(:execute).once
1716
+ subject.execute_command command
1717
+ end
1718
+ end
1719
+
1720
+ context 'with a Prompt component' do
1721
+ def grxml_doc(mode = :dtmf)
1722
+ RubySpeech::GRXML.draw :mode => mode.to_s, :root => 'digits' do
1723
+ rule id: 'digits' do
1724
+ one_of do
1725
+ 0.upto(1) { |d| item { d.to_s } }
1726
+ end
1727
+ end
1728
+ end
1729
+ end
1730
+
1731
+ let :command do
1732
+ Adhearsion::Rayo::Component::Prompt.new(
1733
+ {
1734
+ render_document: {
1735
+ content_type: 'text/uri-list',
1736
+ value: ['http://example.com/hello.mp3']
1737
+ },
1738
+ renderer: renderer
1739
+ },
1740
+ {
1741
+ grammar: {
1742
+ value: grxml_doc,
1743
+ content_type: 'application/srgs+xml'
1744
+ },
1745
+ recognizer: recognizer
1746
+ })
1747
+ end
1748
+
1749
+ let(:mock_action) { Translator::Asterisk::Component::MRCPPrompt.new(command, subject) }
1750
+
1751
+ before { mock_action}
1752
+
1753
+ context "when the recognizer is unimrcp and the renderer is unimrcp" do
1754
+ let(:recognizer) { :unimrcp }
1755
+ let(:renderer) { :unimrcp }
1756
+
1757
+ it 'should create an MRCPPrompt component and execute it asynchronously' do
1758
+ expect(Component::MRCPPrompt).to receive(:new).once.with(command, subject).and_return mock_action
1759
+ expect(mock_action).to receive(:execute).once
1760
+ subject.execute_command command
1761
+ end
1762
+ end
1763
+
1764
+ context "when the recognizer is unimrcp and the renderer is asterisk" do
1765
+ let(:recognizer) { :unimrcp }
1766
+ let(:renderer) { :asterisk }
1767
+
1768
+ it 'should create an MRCPPrompt component and execute it asynchronously' do
1769
+ expect(Component::MRCPNativePrompt).to receive(:new).once.with(command, subject).and_return mock_action
1770
+ expect(mock_action).to receive(:execute).once
1771
+ subject.execute_command command
1772
+ end
1773
+ end
1774
+
1775
+ context "when the recognizer is unimrcp and the renderer is something we can't compose with unimrcp" do
1776
+ let(:recognizer) { :unimrcp }
1777
+ let(:renderer) { :swift }
1778
+
1779
+ it 'should return an error' do
1780
+ subject.execute_command command
1781
+ expect(command.response(0.5)).to eq(Adhearsion::ProtocolError.new.setup(:invalid_command, "Invalid recognizer/renderer combination", subject.id))
1782
+ end
1783
+ end
1784
+
1785
+ context "when the recognizer is something other than unimrcp" do
1786
+ let(:recognizer) { :asterisk }
1787
+ let(:renderer) { :unimrcp }
1788
+
1789
+ it 'should create a ComposedPrompt component and execute it asynchronously' do
1790
+ expect(Component::ComposedPrompt).to receive(:new).once.with(command, subject).and_return mock_action
1791
+ expect(mock_action).to receive(:execute).once
1792
+ subject.execute_command command
1793
+ end
1794
+ end
1795
+ end
1796
+
1797
+ context 'with a Record component' do
1798
+ let :command do
1799
+ Adhearsion::Rayo::Component::Record.new
1800
+ end
1801
+
1802
+ it 'should create a Record component and execute it asynchronously' do
1803
+ mock_action = Translator::Asterisk::Component::Record.new(command, subject)
1804
+ expect(Component::Record).to receive(:new).once.with(command, subject).and_return mock_action
1805
+ expect(mock_action).to receive(:execute).once
1806
+ subject.execute_command command
1807
+ end
1808
+ end
1809
+
1810
+ context 'with a component command' do
1811
+ let(:component_id) { 'foobar' }
1812
+
1813
+ let :command do
1814
+ Adhearsion::Rayo::Component::Stop.new :component_id => component_id
1815
+ end
1816
+
1817
+ let :mock_component do
1818
+ double 'Component', :id => component_id
1819
+ end
1820
+
1821
+ context "for a known component ID" do
1822
+ before { subject.register_component mock_component }
1823
+
1824
+ it 'should send the command to the component for execution' do
1825
+ expect(mock_component).to receive(:execute_command).once
1826
+ subject.execute_command command
1827
+ end
1828
+ end
1829
+
1830
+ context "for a component which began executing but terminated" do
1831
+ let :component_command do
1832
+ Adhearsion::Rayo::Component::Asterisk::AGI::Command.new :name => 'Wait'
1833
+ end
1834
+
1835
+ let(:comp_id) { component_command.response.component_id }
1836
+
1837
+ let(:subsequent_command) { Adhearsion::Rayo::Component::Stop.new :component_id => comp_id }
1838
+
1839
+ let :expected_event do
1840
+ Adhearsion::Event::Complete.new target_call_id: subject.id,
1841
+ component_id: comp_id,
1842
+ source_uri: comp_id,
1843
+ reason: Adhearsion::Event::Complete::Error.new
1844
+ end
1845
+
1846
+ before do
1847
+ component_command.request!
1848
+ subject.execute_command component_command
1849
+ end
1850
+
1851
+ context "normally" do
1852
+ it 'sends an error in response to the command' do
1853
+ component = subject.component_with_id comp_id
1854
+
1855
+ component.send_complete_event Adhearsion::Rayo::Component::Asterisk::AGI::Command::Complete.new
1856
+
1857
+ expect(subject.component_with_id(comp_id)).to be_nil
1858
+
1859
+ subsequent_command.request!
1860
+ subject.execute_command subsequent_command
1861
+ expect(subsequent_command.response).to eq(Adhearsion::ProtocolError.new.setup(:item_not_found, "Could not find a component with ID #{comp_id} for call #{subject.id}", subject.id, comp_id))
1862
+ end
1863
+ end
1864
+
1865
+ context "by crashing" do
1866
+ context "when we dispatch the command to it" do
1867
+ it 'sends an error in response to the command' do
1868
+ component = subject.component_with_id comp_id
1869
+
1870
+ expect(component).to receive(:execute_command).and_raise(Celluloid::DeadActorError)
1871
+
1872
+ subsequent_command.request!
1873
+ subject.execute_command subsequent_command
1874
+ expect(subsequent_command.response).to eq(Adhearsion::ProtocolError.new.setup(:item_not_found, "Could not find a component with ID #{comp_id} for call #{subject.id}", subject.id, comp_id))
1875
+ end
1876
+ end
1877
+ end
1878
+ end
1879
+
1880
+ context "for an unknown component ID" do
1881
+ it 'sends an error in response to the command' do
1882
+ subject.execute_command command
1883
+ expect(command.response).to eq(Adhearsion::ProtocolError.new.setup(:item_not_found, "Could not find a component with ID #{component_id} for call #{subject.id}", subject.id, component_id))
1884
+ end
1885
+ end
1886
+ end
1887
+
1888
+ context 'with a command we do not understand' do
1889
+ let :command do
1890
+ Adhearsion::Rayo::Command::Mute.new
1891
+ end
1892
+
1893
+ it 'sends an error in response to the command' do
1894
+ subject.execute_command command
1895
+ expect(command.response).to eq(Adhearsion::ProtocolError.new.setup('command-not-acceptable', "Did not understand command for call #{subject.id}", subject.id))
1896
+ end
1897
+ end
1898
+ end#execute_command
1899
+
1900
+ describe '#execute_agi_command' do
1901
+ before { stub_uuids Adhearsion.new_uuid }
1902
+
1903
+ let :response do
1904
+ RubyAMI::Response.new 'ActionID' => "552a9d9f-46d7-45d8-a257-06fe95f48d99",
1905
+ 'Message' => 'Added AGI original_command to queue'
1906
+ end
1907
+
1908
+ context 'with an error' do
1909
+ let(:message) { 'Action failed' }
1910
+
1911
+ let :error do
1912
+ RubyAMI::Error.new.tap { |e| e.message = message }
1913
+ end
1914
+
1915
+ it 'should raise the error' do
1916
+ expect(ami_client).to receive(:send_action).once.and_raise error
1917
+ expect { subject.execute_agi_command 'EXEC ANSWER' }.to raise_error(RubyAMI::Error, 'Action failed')
1918
+ end
1919
+
1920
+ context "which is 'No such channel'" do
1921
+ let(:message) { 'No such channel' }
1922
+
1923
+ it 'should raise ChannelGoneError' do
1924
+ expect(ami_client).to receive(:send_action).once.and_raise error
1925
+ expect { subject.execute_agi_command 'EXEC ANSWER' }.to raise_error(ChannelGoneError, message)
1926
+ end
1927
+ end
1928
+
1929
+ context "which is 'Channel SIP/nosuchchannel does not exist.'" do
1930
+ let(:message) { 'Channel SIP/nosuchchannel does not exist.' }
1931
+
1932
+ it 'should raise ChannelGoneError' do
1933
+ expect(ami_client).to receive(:send_action).once.and_raise error
1934
+ expect { subject.execute_agi_command 'EXEC ANSWER' }.to raise_error(ChannelGoneError, message)
1935
+ end
1936
+ end
1937
+ end
1938
+
1939
+ describe 'when receiving an AsyncAGI event' do
1940
+ context 'of type Exec' do
1941
+ let(:ami_event) do
1942
+ RubyAMI::Event.new 'AsyncAGI',
1943
+ "SubEvent" => "Exec",
1944
+ "Channel" => channel,
1945
+ "CommandID" => Adhearsion.new_uuid,
1946
+ "Command" => "EXEC ANSWER",
1947
+ "Result" => "200%20result=123%20(timeout)%0A"
1948
+ end
1949
+
1950
+ it 'should send an appropriate AsyncAGI AMI action' do
1951
+ expect(ami_client).to receive(:send_action).once.with('AGI', 'Channel' => channel, 'Command' => 'EXEC ANSWER', 'CommandID' => Adhearsion.new_uuid).and_return(response)
1952
+ fut = Celluloid::Future.new { subject.execute_agi_command 'EXEC ANSWER' }
1953
+ sleep 0.25
1954
+ subject.process_ami_event ami_event
1955
+ end
1956
+
1957
+ context 'with some parameters' do
1958
+ let(:params) { [1000, 'foo'] }
1959
+
1960
+ it 'should send the appropriate action' do
1961
+ expect(ami_client).to receive(:send_action).once.with('AGI', 'Channel' => channel, 'Command' => 'WAIT FOR DIGIT "1000" "foo"', 'CommandID' => Adhearsion.new_uuid).and_return(response)
1962
+ fut = Celluloid::Future.new { subject.execute_agi_command 'WAIT FOR DIGIT', *params }
1963
+ sleep 0.25
1964
+ subject.process_ami_event ami_event
1965
+ end
1966
+ end
1967
+
1968
+ it 'should return the result' do
1969
+ fut = Celluloid::Future.new { subject.execute_agi_command 'EXEC ANSWER' }
1970
+ sleep 0.25
1971
+ subject.process_ami_event ami_event
1972
+ expect(fut.value).to eq({code: 200, result: 123, data: 'timeout'})
1973
+ end
1974
+ end
1975
+ end
1976
+
1977
+ describe 'when receiving an Asterisk 13 AsyncAGIExec event' do
1978
+ context 'without a subtype' do
1979
+ let(:ami_event) do
1980
+ RubyAMI::Event.new 'AsyncAGIExec',
1981
+ "Channel" => channel,
1982
+ "CommandId" => Adhearsion.new_uuid,
1983
+ "Command" => "EXEC ANSWER",
1984
+ "Result" => "200%20result=123%20(timeout)%0A"
1985
+ end
1986
+
1987
+ it 'should send an appropriate AsyncAGI AMI action' do
1988
+ expect(ami_client).to receive(:send_action).once.with('AGI', 'Channel' => channel, 'Command' => 'EXEC ANSWER', 'CommandID' => Adhearsion.new_uuid).and_return(response)
1989
+ fut = Celluloid::Future.new { subject.execute_agi_command 'EXEC ANSWER' }
1990
+ sleep 0.25
1991
+ subject.process_ami_event ami_event
1992
+ end
1993
+
1994
+ context 'with some parameters' do
1995
+ let(:params) { [1000, 'foo'] }
1996
+
1997
+ it 'should send the appropriate action' do
1998
+ expect(ami_client).to receive(:send_action).once.with('AGI', 'Channel' => channel, 'Command' => 'WAIT FOR DIGIT "1000" "foo"', 'CommandID' => Adhearsion.new_uuid).and_return(response)
1999
+ fut = Celluloid::Future.new { subject.execute_agi_command 'WAIT FOR DIGIT', *params }
2000
+ sleep 0.25
2001
+ subject.process_ami_event ami_event
2002
+ end
2003
+ end
2004
+
2005
+ it 'should return the result' do
2006
+ fut = Celluloid::Future.new { subject.execute_agi_command 'EXEC ANSWER' }
2007
+ sleep 0.25
2008
+ subject.process_ami_event ami_event
2009
+ expect(fut.value).to eq({code: 200, result: 123, data: 'timeout'})
2010
+ end
2011
+ end
2012
+ end
2013
+ end
2014
+
2015
+ describe '#redirect_back' do
2016
+ let(:other_channel) { 'SIP/bar' }
2017
+
2018
+ let :other_call do
2019
+ Call.new other_channel, translator, ami_client, connection
2020
+ end
2021
+
2022
+ it "executes the proper AMI action with only the subject call" do
2023
+ expect(ami_client).to receive(:send_action).once.with 'Redirect',
2024
+ 'Exten' => Translator::Asterisk::REDIRECT_EXTENSION,
2025
+ 'Priority' => Translator::Asterisk::REDIRECT_PRIORITY,
2026
+ 'Context' => Translator::Asterisk::REDIRECT_CONTEXT,
2027
+ 'Channel' => channel
2028
+ subject.redirect_back
2029
+ end
2030
+
2031
+ it "executes the proper AMI action with another call specified" do
2032
+ expect(ami_client).to receive(:send_action).once.with 'Redirect',
2033
+ 'Channel' => channel,
2034
+ 'Exten' => Translator::Asterisk::REDIRECT_EXTENSION,
2035
+ 'Priority' => Translator::Asterisk::REDIRECT_PRIORITY,
2036
+ 'Context' => Translator::Asterisk::REDIRECT_CONTEXT,
2037
+ 'ExtraChannel' => other_channel,
2038
+ 'ExtraExten' => Translator::Asterisk::REDIRECT_EXTENSION,
2039
+ 'ExtraPriority' => Translator::Asterisk::REDIRECT_PRIORITY,
2040
+ 'ExtraContext' => Translator::Asterisk::REDIRECT_CONTEXT
2041
+ subject.redirect_back other_call
2042
+ end
2043
+ end
2044
+ end
2045
+ end
2046
+ end
2047
+ end