blather 0.2.1 → 0.2.2
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.
- data/LICENSE +2 -0
- data/README.rdoc +54 -29
- data/Rakefile +94 -13
- data/VERSION.yml +4 -0
- data/examples/drb_client.rb +2 -4
- data/examples/echo.rb +13 -8
- data/examples/pubsub/cli.rb +64 -0
- data/examples/pubsub/ping_pong.rb +18 -0
- data/examples/pubsub/pubsub_dsl.rb +52 -0
- data/examples/pubsub_client.rb +39 -0
- data/examples/rosterprint.rb +14 -0
- data/examples/xmpp4r/echo.rb +35 -0
- data/ext/extconf.rb +65 -0
- data/lib/blather.rb +18 -121
- data/lib/blather/client.rb +13 -0
- data/lib/blather/client/client.rb +165 -0
- data/lib/blather/client/dsl.rb +99 -0
- data/lib/blather/client/pubsub.rb +53 -0
- data/lib/blather/client/pubsub/node.rb +27 -0
- data/lib/blather/core_ext/active_support.rb +1 -0
- data/lib/blather/core_ext/libxml.rb +7 -1
- data/lib/blather/errors.rb +39 -18
- data/lib/blather/errors/sasl_error.rb +87 -0
- data/lib/blather/errors/stanza_error.rb +262 -0
- data/lib/blather/errors/stream_error.rb +253 -0
- data/lib/blather/jid.rb +9 -16
- data/lib/blather/roster.rb +9 -0
- data/lib/blather/roster_item.rb +7 -4
- data/lib/blather/stanza.rb +19 -25
- data/lib/blather/stanza/disco.rb +9 -0
- data/lib/blather/stanza/disco/disco_info.rb +84 -0
- data/lib/blather/stanza/disco/disco_items.rb +59 -0
- data/lib/blather/stanza/iq.rb +16 -4
- data/lib/blather/stanza/iq/query.rb +6 -4
- data/lib/blather/stanza/iq/roster.rb +38 -38
- data/lib/blather/stanza/pubsub.rb +33 -0
- data/lib/blather/stanza/pubsub/affiliations.rb +52 -0
- data/lib/blather/stanza/pubsub/errors.rb +9 -0
- data/lib/blather/stanza/pubsub/event.rb +21 -0
- data/lib/blather/stanza/pubsub/items.rb +59 -0
- data/lib/blather/stanza/pubsub/owner.rb +9 -0
- data/lib/blather/stanza/pubsub/subscriptions.rb +57 -0
- data/lib/blather/stream.rb +125 -57
- data/lib/blather/stream/client.rb +26 -0
- data/lib/blather/stream/component.rb +34 -0
- data/lib/blather/stream/parser.rb +17 -27
- data/lib/blather/stream/resource.rb +21 -24
- data/lib/blather/stream/sasl.rb +60 -37
- data/lib/blather/stream/session.rb +12 -19
- data/lib/blather/stream/stream_handler.rb +39 -0
- data/lib/blather/stream/tls.rb +22 -18
- data/lib/blather/xmpp_node.rb +91 -17
- data/spec/blather/core_ext/libxml_spec.rb +58 -0
- data/spec/blather/errors/sasl_error_spec.rb +56 -0
- data/spec/blather/errors/stanza_error_spec.rb +148 -0
- data/spec/blather/errors/stream_error_spec.rb +114 -0
- data/spec/blather/errors_spec.rb +40 -0
- data/spec/blather/jid_spec.rb +0 -7
- data/spec/blather/roster_item_spec.rb +5 -0
- data/spec/blather/roster_spec.rb +6 -6
- data/spec/blather/stanza/discos/disco_info_spec.rb +207 -0
- data/spec/blather/stanza/discos/disco_items_spec.rb +136 -0
- data/spec/blather/stanza/iq/query_spec.rb +9 -2
- data/spec/blather/stanza/iq/roster_spec.rb +117 -1
- data/spec/blather/stanza/iq_spec.rb +29 -0
- data/spec/blather/stanza/presence/subscription_spec.rb +12 -1
- data/spec/blather/stanza/presence_spec.rb +29 -0
- data/spec/blather/stanza/pubsub/affiliations_spec.rb +46 -0
- data/spec/blather/stanza/pubsub/items_spec.rb +59 -0
- data/spec/blather/stanza/pubsub/subscriptions_spec.rb +63 -0
- data/spec/blather/stanza/pubsub_spec.rb +26 -0
- data/spec/blather/stanza_spec.rb +13 -1
- data/spec/blather/stream/client_spec.rb +787 -0
- data/spec/blather/stream/component_spec.rb +86 -0
- data/spec/blather/xmpp_node_spec.rb +75 -22
- data/spec/fixtures/pubsub.rb +157 -0
- data/spec/spec_helper.rb +6 -14
- metadata +86 -74
- data/CHANGELOG +0 -5
- data/Manifest +0 -47
- data/blather.gemspec +0 -41
- data/lib/blather/stanza/error.rb +0 -31
- data/spec/blather/stream_spec.rb +0 -462
- 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
|
-
|
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
|
data/spec/blather/stanza_spec.rb
CHANGED
@@ -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'
|
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
|