tp-blather 0.8.2

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