require File.join(File.dirname(__FILE__), *%w[.. spec_helper]) describe 'Blather::Stream' do class MockStream; include Stream; end def mock_stream(&block) @client = mock() @client.stubs(:jid=) stream = MockStream.new @client, JID.new('n@d/r'), 'pass' stream.expects(:send_data).at_least(1).with &block stream end it 'can be started' do client = mock() params = [client, 'n@d/r', 'pass', 'host', 1234] EM.expects(:connect).with do |*parms| parms[0] == 'host' && parms[1] == 1234 && parms[3] == client && parms[5] == 'pass' && parms[4] == JID.new('n@d/r') end Stream.start *(params) end it 'can figure out the host to use based on the jid' do client = mock() params = [client, 'n@d/r', 'pass', 'd', 5222] EM.expects(:connect).with do |*parms| parms[0] == 'd' && parms[1] == 5222 && parms[3] == client && parms[5] == 'pass' && parms[4] == JID.new('n@d/r') end Stream.start client, 'n@d/r', 'pass' end it 'starts the stream once the connection is complete' do s = mock_stream { |d| d =~ /stream:stream/ } s.connection_completed end it 'sends stanzas to the client when the stream is ready' do client = mock() client.stubs(:jid=) client.expects(:call) stream = MockStream.new client, JID.new('n@d/r'), 'pass' stream.expects(:send_data).with do |val| val.must_match(/stream:stream/) stream.receive_data "Message!" end stream.connection_completed end it 'puts itself in the stopped state when unbound' do stream = mock_stream do |val| val.must_match(/stream:stream/) stream.receive_data "" stream.stopped?.wont_equal true stream.unbind stream.stopped?.must_equal true end stream.connection_completed end it 'stops when sent ' do state = nil stream = mock_stream do |val| case state when nil val.must_match(/stream:stream/) state = :started when :started stream.stopped?.wont_equal true state = :stopped stream.receive_data "" true when :stopped stream.stopped?.must_equal true val.must_equal "" true else false end end stream.connection_completed stream.receive_data "" end it 'raises an error when it receives stream:error' do lambda do state = nil stream = mock_stream do |val| case state when nil val.must_match(/stream:stream/) state = :started when :started stream.stopped?.wont_equal true state = :stopped stream.receive_data "" true when :stopped stream.stopped?.must_equal true val.must_equal "" true else false end end stream.connection_completed stream.receive_data "" end.must_raise(StreamError) end it 'starts TLS when asked' do state = nil stream = mock_stream do |val| case state when nil val.must_match(/stream:stream/) state = :started when :started val.must_match(/starttls/) true else false end end stream.connection_completed stream.receive_data "" end it 'connects via SASL MD5 when asked' do Time.any_instance.stubs(:to_f).returns(1.1) state = nil client = mock() client.stubs(:jid=) stream = MockStream.new client, JID.new('n@d/r'), 'pass' stream.expects(:send_data).times(5).with do |val| case state when nil val.must_match(/stream:stream/) state = :started stream.receive_data "DIGEST-MD5" true when :started val.must_match(/auth.*DIGEST\-MD5/) state = :auth_sent stream.receive_data "cmVhbG09InNvbWVyZWFsbSIsbm9uY2U9Ik9BNk1HOXRFUUdtMmhoIixxb3A9ImF1dGgiLGNoYXJzZXQ9dXRmLTgsYWxnb3JpdGhtPW1kNS1zZXNzCg==" true when :auth_sent val.must_equal('bm9uY2U9Ik9BNk1HOXRFUUdtMmhoIixjaGFyc2V0PXV0Zi04LHVzZXJuYW1lPSJuIixyZWFsbT0ic29tZXJlYWxtIixjbm9uY2U9Ijc3N2Q0NWJiYmNkZjUwZDQ5YzQyYzcwYWQ3YWNmNWZlIixuYz0wMDAwMDAwMSxxb3A9YXV0aCxkaWdlc3QtdXJpPSJ4bXBwL2QiLHJlc3BvbnNlPTZiNTlhY2Q1ZWJmZjhjZTA0NTYzMGFiMDU2Zjg3MTdm') state = :response1_sent stream.receive_data "cnNwYXV0aD1lYTQwZjYwMzM1YzQyN2I1NTI3Yjg0ZGJhYmNkZmZmZAo=" true when :response1_sent val.must_equal('') state = :response2_sent stream.receive_data "" true when :response2_sent val.must_match(/stream:stream/) state = :complete true else false end end stream.connection_completed end it 'will connect via SSL PLAIN when asked' do state = nil client = mock() client.stubs(:jid=) stream = MockStream.new client, JID.new('n@d/r'), 'pass' stream.expects(:send_data).times(3).with do |val| case state when nil val.must_match(/stream:stream/) state = :started stream.receive_data "PLAIN" true when :started val.must_equal('bkBkAG4AcGFzcw==') state = :auth_sent stream.receive_data "" true when :auth_sent val.must_match(/stream:stream/) state = :complete true else false end end stream.connection_completed end it 'will connect via SSL ANONYMOUS when asked' do state = nil client = mock() client.stubs(:jid=) stream = MockStream.new client, JID.new('n@d/r'), 'pass' stream.expects(:send_data).times(3).with do |val| case state when nil val.must_match(/stream:stream/) state = :started stream.receive_data "ANONYMOUS" true when :started val.must_equal('bg==') state = :auth_sent stream.receive_data "" true when :auth_sent val.must_match(/stream:stream/) state = :complete true else false end end stream.connection_completed end it 'tried each possible mechanism until it fails completely' do state = nil client = mock() client.stubs(:jid=) stream = MockStream.new client, JID.new('n@d/r'), 'pass' stream.expects(:send_data).times(5).with do |val| case state when nil val.must_match(/stream:stream/) state = :started stream.receive_data "DIGEST-MD5PLAINANONYMOUS" true when :started val.must_match(/mechanism="DIGEST-MD5"/) state = :failed_md5 stream.receive_data "" true when :failed_md5 val.must_match(/mechanism="PLAIN"/) state = :failed_plain stream.receive_data "" true when :failed_plain val.must_match(/mechanism="ANONYMOUS"/) state = :failed_anon stream.receive_data "" true when :failed_anon val.must_match(/\/stream:stream/) state = :complete true else false end end stream.connection_completed end it 'tries each mechanism until it succeeds' do state = nil client = mock() client.stubs(:jid=) stream = MockStream.new client, JID.new('n@d/r'), 'pass' stream.expects(:send_data).times(4).with do |val| case state when nil val.must_match(/stream:stream/) state = :started stream.receive_data "DIGEST-MD5PLAINANONYMOUS" true when :started val.must_match(/mechanism="DIGEST-MD5"/) state = :failed_md5 stream.receive_data "" true when :failed_md5 val.must_match(/mechanism="PLAIN"/) state = :plain_sent stream.receive_data "" true when :plain_sent val.must_match(/stream:stream/) state = :complete true else false end end stream.connection_completed end it 'raises an exception when an unknown mechanism is sent' do state = nil client = mock() client.stubs(:jid=) stream = MockStream.new client, JID.new('n@d/r'), 'pass' stream.expects(:send_data).times(2).with do |val| if !state state = :started val.must_match(/stream:stream/) lambda do stream.receive_data "UNKNOWN" end.must_raise(Stream::SASL::UnknownMechanism) else val.must_match(/failure(.*)invalid\-mechanism/) end end stream.connection_completed end it 'will bind to a resource set by the server' do state = nil class Client; attr_accessor :jid; end client = Client.new jid = JID.new('n@d') stream = MockStream.new client, jid, 'pass' stream.expects(:send_data).times(2).with do |val| case state when nil val.must_match(/stream:stream/) state = :started stream.receive_data "" true when :started val.must_match(%r{}) val =~ %r{]+id="([^"]+)"} state = :complete stream.receive_data "#{jid}/server_resource" client.jid.must_equal JID.new('n@d/server_resource') true else false end end stream.connection_completed end it 'will bind to a resource set by the client' do state = nil class Client; attr_accessor :jid; end client = Client.new jid = JID.new('n@d/r') stream = MockStream.new client, jid, 'pass' stream.expects(:send_data).times(2).with do |val| case state when nil val.must_match(/stream:stream/) state = :started stream.receive_data "" true when :started val.must_match(%r{r}) val =~ %r{]+id="([^"]+)"} state = :complete stream.receive_data "#{jid}" client.jid.must_equal JID.new('n@d/r') true else false end end stream.connection_completed end it 'will establish a session if requested' do state = nil client = mock() client.stubs(:jid=) stream = MockStream.new client, JID.new('n@d/r'), 'pass' client.expects(:stream_started) stream.expects(:send_data).times(2).with do |val| case state when nil val.must_match(/stream:stream/) state = :started stream.receive_data "" true when :started val.must_match('') state = :completed stream.receive_data "" true else false end end stream.connection_completed end end