blather 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. data/LICENSE +2 -0
  2. data/README.rdoc +54 -29
  3. data/Rakefile +94 -13
  4. data/VERSION.yml +4 -0
  5. data/examples/drb_client.rb +2 -4
  6. data/examples/echo.rb +13 -8
  7. data/examples/pubsub/cli.rb +64 -0
  8. data/examples/pubsub/ping_pong.rb +18 -0
  9. data/examples/pubsub/pubsub_dsl.rb +52 -0
  10. data/examples/pubsub_client.rb +39 -0
  11. data/examples/rosterprint.rb +14 -0
  12. data/examples/xmpp4r/echo.rb +35 -0
  13. data/ext/extconf.rb +65 -0
  14. data/lib/blather.rb +18 -121
  15. data/lib/blather/client.rb +13 -0
  16. data/lib/blather/client/client.rb +165 -0
  17. data/lib/blather/client/dsl.rb +99 -0
  18. data/lib/blather/client/pubsub.rb +53 -0
  19. data/lib/blather/client/pubsub/node.rb +27 -0
  20. data/lib/blather/core_ext/active_support.rb +1 -0
  21. data/lib/blather/core_ext/libxml.rb +7 -1
  22. data/lib/blather/errors.rb +39 -18
  23. data/lib/blather/errors/sasl_error.rb +87 -0
  24. data/lib/blather/errors/stanza_error.rb +262 -0
  25. data/lib/blather/errors/stream_error.rb +253 -0
  26. data/lib/blather/jid.rb +9 -16
  27. data/lib/blather/roster.rb +9 -0
  28. data/lib/blather/roster_item.rb +7 -4
  29. data/lib/blather/stanza.rb +19 -25
  30. data/lib/blather/stanza/disco.rb +9 -0
  31. data/lib/blather/stanza/disco/disco_info.rb +84 -0
  32. data/lib/blather/stanza/disco/disco_items.rb +59 -0
  33. data/lib/blather/stanza/iq.rb +16 -4
  34. data/lib/blather/stanza/iq/query.rb +6 -4
  35. data/lib/blather/stanza/iq/roster.rb +38 -38
  36. data/lib/blather/stanza/pubsub.rb +33 -0
  37. data/lib/blather/stanza/pubsub/affiliations.rb +52 -0
  38. data/lib/blather/stanza/pubsub/errors.rb +9 -0
  39. data/lib/blather/stanza/pubsub/event.rb +21 -0
  40. data/lib/blather/stanza/pubsub/items.rb +59 -0
  41. data/lib/blather/stanza/pubsub/owner.rb +9 -0
  42. data/lib/blather/stanza/pubsub/subscriptions.rb +57 -0
  43. data/lib/blather/stream.rb +125 -57
  44. data/lib/blather/stream/client.rb +26 -0
  45. data/lib/blather/stream/component.rb +34 -0
  46. data/lib/blather/stream/parser.rb +17 -27
  47. data/lib/blather/stream/resource.rb +21 -24
  48. data/lib/blather/stream/sasl.rb +60 -37
  49. data/lib/blather/stream/session.rb +12 -19
  50. data/lib/blather/stream/stream_handler.rb +39 -0
  51. data/lib/blather/stream/tls.rb +22 -18
  52. data/lib/blather/xmpp_node.rb +91 -17
  53. data/spec/blather/core_ext/libxml_spec.rb +58 -0
  54. data/spec/blather/errors/sasl_error_spec.rb +56 -0
  55. data/spec/blather/errors/stanza_error_spec.rb +148 -0
  56. data/spec/blather/errors/stream_error_spec.rb +114 -0
  57. data/spec/blather/errors_spec.rb +40 -0
  58. data/spec/blather/jid_spec.rb +0 -7
  59. data/spec/blather/roster_item_spec.rb +5 -0
  60. data/spec/blather/roster_spec.rb +6 -6
  61. data/spec/blather/stanza/discos/disco_info_spec.rb +207 -0
  62. data/spec/blather/stanza/discos/disco_items_spec.rb +136 -0
  63. data/spec/blather/stanza/iq/query_spec.rb +9 -2
  64. data/spec/blather/stanza/iq/roster_spec.rb +117 -1
  65. data/spec/blather/stanza/iq_spec.rb +29 -0
  66. data/spec/blather/stanza/presence/subscription_spec.rb +12 -1
  67. data/spec/blather/stanza/presence_spec.rb +29 -0
  68. data/spec/blather/stanza/pubsub/affiliations_spec.rb +46 -0
  69. data/spec/blather/stanza/pubsub/items_spec.rb +59 -0
  70. data/spec/blather/stanza/pubsub/subscriptions_spec.rb +63 -0
  71. data/spec/blather/stanza/pubsub_spec.rb +26 -0
  72. data/spec/blather/stanza_spec.rb +13 -1
  73. data/spec/blather/stream/client_spec.rb +787 -0
  74. data/spec/blather/stream/component_spec.rb +86 -0
  75. data/spec/blather/xmpp_node_spec.rb +75 -22
  76. data/spec/fixtures/pubsub.rb +157 -0
  77. data/spec/spec_helper.rb +6 -14
  78. metadata +86 -74
  79. data/CHANGELOG +0 -5
  80. data/Manifest +0 -47
  81. data/blather.gemspec +0 -41
  82. data/lib/blather/stanza/error.rb +0 -31
  83. data/spec/blather/stream_spec.rb +0 -462
  84. data/spec/build_safe.rb +0 -20
@@ -8,4 +8,33 @@ describe 'Blather::Stanza::Iq' do
8
8
  it 'creates a new Iq stanza defaulted as a get' do
9
9
  Stanza::Iq.new.type.must_equal :get
10
10
  end
