shingara-blather 0.4.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. data/LICENSE +22 -0
  2. data/README.md +162 -0
  3. data/examples/echo.rb +18 -0
  4. data/examples/execute.rb +16 -0
  5. data/examples/ping_pong.rb +37 -0
  6. data/examples/print_hierarchy.rb +76 -0
  7. data/examples/rosterprint.rb +14 -0
  8. data/examples/stream_only.rb +27 -0
  9. data/examples/xmpp4r/echo.rb +35 -0
  10. data/lib/blather/client/client.rb +310 -0
  11. data/lib/blather/client/dsl/pubsub.rb +170 -0
  12. data/lib/blather/client/dsl.rb +264 -0
  13. data/lib/blather/client.rb +87 -0
  14. data/lib/blather/core_ext/nokogiri.rb +40 -0
  15. data/lib/blather/errors/sasl_error.rb +43 -0
  16. data/lib/blather/errors/stanza_error.rb +107 -0
  17. data/lib/blather/errors/stream_error.rb +82 -0
  18. data/lib/blather/errors.rb +69 -0
  19. data/lib/blather/jid.rb +142 -0
  20. data/lib/blather/roster.rb +111 -0
  21. data/lib/blather/roster_item.rb +122 -0
  22. data/lib/blather/stanza/disco/disco_info.rb +176 -0
  23. data/lib/blather/stanza/disco/disco_items.rb +132 -0
  24. data/lib/blather/stanza/disco.rb +25 -0
  25. data/lib/blather/stanza/iq/query.rb +53 -0
  26. data/lib/blather/stanza/iq/roster.rb +179 -0
  27. data/lib/blather/stanza/iq.rb +138 -0
  28. data/lib/blather/stanza/message.rb +332 -0
  29. data/lib/blather/stanza/presence/status.rb +212 -0
  30. data/lib/blather/stanza/presence/subscription.rb +101 -0
  31. data/lib/blather/stanza/presence.rb +163 -0
  32. data/lib/blather/stanza/pubsub/affiliations.rb +79 -0
  33. data/lib/blather/stanza/pubsub/create.rb +65 -0
  34. data/lib/blather/stanza/pubsub/errors.rb +18 -0
  35. data/lib/blather/stanza/pubsub/event.rb +123 -0
  36. data/lib/blather/stanza/pubsub/items.rb +103 -0
  37. data/lib/blather/stanza/pubsub/publish.rb +103 -0
  38. data/lib/blather/stanza/pubsub/retract.rb +92 -0
  39. data/lib/blather/stanza/pubsub/subscribe.rb +68 -0
  40. data/lib/blather/stanza/pubsub/subscription.rb +134 -0
  41. data/lib/blather/stanza/pubsub/subscriptions.rb +81 -0
  42. data/lib/blather/stanza/pubsub/unsubscribe.rb +68 -0
  43. data/lib/blather/stanza/pubsub.rb +129 -0
  44. data/lib/blather/stanza/pubsub_owner/delete.rb +52 -0
  45. data/lib/blather/stanza/pubsub_owner/purge.rb +52 -0
  46. data/lib/blather/stanza/pubsub_owner.rb +51 -0
  47. data/lib/blather/stanza.rb +149 -0
  48. data/lib/blather/stream/client.rb +31 -0
  49. data/lib/blather/stream/component.rb +38 -0
  50. data/lib/blather/stream/features/resource.rb +63 -0
  51. data/lib/blather/stream/features/sasl.rb +187 -0
  52. data/lib/blather/stream/features/session.rb +44 -0
  53. data/lib/blather/stream/features/tls.rb +28 -0
  54. data/lib/blather/stream/features.rb +53 -0
  55. data/lib/blather/stream/parser.rb +102 -0
  56. data/lib/blather/stream.rb +231 -0
  57. data/lib/blather/xmpp_node.rb +218 -0
  58. data/lib/blather.rb +78 -0
  59. data/spec/blather/client/client_spec.rb +559 -0
  60. data/spec/blather/client/dsl/pubsub_spec.rb +462 -0
  61. data/spec/blather/client/dsl_spec.rb +143 -0
  62. data/spec/blather/core_ext/nokogiri_spec.rb +83 -0
  63. data/spec/blather/errors/sasl_error_spec.rb +33 -0
  64. data/spec/blather/errors/stanza_error_spec.rb +129 -0
  65. data/spec/blather/errors/stream_error_spec.rb +108 -0
  66. data/spec/blather/errors_spec.rb +33 -0
  67. data/spec/blather/jid_spec.rb +87 -0
  68. data/spec/blather/roster_item_spec.rb +96 -0
  69. data/spec/blather/roster_spec.rb +103 -0
  70. data/spec/blather/stanza/discos/disco_info_spec.rb +226 -0
  71. data/spec/blather/stanza/discos/disco_items_spec.rb +148 -0
  72. data/spec/blather/stanza/iq/query_spec.rb +64 -0
  73. data/spec/blather/stanza/iq/roster_spec.rb +140 -0
  74. data/spec/blather/stanza/iq_spec.rb +45 -0
  75. data/spec/blather/stanza/message_spec.rb +132 -0
  76. data/spec/blather/stanza/presence/status_spec.rb +132 -0
  77. data/spec/blather/stanza/presence/subscription_spec.rb +105 -0
  78. data/spec/blather/stanza/presence_spec.rb +66 -0
  79. data/spec/blather/stanza/pubsub/affiliations_spec.rb +57 -0
  80. data/spec/blather/stanza/pubsub/create_spec.rb +56 -0
  81. data/spec/blather/stanza/pubsub/event_spec.rb +84 -0
  82. data/spec/blather/stanza/pubsub/items_spec.rb +79 -0
  83. data/spec/blather/stanza/pubsub/publish_spec.rb +83 -0
  84. data/spec/blather/stanza/pubsub/retract_spec.rb +75 -0
  85. data/spec/blather/stanza/pubsub/subscribe_spec.rb +61 -0
  86. data/spec/blather/stanza/pubsub/subscription_spec.rb +97 -0
  87. data/spec/blather/stanza/pubsub/subscriptions_spec.rb +59 -0
  88. data/spec/blather/stanza/pubsub/unsubscribe_spec.rb +61 -0
  89. data/spec/blather/stanza/pubsub_owner/delete_spec.rb +50 -0
  90. data/spec/blather/stanza/pubsub_owner/purge_spec.rb +50 -0
  91. data/spec/blather/stanza/pubsub_owner_spec.rb +27 -0
  92. data/spec/blather/stanza/pubsub_spec.rb +67 -0
  93. data/spec/blather/stanza_spec.rb +116 -0
  94. data/spec/blather/stream/client_spec.rb +1011 -0
  95. data/spec/blather/stream/component_spec.rb +95 -0
  96. data/spec/blather/stream/parser_spec.rb +145 -0
  97. data/spec/blather/xmpp_node_spec.rb +231 -0
  98. data/spec/fixtures/pubsub.rb +311 -0
  99. data/spec/spec_helper.rb +43 -0
  100. metadata +249 -0
