blather 0.1 → 0.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.
Files changed (50) hide show
  1. data/CHANGELOG +2 -0
  2. data/Manifest +36 -33
  3. data/README.rdoc +11 -17
  4. data/Rakefile +4 -3
  5. data/blather.gemspec +9 -9
  6. data/examples/drb_client.rb +7 -0
  7. data/examples/echo.rb +9 -18
  8. data/lib/blather.rb +140 -31
  9. data/lib/blather/{core/errors.rb → errors.rb} +0 -0
  10. data/lib/blather/{core/jid.rb → jid.rb} +0 -0
  11. data/lib/blather/{core/roster.rb → roster.rb} +0 -0
  12. data/lib/blather/{core/roster_item.rb → roster_item.rb} +0 -0
  13. data/lib/blather/{core/stanza.rb → stanza.rb} +6 -14
  14. data/lib/blather/stanza/error.rb +31 -0
  15. data/lib/blather/{core/stanza → stanza}/iq.rb +2 -2
  16. data/lib/blather/stanza/iq/query.rb +50 -0
  17. data/lib/blather/{core/stanza → stanza}/iq/roster.rb +3 -3
  18. data/lib/blather/{core/stanza → stanza}/message.rb +6 -2
  19. data/lib/blather/{core/stanza → stanza}/presence.rb +10 -0
  20. data/lib/blather/{core/stanza → stanza}/presence/status.rb +8 -6
  21. data/lib/blather/{core/stanza → stanza}/presence/subscription.rb +1 -1
  22. data/lib/blather/{core/stream.rb → stream.rb} +1 -1
  23. data/lib/blather/{core/stream → stream}/parser.rb +0 -5
  24. data/lib/blather/{core/stream → stream}/resource.rb +0 -0
  25. data/lib/blather/{core/stream → stream}/sasl.rb +16 -5
  26. data/lib/blather/{core/stream → stream}/session.rb +0 -0
  27. data/lib/blather/{core/stream → stream}/tls.rb +0 -0
  28. data/lib/blather/{core/sugar.rb → sugar.rb} +4 -0
  29. data/lib/blather/{core/xmpp_node.rb → xmpp_node.rb} +1 -3
  30. data/spec/blather/{core/jid_spec.rb → jid_spec.rb} +16 -1
  31. data/spec/blather/{core/roster_item_spec.rb → roster_item_spec.rb} +1 -1
  32. data/spec/blather/{core/roster_spec.rb → roster_spec.rb} +11 -1
  33. data/spec/blather/stanza/iq/query_spec.rb +34 -0
  34. data/spec/blather/stanza/iq/roster_spec.rb +7 -0
  35. data/spec/blather/stanza/iq_spec.rb +11 -0
  36. data/spec/blather/stanza/message_spec.rb +52 -0
  37. data/spec/blather/stanza/presence/status_spec.rb +102 -0
  38. data/spec/blather/stanza/presence/subscription_spec.rb +74 -0
  39. data/spec/blather/stanza/presence_spec.rb +24 -0
  40. data/spec/blather/{core/stanza_spec.rb → stanza_spec.rb} +1 -1
  41. data/spec/blather/{core/stream_spec.rb → stream_spec.rb} +208 -9
  42. data/spec/blather/{core/xmpp_node_spec.rb → xmpp_node_spec.rb} +1 -1
  43. metadata +75 -69
  44. data/examples/shell_client.rb +0 -28
  45. data/lib/blather/callback.rb +0 -24
  46. data/lib/blather/client.rb +0 -81
  47. data/lib/blather/core/stanza/iq/query.rb +0 -42
  48. data/lib/blather/extensions.rb +0 -4
  49. data/lib/blather/extensions/last_activity.rb +0 -55
  50. data/lib/blather/extensions/version.rb +0 -85
