shingara-blather 0.4.8

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 (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