punchblock 1.9.4 → 2.0.0.beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +1 -2
  4. data/CHANGELOG.md +17 -0
  5. data/Gemfile +1 -0
  6. data/Guardfile +4 -0
  7. data/README.markdown +6 -0
  8. data/Rakefile +16 -0
  9. data/benchmarks/ami_event_name_comparison.rb +14 -0
  10. data/benchmarks/channel.rb +27 -0
  11. data/lib/punchblock/client.rb +2 -6
  12. data/lib/punchblock/command/accept.rb +3 -24
  13. data/lib/punchblock/command/answer.rb +3 -24
  14. data/lib/punchblock/command/dial.rb +24 -76
  15. data/lib/punchblock/command/hangup.rb +3 -19
  16. data/lib/punchblock/command/join.rb +21 -70
  17. data/lib/punchblock/command/mute.rb +3 -3
  18. data/lib/punchblock/command/redirect.rb +6 -39
  19. data/lib/punchblock/command/reject.rb +14 -54
  20. data/lib/punchblock/command/unjoin.rb +8 -40
  21. data/lib/punchblock/command/unmute.rb +3 -3
  22. data/lib/punchblock/command_node.rb +0 -17
  23. data/lib/punchblock/component/asterisk/agi/command.rb +20 -127
  24. data/lib/punchblock/component/asterisk/ami/action.rb +30 -117
  25. data/lib/punchblock/component/component_node.rb +1 -1
  26. data/lib/punchblock/component/input.rb +89 -268
  27. data/lib/punchblock/component/output.rb +106 -154
  28. data/lib/punchblock/component/prompt.rb +51 -0
  29. data/lib/punchblock/component/record.rb +41 -130
  30. data/lib/punchblock/component.rb +1 -0
  31. data/lib/punchblock/connection/asterisk.rb +31 -4
  32. data/lib/punchblock/connection/xmpp.rb +6 -14
  33. data/lib/punchblock/core_ext/blather/stanza.rb +1 -1
  34. data/lib/punchblock/event/active_speaker.rb +2 -10
  35. data/lib/punchblock/event/answered.rb +3 -3
  36. data/lib/punchblock/event/asterisk/ami/event.rb +15 -47
  37. data/lib/punchblock/event/complete.rb +26 -48
  38. data/lib/punchblock/event/dtmf.rb +3 -13
  39. data/lib/punchblock/event/end.rb +10 -11
  40. data/lib/punchblock/event/joined.rb +5 -25
  41. data/lib/punchblock/event/offer.rb +4 -25
  42. data/lib/punchblock/event/ringing.rb +3 -3
  43. data/lib/punchblock/event/unjoined.rb +5 -25
  44. data/lib/punchblock/event.rb +0 -10
  45. data/lib/punchblock/has_headers.rb +20 -26
  46. data/lib/punchblock/rayo_node.rb +46 -23
  47. data/lib/punchblock/ref.rb +39 -18
  48. data/lib/punchblock/translator/asterisk/agi_app.rb +15 -0
  49. data/lib/punchblock/translator/asterisk/agi_command.rb +3 -1
  50. data/lib/punchblock/translator/asterisk/ami_error_converter.rb +20 -0
  51. data/lib/punchblock/translator/asterisk/call.rb +60 -39
  52. data/lib/punchblock/translator/asterisk/channel.rb +41 -0
  53. data/lib/punchblock/translator/asterisk/component/asterisk/agi_command.rb +4 -1
  54. data/lib/punchblock/translator/asterisk/component/asterisk/ami_action.rb +4 -4
  55. data/lib/punchblock/translator/asterisk/component/composed_prompt.rb +62 -0
  56. data/lib/punchblock/translator/asterisk/component/input.rb +1 -0
  57. data/lib/punchblock/translator/asterisk/component/mrcp_native_prompt.rb +56 -0
  58. data/lib/punchblock/translator/asterisk/component/mrcp_prompt.rb +53 -0
  59. data/lib/punchblock/translator/asterisk/component/mrcp_recog_prompt.rb +99 -0
  60. data/lib/punchblock/translator/asterisk/component/output.rb +30 -22
  61. data/lib/punchblock/translator/asterisk/component/record.rb +8 -6
  62. data/lib/punchblock/translator/asterisk/component.rb +6 -5
  63. data/lib/punchblock/translator/asterisk/unimrcp_app.rb +26 -0
  64. data/lib/punchblock/translator/asterisk.rb +24 -28
  65. data/lib/punchblock/translator/dtmf_recognizer.rb +39 -20
  66. data/lib/punchblock/translator/freeswitch/call.rb +15 -14
  67. data/lib/punchblock/translator/freeswitch/component/abstract_output.rb +5 -4
  68. data/lib/punchblock/translator/freeswitch/component/flite_output.rb +1 -1
  69. data/lib/punchblock/translator/freeswitch/component/input.rb +5 -0
  70. data/lib/punchblock/translator/freeswitch/component/output.rb +2 -2
  71. data/lib/punchblock/translator/freeswitch/component/record.rb +19 -13
  72. data/lib/punchblock/translator/freeswitch/component/tts_output.rb +2 -2
  73. data/lib/punchblock/translator/freeswitch/component.rb +2 -5
  74. data/lib/punchblock/translator/freeswitch.rb +2 -2
  75. data/lib/punchblock/translator/input_component.rb +33 -13
  76. data/lib/punchblock/uri_list.rb +21 -0
  77. data/lib/punchblock/version.rb +1 -1
  78. data/lib/punchblock.rb +4 -3
  79. data/punchblock.gemspec +7 -3
  80. data/spec/punchblock/client/component_registry_spec.rb +1 -1
  81. data/spec/punchblock/client_spec.rb +10 -26
  82. data/spec/punchblock/command/accept_spec.rb +41 -7
  83. data/spec/punchblock/command/answer_spec.rb +51 -7
  84. data/spec/punchblock/command/dial_spec.rb +56 -14
  85. data/spec/punchblock/command/hangup_spec.rb +41 -7
  86. data/spec/punchblock/command/join_spec.rb +53 -11
  87. data/spec/punchblock/command/mute_spec.rb +19 -4
  88. data/spec/punchblock/command/redirect_spec.rb +40 -10
  89. data/spec/punchblock/command/reject_spec.rb +43 -11
  90. data/spec/punchblock/command/unjoin_spec.rb +40 -9
  91. data/spec/punchblock/command/unmute_spec.rb +19 -4
  92. data/spec/punchblock/command_node_spec.rb +0 -4
  93. data/spec/punchblock/component/asterisk/agi/command_spec.rb +16 -39
  94. data/spec/punchblock/component/asterisk/ami/action_spec.rb +50 -53
  95. data/spec/punchblock/component/component_node_spec.rb +3 -5
  96. data/spec/punchblock/component/input_spec.rb +194 -61
  97. data/spec/punchblock/component/output_spec.rb +194 -62
  98. data/spec/punchblock/component/prompt_spec.rb +132 -0
  99. data/spec/punchblock/component/record_spec.rb +70 -32
  100. data/spec/punchblock/connection/asterisk_spec.rb +17 -3
  101. data/spec/punchblock/connection/freeswitch_spec.rb +4 -4
  102. data/spec/punchblock/connection/xmpp_spec.rb +20 -38
  103. data/spec/punchblock/event/answered_spec.rb +12 -10
  104. data/spec/punchblock/event/asterisk/ami/event_spec.rb +27 -22
  105. data/spec/punchblock/event/complete_spec.rb +15 -19
  106. data/spec/punchblock/event/dtmf_spec.rb +5 -6
  107. data/spec/punchblock/event/end_spec.rb +20 -10
  108. data/spec/punchblock/event/joined_spec.rb +8 -7
  109. data/spec/punchblock/event/offer_spec.rb +41 -12
  110. data/spec/punchblock/event/ringing_spec.rb +12 -10
  111. data/spec/punchblock/event/started_speaking_spec.rb +5 -6
  112. data/spec/punchblock/event/stopped_speaking_spec.rb +5 -6
  113. data/spec/punchblock/event/unjoined_spec.rb +7 -7
  114. data/spec/punchblock/ref_spec.rb +86 -9
  115. data/spec/punchblock/translator/asterisk/call_spec.rb +317 -154
  116. data/spec/punchblock/translator/asterisk/component/asterisk/agi_command_spec.rb +28 -5
  117. data/spec/punchblock/translator/asterisk/component/asterisk/ami_action_spec.rb +15 -13
  118. data/spec/punchblock/translator/asterisk/component/composed_prompt_spec.rb +237 -0
  119. data/spec/punchblock/translator/asterisk/component/input_spec.rb +171 -14
  120. data/spec/punchblock/translator/asterisk/component/mrcp_native_prompt_spec.rb +652 -0
  121. data/spec/punchblock/translator/asterisk/component/mrcp_prompt_spec.rb +646 -0
  122. data/spec/punchblock/translator/asterisk/component/output_spec.rb +127 -77
  123. data/spec/punchblock/translator/asterisk/component/record_spec.rb +17 -8
  124. data/spec/punchblock/translator/asterisk/component/stop_by_redirect_spec.rb +2 -2
  125. data/spec/punchblock/translator/asterisk/component_spec.rb +3 -7
  126. data/spec/punchblock/translator/asterisk_spec.rb +20 -24
  127. data/spec/punchblock/translator/freeswitch/call_spec.rb +103 -99
  128. data/spec/punchblock/translator/freeswitch/component/flite_output_spec.rb +17 -8
  129. data/spec/punchblock/translator/freeswitch/component/input_spec.rb +26 -14
  130. data/spec/punchblock/translator/freeswitch/component/output_spec.rb +30 -52
  131. data/spec/punchblock/translator/freeswitch/component/record_spec.rb +23 -19
  132. data/spec/punchblock/translator/freeswitch/component/tts_output_spec.rb +18 -8
  133. data/spec/punchblock/translator/freeswitch/component_spec.rb +4 -8
  134. data/spec/punchblock/translator/freeswitch_spec.rb +11 -14
  135. data/spec/punchblock/uri_list_spec.rb +49 -0
  136. data/spec/punchblock_spec.rb +11 -1
  137. data/spec/spec_helper.rb +7 -11
  138. data/spec/support/mock_connection_with_event_handler.rb +1 -1
  139. metadata +104 -24
  140. data/lib/punchblock/header.rb +0 -9
  141. data/lib/punchblock/key_value_pair_node.rb +0 -51
  142. data/spec/punchblock/header_spec.rb +0 -11