@@ -0,0 +1,52 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. .. spec_helper])
2
+
3
+ describe 'Blather::Stanza::Message' do
4
+ it 'registers itself' do
5
+ XMPPNode.class_from_registration(:message, nil).must_equal Stanza::Message
6
+ end
7
+
8
+ it 'provides "attr_accessor" for body' do
9
+ s = Stanza::Message.new
10
+ s.body.must_be_nil
11
+ s.detect { |n| n.element_name == 'body' }.must_be_nil
12
+
13
+ s.body = 'test message'
14
+ s.body.wont_be_nil
15
+ s.detect { |n| n.element_name == 'body' }.wont_be_nil
16
+ end
17
+
18
+ it 'provides "attr_accessor" for subject' do
19
+ s = Stanza::Message.new
20
+ s.subject.must_be_nil
21
+ s.detect { |n| n.element_name == 'subject' }.must_be_nil
22
+
23
+ s.subject = 'test subject'
24
+ s.subject.wont_be_nil
25
+ s.detect { |n| n.element_name == 'subject' }.wont_be_nil
26
+ end
27
+
28
+ it 'provides "attr_accessor" for thread' do
29
+ s = Stanza::Message.new
30
+ s.thread.must_be_nil
31
+ s.detect { |n| n.element_name == 'thread' }.must_be_nil
32
+
33
+ s.thread = 1234
34
+ s.thread.wont_be_nil
35
+ s.detect { |n| n.element_name == 'thread' }.wont_be_nil
36
+ end
37
+
38
+ it 'ensures type is one of Stanza::Message::VALID_TYPES' do
39
+ lambda { Stanza::Message.new nil, nil, :invalid_type_name }.must_raise(Blather::ArgumentError)
40
+
41
+ Stanza::Message::VALID_TYPES.each do |valid_type|
42
+ msg = Stanza::Message.new nil, nil, valid_type
43
+ msg.type.must_equal valid_type
44
+ end
45
+ end
46
+
47
+ Stanza::Message::VALID_TYPES.each do |valid_type|
48
+ it "provides a helper (#{valid_type}?) for type #{valid_type}" do
49
+ Stanza::Message.new.must_respond_to :"#{valid_type}?"
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,102 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. .. .. spec_helper])
2
+
3
+ describe 'Blather::Stanza::Presence::Status' do
4
+ it 'registers itself' do
5
+ XMPPNode.class_from_registration(:status, nil).must_equal Stanza::Presence::Status
6
+ end
7
+
8
+ it 'can set state on creation' do
9
+ status = Stanza::Presence::Status.new :away
10
+ status.state.must_equal :away
11
+ end
12
+
13
+ it 'can set a message on creation' do
14
+ status = Stanza::Presence::Status.new nil, 'Say hello!'
15
+ status.message.must_equal 'Say hello!'
16
+ end
17
+
18
+ it 'ensures type is nil or :unavailable' do
19
+ status = Stanza::Presence::Status.new
20
+ lambda { status.type = :invalid_type_name }.must_raise(Blather::ArgumentError)
21
+
22
+ [nil, :unavailable].each do |valid_type|
23
+ status.type = valid_type
24
+ status.type.must_equal valid_type
25
+ end
26
+ end
27
+
28
+ it 'ensures state is one of Presence::Status::VALID_STATES' do
29
+ status = Stanza::Presence::Status.new
30
+ lambda { status.state = :invalid_type_name }.must_raise(Blather::ArgumentError)
31
+
32
+ Stanza::Presence::Status::VALID_STATES.each do |valid_state|
33
+ status.state = valid_state
34
+ status.state.must_equal valid_state
35
+ end
36
+ end
37
+
38
+ it 'returns :available if state is nil' do
39
+ Stanza::Presence::Status.new.state.must_equal :available
40
+ end
41
+
42
+ it 'returns :unavailable if type is :unavailable' do
43
+ status = Stanza::Presence::Status.new
44
+ status.type = :unavailable
45
+ status.state.must_equal :unavailable
46
+ end
47
+
48
+ it 'ensures priority is not greater than 127' do
49
+ lambda { Stanza::Presence::Status.new.priority = 128 }.must_raise(Blather::ArgumentError)
50
+ end
51
+
52
+ it 'ensures priority is not less than -128' do
53
+ lambda { Stanza::Presence::Status.new.priority = -129 }.must_raise(Blather::ArgumentError)
54
+ end
55
+
56
+ it 'has "attr_accessor" for priority' do
57
+ status = Stanza::Presence::Status.new
58
+ status.priority.must_equal 0
59
+
60
+ status.priority = 10
61
+ status.children.detect { |n| n.element_name == 'priority' }.wont_be_nil
62
+ status.priority.must_equal 10
63
+ end
64
+
65
+ it 'has "attr_accessor" for message' do
66
+ status = Stanza::Presence::Status.new
67
+ status.message.must_be_nil
68
+
69
+ status.message = 'new message'
70
+ status.children.detect { |n| n.element_name == 'status' }.wont_be_nil
71
+ status.message.must_equal 'new message'
72
+ end
73
+
74
+ it 'must be comparable by priority' do
75
+ jid = JID.new 'a@b/c'
76
+
77
+ status1 = Stanza::Presence::Status.new
78
+ status1.from = jid
79
+
80
+ status2 = Stanza::Presence::Status.new
81
+ status2.from = jid
82
+
83
+ status1.priority = 1
84
+ status2.priority = -1
85
+ (status1 <=> status2).must_equal 1
86
+ (status2 <=> status1).must_equal -1
87
+
88
+ status2.priority = 1
89
+ (status1 <=> status2).must_equal 0
90
+ end
91
+
92
+ it 'raises an argument error if compared to a status with a different JID' do
93
+ status1 = Stanza::Presence::Status.new
94
+ status1.from = 'a@b/c'
95
+
96
+ status2 = Stanza::Presence::Status.new
97
+ status2.from = 'd@e/f'
98
+
99
+ lambda { status1 <=> status2 }.must_raise(Blather::ArgumentError)
100
+ end
101
+ end
102
+
@@ -0,0 +1,74 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. .. .. spec_helper])
2
+
3
+ describe 'Blather::Stanza::Presence::Subscription' do
4
+ it 'registers itself' do
5
+ XMPPNode.class_from_registration(:subscription, nil).must_equal Stanza::Presence::Subscription
6
+ end
7
+
8
+ it 'can set to on creation' do
9
+ sub = Stanza::Presence::Subscription.new 'a@b'
10
+ sub.to.to_s.must_equal 'a@b'
11
+ end
12
+
13
+ it 'can set a type on creation' do
14
+ sub = Stanza::Presence::Subscription.new nil, :subscribed
15
+ sub.type.must_equal :subscribed
16
+ end
17
+
18
+ it 'strips JIDs when setting #to' do
19
+ sub = Stanza::Presence::Subscription.new 'a@b/c'
20
+ sub.to.to_s.must_equal 'a@b'
21
+ end
22
+
23
+ it 'generates an approval using #approve!' do
24
+ jid = JID.new 'a@b'
25
+ sub = Stanza::Presence::Subscription.new
26
+ sub.from = jid
27
+ sub.approve!
28
+ sub.to.must_equal jid
29
+ sub.type.must_equal :subscribed
30
+ end
31
+
32
+ it 'generates a refusal using #refuse!' do
33
+ jid = JID.new 'a@b'
34
+ sub = Stanza::Presence::Subscription.new
35
+ sub.from = jid
36
+ sub.refuse!
37
+ sub.to.must_equal jid
38
+ sub.type.must_equal :unsubscribed
39
+ end
40
+
41
+ it 'generates an unsubscript using #unsubscribe!' do
42
+ jid = JID.new 'a@b'
43
+ sub = Stanza::Presence::Subscription.new
44
+ sub.from = jid
45
+ sub.unsubscribe!
46
+ sub.to.must_equal jid
47
+ sub.type.must_equal :unsubscribe
48
+ end
49
+
50
+ it 'generates a cancellation using #cancel!' do
51
+ jid = JID.new 'a@b'
52
+ sub = Stanza::Presence::Subscription.new
53
+ sub.from = jid
54
+ sub.cancel!
55
+ sub.to.must_equal jid
56
+ sub.type.must_equal :unsubscribed
57
+ end
58
+
59
+ it 'generates a request using #request!' do
60
+ jid = JID.new 'a@b'
61
+ sub = Stanza::Presence::Subscription.new
62
+ sub.from = jid
63
+ sub.request!
64
+ sub.to.must_equal jid
65
+ sub.type.must_equal :subscribe
66
+ end
67
+
68
+ it 'has a #request? helper' do
69
+ sub = Stanza::Presence::Subscription.new
70
+ sub.must_respond_to :request?
71
+ sub.type = :subscribe
72
+ sub.request?.must_equal true
73
+ end
74
+ end
@@ -0,0 +1,24 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. .. spec_helper])
2
+
3
+ describe 'Blather::Stanza::Presence' do
4
+ it 'registers itself' do
5
+ XMPPNode.class_from_registration(:presence, nil).must_equal Stanza::Presence
6
+ end
7
+
8
+ it 'ensures type is one of Stanza::Presence::VALID_TYPES' do
9
+ presence = Stanza::Presence.new
10
+ lambda { presence.type = :invalid_type_name }.must_raise(Blather::ArgumentError)
11
+
12
+ Stanza::Presence::VALID_TYPES.each do |valid_type|
13
+ presence.type = valid_type
14
+ presence.type.must_equal valid_type
15
+ end
16
+ end
17
+
18
+ Stanza::Presence::VALID_TYPES.each do |valid_type|
19
+ it "provides a helper (#{valid_type}?) for type #{valid_type}" do
20
+ Stanza::Presence.new.must_respond_to :"#{valid_type}?"
21
+ end
22
+ end
23
+
24
+ end
@@ -1,4 +1,4 @@
1
- require File.join(File.dirname(__FILE__), *%w[.. .. spec_helper])
1
+ 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
@@ -1,4 +1,4 @@
1
- require File.join(File.dirname(__FILE__), *%w[.. .. spec_helper])
1
+ require File.join(File.dirname(__FILE__), *%w[.. spec_helper])
2
2
 
