shingara-blather 0.4.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. data/LICENSE +22 -0
  2. data/README.md +162 -0
  3. data/examples/echo.rb +18 -0
  4. data/examples/execute.rb +16 -0
  5. data/examples/ping_pong.rb +37 -0
  6. data/examples/print_hierarchy.rb +76 -0
  7. data/examples/rosterprint.rb +14 -0
  8. data/examples/stream_only.rb +27 -0
  9. data/examples/xmpp4r/echo.rb +35 -0
  10. data/lib/blather/client/client.rb +310 -0
  11. data/lib/blather/client/dsl/pubsub.rb +170 -0
  12. data/lib/blather/client/dsl.rb +264 -0
  13. data/lib/blather/client.rb +87 -0
  14. data/lib/blather/core_ext/nokogiri.rb +40 -0
  15. data/lib/blather/errors/sasl_error.rb +43 -0
  16. data/lib/blather/errors/stanza_error.rb +107 -0
  17. data/lib/blather/errors/stream_error.rb +82 -0
  18. data/lib/blather/errors.rb +69 -0
  19. data/lib/blather/jid.rb +142 -0
  20. data/lib/blather/roster.rb +111 -0
  21. data/lib/blather/roster_item.rb +122 -0
  22. data/lib/blather/stanza/disco/disco_info.rb +176 -0
  23. data/lib/blather/stanza/disco/disco_items.rb +132 -0
  24. data/lib/blather/stanza/disco.rb +25 -0
  25. data/lib/blather/stanza/iq/query.rb +53 -0
  26. data/lib/blather/stanza/iq/roster.rb +179 -0
  27. data/lib/blather/stanza/iq.rb +138 -0
  28. data/lib/blather/stanza/message.rb +332 -0
  29. data/lib/blather/stanza/presence/status.rb +212 -0
  30. data/lib/blather/stanza/presence/subscription.rb +101 -0
  31. data/lib/blather/stanza/presence.rb +163 -0
  32. data/lib/blather/stanza/pubsub/affiliations.rb +79 -0
  33. data/lib/blather/stanza/pubsub/create.rb +65 -0
  34. data/lib/blather/stanza/pubsub/errors.rb +18 -0
  35. data/lib/blather/stanza/pubsub/event.rb +123 -0
  36. data/lib/blather/stanza/pubsub/items.rb +103 -0
  37. data/lib/blather/stanza/pubsub/publish.rb +103 -0
  38. data/lib/blather/stanza/pubsub/retract.rb +92 -0
  39. data/lib/blather/stanza/pubsub/subscribe.rb +68 -0
  40. data/lib/blather/stanza/pubsub/subscription.rb +134 -0
  41. data/lib/blather/stanza/pubsub/subscriptions.rb +81 -0
  42. data/lib/blather/stanza/pubsub/unsubscribe.rb +68 -0
  43. data/lib/blather/stanza/pubsub.rb +129 -0
  44. data/lib/blather/stanza/pubsub_owner/delete.rb +52 -0
  45. data/lib/blather/stanza/pubsub_owner/purge.rb +52 -0
  46. data/lib/blather/stanza/pubsub_owner.rb +51 -0
  47. data/lib/blather/stanza.rb +149 -0
  48. data/lib/blather/stream/client.rb +31 -0
  49. data/lib/blather/stream/component.rb +38 -0
  50. data/lib/blather/stream/features/resource.rb +63 -0
  51. data/lib/blather/stream/features/sasl.rb +187 -0
  52. data/lib/blather/stream/features/session.rb +44 -0
  53. data/lib/blather/stream/features/tls.rb +28 -0
  54. data/lib/blather/stream/features.rb +53 -0
  55. data/lib/blather/stream/parser.rb +102 -0
  56. data/lib/blather/stream.rb +231 -0
  57. data/lib/blather/xmpp_node.rb +218 -0
  58. data/lib/blather.rb +78 -0
  59. data/spec/blather/client/client_spec.rb +559 -0
  60. data/spec/blather/client/dsl/pubsub_spec.rb +462 -0
  61. data/spec/blather/client/dsl_spec.rb +143 -0
  62. data/spec/blather/core_ext/nokogiri_spec.rb +83 -0
  63. data/spec/blather/errors/sasl_error_spec.rb +33 -0
  64. data/spec/blather/errors/stanza_error_spec.rb +129 -0
  65. data/spec/blather/errors/stream_error_spec.rb +108 -0
  66. data/spec/blather/errors_spec.rb +33 -0
  67. data/spec/blather/jid_spec.rb +87 -0
  68. data/spec/blather/roster_item_spec.rb +96 -0
  69. data/spec/blather/roster_spec.rb +103 -0
  70. data/spec/blather/stanza/discos/disco_info_spec.rb +226 -0
  71. data/spec/blather/stanza/discos/disco_items_spec.rb +148 -0
  72. data/spec/blather/stanza/iq/query_spec.rb +64 -0
  73. data/spec/blather/stanza/iq/roster_spec.rb +140 -0
  74. data/spec/blather/stanza/iq_spec.rb +45 -0
  75. data/spec/blather/stanza/message_spec.rb +132 -0
  76. data/spec/blather/stanza/presence/status_spec.rb +132 -0
  77. data/spec/blather/stanza/presence/subscription_spec.rb +105 -0
  78. data/spec/blather/stanza/presence_spec.rb +66 -0
  79. data/spec/blather/stanza/pubsub/affiliations_spec.rb +57 -0
  80. data/spec/blather/stanza/pubsub/create_spec.rb +56 -0
  81. data/spec/blather/stanza/pubsub/event_spec.rb +84 -0
  82. data/spec/blather/stanza/pubsub/items_spec.rb +79 -0
  83. data/spec/blather/stanza/pubsub/publish_spec.rb +83 -0
  84. data/spec/blather/stanza/pubsub/retract_spec.rb +75 -0
  85. data/spec/blather/stanza/pubsub/subscribe_spec.rb +61 -0
  86. data/spec/blather/stanza/pubsub/subscription_spec.rb +97 -0
  87. data/spec/blather/stanza/pubsub/subscriptions_spec.rb +59 -0
  88. data/spec/blather/stanza/pubsub/unsubscribe_spec.rb +61 -0
  89. data/spec/blather/stanza/pubsub_owner/delete_spec.rb +50 -0
  90. data/spec/blather/stanza/pubsub_owner/purge_spec.rb +50 -0
  91. data/spec/blather/stanza/pubsub_owner_spec.rb +27 -0
  92. data/spec/blather/stanza/pubsub_spec.rb +67 -0
  93. data/spec/blather/stanza_spec.rb +116 -0
  94. data/spec/blather/stream/client_spec.rb +1011 -0
  95. data/spec/blather/stream/component_spec.rb +95 -0
  96. data/spec/blather/stream/parser_spec.rb +145 -0
  97. data/spec/blather/xmpp_node_spec.rb +231 -0
  98. data/spec/fixtures/pubsub.rb +311 -0
  99. data/spec/spec_helper.rb +43 -0
  100. metadata +249 -0