@@ -8,7 +8,7 @@ module Punchblock
8
8
  module AMI
9
9
  describe Action do
10
10
  it 'registers itself' do
11
- RayoNode.class_from_registration(:action, 'urn:xmpp:rayo:asterisk:ami:1').should be == Action
11
+ RayoNode.class_from_registration(:action, 'urn:xmpp:rayo:asterisk:ami:1').should be == described_class
12
12
  end
13
13
 
14
14
  describe "from a stanza" do
@@ -27,31 +27,21 @@ module Punchblock
27
27
  MESSAGE
28
28
  end
29
29
 
30
- subject { RayoNode.import parse_stanza(stanza).root, '9f00061', '1' }
30
+ subject { RayoNode.from_xml parse_stanza(stanza).root, '9f00061', '1' }
31
31
 
32
- it { should be_instance_of Action }
32
+ it { should be_instance_of described_class }
33
33
 
34
34
  it_should_behave_like 'event'
35
35
 
36
36
  its(:name) { should be == 'Originate' }
37
- its(:params) { should be == [Action::Param.new('Channel', 'SIP/101test'),
38
- Action::Param.new('Context', 'default'),
39
- Action::Param.new('Exten', '8135551212'),
40
- Action::Param.new('Priority', '1'),
41
- Action::Param.new('Callerid', '3125551212'),
42
- Action::Param.new('Timeout', '30000'),
43
- Action::Param.new('Variable', 'var1=23|var2=24|var3=25'),
44
- Action::Param.new('Async', '1')
45
- ]}
46
-
47
- its(:params_hash) { should be == {:channel => 'SIP/101test',
48
- :context => 'default',
49
- :exten => '8135551212',
50
- :priority => '1',
51
- :callerid => '3125551212',
52
- :timeout => '30000',
53
- :variable => 'var1=23|var2=24|var3=25',
54
- :async => '1'} }
37
+ its(:params) { should be == { 'Channel' => 'SIP/101test',
38
+ 'Context' => 'default',
39
+ 'Exten' => '8135551212',
40
+ 'Priority' => '1',
41
+ 'Callerid' => '3125551212',
42
+ 'Timeout' => '30000',
43
+ 'Variable' => 'var1=23|var2=24|var3=25',
44
+ 'Async' => '1'} }
55
45
  end
