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.
- data/LICENSE +22 -0
- data/README.md +162 -0
- data/examples/echo.rb +18 -0
- data/examples/execute.rb +16 -0
- data/examples/ping_pong.rb +37 -0
- data/examples/print_hierarchy.rb +76 -0
- data/examples/rosterprint.rb +14 -0
- data/examples/stream_only.rb +27 -0
- data/examples/xmpp4r/echo.rb +35 -0
- data/lib/blather/client/client.rb +310 -0
- data/lib/blather/client/dsl/pubsub.rb +170 -0
- data/lib/blather/client/dsl.rb +264 -0
- data/lib/blather/client.rb +87 -0
- data/lib/blather/core_ext/nokogiri.rb +40 -0
- data/lib/blather/errors/sasl_error.rb +43 -0
- data/lib/blather/errors/stanza_error.rb +107 -0
- data/lib/blather/errors/stream_error.rb +82 -0
- data/lib/blather/errors.rb +69 -0
- data/lib/blather/jid.rb +142 -0
- data/lib/blather/roster.rb +111 -0
- data/lib/blather/roster_item.rb +122 -0
- data/lib/blather/stanza/disco/disco_info.rb +176 -0
- data/lib/blather/stanza/disco/disco_items.rb +132 -0
- data/lib/blather/stanza/disco.rb +25 -0
- data/lib/blather/stanza/iq/query.rb +53 -0
- data/lib/blather/stanza/iq/roster.rb +179 -0
- data/lib/blather/stanza/iq.rb +138 -0
- data/lib/blather/stanza/message.rb +332 -0
- data/lib/blather/stanza/presence/status.rb +212 -0
- data/lib/blather/stanza/presence/subscription.rb +101 -0
- data/lib/blather/stanza/presence.rb +163 -0
- data/lib/blather/stanza/pubsub/affiliations.rb +79 -0
- data/lib/blather/stanza/pubsub/create.rb +65 -0
- data/lib/blather/stanza/pubsub/errors.rb +18 -0
- data/lib/blather/stanza/pubsub/event.rb +123 -0
- data/lib/blather/stanza/pubsub/items.rb +103 -0
- data/lib/blather/stanza/pubsub/publish.rb +103 -0
- data/lib/blather/stanza/pubsub/retract.rb +92 -0
- data/lib/blather/stanza/pubsub/subscribe.rb +68 -0
- data/lib/blather/stanza/pubsub/subscription.rb +134 -0
- data/lib/blather/stanza/pubsub/subscriptions.rb +81 -0
- data/lib/blather/stanza/pubsub/unsubscribe.rb +68 -0
- data/lib/blather/stanza/pubsub.rb +129 -0
- data/lib/blather/stanza/pubsub_owner/delete.rb +52 -0
- data/lib/blather/stanza/pubsub_owner/purge.rb +52 -0
- data/lib/blather/stanza/pubsub_owner.rb +51 -0
- data/lib/blather/stanza.rb +149 -0
- data/lib/blather/stream/client.rb +31 -0
- data/lib/blather/stream/component.rb +38 -0
- data/lib/blather/stream/features/resource.rb +63 -0
- data/lib/blather/stream/features/sasl.rb +187 -0
- data/lib/blather/stream/features/session.rb +44 -0
- data/lib/blather/stream/features/tls.rb +28 -0
- data/lib/blather/stream/features.rb +53 -0
- data/lib/blather/stream/parser.rb +102 -0
- data/lib/blather/stream.rb +231 -0
- data/lib/blather/xmpp_node.rb +218 -0
- data/lib/blather.rb +78 -0
- data/spec/blather/client/client_spec.rb +559 -0
- data/spec/blather/client/dsl/pubsub_spec.rb +462 -0
- data/spec/blather/client/dsl_spec.rb +143 -0
- data/spec/blather/core_ext/nokogiri_spec.rb +83 -0
- data/spec/blather/errors/sasl_error_spec.rb +33 -0
- data/spec/blather/errors/stanza_error_spec.rb +129 -0
- data/spec/blather/errors/stream_error_spec.rb +108 -0
- data/spec/blather/errors_spec.rb +33 -0
- data/spec/blather/jid_spec.rb +87 -0
- data/spec/blather/roster_item_spec.rb +96 -0
- data/spec/blather/roster_spec.rb +103 -0
- data/spec/blather/stanza/discos/disco_info_spec.rb +226 -0
- data/spec/blather/stanza/discos/disco_items_spec.rb +148 -0
- data/spec/blather/stanza/iq/query_spec.rb +64 -0
- data/spec/blather/stanza/iq/roster_spec.rb +140 -0
- data/spec/blather/stanza/iq_spec.rb +45 -0
- data/spec/blather/stanza/message_spec.rb +132 -0
- data/spec/blather/stanza/presence/status_spec.rb +132 -0
- data/spec/blather/stanza/presence/subscription_spec.rb +105 -0
- data/spec/blather/stanza/presence_spec.rb +66 -0
- data/spec/blather/stanza/pubsub/affiliations_spec.rb +57 -0
- data/spec/blather/stanza/pubsub/create_spec.rb +56 -0
- data/spec/blather/stanza/pubsub/event_spec.rb +84 -0
- data/spec/blather/stanza/pubsub/items_spec.rb +79 -0
- data/spec/blather/stanza/pubsub/publish_spec.rb +83 -0
- data/spec/blather/stanza/pubsub/retract_spec.rb +75 -0
- data/spec/blather/stanza/pubsub/subscribe_spec.rb +61 -0
- data/spec/blather/stanza/pubsub/subscription_spec.rb +97 -0
- data/spec/blather/stanza/pubsub/subscriptions_spec.rb +59 -0
- data/spec/blather/stanza/pubsub/unsubscribe_spec.rb +61 -0
- data/spec/blather/stanza/pubsub_owner/delete_spec.rb +50 -0
- data/spec/blather/stanza/pubsub_owner/purge_spec.rb +50 -0
- data/spec/blather/stanza/pubsub_owner_spec.rb +27 -0
- data/spec/blather/stanza/pubsub_spec.rb +67 -0
- data/spec/blather/stanza_spec.rb +116 -0
- data/spec/blather/stream/client_spec.rb +1011 -0
- data/spec/blather/stream/component_spec.rb +95 -0
- data/spec/blather/stream/parser_spec.rb +145 -0
- data/spec/blather/xmpp_node_spec.rb +231 -0
- data/spec/fixtures/pubsub.rb +311 -0
- data/spec/spec_helper.rb +43 -0
- metadata +249 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
require File.join(File.dirname(__FILE__), *%w[.. .. spec_helper])
|
|
2
|
+
|
|
3
|
+
describe Blather::Stream::Component do
|
|
4
|
+
class MockServer; end
|
|
5
|
+
module ServerMock
|
|
6
|
+
def receive_data(data)
|
|
7
|
+
@server ||= MockServer.new
|
|
8
|
+
@server.receive_data data, self
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def mocked_server(times = nil, &block)
|
|
13
|
+
@client ||= mock()
|
|
14
|
+
@client.stubs(:unbind) unless @client.respond_to?(:unbind)
|
|
15
|
+
@client.stubs(:jid=) unless @client.respond_to?(:jid=)
|
|
16
|
+
|
|
17
|
+
MockServer.any_instance.expects(:receive_data).send(*(times ? [:times, times] : [:at_least, 1])).with &block
|
|
18
|
+
EventMachine::run {
|
|
19
|
+
# Mocked server
|
|
20
|
+
EventMachine::start_server '127.0.0.1', 12345, ServerMock
|
|
21
|
+
|
|
22
|
+
# Blather::Stream connection
|
|
23
|
+
EM.connect('127.0.0.1', 12345, Blather::Stream::Component, @client, @jid || 'comp.id', 'secret') { |c| @stream = c }
|
|
24
|
+
}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'can be started' do
|
|
28
|
+
client = mock()
|
|
29
|
+
params = [client, 'comp.id', 'secret', 'host', 1234]
|
|
30
|
+
EM.expects(:connect).with do |*parms|
|
|
31
|
+
parms[0] == 'host' &&
|
|
32
|
+
parms[1] == 1234 &&
|
|
33
|
+
parms[3] == client &&
|
|
34
|
+
parms[4] == 'comp.id'
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
Blather::Stream::Component.start *params
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'shakes hands with the server' do
|
|
41
|
+
state = nil
|
|
42
|
+
mocked_server(2) do |val, server|
|
|
43
|
+
case state
|
|
44
|
+
when nil
|
|
45
|
+
state = :started
|
|
46
|
+
server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:component:accept' xmlns:stream='http://etherx.jabber.org/streams' id='12345'>"
|
|
47
|
+
val.must_match(/stream:stream/)
|
|
48
|
+
|
|
49
|
+
when :started
|
|
50
|
+
server.send_data '<handshake/>'
|
|
51
|
+
EM.stop
|
|
52
|
+
val.must_equal "<handshake>#{Digest::SHA1.hexdigest('12345'+"secret")}</handshake>"
|
|
53
|
+
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it 'starts the stream once the connection is complete' do
|
|
59
|
+
mocked_server(1) { |val, _| EM.stop; val.must_match(/stream:stream/) }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it 'sends stanzas to the client when the stream is ready' do
|
|
63
|
+
@client = mock(:post_init)
|
|
64
|
+
@client.expects(:receive_data).with do |n|
|
|
65
|
+
EM.stop
|
|
66
|
+
n.kind_of? Blather::XMPPNode
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
state = nil
|
|
70
|
+
mocked_server(2) do |val, server|
|
|
71
|
+
case state
|
|
72
|
+
when nil
|
|
73
|
+
state = :started
|
|
74
|
+
server.send_data "<?xml version='1.0'?><stream:stream xmlns='jabber:component:accept' xmlns:stream='http://etherx.jabber.org/streams' id='12345'>"
|
|
75
|
+
val.must_match(/stream:stream/)
|
|
76
|
+
|
|
77
|
+
when :started
|
|
78
|
+
server.send_data '<handshake/>'
|
|
79
|
+
server.send_data "<message to='comp.id' from='d@e/f' type='chat' xml:lang='en'><body>Message!</body></message>"
|
|
80
|
+
val.must_equal "<handshake>#{Digest::SHA1.hexdigest('12345'+"secret")}</handshake>"
|
|
81
|
+
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
it 'sends stanzas to the wire ensuring "from" is set' do
|
|
87
|
+
client = mock()
|
|
88
|
+
client.stubs(:jid)
|
|
89
|
+
client.stubs(:jid=)
|
|
90
|
+
msg = Blather::Stanza::Message.new 'to@jid.com', 'body'
|
|
91
|
+
comp = Blather::Stream::Component.new nil, client, 'jid.com', 'pass'
|
|
92
|
+
comp.expects(:send_data).with { |s| s.must_match(/^<message[^>]*from="jid\.com"/) }
|
|
93
|
+
comp.send msg
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
require File.join(File.dirname(__FILE__), *%w[.. .. spec_helper])
|
|
2
|
+
|
|
3
|
+
describe Blather::Stream::Parser do
|
|
4
|
+
before do
|
|
5
|
+
@client = Class.new do
|
|
6
|
+
attr_reader :data
|
|
7
|
+
def stopped?; false; end
|
|
8
|
+
def receive(data)
|
|
9
|
+
@data ||= []
|
|
10
|
+
@data << data
|
|
11
|
+
end
|
|
12
|
+
end.new
|
|
13
|
+
@parser = Blather::Stream::Parser.new @client
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
after { @client = @parser = nil}
|
|
17
|
+
|
|
18
|
+
def check_parse(data)
|
|
19
|
+
@parser.receive_data data
|
|
20
|
+
@client.data.size.must_equal 1
|
|
21
|
+
@client.data[0].to_s.gsub(/\n\s*/,'').must_equal data
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'handles fragmented parsing' do
|
|
25
|
+
@parser.receive_data '<foo>'
|
|
26
|
+
@parser.receive_data '<bar/>'
|
|
27
|
+
@parser.receive_data '</foo>'
|
|
28
|
+
@client.data.size.must_equal 1
|
|
29
|
+
@client.data[0].to_s.gsub(/\n\s*/,'').must_equal '<foo><bar/></foo>'
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it 'handles a basic example' do
|
|
33
|
+
check_parse "<foo/>"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it 'handles a basic namespace definition' do
|
|
37
|
+
check_parse '<foo xmlns="bar:baz"/>'
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'handles multiple namespace definitions' do
|
|
41
|
+
check_parse '<foo xmlns="bar:baz" xmlns:bar="baz"/>'
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it 'handles prefix namespacing' do
|
|
45
|
+
check_parse '<bar:foo xmlns="bar:baz" xmlns:bar="baz"/>'
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it 'handles namespaces with children' do
|
|
49
|
+
check_parse "<foo xmlns=\"bar:baz\"><bar/></foo>"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it 'handles multiple namespaces with children' do
|
|
53
|
+
check_parse "<foo xmlns=\"bar:baz\" xmlns:bar=\"baz\"><bar/></foo>"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it 'handles prefix namespaces with children' do
|
|
57
|
+
check_parse "<bar:foo xmlns=\"bar:baz\" xmlns:bar=\"baz\"><bar/></bar:foo>"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it 'handles prefix namespaces with children in the namespace' do
|
|
61
|
+
check_parse "<bar:foo xmlns=\"bar:baz\" xmlns:bar=\"baz\"><bar:bar/></bar:foo>"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it 'handles prefix namespaces with children in the namespace' do
|
|
65
|
+
check_parse "<bar:foo xmlns=\"bar:baz\" xmlns:bar=\"baz\"><baz><bar:buz/></baz></bar:foo>"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it 'handles attributes' do
|
|
69
|
+
check_parse '<foo bar="baz"/>'
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it 'handles character input' do
|
|
73
|
+
check_parse '<foo>bar baz fizbuz</foo>'
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it 'handles a complex input' do
|
|
77
|
+
data = [
|
|
78
|
+
'<message type="error" id="another-id">',
|
|
79
|
+
'<error type="modify">',
|
|
80
|
+
'<undefined-condition xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>',
|
|
81
|
+
'<text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas">Some special application diagnostic information...</text>',
|
|
82
|
+
'<special-application-condition xmlns="special:application-ns"/>',
|
|
83
|
+
"</error>",
|
|
84
|
+
"</message>",
|
|
85
|
+
]
|
|
86
|
+
data.each { |d| @parser.receive_data d }
|
|
87
|
+
@client.data.size.must_equal 1
|
|
88
|
+
@client.data[0].to_s.split("\n").map{|n|n.strip}.must_equal data
|
|
89
|
+
@client.data[0].xpath('//*[namespace-uri()="urn:ietf:params:xml:ns:xmpp-stanzas"]').size.must_equal 2
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it 'responds with stream:stream as a separate response' do
|
|
93
|
+
data = [
|
|
94
|
+
'<stream:stream xmlns="jabber:client" xmlns:stream="http://etherx.jabber.org/streams" to="example.com" version="1.0">',
|
|
95
|
+
'<foo/>'
|
|
96
|
+
]
|
|
97
|
+
data.each { |d| @parser.receive_data d }
|
|
98
|
+
@client.data.size.must_equal 2
|
|
99
|
+
@client.data[0].document.xpath('/stream:stream[@to="example.com" and @version="1.0"]', 'xmlns' => 'jabber:client', 'stream' => 'http://etherx.jabber.org/streams').size.must_equal 1
|
|
100
|
+
@client.data[1].to_s.must_equal '<foo/>'
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
it 'response with stream:end when receiving </stream:stream>' do
|
|
104
|
+
@parser.receive_data '<stream:stream xmlns:stream="http://etherx.jabber.org/streams"/>'
|
|
105
|
+
@client.data.size.must_equal 2
|
|
106
|
+
@client.data[1].to_s.must_equal '<stream:end xmlns:stream="http://etherx.jabber.org/streams"/>'
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
it 'raises ParseError when an error is sent' do
|
|
110
|
+
lambda { @parser.receive_data "<stream:stream>" }.must_raise(Blather::ParseError)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
it 'handles stream stanzas without an issue' do
|
|
114
|
+
data = [
|
|
115
|
+
'<stream:stream xmlns="jabber:client" xmlns:stream="http://etherx.jabber.org/streams" to="example.com" version="1.0">',
|
|
116
|
+
'<stream:features/>'
|
|
117
|
+
]
|
|
118
|
+
data.each { |d| @parser.receive_data d }
|
|
119
|
+
@client.data.size.must_equal 2
|
|
120
|
+
@client.data[0].document.xpath('/stream:stream[@to="example.com" and @version="1.0"]', 'xmlns' => 'jabber:client', 'stream' => 'http://etherx.jabber.org/streams').size.must_equal 1
|
|
121
|
+
@client.data[1].to_s.must_equal '<stream:features xmlns:stream="http://etherx.jabber.org/streams"/>'
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
it 'ignores the client namespace on stanzas' do
|
|
125
|
+
[ "<message type='chat' to='n@d' from='n@d/r' id='id1' xmlns='jabber:client'>",
|
|
126
|
+
"<body>exit</body>",
|
|
127
|
+
"<html xmlns='http://jabber.org/protocol/xhtml-im'><body xmlns='http://www.w3.org/1999/xhtml'>exit</body></html>",
|
|
128
|
+
"</message>"
|
|
129
|
+
].each { |d| @parser.receive_data d }
|
|
130
|
+
@client.data.size.must_equal 1
|
|
131
|
+
@client.data[0].document.xpath('/message/body[.="exit"]').wont_be_empty
|
|
132
|
+
@client.data[0].document.xpath('/message/im:html/xhtml:body[.="exit"]', 'im' => 'http://jabber.org/protocol/xhtml-im', 'xhtml' => 'http://www.w3.org/1999/xhtml').wont_be_empty
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
it 'ignores the component namespace on stanzas' do
|
|
136
|
+
[ "<message type='chat' to='n@d' from='n@d/r' id='id1' xmlns='jabber:component:accept'>",
|
|
137
|
+
"<body>exit</body>",
|
|
138
|
+
"<html xmlns='http://jabber.org/protocol/xhtml-im'><body xmlns='http://www.w3.org/1999/xhtml'>exit</body></html>",
|
|
139
|
+
"</message>"
|
|
140
|
+
].each { |d| @parser.receive_data d }
|
|
141
|
+
@client.data.size.must_equal 1
|
|
142
|
+
@client.data[0].document.xpath('/message/body[.="exit"]').wont_be_empty
|
|
143
|
+
@client.data[0].document.xpath('/message/im:html/xhtml:body[.="exit"]', 'im' => 'http://jabber.org/protocol/xhtml-im', 'xhtml' => 'http://www.w3.org/1999/xhtml').wont_be_empty
|
|
144
|
+
end
|
|
145
|
+
end
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
require File.join(File.dirname(__FILE__), *%w[.. spec_helper])
|
|
2
|
+
|
|
3
|
+
describe Blather::XMPPNode do
|
|
4
|
+
before { @doc = Nokogiri::XML::Document.new }
|
|
5
|
+
|
|
6
|
+
it 'generates a new node automatically setting the document' do
|
|
7
|
+
n = Blather::XMPPNode.new 'foo'
|
|
8
|
+
n.element_name.must_equal 'foo'
|
|
9
|
+
n.document.wont_equal @doc
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it 'sets the new document root to the node' do
|
|
13
|
+
n = Blather::XMPPNode.new 'foo'
|
|
14
|
+
n.document.root.must_equal n
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'does not set the document root if the document is provided' do
|
|
18
|
+
n = Blather::XMPPNode.new 'foo', @doc
|
|
19
|
+
n.document.root.wont_equal n
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it 'generates a new node with the given document' do
|
|
23
|
+
n = Blather::XMPPNode.new 'foo', @doc
|
|
24
|
+
n.element_name.must_equal 'foo'
|
|
25
|
+
n.document.must_equal @doc
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it 'generates a node based on the registered_name' do
|
|
29
|
+
foo = Class.new(Blather::XMPPNode)
|
|
30
|
+
foo.registered_name = 'foo'
|
|
31
|
+
foo.new.element_name.must_equal 'foo'
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'sets the namespace on creation' do
|
|
35
|
+
foo = Class.new(Blather::XMPPNode)
|
|
36
|
+
foo.registered_ns = 'foo'
|
|
37
|
+
foo.new('foo').namespace.href.must_equal 'foo'
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'registers sub classes' do
|
|
41
|
+
class RegistersSubClass < Blather::XMPPNode; register 'foo', 'foo:bar'; end
|
|
42
|
+
RegistersSubClass.registered_name.must_equal 'foo'
|
|
43
|
+
RegistersSubClass.registered_ns.must_equal 'foo:bar'
|
|
44
|
+
Blather::XMPPNode.class_from_registration('foo', 'foo:bar').must_equal RegistersSubClass
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it 'imports another node' do
|
|
48
|
+
class ImportSubClass < Blather::XMPPNode; register 'foo', 'foo:bar'; end
|
|
49
|
+
n = Blather::XMPPNode.new('foo')
|
|
50
|
+
n.namespace = 'foo:bar'
|
|
51
|
+
Blather::XMPPNode.import(n).must_be_kind_of ImportSubClass
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it 'provides an attribute reader' do
|
|
55
|
+
foo = Blather::XMPPNode.new
|
|
56
|
+
foo.read_attr(:bar).must_be_nil
|
|
57
|
+
foo[:bar] = 'baz'
|
|
58
|
+
foo.read_attr(:bar).must_equal 'baz'
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it 'provides an attribute reader with converstion' do
|
|
62
|
+
foo = Blather::XMPPNode.new
|
|
63
|
+
foo.read_attr(:bar, :to_sym).must_be_nil
|
|
64
|
+
foo[:bar] = 'baz'
|
|
65
|
+
foo.read_attr(:bar, :to_sym).must_equal :baz
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it 'provides an attribute writer' do
|
|
69
|
+
foo = Blather::XMPPNode.new
|
|
70
|
+
foo[:bar].must_be_nil
|
|
71
|
+
foo.write_attr(:bar, 'baz')
|
|
72
|
+
foo[:bar].must_equal 'baz'
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it 'provides a content reader' do
|
|
76
|
+
foo = Blather::XMPPNode.new('foo')
|
|
77
|
+
foo << (bar = Blather::XMPPNode.new('bar', foo.document))
|
|
78
|
+
bar.content = 'baz'
|
|
79
|
+
foo.read_content(:bar).must_equal 'baz'
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it 'provides a content reader that converts the value' do
|
|
83
|
+
foo = Blather::XMPPNode.new('foo')
|
|
84
|
+
foo << (bar = Blather::XMPPNode.new('bar', foo.document))
|
|
85
|
+
bar.content = 'baz'
|
|
86
|
+
foo.read_content(:bar, :to_sym).must_equal :baz
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it 'provides a content writer' do
|
|
90
|
+
foo = Blather::XMPPNode.new('foo')
|
|
91
|
+
foo.set_content_for :bar, 'baz'
|
|
92
|
+
foo.content_from(:bar).must_equal 'baz'
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it 'provides a content writer that removes a child when set to nil' do
|
|
96
|
+
foo = Blather::XMPPNode.new('foo')
|
|
97
|
+
foo << (bar = Blather::XMPPNode.new('bar', foo.document))
|
|
98
|
+
bar.content = 'baz'
|
|
99
|
+
foo.content_from(:bar).must_equal 'baz'
|
|
100
|
+
foo.xpath('bar').wont_be_empty
|
|
101
|
+
|
|
102
|
+
foo.set_content_for :bar, nil
|
|
103
|
+
foo.content_from(:bar).must_be_nil
|
|
104
|
+
foo.xpath('bar').must_be_empty
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it 'can convert itself into a stanza' do
|
|
108
|
+
class StanzaConvert < Blather::XMPPNode; register 'foo'; end
|
|
109
|
+
n = Blather::XMPPNode.new('foo')
|
|
110
|
+
n.to_stanza.must_be_kind_of StanzaConvert
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
it 'provides "attr_accessor" for namespace' do
|
|
114
|
+
n = Blather::XMPPNode.new('foo')
|
|
115
|
+
n.namespace.must_be_nil
|
|
116
|
+
|
|
117
|
+
n.namespace = 'foo:bar'
|
|
118
|
+
n.namespace_href.must_equal 'foo:bar'
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it 'will remove a child element' do
|
|
122
|
+
n = Blather::XMPPNode.new 'foo'
|
|
123
|
+
n << Blather::XMPPNode.new('bar', n.document)
|
|
124
|
+
n << Blather::XMPPNode.new('bar', n.document)
|
|
125
|
+
|
|
126
|
+
n.find(:bar).size.must_equal 2
|
|
127
|
+
n.remove_child 'bar'
|
|
128
|
+
n.find(:bar).size.must_equal 1
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
it 'will remove a child with a specific xmlns' do
|
|
132
|
+
n = Blather::XMPPNode.new 'foo'
|
|
133
|
+
n << Blather::XMPPNode.new('bar')
|
|
134
|
+
c = Blather::XMPPNode.new('bar')
|
|
135
|
+
c.namespace = 'foo:bar'
|
|
136
|
+
n << c
|
|
137
|
+
|
|
138
|
+
n.find(:bar).size.must_equal 2
|
|
139
|
+
n.remove_child 'bar', 'foo:bar'
|
|
140
|
+
n.find(:bar).size.must_equal 1
|
|
141
|
+
n.find(:bar).first.namespace.must_be_nil
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
it 'will remove all child elements' do
|
|
145
|
+
n = Blather::XMPPNode.new 'foo'
|
|
146
|
+
n << Blather::XMPPNode.new('bar')
|
|
147
|
+
n << Blather::XMPPNode.new('bar')
|
|
148
|
+
|
|
149
|
+
n.find(:bar).size.must_equal 2
|
|
150
|
+
n.remove_children 'bar'
|
|
151
|
+
n.find(:bar).size.must_equal 0
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
it 'provides a copy mechanism' do
|
|
155
|
+
n = Blather::XMPPNode.new 'foo'
|
|
156
|
+
n2 = n.copy
|
|
157
|
+
n2.object_id.wont_equal n.object_id
|
|
158
|
+
n2.element_name.must_equal n.element_name
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
it 'provides an inherit mechanism' do
|
|
162
|
+
n = Blather::XMPPNode.new 'foo'
|
|
163
|
+
n2 = Blather::XMPPNode.new 'foo'
|
|
164
|
+
n2.content = 'bar'
|
|
165
|
+
n2['foo'] = 'bar'
|
|
166
|
+
|
|
167
|
+
n.inherit(n2)
|
|
168
|
+
n['foo'].must_equal 'bar'
|
|
169
|
+
n.content.must_equal 'bar'
|
|
170
|
+
n2.to_s.must_equal n.to_s
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
it 'holds on to namespaces when inheriting content' do
|
|
174
|
+
n = parse_stanza('<message><bar:foo xmlns:bar="http://bar.com"></message>').root
|
|
175
|
+
n2 = Blather::XMPPNode.new('message').inherit n
|
|
176
|
+
n2.to_s.must_equal n.to_s
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
it 'provides a mechanism to inherit attrs' do
|
|
180
|
+
n = Blather::XMPPNode.new 'foo'
|
|
181
|
+
n2 = Blather::XMPPNode.new 'foo'
|
|
182
|
+
n2['foo'] = 'bar'
|
|
183
|
+
|
|
184
|
+
n.inherit_attrs(n2.attributes)
|
|
185
|
+
n['foo'].must_equal 'bar'
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
it 'has a content_from helper that pulls the content from a child node' do
|
|
189
|
+
f = Blather::XMPPNode.new('foo')
|
|
190
|
+
f << (b = Blather::XMPPNode.new('bar'))
|
|
191
|
+
b.content = 'content'
|
|
192
|
+
f.content_from(:bar).must_equal 'content'
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
it 'returns nil when sent #content_from and a missing node' do
|
|
196
|
+
f = Blather::XMPPNode.new('foo')
|
|
197
|
+
f.content_from(:bar).must_be_nil
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
it 'creates a new node and sets content when sent #set_content_for' do
|
|
201
|
+
f = Blather::XMPPNode.new('foo')
|
|
202
|
+
f.must_respond_to :set_content_for
|
|
203
|
+
f.xpath('bar').must_be_empty
|
|
204
|
+
f.set_content_for :bar, :baz
|
|
205
|
+
f.xpath('bar').wont_be_empty
|
|
206
|
+
f.xpath('bar').first.content.must_equal 'baz'
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
it 'removes a child node when sent #set_content_for with nil' do
|
|
210
|
+
f = Blather::XMPPNode.new('foo')
|
|
211
|
+
f << (b = Blather::XMPPNode.new('bar'))
|
|
212
|
+
f.must_respond_to :set_content_for
|
|
213
|
+
f.xpath('bar').wont_be_empty
|
|
214
|
+
f.set_content_for :bar, nil
|
|
215
|
+
f.xpath('bar').must_be_empty
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
it 'will change the content of an existing node when sent #set_content_for' do
|
|
219
|
+
f = Blather::XMPPNode.new('foo')
|
|
220
|
+
f << (b = Blather::XMPPNode.new('bar'))
|
|
221
|
+
b.content = 'baz'
|
|
222
|
+
f.must_respond_to :set_content_for
|
|
223
|
+
f.xpath('bar').wont_be_empty
|
|
224
|
+
f.xpath('bar').first.content.must_equal 'baz'
|
|
225
|
+
control = f.xpath('bar').first.pointer_id
|
|
226
|
+
|
|
227
|
+
f.set_content_for :bar, 'fiz'
|
|
228
|
+
f.xpath('bar').first.content.must_equal 'fiz'
|
|
229
|
+
f.xpath('bar').first.pointer_id.must_equal control
|
|
230
|
+
end
|
|
231
|
+
end
|