blather 0.8.1 → 0.8.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.
- data/.gitignore +1 -0
- data/.travis.yml +3 -2
- data/CHANGELOG.md +6 -0
- data/Guardfile +1 -1
- data/README.md +2 -2
- data/blather.gemspec +6 -5
- data/lib/blather/cert_store.rb +17 -15
- data/lib/blather/client.rb +2 -2
- data/lib/blather/file_transfer.rb +1 -1
- data/lib/blather/file_transfer/ibb.rb +1 -1
- data/lib/blather/file_transfer/s5b.rb +1 -1
- data/lib/blather/stanza/disco/disco_info.rb +1 -1
- data/lib/blather/stanza/iq/query.rb +1 -1
- data/lib/blather/stanza/iq/roster.rb +1 -1
- data/lib/blather/stream.rb +21 -19
- data/lib/blather/stream/client.rb +1 -11
- data/lib/blather/stream/component.rb +2 -10
- data/lib/blather/stream/features.rb +1 -3
- data/lib/blather/stream/parser.rb +8 -8
- data/lib/blather/version.rb +1 -1
- data/spec/blather/client/client_spec.rb +450 -484
- data/spec/blather/stanza/message_spec.rb +3 -2
- data/spec/blather/stream/client_spec.rb +174 -201
- data/spec/blather/stream/component_spec.rb +17 -21
- data/spec/blather/stream/parser_spec.rb +2 -0
- data/spec/blather/stream/ssl_spec.rb +9 -17
- data/spec/blather_spec.rb +1 -1
- data/spec/fixtures/certificate.crt +13 -0
- data/spec/spec_helper.rb +0 -1
- data/spec/support/mock_server.rb +8 -0
- metadata +40 -16
- data/.autotest +0 -13
- data/.gemtest +0 -0
- data/TODO.md +0 -2
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
# [develop](https://github.com/adhearsion/blather/compare/master...develop)
|
2
2
|
|
3
|
+
# [v0.8.2](https://github.com/adhearsion/blather/compare/v0.8.1...v0.8.2) - [2013-01-02](https://rubygems.org/gems/blather/versions/0.8.2)
|
4
|
+
* Bugfix: General spec fixes
|
5
|
+
* Bugfix: Fixes for JRuby and Rubinius
|
6
|
+
* Bugfix: Ensure parsers are shut down correctly
|
7
|
+
* Update: Bump Nokogiri (1.5.6) and EventMachine (1.0.0) minimum versions
|
8
|
+
|
3
9
|
# [v0.8.1](https://github.com/adhearsion/blather/compare/v0.8.0...v0.8.1) - [2012-09-17](https://rubygems.org/gems/blather/versions/0.8.1)
|
4
10
|
* Project moved to the Adhearsion Foundation
|
5
11
|
* Fixes for EventMachine 1.0.x
|
data/Guardfile
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Blather [
|
1
|
+
# Blather [](https://travis-ci.org/adhearsion/blather)
|
2
2
|
|
3
3
|
XMPP DSL (and more) for Ruby written on [EventMachine](http://rubyeventmachine.com/) and [Nokogiri](http://nokogiri.org/).
|
4
4
|
|
@@ -11,7 +11,7 @@ XMPP DSL (and more) for Ruby written on [EventMachine](http://rubyeventmachine.c
|
|
11
11
|
## Project Pages
|
12
12
|
|
13
13
|
* [GitHub](https://github.com/adhearsion/blather)
|
14
|
-
* [
|
14
|
+
* [Rubygems](http://rubygems.org/gems/blather)
|
15
15
|
* [API Documentation](http://rdoc.info/gems/blather/file/README.md)
|
16
16
|
* [Google Group](http://groups.google.com/group/xmpp-blather)
|
17
17
|
|
data/blather.gemspec
CHANGED
@@ -33,8 +33,8 @@ Gem::Specification.new do |s|
|
|
33
33
|
s.rdoc_options = %w{--charset=UTF-8}
|
34
34
|
s.extra_rdoc_files = %w{LICENSE README.md}
|
35
35
|
|
36
|
-
s.add_dependency "eventmachine", [">= 0.
|
37
|
-
s.add_dependency "nokogiri", ["~> 1.5.5"]
|
36
|
+
s.add_dependency "eventmachine", [">= 1.0.0"]
|
37
|
+
s.add_dependency "nokogiri", ["~> 1.5", ">= 1.5.6"]
|
38
38
|
s.add_dependency "niceogiri", ["~> 1.0"]
|
39
39
|
s.add_dependency "activesupport", [">= 2.3.11"]
|
40
40
|
s.add_dependency "girl_friday"
|
@@ -42,10 +42,11 @@ Gem::Specification.new do |s|
|
|
42
42
|
s.add_development_dependency "bundler", ["~> 1.0"]
|
43
43
|
s.add_development_dependency "rake"
|
44
44
|
s.add_development_dependency "rspec", ["~> 2.7"]
|
45
|
-
s.add_development_dependency "mocha", ["~> 0.9
|
45
|
+
s.add_development_dependency "mocha", ["~> 0.9"]
|
46
46
|
s.add_development_dependency "guard-rspec"
|
47
|
-
s.add_development_dependency "yard", ["~> 0.6
|
48
|
-
s.add_development_dependency "jruby-openssl", ["~> 0.7
|
47
|
+
s.add_development_dependency "yard", ["~> 0.6"]
|
48
|
+
s.add_development_dependency "jruby-openssl", ["~> 0.7"] if jruby?
|
49
49
|
s.add_development_dependency "bluecloth" unless jruby? || rbx?
|
50
50
|
s.add_development_dependency "countdownlatch"
|
51
|
+
s.add_development_dependency 'rb-fsevent', ['~> 0.9']
|
51
52
|
end
|
data/lib/blather/cert_store.rb
CHANGED
@@ -6,9 +6,6 @@ module Blather
|
|
6
6
|
# This uses the #{cert_directory}/*.crt files as the list of trusted root
|
7
7
|
# CA certificates.
|
8
8
|
class CertStore
|
9
|
-
@@certs = nil
|
10
|
-
@cert_directory = nil
|
11
|
-
|
12
9
|
def initialize(cert_directory)
|
13
10
|
@cert_directory = cert_directory
|
14
11
|
@store = OpenSSL::X509::Store.new
|
@@ -19,19 +16,24 @@ module Blather
|
|
19
16
|
# store. If the certificate can be trusted, it's added to the store so
|
20
17
|
# it can be used to trust other certs.
|
21
18
|
def trusted?(pem)
|
22
|
-
if cert = OpenSSL::X509::Certificate.new(pem)
|
19
|
+
if cert = OpenSSL::X509::Certificate.new(pem)
|
23
20
|
@store.verify(cert).tap do |trusted|
|
24
|
-
|
21
|
+
begin
|
22
|
+
@store.add_cert(cert) if trusted
|
23
|
+
rescue OpenSSL::X509::StoreError
|
24
|
+
end
|
25
25
|
end
|
26
26
|
end
|
27
|
+
rescue OpenSSL::X509::CertificateError
|
28
|
+
nil
|
27
29
|
end
|
28
30
|
|
29
31
|
# Return true if the domain name matches one of the names in the
|
30
32
|
# certificate. In other words, is the certificate provided to us really
|
31
33
|
# for the domain to which we think we're connected?
|
32
34
|
def domain?(pem, domain)
|
33
|
-
if cert = OpenSSL::X509::Certificate.new(pem)
|
34
|
-
OpenSSL::SSL.verify_certificate_identity(cert, domain)
|
35
|
+
if cert = OpenSSL::X509::Certificate.new(pem)
|
36
|
+
OpenSSL::SSL.verify_certificate_identity(cert, domain)
|
35
37
|
end
|
36
38
|
end
|
37
39
|
|
@@ -39,15 +41,15 @@ module Blather
|
|
39
41
|
# certificates are used to start the trust chain needed to validate certs
|
40
42
|
# we receive from clients and servers.
|
41
43
|
def certs
|
42
|
-
|
44
|
+
@certs ||= begin
|
43
45
|
pattern = /-{5}BEGIN CERTIFICATE-{5}\n.*?-{5}END CERTIFICATE-{5}\n/m
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
46
|
+
Dir[File.join(@cert_directory, '*.crt')]
|
47
|
+
.map {|f| File.read(f) }
|
48
|
+
.map {|c| c.scan(pattern) }
|
49
|
+
.flatten
|
50
|
+
.map {|c| OpenSSL::X509::Certificate.new(c) }
|
51
|
+
.reject {|c| c.not_after < Time.now }
|
49
52
|
end
|
50
|
-
@@certs
|
51
53
|
end
|
52
54
|
end
|
53
|
-
end
|
55
|
+
end
|
data/lib/blather/client.rb
CHANGED
@@ -30,7 +30,7 @@ optparse = OptionParser.new do |opts|
|
|
30
30
|
end
|
31
31
|
options[:log] = log
|
32
32
|
end
|
33
|
-
|
33
|
+
|
34
34
|
opts.on('--certs=[CERTS DIRECTORY]', 'The directory path where the trusted certificates are stored') do |certs|
|
35
35
|
if !File.directory?(certs)
|
36
36
|
$stderr.puts "The certs directory path (#{certs}) is no good."
|
@@ -38,7 +38,7 @@ optparse = OptionParser.new do |opts|
|
|
38
38
|
end
|
39
39
|
options[:certs] = certs
|
40
40
|
end
|
41
|
-
|
41
|
+
|
42
42
|
opts.on_tail('-h', '--help', 'Show this message') do
|
43
43
|
puts opts
|
44
44
|
exit
|
@@ -11,7 +11,7 @@ module Blather
|
|
11
11
|
|
12
12
|
# Set this to true if you want SOCKS5 Bytestreams to attempt to use private network addresses
|
13
13
|
attr_accessor :allow_private_ips
|
14
|
-
|
14
|
+
|
15
15
|
# Create a new FileTransfer
|
16
16
|
#
|
17
17
|
# @param [Blather::Stream] stream the stream the file transfer should use
|
data/lib/blather/stream.rb
CHANGED
@@ -57,7 +57,6 @@ module Blather
|
|
57
57
|
STREAM_NS = 'http://etherx.jabber.org/streams'
|
58
58
|
attr_accessor :password
|
59
59
|
attr_reader :jid
|
60
|
-
@@store = nil
|
61
60
|
|
62
61
|
# Start the stream between client and server
|
63
62
|
#
|
@@ -76,7 +75,7 @@ module Blather
|
|
76
75
|
jid = JID.new jid
|
77
76
|
port ||= 5222
|
78
77
|
if certs_directory
|
79
|
-
|
78
|
+
@store = CertStore.new(certs_directory)
|
80
79
|
end
|
81
80
|
if host
|
82
81
|
connect host, port, self, client, jid, pass, connect_timeout
|
@@ -142,6 +141,8 @@ module Blather
|
|
142
141
|
@to = self.jid.domain
|
143
142
|
@password = pass
|
144
143
|
@connect_timeout = connect_timeout || 180
|
144
|
+
|
145
|
+
@parser = Parser.new self
|
145
146
|
end
|
146
147
|
|
147
148
|
# Called when EM completes the connection to the server
|
@@ -154,7 +155,6 @@ module Blather
|
|
154
155
|
end
|
155
156
|
end
|
156
157
|
@connected = true
|
157
|
-
# @keepalive = EM::PeriodicTimer.new(60) { send_data ' ' }
|
158
158
|
start
|
159
159
|
end
|
160
160
|
|
@@ -162,11 +162,9 @@ module Blather
|
|
162
162
|
# @private
|
163
163
|
def receive_data(data)
|
164
164
|
@parser << data
|
165
|
-
|
166
165
|
rescue ParseError => e
|
167
166
|
@error = e
|
168
|
-
|
169
|
-
stop
|
167
|
+
stop "<stream:error><xml-not-well-formed xmlns='#{StreamError::STREAM_ERR_NS}'/></stream:error>"
|
170
168
|
end
|
171
169
|
|
172
170
|
# Called by EM to verify the peer certificate. If a certificate store directory
|
@@ -178,8 +176,8 @@ module Blather
|
|
178
176
|
# but it only does that for inbound connections, not when we
|
179
177
|
# make a connection to another server.
|
180
178
|
Blather.log "Checking SSL cert: #{pem}"
|
181
|
-
return true
|
182
|
-
|
179
|
+
return true unless @store
|
180
|
+
@store.trusted?(pem).tap do |trusted|
|
183
181
|
close_connection unless trusted
|
184
182
|
end
|
185
183
|
end
|
@@ -193,29 +191,33 @@ module Blather
|
|
193
191
|
# Called by EM when the connection is closed
|
194
192
|
# @private
|
195
193
|
def unbind
|
194
|
+
cleanup
|
195
|
+
|
196
196
|
raise NoConnection unless @inited
|
197
197
|
raise ConnectionFailed unless @connected
|
198
198
|
|
199
|
-
@connect_timer.cancel if @connect_timer
|
200
|
-
# @keepalive.cancel
|
201
199
|
@state = :stopped
|
202
200
|
@client.receive_data @error if @error
|
203
201
|
@client.unbind
|
204
202
|
end
|
205
203
|
|
204
|
+
def cleanup
|
205
|
+
@parser.finish
|
206
|
+
@connect_timer.cancel if @connect_timer
|
207
|
+
end
|
208
|
+
|
206
209
|
# Called by the parser with parsed nodes
|
207
210
|
# @private
|
208
211
|
def receive(node)
|
209
212
|
Blather.log "RECEIVING (#{node.element_name}) #{node}"
|
210
|
-
@node = node
|
211
213
|
|
212
|
-
if
|
213
|
-
case
|
214
|
+
if node.namespace && node.namespace.prefix == 'stream'
|
215
|
+
case node.element_name
|
214
216
|
when 'stream'
|
215
217
|
@state = :ready if @state == :stopped
|
216
218
|
return
|
217
219
|
when 'error'
|
218
|
-
handle_stream_error
|
220
|
+
handle_stream_error node
|
219
221
|
return
|
220
222
|
when 'end'
|
221
223
|
stop
|
@@ -229,7 +231,7 @@ module Blather
|
|
229
231
|
)
|
230
232
|
end
|
231
233
|
end
|
232
|
-
@receiver.receive_data
|
234
|
+
@receiver.receive_data node.to_stanza
|
233
235
|
end
|
234
236
|
|
235
237
|
# Ensure the JID gets attached to the client
|
@@ -242,16 +244,16 @@ module Blather
|
|
242
244
|
protected
|
243
245
|
# Stop the stream
|
244
246
|
# @private
|
245
|
-
def stop
|
247
|
+
def stop(error = nil)
|
246
248
|
unless @state == :stopped
|
247
249
|
@state = :stopped
|
248
|
-
send
|
250
|
+
send "#{error}</stream:stream>"
|
249
251
|
end
|
250
252
|
end
|
251
253
|
|
252
254
|
# @private
|
253
|
-
def handle_stream_error
|
254
|
-
@error = StreamError.import(
|
255
|
+
def handle_stream_error(node)
|
256
|
+
@error = StreamError.import(node)
|
255
257
|
stop
|
256
258
|
end
|
257
259
|
|
@@ -8,17 +8,7 @@ class Stream
|
|
8
8
|
NAMESPACE = 'jabber:client'
|
9
9
|
|
10
10
|
def start
|
11
|
-
@
|
12
|
-
start_stream = <<-STREAM
|
13
|
-
<stream:stream
|
14
|
-
to='#{@to}'
|
15
|
-
xmlns='#{NAMESPACE}'
|
16
|
-
xmlns:stream='#{STREAM_NS}'
|
17
|
-
version='#{VERSION}'
|
18
|
-
xml:lang='#{LANG}'
|
19
|
-
>
|
20
|
-
STREAM
|
21
|
-
send start_stream.gsub(/\s+/, ' ')
|
11
|
+
send "<stream:stream to='#{@to}' xmlns='#{NAMESPACE}' xmlns:stream='#{STREAM_NS}' version='#{VERSION}' xml:lang='#{LANG}'>"
|
22
12
|
end
|
23
13
|
|
24
14
|
def send(stanza)
|
@@ -13,7 +13,7 @@ class Stream
|
|
13
13
|
end
|
14
14
|
|
15
15
|
if node.document.find_first('/stream:stream[not(stream:error)]', :xmlns => NAMESPACE, :stream => STREAM_NS)
|
16
|
-
send
|
16
|
+
send "<handshake>#{Digest::SHA1.hexdigest(node['id']+@password)}</handshake>"
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
@@ -23,15 +23,7 @@ class Stream
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def start
|
26
|
-
@
|
27
|
-
start_stream = <<-STREAM
|
28
|
-
<stream:stream
|
29
|
-
to='#{@jid}'
|
30
|
-
xmlns='#{NAMESPACE}'
|
31
|
-
xmlns:stream='#{STREAM_NS}'
|
32
|
-
>
|
33
|
-
STREAM
|
34
|
-
send start_stream.gsub(/\s+/, ' ')
|
26
|
+
send "<stream:stream to='#{@jid}' xmlns='#{NAMESPACE}' xmlns:stream='#{STREAM_NS}'>"
|
35
27
|
end
|
36
28
|
end #Client
|
37
29
|
|
@@ -15,13 +15,15 @@ class Stream
|
|
15
15
|
@namespaces = {}
|
16
16
|
@namespace_definitions = []
|
17
17
|
@parser = Nokogiri::XML::SAX::PushParser.new self
|
18
|
-
@parser.options = Nokogiri::XML::ParseOptions::
|
18
|
+
@parser.options = Nokogiri::XML::ParseOptions::NOENT
|
19
19
|
end
|
20
20
|
|
21
21
|
def receive_data(string)
|
22
22
|
Blather.log "PARSING: (#{string})" if @@debug
|
23
23
|
@parser << string
|
24
24
|
self
|
25
|
+
rescue Nokogiri::XML::SyntaxError => e
|
26
|
+
error e.message
|
25
27
|
end
|
26
28
|
alias_method :<<, :receive_data
|
27
29
|
|
@@ -54,13 +56,6 @@ class Stream
|
|
54
56
|
end
|
55
57
|
|
56
58
|
deliver(node) if elem == 'stream'
|
57
|
-
|
58
|
-
# $stderr.puts "\n\n"
|
59
|
-
# $stderr.puts [elem, attrs, prefix, uri, namespaces].inspect
|
60
|
-
# $stderr.puts @namespaces.inspect
|
61
|
-
# $stderr.puts [@namespaces[[prefix, uri]].prefix, @namespaces[[prefix, uri]].href].inspect if @namespaces[[prefix, uri]]
|
62
|
-
# $stderr.puts node.inspect
|
63
|
-
# $stderr.puts node.document.to_s.gsub(/\n\s*/,'')
|
64
59
|
end
|
65
60
|
|
66
61
|
def end_element_namespace(elem, prefix, uri)
|
@@ -91,6 +86,11 @@ class Stream
|
|
91
86
|
raise ParseError.new(msg)
|
92
87
|
end
|
93
88
|
|
89
|
+
def finish
|
90
|
+
@parser.finish
|
91
|
+
rescue ParseError, RuntimeError
|
92
|
+
end
|
93
|
+
|
94
94
|
private
|
95
95
|
def deliver(node)
|
96
96
|
@current, @namespaces, @namespace_definitions = nil, {}, []
|
data/lib/blather/version.rb
CHANGED
@@ -2,204 +2,202 @@ require 'spec_helper'
|
|
2
2
|
require 'blather/client/client'
|
3
3
|
|
4
4
|
describe Blather::Client do
|
5
|
+
let(:jid) { Blather::JID.new 'n@d/r' }
|
6
|
+
let(:stream) { mock 'Stream' }
|
7
|
+
|
5
8
|
before do
|
6
|
-
|
7
|
-
@stream = mock
|
8
|
-
@stream.stubs(:send)
|
9
|
-
@jid = Blather::JID.new('n@d/r')
|
9
|
+
stream.stubs :send
|
10
10
|
end
|
11
11
|
|
12
12
|
it 'provides a Blather::JID reader' do
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
subject.post_init stream, jid
|
14
|
+
subject.should respond_to :jid
|
15
|
+
subject.jid.should == jid
|
16
16
|
end
|
17
17
|
|
18
18
|
it 'provides a reader for the roster' do
|
19
|
-
|
20
|
-
|
19
|
+
subject.should respond_to :roster
|
20
|
+
subject.roster.should be_kind_of Blather::Roster
|
21
21
|
end
|
22
22
|
|
23
23
|
it 'provides a status reader' do
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
24
|
+
subject.post_init stream, jid
|
25
|
+
subject.should respond_to :status
|
26
|
+
subject.status = :away
|
27
|
+
subject.status.should == :away
|
28
28
|
end
|
29
29
|
|
30
30
|
it 'should have a caps handler' do
|
31
|
-
|
32
|
-
|
31
|
+
subject.should respond_to :caps
|
32
|
+
subject.caps.should be_kind_of Blather::Stanza::Capabilities
|
33
33
|
end
|
34
34
|
|
35
35
|
it 'can be setup' do
|
36
|
-
|
37
|
-
|
36
|
+
subject.should respond_to :setup
|
37
|
+
subject.setup('me@me.com', 'pass').should == subject
|
38
38
|
end
|
39
39
|
|
40
40
|
it 'knows if it has been setup' do
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
41
|
+
subject.should respond_to :setup?
|
42
|
+
subject.should_not be_setup
|
43
|
+
subject.setup 'me@me.com', 'pass'
|
44
|
+
subject.should be_setup
|
45
45
|
end
|
46
46
|
|
47
47
|
it 'cannot be run before being setup' do
|
48
|
-
lambda {
|
48
|
+
lambda { subject.run }.should raise_error RuntimeError
|
49
49
|
end
|
50
50
|
|
51
51
|
it 'starts up a Component connection when setup without a node' do
|
52
52
|
setup = 'pubsub.jabber.local', 'secret'
|
53
|
-
|
54
|
-
Blather::Stream::Component.expects(:start).with
|
55
|
-
|
53
|
+
subject.setup *setup
|
54
|
+
Blather::Stream::Component.expects(:start).with subject, *setup + [nil, nil, nil, nil]
|
55
|
+
subject.run
|
56
56
|
end
|
57
57
|
|
58
58
|
it 'starts up a Client connection when setup with a node' do
|
59
59
|
setup = 'test@jabber.local', 'secret'
|
60
|
-
|
61
|
-
Blather::Stream::Client.expects(:start).with
|
62
|
-
|
60
|
+
subject.setup *setup
|
61
|
+
Blather::Stream::Client.expects(:start).with subject, *setup + [nil, nil, nil, nil]
|
62
|
+
subject.run
|
63
63
|
end
|
64
64
|
|
65
65
|
it 'knows if it is disconnected' do
|
66
|
-
|
67
|
-
|
66
|
+
subject.should respond_to :connected?
|
67
|
+
subject.should_not be_connected
|
68
68
|
end
|
69
69
|
|
70
70
|
it 'knows if it is connected' do
|
71
|
-
stream = mock
|
72
71
|
stream.expects(:stopped?).returns false
|
73
|
-
|
74
|
-
|
75
|
-
|
72
|
+
subject.setup 'me.com', 'secret'
|
73
|
+
subject.post_init stream, Blather::JID.new('me.com')
|
74
|
+
subject.should be_connected
|
76
75
|
end
|
77
76
|
|
78
77
|
describe 'if it has been setup but not connected yet' do
|
79
78
|
it 'should consider itself disconnected' do
|
80
|
-
|
81
|
-
|
79
|
+
subject.setup 'me.com', 'secret'
|
80
|
+
subject.should_not be_connected
|
82
81
|
end
|
83
82
|
end
|
84
83
|
|
85
84
|
it 'writes to the connection the closes when #close is called' do
|
86
|
-
stream = mock()
|
87
85
|
stream.expects(:close_connection_after_writing)
|
88
|
-
|
89
|
-
|
90
|
-
|
86
|
+
subject.setup 'me.com', 'secret'
|
87
|
+
subject.post_init stream, Blather::JID.new('me.com')
|
88
|
+
subject.close
|
91
89
|
end
|
92
90
|
|
93
91
|
it 'shuts down EM when #unbind is called if it is running' do
|
94
92
|
EM.expects(:reactor_running?).returns true
|
95
93
|
EM.expects(:stop)
|
96
|
-
|
94
|
+
subject.unbind
|
97
95
|
end
|
98
96
|
|
99
97
|
it 'does nothing when #unbind is called and EM is not running' do
|
100
98
|
EM.expects(:reactor_running?).returns false
|
101
99
|
EM.expects(:stop).never
|
102
|
-
|
100
|
+
subject.unbind
|
103
101
|
end
|
104
102
|
|
105
103
|
it 'calls the :disconnected handler with #unbind is called' do
|
106
104
|
EM.expects(:reactor_running?).returns false
|
107
|
-
disconnected = mock
|
105
|
+
disconnected = mock
|
108
106
|
disconnected.expects(:call)
|
109
|
-
|
110
|
-
|
107
|
+
subject.register_handler(:disconnected) { disconnected.call }
|
108
|
+
subject.unbind
|
111
109
|
end
|
112
110
|
|
113
111
|
it 'does not call EM.stop on #unbind if a handler returns positive' do
|
114
112
|
EM.expects(:reactor_running?).never
|
115
113
|
EM.expects(:stop).never
|
116
|
-
disconnected = mock
|
114
|
+
disconnected = mock
|
117
115
|
disconnected.expects(:call).returns true
|
118
|
-
|
119
|
-
|
116
|
+
subject.register_handler(:disconnected) { disconnected.call }
|
117
|
+
subject.unbind
|
120
118
|
end
|
121
119
|
|
122
120
|
it 'calls EM.stop on #unbind if a handler returns negative' do
|
123
121
|
EM.expects(:reactor_running?).returns true
|
124
122
|
EM.expects(:stop)
|
125
|
-
disconnected = mock
|
123
|
+
disconnected = mock
|
126
124
|
disconnected.expects(:call).returns false
|
127
|
-
|
128
|
-
|
125
|
+
subject.register_handler(:disconnected) { disconnected.call }
|
126
|
+
subject.unbind
|
129
127
|
end
|
130
128
|
|
131
129
|
it 'can register a temporary handler based on stanza ID' do
|
132
130
|
stanza = Blather::Stanza::Iq.new
|
133
|
-
response = mock
|
131
|
+
response = mock
|
134
132
|
response.expects(:call)
|
135
|
-
|
136
|
-
|
133
|
+
subject.register_tmp_handler(stanza.id) { |_| response.call }
|
134
|
+
subject.receive_data stanza
|
137
135
|
end
|
138
136
|
|
139
137
|
it 'removes a tmp handler as soon as it is used' do
|
140
138
|
stanza = Blather::Stanza::Iq.new
|
141
|
-
response = mock
|
139
|
+
response = mock
|
142
140
|
response.expects(:call)
|
143
|
-
|
144
|
-
|
145
|
-
|
141
|
+
subject.register_tmp_handler(stanza.id) { |_| response.call }
|
142
|
+
subject.receive_data stanza
|
143
|
+
subject.receive_data stanza
|
146
144
|
end
|
147
145
|
|
148
146
|
it 'will create a handler then write the stanza' do
|
149
147
|
stanza = Blather::Stanza::Iq.new
|
150
|
-
response = mock
|
148
|
+
response = mock
|
151
149
|
response.expects(:call)
|
152
|
-
|
153
|
-
|
150
|
+
subject.expects(:write).with do |s|
|
151
|
+
subject.receive_data stanza
|
154
152
|
s.should == stanza
|
155
153
|
end
|
156
|
-
|
154
|
+
subject.write_with_handler(stanza) { |_| response.call }
|
157
155
|
end
|
158
156
|
|
159
157
|
it 'can register a handler' do
|
160
158
|
stanza = Blather::Stanza::Iq.new
|
161
|
-
response = mock
|
159
|
+
response = mock
|
162
160
|
response.expects(:call).times(2)
|
163
|
-
|
164
|
-
|
165
|
-
|
161
|
+
subject.register_handler(:iq) { |_| response.call }
|
162
|
+
subject.receive_data stanza
|
163
|
+
subject.receive_data stanza
|
166
164
|
end
|
167
165
|
|
168
166
|
it 'allows for breaking out of handlers' do
|
169
167
|
stanza = Blather::Stanza::Iq.new
|
170
168
|
response = mock(:iq => nil)
|
171
|
-
|
169
|
+
subject.register_handler(:iq) do |_|
|
172
170
|
response.iq
|
173
171
|
throw :halt
|
174
172
|
response.fail
|
175
173
|
end
|
176
|
-
|
174
|
+
subject.receive_data stanza
|
177
175
|
end
|
178
176
|
|
179
177
|
it 'allows for passing to the next handler of the same type' do
|
180
178
|
stanza = Blather::Stanza::Iq.new
|
181
179
|
response = mock(:iq1 => nil, :iq2 => nil)
|
182
|
-
|
180
|
+
subject.register_handler(:iq) do |_|
|
183
181
|
response.iq1
|
184
182
|
throw :pass
|
185
183
|
response.fail
|
186
184
|
end
|
187
|
-
|
185
|
+
subject.register_handler(:iq) do |_|
|
188
186
|
response.iq2
|
189
187
|
end
|
190
|
-
|
188
|
+
subject.receive_data stanza
|
191
189
|
end
|
192
190
|
|
193
191
|
it 'allows for passing to the next handler in the hierarchy' do
|
194
192
|
stanza = Blather::Stanza::Iq::Query.new
|
195
193
|
response = mock(:query => nil, :iq => nil)
|
196
|
-
|
194
|
+
subject.register_handler(:query) do |_|
|
197
195
|
response.query
|
198
196
|
throw :pass
|
199
197
|
response.fail
|
200
198
|
end
|
201
|
-
|
202
|
-
|
199
|
+
subject.register_handler(:iq) { |_| response.iq }
|
200
|
+
subject.receive_data stanza
|
203
201
|
end
|
204
202
|
|
205
203
|
it 'can clear handlers' do
|
@@ -209,479 +207,447 @@ describe Blather::Client do
|
|
209
207
|
response = mock
|
210
208
|
response.expects(:call).once
|
211
209
|
|
212
|
-
|
213
|
-
|
210
|
+
subject.register_handler(:message, :chat?) { |_| response.call }
|
211
|
+
subject.receive_data stanza
|
214
212
|
|
215
|
-
|
216
|
-
|
213
|
+
subject.clear_handlers :message, :chat?
|
214
|
+
subject.receive_data stanza
|
217
215
|
end
|
218
|
-
end
|
219
216
|
|
220
|
-
describe '
|
221
|
-
|
222
|
-
|
217
|
+
describe '#write' do
|
218
|
+
it 'writes to the stream' do
|
219
|
+
stanza = Blather::Stanza::Iq.new
|
220
|
+
stream.expects(:send).with stanza
|
221
|
+
subject.setup 'me@me.com', 'me'
|
222
|
+
subject.post_init stream, Blather::JID.new('me.com')
|
223
|
+
subject.write stanza
|
224
|
+
end
|
223
225
|
end
|
224
226
|
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
@client.setup('me@me.com', 'me')
|
230
|
-
@client.post_init stream, Blather::JID.new('me.com')
|
231
|
-
@client.write stanza
|
232
|
-
end
|
233
|
-
end
|
227
|
+
describe '#status=' do
|
228
|
+
before do
|
229
|
+
subject.post_init stream, Blather::JID.new('n@d/r')
|
230
|
+
end
|
234
231
|
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
end
|
232
|
+
it 'updates the state when not sending to a Blather::JID' do
|
233
|
+
stream.stubs(:write)
|
234
|
+
subject.status.should_not equal :away
|
235
|
+
subject.status = :away, 'message'
|
236
|
+
subject.status.should == :away
|
237
|
+
end
|
242
238
|
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
239
|
+
it 'does not update the state when sending to a Blather::JID' do
|
240
|
+
stream.stubs(:write)
|
241
|
+
subject.status.should_not equal :away
|
242
|
+
subject.status = :away, 'message', 'me@me.com'
|
243
|
+
subject.status.should_not equal :away
|
244
|
+
end
|
249
245
|
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
246
|
+
it 'writes the new status to the stream' do
|
247
|
+
Blather::Stanza::Presence::Status.stubs(:next_id).returns 0
|
248
|
+
status = [:away, 'message']
|
249
|
+
stream.expects(:send).with do |s|
|
250
|
+
s.should be_kind_of Blather::Stanza::Presence::Status
|
251
|
+
s.to_s.should == Blather::Stanza::Presence::Status.new(*status).to_s
|
252
|
+
end
|
253
|
+
subject.status = status
|
254
|
+
end
|
255
255
|
end
|
256
256
|
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
s.should be_kind_of Blather::Stanza::Presence::Status
|
262
|
-
s.to_s.should == Blather::Stanza::Presence::Status.new(*status).to_s
|
257
|
+
describe 'default handlers' do
|
258
|
+
it 're-raises errors' do
|
259
|
+
err = Blather::BlatherError.new
|
260
|
+
lambda { subject.receive_data err }.should raise_error Blather::BlatherError
|
263
261
|
end
|
264
|
-
@client.status = status
|
265
|
-
end
|
266
|
-
end
|
267
262
|
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
err = Blather::
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
# it 'responds to iq:set with a "service-unavailable" error' do
|
296
|
-
# get = Blather::Stanza::Iq.new :set
|
297
|
-
# err = Blather::StanzaError.new(get, 'service-unavailable', :cancel).to_node
|
298
|
-
# @client.expects(:write).with { |n| n.to_s.should == err.to_s }
|
299
|
-
# @client.receive_data get
|
300
|
-
# end
|
301
|
-
|
302
|
-
it 'responds to s2c pings with a pong' do
|
303
|
-
ping = Blather::Stanza::Iq::Ping.new :get
|
304
|
-
pong = ping.reply
|
305
|
-
@client.expects(:write).with { |n| n.to_s.should == pong.to_s }
|
306
|
-
@client.receive_data ping
|
307
|
-
end
|
308
|
-
|
309
|
-
it 'handles status changes by updating the roster if the status is from a Blather::JID in the roster' do
|
310
|
-
jid = 'friend@jabber.local'
|
311
|
-
status = Blather::Stanza::Presence::Status.new :away
|
312
|
-
status.stubs(:from).returns jid
|
313
|
-
roster_item = mock()
|
314
|
-
roster_item.expects(:status=).with status
|
315
|
-
@client.stubs(:roster).returns({status.from => roster_item})
|
316
|
-
@client.receive_data status
|
317
|
-
end
|
318
|
-
|
319
|
-
it 'lets status stanzas fall through to other handlers' do
|
320
|
-
jid = 'friend@jabber.local'
|
321
|
-
status = Blather::Stanza::Presence::Status.new :away
|
322
|
-
status.stubs(:from).returns jid
|
323
|
-
roster_item = mock()
|
324
|
-
roster_item.expects(:status=).with status
|
325
|
-
@client.stubs(:roster).returns({status.from => roster_item})
|
326
|
-
|
327
|
-
response = mock()
|
328
|
-
response.expects(:call).with jid
|
329
|
-
@client.register_handler(:status) { |s| response.call s.from.to_s }
|
330
|
-
@client.receive_data status
|
331
|
-
end
|
332
|
-
|
333
|
-
it 'handles an incoming roster node by processing it through the roster' do
|
334
|
-
roster = Blather::Stanza::Iq::Roster.new
|
335
|
-
client_roster = mock()
|
336
|
-
client_roster.expects(:process).with roster
|
337
|
-
@client.stubs(:roster).returns client_roster
|
338
|
-
@client.receive_data roster
|
339
|
-
end
|
340
|
-
|
341
|
-
it 'handles an incoming roster node by processing it through the roster' do
|
342
|
-
roster = Blather::Stanza::Iq::Roster.new
|
343
|
-
client_roster = mock()
|
344
|
-
client_roster.expects(:process).with roster
|
345
|
-
@client.stubs(:roster).returns client_roster
|
346
|
-
|
347
|
-
response = mock()
|
348
|
-
response.expects(:call)
|
349
|
-
@client.register_handler(:roster) { |_| response.call }
|
350
|
-
@client.receive_data roster
|
351
|
-
end
|
352
|
-
end
|
263
|
+
# it 'responds to iq:get with a "service-unavailable" error' do
|
264
|
+
# get = Blather::Stanza::Iq.new :get
|
265
|
+
# err = Blather::StanzaError.new(get, 'service-unavailable', :cancel).to_node
|
266
|
+
# subject.expects(:write).with err
|
267
|
+
# subject.receive_data get
|
268
|
+
# end
|
269
|
+
|
270
|
+
# it 'responds to iq:get with a "service-unavailable" error' do
|
271
|
+
# get = Blather::Stanza::Iq.new :get
|
272
|
+
# err = Blather::StanzaError.new(get, 'service-unavailable', :cancel).to_node
|
273
|
+
# subject.expects(:write).with { |n| n.to_s.should == err.to_s }
|
274
|
+
# subject.receive_data get
|
275
|
+
# end
|
276
|
+
|
277
|
+
# it 'responds to iq:set with a "service-unavailable" error' do
|
278
|
+
# get = Blather::Stanza::Iq.new :set
|
279
|
+
# err = Blather::StanzaError.new(get, 'service-unavailable', :cancel).to_node
|
280
|
+
# subject.expects(:write).with { |n| n.to_s.should == err.to_s }
|
281
|
+
# subject.receive_data get
|
282
|
+
# end
|
283
|
+
|
284
|
+
it 'responds to s2c pings with a pong' do
|
285
|
+
ping = Blather::Stanza::Iq::Ping.new :get
|
286
|
+
pong = ping.reply
|
287
|
+
subject.expects(:write).with { |n| n.to_s.should == pong.to_s }
|
288
|
+
subject.receive_data ping
|
289
|
+
end
|
353
290
|
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
291
|
+
it 'handles status changes by updating the roster if the status is from a Blather::JID in the roster' do
|
292
|
+
jid = 'friend@jabber.local'
|
293
|
+
status = Blather::Stanza::Presence::Status.new :away
|
294
|
+
status.stubs(:from).returns jid
|
295
|
+
roster_item = mock
|
296
|
+
roster_item.expects(:status=).with status
|
297
|
+
subject.stubs(:roster).returns({status.from => roster_item})
|
298
|
+
subject.receive_data status
|
299
|
+
end
|
362
300
|
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
301
|
+
it 'lets status stanzas fall through to other handlers' do
|
302
|
+
jid = 'friend@jabber.local'
|
303
|
+
status = Blather::Stanza::Presence::Status.new :away
|
304
|
+
status.stubs(:from).returns jid
|
305
|
+
roster_item = mock
|
306
|
+
roster_item.expects(:status=).with status
|
307
|
+
subject.stubs(:roster).returns({status.from => roster_item})
|
308
|
+
|
309
|
+
response = mock
|
310
|
+
response.expects(:call).with jid
|
311
|
+
subject.register_handler(:status) { |s| response.call s.from.to_s }
|
312
|
+
subject.receive_data status
|
313
|
+
end
|
370
314
|
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
315
|
+
it 'handles an incoming roster node by processing it through the roster' do
|
316
|
+
roster = Blather::Stanza::Iq::Roster.new
|
317
|
+
client_roster = mock
|
318
|
+
client_roster.expects(:process).with roster
|
319
|
+
subject.stubs(:roster).returns client_roster
|
320
|
+
subject.receive_data roster
|
321
|
+
end
|
322
|
+
|
323
|
+
it 'handles an incoming roster node by processing it through the roster' do
|
324
|
+
roster = Blather::Stanza::Iq::Roster.new
|
325
|
+
client_roster = mock
|
326
|
+
client_roster.expects(:process).with roster
|
327
|
+
subject.stubs(:roster).returns client_roster
|
379
328
|
|
380
|
-
|
381
|
-
|
382
|
-
|
329
|
+
response = mock
|
330
|
+
response.expects(:call)
|
331
|
+
subject.register_handler(:roster) { |_| response.call }
|
332
|
+
subject.receive_data roster
|
333
|
+
end
|
383
334
|
end
|
384
335
|
|
385
|
-
|
386
|
-
|
387
|
-
|
336
|
+
describe 'with a Component stream' do
|
337
|
+
before do
|
338
|
+
class MockComponent < Blather::Stream::Component; def initialize(); end; end
|
339
|
+
stream = MockComponent.new('')
|
340
|
+
stream.stubs(:send_data)
|
341
|
+
subject.setup 'me.com', 'secret'
|
342
|
+
end
|
388
343
|
|
389
|
-
ready
|
390
|
-
|
391
|
-
|
392
|
-
|
344
|
+
it 'calls the ready handler when sent post_init' do
|
345
|
+
ready = mock
|
346
|
+
ready.expects(:call)
|
347
|
+
subject.register_handler(:ready) { ready.call }
|
348
|
+
subject.post_init stream
|
349
|
+
end
|
393
350
|
end
|
394
|
-
end
|
395
351
|
|
396
|
-
describe '
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
352
|
+
describe 'with a Client stream' do
|
353
|
+
before do
|
354
|
+
class MockClientStream < Blather::Stream::Client; def initialize(); end; end
|
355
|
+
stream = MockClientStream.new('')
|
356
|
+
Blather::Stream::Client.stubs(:start).returns stream
|
357
|
+
subject.setup('me@me.com', 'secret').run
|
358
|
+
end
|
403
359
|
|
404
|
-
|
405
|
-
|
406
|
-
|
360
|
+
it 'sends a request for the roster when post_init is called' do
|
361
|
+
stream.expects(:send).with { |stanza| stanza.should be_kind_of Blather::Stanza::Iq::Roster }
|
362
|
+
subject.post_init stream, Blather::JID.new('n@d/r')
|
363
|
+
end
|
407
364
|
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
365
|
+
it 'calls the ready handler after post_init and roster is received' do
|
366
|
+
result_roster = Blather::Stanza::Iq::Roster.new :result
|
367
|
+
stream.stubs(:send).with do |s|
|
368
|
+
result_roster.id = s.id
|
369
|
+
subject.receive_data result_roster
|
370
|
+
true
|
371
|
+
end
|
372
|
+
|
373
|
+
ready = mock
|
374
|
+
ready.expects(:call)
|
375
|
+
subject.register_handler(:ready) { ready.call }
|
376
|
+
subject.post_init stream, Blather::JID.new('n@d/r')
|
377
|
+
end
|
415
378
|
end
|
416
379
|
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
@client.register_filter(:before) { |_| throw :pass; ready.call }
|
422
|
-
@client.register_filter(:before) { |_| ready.call }
|
423
|
-
@client.receive_data stanza
|
424
|
-
end
|
380
|
+
describe 'filters' do
|
381
|
+
it 'raises an error when an invalid filter type is registered' do
|
382
|
+
lambda { subject.register_filter(:invalid) {} }.should raise_error RuntimeError
|
383
|
+
end
|
425
384
|
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
@client.receive_data stanza
|
435
|
-
end
|
385
|
+
it 'can be guarded' do
|
386
|
+
stanza = Blather::Stanza::Iq.new
|
387
|
+
ready = mock
|
388
|
+
ready.expects(:call).once
|
389
|
+
subject.register_filter(:before, :iq, :id => stanza.id) { |_| ready.call }
|
390
|
+
subject.register_filter(:before, :iq, :id => 'not-id') { |_| ready.call }
|
391
|
+
subject.receive_data stanza
|
392
|
+
end
|
436
393
|
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
@client.receive_data stanza
|
446
|
-
end
|
394
|
+
it 'can pass to the next handler' do
|
395
|
+
stanza = Blather::Stanza::Iq.new
|
396
|
+
ready = mock
|
397
|
+
ready.expects(:call).once
|
398
|
+
subject.register_filter(:before) { |_| throw :pass; ready.call }
|
399
|
+
subject.register_filter(:before) { |_| ready.call }
|
400
|
+
subject.receive_data stanza
|
401
|
+
end
|
447
402
|
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
403
|
+
it 'runs them in order' do
|
404
|
+
stanza = Blather::Stanza::Iq.new
|
405
|
+
count = 0
|
406
|
+
subject.register_filter(:before) { |_| count.should == 0; count = 1 }
|
407
|
+
subject.register_filter(:before) { |_| count.should == 1; count = 2 }
|
408
|
+
subject.register_handler(:iq) { |_| count.should == 2; count = 3 }
|
409
|
+
subject.register_filter(:after) { |_| count.should == 3; count = 4 }
|
410
|
+
subject.register_filter(:after) { |_| count.should == 4 }
|
411
|
+
subject.receive_data stanza
|
412
|
+
end
|
456
413
|
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
414
|
+
it 'can modify the stanza' do
|
415
|
+
stanza = Blather::Stanza::Iq.new
|
416
|
+
stanza.from = 'from@test.local'
|
417
|
+
new_jid = 'before@filter.local'
|
418
|
+
ready = mock
|
419
|
+
ready.expects(:call).with new_jid
|
420
|
+
subject.register_filter(:before) { |s| s.from = new_jid }
|
421
|
+
subject.register_handler(:iq) { |s| ready.call s.from.to_s }
|
422
|
+
subject.receive_data stanza
|
423
|
+
end
|
466
424
|
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
425
|
+
it 'can halt the handler chain' do
|
426
|
+
stanza = Blather::Stanza::Iq.new
|
427
|
+
ready = mock
|
428
|
+
ready.expects(:call).never
|
429
|
+
subject.register_filter(:before) { |_| throw :halt }
|
430
|
+
subject.register_handler(:iq) { |_| ready.call }
|
431
|
+
subject.receive_data stanza
|
432
|
+
end
|
433
|
+
|
434
|
+
it 'can be specific to a handler' do
|
435
|
+
stanza = Blather::Stanza::Iq.new
|
436
|
+
ready = mock
|
437
|
+
ready.expects(:call).once
|
438
|
+
subject.register_filter(:before, :iq) { |_| ready.call }
|
439
|
+
subject.register_filter(:before, :message) { |_| ready.call }
|
440
|
+
subject.receive_data stanza
|
441
|
+
end
|
475
442
|
end
|
476
443
|
|
477
|
-
|
478
|
-
|
479
|
-
|
444
|
+
describe 'guards' do
|
445
|
+
let(:stanza) { Blather::Stanza::Iq.new }
|
446
|
+
let(:response) { mock }
|
480
447
|
|
481
|
-
|
482
|
-
|
448
|
+
it 'can be a symbol' do
|
449
|
+
response.expects :call
|
450
|
+
subject.register_handler(:iq, :chat?) { |_| response.call }
|
483
451
|
|
484
|
-
|
485
|
-
|
486
|
-
end
|
452
|
+
stanza.expects(:chat?).returns true
|
453
|
+
subject.receive_data stanza
|
487
454
|
|
488
|
-
|
489
|
-
|
490
|
-
|
455
|
+
stanza.expects(:chat?).returns false
|
456
|
+
subject.receive_data stanza
|
457
|
+
end
|
491
458
|
|
492
|
-
|
493
|
-
|
459
|
+
it 'can be a hash with string match' do
|
460
|
+
response.expects :call
|
461
|
+
subject.register_handler(:iq, :body => 'exit') { |_| response.call }
|
494
462
|
|
495
|
-
|
496
|
-
|
497
|
-
end
|
463
|
+
stanza.expects(:body).returns 'exit'
|
464
|
+
subject.receive_data stanza
|
498
465
|
|
499
|
-
|
500
|
-
|
501
|
-
|
466
|
+
stanza.expects(:body).returns 'not-exit'
|
467
|
+
subject.receive_data stanza
|
468
|
+
end
|
502
469
|
|
503
|
-
|
504
|
-
|
470
|
+
it 'can be a hash with a value' do
|
471
|
+
response.expects :call
|
472
|
+
subject.register_handler(:iq, :number => 0) { |_| response.call }
|
505
473
|
|
506
|
-
|
507
|
-
|
508
|
-
end
|
474
|
+
stanza.expects(:number).returns 0
|
475
|
+
subject.receive_data stanza
|
509
476
|
|
510
|
-
|
511
|
-
|
512
|
-
|
477
|
+
stanza.expects(:number).returns 1
|
478
|
+
subject.receive_data stanza
|
479
|
+
end
|
513
480
|
|
514
|
-
|
515
|
-
|
481
|
+
it 'can be a hash with a regexp' do
|
482
|
+
response.expects :call
|
483
|
+
subject.register_handler(:iq, :body => /exit/) { |_| response.call }
|
516
484
|
|
517
|
-
|
518
|
-
|
485
|
+
stanza.expects(:body).returns 'more than just exit, but exit still'
|
486
|
+
subject.receive_data stanza
|
519
487
|
|
520
|
-
|
521
|
-
|
522
|
-
end
|
488
|
+
stanza.expects(:body).returns 'keyword not found'
|
489
|
+
subject.receive_data stanza
|
523
490
|
|
524
|
-
|
525
|
-
|
526
|
-
|
491
|
+
stanza.expects(:body).returns nil
|
492
|
+
subject.receive_data stanza
|
493
|
+
end
|
527
494
|
|
528
|
-
|
529
|
-
|
530
|
-
|
495
|
+
it 'can be a hash with an array' do
|
496
|
+
response.expects(:call).times(2)
|
497
|
+
subject.register_handler(:iq, :type => [:result, :error]) { |_| response.call }
|
531
498
|
|
532
|
-
|
533
|
-
|
534
|
-
|
499
|
+
stanza = Blather::Stanza::Iq.new
|
500
|
+
stanza.expects(:type).at_least_once.returns :result
|
501
|
+
subject.receive_data stanza
|
535
502
|
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
end
|
503
|
+
stanza = Blather::Stanza::Iq.new
|
504
|
+
stanza.expects(:type).at_least_once.returns :error
|
505
|
+
subject.receive_data stanza
|
540
506
|
|
541
|
-
|
542
|
-
|
543
|
-
|
507
|
+
stanza = Blather::Stanza::Iq.new
|
508
|
+
stanza.expects(:type).at_least_once.returns :get
|
509
|
+
subject.receive_data stanza
|
510
|
+
end
|
544
511
|
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
@client.receive_data stanza
|
512
|
+
it 'chained are treated like andand (short circuited)' do
|
513
|
+
response.expects :call
|
514
|
+
subject.register_handler(:iq, :type => :get, :body => 'test') { |_| response.call }
|
549
515
|
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
end
|
516
|
+
stanza = Blather::Stanza::Iq.new
|
517
|
+
stanza.expects(:type).at_least_once.returns :get
|
518
|
+
stanza.expects(:body).returns 'test'
|
519
|
+
subject.receive_data stanza
|
555
520
|
|
556
|
-
|
557
|
-
|
558
|
-
|
521
|
+
stanza = Blather::Stanza::Iq.new
|
522
|
+
stanza.expects(:type).at_least_once.returns :set
|
523
|
+
stanza.expects(:body).never
|
524
|
+
subject.receive_data stanza
|
525
|
+
end
|
559
526
|
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
@client.receive_data stanza
|
527
|
+
it 'within an Array are treated as oror (short circuited)' do
|
528
|
+
response.expects(:call).times 2
|
529
|
+
subject.register_handler(:iq, [{:type => :get}, {:body => 'test'}]) { |_| response.call }
|
564
530
|
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
end
|
531
|
+
stanza = Blather::Stanza::Iq.new
|
532
|
+
stanza.expects(:type).at_least_once.returns :set
|
533
|
+
stanza.expects(:body).returns 'test'
|
534
|
+
subject.receive_data stanza
|
570
535
|
|
571
|
-
|
572
|
-
|
573
|
-
|
536
|
+
stanza = Blather::Stanza::Iq.new
|
537
|
+
stanza.stubs(:type).at_least_once.returns :get
|
538
|
+
stanza.expects(:body).never
|
539
|
+
subject.receive_data stanza
|
540
|
+
end
|
574
541
|
|
575
|
-
|
576
|
-
|
542
|
+
it 'can be a lambda' do
|
543
|
+
response.expects :call
|
544
|
+
subject.register_handler(:iq, lambda { |s| s.number % 3 == 0 }) { |_| response.call }
|
577
545
|
|
578
|
-
|
579
|
-
|
580
|
-
end
|
546
|
+
stanza.expects(:number).at_least_once.returns 3
|
547
|
+
subject.receive_data stanza
|
581
548
|
|
582
|
-
|
583
|
-
|
584
|
-
xpath.should be_instance_of Nokogiri::XML::NodeSet
|
585
|
-
xpath.should_not be_empty
|
586
|
-
stanza.should == @stanza
|
549
|
+
stanza.expects(:number).at_least_once.returns 2
|
550
|
+
subject.receive_data stanza
|
587
551
|
end
|
588
|
-
@client.register_handler(:iq, "/iq[@id='#{@stanza.id}']") { |stanza, xpath| @response.call stanza, xpath }
|
589
|
-
@client.receive_data @stanza
|
590
|
-
end
|
591
552
|
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
553
|
+
it 'can be an xpath and will send the result to the handler' do
|
554
|
+
response.expects(:call).with do |stanza, xpath|
|
555
|
+
xpath.should be_instance_of Nokogiri::XML::NodeSet
|
556
|
+
xpath.should_not be_empty
|
557
|
+
stanza.should == stanza
|
558
|
+
end
|
559
|
+
subject.register_handler(:iq, "/iq[@id='#{stanza.id}']") { |stanza, xpath| response.call stanza, xpath }
|
560
|
+
subject.receive_data stanza
|
561
|
+
end
|
562
|
+
|
563
|
+
it 'can be an xpath with namespaces and will send the result to the handler' do
|
564
|
+
stanza = Blather::Stanza.parse('<message><foo xmlns="http://bar.com"></message>')
|
565
|
+
response.expects(:call).with do |stanza, xpath|
|
566
|
+
xpath.should be_instance_of Nokogiri::XML::NodeSet
|
567
|
+
xpath.should_not be_empty
|
568
|
+
stanza.should == stanza
|
569
|
+
end
|
570
|
+
subject.register_handler(:message, "/message/bar:foo", :bar => 'http://bar.com') { |stanza, xpath| response.call stanza, xpath }
|
571
|
+
subject.receive_data stanza
|
598
572
|
end
|
599
|
-
@client.register_handler(:message, "/message/bar:foo", :bar => 'http://bar.com') { |stanza, xpath| @response.call stanza, xpath }
|
600
|
-
@client.receive_data @stanza
|
601
|
-
end
|
602
573
|
|
603
|
-
|
604
|
-
|
574
|
+
it 'raises an error when a bad guard is tried' do
|
575
|
+
lambda { subject.register_handler(:iq, 0) {} }.should raise_error RuntimeError
|
576
|
+
end
|
605
577
|
end
|
606
|
-
end
|
607
578
|
|
608
|
-
describe '
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
http://jabber.org/protocol/disco#info
|
682
|
-
http://jabber.org/protocol/disco#items
|
683
|
-
http://jabber.org/protocol/muc
|
684
|
-
}
|
685
|
-
Nokogiri::XML(@caps.c.to_xml).to_s.should == Nokogiri::XML("<presence><c xmlns=\"http://jabber.org/protocol/caps\" hash=\"sha-1\" node=\"http://code.google.com/p/exodus\" ver=\"QgayPKawpkPSDYmwT/WM94uAlu0=\"/></presence>").to_s
|
579
|
+
describe '#caps' do
|
580
|
+
let(:caps) { subject.caps }
|
581
|
+
|
582
|
+
it 'must be of type result' do
|
583
|
+
caps.should respond_to :type
|
584
|
+
caps.type.should == :result
|
585
|
+
end
|
586
|
+
|
587
|
+
it 'can have a client node set' do
|
588
|
+
caps.should respond_to :node=
|
589
|
+
caps.node = "somenode"
|
590
|
+
end
|
591
|
+
|
592
|
+
it 'provides a client node reader' do
|
593
|
+
caps.should respond_to :node
|
594
|
+
caps.node = "somenode"
|
595
|
+
caps.node.should == "somenode##{caps.ver}"
|
596
|
+
end
|
597
|
+
|
598
|
+
it 'can have identities set' do
|
599
|
+
caps.should respond_to :identities=
|
600
|
+
caps.identities = [{:name => "name", :type => "type", :category => "cat"}]
|
601
|
+
end
|
602
|
+
|
603
|
+
it 'provides an identities reader' do
|
604
|
+
caps.should respond_to :identities
|
605
|
+
caps.identities = [{:name => "name", :type => "type", :category => "cat"}]
|
606
|
+
caps.identities.should == [Blather::Stanza::Iq::DiscoInfo::Identity.new({:name => "name", :type => "type", :category => "cat"})]
|
607
|
+
end
|
608
|
+
|
609
|
+
it 'can have features set' do
|
610
|
+
caps.should respond_to :features=
|
611
|
+
caps.features.size.should == 0
|
612
|
+
caps.features = ["feature1"]
|
613
|
+
caps.features.size.should == 1
|
614
|
+
caps.features += [Blather::Stanza::Iq::DiscoInfo::Feature.new("feature2")]
|
615
|
+
caps.features.size.should == 2
|
616
|
+
caps.features = nil
|
617
|
+
caps.features.size.should == 0
|
618
|
+
end
|
619
|
+
|
620
|
+
it 'provides a features reader' do
|
621
|
+
caps.should respond_to :features
|
622
|
+
caps.features = %w{feature1 feature2}
|
623
|
+
caps.features.should == [Blather::Stanza::Iq::DiscoInfo::Feature.new("feature1"), Blather::Stanza::Iq::DiscoInfo::Feature.new("feature2")]
|
624
|
+
end
|
625
|
+
|
626
|
+
it 'provides a client ver reader' do
|
627
|
+
caps.should respond_to :ver
|
628
|
+
caps.node = 'http://code.google.com/p/exodus'
|
629
|
+
caps.identities = [Blather::Stanza::Iq::DiscoInfo::Identity.new({:name => 'Exodus 0.9.1', :type => 'pc', :category => 'client'})]
|
630
|
+
caps.features = %w{
|
631
|
+
http://jabber.org/protocol/caps
|
632
|
+
http://jabber.org/protocol/disco#info
|
633
|
+
http://jabber.org/protocol/disco#items
|
634
|
+
http://jabber.org/protocol/muc
|
635
|
+
}
|
636
|
+
caps.ver.should == 'QgayPKawpkPSDYmwT/WM94uAlu0='
|
637
|
+
caps.node.should == "http://code.google.com/p/exodus#QgayPKawpkPSDYmwT/WM94uAlu0="
|
638
|
+
end
|
639
|
+
|
640
|
+
it 'can construct caps presence correctly' do
|
641
|
+
caps.should respond_to :c
|
642
|
+
caps.node = 'http://code.google.com/p/exodus'
|
643
|
+
caps.identities = [Blather::Stanza::Iq::DiscoInfo::Identity.new({:name => 'Exodus 0.9.1', :type => 'pc', :category => 'client'})]
|
644
|
+
caps.features = %w{
|
645
|
+
http://jabber.org/protocol/caps
|
646
|
+
http://jabber.org/protocol/disco#info
|
647
|
+
http://jabber.org/protocol/disco#items
|
648
|
+
http://jabber.org/protocol/muc
|
649
|
+
}
|
650
|
+
Nokogiri::XML(caps.c.to_xml).to_s.should == Nokogiri::XML("<presence><c xmlns=\"http://jabber.org/protocol/caps\" hash=\"sha-1\" node=\"http://code.google.com/p/exodus\" ver=\"QgayPKawpkPSDYmwT/WM94uAlu0=\"/></presence>").to_s
|
651
|
+
end
|
686
652
|
end
|
687
653
|
end
|