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,148 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. .. spec_helper])
2
+
3
+ def stanza_error_node(type = 'cancel', error = 'internal-server-error', msg = nil)
4
+ node = Stanza::Message.new 'error@jabber.local', 'test message', :error
5
+ XML::Document.new.root = node
6
+
7
+ error_node = XMPPNode.new('error')
8
+ error_node['type'] = type.to_s
9
+
10
+ err = XMPPNode.new(error)
11
+ err.namespace = 'urn:ietf:params:xml:ns:xmpp-stanzas'
12
+ error_node << err
13
+
14
+ if msg
15
+ text = XMPPNode.new('text')
16
+ text.namespace = 'urn:ietf:params:xml:ns:xmpp-stanzas'
17
+ text << msg
18
+ error_node << text
19
+ end
20
+
21
+ extra = XMPPNode.new('extra-error')
22
+ extra.namespace = 'blather:stanza:error'
23
+ extra << 'Blather Error'
24
+ error_node << extra
25
+
26
+ node << error_node
27
+ node
28
+ end
29
+
30
+ describe 'Blather::StanzaError' do
31
+ it 'can import a node' do
32
+ StanzaError.must_respond_to :import
33
+ e = StanzaError.import stanza_error_node
34
+ e.must_be_kind_of StanzaError
35
+ end
36
+
37
+ it 'knows what class to instantiate' do
38
+ e = StanzaError.import stanza_error_node
39
+ e.must_be_instance_of StanzaError::InternalServerError
40
+ end
41
+
42
+ describe 'valid types' do
43
+ before { @original = Stanza::Message.new 'error@jabber.local', 'test message', :error }
44
+
45
+ it 'ensures type is one of Stanza::Message::VALID_TYPES' do
46
+ lambda { StanzaError.new @original, :invalid_type_name }.must_raise(Blather::ArgumentError)
47
+
48
+ StanzaError::VALID_TYPES.each do |valid_type|
49
+ msg = StanzaError.new @original, valid_type
50
+ msg.type.must_equal valid_type
51
+ end
52
+ end
53
+ end
54
+
55
+ describe 'when instantiated' do
56
+ before do
57
+ @type = 'cancel'
58
+ @err_name = 'internal-server-error'
59
+ @msg = 'the server has experienced a misconfiguration'
60
+ @err = StanzaError.import stanza_error_node(@type, @err_name, @msg)
61
+ end
62
+
63
+ it 'provides a type attribute' do
64
+ @err.must_respond_to :type
65
+ @err.type.must_equal @type.to_sym
66
+ end
67
+
68
+ it 'provides a err_name attribute' do
69
+ @err.must_respond_to :err_name
70
+ @err.err_name.must_equal @err_name
71
+ end
72
+
73
+ it 'provides a text attribute' do
74
+ @err.must_respond_to :text
75
+ @err.text.must_equal @msg
76
+ end
77
+
78
+ it 'provides a reader to the original node' do
79
+ @err.must_respond_to :original
80
+ @err.original.must_be_instance_of Stanza::Message
81
+ end
82
+
83
+ it 'provides an extras attribute' do
84
+ @err.must_respond_to :extras
85
+ @err.extras.must_be_instance_of Array
86
+ @err.extras.first.element_name.must_equal 'extra-error'
87
+ end
88
+
89
+ it 'describes itself' do
90
+ @err.to_s.must_match(/#{@err_name}/)
91
+ @err.to_s.must_match(/#{@msg}/)
92
+
93
+ @err.inspect.must_match(/#{@err_name}/)
94
+ @err.inspect.must_match(/#{@msg}/)
95
+ end
96
+
97
+ it 'can be turned into xml' do
98
+ @err.must_respond_to :to_xml
99
+ control = "<body>test message</body>\n<error>\n<internal-server-error xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/>\n<text xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\">the server has experienced a misconfiguration</text>\n<extra-error xmlns=\"blather:stanza:error\">Blather Error</extra-error>\n</error>\n</message>".split("\n")
100
+ test = @err.to_xml.split("\n")
101
+ test_msg = test.shift
102
+ test.must_equal control
103
+
104
+ test_msg.must_match(/<message[^>]*id="#{@err.original.id}"/)
105
+ test_msg.must_match(/<message[^>]*from="error@jabber\.local"/)
106
+ test_msg.must_match(/<message[^>]*type="error"/)
107
+ end
108
+ end
109
+
110
+ describe 'each XMPP stanza error type' do
111
+ %w[ bad-request
112
+ conflict
113
+ feature-not-implemented
114
+ forbidden
115
+ gone
116
+ internal-server-error
117
+ item-not-found
118
+ jid-malformed
119
+ not-acceptable
120
+ not-allowed
121
+ not-authorized
122
+ payment-required
123
+ recipient-unavailable
124
+ redirect
125
+ registration-required
126
+ remote-server-not-found
127
+ remote-server-timeout
128
+ resource-constraint
129
+ service-unavailable
130
+ subscription-required
131
+ undefined-condition
132
+ unexpected-request
133
+ ].each do |error_type|
134
+ it "provides a class for #{error_type}" do
135
+ e = StanzaError.import stanza_error_node(:cancel, error_type)
136
+ klass = error_type.gsub(/^\w/) { |v| v.upcase }.gsub(/\-(\w)/) { |v| v.delete('-').upcase }
137
+ e.must_be_instance_of eval("StanzaError::#{klass}")
138
+ end
139
+
140
+ it "registers #{error_type} in the handler heirarchy" do
141
+ e = StanzaError.import stanza_error_node(:cancel, error_type)
142
+ e.handler_heirarchy.must_equal ["stanza_#{error_type.gsub('-','_').gsub('_error','')}_error".to_sym, :stanza_error, :error]
143
+ end
144
+ end
145
+ end
146
+ end
147
+
148
+
@@ -0,0 +1,114 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. .. spec_helper])
2
+
3
+ def stream_error_node(error = 'internal-server-error', msg = nil)
4
+ node = XMPPNode.new('stream:error')
5
+ XML::Document.new.root = node
6
+
7
+ err = XMPPNode.new(error)
8
+ err.namespace = 'urn:ietf:params:xml:ns:xmpp-streams'
9
+ node << err
10
+
11
+ if msg
12
+ text = XMPPNode.new('text')
13
+ text.namespace = 'urn:ietf:params:xml:ns:xmpp-streams'
14
+ text << msg
15
+ node << text
16
+ end
17
+
18
+ extra = XMPPNode.new('extra-error')
19
+ extra.namespace = 'blather:stream:error'
20
+ extra << 'Blather Error'
21
+
22
+ node << extra
23
+ node
24
+ end
25
+
26
+ describe 'Blather::StreamError' do
27
+ it 'can import a node' do
28
+ StreamError.must_respond_to :import
29
+ e = StreamError.import stream_error_node
30
+ e.must_be_kind_of StreamError
31
+ end
32
+
33
+ it 'knows what class to instantiate' do
34
+ e = StreamError.import stream_error_node
35
+ e.must_be_instance_of StreamError::InternalServerError
36
+ end
37
+ end
38
+
39
+ describe 'Blather::StreamError when instantiated' do
40
+ before do
41
+ @err_name = 'internal-server-error'
42
+ @msg = 'the server has experienced a misconfiguration'
43
+ @err = StreamError.import stream_error_node(@err_name, @msg)
44
+ end
45
+
46
+ it 'provides a err_name attribute' do
47
+ @err.must_respond_to :err_name
48
+ @err.err_name.must_equal @err_name
49
+ end
50
+
51
+ it 'provides a text attribute' do
52
+ @err.must_respond_to :text
53
+ @err.text.must_equal @msg
54
+ end
55
+
56
+ it 'provides an extras attribute' do
57
+ @err.must_respond_to :extras
58
+ @err.extras.must_be_instance_of Array
59
+ @err.extras.size.must_equal 1
60
+ @err.extras.first.element_name.must_equal 'extra-error'
61
+ end
62
+
63
+ it 'describes itself' do
64
+ @err.to_s.must_match(/#{@type}/)
65
+ @err.to_s.must_match(/#{@msg}/)
66
+
67
+ @err.inspect.must_match(/#{@type}/)
68
+ @err.inspect.must_match(/#{@msg}/)
69
+ end
70
+
71
+ it 'can be turned into xml' do
72
+ @err.must_respond_to :to_xml
73
+ @err.to_xml.must_equal "<stream:error>\n<internal-server-error xmlns=\"urn:ietf:params:xml:ns:xmpp-streams\"/>\n<text xmlns=\"urn:ietf:params:xml:ns:xmpp-streams\">the server has experienced a misconfiguration</text>\n<extra-error xmlns=\"blather:stream:error\">Blather Error</extra-error>\n</stream:error>"
74
+ end
75
+ end
76
+
77
+ describe 'Each XMPP stream error type' do
78
+ %w[ bad-format
79
+ bad-namespace-prefix
80
+ conflict
81
+ connection-timeout
82
+ host-gone
83
+ host-unknown
84
+ improper-addressing
85
+ internal-server-error
86
+ invalid-from
87
+ invalid-id
88
+ invalid-namespace
89
+ invalid-xml
90
+ not-authorized
91
+ policy-violation
92
+ remote-connection-failed
93
+ resource-constraint
94
+ restricted-xml
95
+ see-other-host
96
+ system-shutdown
97
+ undefined-condition
98
+ unsupported-encoding
99
+ unsupported-stanza-type
100
+ unsupported-version
101
+ xml-not-well-formed
102
+ ].each do |error_type|
103
+ it "provides a class for #{error_type}" do
104
+ e = StreamError.import stream_error_node(error_type)
105
+ klass = error_type.gsub(/^\w/) { |v| v.upcase }.gsub(/\-(\w)/) { |v| v.delete('-').upcase }
106
+ e.must_be_instance_of eval("StreamError::#{klass}")
107
+ end
108
+
109
+ it "registers #{error_type} in the handler heirarchy" do
110
+ e = StreamError.import stream_error_node(error_type)
111
+ e.handler_heirarchy.must_equal ["stream_#{error_type.gsub('-','_').gsub('_error','')}_error".to_sym, :stream_error, :error]
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,40 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. spec_helper])
2
+
3
+ describe 'Blather::BlatherError' do
4
+ it 'is handled by :error' do
5
+ BlatherError.new.handler_heirarchy.must_equal [:error]
6
+ end
7
+ end
8
+
9
+ describe 'Blather::ParseError' do
10
+ before { @error = ParseError.new('</generate-parse-error>"') }
11
+
12
+ it 'is registers with the handler heirarchy' do
13
+ @error.handler_heirarchy.must_equal [:parse_error, :error]
14
+ end
15
+
16
+ it 'contains the error message' do
17
+ @error.must_respond_to :message
18
+ @error.message.must_equal '</generate-parse-error>"'
19
+ end
20
+ end
21
+
22
+ describe 'Blather::TLSFailure' do
23
+ it 'is registers with the handler heirarchy' do
24
+ TLSFailure.new.handler_heirarchy.must_equal [:tls_failure, :error]
25
+ end
26
+ end
27
+
28
+ describe 'Blather::UnknownResponse' do
29
+ before { @error = UnknownResponse.new(XMPPNode.new('foo-bar')) }
30
+
31
+ it 'is registers with the handler heirarchy' do
32
+ @error.handler_heirarchy.must_equal [:unknown_response_error, :error]
33
+ end
34
+
35
+ it 'holds on to a copy of the failure node' do
36
+ @error.must_respond_to :node
37
+ @error.node.element_name.must_equal 'foo-bar'
38
+ end
39
+ end
40
+
@@ -1,4 +1,4 @@
1
- require File.join(File.dirname(__FILE__), *%w[.. .. spec_helper])
1
+ require File.join(File.dirname(__FILE__), *%w[.. spec_helper])
2
2
 
3
3
  describe 'Blather::JID' do
4
4
  it 'does nothing if creaded from JID' do
@@ -75,4 +75,12 @@ describe 'Blather::JID' do
75
75
  JID.new('n', 'd', 'r').to_s.must_equal 'n@d/r'
76
76
  JID.new('n', 'd').to_s.must_equal 'n@d'
77
77
  end
78
+
79
+ it 'provides a #stripped? helper' do
80
+ jid = JID.new 'a@b/c'
81
+ jid.must_respond_to :stripped?
82
+ jid.stripped?.wont_equal true
83
+ jid.strip!
84
+ jid.stripped?.must_equal true
85
+ end
78
86
  end
@@ -1,4 +1,4 @@
1
- require File.join(File.dirname(__FILE__), *%w[.. .. spec_helper])
1
+ require File.join(File.dirname(__FILE__), *%w[.. spec_helper])
2
2
 
3
3
  describe 'Blather::RosterItem' do
4
4
  it 'initializes with JID' do
@@ -77,4 +77,9 @@ describe 'Blather::RosterItem' do
77
77
  @i.status = @p
78
78
  @i.status = @p2
79
79
  end
80
+
81
+ it 'initializes groups to [nil] if the item is not part of a group' do
82
+ i = RosterItem.new 'n@d'
83
+ i.groups.must_equal [nil]
84
+ end
80
85
  end
@@ -1,4 +1,4 @@
1
- require File.join(File.dirname(__FILE__), *%w[.. .. spec_helper])
1
+ require File.join(File.dirname(__FILE__), *%w[.. spec_helper])
2
2
 
3
3
  describe 'Blather::Roster' do
4
4
  before do
@@ -19,27 +19,37 @@ describe 'Blather::Roster' do
19
19
  it 'processes @stanzas with remove requests' do
20
20
  s = @roster['n@d/0r']
21
21
  s.subscription = :remove
22
- proc { @roster.process(s.to_stanza) }.must_change('@roster.items', :length, :by => -1)
22
+ proc { @roster.process(s.to_stanza) }.must_change('@roster.items.length', :by => -1)
23
23
  end
24
24
 
25
25
  it 'processes @stanzas with add requests' do
26
26
  s = Stanza::Iq::Roster::RosterItem.new('a@b/c').to_stanza
27
- proc { @roster.process(s) }.must_change('@roster.items', :length, :by => 1)
27
+ proc { @roster.process(s) }.must_change('@roster.items.length', :by => 1)
28
28
  end
29
29
 
30
30
  it 'allows a jid to be pushed' do
31
31
  jid = 'a@b/c'
32
- proc { @roster.push(jid) }.must_change('@roster.items', :length, :by => 1)
32
+ proc { @roster.push(jid) }.must_change('@roster.items.length', :by => 1)
33
33
  @roster[jid].wont_be_nil
34
34
  end
35
35
 
36
36
  it 'allows an item to be pushed' do
37
37
  jid = 'a@b/c'
38
38
  item = RosterItem.new(JID.new(jid))
39
- proc { @roster.push(item) }.must_change('@roster.items', :length, :by => 1)
39
+ proc { @roster.push(item) }.must_change('@roster.items.length', :by => 1)
40
40
  @roster[jid].wont_be_nil
41
41
  end
42
42
 
43
+ it 'aliases #<< to #push and returns self to allow for chaining' do
44
+ jid = 'a@b/c'
45
+ item = RosterItem.new(JID.new(jid))
46
+ jid2 = 'd@e/f'
47
+ item2 = RosterItem.new(JID.new(jid2))
48
+ proc { @roster << item << item2 }.must_change('@roster.items.length', :by => 2)
49
+ @roster[jid].wont_be_nil
50
+ @roster[jid2].wont_be_nil
51
+ end
52
+
43
53
  it 'sends a @roster addition over the wire' do
44
54
  stream = mock()
45
55
  stream.expects(:send_data)
@@ -48,7 +58,7 @@ describe 'Blather::Roster' do
48
58
  end
49
59
 
50
60
  it 'removes a JID' do
51
- proc { @roster.delete 'n@d' }.must_change('@roster.items', :length, :by => -1)
61
+ proc { @roster.delete 'n@d' }.must_change('@roster.items.length', :by => -1)
52
62
  end
53
63
 
54
64
  it 'sends a @roster removal over the wire' do
@@ -0,0 +1,207 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. .. .. .. spec_helper])
2
+
3
+ def disco_info_xml
4
+ <<-XML
5
+ <iq type='result'
6
+ from='romeo@montague.net/orchard'
7
+ to='juliet@capulet.com/balcony'
8
+ id='info4'>
9
+ <query xmlns='http://jabber.org/protocol/disco#info'>
10
+ <identity
11
+ category='client'
12
+ type='pc'
13
+ name='Gabber'/>
14
+ <feature var='jabber:iq:time'/>
15
+ <feature var='jabber:iq:version'/>
16
+ </query>
17
+ </iq>
18
+ XML
19
+ end
20
+
21
+ describe 'Blather::Stanza::Iq::DiscoInfo' do
22
+ it 'registers itself' do
23
+ XMPPNode.class_from_registration(:query, 'http://jabber.org/protocol/disco#info').must_equal Blather::Stanza::Iq::DiscoInfo
24
+ end
25
+
26
+ it 'has a node attribute' do
27
+ n = Blather::Stanza::Iq::DiscoInfo.new nil, [], [], 'music'
28
+ n.node.must_equal 'music'
29
+ n.node = :foo
30
+ n.node.must_equal 'foo'
31
+ end
32
+
33
+ it 'inherits a list of identities' do
34
+ n = XML::Document.string disco_info_xml
35
+ r = Stanza::Iq::DiscoInfo.new.inherit n.root
36
+ r.identities.size.must_equal 1
37
+ r.identities.map { |i| i.class }.uniq.must_equal [Stanza::Iq::DiscoInfo::Identity]
38
+ end
39
+
40
+ it 'inherits a list of features' do
41
+ n = XML::Document.string disco_info_xml
42
+ r = Stanza::Iq::DiscoInfo.new.inherit n.root
43
+ r.features.size.must_equal 2
44
+ r.features.map { |i| i.class }.uniq.must_equal [Stanza::Iq::DiscoInfo::Feature]
45
+ end
46
+ end
47
+
48
+ describe 'Blather::Stanza::Iq::DiscoInfo identities' do
49
+ it 'takes a list of hashes for identities' do
50
+ ids = [
51
+ {:name => 'name', :type => 'type', :category => 'category'},
52
+ {:name => 'name1', :type => 'type1', :category => 'category1'},
53
+ ]
54
+
55
+ control = [ Stanza::Iq::DiscoInfo::Identity.new(*%w[name type category]),
56
+ Stanza::Iq::DiscoInfo::Identity.new(*%w[name1 type1 category1])]
57
+
58
+ di = Stanza::Iq::DiscoInfo.new nil, ids
59
+ di.identities.size.must_equal 2
60
+ di.identities.each { |i| control.include?(i).must_equal true }
61
+ end
62
+
63
+ it 'takes a list of Identity objects as identities' do
64
+ control = [ Stanza::Iq::DiscoInfo::Identity.new(*%w[name type category]),
65
+ Stanza::Iq::DiscoInfo::Identity.new(*%w[name1 type1 category1])]
66
+
67
+ di = Stanza::Iq::DiscoInfo.new nil, control
68
+ di.identities.size.must_equal 2
69
+ di.identities.each { |i| control.include?(i).must_equal true }
70
+ end
71
+
72
+ it 'takes a single hash as identity' do
73
+ control = [Stanza::Iq::DiscoInfo::Identity.new(*%w[name type category])]
74
+
75
+ di = Stanza::Iq::DiscoInfo.new nil, {:name => 'name', :type => 'type', :category => 'category'}
76
+ di.identities.size.must_equal 1
77
+ di.identities.each { |i| control.include?(i).must_equal true }
78
+ end
79
+
80
+ it 'takes a single identity object as identity' do
81
+ control = [Stanza::Iq::DiscoInfo::Identity.new(*%w[name type category])]
82
+
83
+ di = Stanza::Iq::DiscoInfo.new nil, control.first
84
+ di.identities.size.must_equal 1
85
+ di.identities.each { |i| control.include?(i).must_equal true }
86
+ end
87
+
88
+ it 'takes a mix of hashes and identity objects as identities' do
89
+ ids = [
90
+ {:name => 'name', :type => 'type', :category => 'category'},
91
+ Stanza::Iq::DiscoInfo::Identity.new(*%w[name1 type1 category1]),
92
+ ]
93
+
94
+ control = [ Stanza::Iq::DiscoInfo::Identity.new(*%w[name type category]),
95
+ Stanza::Iq::DiscoInfo::Identity.new(*%w[name1 type1 category1])]
96
+
97
+ di = Stanza::Iq::DiscoInfo.new nil, ids
98
+ di.identities.size.must_equal 2
99
+ di.identities.each { |i| control.include?(i).must_equal true }
100
+ end
101
+ end
102
+
103
+ describe 'Blather::Stanza::Iq::DiscoInfo features' do
104
+ it 'takes a list of features as strings' do
105
+ features = %w[feature1 feature2 feature3]
106
+ control = features.map { |f| Stanza::Iq::DiscoInfo::Feature.new f }
107
+
108
+ di = Stanza::Iq::DiscoInfo.new nil, [], features
109
+ di.features.size.must_equal 3
110
+ di.features.each { |f| control.include?(f).must_equal true }
111
+ end
112
+
113
+ it 'takes a list of features as Feature objects' do
114
+ features = %w[feature1 feature2 feature3]
115
+ control = features.map { |f| Stanza::Iq::DiscoInfo::Feature.new f }
116
+
117
+ di = Stanza::Iq::DiscoInfo.new nil, [], control
118
+ di.features.size.must_equal 3
119
+ di.features.each { |f| control.include?(f).must_equal true }
120
+ end
121
+
122
+ it 'takes a single string' do
123
+ control = [Stanza::Iq::DiscoInfo::Feature.new('feature1')]
124
+
125
+ di = Stanza::Iq::DiscoInfo.new nil, [], 'feature1'
126
+ di.features.size.must_equal 1
127
+ di.features.each { |f| control.include?(f).must_equal true }
128
+ end
129
+
130
+ it 'takes a single Feature object' do
131
+ control = [Stanza::Iq::DiscoInfo::Feature.new('feature1')]
132
+
133
+ di = Stanza::Iq::DiscoInfo.new nil, [], control.first
134
+ di.features.size.must_equal 1
135
+ di.features.each { |f| control.include?(f).must_equal true }
136
+ end
137
+
138
+ it 'takes a mixed list of features as Feature objects and strings' do
139
+ features = %w[feature1 feature2 feature3]
140
+ control = features.map { |f| Stanza::Iq::DiscoInfo::Feature.new f }
141
+ features[1] = control[1]
142
+
143
+ di = Stanza::Iq::DiscoInfo.new nil, [], features
144
+ di.features.size.must_equal 3
145
+ di.features.each { |f| control.include?(f).must_equal true }
146
+ end
147
+ end
148
+
149
+ describe 'Blather::Stanza::Iq::DiscoInfo::Identity' do
150
+ it 'will auto-inherit nodes' do
151
+ n = XML::Document.string "<identity name='Personal Events' type='pep' category='pubsub' node='publish' />"
152
+ i = Stanza::Iq::DiscoInfo::Identity.new n.root
153
+ i.name.must_equal 'Personal Events'
154
+ i.type.must_equal :pep
155
+ i.category.must_equal :pubsub
156
+ end
157
+
158
+ it 'has a category attribute' do
159
+ n = Blather::Stanza::Iq::DiscoInfo::Identity.new(*%w[name type cat])
160
+ n.category.must_equal :cat
161
+ n.category = :foo
162
+ n.category.must_equal :foo
163
+ end
164
+
165
+ it 'has a type attribute' do
166
+ n = Blather::Stanza::Iq::DiscoInfo::Identity.new(*%w[name type cat])
167
+ n.type.must_equal :type
168
+ n.type = :foo
169
+ n.type.must_equal :foo
170
+ end
171
+
172
+ it 'has a name attribute' do
173
+ n = Blather::Stanza::Iq::DiscoInfo::Identity.new(*%w[name type cat])
174
+ n.name.must_equal 'name'
175
+ n.name = :foo
176
+ n.name.must_equal 'foo'
177
+ end
178
+
179
+ it 'can determine equality' do
180
+ a = Blather::Stanza::Iq::DiscoInfo::Identity.new(*%w[name type cat])
181
+ a.must_respond_to :eql?
182
+ a.must_equal Blather::Stanza::Iq::DiscoInfo::Identity.new(*%w[name type cat])
183
+ a.wont_equal "<identity name='Personal Events' type='pep' category='pubsub' node='publish' />"
184
+ end
185
+ end
186
+
187
+ describe 'Blather::Stanza::Iq::DiscoInfo::Feature' do
188
+ it 'will auto-inherit nodes' do
189
+ n = XML::Document.string "<feature var='ipv6' />"
190
+ i = Stanza::Iq::DiscoInfo::Feature.new n.root
191
+ i.var.must_equal 'ipv6'
192
+ end
193
+
194
+ it 'has a var attribute' do
195
+ n = Blather::Stanza::Iq::DiscoInfo::Feature.new 'var'
196
+ n.var.must_equal 'var'
197
+ n.var = :foo
198
+ n.var.must_equal 'foo'
199
+ end
200
+
201
+ it 'can determine equality' do
202
+ a = Blather::Stanza::Iq::DiscoInfo::Feature.new('var')
203
+ a.must_respond_to :eql?
204
+ a.must_equal Blather::Stanza::Iq::DiscoInfo::Feature.new('var')
205
+ a.wont_equal "<feature var='ipv6' />"
206
+ end
207
+ end