56
46
 
57
47
  describe "testing equality" do
@@ -76,22 +66,32 @@ module Punchblock
76
66
 
77
67
  describe "when setting options in initializer" do
78
68
  subject do
79
- Action.new :name => 'Originate',
80
- :params => { :channel => 'SIP/101test' }
69
+ described_class.new :name => 'Originate',
70
+ :params => { 'Channel' => 'SIP/101test' }
81
71
  end
82
72
 
83
- its(:name) { should be == 'Originate' }
84
- its(:params) { should be == [Action::Param.new(:channel, 'SIP/101test')]}
85
- its(:params_hash) { should be == { :channel => 'SIP/101test' } }
86
- end
73
+ its(:name) { should be == 'Originate' }
74
+ its(:params) { should be == { 'Channel' => 'SIP/101test' } }
75
+
76
+ describe "exporting to Rayo" do
77
+ it "should export to XML that can be understood by its parser" do
78
+ new_instance = RayoNode.from_xml subject.to_rayo
79
+ new_instance.should be_instance_of described_class
80
+ new_instance.name.should == 'Originate'
81
+ new_instance.params.should == { 'Channel' => 'SIP/101test' }
82
+ end
87
83
 
88
- class Action
89
- describe Param do
90
- let(:class_name) { Param }
91
- let(:element_name) { 'param' }
92
- it_should_behave_like 'key_value_pairs'
84
+ it "should render to a parent node if supplied" do
85
+ doc = Nokogiri::XML::Document.new
86
+ parent = Nokogiri::XML::Node.new 'foo', doc
87
+ doc.root = parent
88
+ rayo_doc = subject.to_rayo(parent)
89
+ rayo_doc.should == parent
90
+ end
93
91
  end