11
+
12
+ it 'wont import non-iq stanzas' do
13
+ lambda { Stanza::Iq.import(XMPPNode.new('foo')) }.must_raise(Blather::ArgumentError)
14
+ end
15
+
16
+ it 'creates a new Stanza::Iq object on import' do
17
+ Stanza::Iq.import(XMPPNode.new('iq')).must_be_kind_of Stanza::Iq
18
+ end
19
+
20
+ it 'creates a proper object based on its children' do
21
+ n = XMPPNode.new('iq')
22
+ n << XMPPNode.new('query')
23
+ Stanza::Iq.import(n).must_be_kind_of Stanza::Iq::Query
24
+ end
25
+
26
+ it 'ensures type is one of Stanza::Iq::VALID_TYPES' do
27
+ lambda { Stanza::Iq.new :invalid_type_name }.must_raise(Blather::ArgumentError)
28
+
29
+ Stanza::Iq::VALID_TYPES.each do |valid_type|
30
+ n = Stanza::Iq.new valid_type
31
+ n.type.must_equal valid_type
32
+ end
33
+ end
34
+
35
+ Stanza::Iq::VALID_TYPES.each do |valid_type|
36
+ it "provides a helper (#{valid_type}?) for type #{valid_type}" do
37
+ Stanza::Iq.new.must_respond_to :"#{valid_type}?"
38
+ end
39
+ end
11
40
  end
@@ -71,4 +71,15 @@ describe 'Blather::Stanza::Presence::Subscription' do
71
71
  sub.type = :subscribe
72
72
  sub.request?.must_equal true
73
73
  end
74
- end
74
+
75
+ it "will inherit only another node's attributes" do
76
+ inheritable = XMPPNode.new 'foo'
77
+ inheritable.attributes[:bar] = 'baz'
78
+
79
+ sub = Stanza::Presence::Subscription.new
80
+ sub.must_respond_to :inherit
81
+
82
+ sub.inherit inheritable
83
+ sub.attributes[:bar].must_equal 'baz'
84
+ end
85
+ end
@@ -21,4 +21,33 @@ describe 'Blather::Stanza::Presence' do
21
21
  end
22
22
  end
23
23
 
24
+ it 'creates a Status object when importing a node with type == nil' do
25
+ s = Stanza::Presence.import(XMPPNode.new)
26
+ s.must_be_kind_of Stanza::Presence::Status
27
+ s.state.must_equal :available
28
+ end
29
+
30
+ it 'creates a Status object when importing a node with type == "unavailable"' do
31
+ n = XMPPNode.new
32
+ n.attributes[:type] = :unavailable
33
+ s = Stanza::Presence.import(n)
34
+ s.must_be_kind_of Stanza::Presence::Status
35
+ s.state.must_equal :unavailable
36
+ end
37
+
38
+ it 'creates a Subscription object when importing a node with type == "subscribe"' do
39
+ n = XMPPNode.new
40
+ n.attributes[:type] = :subscribe
41
+ s = Stanza::Presence.import(n)
42
+ s.must_be_kind_of Stanza::Presence::Subscription
43
+ s.type.must_equal :subscribe
44
+ end
45
+
46
+ it 'creates a Presence object when importing a node with type equal to something unkown' do
47
+ n = XMPPNode.new
48
+ n.attributes[:type] = :foo
49
+ s = Stanza::Presence.import(n)
50
+ s.must_be_kind_of Stanza::Presence
51
+ s.type.must_equal :foo
52
+ end
24
53
  end