@@ -0,0 +1,33 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. .. spec_helper])
2
+
3
+ def sasl_error_node(err_name = 'aborted')
4
+ node = Blather::XMPPNode.new 'failure'
5
+ node.namespace = Blather::SASLError::SASL_ERR_NS
6
+
7
+ node << Blather::XMPPNode.new(err_name, node.document)
8
+ node
9
+ end
10
+
11
+ describe Blather::SASLError do
12
+ it 'can import a node' do
13
+ Blather::SASLError.must_respond_to :import
14
+ e = Blather::SASLError.import sasl_error_node
15
+ e.must_be_kind_of Blather::SASLError
16
+ end
17
+
18
+ describe 'each XMPP SASL error type' do
19
+ %w[ aborted
20
+ incorrect-encoding
21
+ invalid-authzid
22
+ invalid-mechanism
23
+ mechanism-too-weak
24
+ not-authorized
25
+ temporary-auth-failure
26
+ ].each do |error_type|
27
+ it "handles the name for #{error_type}" do
28
+ e = Blather::SASLError.import sasl_error_node(error_type)
29
+ e.name.must_equal error_type.gsub('-','_').to_sym
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,129 @@
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 = Blather::Stanza::Message.new 'error@jabber.local', 'test message', :error
5
+
6
+ node << (error_node = Blather::XMPPNode.new('error'))
7
+ error_node['type'] = type.to_s
8
+
9
+ error_node << (err = Blather::XMPPNode.new(error, error_node.document))
10
+ err.namespace = 'urn:ietf:params:xml:ns:xmpp-stanzas'
11
+
12
+ if msg
13
+ error_node << (text = Blather::XMPPNode.new('text', error_node.document))
14
+ text.namespace = 'urn:ietf:params:xml:ns:xmpp-stanzas'
15
+ text.content = msg
16
+ end
17
+
18
+ error_node << (extra = Blather::XMPPNode.new('extra-error', error_node.document))
19
+ extra.namespace = 'blather:stanza:error'
20
+ extra.content = 'Blather Error'
21
+
22
+ node
23
+ end
24
+
25
+ describe Blather::StanzaError do
26
+ it 'can import a node' do
27
+ Blather::StanzaError.must_respond_to :import
28
+ e = Blather::StanzaError.import stanza_error_node
29
+ e.must_be_kind_of Blather::StanzaError
30
+ end
31
+
32
+ describe 'valid types' do
33
+ before { @original = Blather::Stanza::Message.new 'error@jabber.local', 'test message', :error }
34
+
35
+ it 'ensures type is one of Stanza::Message::VALID_TYPES' do
36
+ lambda { Blather::StanzaError.new @original, :gone, :invalid_type_name }.must_raise(Blather::ArgumentError)
37
+
38
+ Blather::StanzaError::VALID_TYPES.each do |valid_type|
39
+ msg = Blather::StanzaError.new @original, :gone, valid_type
40
+ msg.type.must_equal valid_type
41
+ end
42
+ end
43
+ end
44
+
45
+ describe 'when instantiated' do
46
+ before do
47
+ @type = 'cancel'
48
+ @err_name = 'internal-server-error'
49
+ @msg = 'the server has experienced a misconfiguration'
50
+ @err = Blather::StanzaError.import stanza_error_node(@type, @err_name, @msg)
51
+ end
52
+
53
+ it 'provides a type attribute' do
54
+ @err.must_respond_to :type
55
+ @err.type.must_equal @type.to_sym
56
+ end
57
+
58
+ it 'provides a name attribute' do
59
+ @err.must_respond_to :name
60
+ @err.name.must_equal @err_name.gsub('-','_').to_sym
61
+ end
62
+
63
+ it 'provides a text attribute' do
64
+ @err.must_respond_to :text
65
+ @err.text.must_equal @msg
66
+ end
67
+
68
+ it 'provides a reader to the original node' do
69
+ @err.must_respond_to :original
70
+ @err.original.must_be_instance_of Blather::Stanza::Message
71
+ end
72
+
73
+ it 'provides an extras attribute' do
74
+ @err.must_respond_to :extras
75
+ @err.extras.must_be_instance_of Array
76
+ @err.extras.first.element_name.must_equal 'extra-error'
77
+ end
78
+
79
+ it 'describes itself' do
80
+ @err.to_s.must_match(/#{@err_name}/)
81
+ @err.to_s.must_match(/#{@msg}/)
82
+
83
+ @err.inspect.must_match(/#{@err_name}/)
84
+ @err.inspect.must_match(/#{@msg}/)
85
+ end
86
+
87
+ it 'can be turned into xml' do
88
+ @err.must_respond_to :to_xml
89
+ doc = parse_stanza @err.to_xml
90
+
91
+ doc.xpath("/message[@from='error@jabber.local' and @type='error']").wont_be_empty
92
+ doc.xpath("/message/error").wont_be_empty
93
+ doc.xpath("/message/error/err_ns:internal-server-error", :err_ns => Blather::StanzaError::STANZA_ERR_NS).wont_be_empty
94
+ doc.xpath("/message/error/err_ns:text[.='the server has experienced a misconfiguration']", :err_ns => Blather::StanzaError::STANZA_ERR_NS).wont_be_empty
95
+ doc.xpath("/message/error/extra_ns:extra-error[.='Blather Error']", :extra_ns => 'blather:stanza:error').wont_be_empty
96
+ end
97
+ end
98
+
99
+ describe 'each XMPP stanza error type' do
100
+ %w[ bad-request
101
+ conflict
102
+ feature-not-implemented
103
+ forbidden
104
+ gone
105
+ internal-server-error
106
+ item-not-found
107
+ jid-malformed
108
+ not-acceptable
109
+ not-allowed
110
+ not-authorized
111
+ payment-required
112
+ recipient-unavailable
113
+ redirect
114
+ registration-required
115
+ remote-server-not-found
116
+ remote-server-timeout
117
+ resource-constraint
118
+ service-unavailable
119
+ subscription-required
120
+ undefined-condition
121
+ unexpected-request
122
+ ].each do |error_type|
123
+ it "handles the name for #{error_type}" do
124
+ e = Blather::StanzaError.import stanza_error_node(:cancel, error_type)
125
+ e.name.must_equal error_type.gsub('-','_').to_sym
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,108 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. .. spec_helper])
2
+
3
+ def stream_error_node(error = 'internal-server-error', msg = nil)
4
+ node = Blather::XMPPNode.new('error')
5
+ node.namespace = {'stream' => Blather::Stream::STREAM_NS}
6
+
7
+ node << (err = Blather::XMPPNode.new(error, node.document))
8
+ err.namespace = 'urn:ietf:params:xml:ns:xmpp-streams'
9
+
10
+ if msg
11
+ node << (text = Blather::XMPPNode.new('text', node.document))
12
+ text.namespace = 'urn:ietf:params:xml:ns:xmpp-streams'
13
+ text.content = msg
14
+ end
15
+
16
+ node << (extra = Blather::XMPPNode.new('extra-error', node.document))
17
+ extra.namespace = 'blather:stream:error'
18
+ extra.content = 'Blather Error'
19
+
20
+ node
21
+ end
22
+
23
+ describe 'Blather::StreamError' do
24
+ it 'can import a node' do
25
+ err = stream_error_node 'internal-server-error', 'the message'
26
+ Blather::StreamError.must_respond_to :import
27
+ e = Blather::StreamError.import err
28
+ e.must_be_kind_of Blather::StreamError
29
+
30
+ e.name.must_equal :internal_server_error
31
+ e.text.must_equal 'the message'
32
+ e.extras.must_equal err.find('descendant::*[name()="extra-error"]', 'blather:stream:error').map {|n|n}
33
+ end
34
+ end
35
+
36
+ describe 'Blather::StreamError when instantiated' do
37
+ before do
38
+ @err_name = 'internal-server-error'
39
+ @msg = 'the server has experienced a misconfiguration'
40
+ @err = Blather::StreamError.import stream_error_node(@err_name, @msg)
41
+ end
42
+
43
+ it 'provides a err_name attribute' do
44
+ @err.must_respond_to :name
45
+ @err.name.must_equal @err_name.gsub('-','_').to_sym
46
+ end
47
+
48
+ it 'provides a text attribute' do
49
+ @err.must_respond_to :text
50
+ @err.text.must_equal @msg
51
+ end
52
+
53
+ it 'provides an extras attribute' do
54
+ @err.must_respond_to :extras
55
+ @err.extras.must_be_instance_of Array
56
+ @err.extras.size.must_equal 1
57
+ @err.extras.first.element_name.must_equal 'extra-error'
58
+ end
59
+
60
+ it 'describes itself' do
61
+ @err.to_s.must_match(/#{@type}/)
62
+ @err.to_s.must_match(/#{@msg}/)
63
+
64
+ @err.inspect.must_match(/#{@type}/)
65
+ @err.inspect.must_match(/#{@msg}/)
66
+ end
67
+
68
+ it 'can be turned into xml' do
69
+ @err.must_respond_to :to_xml
70
+ doc = parse_stanza @err.to_xml
71
+ doc.xpath("//err_ns:internal-server-error", :err_ns => Blather::StreamError::STREAM_ERR_NS).wont_be_empty
72
+ doc.xpath("//err_ns:text[.='the server has experienced a misconfiguration']", :err_ns => Blather::StreamError::STREAM_ERR_NS).wont_be_empty
73
+ doc.xpath("//err_ns:extra-error[.='Blather Error']", :err_ns => 'blather:stream:error').wont_be_empty
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 "handles the name for #{error_type}" do
104
+ e = Blather::StreamError.import stream_error_node(error_type)
105
+ e.name.must_equal error_type.gsub('-','_').to_sym
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,33 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. spec_helper])
2
+
3
+ describe Blather::BlatherError do
4
+ it 'is handled by :error' do
5
+ Blather::BlatherError.new.handler_hierarchy.must_equal [:error]
6
+ end
7
+ end
8
+
9
+ describe 'Blather::ParseError' do
10
+ before { @error = Blather::ParseError.new('</generate-parse-error>"') }
11
+
12
+ it 'is registers with the handler hierarchy' do
13
+ @error.handler_hierarchy.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::UnknownResponse' do
23
+ before { @error = Blather::UnknownResponse.new(Blather::XMPPNode.new('foo-bar')) }
24
+
25
+ it 'is registers with the handler hierarchy' do
26
+ @error.handler_hierarchy.must_equal [:unknown_response_error, :error]
27
+ end
28
+
29
+ it 'holds on to a copy of the failure node' do
30
+ @error.must_respond_to :node
31
+ @error.node.element_name.must_equal 'foo-bar'
32
+ end
33
+ end
@@ -0,0 +1,87 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. spec_helper])
2
+
3
+ describe Blather::JID do
4
+ it 'does nothing if creaded from Blather::JID' do
5
+ jid = Blather::JID.new 'n@d/r'
6
+ Blather::JID.new(jid).object_id.must_equal jid.object_id
7
+ end
8
+
9
+ it 'creates a new Blather::JID from (n,d,r)' do
10
+ jid = Blather::JID.new('n', 'd', 'r')
11
+ jid.node.must_equal 'n'
12
+ jid.domain.must_equal 'd'
13
+ jid.resource.must_equal 'r'
14
+ end
15
+
16
+ it 'creates a new Blather::JID from (n,d)' do
17
+ jid = Blather::JID.new('n', 'd')
18
+ jid.node.must_equal 'n'
19
+ jid.domain.must_equal 'd'
20
+ end
21
+
22
+ it 'creates a new Blather::JID from (n@d)' do
23
+ jid = Blather::JID.new('n@d')
24
+ jid.node.must_equal 'n'
25
+ jid.domain.must_equal 'd'
26
+ end
27
+
28
+ it 'creates a new Blather::JID from (n@d/r)' do
29
+ jid = Blather::JID.new('n@d/r')
30
+ jid.node.must_equal 'n'
31
+ jid.domain.must_equal 'd'
32
+ jid.resource.must_equal 'r'
33
+ end
34
+
35
+ it 'requires at least a node' do
36
+ proc { Blather::JID.new }.must_raise ::ArgumentError
37
+ end
38
+
39
+ it 'ensures length of node is no more than 1023 characters' do
40
+ proc { Blather::JID.new('n'*1024) }.must_raise Blather::ArgumentError
41
+ end
42
+
43
+ it 'ensures length of domain is no more than 1023 characters' do
44
+ proc { Blather::JID.new('n', 'd'*1024) }.must_raise Blather::ArgumentError
45
+ end
46
+
47
+ it 'ensures length of resource is no more than 1023 characters' do
48
+ proc { Blather::JID.new('n', 'd', 'r'*1024) }.must_raise Blather::ArgumentError
49
+ end
50
+
51
+ it 'compares Blather::JIDs' do
52
+ (Blather::JID.new('a@b/c') <=> Blather::JID.new('d@e/f')).must_equal -1
53
+ (Blather::JID.new('a@b/c') <=> Blather::JID.new('a@b/c')).must_equal 0
54
+ (Blather::JID.new('d@e/f') <=> Blather::JID.new('a@b/c')).must_equal 1
55
+ end
56
+
57
+ it 'checks for equality' do
58
+ (Blather::JID.new('n@d/r') == Blather::JID.new('n@d/r')).must_equal true
59
+ Blather::JID.new('n@d/r').eql?(Blather::JID.new('n@d/r')).must_equal true
60
+ end
61
+
62
+ it 'will strip' do
63
+ jid = Blather::JID.new('n@d/r')
64
+ jid.stripped.must_equal Blather::JID.new('n@d')
65
+ jid.must_equal Blather::JID.new('n@d/r')
66
+ end
67
+
68
+ it 'will strip itself' do
69
+ jid = Blather::JID.new('n@d/r')
70
+ jid.strip!
71
+ jid.must_equal Blather::JID.new('n@d')
72
+ end
73
+
74
+ it 'has a string representation' do
75
+ Blather::JID.new('n@d/r').to_s.must_equal 'n@d/r'
76
+ Blather::JID.new('n', 'd', 'r').to_s.must_equal 'n@d/r'
77
+ Blather::JID.new('n', 'd').to_s.must_equal 'n@d'
78
+ end
79
+
80
+ it 'provides a #stripped? helper' do
81
+ jid = Blather::JID.new 'a@b/c'
82
+ jid.must_respond_to :stripped?
83
+ jid.stripped?.wont_equal true
84
+ jid.strip!
85
+ jid.stripped?.must_equal true
86
+ end
87
+ end
@@ -0,0 +1,96 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. spec_helper])
2
+
3
+ describe Blather::RosterItem do
4
+ it 'can be initialized with Blather::JID' do
5
+ jid = Blather::JID.new(jid)
6
+ i = Blather::RosterItem.new jid
7
+ i.jid.must_equal jid
8
+ end
9
+
10
+ it 'can be initialized with an Iq::RosterItem' do
11
+ jid = 'n@d/r'
12
+ i = Blather::RosterItem.new Blather::Stanza::Iq::Roster::RosterItem.new(jid)
13
+ i.jid.must_equal Blather::JID.new(jid).stripped
14
+ end
15
+
16
+ it 'can be initialized with a string' do
17
+ jid = 'n@d/r'
18
+ i = Blather::RosterItem.new jid
19
+ i.jid.must_equal Blather::JID.new(jid).stripped
20
+ end
21
+
22
+ it 'returns the same object when intialized with a Blather::RosterItem' do
23
+ control = Blather::RosterItem.new 'n@d/r'
24
+ Blather::RosterItem.new(control).must_be_same_as control
25
+ end
26
+
27
+ it 'has a Blather::JID setter that strips the Blather::JID' do
28
+ jid = Blather::JID.new('n@d/r')
29
+ i = Blather::RosterItem.new nil
30
+ i.jid = jid
31
+ i.jid.must_equal jid.stripped
32
+ end
33
+
34
+ it 'has a subscription setter that forces a symbol' do
35
+ i = Blather::RosterItem.new nil
36
+ i.subscription = 'remove'
37
+ i.subscription.must_equal :remove
38
+ end
39
+
40
+ it 'forces the type of subscription' do
41
+ proc { Blather::RosterItem.new(nil).subscription = 'foo' }.must_raise Blather::ArgumentError
42
+ end
43
+
44
+ it 'returns :none if the subscription field is blank' do
45
+ Blather::RosterItem.new(nil).subscription.must_equal :none
46
+ end
47
+
48
+ it 'ensure #ask is a symbol' do
49
+ i = Blather::RosterItem.new(nil)
50
+ i.ask = 'subscribe'
51
+ i.ask.must_equal :subscribe
52
+ end
53
+
54
+ it 'forces #ask to be :subscribe or nothing at all' do
55
+ proc { Blather::RosterItem.new(nil).ask = 'foo' }.must_raise Blather::ArgumentError
56
+ end
57
+
58
+ it 'generates a stanza with #to_stanza' do
59
+ jid = Blather::JID.new('n@d/r')
60
+ i = Blather::RosterItem.new jid
61
+ s = i.to_stanza
62
+ s.must_be_kind_of Blather::Stanza::Iq::Roster
63
+ s.items.first.jid.must_equal jid.stripped
64
+ end
65
+
66
+ it 'returns status based on priority' do
67
+ setup_item_with_presences
68
+ @i.status.must_equal @p2
69
+ end
70
+
71
+ it 'returns status based on resource' do
72
+ setup_item_with_presences
73
+ @i.status('a').must_equal @p
74
+ end
75
+
76
+ def setup_item_with_presences
77
+ @jid = Blather::JID.new('n@d/r')
78
+ @i = Blather::RosterItem.new @jid
79
+
80
+ @p = Blather::Stanza::Presence::Status.new(:away)
81
+ @p.from = 'n@d/a'
82
+ @p.priority = 0
83
+
84
+ @p2 = Blather::Stanza::Presence::Status.new(:dnd)
85
+ @p2.from = 'n@d/b'
86
+ @p2.priority = -1
87
+
88
+ @i.status = @p
89
+ @i.status = @p2
90
+ end
91
+
92
+ it 'initializes groups to [nil] if the item is not part of a group' do
93
+ i = Blather::RosterItem.new 'n@d'
94
+ i.groups.must_equal [nil]
95
+ end
96
+ end
@@ -0,0 +1,103 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. spec_helper])
2
+
3
+ describe Blather::Roster do
4
+ before do
5
+ @stream = mock()
6
+ @stream.stubs(:write)
7
+
8
+ @stanza = mock()
9
+ items = []; 4.times { |n| items << Blather::JID.new("n@d/#{n}r") }
10
+ @stanza.stubs(:items).returns(items)
11
+
12
+ @roster = Blather::Roster.new(@stream, @stanza)
13
+ end
14
+
15
+ it 'initializes with items' do
16
+ @roster.items.map { |_,i| i.jid.to_s }.must_equal(@stanza.items.map { |i| i.stripped.to_s }.uniq)
17
+ end
18
+
19
+ it 'processes @stanzas with remove requests' do
20
+ s = @roster['n@d/0r']
21
+ s.subscription = :remove
22
+ proc { @roster.process(s.to_stanza) }.must_change('@roster.items.length', :by => -1)
23
+ end
24
+
25
+ it 'processes @stanzas with add requests' do
26
+ s = Blather::Stanza::Iq::Roster::RosterItem.new('a@b/c').to_stanza
27
+ proc { @roster.process(s) }.must_change('@roster.items.length', :by => 1)
28
+ end
29
+
30
+ it 'allows a jid to be pushed' do
31
+ jid = 'a@b/c'
32
+ proc { @roster.push(jid) }.must_change('@roster.items.length', :by => 1)
33
+ @roster[jid].wont_be_nil
34
+ end
35
+
36
+ it 'allows an item to be pushed' do
37
+ jid = 'a@b/c'
38
+ item = Blather::RosterItem.new(Blather::JID.new(jid))
39
+ proc { @roster.push(item) }.must_change('@roster.items.length', :by => 1)
40
+ @roster[jid].wont_be_nil
41
+ end
42
+
43
+ it 'aliases #<< to #push and returns self to allow for chaining' do
44
+ jid = 'a@b/c'
45
+ item = Blather::RosterItem.new(Blather::JID.new(jid))
46
+ jid2 = 'd@e/f'
47
+ item2 = Blather::RosterItem.new(Blather::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
+
53
+ it 'sends a @roster addition over the wire' do
54
+ client = mock(:write => nil)
55
+ roster = Blather::Roster.new client, @stanza
56
+ roster.push('a@b/c')
57
+ end
58
+
59
+ it 'removes a Blather::JID' do
60
+ proc { @roster.delete 'n@d' }.must_change('@roster.items.length', :by => -1)
61
+ end
62
+
63
+ it 'sends a @roster removal over the wire' do
64
+ client = mock(:write => nil)
65
+ roster = Blather::Roster.new client, @stanza
66
+ roster.delete('a@b/c')
67
+ end
68
+
69
+ it 'returns an item through []' do
70
+ item = @roster['n@d']
71
+ item.must_be_kind_of Blather::RosterItem
72
+ item.jid.must_equal Blather::JID.new('n@d')
73
+ end
74
+
75
+ it 'responds to #each' do
76
+ @roster.must_respond_to :each
77
+ end
78
+
79
+ it 'cycles through the items using #each' do
80
+ @roster.map { |i| i }.sort.must_equal(@roster.items.sort)
81
+ end
82
+
83
+ it 'returns a duplicate of items through #items' do
84
+ items = @roster.items
85
+ items.delete 'n@d'
86
+ items.wont_equal @roster.items
87
+ end
88
+
89
+ it 'will group roster items' do
90
+ @roster.delete 'n@d'
91
+ item1 = Blather::RosterItem.new("n1@d")
92
+ item1.groups = ['group1', 'group2']
93
+ item2 = Blather::RosterItem.new("n2@d")
94
+ item2.groups = ['group1', 'group3']
95
+ @roster << item1 << item2
96
+
97
+ @roster.grouped.must_equal({
98
+ 'group1' => [item1, item2],
99
+ 'group2' => [item1],
100
+ 'group3' => [item2]
101
+ })
102
+ end
103
+ end