92
+ end
94
93
 
94
+ class Action
95
95
  class Complete
96
96
  describe Success do
97
97
  let :stanza do
@@ -99,6 +99,7 @@ module Punchblock
99
99
  <complete xmlns="urn:xmpp:rayo:ext:1">
100
100
  <success xmlns="urn:xmpp:rayo:asterisk:ami:complete:1">
101
101
  <message>Originate successfully queued</message>
102
+ <text-body>Some thing happened</text-body>
102
103
  <attribute name="Channel" value="SIP/101-3f3f"/>
103
104
  <attribute name="State" value="Ring"/>
104
105
  </success>
@@ -106,35 +107,31 @@ module Punchblock
106
107
  MESSAGE
107
108
  end
108
109
 
109
- subject { RayoNode.import(parse_stanza(stanza).root).reason }
110
+ subject { RayoNode.from_xml(parse_stanza(stanza).root).reason }
110
111
 
111
- it { should be_instance_of Success }
112
+ it { should be_instance_of described_class }
112
113
 
113
- its(:name) { should be == :success }
114
- its(:message) { should be == "Originate successfully queued" }
115
- its(:attributes) { should be == [Attribute.new('Channel', 'SIP/101-3f3f'), Attribute.new('State', 'Ring')]}
116
- its(:attributes_hash) { should be == {:channel => 'SIP/101-3f3f', :state => 'Ring'} }
114
+ its(:name) { should be == :success }
115
+ its(:message) { should be == "Originate successfully queued" }
116
+ its(:text_body) { should be == 'Some thing happened' }
117
+ its(:headers) { should be == {'Channel' => 'SIP/101-3f3f', 'State' => 'Ring'} }
118
+ its(:attributes) { should be == {'Channel' => 'SIP/101-3f3f', 'State' => 'Ring'} } # For BC
117
119
 
118
120
  describe "when setting options in initializer" do
119
121
  subject do
120
- Success.new :message => 'Originate successfully queued', :attributes => {:channel => 'SIP/101-3f3f', :state => 'Ring'}
122
+ described_class.new message: 'Originate successfully queued', text_body: 'Some thing happened', headers: {'Channel' => 'SIP/101-3f3f', 'State' => 'Ring'}
121
123
  end
122
124
 
123
- its(:message) { should be == 'Originate successfully queued' }
124
- its(:attributes) { should be == [Attribute.new(:channel, 'SIP/101-3f3f'), Attribute.new(:state, 'Ring')]}
125
- its(:attributes_hash) { should be == {:channel => 'SIP/101-3f3f', :state => 'Ring'} }
125
+ its(:message) { should be == 'Originate successfully queued' }
126
+ its(:text_body) { should be == 'Some thing happened' }
127
+ its(:headers) { should be == {'Channel' => 'SIP/101-3f3f', 'State' => 'Ring'} }
128
+ its(:attributes) { should be == {'Channel' => 'SIP/101-3f3f', 'State' => 'Ring'} } # For BC
126
129
  end
127
130
  end
128
-
129
- describe Attribute do
130
- let(:class_name) { Attribute }
131
- let(:element_name) { 'attribute' }
132
- it_should_behave_like 'key_value_pairs'
133
- end
134
131
  end
135
132
  end
136
- end # Action
137
- end # AMI
138
- end # Asterisk
139
- end # Component
140
- end # Punchblock
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
@@ -52,7 +52,7 @@ module Punchblock
52
52
  end
53
53
 
54
54
  describe "with an event handler set" do