@@ -0,0 +1,46 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. .. .. spec_helper])
2
+ require File.join(File.dirname(__FILE__), *%w[.. .. .. fixtures pubsub])
3
+
4
+ describe 'Blather::Stanza::PubSub::Affiliations' do
5
+ it 'registers itself' do
6
+ XMPPNode.class_from_registration(:pubsub_affiliations, 'http://jabber.org/protocol/pubsub').must_equal Stanza::PubSub::Affiliations
7
+ end
8
+
9
+ it 'ensures an affiliations node is present on create' do
10
+ affiliations = Stanza::PubSub::Affiliations.new
11
+ affiliations.pubsub.children.detect { |n| n.element_name == 'affiliations' }.wont_be_nil
12
+ end
13
+
14
+ it 'ensures an affiliations node exists when calling #affiliations' do
15
+ affiliations = Stanza::PubSub::Affiliations.new
16
+ affiliations.pubsub.remove_child :affiliations
17
+ affiliations.pubsub.children.detect { |n| n.element_name == 'affiliations' }.must_be_nil
18
+
19
+ affiliations.affiliations.wont_be_nil
20
+ affiliations.pubsub.children.detect { |n| n.element_name == 'affiliations' }.wont_be_nil
21
+ end
22
+
23
+ it 'defaults to a get node' do
24
+ aff = Stanza::PubSub::Affiliations.new
25
+ aff.type.must_equal :get
26
+ end
27
+
28
+ it 'sets the host if requested' do
29
+ aff = Stanza::PubSub::Affiliations.new :get, 'pubsub.jabber.local'
30
+ aff.to.must_equal JID.new('pubsub.jabber.local')
31
+ end
32
+
33
+ it 'can import an affiliates result node' do
34
+ node = XML::Document.string(affiliations_xml).root
35
+
36
+ affiliations = Stanza::PubSub::Affiliations.new.inherit node
37
+ affiliations.size.must_equal 5
38
+ affiliations.list.must_equal({
39
+ :owner => ['node1', 'node2'],
40
+ :publisher => ['node3'],
41
+ :outcast => ['node4'],
42
+ :member => ['node5'],
43
+ :none => ['node6']
44
+ })
45
+ end
46
+ end
@@ -0,0 +1,59 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. .. .. spec_helper])
2
+ require File.join(File.dirname(__FILE__), *%w[.. .. .. fixtures pubsub])
3
+
4
+ describe 'Blather::Stanza::PubSub::Items' do
5
+ it 'registers itself' do
6
+ XMPPNode.class_from_registration(:pubsub_items, 'http://jabber.org/protocol/pubsub').must_equal Stanza::PubSub::Items
7
+ end
8
+
9
+ it 'ensures an items node is present on create' do
10
+ items = Stanza::PubSub::Items.new
11
+ items.pubsub.children.detect { |n| n.element_name == 'items' }.wont_be_nil
12
+ end
13
+
14
+ it 'ensures an items node exists when calling #items' do
15
+ items = Stanza::PubSub::Items.new
16
+ items.pubsub.remove_child :items
17
+ items.pubsub.children.detect { |n| n.element_name == 'items' }.must_be_nil
18
+
19
+ items.items.wont_be_nil
20
+ items.pubsub.children.detect { |n| n.element_name == 'items' }.wont_be_nil
21
+ end
22
+
23
+ it 'defaults to a get node' do
24
+ aff = Stanza::PubSub::Items.new
25
+ aff.type.must_equal :get
26
+ end
27
+
28
+ it 'can create an items request node to request all items' do
29
+ host = 'pubsub.jabber.local'
30
+ node = 'princely_musings'
31
+
32
+ items = Stanza::PubSub::Items.request host, node
33
+ items.find("//pubsub/items[@node=\"#{node}\"]").size.must_equal 1
34
+ items.to.must_equal JID.new(host)
35
+ end
36
+
37
+ it 'can create an items request node to request some items' do
38
+ host = 'pubsub.jabber.local'
39
+ node = 'princely_musings'
40
+ items = %w[item1 item2]
41
+
42
+ items_xpath = items.map { |i| "@id=\"#{i}\"" } * ' or '
43
+
44
+ items = Stanza::PubSub::Items.request host, node, items
45
+ items.find("//pubsub/items[@node=\"#{node}\"]/item[#{items_xpath}]").size.must_equal 2
46
+ items.to.must_equal JID.new(host)
47
+ end
48
+
49
+ it 'can create an items request node to request "max_number" of items' do
50
+ host = 'pubsub.jabber.local'
51
+ node = 'princely_musings'
52
+ max = 3
53
+
54
+ items = Stanza::PubSub::Items.request host, node, nil, max
55
+ items.find("//pubsub/items[@node=\"#{node}\" and @max_items=\"#{max}\"]").size.must_equal 1
56
+ items.to.must_equal JID.new(host)
57
+ end
58
+
59
+ end
@@ -0,0 +1,63 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. .. .. spec_helper])
2
+
3
+ def subscriptions_xml
4
+ <<-NODE
5
+ <iq type='result'
6
+ from='pubsub.shakespeare.lit'
7
+ to='francisco@denmark.lit'
8
+ id='affil1'>
9
+ <pubsub xmlns='http://jabber.org/protocol/pubsub'>
10
+ <subscriptions>
11
+ <subscription node='node1' subscription='subscribed'/>
12
+ <subscription node='node2' subscription='subscribed'/>
13
+ <subscription node='node3' subscription='unconfigured'/>
14
+ <subscription node='node4' subscription='pending'/>
15
+ <subscription node='node5' subscription='none'/>
16
+ </subscriptions>
17
+ </pubsub>
18
+ </iq>
19
+ NODE
20
+ end
21
+
22
+ describe 'Blather::Stanza::PubSub::Subscriptions' do
23
+ it 'registers itself' do
24
+ XMPPNode.class_from_registration(:pubsub_subscriptions, 'http://jabber.org/protocol/pubsub').must_equal Stanza::PubSub::Subscriptions
25
+ end
26
+
27
+ it 'ensures an subscriptions node is present on create' do
28
+ subscriptions = Stanza::PubSub::Subscriptions.new
29
+ subscriptions.pubsub.children.detect { |n| n.element_name == 'subscriptions' }.wont_be_nil
30
+ end
31
+
32
+ it 'ensures an subscriptions node exists when calling #subscriptions' do
33
+ subscriptions = Stanza::PubSub::Subscriptions.new
34
+ subscriptions.pubsub.remove_child :subscriptions
35
+ subscriptions.pubsub.children.detect { |n| n.element_name == 'subscriptions' }.must_be_nil
36
+
37
+ subscriptions.list.wont_be_nil
38
+ subscriptions.pubsub.children.detect { |n| n.element_name == 'subscriptions' }.wont_be_nil
39
+ end
40
+
41
+ it 'defaults to a get node' do
42
+ aff = Stanza::PubSub::Subscriptions.new
43
+ aff.type.must_equal :get
44
+ end
45
+
46
+ it 'sets the host if requested' do
47
+ aff = Stanza::PubSub::Subscriptions.new :get, 'pubsub.jabber.local'
48
+ aff.to.must_equal JID.new('pubsub.jabber.local')
49
+ end
50
+
51
+ it 'can import a subscriptions result node' do
52
+ node = XML::Document.string(subscriptions_xml).root
53
+
54
+ subscriptions = Stanza::PubSub::Subscriptions.new.inherit node
55
+ subscriptions.size.must_equal 4
56
+ subscriptions.list.must_equal({
57
+ :subscribed => ['node1', 'node2'],
58
+ :unconfigured => ['node3'],
59
+ :pending => ['node4'],
60
+ :none => ['node5']
61
+ })
62
+ end
63
+ end
@@ -0,0 +1,26 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. .. spec_helper])
2
+
3
+ describe 'Blather::Stanza::PubSub' do
4
+ it 'registers itself' do
5
+ XMPPNode.class_from_registration(:pubsub, 'http://jabber.org/protocol/pubsub').must_equal Stanza::PubSub
6
+ end
7
+
8
+ it 'ensures a pubusb node is present on create' do
9
+ pubsub = Stanza::PubSub.new
10
+ pubsub.children.detect { |n| n.element_name == 'pubsub' }.wont_be_nil
11
+ end
12
+
13
+ it 'ensures a pubsub node exists when calling #pubsub' do
14
+ pubsub = Stanza::PubSub.new
15
+ pubsub.remove_child :pubsub
16
+ pubsub.children.detect { |n| n.element_name == 'pubsub' }.must_be_nil
17
+
18
+ pubsub.pubsub.wont_be_nil
19
+ pubsub.children.detect { |n| n.element_name == 'pubsub' }.wont_be_nil
20
+ end
21
+
22
+ it 'sets the host if requested' do
23
+ aff = Stanza::PubSub.new :get, 'pubsub.jabber.local'
24
+ aff.to.must_equal JID.new('pubsub.jabber.local')
25
+ end
26
+ end
@@ -2,7 +2,7 @@ require File.join(File.dirname(__FILE__), *%w[.. spec_helper])
2
2
 
