shingara-blather 0.4.8

Sign up to get free protection for your applications and to get access to all the features.
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