55
- let(:handler) { mock 'Response' }
55
+ let(:handler) { double 'Response' }
56
56
 
57
57
  before do
58
58
  handler.should_receive(:call).once.with(event)
@@ -74,9 +74,7 @@ module Punchblock
74
74
  let(:component_id) { 'abc123' }
75
75
 
76
76
  let :ref do
77
- Ref.new.tap do |ref|
78
- ref.id = component_id
79
- end
77
+ Ref.new uri: component_id
80
78
  end
81
79
 
82
80
  it "should set the component ID from the ref" do
@@ -90,7 +88,7 @@ module Punchblock
90
88
  before do
91
89
  subject.request!
92
90
  subject.client = Client.new
93
- subject.response = Ref.new id: 'abc'
91
+ subject.response = Ref.new uri: 'abc'
94
92
  subject.client.find_component_by_id('abc').should be subject
95
93
  end
96
94
 
@@ -6,41 +6,101 @@ module Punchblock
6
6
  module Component
7
7
  describe Input do
8
8
  it 'registers itself' do
9
- RayoNode.class_from_registration(:input, 'urn:xmpp:rayo:input:1').should be == Input
9
+ RayoNode.class_from_registration(:input, 'urn:xmpp:rayo:input:1').should be == described_class
10
10
  end
11
11
 
12
12
  describe "when setting options in initializer" do
13
13
  subject do
14
- Input.new :grammar => {:value => '[5 DIGITS]', :content_type => 'application/grammar+custom'},
15
- :mode => :speech,
14
+ described_class.new grammar: {value: '[5 DIGITS]', content_type: 'application/grammar+custom'},
15
+ :mode => :voice,
16
16
  :terminator => '#',
17
17
  :max_silence => 1000,
18
- :recognizer => 'en-US',
18
+ :recognizer => 'default',
19
+ :language => 'en-US',
19
20
  :initial_timeout => 2000,
20
21
  :inter_digit_timeout => 2000,
21
22
  :sensitivity => 0.5,
22
23
  :min_confidence => 0.5
23
24
  end
24
25
 
25
- its(:grammar) { should be == Input::Grammar.new(:value => '[5 DIGITS]', :content_type => 'application/grammar+custom') }
26
- its(:mode) { should be == :speech }
26
+ its(:grammars) { should be == [Input::Grammar.new(:value => '[5 DIGITS]', :content_type => 'application/grammar+custom')] }
27
+ its(:mode) { should be == :voice }
27
28
  its(:terminator) { should be == '#' }
28
29
  its(:max_silence) { should be == 1000 }
29
- its(:recognizer) { should be == 'en-US' }
30
+ its(:recognizer) { should be == 'default' }
31
+ its(:language) { should be == 'en-US' }
30
32
  its(:initial_timeout) { should be == 2000 }
31
33
  its(:inter_digit_timeout) { should be == 2000 }
32
34
  its(:sensitivity) { should be == 0.5 }
33
35
  its(:min_confidence) { should be == 0.5 }
36
+
37
+ context "with multiple grammars" do
38
+ subject do
39
+ Input.new :grammars => [
40
+ {:value => '[5 DIGITS]', :content_type => 'application/grammar+custom'},
41
+ {:value => '[10 DIGITS]', :content_type => 'application/grammar+custom'}
42
+ ]
43
+ end
44
+
45
+ its(:grammars) { should be == [
46
+ Input::Grammar.new(:value => '[5 DIGITS]', :content_type => 'application/grammar+custom'),
47
+ Input::Grammar.new(:value => '[10 DIGITS]', :content_type => 'application/grammar+custom')
48
+ ]}
49
+ end
50
+
51
+ context "with a nil grammar" do
52
+ it "removes all grammars" do
53
+ subject.grammar = nil
54
+ subject.grammars.should == []
55
+ end
56
+ end
57
+
58
+ context "without any grammars" do
59
+ subject { described_class.new }
60
+
61
+ its(:grammars) { should == [] }
62
+ end
63
+
64
+ describe "exporting to Rayo" do
65
+ it "should export to XML that can be understood by its parser" do
66
+ new_instance = RayoNode.from_xml subject.to_rayo
67
+ new_instance.should be_instance_of described_class
68
+ new_instance.grammars.should be == [Input::Grammar.new(value: '[5 DIGITS]', content_type: 'application/grammar+custom')]
69
+ new_instance.mode.should be == :voice
70
+ new_instance.terminator.should be == '#'
71
+ new_instance.max_silence.should be == 1000
72
+ new_instance.recognizer.should be == 'default'
73
+ new_instance.language.should be == 'en-US'
74
+ new_instance.initial_timeout.should be == 2000
75
+ new_instance.inter_digit_timeout.should be == 2000
76
+ new_instance.sensitivity.should be == 0.5
77
+ new_instance.min_confidence.should be == 0.5
78
+ end
79
+
80
+ it "should wrap the grammar value in CDATA" do
81
+ grammar_node = subject.to_rayo.at_xpath('ns:grammar', ns: described_class.registered_ns)
82
+ grammar_node.children.first.should be_a Nokogiri::XML::CDATA
83
+ end
84
+
85
+ it "should render to a parent node if supplied" do
86
+ doc = Nokogiri::XML::Document.new
87
+ parent = Nokogiri::XML::Node.new 'foo', doc
88
+ doc.root = parent
89
+ rayo_doc = subject.to_rayo(parent)
90
+ rayo_doc.should == parent
91
+ end
92
+ end
34
93
  end
