sprsquish-blather 0.1 → 0.2.3

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 (73) hide show
  1. data/LICENSE +2 -0
  2. data/README.rdoc +100 -0
  3. data/Rakefile +110 -0
  4. data/examples/drb_client.rb +5 -0
  5. data/examples/echo.rb +18 -0
  6. data/ext/extconf.rb +65 -0
  7. data/ext/push_parser.c +231 -0
  8. data/lib/blather/client.rb +219 -44
  9. data/lib/blather/{core/sugar.rb → core_ext/active_support.rb} +25 -13
  10. data/lib/blather/core_ext/libxml.rb +28 -0
  11. data/lib/blather/errors/sasl_error.rb +87 -0
  12. data/lib/blather/errors/stanza_error.rb +262 -0
  13. data/lib/blather/errors/stream_error.rb +253 -0
  14. data/lib/blather/errors.rb +48 -0
  15. data/lib/blather/{core/jid.rb → jid.rb} +15 -26
  16. data/lib/blather/{core/roster.rb → roster.rb} +22 -0
  17. data/lib/blather/{core/roster_item.rb → roster_item.rb} +39 -8
  18. data/lib/blather/stanza/iq/disco.rb +11 -0
  19. data/lib/blather/stanza/iq/discos/disco_info.rb +86 -0
  20. data/lib/blather/stanza/iq/discos/disco_items.rb +61 -0
  21. data/lib/blather/stanza/iq/query.rb +51 -0
  22. data/lib/blather/stanza/iq/roster.rb +90 -0
  23. data/lib/blather/stanza/iq.rb +38 -0
  24. data/lib/blather/stanza/message.rb +58 -0
  25. data/lib/blather/stanza/presence/status.rb +78 -0
  26. data/lib/blather/stanza/presence/subscription.rb +72 -0
  27. data/lib/blather/stanza/presence.rb +45 -0
  28. data/lib/blather/stanza.rb +101 -0
  29. data/lib/blather/stream/client.rb +26 -0
  30. data/lib/blather/stream/component.rb +34 -0
  31. data/lib/blather/stream/parser.rb +70 -0
  32. data/lib/blather/stream/resource.rb +48 -0
  33. data/lib/blather/stream/sasl.rb +173 -0
  34. data/lib/blather/stream/session.rb +36 -0
  35. data/lib/blather/stream/stream_handler.rb +39 -0
  36. data/lib/blather/stream/tls.rb +33 -0
  37. data/lib/blather/stream.rb +249 -0
  38. data/lib/blather/xmpp_node.rb +199 -0
  39. data/lib/blather.rb +40 -41
  40. data/spec/blather/core_ext/libxml_spec.rb +58 -0
  41. data/spec/blather/errors/sasl_error_spec.rb +56 -0
  42. data/spec/blather/errors/stanza_error_spec.rb +148 -0
  43. data/spec/blather/errors/stream_error_spec.rb +114 -0
  44. data/spec/blather/errors_spec.rb +40 -0
  45. data/spec/blather/{core/jid_spec.rb → jid_spec.rb} +9 -1
  46. data/spec/blather/{core/roster_item_spec.rb → roster_item_spec.rb} +6 -1
  47. data/spec/blather/{core/roster_spec.rb → roster_spec.rb} +16 -6
  48. data/spec/blather/stanza/iq/discos/disco_info_spec.rb +207 -0
  49. data/spec/blather/stanza/iq/discos/disco_items_spec.rb +136 -0
  50. data/spec/blather/stanza/iq/query_spec.rb +34 -0
  51. data/spec/blather/stanza/iq/roster_spec.rb +123 -0
  52. data/spec/blather/stanza/iq_spec.rb +40 -0
  53. data/spec/blather/stanza/message_spec.rb +52 -0
  54. data/spec/blather/stanza/presence/status_spec.rb +102 -0
  55. data/spec/blather/stanza/presence/subscription_spec.rb +85 -0
  56. data/spec/blather/stanza/presence_spec.rb +53 -0
  57. data/spec/blather/{core/stanza_spec.rb → stanza_spec.rb} +14 -2
  58. data/spec/blather/stream/client_spec.rb +787 -0
  59. data/spec/blather/stream/component_spec.rb +86 -0
  60. data/spec/blather/{core/xmpp_node_spec.rb → xmpp_node_spec.rb} +76 -23
  61. data/spec/build_safe.rb +20 -0
  62. data/spec/spec_helper.rb +7 -17
  63. metadata +79 -59
  64. data/CHANGELOG +0 -1
  65. data/blather.gemspec +0 -73
  66. data/lib/blather/callback.rb +0 -24
  67. data/lib/blather/core/errors.rb +0 -24
  68. data/lib/blather/core/stanza.rb +0 -90
  69. data/lib/blather/core/stream.rb +0 -179
  70. data/lib/blather/core/xmpp_node.rb +0 -95
  71. data/lib/blather/extensions/last_activity.rb +0 -57
  72. data/lib/blather/extensions/version.rb +0 -85
  73. data/spec/blather/core/stream_spec.rb +0 -263