3
3
  describe 'Blather::Stream' do
4
4
  class MockStream; include Stream; end
@@ -44,17 +44,108 @@ describe 'Blather::Stream' do
44
44
  s.connection_completed
45
45
  end
46
46
 
47
+ it 'sends stanzas to the client when the stream is ready' do
48
+ client = mock()
49
+ client.stubs(:jid=)
50
+ client.expects(:call)
51
+ stream = MockStream.new client, JID.new('n@d/r'), 'pass'
52
+
53
+ stream.expects(:send_data).with do |val|
54
+ val.must_match(/stream:stream/)
55
+ stream.receive_data "<stream:stream><message to='a@b/c' from='d@e/f' type='chat' xml:lang='en'><body>Message!</body></message>"
56
+ end
57
+ stream.connection_completed
58
+ end
59
+
60
+ it 'puts itself in the stopped state when unbound' do
61
+ stream = mock_stream do |val|
62
+ val.must_match(/stream:stream/)
63
+ stream.receive_data "<stream:stream>"
64
+
65
+ stream.stopped?.wont_equal true
66
+ stream.unbind
67
+ stream.stopped?.must_equal true
68
+ end
69
+ stream.connection_completed
70
+ end
71
+
72
+ it 'stops when sent </stream:stream>' do
73
+ state = nil
74
+ stream = mock_stream do |val|
75
+ case state
76
+ when nil
77
+ val.must_match(/stream:stream/)
78
+ state = :started
79
+
80
+ when :started
81
+ stream.stopped?.wont_equal true
82
+ state = :stopped
83
+ stream.receive_data "</stream:stream>"
84
+ true
85
+
86
+ when :stopped
87
+ stream.stopped?.must_equal true
88
+ val.must_equal "</stream:stream>"
89
+ true
90
+
91
+ else
92
+ false
93
+
94
+ end
95
+ end
96
+ stream.connection_completed
97
+ stream.receive_data "<stream:stream><stream:features><starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls' /></stream:features>"
98
+ end
99
+
100
+ it 'raises an error when it receives stream:error' do
101
+ lambda do
102
+ state = nil
103
+ stream = mock_stream do |val|
104
+ case state
105
+ when nil
106
+ val.must_match(/stream:stream/)
107
+ state = :started
108
+
109
+ when :started
110
+ stream.stopped?.wont_equal true
111
+ state = :stopped
112
+ stream.receive_data "<stream:error><conflict xmlns='urn:ietf:params:xml:ns:xmpp-streams'/></stream:error>"
113
+ true
114
+
115
+ when :stopped
116
+ stream.stopped?.must_equal true
117
+ val.must_equal "</stream:stream>"
118
+ true
119
+
120
+ else
121
+ false
122
+
123
+ end
124
+ end
125
+ stream.connection_completed
126
+ stream.receive_data "<stream:stream><stream:features><starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls' /></stream:features>"
127
+ end.must_raise(StreamError)
128
+ end
129
+
47
130
  it 'starts TLS when asked' do