35
94
 
36
95
  describe "from a stanza" do
37
96
  let :stanza do
38
97
  <<-MESSAGE
39
98
  <input xmlns="urn:xmpp:rayo:input:1"
40
- mode="speech"
99
+ mode="voice"
41
100
  terminator="#"
42
101
  max-silence="1000"
43
- recognizer="en-US"
102
+ recognizer="default"
103
+ language="en-US"
44
104
  initial-timeout="2000"
45
105
  inter-digit-timeout="2000"
46
106
  sensitivity="0.5"
@@ -48,23 +108,32 @@ module Punchblock
48
108
  <grammar content-type="application/grammar+custom">
49
109
  <![CDATA[ [5 DIGITS] ]]>
50
110
  </grammar>
111
+ <grammar content-type="application/grammar+custom">
112
+ <![CDATA[ [10 DIGITS] ]]>
113
+ </grammar>
51
114
  </input>
52
115
  MESSAGE
53
116
  end
54
117
 
55
- subject { RayoNode.import parse_stanza(stanza).root, '9f00061', '1' }
118
+ subject { RayoNode.from_xml parse_stanza(stanza).root, '9f00061', '1' }
56
119
 
57
120
  it { should be_instance_of Input }
58
121
 
59
- its(:grammar) { should be == Input::Grammar.new(:value => '[5 DIGITS]', :content_type => 'application/grammar+custom') }
60
- its(:mode) { should be == :speech }
122
+ its(:grammars) { should be == [Input::Grammar.new(:value => '[5 DIGITS]', :content_type => 'application/grammar+custom'), Input::Grammar.new(:value => '[10 DIGITS]', :content_type => 'application/grammar+custom')] }
123
+ its(:mode) { should be == :voice }
61
124
  its(:terminator) { should be == '#' }
62
125
  its(:max_silence) { should be == 1000 }
63
- its(:recognizer) { should be == 'en-US' }
126
+ its(:recognizer) { should be == 'default' }
127
+ its(:language) { should be == 'en-US' }
64
128
  its(:initial_timeout) { should be == 2000 }
65
129
  its(:inter_digit_timeout) { should be == 2000 }
66
130
  its(:sensitivity) { should be == 0.5 }
67
131
  its(:min_confidence) { should be == 0.5 }
132
+
133
+ context "without any grammars" do
134
+ let(:stanza) { '<input xmlns="urn:xmpp:rayo:input:1"/>' }
135
+ its(:grammars) { should be == [] }
136
+ end
68
137
  end
69
138
 
70
139
  def grxml_doc(mode = :dtmf)
@@ -83,37 +152,19 @@ module Punchblock
83
152
  its(:content_type) { should be == 'application/srgs+xml' }
84
153
  end
85
154
 
86
- describe 'with a simple grammar' do
87
- subject { Input::Grammar.new :value => '[5 DIGITS]', :content_type => 'application/grammar+custom' }
88
-
89
- let(:expected_message) { "<![CDATA[ [5 DIGITS] ]]>" }
90
-
91
- it "should wrap grammar in CDATA" do
92
- subject.child.to_xml.should be == expected_message.strip
93
- end
94
- end
95
-
96
155
  describe 'with a GRXML grammar' do
97
156
  subject { Input::Grammar.new :value => grxml_doc, :content_type => 'application/srgs+xml' }
98
157
 
99
158
  its(:content_type) { should be == 'application/srgs+xml' }
100
159
 
101
- let(:expected_message) { "<![CDATA[ #{grxml_doc} ]]>" }
102
-
103
- it "should wrap GRXML in CDATA" do
104
- subject.child.to_xml.should be == expected_message.strip
105
- end
106
-
107
160
  its(:value) { should be == grxml_doc }