@@ -0,0 +1,249 @@
1
+ module Blather
2
+
3
+ class Stream < EventMachine::Connection
4
+ ##
5
+ # Start the stream between client and server
6
+ # [client] must be an object that will respond to #call and #jid=
7
+ # [jid] must be a valid argument for JID.new (see JID)
8
+ # [pass] must be the password
9
+ # [host] (optional) must be the hostname or IP to connect to. defaults to the domain of [jid]
10
+ # [port] (optional) must be the port to connect to. defaults to 5222
11
+ def self.start(client, jid, pass, host = nil, port = 5222)
12
+ jid = JID.new jid
13
+ host ||= jid.domain
14
+
15
+ EM.connect host, port, self, client, jid, pass
16
+ end
17
+
18
+ ##
19
+ # Send data over the wire
20
+ # The argument for this can be anything that
21
+ # responds to #to_s
22
+ def send(stanza)
23
+ #TODO Queue if not ready
24
+ LOG.debug "SENDING: (#{caller[1]}) #{stanza}"
25
+ send_data stanza.respond_to?(:to_xml) ? stanza.to_xml : stanza.to_s
26
+ end
27
+
28
+ ##
29
+ # True if the stream is in the stopped state
30
+ def stopped?
31
+ @state == :stopped
32
+ end
33
+
34
+ ##
35
+ # True when the stream is in the negotiation phase.
36
+ def negotiating?
37
+ ![:stopped, :ready].include? @state
38
+ end
39
+
40
+ ##
41
+ # True when the stream is ready
42
+ # The stream is ready immediately after receiving <stream:stream>
43
+ # and before any feature negotion. Once feature negoation starts
44
+ # the stream will not be ready until all negotations have completed
45
+ # successfully.
46
+ def ready?
47
+ @state == :ready
48
+ end
49
+
50
+ ##
51
+ # Called by EM.connect to initialize stream variables
52
+ def initialize(client, jid, pass) # :nodoc:
53
+ super()
54
+
55
+ @error = nil
56
+ @client = client
57
+
58
+ self.jid = jid
59
+ @pass = pass
60
+
61
+ @to = @jid.domain
62
+ end
63
+
64
+ ##
65
+ # Called when EM completes the connection to the server
66
+ # this kicks off the starttls/authorize/bind process
67
+ def connection_completed # :nodoc:
68
+ # @keepalive = EM::Timer.new(60) { send_data ' ' }
69
+ @state = :stopped
70
+ dispatch
71
+ end
72
+
73
+ ##
74
+ # Called by EM with data from the wire
75
+ def receive_data(data) # :nodoc:
76
+ LOG.debug "\n#{'-'*30}\n"
77
+ LOG.debug "<< #{data}"
78
+ @parser.receive_data data
79
+
80
+ rescue ParseError => e
81
+ @error = e
82
+ stop
83
+ end
84
+
85
+ ##
86
+ # Called by EM when the connection is closed
87
+ def unbind # :nodoc:
88
+ # @keepalive.cancel
89
+ @state = :stopped
90
+ @client.call @error if @error
91
+ @client.stopped
92
+ end
93
+
94
+ ##
95
+ # Called by the parser with parsed nodes
96
+ def receive(node) # :nodoc:
97
+ LOG.debug "RECEIVING (#{node.element_name}) #{node}"
98
+ @node = node
99
+
100
+ case @node.element_name
101
+ when 'stream:stream'
102
+ @state = :ready if @state == :stopped
103
+
104
+ when 'stream:end'
105
+ stop
106
+
107
+ when 'stream:features'
108
+ @features = @node.children
109
+ @state = :features
110
+ dispatch
111
+
112
+ when 'stream:error'
113
+ @error = StreamError.import @node
114
+ stop
115
+ @state = :error
116
+
117
+ else
118
+ dispatch
119
+
120
+ end
121
+ end
122
+
123
+ ##
124
+ # Ensure the JID gets attached to the client
125
+ def jid=(new_jid) # :nodoc:
126
+ LOG.debug "NEW JID: #{new_jid}"
127
+ @jid = JID.new new_jid
128
+ @client.jid = @jid
129
+ end
130
+
131
+ protected
132
+ ##
133
+ # Dispatch based on current state
134
+ def dispatch
135
+ __send__ @state
136
+ end
137
+
138
+ ##
139
+ # Start the stream
140
+ # Each time the stream is started or re-started we need to kill off the old
141
+ # parser so as not to confuse it
142
+ def start
143
+ end
144
+
145
+ ##
146
+ # Stop the stream
147
+ def stop
148
+ unless @state == :stopped
149
+ @state = :stopped
150
+ send '</stream:stream>'
151
+ end
152
+ end
153
+
154
+ ##
155
+ # Called when @state == :stopped to start the stream
156
+ # Counter intuitive, I know
157
+ def stopped
158
+ start
159
+ end
160
+
161
+ ##
162
+ # Called when @state == :ready
163
+ # Simply passes the stanza to the client
164
+ def ready
165
+ @client.call @node.to_stanza
166
+ end
167
+
168
+ ##
169
+ # Called when @state == :features
170
+ # Runs through the list of features starting each one in turn
171
+ def features
172
+ feature = @features.first
173
+ LOG.debug "FEATURE: #{feature}"
174
+ @state = case feature ? feature.namespaces.default.href : nil
175
+ when 'urn:ietf:params:xml:ns:xmpp-tls' then :establish_tls
176
+ when 'urn:ietf:params:xml:ns:xmpp-sasl' then :authenticate_sasl
177
+ when 'urn:ietf:params:xml:ns:xmpp-bind' then :bind_resource
178
+ when 'urn:ietf:params:xml:ns:xmpp-session' then :establish_session
179
+ else :ready
180
+ end
181
+
182
+ # Dispatch to the individual feature methods unless
183
+ # feature negotiation is complete
184
+ dispatch unless ready?
185
+ end
186
+
187
+ ##
188
+ # Start TLS
189
+ def establish_tls
190
+ unless @tls
191
+ @tls = TLS.new self
192
+ # on success destroy the TLS object and restart the stream
193
+ @tls.on_success { LOG.debug "TLS: SUCCESS"; @tls = nil; start }
194
+ # on failure stop the stream
195
+ @tls.on_failure { |err| LOG.debug "TLS: FAILURE"; @error = err; stop }
196
+
197
+ @node = @features.shift
198
+ end
199
+ @tls.handle @node
200
+ end
201
+
202
+ ##
203
+ # Authenticate via SASL
204
+ def authenticate_sasl
205
+ unless @sasl
206
+ @sasl = SASL.new(self, @jid, @pass)
207
+ # on success destroy the SASL object and restart the stream
208
+ @sasl.on_success { LOG.debug "SASL SUCCESS"; @sasl = nil; start }
209
+ # on failure set the error and stop the stream
210
+ @sasl.on_failure { |err| LOG.debug "SASL FAIL"; @error = err; stop }
211
+
212
+ @node = @features.shift
213
+ end
214
+ @sasl.handle @node
215
+ end
216
+
217
+ ##
218
+ # Bind to the resource provided by either the client or the server
219
+ def bind_resource
220
+ unless @resource
221
+ @resource = Resource.new self, @jid
222
+ # on success destroy the Resource object, set the jid, continue along the features dispatch process
223
+ @resource.on_success { |jid| LOG.debug "RESOURCE: SUCCESS"; @resource = nil; self.jid = jid; @state = :features; dispatch }
224
+ # on failure end the stream
225
+ @resource.on_failure { |err| LOG.debug "RESOURCE: FAILURE"; @error = err; stop }
226
+
227
+ @node = @features.shift
228
+ end
229
+ @resource.handle @node
230
+ end
231
+
232
+ ##
233
+ # Establish the session between client and server
234
+ def establish_session
235
+ unless @session
236
+ @session = Session.new self, @to
237
+ # on success destroy the session object, let the client know the stream has been started
238
+ # then continue the features dispatch process
239
+ @session.on_success { LOG.debug "SESSION: SUCCESS"; @session = nil; @client.stream_started(self); @state = :features; dispatch }
240
+ # on failure end the stream
241
+ @session.on_failure { |err| LOG.debug "SESSION: FAILURE"; @error = err; stop }
242
+
243
+ @node = @features.shift
244
+ end
245
+ @session.handle @node
246
+ end
247
+ end
248
+
249
+ end
@@ -0,0 +1,199 @@
1
+ module Blather
2
+
3
+ ##
4
+ # Base XML Node
5
+ # All XML classes subclass XMPPNode
6
+ # it allows the addition of helpers
7
+ class XMPPNode < XML::Node
8
+ BASE_NAMES = %w[presence message iq].freeze
9
+
10
+ @@registrations = {}
11
+
12
+ class_inheritable_accessor :ns,
13
+ :name
14
+
15
+ ##
16
+ # Lets a subclass register itself
17
+ #
18
+ # This registers a namespace that is used when looking
19
+ # up the class name of the object to instantiate when a new
20
+ # stanza is received
21
+ def self.register(name, ns = nil)
22
+ self.name = name.to_s
23
+ self.ns = ns
24
+ @@registrations[[self.name, self.ns]] = self
25
+ end
26
+
27
+ ##
28
+ # Find the class to use given the name and namespace of a stanza
29
+ def self.class_from_registration(name, xmlns)
30
+ name = name.to_s
31
+ @@registrations[[name, xmlns]] || @@registrations[[name, nil]]
32
+ end
33
+
34
+ ##
35
+ # Looks up the class to use then instantiates an object
36
+ # of that class and imports all the <tt>node</tt>'s attributes
37
+ # and children into it.
38
+ def self.import(node)
39
+ klass = class_from_registration(node.element_name, node.namespace)
40
+ if klass && klass != self
41
+ klass.import(node)
42
+ else
43
+ new(node.element_name).inherit(node)
44
+ end
45
+ end
46
+
47
+ ##
48
+ # Provides an attribute reader helper. Default behavior is to
49
+ # conver the values of the attribute into a symbol. This can
50
+ # be turned off by passing <tt>:to_sym => false</tt>
51
+ #
52
+ # class Node
53
+ # attribute_reader :type
54
+ # attribute_reader :name, :to_sym => false
55
+ # end
56
+ #
57
+ # n = Node.new
58
+ # n.attributes[:type] = 'foo'
59
+ # n.type == :foo
60
+ # n.attributes[:name] = 'bar'
61
+ # n.name == 'bar'
62
+ def self.attribute_reader(*syms)
63
+ opts = syms.last.is_a?(Hash) ? syms.pop : {}
64
+ syms.flatten.each do |sym|
65
+ class_eval(<<-END, __FILE__, __LINE__)
66
+ def #{sym}
67
+ attributes[:#{sym}]#{".to_sym unless attributes[:#{sym}].blank?" unless opts[:to_sym] == false}
68
+ end
69
+ END
70
+ end
71
+ end
72
+
73
+ ##
74
+ # Provides an attribute writer helper.
75
+ #
76
+ # class Node
77
+ # attribute_writer :type
78
+ # end
79
+ #
80
+ # n = Node.new
81
+ # n.type = 'foo'
82
+ # n.attributes[:type] == 'foo'
83
+ def self.attribute_writer(*syms)
84
+ syms.flatten.each do |sym|
85
+ next if sym.is_a?(Hash)
86
+ class_eval(<<-END, __FILE__, __LINE__)
87
+ def #{sym}=(value)
88
+ attributes[:#{sym}] = value
89
+ end
90
+ END
91
+ end
92
+ end
93
+
94
+ ##
95
+ # Provides an attribute accessor helper combining
96
+ # <tt>attribute_reader</tt> and <tt>attribute_writer</tt>
97
+ #
98
+ # class Node
99
+ # attribute_accessor :type
100
+ # attribute_accessor :name, :to_sym => false
101
+ # end
102
+ #
103
+ # n = Node.new
104
+ # n.type = 'foo'
105
+ # n.type == :foo
106
+ # n.name = 'bar'
107
+ # n.name == 'bar'
108
+ def self.attribute_accessor(*syms)
109
+ attribute_reader *syms
110
+ attribute_writer *syms
111
+ end
112
+
113
+ ##
114
+ # Automatically sets the namespace registered by the subclass
115
+ def initialize(name = nil, content = nil)
116
+ name ||= self.class.name
117
+ content = content.to_s if content
118
+
119
+ super name.to_s, content
120
+ self.namespace = self.class.ns unless BASE_NAMES.include?(name.to_s)
121
+ end
122
+
123
+ ##
124
+ # Quickway of turning itself into a proper object
125
+ def to_stanza
126
+ self.class.import self
127
+ end
128
+
129
+ def namespace=(ns)
130
+ if ns
131
+ ns = {nil => ns} unless ns.is_a?(Hash)
132
+ ns.each { |p,n| XML::Namespace.new self, p, n }
133
+ end
134
+ end
135
+
136
+ def namespace(prefix = nil)
137
+ (ns = namespaces.find_by_prefix(prefix)) ? ns.href : nil
138
+ end
139
+
140
+ ##
141
+ # Remove a child with the name and (optionally) namespace given
142
+ def remove_child(name, ns = nil)
143
+ name = name.to_s
144
+ self.detect { |n| n.remove! if n.element_name == name && (!ns || n.namespace == ns) }
145
+ end
146
+
147
+ ##
148
+ # Remove all children with a given name
149
+ def remove_children(name)
150
+ name = name.to_s
151
+ self.find(name).each { |n| n.remove! }
152
+ end
153
+
154
+ ##
155
+ # Pull the content from a child
156
+ def content_from(name)
157
+ name = name.to_s
158
+ (child = self.detect { |n| n.element_name == name }) ? child.content : nil
159
+ end
160
+
161
+ ##
162
+ # Create a copy
163
+ def copy(deep = true)
164
+ copy = self.class.new.inherit(self)
165
+ copy.element_name = self.element_name
166
+ copy
167
+ end
168
+
169
+ ##
170
+ # Inherit all of <tt>stanza</tt>'s attributes and children
171
+ def inherit(stanza)
172
+ inherit_attrs stanza.attributes
173
+ stanza.children.each { |c| self << c.copy(true) }
174
+ self
175
+ end
176
+
177
+ ##
178
+ # Inherit only <tt>stanza</tt>'s attributes
179
+ def inherit_attrs(attrs)
180
+ attrs.each { |a| attributes[a.name] = a.value }
181
+ self
182
+ end
183
+
184
+ ##
185
+ # Turn itself into an XML string and remove all whitespace between nodes
186
+ def to_xml
187
+ # TODO: Fix this for HTML nodes (and any other that might require whitespace)
188
+ to_s.gsub(">\n<", '><')
189
+ end
190
+
191
+ ##
192
+ # Override #find to work when a node isn't attached to a document
193
+ def find(what, nslist = nil)
194
+ what = what.to_s
195
+ (self.doc ? super(what, nslist) : select { |i| i.element_name == what })
196
+ end
197
+ end #XMPPNode
198
+
199
+ end
data/lib/blather.rb CHANGED
@@ -1,54 +1,53 @@
1
1
  $:.unshift File.dirname(__FILE__)
2
+ $:.unshift File.join(File.dirname(__FILE__), '..')
2
3
 
4
+ # Require the necessary files
3
5
  %w[
4
6
  rubygems
5
- xml/libxml
6
7
  eventmachine
8
+ ext/push_parser
9
+ xml/libxml
7
10
  digest/md5
8
11
  logger
9
12
 
10
- blather/callback
11
-
12
- blather/core/errors
13
- blather/core/jid
14
- blather/core/roster
15
- blather/core/roster_item
16
- blather/core/sugar
17
- blather/core/xmpp_node
18
-
19
- blather/core/stanza
20
- blather/core/stanza/iq
21
- blather/core/stanza/iq/query
22
- blather/core/stanza/iq/roster
23
- blather/core/stanza/message
24
- blather/core/stanza/presence
25
- blather/core/stanza/presence/status
26
- blather/core/stanza/presence/subscription
27
-
28
- blather/core/stream
29
- blather/core/stream/parser
30
- blather/core/stream/resource
31
- blather/core/stream/sasl
32
- blather/core/stream/session
33
- blather/core/stream/tls
13
+ blather/core_ext/active_support
14
+ blather/core_ext/libxml
15
+
16
+ blather/errors
17
+ blather/errors/sasl_error
18
+ blather/errors/stanza_error
19
+ blather/errors/stream_error
20
+ blather/jid
21
+ blather/roster
22
+ blather/roster_item
23
+ blather/xmpp_node
24
+
25
+ blather/stanza
26
+ blather/stanza/iq
27
+ blather/stanza/iq/query
28
+ blather/stanza/iq/roster
29
+ blather/stanza/iq/disco
30
+ blather/stanza/iq/discos/disco_info
31
+ blather/stanza/iq/discos/disco_items
32
+ blather/stanza/message
33
+ blather/stanza/presence
34
+ blather/stanza/presence/status
35
+ blather/stanza/presence/subscription
36
+
37
+ blather/stream
38
+ blather/stream/client
39
+ blather/stream/component
40
+ blather/stream/stream_handler
41
+ blather/stream/parser
42
+ blather/stream/resource
43
+ blather/stream/sasl
44
+ blather/stream/session
45
+ blather/stream/tls
34
46
  ].each { |r| require r }
35
47
 
36
- XML::Parser.indent_tree_output = false
48
+ XML.indent_tree_output = false
37
49
 
38
50
  module Blather
39
- LOG = Logger.new STDOUT
40
-
41
- def run(jid, password, client, host = nil, port = 5222)
42
- EM.run { Stream.start client, JID.new(jid), password, host, port }
43
- end
44
- module_function :run
45
-
46
- MAJOR = 0
47
- MINOR = 1
48
- VERSION = [MAJOR, MINOR]*'.'
49
-
50
- def version
51
- VERSION
52
- end
53
- module_function :version
51
+ LOG = Logger.new(STDOUT) unless const_defined?(:LOG)
52
+ LOG.level = Logger::INFO
54
53
  end
@@ -0,0 +1,58 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. .. spec_helper])
2
+
3
+ describe 'LibXML::XML::Node' do
4
+ it 'aliases #name to #element_name' do
5
+ node = LibXML::XML::Node.new 'foo'
6
+ node.must_respond_to :element_name
7
+ node.element_name.must_equal node.name
8
+ end
9
+
10
+ it 'aliases #name= to #element_name=' do
11
+ node = LibXML::XML::Node.new 'foo'
12
+ node.must_respond_to :element_name=
13
+ node.element_name.must_equal node.name
14
+ node.element_name = 'bar'
15
+ node.element_name.must_equal 'bar'
16
+ end
17
+ end
18
+
19
+ describe 'LibXML::XML::Attributes' do
20
+ it 'provides a helper to remove a specified attribute' do
21
+ attrs = LibXML::XML::Node.new('foo').attributes
22
+ attrs['foo'] = 'bar'
23
+ attrs['foo'].must_equal 'bar'
24
+ attrs.remove 'foo'
25
+ attrs['foo'].must_be_nil
26
+
27
+ attrs['foo'] = 'bar'
28
+ attrs['foo'].must_equal 'bar'
29
+ attrs.remove :foo
30
+ attrs['foo'].must_be_nil
31
+ end
32
+
33
+ it 'allows symbols as hash keys' do
34
+ attrs = LibXML::XML::Node.new('foo').attributes
35
+ attrs['foo'] = 'bar'
36
+
37
+ attrs['foo'].must_equal 'bar'
38
+ attrs[:foo].must_equal 'bar'
39
+ end
40
+
41
+ it 'removes an attribute when set to nil' do
42
+ attrs = LibXML::XML::Node.new('foo').attributes
43
+ attrs['foo'] = 'bar'
44
+
45
+ attrs['foo'].must_equal 'bar'
46
+ attrs['foo'] = nil
47
+ attrs['foo'].must_be_nil
48
+ end
49
+
50
+ it 'allows attribute values to change' do
51
+ attrs = LibXML::XML::Node.new('foo').attributes
52
+ attrs['foo'] = 'bar'
53
+
54
+ attrs['foo'].must_equal 'bar'
55
+ attrs['foo'] = 'baz'
56
+ attrs['foo'].must_equal 'baz'
57
+ end
58
+ end
@@ -0,0 +1,56 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. .. spec_helper])
2
+
3
+ def sasl_error_node(err_name = 'aborted')
4
+ node = XMPPNode.new 'failure'
5
+ node.namespace = 'urn:ietf:params:xml:ns:xmpp-sasl'
6
+
7
+ node << XMPPNode.new(err_name)
8
+ node
9
+ end
10
+
11
+ describe 'Blather::SASLError' do
12
+ it 'can import a node' do
13
+ SASLError.must_respond_to :import
14
+ e = SASLError.import sasl_error_node
15
+ e.must_be_kind_of SASLError
16
+ end
17
+
18
+ it 'knows what class to instantiate' do
19
+ e = SASLError.import sasl_error_node
20
+ e.must_be_instance_of SASLError::Aborted
21
+ end
22
+
23
+ describe 'when instantiated' do
24
+ before do
25
+ @err_name = 'mechanism-too-weak'
26
+ @err = SASLError.import sasl_error_node(@err_name)
27
+ end
28
+
29
+ it 'provides a err_name attribute' do
30
+ @err.must_respond_to :err_name
31
+ @err.err_name.must_equal @err_name
32
+ end
33
+ end
34
+
35
+ describe 'each XMPP SASL error type' do
36
+ %w[ aborted
37
+ incorrect-encoding
38
+ invalid-authzid
39
+ invalid-mechanism
40
+ mechanism-too-weak
41
+ not-authorized
42
+ temporary-auth-failure
43
+ ].each do |error_type|
44
+ it "provides a class for #{error_type}" do
45
+ e = SASLError.import sasl_error_node(error_type)
46
+ klass = error_type.gsub(/^\w/) { |v| v.upcase }.gsub(/\-(\w)/) { |v| v.delete('-').upcase }
47
+ e.must_be_instance_of eval("SASLError::#{klass}")
48
+ end
49
+
50
+ it "registers #{error_type} in the handler heirarchy" do
51
+ e = SASLError.import sasl_error_node(error_type)
52
+ e.handler_heirarchy.must_equal ["sasl_#{error_type.gsub('-','_').gsub('_error','')}_error".to_sym, :sasl_error, :error]
53
+ end
54
+ end
55
+ end
56
+ end