48
131
  state = nil
49
- @stream = mock_stream do |val|
50
- case
51
- when state.nil? && val =~ /stream:stream/ then state = :started
52
- when state == :started && val =~ /starttls/ then true
53
- else false
132
+ stream = mock_stream do |val|
133
+ case state
134
+ when nil
135
+ val.must_match(/stream:stream/)
136
+ state = :started
137
+
138
+ when :started
139
+ val.must_match(/starttls/)
140
+ true
141
+
142
+ else
143
+ false
144
+
54
145
  end
55
146
  end
56
- @stream.connection_completed
57
- @stream.receive_data "<stream:stream><stream:features><starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls' /></stream:features>"
147
+ stream.connection_completed
148
+ stream.receive_data "<stream:stream><stream:features><starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls' /></stream:features>"
58
149
  end
59
150
 
60
151
  it 'connects via SASL MD5 when asked' do
@@ -80,7 +171,7 @@ describe 'Blather::Stream' do
80
171
  true
81
172
 
82
173
  when :auth_sent
83
- val.must_equal('<response xmlns="urn:ietf:params:xml:ns:xmpp-sasl">bm9uY2U9Ik9BNk1HOXRFUUdtMmhoIixjaGFyc2V0PXV0Zi04LHVzZXJuYW1lPSJuIixyZWFsbT0ic29tZXJlYWxtIixjbm9uY2U9Ijc3N2Q0NWJiYmNkZjUwZDQ5YzQyYzcwYWQ3YWNmNWZlIixuYz0wMDAwMDAwMSxxb3A9YXV0aCxkaWdlc3QtdXJpPSJ4bXBwL2Qi</response>')
174
+ val.must_equal('<response xmlns="urn:ietf:params:xml:ns:xmpp-sasl">bm9uY2U9Ik9BNk1HOXRFUUdtMmhoIixjaGFyc2V0PXV0Zi04LHVzZXJuYW1lPSJuIixyZWFsbT0ic29tZXJlYWxtIixjbm9uY2U9Ijc3N2Q0NWJiYmNkZjUwZDQ5YzQyYzcwYWQ3YWNmNWZlIixuYz0wMDAwMDAwMSxxb3A9YXV0aCxkaWdlc3QtdXJpPSJ4bXBwL2QiLHJlc3BvbnNlPTZiNTlhY2Q1ZWJmZjhjZTA0NTYzMGFiMDU2Zjg3MTdm</response>')
84
175
  state = :response1_sent
