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
@@ -8,334 +8,1238 @@ module Adhearsion
8
8
  describe Input do
9
9
  include CallControllerTestHelpers
10
10
 
11
- describe "#play_sound_files_for_menu" do
12
- let(:renderer_options) { { renderer: nil } }
13
- let(:options) { Hash.new }
14
- let(:menu_instance) { MenuDSL::Menu.new(options) }
15
- let(:sound_file) { "press a button" }
16
- let(:sound_files) { [sound_file] }
17
-
18
- it "should play the sound files for the menu" do
19
- expect(subject).to receive(:interruptible_play).with(sound_file, renderer_options).and_return("1")
20
- expect(subject.play_sound_files_for_menu(menu_instance, sound_files)).to eq('1')
11
+ def self.temp_config_value(key, value, namespace = Adhearsion.config.core.media)
12
+ before do
13
+ @original_value = namespace[key]
14
+ namespace[key] = value
21
15
  end
22
16
 
23
- it "should wait for digit if nothing is pressed during playback" do
24
- expect(subject).to receive(:interruptible_play).with(sound_file, renderer_options).and_return(nil)
25
- expect(subject).to receive(:wait_for_digit).with(menu_instance.timeout).and_return("1")
26
- expect(subject.play_sound_files_for_menu(menu_instance, sound_files)).to eq('1')
27
- end
17
+ after { namespace[key] = @original_value }
18
+ end
28
19
 
29
- context "with a renderer specified" do
30
- let(:options) { { :renderer => :native } }
31
- let(:renderer_options) { { renderer: :native } }
32
- it "should play the sound files for the menu" do
33
- expect(subject).to receive(:interruptible_play).with(sound_file, renderer_options).and_return("1")
34
- expect(subject.play_sound_files_for_menu(menu_instance, sound_files)).to eq('1')
35
- end
20
+ let(:prompts) { ['http://example.com/nice-to-meet-you.mp3', 'http://example.com/press-some-buttons.mp3'] }
36
21
 
22
+ let :expected_ssml do
23
+ RubySpeech::SSML.draw do
24
+ audio src: 'http://example.com/nice-to-meet-you.mp3'
25
+ audio src: 'http://example.com/press-some-buttons.mp3'
37
26
  end
27
+ end
28
+
29
+ let :expected_output_options do
30
+ {
31
+ render_document: {value: expected_ssml},
32
+ renderer: nil
33
+ }
34
+ end
35
+
36
+ let :expected_input_options do
37
+ {
38
+ mode: :dtmf,
39
+ initial_timeout: 5000,
40
+ inter_digit_timeout: 2000,
41
+ max_silence: 5000,
42
+ min_confidence: 0.5,
43
+ recognizer: nil,
44
+ language: 'en-US',
45
+ grammar: { value: expected_grxml }
46
+ }
47
+ end
38
48
 
39
- context "when the menu is not interruptible" do
40
- let(:options) { { :interruptible => false } }
41
- it "should play the sound files and wait for digit" do
42
- expect(subject).to receive(:play).with(sound_file, renderer_options).and_return true
43
- expect(subject).to receive(:wait_for_digit).with(menu_instance.timeout).and_return("1")
44
- expect(subject.play_sound_files_for_menu(menu_instance, sound_files)).to eq('1')
49
+ let(:expected_barge_in) { true }
50
+
51
+ let :expected_prompt do
52
+ Adhearsion::Rayo::Component::Prompt.new expected_output_options, expected_input_options, barge_in: expected_barge_in
53
+ end
54
+
55
+ let(:reason) { Adhearsion::Rayo::Component::Input::Complete::NoMatch.new }
56
+
57
+ before { allow_any_instance_of(Adhearsion::Rayo::Component::Prompt).to receive(:complete_event).and_return(double(reason: reason)) }
58
+
59
+ describe "#ask" do
60
+ let :digit_limit_grammar do
61
+ RubySpeech::GRXML.draw mode: 'dtmf', root: 'digits', tag_format: 'semantics/1.0-literals' do
62
+ rule id: 'digits', scope: 'public' do
63
+ item repeat: '0-5' do
64
+ one_of do
65
+ 0.upto(9) { |d| item { d.to_s } }
66
+ item { "#" }
67
+ item { "*" }
68
+ end
69
+ end
70
+ end
45
71
  end
46
72
  end
47
73
 
48
- context "with a renderer specified and not interruptible" do
49
- let(:options) { { :renderer => :native, :interruptible => false } }
50
- let(:renderer_options) { { renderer: :native } }
51
- it "should pass the renderer option to #play" do
52
- expect(subject).to receive(:play).with(sound_file, renderer_options).and_return true
53
- expect(subject).to receive(:wait_for_digit).with(menu_instance.timeout).and_return("1")
54
- expect(subject.play_sound_files_for_menu(menu_instance, sound_files)).to eq('1')
74
+ context "without a digit limit, terminator digit or grammar" do
75
+ it "raises ArgumentError" do
76
+ expect { subject.ask prompts }.to raise_error(ArgumentError, "You must specify at least one of limit, terminator or grammar")
55
77
  end
56
78
  end
57
- end#play_sound_files_for_menu
58
79
 
59
- describe "#wait_for_digit" do
60
- let(:timeout) { 2 }
61
- let(:timeout_ms) { 2000 }
80
+ context "with a digit limit" do
81
+ let(:expected_grxml) { digit_limit_grammar }
62
82
 
63
- let(:grxml) {
64
- RubySpeech::GRXML.draw :mode => 'dtmf', :root => 'inputdigits' do
65
- rule id: 'inputdigits', scope: 'public' do
66
- one_of do
67
- 0.upto(9) { |d| item { d.to_s } }
68
- item { "#" }
69
- item { "*" }
70
- end
83
+ it "executes a Prompt component with the correct prompts and grammar" do
84
+ expect_component_execution expected_prompt
85
+
86
+ subject.ask prompts, limit: 5
87
+ end
88
+
89
+ context "with nil prompts" do
90
+ let(:prompts) { [nil, 'http://example.com/nice-to-meet-you.mp3', 'http://example.com/press-some-buttons.mp3'] }
91
+
92
+ it "executes a Prompt component with the correct prompts and grammar" do
93
+ expect_component_execution expected_prompt
94
+ subject.ask prompts, limit: 5
95
+ end
96
+ end
97
+
98
+ context "with no prompts" do
99
+ it "executes an Input component with the correct grammar" do
100
+ allow_any_instance_of(Adhearsion::Rayo::Component::Input).to receive(:complete_event).and_return(double(reason: reason))
101
+ expect_component_execution Adhearsion::Rayo::Component::Input.new(expected_input_options)
102
+ subject.ask limit: 5
71
103
  end
72
104
  end
73
- }
74
105
 