3
3
  describe 'Blather::Stanza' do
4
4
  it 'provides .next_id helper for generating new IDs' do
5
- proc { Stanza.next_id }.must_change 'Stanza', :next_id
5
+ proc { Stanza.next_id }.must_change 'Stanza.next_id'
6
6
  end
7
7
 
8
8
  it 'can import a node' do
@@ -92,4 +92,16 @@ describe 'Blather::Stanza' do
92
92
  s.type.wont_be_nil
93
93
  s['type'].wont_be_nil
94
94
  end
95
+
96
+ it 'can be converted into an error by error name' do
97
+ s = Stanza.new('message')
98
+ err = s.as_error 'internal-server-error', 'cancel'
99
+ err.must_be_instance_of StanzaError::InternalServerError
100
+ end
101
+
102
+ it 'can be converted into an error by error class' do
103
+ s = Stanza.new('message')
104
+ err = s.as_error StanzaError::InternalServerError, 'cancel'
105
+ err.must_be_instance_of StanzaError::InternalServerError
106
+ end
95
107
  end
@@ -0,0 +1,787 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. .. spec_helper])
2
+
3
+ describe 'Blather::Stream::Client' do
4
+ class MockServer; end
5
+ module ServerMock
6
+ def receive_data(data)
7
+ @server ||= MockServer.new
8
+ @server.receive_data data, self
9
+ end
10
+ end
11
+
12
+ def mocked_server(times = nil, &block)
13
+ @client ||= mock()
14
+ @client.stubs(:stopped) unless @client.respond_to?(:stopped)
15
+ @client.stubs(:jid=) unless @client.respond_to?(:jid=)
16
+
17
+ MockServer.any_instance.expects(:receive_data).send(*(times ? [:times, times] : [:at_least, 1])).with &block
18
+ EventMachine::run {
19
+ # Mocked server
20
+ EventMachine::start_server '127.0.0.1', 12345, ServerMock
21
+
22
+ # Stream connection
23
+ EM.connect('127.0.0.1', 12345, Stream::Client, @client, @jid || JID.new('n@d/r'), 'pass') { |c| @stream = c }
24
+ }
25
+ end
26
+
27
+ it 'can be started' do
28
+ client = mock()
29
+ params = [client, 'n@d/r', 'pass', 'host', 1234]
30
+ EM.expects(:connect).with do |*parms|
31
+ parms[0] == 'host' &&
32
+ parms[1] == 1234 &&
33
+ parms[3] == client &&
34
+ parms[5] == 'pass' &&
35
+ parms[4] == JID.new('n@d/r')
36
+ end
37
+
38
+ Stream::Client.start *(params)
39
+ end
40
+
41
+ it 'can figure out the host to use based on the jid' do
42
+ client = mock()
43
+ params = [client, 'n@d/r', 'pass', 'd', 5222]
44
+ EM.expects(:connect).with do |*parms|
45
+ parms[0] == 'd' &&
46
+ parms[1] == 5222 &&
47
+ parms[3] == client &&
48
+ parms[5] == 'pass' &&
49
+ parms[4] == JID.new('n@d/r')
50
+ end
51
+
52
+ Stream::Client.start client, 'n@d/r', 'pass'
53
+ end
54
+
55
+ it 'starts the stream once the connection is complete' do
56
+ mocked_server(1) { |val, _| EM.stop; val.must_match(/stream:stream/) }
57
+ end
58
+
59
+ it 'sends stanzas to the client when the stream is ready' do
60
+ @client = mock()
61
+ @client.expects(:call).with do |n|
62
+ EM.stop
63
+ n.kind_of?(Stanza::Message) && @stream.ready?.must_equal(true)
64
+ end
65
+
66
+ mocked_server(1) do |val, server|
67
+ val.must_match(/stream:stream/)
68
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
69
+ server.send_data "<message to='a@b/c' from='d@e/f' type='chat' xml:lang='en'><body>Message!</body></message>"
70
+ end
71
+ end
72
+
73
+ it 'puts itself in the stopped state and calls @client.stopped when stopped' do
74
+ @client = mock()
75
+ @client.expects(:stopped).at_least_once
76
+
77
+ started = false
78
+ mocked_server(2) do |val, server|
79
+ if !started
80
+ started = true
81
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
82
+ server.send_data "<stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' /></stream:features>"
83
+ val.must_match(/stream:stream/)
84
+
85
+ else
86
+ EM.stop
87
+ @stream.stopped?.must_equal false
88
+ @stream.unbind
89
+ @stream.stopped?.must_equal true
90
+
91
+ end
92
+ end
93
+ end
94
+
95
+ it 'will be in the negotiating state during feature negotiations' do
96
+ state = nil
97
+ @client = mock()
98
+ @client.stubs(:stream_started)
99
+ @client.expects(:call).with do |n|
100
+ EM.stop
101
+ state.must_equal(:negotiated) && @stream.negotiating?.must_equal(false)
102
+ end
103
+
104
+ mocked_server(2) do |val, server|
105
+ case state
106
+ when nil
107
+ state = :started
108
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
109
+ server.send_data "<stream:features><session xmlns='urn:ietf:params:xml:ns:xmpp-session' /></stream:features>"
110
+ true
111
+
112
+ when :started
113
+ state = :negotiated
114
+ @stream.negotiating?.must_equal(true)
115
+ server.send_data "<iq from='d' type='result' id='#{val[/id="([^"]+)"/,1]}' />"
116
+ server.send_data "<message to='a@b/c' from='d@e/f' type='chat' xml:lang='en'><body>Message!</body></message>"
117
+ true
118
+
119
+ else
120
+ EM.stop
121
+ false
122
+
123
+ end
124
+ end
125
+ end
126
+
127
+ it 'stops when sent </stream:stream>' do
128
+ state = nil
129
+ mocked_server(3) do |val, server|
130
+ case state
131
+ when nil
132
+ state = :started
133
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' xml:lang='en'>"
134
+ server.send_data "<stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' /></stream:features>"
135
+ val.must_match(/stream:stream/)
136
+
137
+ when :started
138
+ state = :stopped
139
+ server.send_data '</stream:stream>'
140
+ @stream.stopped?.must_equal false
141
+
142
+ when :stopped
143
+ EM.stop
144
+ @stream.stopped?.must_equal true
145
+ val.must_equal '</stream:stream>'
146
+
147
+ else
148
+ EM.stop
149
+ false
150
+
151
+ end
152
+ end
153
+ end
154
+
155
+ it 'sends client an error on stream:error' do
156
+ @client = mock()
157
+ @client.expects(:call).with do |v|
158
+ v.must_be_instance_of(StreamError::Conflict)
159
+ v.text.must_equal 'Already signed in'
160
+ v.to_s.must_equal "Stream Error (conflict): #{v.text}"
161
+ end
162
+
163
+ state = nil
164
+ mocked_server(3) do |val, server|
165
+ case state
166
+ when nil
167
+ state = :started
168
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>"
169
+ server.send_data "<stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' /></stream:features>"
170
+ val.must_match(/stream:stream/)
171
+
172
+ when :started
173
+ state = :stopped
174
+ server.send_data "<stream:error><conflict xmlns='urn:ietf:params:xml:ns:xmpp-streams' />"
175
+ server.send_data "<text xmlns='urn:ietf:params:xml:ns:xmpp-streams'>Already signed in</text></stream:error>"
176
+
177
+ when :stopped
178
+ EM.stop
179
+ val.must_equal "</stream:stream>"
180
+
181
+ else
182
+ EM.stop
183
+ false
184
+
185
+ end
186
+ end
187
+ end
188
+
189
+ it 'starts TLS when asked' do
190
+ state = nil
191
+ mocked_server(3) do |val, server|
192
+ case state
193
+ when nil
194
+ state = :started
195
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'><stream:features><starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls' /></stream:features>"
196
+ val.must_match(/stream:stream/)
197
+
198
+ when :started
199
+ state = :tls
200
+ @stream.expects(:start_tls)
201
+ server.send_data "<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>"
202
+ val.must_match(/starttls/)
203
+
204
+ when :tls
205
+ EM.stop
206
+ true
207
+
208
+ else
209
+ EM.stop
210
+ false
211
+
212
+ end
213
+ end
214
+ end
215
+
216
+ it 'will fail if TLS negotiation fails' do
217
+ state = nil
218
+ @client = mock()
219
+ @client.expects(:call).with { |v| v.must_be_kind_of TLSFailure }
220
+ mocked_server(3) do |val, server|
221
+ case state
222
+ when nil
223
+ state = :started
224
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'><stream:features><starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls' /></stream:features>"
225
+ val.must_match(/stream:stream/)
226
+
227
+ when :started
228
+ state = :tls
229
+ @stream.expects(:start_tls).never
230
+ server.send_data "<failure xmlns='urn:ietf:params:xml:ns:xmpp-tls'/></stream:stream>"
231
+ val.must_match(/starttls/)
232
+
233
+ when :tls
234
+ EM.stop
235
+ val.must_equal "</stream:stream>"
236
+
237
+ else
238
+ EM.stop
239
+ false
240
+
241
+ end
242
+ end
243
+ end
244
+
245
+ it 'will fail if a bad node comes through TLS negotiations' do
246
+ state = nil
247
+ @client = mock()
248
+ @client.expects(:call).with do |v|
249
+ v.must_be_kind_of UnknownResponse
250
+ v.node.element_name.must_equal 'foo-bar'
251
+ end
252
+ mocked_server(3) do |val, server|
253
+ case state
254
+ when nil
255
+ state = :started
256
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'><stream:features><starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls' /></stream:features>"
257
+ val.must_match(/stream:stream/)
258
+
259
+ when :started
260
+ state = :tls
261
+ @stream.expects(:start_tls).never
262
+ server.send_data "<foo-bar xmlns='urn:ietf:params:xml:ns:xmpp-tls'/></stream:stream>"
263
+ val.must_match(/starttls/)
264
+
265
+ when :tls
266
+ EM.stop
267
+ val.must_equal "</stream:stream>"
268
+
269
+ else
270
+ EM.stop
271
+ false
272
+
273
+ end
274
+ end
275
+ end
276
+
277
+ it 'connects via SASL MD5 when asked' do
278
+ Time.any_instance.stubs(:to_f).returns(1.1)
279
+ state = nil
280
+
281
+ mocked_server(5) do |val, server|
282
+ case state
283
+ when nil
284
+ state = :started
285
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'><stream:features><mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>DIGEST-MD5</mechanism></mechanisms></stream:features>"
286
+ val.must_match(/stream:stream/)
287
+
288
+ when :started
289
+ state = :auth_sent
290
+ server.send_data "<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>cmVhbG09InNvbWVyZWFsbSIsbm9uY2U9Ik9BNk1HOXRFUUdtMmhoIixxb3A9ImF1dGgiLGNoYXJzZXQ9dXRmLTgsYWxnb3JpdGhtPW1kNS1zZXNzCg==</challenge>"
291
+ val.must_match(/auth.*DIGEST\-MD5/)
292
+
293
+ when :auth_sent
294
+ state = :response1_sent
295
+ server.send_data "<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>cnNwYXV0aD1lYTQwZjYwMzM1YzQyN2I1NTI3Yjg0ZGJhYmNkZmZmZAo=</challenge>"
296
+ val.must_equal('<response xmlns="urn:ietf:params:xml:ns:xmpp-sasl">bm9uY2U9Ik9BNk1HOXRFUUdtMmhoIixjaGFyc2V0PXV0Zi04LHVzZXJuYW1lPSJuIixyZWFsbT0ic29tZXJlYWxtIixjbm9uY2U9Ijc3N2Q0NWJiYmNkZjUwZDQ5YzQyYzcwYWQ3YWNmNWZlIixuYz0wMDAwMDAwMSxxb3A9YXV0aCxkaWdlc3QtdXJpPSJ4bXBwL2QiLHJlc3BvbnNlPTZiNTlhY2Q1ZWJmZjhjZTA0NTYzMGFiMDU2Zjg3MTdm</response>')
297
+
298
+ when :response1_sent
299
+ state = :response2_sent
300
+ server.send_data "<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />"
301
+ val.must_match(%r{<response xmlns="urn:ietf:params:xml:ns:xmpp-sasl"\s?/>})
302
+
303
+ when :response2_sent
304
+ EM.stop
305
+ state = :complete
306
+ val.must_match(/stream:stream/)
307
+
308
+ else
309
+ EM.stop
310
+ false
311
+
312
+ end
313
+ end
314
+ end
315
+
316
+ it 'will connect via SSL PLAIN when asked' do
317
+ state = nil
318
+ mocked_server(3) do |val, server|
319
+ case state
320
+ when nil
321
+ state = :started
322
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'><stream:features><mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>PLAIN</mechanism></mechanisms></stream:features>"
323
+ val.must_match(/stream:stream/)
324
+
325
+ when :started
326
+ state = :auth_sent
327
+ server.send_data "<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />"
328
+ val.must_equal('<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="PLAIN">bkBkAG4AcGFzcw==</auth>')
329
+
330
+ when :auth_sent
331
+ EM.stop
332
+ state = :complete
333
+ val.must_match(/stream:stream/)
334
+
335
+ else
336
+ EM.stop
337
+ false
338
+
339
+ end
340
+ end
341
+ end
342
+
343
+ it 'will connect via SSL ANONYMOUS when asked' do
344
+ state = nil
345
+
346
+ mocked_server(3) do |val, server|
347
+ case state
348
+ when nil
349
+ state = :started
350
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'><stream:features><mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>ANONYMOUS</mechanism></mechanisms></stream:features>"
351
+ val.must_match(/stream:stream/)
352
+
353
+ when :started
354
+ state = :auth_sent
355
+ server.send_data "<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />"
356
+ val.must_equal('<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="ANONYMOUS">bg==</auth>')
357
+
358
+ when :auth_sent
359
+ EM.stop
360
+ state = :complete
361
+ val.must_match(/stream:stream/)
362
+
363
+ else
364
+ EM.stop
365
+ false
366
+
367
+ end
368
+ end
369
+ end
370
+
371
+ it 'tried each possible mechanism until it fails completely' do
372
+ state = nil
373
+ @client = mock()
374
+ @client.expects(:call).with do |n|
375
+ n.must_be_kind_of(SASLError)
376
+ n.must_be_instance_of SASLError::NotAuthorized
377
+ end
378
+
379
+ mocked_server(5) do |val, server|
380
+ case state
381
+ when nil
382
+ state = :started
383
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'><stream:features><mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>DIGEST-MD5</mechanism><mechanism>PLAIN</mechanism><mechanism>ANONYMOUS</mechanism></mechanisms></stream:features>"
384
+ val.must_match(/stream:stream/)
385
+
386
+ when :started
387
+ state = :failed_md5
388
+ server.send_data "<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><not-authorized /></failure>"
389
+ val.must_match(/mechanism="DIGEST-MD5"/)
390
+
391
+ when :failed_md5
392
+ state = :failed_plain
393
+ server.send_data "<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><not-authorized /></failure>"
394
+ val.must_match(/mechanism="PLAIN"/)
395
+
396
+ when :failed_plain
397
+ state = :failed_anon
398
+ server.send_data "<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><not-authorized /></failure>"
399
+ val.must_match(/mechanism="ANONYMOUS"/)
400
+
401
+ when :failed_anon
402
+ EM.stop
403
+ state = :complete
404
+ val.must_match(/\/stream:stream/)
405
+
406
+ else
407
+ EM.stop
408
+ false
409
+
410
+ end
411
+ end
412
+ end
413
+
414
+ it 'tries each mechanism until it succeeds' do
415
+ state = nil
416
+ mocked_server(4) do |val, server|
417
+ case state
418
+ when nil
419
+ state = :started
420
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'><stream:features><mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>DIGEST-MD5</mechanism><mechanism>PLAIN</mechanism><mechanism>ANONYMOUS</mechanism></mechanisms></stream:features>"
421
+ val.must_match(/stream:stream/)
422
+
423
+ when :started
424
+ state = :failed_md5
425
+ server.send_data "<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><not-authorized /></failure>"
426
+ val.must_match(/mechanism="DIGEST-MD5"/)
427
+
428
+ when :failed_md5
429
+ state = :plain_sent
430
+ server.send_data "<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />"
431
+ val.must_match(/mechanism="PLAIN"/)
432
+
433
+ when :plain_sent
434
+ EM.stop
435
+ val.must_match(/stream:stream/)
436
+
437
+ else
438
+ EM.stop
439
+ false
440
+
441
+ end
442
+ end
443
+ end
444
+
445
+ it 'sends client an error when an unknown mechanism is sent' do
446
+ @client = mock()
447
+ @client.expects(:call).with { |v| v.must_be_kind_of(Stream::SASL::UnknownMechanism) }
448
+ started = false
449
+ mocked_server(2) do |val, server|
450
+ if !started
451
+ started = true
452
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
453
+ server.send_data "<stream:features><mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>UNKNOWN</mechanism></mechanisms></stream:features>"
454
+ val.must_match(/stream:stream/)
455
+
456
+ else
457
+ EM.stop
458
+ val.must_match(/failure(.*)invalid\-mechanism/)
459
+
460
+ end
461
+ end
462
+ end
463
+
464
+ %w[ aborted
465
+ incorrect-encoding
466
+ invalid-authzid
467
+ invalid-mechanism
468
+ mechanism-too-weak
469
+ not-authorized
470
+ temporary-auth-failure
471
+ ].each do |error_type|
472
+ it "fails on #{error_type}" do
473
+ @client = mock()
474
+ @client.expects(:call).with do |n|
475
+ n.must_be_instance_of SASLError.class_from_registration(error_type)
476
+ end
477
+ state = nil
478
+ mocked_server(3) do |val, server|
479
+ case state
480
+ when nil
481
+ state = :started
482
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'><stream:features><mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>PLAIN</mechanism></mechanisms></stream:features>"
483
+ val.must_match(/stream:stream/)
484
+
485
+ when :started
486
+ state = :auth_sent
487
+ server.send_data "<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><#{error_type} /></failure>"
488
+ val.must_equal('<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="PLAIN">bkBkAG4AcGFzcw==</auth>')
489
+
490
+ when :auth_sent
491
+ EM.stop
492
+ state = :complete
493
+ val.must_match(/\/stream:stream/)
494
+
495
+ else
496
+ EM.stop
497
+ false
498
+
499
+ end
500
+ end
501
+ end
502
+ end
503
+
504
+ it 'fails when an unkown node comes through during SASL negotiation' do
505
+ @client = mock()
506
+ @client.expects(:call).with do |n|
507
+ n.must_be_instance_of UnknownResponse
508
+ n.node.element_name.must_equal 'foo-bar'
509
+ end
510
+ state = nil
511
+ mocked_server(3) do |val, server|
512
+ case state
513
+ when nil
514
+ state = :started
515
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'><stream:features><mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>PLAIN</mechanism></mechanisms></stream:features>"
516
+ val.must_match(/stream:stream/)
517
+
518
+ when :started
519
+ state = :auth_sent
520
+ server.send_data "<foo-bar />"
521
+ val.must_equal('<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="PLAIN">bkBkAG4AcGFzcw==</auth>')
522
+
523
+ when :auth_sent
524
+ EM.stop
525
+ state = :complete
526
+ val.must_match(/\/stream:stream/)
527
+
528
+ else
529
+ EM.stop
530
+ false
531
+
532
+ end
533
+ end
534
+ end
535
+
536
+ it 'will bind to a resource set by the server' do
537
+ state = nil
538
+ class Client; attr_accessor :jid; end
539
+ @client = Client.new
540
+ @jid = JID.new('n@d')
541
+
542
+ mocked_server(3) do |val, server|
543
+ case state
544
+ when nil
545
+ state = :started
546
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'><stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' /></stream:features>"
547
+ val.must_match(/stream:stream/)
548
+
549
+ when :started
550
+ state = :complete
551
+ val =~ %r{<iq[^>]+id="([^"]+)"}
552
+ server.send_data "<iq type='result' id='#{$1}'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><jid>#{@jid}/server_resource</jid></bind></iq>"
553
+ server.send_data "<stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' /></stream:features>"
554
+ val.must_match(%r{<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"\s?/>})
555
+
556
+ when :complete
557
+ EM.stop
558
+ @client.jid.must_equal JID.new('n@d/server_resource')
559
+
560
+ else
561
+ EM.stop
562
+ false
563
+
564
+ end
565
+ end
566
+ end
567
+
568
+ it 'will bind to a resource set by the client' do
569
+ state = nil
570
+ class Client; attr_accessor :jid; end
571
+ @client = Client.new
572
+ @jid = JID.new('n@d/r')
573
+
574
+ mocked_server(3) do |val, server|
575
+ case state
576
+ when nil
577
+ state = :started
578
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'><stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' /></stream:features>"
579
+ val.must_match(/stream:stream/)
580
+
581
+ when :started
582
+ state = :complete
583
+ val =~ %r{<iq[^>]+id="([^"]+)"}
584
+ server.send_data "<iq type='result' id='#{$1}'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><jid>#{@jid}</jid></bind></iq>"
585
+ server.send_data "<stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' /></stream:features>"
586
+ val.must_match(%r{<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><resource>r</resource></bind>})
587
+
588
+ when :complete
589
+ EM.stop
590
+ @client.jid.must_equal JID.new('n@d/r')
591
+
592
+ else
593
+ EM.stop
594
+ false
595
+
596
+ end
597
+ end
598
+ end
599
+
600
+ it 'will return an error if resource binding errors out' do
601
+ state = nil
602
+ @client = mock()
603
+ @client.expects(:call).with do |n|
604
+ n.must_be_instance_of StanzaError::BadRequest
605
+ end
606
+ mocked_server(3) do |val, server|
607
+ case state
608
+ when nil
609
+ state = :started
610
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'><stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' /></stream:features>"
611
+ val.must_match(/stream:stream/)
612
+
613
+ when :started
614
+ state = :complete
615
+ val =~ %r{<iq[^>]+id="([^"]+)"}
616
+ server.send_data "<iq type='error' id='#{$1}'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><resource>r</resource></bind><error type='modify'><bad-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error></iq>"
617
+ val.must_match(%r{<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><resource>r</resource></bind>})
618
+
619
+ when :complete
620
+ EM.stop
621
+ val.must_match(/\/stream:stream/)
622
+
623
+ else
624
+ EM.stop
625
+ false
626
+
627
+ end
628
+ end
629
+ end
630
+
631
+ it 'will return an error if an unkown node comes through during resouce binding' do
632
+ state = nil
633
+ @client = mock()
634
+ @client.expects(:call).with do |n|
635
+ n.must_be_instance_of UnknownResponse
636
+ n.node.element_name.must_equal 'foo-bar'
637
+ end
638
+ mocked_server(3) do |val, server|
639
+ case state
640
+ when nil
641
+ state = :started
642
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'><stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' /></stream:features>"
643
+ val.must_match(/stream:stream/)
644
+
645
+ when :started
646
+ state = :complete
647
+ val =~ %r{<iq[^>]+id="([^"]+)"}
648
+ server.send_data "<foo-bar />"
649
+ val.must_match(%r{<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><resource>r</resource></bind>})
650
+
651
+ when :complete
652
+ EM.stop
653
+ val.must_match(/\/stream:stream/)
654
+
655
+ else
656
+ EM.stop
657
+ false
658
+
659
+ end
660
+ end
661
+ end
662
+
663
+ it 'will establish a session if requested' do
664
+ state = nil
665
+ @client = mock()
666
+ @client.expects(:stream_started)
667
+
668
+ mocked_server(3) do |val, server|
669
+ case state
670
+ when nil
671
+ state = :started
672
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
673
+ server.send_data "<stream:features><session xmlns='urn:ietf:params:xml:ns:xmpp-session' /></stream:features>"
674
+ val.must_match(/stream:stream/)
675
+
676
+ when :started
677
+ state = :completed
678
+ server.send_data "<iq from='d' type='result' id='#{val[/id="([^"]+)"/,1]}' />"
679
+ server.send_data "<stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' /></stream:features>"
680
+ val.must_match(%r{<iq id="[^"]+" type="set" to="d"><session xmlns="urn:ietf:params:xml:ns:xmpp-session"\s?/></iq>})
681
+
682
+ when :completed
683
+ EM.stop
684
+ true
685
+
686
+ else
687
+ EM.stop
688
+ false
689
+
690
+ end
691
+ end
692
+ end
693
+
694
+ it 'will return an error if session establishment errors out' do
695
+ state = nil
696
+ @client = mock()
697
+ @client.expects(:call).with do |n|
698
+ n.must_be_instance_of StanzaError::InternalServerError
699
+ end
700
+ mocked_server(3) do |val, server|
701
+ case state
702
+ when nil
703
+ state = :started
704
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
705
+ server.send_data "<stream:features><session xmlns='urn:ietf:params:xml:ns:xmpp-session' /></stream:features>"
706
+ val.must_match(/stream:stream/)
707
+
708
+ when :started
709
+ state = :completed
710
+ server.send_data "<iq from='d' type='error' id='#{val[/id="([^"]+)"/,1]}'><session xmlns='urn:ietf:params:xml:ns:xmpp-session'/><error type='wait'><internal-server-error xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error></iq>"
711
+ val.must_match(%r{<iq id="[^"]+" type="set" to="d"><session xmlns="urn:ietf:params:xml:ns:xmpp-session"\s?/></iq>})
712
+
713
+ when :completed
714
+ EM.stop
715
+ val.must_match(/\/stream:stream/)
716
+
717
+ else
718
+ EM.stop
719
+ false
720
+
721
+ end
722
+ end
723
+ end
724
+
725
+ it 'will return an error if an unknown node come through during session establishment' do
726
+ state = nil
727
+ @client = mock()
728
+ @client.expects(:call).with do |n|
729
+ n.must_be_instance_of UnknownResponse
730
+ n.node.element_name.must_equal 'foo-bar'
731
+ end
732
+ mocked_server(3) do |val, server|
733
+ case state
734
+ when nil
735
+ state = :started
736
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
737
+ server.send_data "<stream:features><session xmlns='urn:ietf:params:xml:ns:xmpp-session' /></stream:features>"
738
+ val.must_match(/stream:stream/)
739
+
740
+ when :started
741
+ state = :completed
742
+ server.send_data '<foo-bar />'
743
+ val.must_match(%r{<iq id="[^"]+" type="set" to="d"><session xmlns="urn:ietf:params:xml:ns:xmpp-session"\s?/></iq>})
744
+
745
+ when :completed
746
+ EM.stop
747
+ val.must_match(/\/stream:stream/)
748
+
749
+ else
750
+ EM.stop
751
+ false
752
+
753
+ end
754
+ end
755
+ end
756
+
757
+ it 'sends client an error on parse error' do
758
+ @client = mock()
759
+ @client.expects(:call).with do |v|
760
+ v.must_be_kind_of ParseError
761
+ v.message.must_match(/generate\-parse\-error/)
762
+ end
763
+ state = nil
764
+ mocked_server(3) do |val, server|
765
+ case state
766
+ when nil
767
+ state = :started
768
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>"
769
+ server.send_data "<stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' /></stream:features>"
770
+ val.must_match(/stream:stream/)
771
+
772
+ when :started
773
+ state = :parse_error
774
+ server.send_data "</generate-parse-error>"
775
+
776
+ when :parse_error
777
+ EM.stop
778
+ val.must_equal "</stream:stream>"
779
+
780
+ else
781
+ EM.stop
782
+ false
783
+
784
+ end
785
+ end
786
+ end
787
+ end