blather 0.2.1 → 0.2.2

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 (84) hide show
  1. data/LICENSE +2 -0
  2. data/README.rdoc +54 -29
  3. data/Rakefile +94 -13
  4. data/VERSION.yml +4 -0
  5. data/examples/drb_client.rb +2 -4
  6. data/examples/echo.rb +13 -8
  7. data/examples/pubsub/cli.rb +64 -0
  8. data/examples/pubsub/ping_pong.rb +18 -0
  9. data/examples/pubsub/pubsub_dsl.rb +52 -0
  10. data/examples/pubsub_client.rb +39 -0
  11. data/examples/rosterprint.rb +14 -0
  12. data/examples/xmpp4r/echo.rb +35 -0
  13. data/ext/extconf.rb +65 -0
  14. data/lib/blather.rb +18 -121
  15. data/lib/blather/client.rb +13 -0
  16. data/lib/blather/client/client.rb +165 -0
  17. data/lib/blather/client/dsl.rb +99 -0
  18. data/lib/blather/client/pubsub.rb +53 -0
  19. data/lib/blather/client/pubsub/node.rb +27 -0
  20. data/lib/blather/core_ext/active_support.rb +1 -0
  21. data/lib/blather/core_ext/libxml.rb +7 -1
  22. data/lib/blather/errors.rb +39 -18
  23. data/lib/blather/errors/sasl_error.rb +87 -0
  24. data/lib/blather/errors/stanza_error.rb +262 -0
  25. data/lib/blather/errors/stream_error.rb +253 -0
  26. data/lib/blather/jid.rb +9 -16
  27. data/lib/blather/roster.rb +9 -0
  28. data/lib/blather/roster_item.rb +7 -4
  29. data/lib/blather/stanza.rb +19 -25
  30. data/lib/blather/stanza/disco.rb +9 -0
  31. data/lib/blather/stanza/disco/disco_info.rb +84 -0
  32. data/lib/blather/stanza/disco/disco_items.rb +59 -0
  33. data/lib/blather/stanza/iq.rb +16 -4
  34. data/lib/blather/stanza/iq/query.rb +6 -4
  35. data/lib/blather/stanza/iq/roster.rb +38 -38
  36. data/lib/blather/stanza/pubsub.rb +33 -0
  37. data/lib/blather/stanza/pubsub/affiliations.rb +52 -0
  38. data/lib/blather/stanza/pubsub/errors.rb +9 -0
  39. data/lib/blather/stanza/pubsub/event.rb +21 -0
  40. data/lib/blather/stanza/pubsub/items.rb +59 -0
  41. data/lib/blather/stanza/pubsub/owner.rb +9 -0
  42. data/lib/blather/stanza/pubsub/subscriptions.rb +57 -0
  43. data/lib/blather/stream.rb +125 -57
  44. data/lib/blather/stream/client.rb +26 -0
  45. data/lib/blather/stream/component.rb +34 -0
  46. data/lib/blather/stream/parser.rb +17 -27
  47. data/lib/blather/stream/resource.rb +21 -24
  48. data/lib/blather/stream/sasl.rb +60 -37
  49. data/lib/blather/stream/session.rb +12 -19
  50. data/lib/blather/stream/stream_handler.rb +39 -0
  51. data/lib/blather/stream/tls.rb +22 -18
  52. data/lib/blather/xmpp_node.rb +91 -17
  53. data/spec/blather/core_ext/libxml_spec.rb +58 -0
  54. data/spec/blather/errors/sasl_error_spec.rb +56 -0
  55. data/spec/blather/errors/stanza_error_spec.rb +148 -0
  56. data/spec/blather/errors/stream_error_spec.rb +114 -0
  57. data/spec/blather/errors_spec.rb +40 -0
  58. data/spec/blather/jid_spec.rb +0 -7
  59. data/spec/blather/roster_item_spec.rb +5 -0
  60. data/spec/blather/roster_spec.rb +6 -6
  61. data/spec/blather/stanza/discos/disco_info_spec.rb +207 -0
  62. data/spec/blather/stanza/discos/disco_items_spec.rb +136 -0
  63. data/spec/blather/stanza/iq/query_spec.rb +9 -2
  64. data/spec/blather/stanza/iq/roster_spec.rb +117 -1
  65. data/spec/blather/stanza/iq_spec.rb +29 -0
  66. data/spec/blather/stanza/presence/subscription_spec.rb +12 -1
  67. data/spec/blather/stanza/presence_spec.rb +29 -0
  68. data/spec/blather/stanza/pubsub/affiliations_spec.rb +46 -0
  69. data/spec/blather/stanza/pubsub/items_spec.rb +59 -0
  70. data/spec/blather/stanza/pubsub/subscriptions_spec.rb +63 -0
  71. data/spec/blather/stanza/pubsub_spec.rb +26 -0
  72. data/spec/blather/stanza_spec.rb +13 -1
  73. data/spec/blather/stream/client_spec.rb +787 -0
  74. data/spec/blather/stream/component_spec.rb +86 -0
  75. data/spec/blather/xmpp_node_spec.rb +75 -22
  76. data/spec/fixtures/pubsub.rb +157 -0
  77. data/spec/spec_helper.rb +6 -14
  78. metadata +86 -74
  79. data/CHANGELOG +0 -5
  80. data/Manifest +0 -47
  81. data/blather.gemspec +0 -41
  82. data/lib/blather/stanza/error.rb +0 -31
  83. data/spec/blather/stream_spec.rb +0 -462
  84. data/spec/build_safe.rb +0 -20
@@ -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
@@ -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
+
@@ -76,13 +76,6 @@ describe 'Blather::JID' do
76
76
  JID.new('n', 'd').to_s.must_equal 'n@d'
77
77
  end
78
78
 
79
- it 'falls back to basic ruby if idn gem is not present' do
80
- JID.const_set 'USE_STRINGPREP', false
81
- jid = JID.new 'AB#$cdE@&^FE'
82
- jid.node.must_equal 'ab#$cde'
83
- jid.domain.must_equal '&^fe'
84
- end
85
-
86
79
  it 'provides a #stripped? helper' do
87
80
  jid = JID.new 'a@b/c'
88
81
  jid.must_respond_to :stripped?
@@ -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
@@ -19,24 +19,24 @@ 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
 
@@ -45,7 +45,7 @@ describe 'Blather::Roster' do
45
45
  item = RosterItem.new(JID.new(jid))
46
46
  jid2 = 'd@e/f'
47
47
  item2 = RosterItem.new(JID.new(jid2))
48
- proc { @roster << item << item2 }.must_change('@roster.items', :length, :by => 2)
48
+ proc { @roster << item << item2 }.must_change('@roster.items.length', :by => 2)
49
49
  @roster[jid].wont_be_nil
50
50
  @roster[jid2].wont_be_nil
51
51
  end
@@ -58,7 +58,7 @@ describe 'Blather::Roster' do
58
58
  end
59
59
 
60
60
  it 'removes a JID' do
61
- 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)
62
62
  end
63
63
 
64
64
  it 'sends a @roster removal over the wire' do