@@ -0,0 +1,1011 @@
1
+ require 'resolv'
2
+ require File.join(File.dirname(__FILE__), *%w[.. .. spec_helper])
3
+
4
+ describe Blather::Stream::Client do
5
+ class MockServer; end
6
+ module ServerMock
7
+ def receive_data(data)
8
+ @server ||= MockServer.new
9
+ @server.receive_data data, self
10
+ end
11
+ end
12
+
13
+ def mocked_server(times = nil, &block)
14
+ @client ||= mock()
15
+ @client.stubs(:unbind) unless @client.respond_to?(:unbind)
16
+ @client.stubs(:post_init) unless @client.respond_to?(:post_init)
17
+ @client.stubs(:jid=) unless @client.respond_to?(:jid=)
18
+
19
+ MockServer.any_instance.expects(:receive_data).send(*(times ? [:times, times] : [:at_least, 1])).with &block
20
+ EventMachine::run {
21
+ EM.add_timer(0.5) { EM.stop if EM.reactor_running? }
22
+
23
+ # Mocked server
24
+ EventMachine::start_server '127.0.0.1', 12345, ServerMock
25
+
26
+ # Blather::Stream connection
27
+ EM.connect('127.0.0.1', 12345, Blather::Stream::Client, @client, @jid || Blather::JID.new('n@d/r'), 'pass') { |c| @stream = c }
28
+ }
29
+ end
30
+
31
+
32
+ it 'can be started' do
33
+ client = mock()
34
+ params = [client, 'n@d/r', 'pass', 'host', 1234]
35
+ EM.expects(:connect).with do |*parms|
36
+ parms[0].must_equal 'host'
37
+ parms[1].must_equal 1234
38
+ parms[3].must_equal client
39
+ parms[5].must_equal 'pass'
40
+ parms[4].must_equal Blather::JID.new('n@d/r')
41
+ end
42
+
43
+ Blather::Stream::Client.start *params
44
+ end
45
+
46
+ it 'attempts to find the SRV record if a host is not provided' do
47
+ dns = mock(:sort! => nil, :empty? => false)
48
+ dns.expects(:each).yields(mock({
49
+ :target => 'd',
50
+ :port => 5222
51
+ }))
52
+ Resolv::DNS.expects(:open).yields(mock(:getresources => dns))
53
+
54
+ client = Class.new
55
+ EM.expects(:connect).with do |*parms|
56
+ parms[0].must_equal 'd'
57
+ parms[1].must_equal 5222
58
+ parms[3].must_equal client
59
+ parms[5].must_equal 'pass'
60
+ parms[4].must_equal Blather::JID.new('n@d/r')
61
+ end
62
+
63
+ Blather::Stream::Client.start client, 'n@d/r', 'pass'
64
+ end
65
+
66
+ it 'will attempt as many connections as it takes' do
67
+ dns = [mock(:target => 'd', :port => 5222), mock(:target => 'g', :port => 1234)]
68
+ dns.stubs(:sort!) #ignore sorting
69
+ Resolv::DNS.expects(:open).yields(mock(:getresources => dns))
70
+
71
+ client = Class.new
72
+ EM.expects(:connect).with do |*parms|
73
+ raise Blather::Stream::NoConnection if parms[0] == 'd'
74
+ parms[0].must_equal 'g'
75
+ parms[1].must_equal 1234
76
+ parms[3].must_equal client
77
+ parms[5].must_equal 'pass'
78
+ parms[4].must_equal Blather::JID.new('n@d/r')
79
+ end
80
+ Blather::Stream::Client.start client, 'n@d/r', 'pass'
81
+ end
82
+
83
+ it 'will not attempt to connect more often than necessary' do
84
+ dns = [mock(:target => 'd', :port => 5222), mock()]
85
+ dns.stubs(:sort!) #ignore sorting
86
+ Resolv::DNS.expects(:open).yields(mock(:getresources => dns))
87
+
88
+ client = Class.new
89
+ EM.expects(:connect).with do |*parms|
90
+ parms[0].must_equal 'd'
91
+ parms[1].must_equal 5222
92
+ parms[3].must_equal client
93
+ parms[5].must_equal 'pass'
94
+ parms[4].must_equal Blather::JID.new('n@d/r')
95
+ end
96
+ Blather::Stream::Client.start client, 'n@d/r', 'pass'
97
+ end
98
+
99
+ it 'can figure out the host to use based on the jid' do
100
+ Resolv::DNS.expects(:open).yields(mock(:getresources => mock(:empty? => true)))
101
+ client = Class.new
102
+ params = [client, 'n@d/r', 'pass', nil, 5222]
103
+ EM.expects(:connect).with do |*parms|
104
+ parms[0].must_equal 'd'
105
+ parms[1].must_equal 5222
106
+ parms[3].must_equal client
107
+ parms[5].must_equal 'pass'
108
+ parms[4].must_equal Blather::JID.new('n@d/r')
109
+ end
110
+
111
+ Blather::Stream::Client.start client, 'n@d/r', 'pass'
112
+ end
113
+
114
+ it 'starts the stream once the connection is complete' do
115
+ mocked_server(1) { |val, _| EM.stop; val.must_match(/stream:stream/) }
116
+ end
117
+
118
+ it 'sends stanzas to the client when the stream is ready' do
119
+ @client = mock()
120
+ @client.expects(:receive_data).with do |n|
121
+ EM.stop
122
+ n.must_be_kind_of Blather::Stanza::Message
123
+ end
124
+
125
+ mocked_server(1) do |val, server|
126
+ val.must_match(/stream:stream/)
127
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
128
+ server.send_data "<message to='a@b/c' from='d@e/f' type='chat' xml:lang='en'><body>Message!</body></message>"
129
+ end
130
+ end
131
+
132
+ it 'puts itself in the stopped state and calls @client.unbind when unbound' do
133
+ @client = mock()
134
+ @client.expects(:unbind).at_least_once
135
+
136
+ started = false
137
+ mocked_server(2) do |val, server|
138
+ if !started
139
+ started = true
140
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
141
+ server.send_data "<stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' /></stream:features>"
142
+ val.must_match(/stream:stream/)
143
+
144
+ else
145
+ EM.stop
146
+ @stream.stopped?.must_equal false
147
+ @stream.unbind
148
+ @stream.stopped?.must_equal true
149
+
150
+ end
151
+ end
152
+ end
153
+
154
+ it 'will be in the negotiating state during feature negotiations' do
155
+ state = nil
156
+ @client = mock()
157
+ @client.expects(:receive_data).with do |n|
158
+ EM.stop
159
+ state.must_equal(:negotiated) && @stream.negotiating?.must_equal(false)
160
+ end
161
+
162
+ mocked_server(2) do |val, server|
163
+ case state
164
+ when nil
165
+ state = :started
166
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
167
+ server.send_data "<stream:features><session xmlns='urn:ietf:params:xml:ns:xmpp-session' /></stream:features>"
168
+ true
169
+
170
+ when :started
171
+ state = :negotiated
172
+ @stream.negotiating?.must_equal true
173
+ server.send_data "<iq from='d' type='result' id='#{val[/id="([^"]+)"/,1]}' />"
174
+ server.send_data "<message to='a@b/c' from='d@e/f' type='chat' xml:lang='en'><body>Message!</body></message>"
175
+ true
176
+
177
+ else
178
+ EM.stop
179
+ false
180
+
181
+ end
182
+ end
183
+ end
184
+
185
+ it 'stops when sent </stream:stream>' do
186
+ state = nil
187
+ mocked_server(3) do |val, server|
188
+ case state
189
+ when nil
190
+ state = :started
191
+ 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'>"
192
+ server.send_data "<stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' /></stream:features>"
193
+ val.must_match(/stream:stream/)
194
+
195
+ when :started
196
+ state = :stopped
197
+ server.send_data '</stream:stream>'
198
+ @stream.stopped?.must_equal false
199
+
200
+ when :stopped
201
+ EM.stop
202
+ @stream.stopped?.must_equal true
203
+ val.must_equal '</stream:stream>'
204
+
205
+ else
206
+ EM.stop
207
+ false
208
+
209
+ end
210
+ end
211
+ end
212
+
213
+ it 'sends client an error on stream:error' do
214
+ @client = mock()
215
+ @client.expects(:receive_data).with do |v|
216
+ v.name.must_equal :conflict
217
+ v.text.must_equal 'Already signed in'
218
+ v.to_s.must_equal "Stream Error (conflict): #{v.text}"
219
+ end
220
+
221
+ state = nil
222
+ mocked_server(3) do |val, server|
223
+ case state
224
+ when nil
225
+ state = :started
226
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>"
227
+ server.send_data "<stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' /></stream:features>"
228
+ val.must_match(/stream:stream/)
229
+
230
+ when :started
231
+ state = :stopped
232
+ server.send_data "<stream:error><conflict xmlns='urn:ietf:params:xml:ns:xmpp-streams' />"
233
+ server.send_data "<text xmlns='urn:ietf:params:xml:ns:xmpp-streams'>Already signed in</text></stream:error>"
234
+
235
+ when :stopped
236
+ EM.stop
237
+ val.must_equal "</stream:stream>"
238
+
239
+ else
240
+ EM.stop
241
+ false
242
+
243
+ end
244
+ end
245
+ end
246
+
247
+ it 'skips features it is unable to handle' do
248
+ state = nil
249
+ mocked_server() do |val, server|
250
+ case state
251
+ when nil
252
+ state = :started
253
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
254
+ server.send_data "<stream:features><auth xmlns='http://jabber.org/features/iq-auth'/><starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls' /></stream:features>"
255
+ val.must_match(/stream:stream/)
256
+
257
+ when :started
258
+ EM.stop
259
+ val.must_match(/starttls/)
260
+
261
+ else
262
+ EM.stop
263
+ false
264
+
265
+ end
266
+ end
267
+ end
268
+
269
+ it 'starts TLS when asked' do
270
+ state = nil
271
+ mocked_server(3) do |val, server|
272
+ case state
273
+ when nil
274
+ state = :started
275
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
276
+ server.send_data "<stream:features><starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls' /></stream:features>"
277
+ val.must_match(/stream:stream/)
278
+
279
+ when :started
280
+ state = :tls
281
+ @stream.expects(:start_tls)
282
+ server.send_data "<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>"
283
+ val.must_match(/starttls/)
284
+
285
+ when :tls
286
+ EM.stop
287
+ true
288
+
289
+ else
290
+ EM.stop
291
+ false
292
+
293
+ end
294
+ end
295
+ end
296
+
297
+ it 'will fail if TLS negotiation fails' do
298
+ state = nil
299
+ @client = mock()
300
+ @client.expects(:receive_data).with { |v| v.must_be_kind_of Blather::Stream::TLS::TLSFailure }
301
+ mocked_server(3) do |val, server|
302
+ case state
303
+ when nil
304
+ state = :started
305
+ 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>"
306
+ val.must_match(/stream:stream/)
307
+
308
+ when :started
309
+ state = :tls
310
+ @stream.expects(:start_tls).never
311
+ server.send_data "<failure xmlns='urn:ietf:params:xml:ns:xmpp-tls'/></stream:stream>"
312
+ val.must_match(/starttls/)
313
+
314
+ when :tls
315
+ EM.stop
316
+ val.must_equal "</stream:stream>"
317
+
318
+ else
319
+ EM.stop
320
+ false
321
+
322
+ end
323
+ end
324
+ end
325
+
326
+ it 'will fail if a bad node comes through TLS negotiations' do
327
+ state = nil
328
+ @client = mock()
329
+ @client.expects(:receive_data).with do |v|
330
+ v.must_be_kind_of Blather::Stream::TLS::TLSFailure
331
+ end
332
+ mocked_server(3) do |val, server|
333
+ case state
334
+ when nil
335
+ state = :started
336
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
337
+ server.send_data "<stream:features><starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls' /></stream:features>"
338
+ val.must_match(/stream:stream/)
339
+
340
+ when :started
341
+ state = :tls
342
+ @stream.expects(:start_tls).never
343
+ server.send_data "<foo-bar xmlns='urn:ietf:params:xml:ns:xmpp-tls'/></stream:stream>"
344
+ val.must_match(/starttls/)
345
+
346
+ when :tls
347
+ EM.stop
348
+ val.must_equal "</stream:stream>"
349
+
350
+ else
351
+ EM.stop
352
+ false
353
+
354
+ end
355
+ end
356
+ end
357
+
358
+ it 'connects via SASL MD5 when asked' do
359
+ Time.any_instance.stubs(:to_f).returns(1.1)
360
+ state = nil
361
+
362
+ mocked_server(5) do |val, server|
363
+ case state
364
+ when nil
365
+ state = :started
366
+ 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>"
367
+ val.must_match(/stream:stream/)
368
+
369
+ when :started
370
+ state = :auth_sent
371
+ server.send_data "<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>cmVhbG09InNvbWVyZWFsbSIsbm9uY2U9Ik9BNk1HOXRFUUdtMmhoIixxb3A9ImF1dGgiLGNoYXJzZXQ9dXRmLTgsYWxnb3JpdGhtPW1kNS1zZXNzCg==</challenge>"
372
+ val.must_match(/auth.*DIGEST\-MD5/)
373
+
374
+ when :auth_sent
375
+ state = :response1_sent
376
+ server.send_data "<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>cnNwYXV0aD1lYTQwZjYwMzM1YzQyN2I1NTI3Yjg0ZGJhYmNkZmZmZAo=</challenge>"
377
+ val.must_equal('<response xmlns="urn:ietf:params:xml:ns:xmpp-sasl">bm9uY2U9Ik9BNk1HOXRFUUdtMmhoIixjaGFyc2V0PXV0Zi04LHVzZXJuYW1lPSJuIixyZWFsbT0ic29tZXJlYWxtIixjbm9uY2U9Ijc3N2Q0NWJiYmNkZjUwZDQ5YzQyYzcwYWQ3YWNmNWZlIixuYz0wMDAwMDAwMSxxb3A9YXV0aCxkaWdlc3QtdXJpPSJ4bXBwL2QiLHJlc3BvbnNlPTZiNTlhY2Q1ZWJmZjhjZTA0NTYzMGFiMDU2Zjg3MTdm</response>')
378
+
379
+ when :response1_sent
380
+ state = :response2_sent
381
+ server.send_data "<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />"
382
+ val.must_match(%r{<response xmlns="urn:ietf:params:xml:ns:xmpp-sasl"\s?/>})
383
+
384
+ when :response2_sent
385
+ EM.stop
386
+ state = :complete
387
+ val.must_match(/stream:stream/)
388
+
389
+ else
390
+ EM.stop
391
+ false
392
+
393
+ end
394
+ end
395
+ end
396
+
397
+ it 'will connect via SSL PLAIN when asked' do
398
+ state = nil
399
+ mocked_server(3) do |val, server|
400
+ case state
401
+ when nil
402
+ state = :started
403
+ 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>"
404
+ val.must_match(/stream:stream/)
405
+
406
+ when :started
407
+ state = :auth_sent
408
+ server.send_data "<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />"
409
+ val.must_equal('<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="PLAIN">bkBkAG4AcGFzcw==</auth>')
410
+
411
+ when :auth_sent
412
+ EM.stop
413
+ state = :complete
414
+ val.must_match(/stream:stream/)
415
+
416
+ else
417
+ EM.stop
418
+ false
419
+
420
+ end
421
+ end
422
+ end
423
+
424
+ it 'will connect via SSL ANONYMOUS when asked' do
425
+ state = nil
426
+
427
+ mocked_server(3) do |val, server|
428
+ case state
429
+ when nil
430
+ state = :started
431
+ 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>"
432
+ val.must_match(/stream:stream/)
433
+
434
+ when :started
435
+ state = :auth_sent
436
+ server.send_data "<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />"
437
+ val.must_equal('<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="ANONYMOUS"/>')
438
+
439
+ when :auth_sent
440
+ EM.stop
441
+ state = :complete
442
+ val.must_match(/stream:stream/)
443
+
444
+ else
445
+ EM.stop
446
+ false
447
+
448
+ end
449
+ end
450
+ end
451
+
452
+ it 'connects via ANONYMOUS if the Blather::JID has a blank node' do
453
+ state = nil
454
+ @jid = Blather::JID.new '@d'
455
+
456
+ mocked_server(3) do |val, server|
457
+ case state
458
+ when nil
459
+ state = :started
460
+ 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>"
461
+ val.must_match(/stream:stream/)
462
+
463
+ when :started
464
+ state = :auth_sent
465
+ server.send_data "<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />"
466
+ val.must_equal('<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="ANONYMOUS"/>')
467
+
468
+ when :auth_sent
469
+ EM.stop
470
+ state = :complete
471
+ val.must_match(/stream:stream/)
472
+
473
+ else
474
+ EM.stop
475
+ false
476
+
477
+ end
478
+ end
479
+ end
480
+
481
+ it 'fails if asked to connect via ANONYMOUS but the server does not support it' do
482
+ state = nil
483
+ @jid = Blather::JID.new '@d'
484
+ @client = mock()
485
+ @client.expects(:receive_data).with { |s| s.must_be_instance_of Blather::BlatherError }
486
+
487
+ mocked_server(2) do |val, server|
488
+ case state
489
+ when nil
490
+ state = :started
491
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
492
+ server.send_data "<stream:features><mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>DIGEST-MD5</mechanism><mechanism>PLAIN</mechanism></mechanisms></stream:features>"
493
+ val.must_match(/stream:stream/)
494
+
495
+ when :started
496
+ EM.stop
497
+ val.must_match(/stream:stream/)
498
+
499
+ else
500
+ EM.stop
501
+ false
502
+
503
+ end
504
+ end
505
+ end
506
+
507
+ it 'tries each possible mechanism until it fails completely' do
508
+ state = nil
509
+ @client = mock()
510
+ @client.expects(:receive_data).with do |n|
511
+ n.must_be_kind_of(Blather::SASLError)
512
+ n.name.must_equal :not_authorized
513
+ end
514
+
515
+ mocked_server(5) do |val, server|
516
+ case state
517
+ when nil
518
+ state = :started
519
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
520
+ server.send_data "<stream:features><mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>DIGEST-MD5</mechanism><mechanism>PLAIN</mechanism><mechanism>ANONYMOUS</mechanism></mechanisms></stream:features>"
521
+ val.must_match(/stream:stream/)
522
+
523
+ when :started
524
+ state = :failed_md5
525
+ server.send_data "<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><not-authorized /></failure>"
526
+ val.must_match(/mechanism="DIGEST-MD5"/)
527
+
528
+ when :failed_md5
529
+ state = :failed_plain
530
+ server.send_data "<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><not-authorized /></failure>"
531
+ val.must_match(/mechanism="PLAIN"/)
532
+
533
+ when :failed_plain
534
+ state = :failed_anon
535
+ server.send_data "<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><not-authorized /></failure>"
536
+ val.must_match(/mechanism="ANONYMOUS"/)
537
+
538
+ when :failed_anon
539
+ EM.stop
540
+ state = :complete
541
+ val.must_match(/\/stream:stream/)
542
+
543
+ else
544
+ EM.stop
545
+ false
546
+
547
+ end
548
+ end
549
+ end
550
+
551
+ it 'tries each mechanism until it succeeds' do
552
+ state = nil
553
+ mocked_server(4) do |val, server|
554
+ case state
555
+ when nil
556
+ state = :started
557
+ 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>"
558
+ val.must_match(/stream:stream/)
559
+
560
+ when :started
561
+ state = :failed_md5
562
+ server.send_data "<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><not-authorized /></failure>"
563
+ val.must_match(/mechanism="DIGEST-MD5"/)
564
+
565
+ when :failed_md5
566
+ state = :plain_sent
567
+ server.send_data "<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />"
568
+ val.must_match(/mechanism="PLAIN"/)
569
+
570
+ when :plain_sent
571
+ EM.stop
572
+ val.must_match(/stream:stream/)
573
+
574
+ else
575
+ EM.stop
576
+ false
577
+
578
+ end
579
+ end
580
+ end
581
+
582
+ it 'will ignore methods it does not understand' do
583
+ state = nil
584
+ mocked_server(3) do |val, server|
585
+ case state
586
+ when nil
587
+ state = :started
588
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
589
+ server.send_data "<stream:features><mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>CRAM-MD5</mechanism><mechanism>PLAIN</mechanism></mechanisms></stream:features>"
590
+ val.must_match(/stream:stream/)
591
+
592
+ when :started
593
+ state = :auth_sent
594
+ server.send_data "<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />"
595
+ val.must_equal('<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="PLAIN">bkBkAG4AcGFzcw==</auth>')
596
+
597
+ when :auth_sent
598
+ EM.stop
599
+ state = :complete
600
+ val.must_match(/stream:stream/)
601
+
602
+ else
603
+ EM.stop
604
+ false
605
+
606
+ end
607
+ end
608
+ end
609
+ =begin
610
+ it 'sends client an error when an unknown mechanism is sent' do
611
+ @client = mock()
612
+ @client.expects(:receive_data).with { |v| v.must_be_kind_of(Blather::Stream::SASL::UnknownMechanism) }
613
+ started = false
614
+ mocked_server(2) do |val, server|
615
+ if !started
616
+ started = true
617
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
618
+ server.send_data "<stream:features><mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>UNKNOWN</mechanism></mechanisms></stream:features>"
619
+ val.must_match(/stream:stream/)
620
+
621
+ else
622
+ EM.stop
623
+ val.must_match(/failure(.*)invalid\-mechanism/)
624
+
625
+ end
626
+ end
627
+ end
628
+ =end
629
+ %w[ aborted
630
+ incorrect-encoding
631
+ invalid-authzid
632
+ invalid-mechanism
633
+ mechanism-too-weak
634
+ not-authorized
635
+ temporary-auth-failure
636
+ ].each do |error_type|
637
+ it "fails on #{error_type}" do
638
+ @client = mock()
639
+ @client.expects(:receive_data).with do |n|
640
+ n.name.must_equal error_type.gsub('-','_').to_sym
641
+ end
642
+ state = nil
643
+ mocked_server(3) do |val, server|
644
+ case state
645
+ when nil
646
+ state = :started
647
+ 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>"
648
+ val.must_match(/stream:stream/)
649
+
650
+ when :started
651
+ state = :auth_sent
652
+ server.send_data "<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><#{error_type} /></failure>"
653
+ val.must_equal('<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="PLAIN">bkBkAG4AcGFzcw==</auth>')
654
+
655
+ when :auth_sent
656
+ EM.stop
657
+ state = :complete
658
+ val.must_match(/\/stream:stream/)
659
+
660
+ else
661
+ EM.stop
662
+ false
663
+
664
+ end
665
+ end
666
+ end
667
+ end
668
+
669
+ it 'fails when an unkown node comes through during SASL negotiation' do
670
+ @client = mock()
671
+ @client.expects(:receive_data).with do |n|
672
+ n.must_be_instance_of Blather::UnknownResponse
673
+ n.node.element_name.must_equal 'foo-bar'
674
+ end
675
+ state = nil
676
+ mocked_server(3) do |val, server|
677
+ case state
678
+ when nil
679
+ state = :started
680
+ 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>"
681
+ val.must_match(/stream:stream/)
682
+
683
+ when :started
684
+ state = :auth_sent
685
+ server.send_data "<foo-bar />"
686
+ val.must_equal('<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="PLAIN">bkBkAG4AcGFzcw==</auth>')
687
+
688
+ when :auth_sent
689
+ EM.stop
690
+ state = :complete
691
+ val.must_match(/\/stream:stream/)
692
+
693
+ else
694
+ EM.stop
695
+ false
696
+
697
+ end
698
+ end
699
+ end
700
+
701
+ it 'will bind to a resource set by the server' do
702
+ state = nil
703
+ class Client; attr_accessor :jid; end
704
+ @client = Client.new
705
+ @jid = Blather::JID.new('n@d')
706
+
707
+ mocked_server(3) do |val, server|
708
+ case state
709
+ when nil
710
+ state = :started
711
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
712
+ server.send_data "<stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' /></stream:features>"
713
+ val.must_match(/stream:stream/)
714
+
715
+ when :started
716
+ state = :complete
717
+ val =~ %r{<iq[^>]+id="([^"]+)"}
718
+ server.send_data "<iq type='result' id='#{$1}'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><jid>#{@jid}/server_resource</jid></bind></iq>"
719
+ server.send_data "<stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' /></stream:features>"
720
+ val.must_match(%r{<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"\s?/>})
721
+
722
+ when :complete
723
+ EM.stop
724
+ @stream.jid.must_equal Blather::JID.new('n@d/server_resource')
725
+
726
+ else
727
+ EM.stop
728
+ false
729
+
730
+ end
731
+ end
732
+ end
733
+
734
+ it 'will bind to a resource set by the client' do
735
+ state = nil
736
+ class Client; attr_accessor :jid; end
737
+ @client = Client.new
738
+ @jid = Blather::JID.new('n@d/r')
739
+
740
+ mocked_server(3) do |val, server|
741
+ case state
742
+ when nil
743
+ state = :started
744
+ 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>"
745
+ val.must_match(/stream:stream/)
746
+
747
+ when :started
748
+ state = :complete
749
+ doc = parse_stanza val
750
+ doc.xpath('/iq/bind_ns:bind/bind_ns:resource[.="r"]', :bind_ns => Blather::Stream::Resource::BIND_NS).wont_be_empty
751
+
752
+ server.send_data "<iq type='result' id='#{doc.find_first('iq')['id']}'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><jid>#{@jid}</jid></bind></iq>"
753
+ server.send_data "<stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' /></stream:features>"
754
+
755
+ when :complete
756
+ EM.stop
757
+ @stream.jid.must_equal Blather::JID.new('n@d/r')
758
+
759
+ else
760
+ EM.stop
761
+ false
762
+
763
+ end
764
+ end
765
+ end
766
+
767
+ it 'will error out if the bind ID mismatches' do
768
+ state = nil
769
+ @jid = Blather::JID.new('n@d')
770
+ @client = mock()
771
+
772
+ mocked_server(3) do |val, server|
773
+ case state
774
+ when nil
775
+ state = :started
776
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
777
+ server.send_data "<stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' /></stream:features>"
778
+ val.must_match(/stream:stream/)
779
+
780
+ when :started
781
+ state = :complete
782
+ val =~ %r{<iq[^>]+id="([^"]+)"}
783
+ @client.expects(:receive_data).with("BIND result ID mismatch. Expected: #{$1}. Received: #{$1}-bad")
784
+ server.send_data "<iq type='result' id='#{$1}-bad'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><jid>#{@jid}/server_resource</jid></bind></iq>"
785
+ val.must_match(%r{<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"\s?/>})
786
+
787
+ when :complete
788
+ EM.stop
789
+ true
790
+
791
+ else
792
+ EM.stop
793
+ false
794
+
795
+ end
796
+ end
797
+ end
798
+
799
+ it 'will return an error if resource binding errors out' do
800
+ state = nil
801
+ @client = mock()
802
+ @client.expects(:receive_data).with do |n|
803
+ n.name.must_equal :bad_request
804
+ end
805
+ mocked_server(3) do |val, server|
806
+ case state
807
+ when nil
808
+ state = :started
809
+ 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>"
810
+ val.must_match(/stream:stream/)
811
+
812
+ when :started
813
+ state = :complete
814
+ doc = parse_stanza val
815
+ doc.xpath('/iq/bind_ns:bind/bind_ns:resource[.="r"]', :bind_ns => Blather::Stream::Resource::BIND_NS).wont_be_empty
816
+ server.send_data "<iq type='error' id='#{doc.find_first('iq')['id']}'><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>"
817
+
818
+ when :complete
819
+ EM.stop
820
+ val.must_match(/\/stream:stream/)
821
+
822
+ else
823
+ EM.stop
824
+ false
825
+
826
+ end
827
+ end
828
+ end
829
+
830
+ it 'will return an error if an unkown node comes through during resouce binding' do
831
+ state = nil
832
+ @client = mock()
833
+ @client.expects(:receive_data).with do |n|
834
+ n.must_be_instance_of Blather::UnknownResponse
835
+ n.node.element_name.must_equal 'foo-bar'
836
+ end
837
+ mocked_server(3) do |val, server|
838
+ case state
839
+ when nil
840
+ state = :started
841
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
842
+ server.send_data "<stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' /></stream:features>"
843
+ val.must_match(/stream:stream/)
844
+
845
+ when :started
846
+ state = :complete
847
+ doc = parse_stanza val
848
+ doc.xpath('/iq/bind_ns:bind/bind_ns:resource[.="r"]', :bind_ns => Blather::Stream::Resource::BIND_NS).wont_be_empty
849
+ server.send_data "<foo-bar />"
850
+
851
+ when :complete
852
+ EM.stop
853
+ val.must_match(/\/stream:stream/)
854
+
855
+ else
856
+ EM.stop
857
+ false
858
+
859
+ end
860
+ end
861
+ end
862
+
863
+ it 'will establish a session if requested' do
864
+ state = nil
865
+ @client = mock()
866
+ @client.expects(:post_init)
867
+
868
+ mocked_server(3) do |val, server|
869
+ case state
870
+ when nil
871
+ state = :started
872
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
873
+ server.send_data "<stream:features><session xmlns='urn:ietf:params:xml:ns:xmpp-session' /></stream:features>"
874
+ val.must_match(/stream:stream/)
875
+
876
+ when :started
877
+ state = :completed
878
+ doc = parse_stanza val
879
+ doc.find('/iq[@type="set" and @to="d"]/sess_ns:session', :sess_ns => Blather::Stream::Session::SESSION_NS).wont_be_empty
880
+ server.send_data "<iq from='d' type='result' id='#{doc.find_first('iq')['id']}' />"
881
+ server.send_data "<stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' /></stream:features>"
882
+
883
+ when :completed
884
+ EM.stop
885
+ true
886
+
887
+ else
888
+ EM.stop
889
+ false
890
+
891
+ end
892
+ end
893
+ end
894
+
895
+ it 'will return an error if session establishment errors out' do
896
+ state = nil
897
+ @client = mock()
898
+ @client.expects(:receive_data).with do |n|
899
+ n.name.must_equal :internal_server_error
900
+ end
901
+ mocked_server(3) do |val, server|
902
+ case state
903
+ when nil
904
+ state = :started
905
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
906
+ server.send_data "<stream:features><session xmlns='urn:ietf:params:xml:ns:xmpp-session' /></stream:features>"
907
+ val.must_match(/stream:stream/)
908
+
909
+ when :started
910
+ state = :completed
911
+ doc = parse_stanza val
912
+ doc.find('/iq[@type="set" and @to="d"]/sess_ns:session', :sess_ns => Blather::Stream::Session::SESSION_NS).wont_be_empty
913
+ server.send_data "<iq from='d' type='error' id='#{doc.find_first('iq')['id']}'><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>"
914
+
915
+ when :completed
916
+ EM.stop
917
+ val.must_match(/\/stream:stream/)
918
+
919
+ else
920
+ EM.stop
921
+ false
922
+
923
+ end
924
+ end
925
+ end
926
+
927
+ it 'will return an error if an unknown node come through during session establishment' do
928
+ state = nil
929
+ @client = mock()
930
+ @client.expects(:receive_data).with do |n|
931
+ n.must_be_instance_of Blather::UnknownResponse
932
+ n.node.element_name.must_equal 'foo-bar'
933
+ end
934
+ mocked_server(3) do |val, server|
935
+ case state
936
+ when nil
937
+ state = :started
938
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"
939
+ server.send_data "<stream:features><session xmlns='urn:ietf:params:xml:ns:xmpp-session' /></stream:features>"
940
+ val.must_match(/stream:stream/)
941
+
942
+ when :started
943
+ state = :completed
944
+ doc = parse_stanza val
945
+ doc.find('/iq[@type="set" and @to="d"]/sess_ns:session', :sess_ns => Blather::Stream::Session::SESSION_NS).wont_be_empty
946
+ server.send_data '<foo-bar />'
947
+
948
+ when :completed
949
+ EM.stop
950
+ val.must_match(/\/stream:stream/)
951
+
952
+ else
953
+ EM.stop
954
+ false
955
+
956
+ end
957
+ end
958
+ end
959
+
960
+ it 'sends client an error and reply to the server on parse error' do
961
+ @client = mock()
962
+ @client.expects(:receive_data).with do |v|
963
+ v.must_be_kind_of Blather::ParseError
964
+ v.message.must_match(/generate\-parse\-error/)
965
+ end
966
+ state = nil
967
+ mocked_server(3) do |val, server|
968
+ case state
969
+ when nil
970
+ state = :started
971
+ server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>"
972
+ server.send_data "<stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' /></stream:features>"
973
+ val.must_match(/stream:stream/)
974
+
975
+ when :started
976
+ state = :parse_error
977
+ server.send_data "</generate-parse-error>"
978
+
979
+ when :parse_error
980
+ EM.stop
981
+ val.must_equal "<stream:error><xml-not-well-formed xmlns='urn:ietf:params:xml:ns:xmpp-streams'/></stream:error></stream:stream>"
982
+
983
+ else
984
+ EM.stop
985
+ false
986
+
987
+ end
988
+ end
989
+ end
990
+
991
+ it 'sends stanzas to the wire ensuring "from" is the full JID if set' do
992
+ client = mock()
993
+ client.stubs(:jid)
994
+ client.stubs(:jid=)
995
+ msg = Blather::Stanza::Message.new 'to@jid.com', 'body'
996
+ msg.from = 'node@jid.com'
997
+ comp = Blather::Stream::Client.new nil, client, 'node@jid.com/resource', 'pass'
998
+ comp.expects(:send_data).with { |s| s.must_match(/^<message[^>]*from="node@jid\.com\/resource"/) }
999
+ comp.send msg
1000
+ end
1001
+
1002
+ it 'sends stanzas to the wire leaving "from" nil if not set' do
1003
+ client = mock()
1004
+ client.stubs(:jid)
1005
+ client.stubs(:jid=)
1006
+ msg = Blather::Stanza::Message.new 'to@jid.com', 'body'
1007
+ comp = Blather::Stream::Client.new nil, client, 'node@jid.com/resource', 'pass'
1008
+ comp.expects(:send_data).with { |s| s.wont_match(/^<message[^>]*from=/); true }
1009
+ comp.send msg
1010
+ end
1011
+ end