75
- let(:input_component) {
76
- Punchblock::Component::Input.new(
77
- { :mode => :dtmf,
78
- :initial_timeout => timeout_ms,
79
- :inter_digit_timeout => timeout_ms,
80
- :grammar => {
81
- :value => grxml.to_s
106
+ context "with no prompts, but with a render_document option" do
107
+ let :expected_output_options do
108
+ {
109
+ render_document: {url: 'http://foo.com/bar'},
110
+ renderer: nil
82
111
  }
83
- }
84
- )
85
- }
112
+ end
86
113
 
87
- let(:utterance) { 'dtmf-5' }
114
+ it "executes an Input component with the correct grammar" do
115
+ allow_any_instance_of(Adhearsion::Rayo::Component::Input).to receive(:complete_event).and_return(double(reason: reason))
116
+ expect_component_execution expected_prompt
117
+ subject.ask limit: 5, render_document: {url: 'http://foo.com/bar'}
118
+ end
119
+ end
88
120
 
89
- def expect_component_complete_event
90
- expect_input_component_complete_event utterance
121
+ context "with only nil prompts" do
122
+ it "executes an Input component with the correct grammar" do
123
+ allow_any_instance_of(Adhearsion::Rayo::Component::Input).to receive(:complete_event).and_return(double(reason: reason))
124
+ expect_component_execution Adhearsion::Rayo::Component::Input.new(expected_input_options)
125
+ subject.ask nil, limit: 5
126
+ end
127
+ end
91
128
  end
92
129
 
93
- it "sends the correct input component" do
94
- expect_component_complete_event
95
- expect(subject).to receive(:execute_component_and_await_completion).once.with(input_component).and_return input_component
96
- subject.wait_for_digit timeout
97
- end
130
+ context "with a terminator" do
131
+ let :expected_grxml do
132
+ RubySpeech::GRXML.draw mode: 'dtmf', root: 'digits', tag_format: 'semantics/1.0-literals' do
133
+ rule id: 'digits', scope: 'public' do
134
+ item repeat: '0-' do
135
+ one_of do
136
+ 0.upto(9) { |d| item { d.to_s } }
137
+ item { "#" }
138
+ item { "*" }
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
144
+
145
+ before do
146
+ expected_input_options.merge! terminator: '#'
147
+ end
148
+
149
+ it "executes a Prompt component with the correct prompts and grammar" do
150
+ expect_component_execution expected_prompt
98
151
 
99
- it "returns the correct pressed digit" do
100
- expect_component_complete_event
101
- expect(subject).to receive(:execute_component_and_await_completion).once.with(kind_of(Punchblock::Component::Input)).and_return input_component
102
- expect(subject.wait_for_digit(timeout)).to eq('5')
152
+ subject.ask prompts, terminator: '#'
153
+ end
103
154
  end
104
155
 
105
- context "when the utterance does not have dtmf- prefix" do
106
- let(:utterance) { '5' }
156
+ context "with a digit limit and a terminator" do
157
+ let :expected_grxml do
158
+ RubySpeech::GRXML.draw mode: 'dtmf', root: 'digits', tag_format: 'semantics/1.0-literals' do
159
+ rule id: 'digits', scope: 'public' do
160
+ item repeat: '0-5' do
161
+ one_of do
162
+ 0.upto(9) { |d| item { d.to_s } }
163
+ item { "#" }
164
+ item { "*" }
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
170
+
171
+ before do
172
+ expected_input_options.merge! grammar: { value: expected_grxml },
173
+ terminator: '#'
174
+ end
107
175
 
108
- it "returns the correct pressed digit" do
109
- expect_component_complete_event
110
- expect(subject).to receive(:execute_component_and_await_completion).once.with(kind_of(Punchblock::Component::Input)).and_return input_component
111
- expect(subject.wait_for_digit(timeout)).to eq('5')
176
+ it "executes a Prompt component with the correct prompts and grammar" do
177
+ expect_component_execution expected_prompt
178
+
179
+ subject.ask prompts, limit: 5, terminator: '#'
112
180
  end
113
181
  end
114
182
 
115
- context "with a nil timeout" do
116
- let(:timeout) { nil }
117
- let(:timeout_ms) { nil }
183
+ context "with an inline GRXML grammar specified" do
184
+ let :expected_grxml do
185
+ RubySpeech::GRXML.draw root: 'main', language: 'en-us', mode: :voice, tag_format: 'semantics/1.0-literals' do
186
+ rule id: 'main', scope: 'public' do
187
+ one_of do
188
+ item { 'yes' }
189
+ item { 'no' }
190
+ end
191
+ end
192
+ end
193
+ end
194
+
195
+ before do
196
+ expected_input_options.merge! grammar: { value: expected_grxml }
197
+ end
198
+
199
+ it "executes a Prompt component with the correct prompts and grammar" do
200
+ expect_component_execution expected_prompt
118
201
 
119
- it "does not set a timeout on the component" do
120
- expect_component_complete_event
121
- expect(subject).to receive(:execute_component_and_await_completion).once.with(input_component).and_return input_component
122
- subject.wait_for_digit timeout
202
+ subject.ask prompts, grammar: expected_grxml
203
+ end
204
+
205
+ context "with multiple grammars specified" do
206
+ let :other_expected_grxml do
207
+ RubySpeech::GRXML.draw root: 'main', mode: :dtmf, tag_format: 'semantics/1.0-literals' do
208
+ rule id: 'main', scope: 'public' do
209
+ one_of do
210
+ item { 1 }
211
+ item { 2 }
212
+ end
213
+ end
214
+ end
215
+ end
216
+
217
+ before do
218
+ expected_input_options.merge! grammars: [{ value: expected_grxml }, { value: other_expected_grxml }]
219
+ end
220
+
221
+ it "executes a Prompt component with the correct prompts and grammar" do
222
+ expect_component_execution expected_prompt
223
+
224
+ subject.ask prompts, grammar: [expected_grxml, other_expected_grxml]
225
+ end
123
226
  end
124
227
  end
125
- end # wait_for_digit
126
228
 
127
- describe "#jump_to" do
128
- let(:match_object) { Class.new }
129
- let(:overrides) { Hash.new(:extension => "1") }
130
- let(:block) { Proc.new {} }
229
+ context "with a grammar URL specified" do
230
+ let(:expected_grxml) { digit_limit_grammar }
231
+ let(:grammar_url) { 'http://example.com/cities.grxml' }
232
+
233
+ before do
234
+ expected_input_options.merge! grammar: { url: grammar_url }
235
+ end
236
+
237
+ it "executes a Prompt component with the correct prompts and grammar" do
238
+ expect_component_execution expected_prompt
239
+
240
+ subject.ask prompts, grammar_url: grammar_url
241
+ end
242
+
243
+ context "with multiple grammar URLs specified" do
244
+ let(:other_grammar_url) { 'http://example.com/states.grxml' }
245
+
246
+ before do
247
+ expected_input_options.merge! grammars: [{ url: grammar_url }, { url: other_grammar_url }]
248
+ end
249
+
250
+ it "executes a Prompt component with the correct prompts and grammar" do
251
+ expect_component_execution expected_prompt
252
+
253
+ subject.ask prompts, grammar_url: [grammar_url, other_grammar_url]
254
+ end
255
+ end
256
+
257
+ context "with grammars specified inline and by URL" do
258
+ before do
259
+ expected_input_options.merge! grammars: [{ value: expected_grxml }, { url: grammar_url }]
260
+ end
261
+
262
+ it "executes a Prompt component with the correct prompts and grammar" do
263
+ expect_component_execution expected_prompt
131
264
 
132
- it "calls instance_exec if the match object has a block" do
133
- allow(match_object).to receive_messages(:block => block)
134
- expect(subject).to receive(:instance_exec).with(overrides[:extension], &block)
135
- subject.jump_to(match_object, overrides)
265
+ subject.ask prompts, grammar: expected_grxml, grammar_url: [grammar_url]
266
+ end
267
+ end
136
268
  end
137
269
 
138
- it "calls invoke if the match object does not have a block" do
139
- expect(match_object).to receive(:block).and_return(false)
140
- expect(match_object).to receive(:match_payload).and_return(:payload)
141
- expect(subject).to receive(:invoke).with(:payload, overrides)
142
- subject.jump_to(match_object, overrides)
270
+ context "with interruptible: false" do
271
+ let(:expected_grxml) { digit_limit_grammar }
272
+
273
+ let(:expected_barge_in) { false }
274
+
275
+ it "executes a Prompt with barge-in disabled" do
276
+ expect_component_execution expected_prompt
277
+
278
+ subject.ask prompts, limit: 5, interruptible: false
279
+ end
143
280
  end
144
- end#jump_to
145
281
 
146
- describe "#menu" do
147
- let(:sound_files) { ["press", "button"] }
282
+ context "with a timeout specified" do
283
+ let(:expected_grxml) { digit_limit_grammar }
284
+
285
+ before do
286
+ expected_input_options.merge! initial_timeout: 10000,
287
+ inter_digit_timeout: 2000,
288
+ max_silence: 10000
289
+ end
290
+
291
+ it "executes a Prompt with correct timeout (initial, inter-digit & max-silence)" do
292
+ expect_component_execution expected_prompt
148
293
 
149
- let(:menu_instance) do
150
- MenuDSL::Menu.new { match(1) {} }
294
+ subject.ask prompts, limit: 5, timeout: 10
295
+ end
151
296
  end
152
297
 
153
- let(:result_done) { MenuDSL::Menu::MenuResultDone.new }
154
- let(:result_terminated) { MenuDSL::Menu::MenuTerminated.new }
155
- let(:result_limit_reached) { MenuDSL::Menu::MenuLimitReached.new }
156
- let(:result_invalid) { MenuDSL::Menu::MenuResultInvalid.new }
157
- let(:result_get_another_or_timeout) { MenuDSL::Menu::MenuGetAnotherDigitOrTimeout.new }
158
- let(:result_get_another_or_finish) { MenuDSL::Menu::MenuGetAnotherDigitOrFinish.new(:match_object, :new_extension) }
159
- let(:result_found) { MenuDSL::Menu::MenuResultFound.new(:match_object, :new_extension) }
298
+ context "with a negative timeout specified" do
299
+ let(:expected_grxml) { digit_limit_grammar }
300
+
301
+ before do
302
+ expected_input_options.merge! initial_timeout: -1,
303
+ inter_digit_timeout: -1,
304
+ max_silence: -1
305
+ end
160
306
 
161
- let(:status) { :foo }
162
- let(:response) { '1234' }
307
+ it "executes a Prompt with correct timeout (initial, inter-digit & max-silence)" do
308
+ expect_component_execution expected_prompt
163
309
 
164
- before do
165
- allow(menu_instance).to receive_messages :status => status, :result => response
166
- expect(MenuDSL::Menu).to receive(:new).and_return(menu_instance)
310
+ subject.ask prompts, limit: 5, timeout: -1, inter_digit_timeout: -1
311
+ end
167
312
  end
168
313
 
169
- it "logs a warning about dropped DTMF" do
170
- expect(menu_instance).to receive(:continue).and_return(result_done)
314
+ context "with a different default timeout" do
315
+ let(:expected_grxml) { digit_limit_grammar }
171
316
 
172
- expect(call.logger).to receive(:warn).with(/deprecated/)
173
- subject.menu sound_files
317
+ before do
318
+ expected_input_options.merge! initial_timeout: 10000,
319
+ inter_digit_timeout: 2000,
320
+ max_silence: 10000
321
+ end
322
+
323
+ temp_config_value :timeout, 10
324
+
325
+ it "executes a Prompt with correct timeout (initial, inter-digit & max-silence)" do
326
+ expect_component_execution expected_prompt
327
+
328
+ subject.ask prompts, limit: 5
329
+ end
174
330
  end
175
331
 
176
- it "exits the function if MenuResultDone" do
177
- expect(menu_instance).to receive(:should_continue?).and_return(true)
178
- expect(menu_instance).to receive(:continue).and_return(result_done)
179
- result = subject.menu sound_files
180
- expect(result.menu).to be menu_instance
181
- expect(result.response).to be response
332
+ context "with a different default minimum confidence" do
333
+ let(:expected_grxml) { digit_limit_grammar }
334
+
335
+ before do
336
+ expected_input_options.merge! min_confidence: 0.8
337
+ end
338
+
339
+ temp_config_value :min_confidence, 0.8
340
+
341
+ it "executes a Prompt with correct minimum confidence" do
342
+ expect_component_execution expected_prompt
343
+
344
+ subject.ask prompts, limit: 5
345
+ end
182
346
  end
183
347
 
184
- it "exits the function if MenuTerminated" do
185
- expect(menu_instance).to receive(:should_continue?).and_return(true)
186
- expect(menu_instance).to receive(:continue).and_return(result_terminated)
187
- result = subject.menu sound_files
188
- expect(result.menu).to be menu_instance
189
- expect(result.response).to be response
348
+ context "with a different default recognizer" do
349
+ let(:expected_grxml) { digit_limit_grammar }
350
+
351
+ before do
352
+ expected_input_options.merge! recognizer: 'something_else'
353
+ end
354
+
355
+ temp_config_value :recognizer, 'something_else'
356
+
357
+ it "executes a Prompt with correct recognizer" do
358
+ expect_component_execution expected_prompt
359
+
360
+ subject.ask prompts, limit: 5
361
+ end
190
362
  end
191
363
 
192
- it "exits the function if MenuLimitReached" do
193
- expect(menu_instance).to receive(:should_continue?).and_return(true)
194
- expect(menu_instance).to receive(:continue).and_return(result_limit_reached)
195
- result = subject.menu sound_files
196
- expect(result.menu).to be menu_instance
197
- expect(result.response).to be response
364
+ context "with a different default input language" do
365
+ let(:expected_grxml) { digit_limit_grammar }
366
+
367
+ before do
368
+ expected_input_options.merge! language: 'pt-BR'
369
+ end
370
+
371
+ temp_config_value :input_language, 'pt-BR'
372
+
373
+ it "executes a Prompt with correct input language" do
374
+ expect_component_execution expected_prompt
375
+
376
+ subject.ask prompts, limit: 5
377
+ end
198
378
  end
199
379
 
200
- it "executes failure hook and returns :failure if menu fails" do
201
- expect(menu_instance).to receive(:should_continue?).and_return(false)
202
- expect(menu_instance).to receive(:execute_failure_hook).once
203
- result = subject.menu sound_files
204
- expect(result.menu).to be menu_instance
205
- expect(result.response).to be response
380
+ context "with a different default output renderer" do
381
+ let(:expected_grxml) { digit_limit_grammar }
382
+
383
+ before do
384
+ expected_output_options.merge! renderer: 'something_else'
385
+ end
386
+
387
+ temp_config_value :default_renderer, 'something_else', Adhearsion.config.core.media
388
+
389
+ it "executes a Prompt with correct renderer" do
390
+ expect_component_execution expected_prompt
391
+
392
+ subject.ask prompts, limit: 5
393
+ end
206
394
  end
207
395
 
208
- it "executes invalid hook if input is invalid" do
209
- expect(menu_instance).to receive(:should_continue?).twice.and_return(true)
210
- expect(menu_instance).to receive(:continue).and_return(result_invalid, result_done)
211
- expect(menu_instance).to receive(:execute_invalid_hook).once
212
- expect(menu_instance).to receive(:restart!).once
213
- result = subject.menu sound_files
214
- expect(result.menu).to be menu_instance
215
- expect(result.response).to be response
396
+ context "with a different default output voice" do
397
+ let(:expected_grxml) { digit_limit_grammar }
398
+
399
+ before do
400
+ expected_output_options.merge! voice: 'something_else'
401
+ end
402
+
403
+ temp_config_value :default_voice, 'something_else', Adhearsion.config.core.media
404
+
405
+ it "executes a Prompt with correct voice" do
406
+ expect_component_execution expected_prompt
407
+
408
+ subject.ask prompts, limit: 5
409
+ end
216
410
  end
217
411
 
218
- it "plays audio, then executes timeout hook if input times out" do
219
- expect(menu_instance).to receive(:should_continue?).twice.and_return(true)
220
- expect(menu_instance).to receive(:continue).and_return(result_get_another_or_timeout, result_done)
221
- expect(subject).to receive(:play_sound_files_for_menu).with(menu_instance, sound_files).and_return(nil)
222
- expect(menu_instance).to receive(:execute_timeout_hook).once
223
- expect(menu_instance).to receive(:restart!).once
224
- subject.menu sound_files
412
+ context "with overridden input options" do
413
+ let(:expected_grxml) { digit_limit_grammar }
414
+
415
+ before do
416
+ expected_input_options.merge! inter_digit_timeout: 35000
417
+ end
418
+
419
+ it "executes a Prompt with correct input options" do
420
+ expect_component_execution expected_prompt
421
+
422
+ subject.ask prompts, limit: 5, input_options: {inter_digit_timeout: 35000}
423
+ end
225
424
  end
226
425
 
227
- it "plays audio, then adds digit to digit buffer if input is received" do
228
- expect(menu_instance).to receive(:should_continue?).twice.and_return(true)
229
- expect(menu_instance).to receive(:continue).and_return(result_get_another_or_timeout, result_done)
230
- expect(subject).to receive(:play_sound_files_for_menu).with(menu_instance, sound_files).and_return("1")
231
- expect(menu_instance).to receive(:<<).with("1").once
232
- subject.menu sound_files
426
+ context "with overridden output options" do
427
+ let(:expected_grxml) { digit_limit_grammar }
428
+
429
+ before do
430
+ expected_output_options.merge! max_time: 35000
431
+ end
432
+
433
+ it "executes a Prompt with correct output options" do
434
+ expect_component_execution expected_prompt
435
+
436
+ subject.ask prompts, limit: 5, output_options: {max_time: 35000}
437
+ end
233
438
  end
234
439
 
235
- it "plays audio, then jumps to payload when input is finished" do
236
- expect(menu_instance).to receive(:should_continue?).and_return(true)
237
- expect(menu_instance).to receive(:continue).and_return(result_get_another_or_finish)
238
- expect(subject).to receive(:play_sound_files_for_menu).with(menu_instance, sound_files).and_return(nil)
239
- expect(subject).to receive(:jump_to).with(:match_object, :extension => :new_extension).once
240
- subject.menu sound_files
440
+ context "when the call is dead when trying to execute the prompt" do
441
+ before { call.terminate }
442
+
443
+ it "should raise Adhearsion::Call::Hangup" do
444
+ expect { subject.ask prompts, limit: 5 }.to raise_error Adhearsion::Call::Hangup
445
+ end
241
446
  end
242
447
 
243
- it "jumps to payload when result is found" do
244
- expect(menu_instance).to receive(:should_continue?).and_return(true)
245
- expect(menu_instance).to receive(:continue).and_return(result_found)
246
- expect(subject).to receive(:jump_to).with(:match_object, :extension => :new_extension).once
247
- result = subject.menu sound_files
248
- expect(result.menu).to be menu_instance
249
- expect(result.response).to be response
448
+ context "when a utterance is received" do
449
+ let(:expected_grxml) { digit_limit_grammar }
450
+
451
+ before { expect_component_execution expected_prompt }
452
+
453
+ let(:result) { subject.ask prompts, limit: 5 }
454
+
455
+ context "that is a match" do
456
+ let(:mode) { :dtmf }
457
+ let(:utterance) { '123' }
458
+
459
+ let :nlsml do
460
+ utterance = self.utterance
461
+ mode = self.mode
462
+ RubySpeech::NLSML.draw do
463
+ interpretation confidence: 1 do
464
+ input utterance, mode: mode
465
+ instance 'Foo'
466
+ end
467
+ end
468
+ end
469
+
470
+ let(:reason) { Adhearsion::Rayo::Component::Input::Complete::Match.new nlsml: nlsml }
471
+
472
+ it "returns :match status and the utterance" do
473
+ expect(result.status).to be :match
474
+ expect(result).to be_match
475
+ expect(result.mode).to be :dtmf
476
+ expect(result.confidence).to eq(1)
477
+ expect(result.utterance).to eq('123')
478
+ expect(result.interpretation).to eq('Foo')
479
+ expect(result.nlsml).to eq(nlsml)
480
+ end
481
+
482
+ context "with speech input" do
483
+ let(:mode) { :speech }
484
+ let(:utterance) { 'Hello world' }
485
+
486
+ it "should not alter the utterance" do
487
+ expect(result.utterance).to eq('Hello world')
488
+ end
489
+ end
490
+
491
+ context "with a single DTMF digit" do
492
+ context 'with dtmf- prefixes' do
493
+ let(:utterance) { 'dtmf-3' }
494
+
495
+ it "removes dtmf- previxes" do
496
+ expect(result.utterance).to eq('3')
497
+ end
498
+ end
499
+
500
+ context 'with "star"' do
501
+ let(:utterance) { "dtmf-star" }
502
+
503
+ it "interprets as *" do
504
+ expect(result.utterance).to eq('*')
505
+ end
506
+ end
507
+
508
+ context 'with "*"' do
509
+ let(:utterance) { '*' }
510
+
511
+ it "interprets as *" do
512
+ expect(result.utterance).to eq('*')
513
+ end
514
+ end
515
+
516
+ context 'with "pound"' do
517
+ let(:utterance) { 'dtmf-pound' }
518
+
519
+ it "interprets pound as #" do
520
+ expect(result.utterance).to eq('#')
521
+ end
522
+ end
523
+
524
+ context 'with "#"' do
525
+ let(:utterance) { '#' }
526
+
527
+ it "interprets # as #" do
528
+ expect(result.utterance).to eq('#')
529
+ end
530
+ end
531
+
532
+ context 'without a dtmf- prefix' do
533
+ let(:utterance) { '1' }
534
+
535
+ it "correctly interprets the digits" do
536
+ expect(result.utterance).to eq('1')
537
+ end
538
+ end
539
+
540
+ context 'with "star"' do
541
+ let(:utterance) { nil }
542
+
543
+ it "is nil when utterance is nil" do
544
+ expect(result.utterance).to eq(nil)
545
+ end
546
+ end
547
+ end
548
+
549
+ context "with multiple digits separated by spaces" do
550
+ let(:utterance) { '1 dtmf-5 dtmf-star # 2' }
551
+
552
+ it "returns the digits without space separation" do
553
+ expect(result.utterance).to eq('15*#2')
554
+ end
555
+ end
556
+ end
557
+
558
+ context "that is a nomatch" do
559
+ let(:reason) { Adhearsion::Rayo::Component::Input::Complete::NoMatch.new }
560
+
561
+ it "returns :nomatch status and a nil utterance" do
562
+ expect(result.status).to eql(:nomatch)
563
+ expect(result).not_to be_match
564
+ expect(result.utterance).to be_nil
565
+ end
566
+ end
567
+
568
+ context "that is a noinput" do
569
+ let(:reason) { Adhearsion::Rayo::Component::Input::Complete::NoInput.new }
570
+
571
+ it "returns :noinput status and a nil utterance" do
572
+ expect(result.status).to eql(:noinput)
573
+ expect(result).not_to be_match
574
+ expect(result.utterance).to be_nil
575
+ end
576
+ end
577
+
578
+ context "that is a hangup" do
579
+ let(:reason) { Adhearsion::Event::Complete::Hangup.new }
580
+
581
+ it "returns :hangup status and a nil utterance" do
582
+ expect(result.status).to eql(:hangup)
583
+ expect(result).not_to be_match
584
+ expect(result.utterance).to be_nil
585
+ end
586
+ end
587
+
588
+ context "that is a stop" do
589
+ let(:reason) { Adhearsion::Event::Complete::Stop.new }
590
+
591
+ it "returns :stop status and a nil utterance" do
592
+ expect(result.status).to eql(:stop)
593
+ expect(result).not_to be_match
594
+ expect(result.utterance).to be_nil
595
+ end
596
+ end
597
+
598
+ context "that is an error" do
599
+ let(:reason) { Adhearsion::Event::Complete::Error.new details: 'foobar' }
600
+
601
+ it "should raise an error with a message of 'foobar" do
602
+ expect { subject.ask prompts, limit: 5 }.to raise_error(Adhearsion::CallController::Input::InputError, /foobar/)
603
+ end
604
+ end
250
605
  end
606
+ end
251
607
 
252
- context "if a digit limit is specified" do
253
- it "should raise an ArgumentError"
608
+ describe "#menu" do
609
+ context "with no block" do
610
+ it "should raise ArgumentError" do
611
+ expect { subject.menu }.to raise_error(ArgumentError, /specify a block to build the menu/)
612
+ end
254
613
  end
255
614
 
256
- context "if a terminator digit is specified" do
257
- it "should raise an ArgumentError"
615
+ context "with no matches" do
616
+ it "should raise ArgumentError" do
617
+ expect do
618
+ subject.menu "Hello?" do
619
+ end
620
+ end.to raise_error(ArgumentError, /specify one or more matches/)
621
+ end
258
622
  end
259
623
 
260
- end#describe
624
+ context "with several matches specified" do
625
+ let :expected_grxml do
626
+ RubySpeech::GRXML.draw mode: 'dtmf', root: 'options', tag_format: 'semantics/1.0-literals' do
627
+ rule id: 'options', scope: 'public' do
628
+ item do
629
+ one_of do
630
+ item do
631
+ tag { '0' }
632
+ '1'
633
+ end
634
+ end
635
+ end
636
+ end
637
+ end
638
+ end
261
639
 
262
- describe "#ask" do
263
- let(:sound_files) { ["press", "button"] }
640
+ let(:foo) { :bar }
264
641
 
265
- context "mocking out the menu" do
266
- let(:menu_instance) { MenuDSL::Menu.new :limit => 2 }
642
+ it "makes the block context available" do
643
+ expect_component_execution expected_prompt
644
+ doo = nil
645
+ subject.menu prompts do
646
+ doo = foo
647
+ match(1) { do_nothing }
648
+ end
649
+ expect(doo).to eq(:bar)
650
+ end
267
651
 
268
- let(:result_done) { MenuDSL::Menu::MenuResultDone.new }
269
- let(:result_terminated) { MenuDSL::Menu::MenuTerminated.new }
270
- let(:result_limit_reached) { MenuDSL::Menu::MenuLimitReached.new }
271
- let(:result_invalid) { MenuDSL::Menu::MenuResultInvalid.new }
272
- let(:result_get_another_or_timeout) { MenuDSL::Menu::MenuGetAnotherDigitOrTimeout.new }
273
- let(:result_get_another_or_finish) { MenuDSL::Menu::MenuGetAnotherDigitOrFinish.new(:match_object, :new_extension) }
274
- let(:result_found) { MenuDSL::Menu::MenuResultFound.new(:match_object, :new_extension) }
652
+ context "with nil prompts" do
653
+ let(:prompts) { [nil, 'http://example.com/nice-to-meet-you.mp3', 'http://example.com/press-some-buttons.mp3'] }
275
654
 
276
- before do
277
- menu_instance
278
- expect(MenuDSL::Menu).to receive(:new).and_return(menu_instance)
655
+ it "executes a Prompt component with the correct prompts and grammar" do
656
+ expect_component_execution expected_prompt
657
+ subject.menu prompts do
658
+ match(1) {}
659
+ end
660
+ end
279
661
  end
280
662
 
281
- it "logs a warning about dropped DTMF" do
282
- expect(menu_instance).to receive(:continue).and_return(result_done)
663
+ context "with no prompts" do
664
+ it "executes an Input component with the correct grammar" do
665
+ allow_any_instance_of(Adhearsion::Rayo::Component::Input).to receive(:complete_event).and_return(double(reason: reason))
666
+ expect_component_execution Adhearsion::Rayo::Component::Input.new(expected_input_options)
667
+ subject.menu do
668
+ match(1) {}
669
+ end
670
+ end
671
+ end
283
672
 
284
- expect(call.logger).to receive(:warn).with(/deprecated/)
285
- subject.ask sound_files
673
+ context "with only nil prompts" do
674
+ it "executes an Input component with the correct grammar" do
675
+ allow_any_instance_of(Adhearsion::Rayo::Component::Input).to receive(:complete_event).and_return(double(reason: reason))
676
+ expect_component_execution Adhearsion::Rayo::Component::Input.new(expected_input_options)
677
+ subject.menu nil do
678
+ match(1) {}
679
+ end
680
+ end
286
681
  end
287
682
 
288
- it "exits the function if MenuResultDone" do
289
- expect(menu_instance).to receive(:continue).and_return(result_done)
290
- result = subject.ask sound_files
291
- expect(result.menu).to be menu_instance
292
- expect(result.response).to eq('')
683
+ context "with interruptible: false" do
684
+ let(:expected_barge_in) { false }
685
+
686
+ it "executes a Prompt with barge-in disabled" do
687
+ expect_component_execution expected_prompt
688
+
689
+ subject.menu prompts, interruptible: false do
690
+ match(1) {}
691
+ end
692
+ end
293
693
  end
294
694
 
295
- it "exits the function if MenuTerminated" do
296
- expect(menu_instance).to receive(:continue).and_return(result_terminated)
297
- result = subject.ask sound_files
298
- expect(result.menu).to be menu_instance
299
- expect(result.response).to eq('')
695
+ context "with a timeout specified" do
696
+ before do
697
+ expected_input_options.merge! initial_timeout: 10000,
698
+ inter_digit_timeout: 10000,
699
+ max_silence: 10000
700
+ end
701
+
702
+ it "executes a Prompt with correct timeout (initial, inter-digit & max-silence)" do
703
+ expect_component_execution expected_prompt
704
+
705
+ subject.menu prompts, timeout: 10, inter_digit_timeout: 10 do
706
+ match(1) {}
707
+ end
708
+ end
300
709
  end
301
710
 
302
- it "exits the function if MenuLimitReached" do
303
- expect(menu_instance).to receive(:continue).and_return(result_limit_reached)
304
- result = subject.ask sound_files
305
- expect(result.menu).to be menu_instance
306
- expect(result.response).to eq('')
711
+ context "with a different default timeout" do
712
+ before do
713
+ expected_input_options.merge! initial_timeout: 10000,
714
+ max_silence: 10000
715
+ end
716
+
717
+ temp_config_value :timeout, 10
718
+
719
+ it "executes a Prompt with correct timeout (initial & max-silence)" do
720
+ expect_component_execution expected_prompt
721
+
722
+ subject.menu prompts do
723
+ match(1) {}
724
+ end
725
+ end
307
726
  end
308
727
 
309
- it "plays audio, then executes timeout hook if input times out" do
310
- expect(menu_instance).to receive(:continue).and_return(result_get_another_or_timeout)
311
- expect(subject).to receive(:play_sound_files_for_menu).once.with(menu_instance, sound_files).and_return(nil)
312
- result = subject.ask sound_files
313
- expect(result.menu).to be menu_instance
314
- expect(result.response).to eq('')
315
- expect(result.status).to be :timeout
728
+ context "with a different default inter-digit timeout" do
729
+ before do
730
+ expected_input_options.merge! inter_digit_timeout: 10000
731
+ end
732
+
733
+ temp_config_value :inter_digit_timeout, 10
734
+
735
+ it "executes a Prompt with correct timeout (inter-digit)" do
736
+ expect_component_execution expected_prompt
737
+
738
+ subject.menu prompts do
739
+ match(1) {}
740
+ end
741
+ end
316
742
  end
317
743
 
318
- it "plays audio, then adds digit to digit buffer if input is received" do
319
- expect(menu_instance).to receive(:continue).and_return(result_get_another_or_timeout, result_done)
320
- expect(subject).to receive(:play_sound_files_for_menu).once.with(menu_instance, sound_files).and_return("1")
321
- expect(menu_instance).to receive(:<<).with("1").once
322
- subject.ask sound_files
744
+ context "with a different default minimum confidence" do
745
+ before do
746
+ expected_input_options.merge! min_confidence: 0.8
747
+ end
748
+
749
+ temp_config_value :min_confidence, 0.8
750
+
751
+ it "executes a Prompt with correct minimum confidence" do
752
+ expect_component_execution expected_prompt
753
+
754
+ subject.menu prompts do
755
+ match(1) {}
756
+ end
757
+ end
323
758
  end
324
- end
325
759
 
326
- context "with a block passed" do
327
- it "validates the buffer using that block, terminating on something truthy" do
328
- expect(subject).to receive(:play_sound_files_for_menu).twice.and_return("1")
329
- result = subject.ask sound_files, limit: 3 do |buffer|
330
- buffer == '11'
760
+ context "with a different default recognizer" do
761
+ before do
762
+ expected_input_options.merge! recognizer: 'something_else'
763
+ end
764
+
765
+ temp_config_value :recognizer, 'something_else'
766
+
767
+ it "executes a Prompt with correct recognizer" do
768
+ expect_component_execution expected_prompt
769
+
770
+ subject.menu prompts do
771
+ match(1) {}
772
+ end
331
773
  end
332
- expect(result.status).to eq(:validator_terminated)
333
- expect(result.response).to eq('11')
334
774
  end
335
- end
336
775
 
337
- end#describe
776
+ context "with a different default input language" do
777
+ before do
778
+ expected_input_options.merge! language: 'pt-BR'
779
+ end
780
+
781
+ temp_config_value :input_language, 'pt-BR'
782
+
783
+ it "executes a Prompt with correct input language" do
784
+ expect_component_execution expected_prompt
785
+
786
+ subject.menu prompts do
787
+ match(1) {}
788
+ end
789
+ end
790
+ end
791
+
792
+ context "with a different default output renderer" do
793
+ before do
794
+ expected_output_options.merge! renderer: 'something_else'
795
+ end
796
+
797
+ temp_config_value :default_renderer, 'something_else', Adhearsion.config.core.media
798
+
799
+ it "executes a Prompt with correct renderer" do
800
+ expect_component_execution expected_prompt
801
+
802
+ subject.menu prompts do
803
+ match(1) {}
804
+ end
805
+ end
806
+ end
807
+
808
+ context "with a different default output voice" do
809
+ before do
810
+ expected_output_options.merge! voice: 'something_else'
811
+ end
812
+
813
+ temp_config_value :default_voice, 'something_else', Adhearsion.config.core.media
814
+
815
+ it "executes a Prompt with correct voice" do
816
+ expect_component_execution expected_prompt
817
+
818
+ subject.menu prompts do
819
+ match(1) {}
820
+ end
821
+ end
822
+ end
823
+
824
+ context "with overridden input options" do
825
+ before do
826
+ expected_input_options.merge! inter_digit_timeout: 35000
827
+ end
828
+
829
+ it "executes a Prompt with correct input options" do
830
+ expect_component_execution expected_prompt
831
+
832
+ subject.menu prompts, input_options: {inter_digit_timeout: 35000} do
833
+ match(1) {}
834
+ end
835
+ end
836
+ end
837
+
838
+ context "with overridden output options" do
839
+ before do
840
+ expected_output_options.merge! max_time: 35000
841
+ end
842
+
843
+ it "executes a Prompt with correct output options" do
844
+ expect_component_execution expected_prompt
845
+
846
+ subject.menu prompts, output_options: {max_time: 35000} do
847
+ match(1) {}
848
+ end
849
+ end
850
+ end
851
+
852
+ context "when using ASR mode" do
853
+ before do
854
+ expected_input_options.merge! mode: :voice
855
+ end
856
+
857
+ let :expected_grxml do
858
+ RubySpeech::GRXML.draw mode: 'voice', root: 'options', tag_format: 'semantics/1.0-literals' do
859
+ rule id: 'options', scope: 'public' do
860
+ item do
861
+ one_of do
862
+ item do
863
+ tag { '0' }
864
+ 'Hello world'
865
+ end
866
+ end
867
+ end
868
+ end
869
+ end
870
+ end
871
+
872
+ it "executes a Prompt with correct input mode, and the correct grammar mode" do
873
+ expect_component_execution expected_prompt
874
+
875
+ subject.menu prompts, mode: :voice do
876
+ match("Hello world") {}
877
+ end
878
+ end
879
+ end
880
+
881
+ context "when the call is dead when trying to execute the prompt" do
882
+ before { call.terminate }
883
+
884
+ it "should raise Adhearsion::Call::Hangup" do
885
+ expect do
886
+ subject.menu prompts do
887
+ match(1) {}
888
+ end
889
+ end.to raise_error Adhearsion::Call::Hangup
890
+ end
891
+ end
892
+
893
+ context "when input completes with an error" do
894
+ let(:reason) { Adhearsion::Event::Complete::Error.new details: 'foobar' }
895
+
896
+ it "should raise an error with a message of 'foobar'" do
897
+ expect_component_execution expected_prompt
898
+
899
+ expect do
900
+ subject.menu prompts do
901
+ match(1) {}
902
+ end
903
+ end.to raise_error(Adhearsion::CallController::Input::InputError, /foobar/)
904
+ end
905
+ end
906
+
907
+ context "when input doesn't match any of the specified matches" do
908
+ it "runs the invalid and failure handlers" do
909
+ expect_component_execution expected_prompt
910
+ expect(self).to receive(:do_something_on_invalid).once.ordered
911
+ expect(self).to receive(:do_something_on_failure).once.ordered
912
+
913
+ subject.menu prompts do
914
+ match(1) {}
915
+
916
+ invalid { do_something_on_invalid }
917
+ failure { do_something_on_failure }
918
+ end
919
+ end
920
+
921
+ context "when allowed multiple tries" do
922
+ let :nlsml do
923
+ RubySpeech::NLSML.draw do
924
+ interpretation confidence: 1 do
925
+ input '1', mode: :dtmf
926
+ instance '0'
927
+ end
928
+ end
929
+ end
930
+
931
+ let(:reason2) { Adhearsion::Rayo::Component::Input::Complete::Match.new nlsml: nlsml }
932
+
933
+ it "executes the prompt repeatedly until it gets a match" do
934
+ some_controller_class = Class.new Adhearsion::CallController
935
+
936
+ expect_component_execution(expected_prompt).twice
937
+ expect(self).to receive(:do_something_on_invalid).once.ordered
938
+ expect(self).to receive(:invoke).once.with(some_controller_class, extension: '1').ordered
939
+ expect(self).to receive(:do_something_on_failure).never
940
+
941
+ invocation_count = 0
942
+ allow_any_instance_of(Adhearsion::Rayo::Component::Prompt).to receive(:complete_event) do
943
+ invocation_count += 1
944
+ case invocation_count
945
+ when 1 then double(reason: reason)
946
+ when 2 then double(reason: reason2)
947
+ else raise('Too many attempts')
948
+ end
949
+ end
950
+
951
+ subject.menu prompts, tries: 3 do
952
+ match 1, some_controller_class
953
+
954
+ invalid { do_something_on_invalid }
955
+ failure { do_something_on_failure }
956
+ end
957
+ end
958
+ end
959
+ end
960
+
961
+ context "when we don't get any input" do
962
+ let(:reason) { Adhearsion::Rayo::Component::Input::Complete::NoInput.new }
963
+
964
+ it "runs the timeout and failure handlers" do
965
+ expect_component_execution expected_prompt
966
+ expect(self).to receive(:do_something_on_timeout).once.ordered
967
+ expect(self).to receive(:do_something_on_failure).once.ordered
968
+
969
+ subject.menu prompts do
970
+ match(1) {}
338
971
 
339
- end#shared
972
+ timeout { do_something_on_timeout }
973
+ failure { do_something_on_failure }
974
+ end
975
+ end
976
+
977
+ context "when allowed multiple tries" do
978
+ let :nlsml do
979
+ RubySpeech::NLSML.draw do
980
+ interpretation confidence: 1 do
981
+ input '1', mode: :dtmf
982
+ instance '0'
983
+ end
984
+ end
985
+ end
986
+
987
+ let(:reason2) { Adhearsion::Rayo::Component::Input::Complete::Match.new nlsml: nlsml }
988
+
989
+ it "executes the prompt repeatedly until it gets a match" do
990
+ some_controller_class = Class.new Adhearsion::CallController
991
+
992
+ expect_component_execution(expected_prompt).twice
993
+ expect(self).to receive(:do_something_on_timeout).once.ordered
994
+ expect(self).to receive(:invoke).once.with(some_controller_class, extension: '1').ordered
995
+ expect(self).to receive(:do_something_on_failure).never
996
+
997
+ invocation_count = 0
998
+ allow_any_instance_of(Adhearsion::Rayo::Component::Prompt).to receive(:complete_event) do
999
+ invocation_count += 1
1000
+ case invocation_count
1001
+ when 1 then double(reason: reason)
1002
+ when 2 then double(reason: reason2)
1003
+ else raise('Too many attempts')
1004
+ end
1005
+ end
1006
+
1007
+ subject.menu prompts, tries: 3 do
1008
+ match 1, some_controller_class
1009
+
1010
+ timeout { do_something_on_timeout }
1011
+ failure { do_something_on_failure }
1012
+ end
1013
+ end
1014
+ end
1015
+ end
1016
+
1017
+ context "when the input unambiguously matches a specified match" do
1018
+ let :expected_grxml do
1019
+ RubySpeech::GRXML.draw mode: 'dtmf', root: 'options', tag_format: 'semantics/1.0-literals' do
1020
+ rule id: 'options', scope: 'public' do
1021
+ item do
1022
+ one_of do
1023
+ item do
1024
+ tag { '0' }
1025
+ '2'
1026
+ end
1027
+ item do
1028
+ tag { '1' }
1029
+ '1'
1030
+ end
1031
+ item do
1032
+ tag { '2' }
1033
+ '3'
1034
+ end
1035
+ end
1036
+ end
1037
+ end
1038
+ end
1039
+ end
1040
+
1041
+ let :nlsml do
1042
+ RubySpeech::NLSML.draw do
1043
+ interpretation confidence: 1 do
1044
+ input '3', mode: :dtmf
1045
+ instance '2'
1046
+ end
1047
+ end
1048
+ end
1049
+
1050
+ let(:reason) { Adhearsion::Rayo::Component::Input::Complete::Match.new nlsml: nlsml }
1051
+
1052
+ context "which specifies a controller class" do
1053
+ it "invokes the specfied controller, with the matched input as the :extension key in its metadata" do
1054
+ some_controller_class = Class.new Adhearsion::CallController
1055
+
1056
+ expect_component_execution expected_prompt
1057
+ expect(self).to receive(:invoke).once.with(some_controller_class, extension: '3')
1058
+
1059
+ subject.menu prompts do
1060
+ match(2) {}
1061
+ match(1) {}
1062
+ match 3, some_controller_class
1063
+ end
1064
+ end
1065
+ end
1066
+
1067
+ context "which specifies a block to be run" do
1068
+ it "invokes the block, passing in the input that matched" do
1069
+ expect_component_execution expected_prompt
1070
+ expect(self).to receive(:do_something_on_match).once.with('3')
1071
+
1072
+ subject.menu prompts do
1073
+ match(2) {}
1074
+ match(1) {}
1075
+ match(3) { |v| do_something_on_match v }
1076
+ end
1077
+ end
1078
+ end
1079
+
1080
+ context "when the match was a set of options" do
1081
+ let :expected_grxml do
1082
+ RubySpeech::GRXML.draw mode: 'dtmf', root: 'options', tag_format: 'semantics/1.0-literals' do
1083
+ rule id: 'options', scope: 'public' do
1084
+ item do
1085
+ one_of do
1086
+ item do
1087
+ tag { '0' }
1088
+ '0'
1089
+ end
1090
+ item do
1091
+ tag { '1' }
1092
+ '1'
1093
+ end
1094
+ item do
1095
+ tag { '2' }
1096
+ one_of do
1097
+ item { '2' }
1098
+ item { '3' }
1099
+ end
1100
+ end
1101
+ end
1102
+ end
1103
+ end
1104
+ end
1105
+ end
1106
+
1107
+ it "invokes the match payload" do
1108
+ expect_component_execution expected_prompt
1109
+ expect(self).to receive(:do_something_on_match).once.with('3')
1110
+
1111
+ subject.menu prompts do
1112
+ match(0) {}
1113
+ match(1) {}
1114
+ match(2,3) { |v| do_something_on_match v }
1115
+ end
1116
+ end
1117
+ end
1118
+
1119
+ context "when the match was a range" do
1120
+ let :expected_grxml do
1121
+ RubySpeech::GRXML.draw mode: 'dtmf', root: 'options', tag_format: 'semantics/1.0-literals' do
1122
+ rule id: 'options', scope: 'public' do
1123
+ item do
1124
+ one_of do
1125
+ item do
1126
+ tag { '0' }
1127
+ '0'
1128
+ end
1129
+ item do
1130
+ tag { '1' }
1131
+ '1'
1132
+ end
1133
+ item do
1134
+ tag { '2' }
1135
+ one_of do
1136
+ item { '2' }
1137
+ item { '3' }
1138
+ end
1139
+ end
1140
+ end
1141
+ end
1142
+ end
1143
+ end
1144
+ end
1145
+
1146
+ it "invokes the match payload" do
1147
+ expect_component_execution expected_prompt
1148
+ expect(self).to receive(:do_something_on_match).once.with('3')
1149
+
1150
+ subject.menu prompts do
1151
+ match(0) {}
1152
+ match(1) {}
1153
+ match(2..3) { |v| do_something_on_match v }
1154
+ end
1155
+ end
1156
+ end
1157
+
1158
+ context "when the match was an array of options" do
1159
+ let :expected_grxml do
1160
+ RubySpeech::GRXML.draw mode: 'dtmf', root: 'options', tag_format: 'semantics/1.0-literals' do
1161
+ rule id: 'options', scope: 'public' do
1162
+ item do
1163
+ one_of do
1164
+ item do
1165
+ tag { '0' }
1166
+ '0'
1167
+ end
1168
+ item do
1169
+ tag { '1' }
1170
+ '1'
1171
+ end
1172
+ item do
1173
+ tag { '2' }
1174
+ one_of do
1175
+ item { '2' }
1176
+ item { '3' }
1177
+ end
1178
+ end
1179
+ end
1180
+ end
1181
+ end
1182
+ end
1183
+ end
1184
+
1185
+ it "invokes the match payload" do
1186
+ expect_component_execution expected_prompt
1187
+ expect(self).to receive(:do_something_on_match).once.with('3')
1188
+
1189
+ subject.menu prompts do
1190
+ match(0) {}
1191
+ match(1) {}
1192
+ match([2,3]) { |v| do_something_on_match v }
1193
+ end
1194
+ end
1195
+ end
1196
+ end
1197
+
1198
+ context "when the input abmiguously matches multiple specified matches" do
1199
+ let :expected_grxml do
1200
+ RubySpeech::GRXML.draw mode: 'dtmf', root: 'options', tag_format: 'semantics/1.0-literals' do
1201
+ rule id: 'options', scope: 'public' do
1202
+ item do
1203
+ one_of do
1204
+ item do
1205
+ tag { '0' }
1206
+ '1'
1207
+ end
1208
+ item do
1209
+ tag { '1' }
1210
+ '1'
1211
+ end
1212
+ end
1213
+ end
1214
+ end
1215
+ end
1216
+ end
1217
+
1218
+ let :nlsml do
1219
+ RubySpeech::NLSML.draw do
1220
+ interpretation confidence: 1 do
1221
+ input '1', mode: :dtmf
1222
+ instance '0'
1223
+ instance '1'
1224
+ end
1225
+ end
1226
+ end
1227
+
1228
+ let(:reason) { Adhearsion::Rayo::Component::Input::Complete::Match.new nlsml: nlsml }
1229
+
1230
+ it "executes the first successful match" do
1231
+ expect_component_execution expected_prompt
1232
+ expect(self).to receive(:do_something_on_match).once.with('1')
1233
+ expect(self).to receive(:do_otherthing_on_match).never
1234
+
1235
+ subject.menu prompts do
1236
+ match(1) { |v| do_something_on_match v }
1237
+ match(1) { |v| do_otherthing_on_match v }
1238
+ end
1239
+ end
1240
+ end
1241
+ end
1242
+ end
1243
+ end
340
1244
  end
341
1245
  end