108
161
 
109
162
  describe "comparison" do
110
- let(:grammar2) { Input::Grammar.new :value => '<grammar xmlns="http://www.w3.org/2001/06/grammar" version="1.0" xml:lang="en-US" mode="dtmf" root="digits"><rule id="digits"><one-of><item>0</item><item>1</item></one-of></rule></grammar>' }
111
- let(:grammar3) { Input::Grammar.new :value => grxml_doc }
112
- let(:grammar4) { Input::Grammar.new :value => grxml_doc(:speech) }
163
+ let(:grammar2) { Input::Grammar.new :value => grxml_doc }
164
+ let(:grammar3) { Input::Grammar.new :value => grxml_doc(:voice) }
113
165
 
114
166
  it { should be == grammar2 }
115
- it { should be == grammar3 }
116
- it { should_not be == grammar4 }
167
+ it { should_not be == grammar3 }
117
168
  end
118
169
  end
119
170
 
@@ -138,8 +189,8 @@ module Punchblock
138
189
  end
139
190
 
140
191
  describe "actions" do
141
- let(:mock_client) { mock 'Client' }
142
- let(:command) { Input.new :grammar => '[5 DIGITS]' }
192
+ let(:mock_client) { double 'Client' }
193
+ let(:command) { described_class.new grammar: {value: '[5 DIGITS]', content_type: 'application/grammar+custom'} }
143
194
 
144
195
  before do
145
196
  command.component_id = 'abc123'
@@ -176,41 +227,123 @@ module Punchblock
176
227
  end
177
228
  end
178
229
 
179
- describe Input::Complete::Success do
230
+ describe Input::Complete::Match do
231
+ let :nlsml_string do
232
+ '''
233
+ <result xmlns="http://www.ietf.org/xml/ns/mrcpv2" grammar="http://flight">
234
+ <interpretation confidence="0.60">
235
+ <input mode="voice">I want to go to Pittsburgh</input>
236
+ <instance>
237
+ <airline>
238
+ <to_city>Pittsburgh</to_city>
239
+ </airline>
240
+ </instance>
241
+ </interpretation>
242
+ <interpretation confidence="0.40">
243
+ <input>I want to go to Stockholm</input>
244
+ <instance>
245
+ <airline>
246
+ <to_city>Stockholm</to_city>
247
+ </airline>
248
+ </instance>
249
+ </interpretation>
250
+ </result>
251
+ '''
252
+ end
253
+
180
254
  let :stanza do
181
255
  <<-MESSAGE
182
256
  <complete xmlns='urn:xmpp:rayo:ext:1'>
