punchblock 1.9.4 → 2.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
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