spec/punchblock/component/input_spec.rb in punchblock-1.9.4 vs spec/punchblock/component/input_spec.rb in punchblock-2.0.0.beta1

- old
+ new

@@ -4,69 +4,138 @@ module Punchblock module Component describe Input do it 'registers itself' do - RayoNode.class_from_registration(:input, 'urn:xmpp:rayo:input:1').should be == Input + RayoNode.class_from_registration(:input, 'urn:xmpp:rayo:input:1').should be == described_class end describe "when setting options in initializer" do subject do - Input.new :grammar => {:value => '[5 DIGITS]', :content_type => 'application/grammar+custom'}, - :mode => :speech, + described_class.new grammar: {value: '[5 DIGITS]', content_type: 'application/grammar+custom'}, + :mode => :voice, :terminator => '#', :max_silence => 1000, - :recognizer => 'en-US', + :recognizer => 'default', + :language => 'en-US', :initial_timeout => 2000, :inter_digit_timeout => 2000, :sensitivity => 0.5, :min_confidence => 0.5 end - its(:grammar) { should be == Input::Grammar.new(:value => '[5 DIGITS]', :content_type => 'application/grammar+custom') } - its(:mode) { should be == :speech } + its(:grammars) { should be == [Input::Grammar.new(:value => '[5 DIGITS]', :content_type => 'application/grammar+custom')] } + its(:mode) { should be == :voice } its(:terminator) { should be == '#' } its(:max_silence) { should be == 1000 } - its(:recognizer) { should be == 'en-US' } + its(:recognizer) { should be == 'default' } + its(:language) { should be == 'en-US' } its(:initial_timeout) { should be == 2000 } its(:inter_digit_timeout) { should be == 2000 } its(:sensitivity) { should be == 0.5 } its(:min_confidence) { should be == 0.5 } + + context "with multiple grammars" do + subject do + Input.new :grammars => [ + {:value => '[5 DIGITS]', :content_type => 'application/grammar+custom'}, + {:value => '[10 DIGITS]', :content_type => 'application/grammar+custom'} + ] + end + + 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') + ]} + end + + context "with a nil grammar" do + it "removes all grammars" do + subject.grammar = nil + subject.grammars.should == [] + end + end + + context "without any grammars" do + subject { described_class.new } + + its(:grammars) { should == [] } + end + + describe "exporting to Rayo" do + it "should export to XML that can be understood by its parser" do + new_instance = RayoNode.from_xml subject.to_rayo + new_instance.should be_instance_of described_class + new_instance.grammars.should be == [Input::Grammar.new(value: '[5 DIGITS]', content_type: 'application/grammar+custom')] + new_instance.mode.should be == :voice + new_instance.terminator.should be == '#' + new_instance.max_silence.should be == 1000 + new_instance.recognizer.should be == 'default' + new_instance.language.should be == 'en-US' + new_instance.initial_timeout.should be == 2000 + new_instance.inter_digit_timeout.should be == 2000 + new_instance.sensitivity.should be == 0.5 + new_instance.min_confidence.should be == 0.5 + end + + it "should wrap the grammar value in CDATA" do + grammar_node = subject.to_rayo.at_xpath('ns:grammar', ns: described_class.registered_ns) + grammar_node.children.first.should be_a Nokogiri::XML::CDATA + end + + it "should render to a parent node if supplied" do + doc = Nokogiri::XML::Document.new + parent = Nokogiri::XML::Node.new 'foo', doc + doc.root = parent + rayo_doc = subject.to_rayo(parent) + rayo_doc.should == parent + end + end end describe "from a stanza" do let :stanza do <<-MESSAGE <input xmlns="urn:xmpp:rayo:input:1" - mode="speech" + mode="voice" terminator="#" max-silence="1000" - recognizer="en-US" + recognizer="default" + language="en-US" initial-timeout="2000" inter-digit-timeout="2000" sensitivity="0.5" min-confidence="0.5"> <grammar content-type="application/grammar+custom"> <![CDATA[ [5 DIGITS] ]]> </grammar> + <grammar content-type="application/grammar+custom"> + <![CDATA[ [10 DIGITS] ]]> + </grammar> </input> MESSAGE end - subject { RayoNode.import parse_stanza(stanza).root, '9f00061', '1' } + subject { RayoNode.from_xml parse_stanza(stanza).root, '9f00061', '1' } it { should be_instance_of Input } - its(:grammar) { should be == Input::Grammar.new(:value => '[5 DIGITS]', :content_type => 'application/grammar+custom') } - its(:mode) { should be == :speech } + 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')] } + its(:mode) { should be == :voice } its(:terminator) { should be == '#' } its(:max_silence) { should be == 1000 } - its(:recognizer) { should be == 'en-US' } + its(:recognizer) { should be == 'default' } + its(:language) { should be == 'en-US' } its(:initial_timeout) { should be == 2000 } its(:inter_digit_timeout) { should be == 2000 } its(:sensitivity) { should be == 0.5 } its(:min_confidence) { should be == 0.5 } + + context "without any grammars" do + let(:stanza) { '<input xmlns="urn:xmpp:rayo:input:1"/>' } + its(:grammars) { should be == [] } + end end def grxml_doc(mode = :dtmf) RubySpeech::GRXML.draw :mode => mode.to_s, :root => 'digits' do rule id: 'digits' do @@ -81,41 +150,23 @@ describe "when not passing a content type" do subject { Input::Grammar.new :value => grxml_doc } its(:content_type) { should be == 'application/srgs+xml' } end - describe 'with a simple grammar' do - subject { Input::Grammar.new :value => '[5 DIGITS]', :content_type => 'application/grammar+custom' } - - let(:expected_message) { "<![CDATA[ [5 DIGITS] ]]>" } - - it "should wrap grammar in CDATA" do - subject.child.to_xml.should be == expected_message.strip - end - end - describe 'with a GRXML grammar' do subject { Input::Grammar.new :value => grxml_doc, :content_type => 'application/srgs+xml' } its(:content_type) { should be == 'application/srgs+xml' } - let(:expected_message) { "<![CDATA[ #{grxml_doc} ]]>" } - - it "should wrap GRXML in CDATA" do - subject.child.to_xml.should be == expected_message.strip - end - its(:value) { should be == grxml_doc } describe "comparison" do - 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>' } - let(:grammar3) { Input::Grammar.new :value => grxml_doc } - let(:grammar4) { Input::Grammar.new :value => grxml_doc(:speech) } + let(:grammar2) { Input::Grammar.new :value => grxml_doc } + let(:grammar3) { Input::Grammar.new :value => grxml_doc(:voice) } it { should be == grammar2 } - it { should be == grammar3 } - it { should_not be == grammar4 } + it { should_not be == grammar3 } end end describe 'with a grammar reference by URL' do let(:url) { 'http://foo.com/bar.grxml' } @@ -136,12 +187,12 @@ end end end describe "actions" do - let(:mock_client) { mock 'Client' } - let(:command) { Input.new :grammar => '[5 DIGITS]' } + let(:mock_client) { double 'Client' } + let(:command) { described_class.new grammar: {value: '[5 DIGITS]', content_type: 'application/grammar+custom'} } before do command.component_id = 'abc123' command.target_call_id = '123abc' command.client = mock_client @@ -174,73 +225,155 @@ end end end end - describe Input::Complete::Success do + describe Input::Complete::Match do + let :nlsml_string do + ''' +<result xmlns="http://www.ietf.org/xml/ns/mrcpv2" grammar="http://flight"> + <interpretation confidence="0.60"> + <input mode="voice">I want to go to Pittsburgh</input> + <instance> + <airline> + <to_city>Pittsburgh</to_city> + </airline> + </instance> + </interpretation> + <interpretation confidence="0.40"> + <input>I want to go to Stockholm</input> + <instance> + <airline> + <to_city>Stockholm</to_city> + </airline> + </instance> + </interpretation> +</result> + ''' + end + let :stanza do <<-MESSAGE <complete xmlns='urn:xmpp:rayo:ext:1'> -<success mode="speech" confidence="0.45" xmlns='urn:xmpp:rayo:input:complete:1'> - <interpretation>1234</interpretation> - <utterance>one two three four</utterance> -</success> + <match xmlns="urn:xmpp:rayo:input:complete:1" content-type="application/nlsml+xml"> + <![CDATA[#{nlsml_string}]]> + </match> </complete> MESSAGE end - subject { RayoNode.import(parse_stanza(stanza).root).reason } + let :expected_nlsml do + RubySpeech.parse nlsml_string + end - it { should be_instance_of Input::Complete::Success } + subject { RayoNode.from_xml(parse_stanza(stanza).root).reason } - its(:name) { should be == :success } - its(:mode) { should be == :speech } - its(:confidence) { should be == 0.45 } - its(:interpretation) { should be == '1234' } - its(:utterance) { should be == 'one two three four' } + it { should be_instance_of Input::Complete::Match } - describe "when setting options in initializer" do + its(:name) { should be == :match } + its(:content_type) { should be == 'application/nlsml+xml' } + its(:nlsml) { should be == expected_nlsml } + its(:mode) { should be == :voice } + its(:confidence) { should be == 0.6 } + its(:interpretation) { should be == { airline: { to_city: 'Pittsburgh' } } } + its(:utterance) { should be == 'I want to go to Pittsburgh' } + + describe "when creating from an NLSML document" do subject do - Input::Complete::Success.new :mode => :dtmf, - :confidence => 1, - :utterance => '123', - :interpretation => 'dtmf-1 dtmf-2 dtmf-3' + Input::Complete::Match.new :nlsml => expected_nlsml end + its(:content_type) { should be == 'application/nlsml+xml' } + its(:nlsml) { should be == expected_nlsml } + its(:mode) { should be == :voice } + its(:confidence) { should be == 0.6 } + its(:interpretation) { should be == { airline: { to_city: 'Pittsburgh' } } } + its(:utterance) { should be == 'I want to go to Pittsburgh' } + end - its(:mode) { should be == :dtmf } - its(:confidence) { should be == 1 } - its(:utterance) { should be == '123' } - its(:interpretation) { should be == 'dtmf-1 dtmf-2 dtmf-3' } + context "when not enclosed in CDATA, but escaped" do + let :stanza do + <<-MESSAGE +<complete xmlns='urn:xmpp:rayo:ext:1'> + <match xmlns="urn:xmpp:rayo:input:complete:1" content-type="application/nlsml+xml"> + &lt;result xmlns=&quot;http://www.ietf.org/xml/ns/mrcpv2&quot; grammar=&quot;http://flight&quot;/&gt; + </match> +</complete> + MESSAGE + end + + it "should parse the NLSML correctly" do + subject.nlsml.grammar.should == "http://flight" + end end + + context "when nested directly" do + let :stanza do + <<-MESSAGE +<complete xmlns='urn:xmpp:rayo:ext:1'> + <match xmlns="urn:xmpp:rayo:input:complete:1" content-type="application/nlsml+xml"> + #{nlsml_string} + </match> +</complete> + MESSAGE + end + + it "should parse the NLSML correctly" do + subject.nlsml.grammar.should == "http://flight" + end + end + + describe "comparison" do + context "with the same nlsml" do + it "should be equal" do + subject.should == RayoNode.from_xml(parse_stanza(stanza).root).reason + end + end + + context "with different nlsml" do + let :other_stanza do + <<-MESSAGE +<complete xmlns='urn:xmpp:rayo:ext:1'> + <match xmlns="urn:xmpp:rayo:input:complete:1"> + <![CDATA[<result xmlns="http://www.ietf.org/xml/ns/mrcpv2" grammar="http://flight"/>]]> + </match> +</complete> + MESSAGE + end + + it "should not be equal" do + subject.should_not == RayoNode.from_xml(parse_stanza(other_stanza).root).reason + end + end + end end describe Input::Complete::NoMatch do let :stanza do <<-MESSAGE <complete xmlns='urn:xmpp:rayo:ext:1'> -<nomatch xmlns='urn:xmpp:rayo:input:complete:1' /> + <nomatch xmlns='urn:xmpp:rayo:input:complete:1' /> </complete> MESSAGE end - subject { RayoNode.import(parse_stanza(stanza).root).reason } + subject { RayoNode.from_xml(parse_stanza(stanza).root).reason } it { should be_instance_of Input::Complete::NoMatch } its(:name) { should be == :nomatch } end describe Input::Complete::NoInput do let :stanza do <<-MESSAGE <complete xmlns='urn:xmpp:rayo:ext:1'> -<noinput xmlns='urn:xmpp:rayo:input:complete:1' /> + <noinput xmlns='urn:xmpp:rayo:input:complete:1' /> </complete> MESSAGE end - subject { RayoNode.import(parse_stanza(stanza).root).reason } + subject { RayoNode.from_xml(parse_stanza(stanza).root).reason } it { should be_instance_of Input::Complete::NoInput } its(:name) { should be == :noinput } end