85
176
  stream.receive_data "<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>cnNwYXV0aD1lYTQwZjYwMzM1YzQyN2I1NTI3Yjg0ZGJhYmNkZmZmZAo=</challenge>"
86
177
  true
@@ -170,6 +261,112 @@ describe 'Blather::Stream' do
170
261
  stream.connection_completed
171
262
  end
172
263
 
264
+ it 'tried each possible mechanism until it fails completely' do
265
+ state = nil
266
+ client = mock()
267
+ client.stubs(:jid=)
268
+ stream = MockStream.new client, JID.new('n@d/r'), 'pass'
269
+
270
+ stream.expects(:send_data).times(5).with do |val|
271
+ case state
272
+ when nil
273
+ val.must_match(/stream:stream/)
274
+ state = :started
275
+ stream.receive_data "<stream:stream><stream:features><mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>DIGEST-MD5</mechanism><mechanism>PLAIN</mechanism><mechanism>ANONYMOUS</mechanism></mechanisms></stream:features>"
276
+ true
277
+
278
+ when :started
279
+ val.must_match(/mechanism="DIGEST-MD5"/)
280
+ state = :failed_md5
281
+ stream.receive_data "<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><not-authorized/></failure>"
282
+ true
283
+
284
+ when :failed_md5
285
+ val.must_match(/mechanism="PLAIN"/)
286
+ state = :failed_plain
287
+ stream.receive_data "<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><not-authorized/></failure>"
288
+ true
289
+
290
+ when :failed_plain
291
+ val.must_match(/mechanism="ANONYMOUS"/)
292
+ state = :failed_anon
293
+ stream.receive_data "<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><not-authorized/></failure>"
294
+ true
295
+
296
+ when :failed_anon
297
+ val.must_match(/\/stream:stream/)
298
+ state = :complete
299
+ true
300
+
301
+ else
302
+ false
303
+
304
+ end
305
+ end
306
+ stream.connection_completed
307
+ end
308
+
309
+ it 'tries each mechanism until it succeeds' do
310
+ state = nil
311
+ client = mock()
312
+ client.stubs(:jid=)
313
+ stream = MockStream.new client, JID.new('n@d/r'), 'pass'
314
+
315
+ stream.expects(:send_data).times(4).with do |val|
316
+ case state
317
+ when nil
318
+ val.must_match(/stream:stream/)
319
+ state = :started
320
+ stream.receive_data "<stream:stream><stream:features><mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>DIGEST-MD5</mechanism><mechanism>PLAIN</mechanism><mechanism>ANONYMOUS</mechanism></mechanisms></stream:features>"
321
+ true
322
+
323
+ when :started
324
+ val.must_match(/mechanism="DIGEST-MD5"/)
325
+ state = :failed_md5
326
+ stream.receive_data "<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><not-authorized/></failure>"
327
+ true
328
+
329
+ when :failed_md5
330
+ val.must_match(/mechanism="PLAIN"/)
331
+ state = :plain_sent
332
+ stream.receive_data "<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>"
333
+ true
334
+
335
+ when :plain_sent
336
+ val.must_match(/stream:stream/)
337
+ state = :complete
338
+ true
339
+
340
+ else
341
+ false
342
+
343
+ end
344
+ end
345
+ stream.connection_completed
346
+ end
347
+
348
+ it 'raises an exception when an unknown mechanism is sent' do
349
+ state = nil
350
+ client = mock()
351
+ client.stubs(:jid=)
352
+ stream = MockStream.new client, JID.new('n@d/r'), 'pass'
353
+
354
+ stream.expects(:send_data).times(2).with do |val|
355
+ if !state
356
+ state = :started
357
+ val.must_match(/stream:stream/)
358
+ lambda do
359
+ stream.receive_data "<stream:stream><stream:features><mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>UNKNOWN</mechanism></mechanisms></stream:features>"
360
+ end.must_raise(Stream::SASL::UnknownMechanism)
361
+
362
+ else
363
+ val.must_match(/failure(.*)invalid\-mechanism/)
364
+
365
+ end
366
+ end
367
+ stream.connection_completed
368
+ end
369
+
173
370
  it 'will bind to a resource set by the server' do
174
371
  state = nil
175
372
  class Client; attr_accessor :jid; end
@@ -240,6 +437,7 @@ describe 'Blather::Stream' do
240
437
  client.stubs(:jid=)
241
438
  stream = MockStream.new client, JID.new('n@d/r'), 'pass'
242
439
 
440
+ client.expects(:stream_started)
243
441
  stream.expects(:send_data).times(2).with do |val|
244
442
  case state
245
443
  when nil
@@ -251,6 +449,7 @@ describe 'Blather::Stream' do
251
449
  when :started
252
450
  val.must_match('<iq id="[^"]+" type="set" to="d"><session xmlns="urn:ietf:params:xml:ns:xmpp-session"/></iq>')
253
451
  state = :completed
452
+ stream.receive_data "<iq from='d' type='result' id='#{val[/id="([^"]+)"/,1]}'/>"
254
453
  true
255
454
 
256
455
  else