183
- <success mode="speech" confidence="0.45" xmlns='urn:xmpp:rayo:input:complete:1'>
184
- <interpretation>1234</interpretation>
185
- <utterance>one two three four</utterance>
186
- </success>
257
+ <match xmlns="urn:xmpp:rayo:input:complete:1" content-type="application/nlsml+xml">
258
+ <![CDATA[#{nlsml_string}]]>
259
+ </match>
187
260
  </complete>
188
261
  MESSAGE
189
262
  end
190
263
 
191
- subject { RayoNode.import(parse_stanza(stanza).root).reason }
264
+ let :expected_nlsml do
265
+ RubySpeech.parse nlsml_string
266
+ end
267
+
268
+ subject { RayoNode.from_xml(parse_stanza(stanza).root).reason }
192
269
 
193
- it { should be_instance_of Input::Complete::Success }
270
+ it { should be_instance_of Input::Complete::Match }
194
271
 
195
- its(:name) { should be == :success }
196
- its(:mode) { should be == :speech }
197
- its(:confidence) { should be == 0.45 }
198
- its(:interpretation) { should be == '1234' }
199
- its(:utterance) { should be == 'one two three four' }
272
+ its(:name) { should be == :match }
273
+ its(:content_type) { should be == 'application/nlsml+xml' }
274
+ its(:nlsml) { should be == expected_nlsml }
275
+ its(:mode) { should be == :voice }
276
+ its(:confidence) { should be == 0.6 }
277
+ its(:interpretation) { should be == { airline: { to_city: 'Pittsburgh' } } }
278
+ its(:utterance) { should be == 'I want to go to Pittsburgh' }
200
279
 
201
- describe "when setting options in initializer" do
280
+ describe "when creating from an NLSML document" do
202
281
  subject do
203
- Input::Complete::Success.new :mode => :dtmf,
204
- :confidence => 1,
205
- :utterance => '123',
206
- :interpretation => 'dtmf-1 dtmf-2 dtmf-3'
282
+ Input::Complete::Match.new :nlsml => expected_nlsml
207
283
  end
208
284
 
285
+ its(:content_type) { should be == 'application/nlsml+xml' }
286
+ its(:nlsml) { should be == expected_nlsml }
287
+ its(:mode) { should be == :voice }
288
+ its(:confidence) { should be == 0.6 }
289
+ its(:interpretation) { should be == { airline: { to_city: 'Pittsburgh' } } }
290
+ its(:utterance) { should be == 'I want to go to Pittsburgh' }
291
+ end
209
292
 
210
- its(:mode) { should be == :dtmf }
211
- its(:confidence) { should be == 1 }
212
- its(:utterance) { should be == '123' }
213
- its(:interpretation) { should be == 'dtmf-1 dtmf-2 dtmf-3' }
293
+ context "when not enclosed in CDATA, but escaped" do
294
+ let :stanza do
295
+ <<-MESSAGE
296
+ <complete xmlns='urn:xmpp:rayo:ext:1'>
297
+ <match xmlns="urn:xmpp:rayo:input:complete:1" content-type="application/nlsml+xml">
298
+ &lt;result xmlns=&quot;http://www.ietf.org/xml/ns/mrcpv2&quot; grammar=&quot;http://flight&quot;/&gt;
299
+ </match>
300
+ </complete>
301
+ MESSAGE
302
+ end
303
+
304
+ it "should parse the NLSML correctly" do
305
+ subject.nlsml.grammar.should == "http://flight"
306
+ end
307
+ end
308
+
309
+ context "when nested directly" do
310
+ let :stanza do
311
+ <<-MESSAGE
312
+ <complete xmlns='urn:xmpp:rayo:ext:1'>
313
+ <match xmlns="urn:xmpp:rayo:input:complete:1" content-type="application/nlsml+xml">
314
+ #{nlsml_string}
315
+ </match>
316
+ </complete>
317
+ MESSAGE
318
+ end
319
+
320
+ it "should parse the NLSML correctly" do
321
+ subject.nlsml.grammar.should == "http://flight"
322
+ end
323
+ end
324
+
325
+ describe "comparison" do
326
+ context "with the same nlsml" do
327
+ it "should be equal" do
328
+ subject.should == RayoNode.from_xml(parse_stanza(stanza).root).reason
329
+ end
330
+ end
331
+
332
+ context "with different nlsml" do
333
+ let :other_stanza do
334
+ <<-MESSAGE
335
+ <complete xmlns='urn:xmpp:rayo:ext:1'>
336
+ <match xmlns="urn:xmpp:rayo:input:complete:1">
337
+ <![CDATA[<result xmlns="http://www.ietf.org/xml/ns/mrcpv2" grammar="http://flight"/>]]>
338
+ </match>
339
+ </complete>
340
+ MESSAGE
341
+ end
342
+
343
+ it "should not be equal" do
344
+ subject.should_not == RayoNode.from_xml(parse_stanza(other_stanza).root).reason
345
+ end
346
+ end
214
347
  end
215
348
  end
216
349
 
@@ -218,12 +351,12 @@ module Punchblock
218
351
  let :stanza do
219
352
  <<-MESSAGE
220
353
  <complete xmlns='urn:xmpp:rayo:ext:1'>
221
- <nomatch xmlns='urn:xmpp:rayo:input:complete:1' />
354
+ <nomatch xmlns='urn:xmpp:rayo:input:complete:1' />
222
355
  </complete>
223
356
  MESSAGE
224
357
  end
225
358
 
226
- subject { RayoNode.import(parse_stanza(stanza).root).reason }
359
+ subject { RayoNode.from_xml(parse_stanza(stanza).root).reason }
227
360
 
228
361
  it { should be_instance_of Input::Complete::NoMatch }
229
362
 
@@ -234,12 +367,12 @@ module Punchblock
234
367
  let :stanza do
235
368
  <<-MESSAGE
236
369
  <complete xmlns='urn:xmpp:rayo:ext:1'>
237
- <noinput xmlns='urn:xmpp:rayo:input:complete:1' />
370
+ <noinput xmlns='urn:xmpp:rayo:input:complete:1' />
238
371
  </complete>
239
372
  MESSAGE
240
373
  end
241
374
 
242
- subject { RayoNode.import(parse_stanza(stanza).root).reason }
375
+ subject { RayoNode.from_xml(parse_stanza(stanza).root).reason }
243
376
 
244
377
  it { should be_instance_of Input::Complete::NoInput }
245
378