sprsquish-blather 0.3.0 → 0.3.1

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/README.rdoc CHANGED
@@ -60,7 +60,7 @@ Guards act like AND statements. Each condition must be met if the handler is to
60
60
  # Equivalent to saying (stanza.chat? && stanza.body)
61
61
  message :chat?, :body
62
62
 
63
- There are 5 different types of guards:
63
+ The different types of guards are:
64
64
 
65
65
  # Symbol
66
66
  # Checks for a non-false reply to calling the symbol on the stanza
@@ -93,9 +93,24 @@ There are 5 different types of guards:
93
93
  # Equivalent to stanza.body == 'foo' || stanza.body == 'baz'
94
94
  message [{:body => 'foo'}, {:body => 'baz'}]
95
95
 
96
+ == On the Command Line:
97
+
98
+ Default usage is:
99
+
100
+ [blather_script] [options] node@domain.com/resource password [host] [port]
101
+
102
+ Command line options:
103
+
104
+ -D, --debug Run in debug mode (you will see all XMPP communication)
105
+ -d, --daemonize Daemonize the process
106
+ --pid=[PID] Write the PID to this file
107
+ --log=[LOG] Write to the [LOG] file instead of stdout/stderr
108
+ -h, --help Show this message
109
+ -v, --version Show version
110
+
111
+
96
112
  = TODO
97
113
 
98
- * Better Documentation
99
114
  * Add XPath guard that passes the result to the handler
100
115
  * Add Disco the the DSL
101
116
  * PubSub (XEP-0060: http://xmpp.org/extensions/xep-0060.html)
data/Rakefile CHANGED
@@ -16,7 +16,7 @@ begin
16
16
  gem.extensions = ['Rakefile']
17
17
 
18
18
  gem.add_dependency 'eventmachine', '>= 0.12.6'
19
- gem.add_dependency 'libxml-ruby', '>= 1.1.3'
19
+ gem.add_dependency 'libxml-ruby', '>= 1.1.2'
20
20
 
21
21
  gem.files = FileList['examples/**/*', 'lib/**/*', 'ext/*.{rb,c}'].to_a
22
22
 
@@ -0,0 +1,37 @@
1
+ require 'blather/client/dsl'
2
+ $stdout.sync = true
3
+
4
+ module Ping
5
+ extend Blather::DSL
6
+ def self.run; client.run; end
7
+
8
+ setup 'echo@jabber.local/ping', 'echo'
9
+
10
+ status :from => Blather::JID.new('echo@jabber.local/pong') do |s|
11
+ puts "serve!"
12
+ say s.from, 'ping'
13
+ end
14
+
15
+ message :chat?, :body => 'pong' do |m|
16
+ puts "ping!"
17
+ say m.from, 'ping'
18
+ end
19
+ end
20
+
21
+ module Pong
22
+ extend Blather::DSL
23
+ def self.run; client.run; end
24
+
25
+ setup 'echo@jabber.local/pong', 'echo'
26
+ message :chat?, :body => 'ping' do |m|
27
+ puts "pong!"
28
+ say m.from, 'pong'
29
+ end
30
+ end
31
+
32
+ trap(:INT) { EM.stop }
33
+ trap(:TERM) { EM.stop }
34
+ EM.run do
35
+ Ping.run
36
+ Pong.run
37
+ end
data/lib/blather.rb CHANGED
@@ -1,6 +1,3 @@
1
- $:.unshift File.dirname(__FILE__)
2
- $:.unshift File.join(File.dirname(__FILE__), '..')
3
-
4
1
  # Require the necessary files
5
2
  %w[
6
3
  rubygems
@@ -48,6 +45,6 @@ $:.unshift File.join(File.dirname(__FILE__), '..')
48
45
  XML.indent_tree_output = false
49
46
 
50
47
  module Blather
51
- LOG = Logger.new(STDOUT) unless const_defined?(:LOG)
48
+ LOG = Logger.new($stdout) unless const_defined?(:LOG)
52
49
  LOG.level = Logger::INFO
53
50
  end
@@ -1,13 +1,85 @@
1
+ require 'optparse'
1
2
  require File.join(File.dirname(__FILE__), *%w[client dsl])
2
3
 
3
4
  include Blather::DSL
4
5
 
6
+ options = {}
7
+ optparse = OptionParser.new do |opts|
8
+ opts.banner = "Run with #{$0} [options] user@server/resource password [host] [port]"
9
+
10
+ opts.on('-D', '--debug', 'Run in debug mode (you will see all XMPP communication)') do |debug|
11
+ options[:debug] = debug
12
+ end
13
+
14
+ opts.on('-d', '--daemonize', 'Daemonize the process') do |daemonize|
15
+ options[:daemonize] = daemonize
16
+ end
17
+
18
+ opts.on('--pid=[PID]', 'Write the PID to this file') do |pid|
19
+ if !File.writable?(File.dirname(pid))
20
+ $stderr.puts "Unable to write log file to #{pid}"
21
+ exit 1
22
+ end
23
+ options[:pid] = pid
24
+ end
25
+
26
+ opts.on('--log=[LOG]', 'Write to the [LOG] file instead of stdout/stderr') do |log|
27
+ if !File.writable?(File.dirname(log))
28
+ $stderr.puts "Unable to write log file to #{log}"
29
+ exit 1
30
+ end
31
+ options[:log] = log
32
+ end
33
+
34
+ opts.on_tail('-h', '--help', 'Show this message') do
35
+ puts opts
36
+ exit
37
+ end
38
+
39
+ opts.on_tail('-v', '--version', 'Show version') do
40
+ require 'yaml'
41
+ version = YAML.load_file File.join(File.dirname(__FILE__), %w[.. .. VERSION.yml])
42
+ puts "Blather v#{version[:major]}.#{version[:minor]}.#{version[:patch]}"
43
+ exit
44
+ end
45
+ end
46
+ optparse.parse!
47
+
5
48
  at_exit do
6
49
  unless client.setup?
7
50
  if ARGV.length < 2
8
- puts "Run with #{$0} user@server/resource password [host] [port]"
9
- else
10
- client.setup(*ARGV).run
51
+ puts optparse
52
+ exit 1
53
+ end
54
+ client.setup(*ARGV)
55
+ end
56
+
57
+ def run(options)
58
+ $stdin.reopen "/dev/null"
59
+
60
+ if options[:log]
61
+ log = File.new(options[:log], 'a')
62
+ log.sync = options[:debug]
63
+ Blather::LOG.level = Logger::DEBUG if options[:debug]
64
+ $stdout.reopen log
65
+ $stderr.reopen $stdout
66
+ end
67
+ trap(:INT) { EM.stop }
68
+ trap(:TERM) { EM.stop }
69
+ EM.run { client.run }
70
+ end
71
+
72
+ if options[:daemonize]
73
+ pid = fork do
74
+ Process.setsid
75
+ exit if fork
76
+ File.open(options[:pid], 'w') { |f| f << Process.pid } if options[:pid]
77
+ run options
78
+ FileUtils.rm(options[:pid]) if options[:pid]
11
79
  end
80
+ ::Process.detach pid
81
+ exit
82
+ else
83
+ run options
12
84
  end
13
85
  end
@@ -3,8 +3,8 @@ require File.join(File.dirname(__FILE__), *%w[.. .. blather])
3
3
  module Blather #:nodoc:
4
4
 
5
5
  class Client #:nodoc:
6
- attr_accessor :jid,
7
- :roster
6
+ attr_reader :jid,
7
+ :roster
8
8
 
9
9
  def initialize
10
10
  @state = :initializing
@@ -17,6 +17,24 @@ module Blather #:nodoc:
17
17
  setup_initial_handlers
18
18
  end
19
19
 
20
+ def jid=(new_jid)
21
+ @jid = JID.new new_jid
22
+ end
23
+
24
+ def status
25
+ @status.state
26
+ end
27
+
28
+ def status=(state)
29
+ state, msg, to = state
30
+
31
+ status = Stanza::Presence::Status.new state, msg
32
+ status.to = to
33
+ @status = status unless to
34
+
35
+ write status
36
+ end
37
+
20
38
  def setup?
21
39
  @setup.is_a? Array
22
40
  end
@@ -30,11 +48,8 @@ module Blather #:nodoc:
30
48
 
31
49
  def run
32
50
  raise 'not setup!' unless setup?
33
- trap(:INT) { EM.stop }
34
- EM.run {
35
- klass = @setup[0].node ? Blather::Stream::Client : Blather::Stream::Component
36
- klass.start self, *@setup
37
- }
51
+ klass = @setup[0].node ? Blather::Stream::Client : Blather::Stream::Component
52
+ @stream = klass.start self, *@setup
38
53
  end
39
54
 
40
55
  def register_tmp_handler(id, &handler)
@@ -42,62 +57,38 @@ module Blather #:nodoc:
42
57
  end
43
58
 
44
59
  def register_handler(type, *guards, &handler)
60
+ check_guards guards
45
61
  @handlers[type] ||= []
46
62
  @handlers[type] << [guards, handler]
47
63
  end
48
64
 
49
- def status
50
- @status.state
51
- end
52
-
53
- def status=(state)
54
- state, msg, to = state
55
-
56
- status = Stanza::Presence::Status.new state, msg
57
- status.to = to
58
- @status = status unless to
59
-
60
- write status
61
- end
62
-
63
65
  def write(stanza)
64
66
  stanza.from ||= jid if stanza.respond_to?(:from)
65
67
  @stream.send(stanza) if @stream
66
68
  end
67
69
 
68
- def write_with_handler(stanza, &hanlder)
70
+ def write_with_handler(stanza, &handler)
69
71
  register_tmp_handler stanza.id, &handler
70
72
  write stanza
71
73
  end
72
74
 
73
- def stream_started(stream)
74
- @stream = stream
75
-
76
- #retreive roster
77
- if @stream.is_a?(Stream::Component)
78
- @state = :ready
79
- call_handler_for :ready, nil
80
- else
81
- r = Stanza::Iq::Roster.new
82
- register_tmp_handler r.id do |node|
83
- roster.process node
84
- @state = :ready
85
- write @status
86
- call_handler_for :ready, nil
87
- end
88
- write r
75
+ def post_init
76
+ case @stream
77
+ when Stream::Component then ready!
78
+ when Stream::Client then client_post_init
79
+ else raise "Don't know #{@stream.class} stream type. How the hell did this happen!?"
89
80
  end
90
81
  end
91
82
 
92
- def stop
83
+ def close
93
84
  @stream.close_connection_after_writing
94
85
  end
95
86
 
96
- def stopped
97
- EM.stop
87
+ def unbind
88
+ EM.stop if EM.reactor_running?
98
89
  end
99
90
 
100
- def call(stanza)
91
+ def receive_data(stanza)
101
92
  if handler = @tmp_handlers.delete(stanza.id)
102
93
  handler.call stanza
103
94
  else
@@ -107,21 +98,14 @@ module Blather #:nodoc:
107
98
  end
108
99
  end
109
100
 
110
- def call_handler_for(type, stanza)
111
- if @handlers[type]
112
- @handlers[type].find { |guards, handler| handler.call(stanza) unless guarded?(guards, stanza) }
113
- true
114
- end
115
- end
116
-
117
101
  protected
118
102
  def setup_initial_handlers
119
103
  register_handler :error do |err|
120
104
  raise err
121
105
  end
122
106
 
123
- register_handler :iq do |iq|
124
- write(StanzaError.new(iq, 'service-unavailable', :cancel).to_node) if [:set, :get].include?(iq.type)
107
+ register_handler :iq, :type => [:get, :set] do |iq|
108
+ write(StanzaError.new(iq, 'service-unavailable', :cancel).to_node)
125
109
  end
126
110
 
127
111
  register_handler :status do |status|
@@ -133,8 +117,30 @@ module Blather #:nodoc:
133
117
  end
134
118
  end
135
119
 
120
+ def ready!
121
+ @state = :ready
122
+ call_handler_for :ready, nil
123
+ end
124
+
125
+ def client_post_init
126
+ write_with_handler Stanza::Iq::Roster.new do |node|
127
+ roster.process node
128
+ write @status
129
+ ready!
130
+ end
131
+ end
132
+
133
+ def call_handler_for(type, stanza)
134
+ if @handlers[type]
135
+ @handlers[type].find { |guards, handler| handler.call(stanza) unless guarded?(guards, stanza) }
136
+ true
137
+ end
138
+ end
139
+
136
140
  ##
137
141
  # If any of the guards returns FALSE this returns true
142
+ # the logic is reversed to allow short circuiting
143
+ # (why would anyone want to loop over more values than necessary?)
138
144
  def guarded?(guards, stanza)
139
145
  guards.find do |guard|
140
146
  case guard
@@ -158,8 +164,16 @@ module Blather #:nodoc:
158
164
  end
159
165
  when Proc
160
166
  !guard.call(stanza)
161
- else
162
- raise "Bad guard: #{guard.inspect}"
167
+ end
168
+ end
169
+ end
170
+
171
+ def check_guards(guards)
172
+ guards.each do |guard|
173
+ case guard
174
+ when Array then guard.each { |g| check_guards([g]) }
175
+ when Symbol, Proc, Hash then nil
176
+ else raise "Bad guard: #{guard.inspect}"
163
177
  end
164
178
  end
165
179
  end
@@ -13,14 +13,13 @@ module Blather
13
13
  # host and port are optional defaulting to the domain in the JID and 5222 respectively
14
14
  def setup(jid, password, host = nil, port = nil)
15
15
  client.setup(jid, password, host, port)
16
- at_exit { client.run }
17
16
  end
18
17
 
19
18
  ##
20
19
  # Shutdown the connection.
21
20
  # Flushes the write buffer then stops EventMachine
22
21
  def shutdown
23
- client.stop
22
+ client.close
24
23
  end
25
24
 
26
25
  ##
@@ -37,13 +36,13 @@ module Blather
37
36
 
38
37
  ##
39
38
  # Set current status
40
- def status(state = nil, msg = nil)
39
+ def set_status(state = nil, msg = nil)
41
40
  client.status = state, msg
42
41
  end
43
42
 
44
43
  ##
45
44
  # Direct access to the roster
46
- def roster
45
+ def my_roster
47
46
  client.roster
48
47
  end
49
48
 
@@ -51,7 +50,7 @@ module Blather
51
50
  # Write data to the stream
52
51
  # Anything that resonds to #to_s can be paseed to the stream
53
52
  def write(stanza)
54
- client.write(stanza)
53
+ client.write stanza
55
54
  end
56
55
 
57
56
  ##
@@ -68,7 +67,9 @@ module Blather
68
67
  end
69
68
 
70
69
  ##
71
- #
70
+ # Request items or info from an entity
71
+ # discover (items|info), [jid], [node] do |response|
72
+ # end
72
73
  def discover(what, who, where, &callback)
73
74
  stanza = Blather::Stanza.class_from_registration(:query, "http://jabber.org/protocol/disco##{what}").new
74
75
  stanza.to = who
@@ -78,22 +79,16 @@ module Blather
78
79
  write stanza
79
80
  end
80
81
 
81
- ##
82
- # PubSub proxy
83
- def pubsub
84
- client.pubsub
85
- end
86
-
87
82
  ##
88
83
  # Checks to see if the method is part of the handlers list.
89
84
  # If so it creates a handler, otherwise it'll pass it back
90
85
  # to Ruby's method_missing handler
91
- def method_missing(method, *args, &block)
92
- if Blather::Stanza.handler_list.include?(method)
93
- handle method, *args, &block
94
- else
95
- super
96
- end
86
+ Blather::Stanza.handler_list.each do |handler_name|
87
+ module_eval <<-METHOD, __FILE__, __LINE__
88
+ def #{handler_name}(*args, &callback)
89
+ handle :#{handler_name}, *args, &callback
90
+ end
91
+ METHOD
97
92
  end
98
93
  end #DSL
99
94
  end #Blather
@@ -52,13 +52,6 @@ class Stanza
52
52
  self.category = category
53
53
  end
54
54
  end
55
-
56
- def eql?(other)
57
- other.kind_of?(self.class) &&
58
- other.name == self.name &&
59
- other.type == self.type &&
60
- other.category == self.category
61
- end
62
55
  end
63
56
 
64
57
  class Feature < XMPPNode
@@ -72,11 +65,6 @@ class Stanza
72
65
  self.var = var
73
66
  end
74
67
  end
75
-
76
- def eql?(other)
77
- other.kind_of?(self.class) &&
78
- other.var == self.var
79
- end
80
68
  end
81
69
  end
82
70
 
@@ -46,13 +46,6 @@ class Stanza
46
46
 
47
47
  attribute_accessor :node, :name, :to_sym => false
48
48
  end
49
-
50
- def eql?(other)
51
- other.kind_of?(self.class) &&
52
- other.jid == self.jid &&
53
- other.node == self.node &&
54
- other.name == self.name
55
- end
56
49
  end
57
50
 
58
51
  end #Stanza
@@ -87,8 +87,8 @@ module Blather
87
87
  def unbind # :nodoc:
88
88
  # @keepalive.cancel
89
89
  @state = :stopped
90
- @client.call @error if @error
91
- @client.stopped
90
+ @client.receive_data @error if @error
91
+ @client.unbind
92
92
  end
93
93
 
94
94
  ##
@@ -162,7 +162,7 @@ module Blather
162
162
  # Called when @state == :ready
163
163
  # Simply passes the stanza to the client
164
164
  def ready
165
- @client.call @node.to_stanza
165
+ @client.receive_data @node.to_stanza
166
166
  end
167
167
 
168
168
  def handle_stream_error
@@ -242,7 +242,7 @@ module Blather
242
242
  @session = Session.new self, @to
243
243
  # on success destroy the session object, let the client know the stream has been started
244
244
  # then continue the features dispatch process
245
- @session.on_success { LOG.debug "SESSION: SUCCESS"; @session = nil; @client.stream_started(self); @state = :features; dispatch }
245
+ @session.on_success { LOG.debug "SESSION: SUCCESS"; @session = nil; @client.post_init; @state = :features; dispatch }
246
246
  # on failure end the stream
247
247
  @session.on_failure { |err| LOG.debug "SESSION: FAILURE"; @error = err; stop }
248
248
 
@@ -6,7 +6,7 @@ class Stream
6
6
 
7
7
  def receive(node) # :nodoc:
8
8
  if node.element_name == 'handshake'
9
- @client.stream_started(self)
9
+ @client.post_init
10
10
  else
11
11
  super
12
12
  end
@@ -0,0 +1,389 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. .. spec_helper])
2
+ require 'blather/client/client'
3
+
4
+ describe 'Blather::Client' do
5
+ before do
6
+ @client = Blather::Client.new
7
+ end
8
+
9
+ it 'provides a JID accessor' do
10
+ @client.must_respond_to :jid
11
+ @client.jid.must_be_nil
12
+
13
+ jid = 'me@me.com/test'
14
+ @client.must_respond_to :jid=
15
+ @client.jid = jid
16
+ @client.jid.must_be_kind_of JID
17
+ @client.jid.must_equal JID.new(jid)
18
+ end
19
+
20
+ it 'provides a reader for the roster' do
21
+ @client.must_respond_to :roster
22
+ @client.roster.must_be_kind_of Roster
23
+ end
24
+
25
+ it 'provides a status reader' do
26
+ @client.must_respond_to :status
27
+ @client.status = :away
28
+ @client.status.must_equal :away
29
+ end
30
+
31
+ it 'can be setup' do
32
+ @client.must_respond_to :setup
33
+ @client.setup('me@me.com', 'pass').must_equal @client
34
+ end
35
+
36
+ it 'knows if it has been setup' do
37
+ @client.must_respond_to :setup?
38
+ @client.setup?.must_equal false
39
+ @client.setup 'me@me.com', 'pass'
40
+ @client.setup?.must_equal true
41
+ end
42
+
43
+ it 'cannot be run before being setup' do
44
+ lambda { @client.run }.must_raise RuntimeError
45
+ end
46
+
47
+ it 'starts up a Component connection when setup without a node' do
48
+ setup = 'pubsub.jabber.local', 'secret'
49
+ @client.setup *setup
50
+ Blather::Stream::Component.expects(:start).with @client, *setup
51
+ @client.run
52
+ end
53
+
54
+ it 'starts up a Client connection when setup with a node' do
55
+ setup = 'test@jabber.local', 'secret'
56
+ @client.setup *setup
57
+ Blather::Stream::Client.expects(:start).with @client, *setup
58
+ @client.run
59
+ end
60
+
61
+ it 'writes to the connection the closes when #close is called' do
62
+ stream = mock()
63
+ stream.expects(:close_connection_after_writing)
64
+ Blather::Stream::Component.stubs(:start).returns stream
65
+ @client.setup('me.com', 'secret').run
66
+ @client.close
67
+ end
68
+
69
+ it 'shuts down EM when #unbind is called if it is running' do
70
+ EM.expects(:reactor_running?).returns true
71
+ EM.expects(:stop)
72
+ @client.unbind
73
+ end
74
+
75
+ it 'does nothing when #unbind is called and EM is not running' do
76
+ EM.expects(:reactor_running?).returns false
77
+ EM.expects(:stop).never
78
+ @client.unbind
79
+ end
80
+
81
+ it 'raises an error if the stream type somehow is not supported' do
82
+ Blather::Stream::Component.stubs(:start).returns nil
83
+ @client.setup('me.com', 'secret').run
84
+ lambda { @client.post_init }.must_raise RuntimeError
85
+ end
86
+
87
+ it 'can register a temporary handler based on stanza ID' do
88
+ stanza = Stanza::Iq.new
89
+ response = mock()
90
+ response.expects(:call)
91
+ @client.register_tmp_handler(stanza.id) { |_| response.call }
92
+ @client.receive_data stanza
93
+ end
94
+
95
+ it 'removes a tmp handler as soon as it is used' do
96
+ stanza = Stanza::Iq.new
97
+ response = mock()
98
+ response.expects(:call)
99
+ @client.register_tmp_handler(stanza.id) { |_| response.call }
100
+ @client.receive_data stanza
101
+ @client.receive_data stanza
102
+ end
103
+
104
+ it 'will create a handler then write the stanza' do
105
+ stanza = Stanza::Iq.new
106
+ response = mock()
107
+ response.expects(:call)
108
+ @client.expects(:write).with do |s|
109
+ @client.receive_data stanza
110
+ s.must_equal stanza
111
+ end
112
+ @client.write_with_handler(stanza) { |_| response.call }
113
+ end
114
+
115
+ it 'can register a handler' do
116
+ stanza = Stanza::Iq.new
117
+ response = mock()
118
+ response.expects(:call).times(2)
119
+ @client.register_handler(:iq) { |_| response.call }
120
+ @client.receive_data stanza
121
+ @client.receive_data stanza
122
+ end
123
+ end
124
+
125
+ describe 'Blather::Client#write' do
126
+ before do
127
+ @client = Blather::Client.new
128
+ end
129
+
130
+ it 'sets the from attr on a stanza' do
131
+ jid = 'me@me.com'
132
+ stanza = mock(:from => nil)
133
+ stanza.expects(:from=).with jid
134
+ @client.jid = jid
135
+ @client.write stanza
136
+ end
137
+
138
+ it 'does not set the from attr if it already exists' do
139
+ jid = 'me@me.com'
140
+ stanza = Stanza::Iq.new
141
+ stanza.from = jid
142
+ stanza.expects(:from).returns jid
143
+ stanza.expects(:from=).never
144
+ @client.jid = jid
145
+ @client.write stanza
146
+ end
147
+
148
+ it 'writes to the stream' do
149
+ stanza = Stanza::Iq.new
150
+ stream = mock()
151
+ stream.expects(:send).with stanza
152
+ Blather::Stream::Client.expects(:start).returns stream
153
+ @client.setup('me@me.com', 'me').run
154
+ @client.write stanza
155
+ end
156
+ end
157
+
158
+ describe 'Blather::Client#status=' do
159
+ before do
160
+ @client = Blather::Client.new
161
+ end
162
+
163
+ it 'updates the state when not sending to a JID' do
164
+ @client.status.wont_equal :away
165
+ @client.status = :away, 'message'
166
+ @client.status.must_equal :away
167
+ end
168
+
169
+ it 'does not update the state when sending to a JID' do
170
+ @client.status.wont_equal :away
171
+ @client.status = :away, 'message', 'me@me.com'
172
+ @client.status.wont_equal :away
173
+ end
174
+
175
+ it 'writes the new status to the stream' do
176
+ Stanza::Presence::Status.stubs(:next_id).returns 0
177
+ status = [:away, 'message']
178
+ @client.expects(:write).with do |s|
179
+ s.must_be_kind_of Stanza::Presence::Status
180
+ s.to_s.must_equal Stanza::Presence::Status.new(*status).to_s
181
+ end
182
+ @client.status = status
183
+ end
184
+ end
185
+
186
+ describe 'Blather::Client default handlers' do
187
+ before do
188
+ @client = Blather::Client.new
189
+ end
190
+
191
+ it 're-raises errors' do
192
+ err = BlatherError.new
193
+ lambda { @client.receive_data err }.must_raise BlatherError
194
+ end
195
+
196
+ it 'responds to iq:get with a "service-unavailable" error' do
197
+ get = Stanza::Iq.new :get
198
+ err = StanzaError.new(get, 'service-unavailable', :cancel).to_node
199
+ @client.expects(:write).with err
200
+ @client.receive_data get
201
+ end
202
+
203
+ it 'responds to iq:get with a "service-unavailable" error' do
204
+ get = Stanza::Iq.new :get
205
+ err = StanzaError.new(get, 'service-unavailable', :cancel).to_node
206
+ @client.expects(:write).with err
207
+ @client.receive_data get
208
+ end
209
+
210
+ it 'responds to iq:set with a "service-unavailable" error' do
211
+ get = Stanza::Iq.new :set
212
+ err = StanzaError.new(get, 'service-unavailable', :cancel).to_node
213
+ @client.expects(:write).with err
214
+ @client.receive_data get
215
+ end
216
+
217
+ it 'handles status changes by updating the roster if the status is from a JID in the roster' do
218
+ jid = 'friend@jabber.local'
219
+ status = Stanza::Presence::Status.new :away
220
+ status.stubs(:from).returns jid
221
+ roster_item = mock()
222
+ roster_item.expects(:status=).with status
223
+ @client.stubs(:roster).returns({status.from => roster_item})
224
+ @client.receive_data status
225
+ end
226
+
227
+ it 'handles an incoming roster node by processing it through the roster' do
228
+ roster = Stanza::Iq::Roster.new
229
+ client_roster = mock()
230
+ client_roster.expects(:process).with roster
231
+ @client.stubs(:roster).returns client_roster
232
+ @client.receive_data roster
233
+ end
234
+ end
235
+
236
+ describe 'Blather::Client with a Component stream' do
237
+ before do
238
+ class MockComponent < Blather::Stream::Component; def initialize(); end; end
239
+ @client = Blather::Client.new
240
+ Blather::Stream::Component.stubs(:start).returns MockComponent.new('')
241
+ @client.setup('me.com', 'secret').run
242
+ end
243
+
244
+ it 'calls the ready handler when sent post_init' do
245
+ ready = mock()
246
+ ready.expects(:call)
247
+ @client.register_handler(:ready) { ready.call }
248
+ @client.post_init
249
+ end
250
+ end
251
+
252
+ describe 'Blather::Client with a Client stream' do
253
+ before do
254
+ class MockClientStream < Blather::Stream::Client; def initialize(); end; end
255
+ @stream = MockClientStream.new('')
256
+ @client = Blather::Client.new
257
+ Blather::Stream::Client.stubs(:start).returns @stream
258
+ @client.setup('me@me.com', 'secret').run
259
+ end
260
+
261
+ it 'sends a request for the roster when post_init is called' do
262
+ @stream.expects(:send).with { |stanza| stanza.must_be_kind_of Stanza::Iq::Roster }
263
+ @client.post_init
264
+ end
265
+
266
+ it 'calls the ready handler after post_init and roster is received' do
267
+ result_roster = Stanza::Iq::Roster.new :result
268
+ @stream.stubs(:send).with { |s| result_roster.id = s.id; @client.receive_data result_roster; true }
269
+
270
+ ready = mock()
271
+ ready.expects(:call)
272
+ @client.register_handler(:ready) { ready.call }
273
+ @client.post_init
274
+ end
275
+ end
276
+
277
+ describe 'Blather::Client guards' do
278
+ before do
279
+ @client = Blather::Client.new
280
+ @stanza = Stanza::Iq.new
281
+ @response = mock()
282
+ end
283
+
284
+ it 'can be a symbol' do
285
+ @response.expects :call
286
+ @client.register_handler(:iq, :chat?) { |_| @response.call }
287
+
288
+ @stanza.expects(:chat?).returns true
289
+ @client.receive_data @stanza
290
+
291
+ @stanza.expects(:chat?).returns false
292
+ @client.receive_data @stanza
293
+ end
294
+
295
+ it 'can be a hash with string match' do
296
+ @response.expects :call
297
+ @client.register_handler(:iq, :body => 'exit') { |_| @response.call }
298
+
299
+ @stanza.expects(:body).returns 'exit'
300
+ @client.receive_data @stanza
301
+
302
+ @stanza.expects(:body).returns 'not-exit'
303
+ @client.receive_data @stanza
304
+ end
305
+
306
+ it 'can be a hash with a value' do
307
+ @response.expects :call
308
+ @client.register_handler(:iq, :number => 0) { |_| @response.call }
309
+
310
+ @stanza.expects(:number).returns 0
311
+ @client.receive_data @stanza
312
+
313
+ @stanza.expects(:number).returns 1
314
+ @client.receive_data @stanza
315
+ end
316
+
317
+ it 'can be a hash with a regexp' do
318
+ @response.expects :call
319
+ @client.register_handler(:iq, :body => /exit/) { |_| @response.call }
320
+
321
+ @stanza.expects(:body).returns 'more than just exit, but exit still'
322
+ @client.receive_data @stanza
323
+
324
+ @stanza.expects(:body).returns 'keyword not found'
325
+ @client.receive_data @stanza
326
+ end
327
+
328
+ it 'can be a hash with an array' do
329
+ @response.expects(:call).times(2)
330
+ @client.register_handler(:iq, :type => [:result, :error]) { |_| @response.call }
331
+
332
+ stanza = Stanza::Iq.new
333
+ stanza.expects(:type).at_least_once.returns :result
334
+ @client.receive_data stanza
335
+
336
+ stanza = Stanza::Iq.new
337
+ stanza.expects(:type).at_least_once.returns :error
338
+ @client.receive_data stanza
339
+
340
+ stanza = Stanza::Iq.new
341
+ stanza.expects(:type).at_least_once.returns :get
342
+ @client.receive_data stanza
343
+ end
344
+
345
+ it 'chained are treated like andand (short circuited)' do
346
+ @response.expects :call
347
+ @client.register_handler(:iq, :type => :get, :body => 'test') { |_| @response.call }
348
+
349
+ stanza = Stanza::Iq.new
350
+ stanza.expects(:type).at_least_once.returns :get
351
+ stanza.expects(:body).returns 'test'
352
+ @client.receive_data stanza
353
+
354
+ stanza = Stanza::Iq.new
355
+ stanza.expects(:type).at_least_once.returns :set
356
+ stanza.expects(:body).never
357
+ @client.receive_data stanza
358
+ end
359
+
360
+ it 'within an Array are treated as oror (short circuited)' do
361
+ @response.expects(:call).times 2
362
+ @client.register_handler(:iq, [{:type => :get}, {:body => 'test'}]) { |_| @response.call }
363
+
364
+ stanza = Stanza::Iq.new
365
+ stanza.expects(:type).at_least_once.returns :set
366
+ stanza.expects(:body).returns 'test'
367
+ @client.receive_data stanza
368
+
369
+ stanza = Stanza::Iq.new
370
+ stanza.stubs(:type).at_least_once.returns :get
371
+ stanza.expects(:body).never
372
+ @client.receive_data stanza
373
+ end
374
+
375
+ it 'can be a lambda' do
376
+ @response.expects :call
377
+ @client.register_handler(:iq, lambda { |s| s.number % 3 == 0 }) { |_| @response.call }
378
+
379
+ @stanza.expects(:number).at_least_once.returns 3
380
+ @client.receive_data @stanza
381
+
382
+ @stanza.expects(:number).at_least_once.returns 2
383
+ @client.receive_data @stanza
384
+ end
385
+
386
+ it 'raises an error when a bad guard is tried' do
387
+ lambda { @client.register_handler(:iq, 0) {} }.must_raise RuntimeError
388
+ end
389
+ end
@@ -0,0 +1,105 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. .. spec_helper])
2
+ require 'blather/client/dsl'
3
+
4
+ describe 'Blather::DSL' do
5
+ before do
6
+ @client = mock()
7
+ @dsl = class MockDSL; include Blather::DSL; end.new
8
+ Client.stubs(:new).returns(@client)
9
+ end
10
+
11
+ it 'wraps the setup' do
12
+ args = ['jid', 'pass', 'host', 0000]
13
+ @client.expects(:setup).with *args
14
+ @dsl.setup *args
15
+ end
16
+
17
+ it 'allows host to be nil in setup' do
18
+ args = ['jid', 'pass']
19
+ @client.expects(:setup).with *(args + [nil, nil])
20
+ @dsl.setup *args
21
+ end
22
+
23
+ it 'allows port to be nil in setup' do
24
+ args = ['jid', 'pass', 'host']
25
+ @client.expects(:setup).with *(args + [nil])
26
+ @dsl.setup *args
27
+ end
28
+
29
+ it 'stops when shutdown is called' do
30
+ @client.expects(:close)
31
+ @dsl.shutdown
32
+ end
33
+
34
+ it 'sets up handlers' do
35
+ type = :message
36
+ guards = [:chat?, {:body => 'exit'}]
37
+ @client.expects(:register_handler).with type, *guards
38
+ @dsl.handle type, *guards
39
+ end
40
+
41
+ it 'provides a helper for ready state' do
42
+ @client.expects(:register_handler).with :ready
43
+ @dsl.when_ready
44
+ end
45
+
46
+ it 'sets the initial status' do
47
+ state = :away
48
+ msg = 'do not disturb'
49
+ @client.expects(:status=).with [state, msg]
50
+ @dsl.set_status state, msg
51
+ end
52
+
53
+ it 'provides a roster accessor' do
54
+ @client.expects :roster
55
+ @dsl.my_roster
56
+ end
57
+
58
+ it 'provides a writer' do
59
+ stanza = Blather::Stanza::Iq.new
60
+ @client.expects(:write).with stanza
61
+ @dsl.write stanza
62
+ end
63
+
64
+ it 'provides a "say" helper' do
65
+ to, msg = 'me@me.com', 'hello!'
66
+ Blather::Stanza::Message.stubs(:next_id).returns 0
67
+ @client.expects(:write).with Blather::Stanza::Message.new(to, msg)
68
+ @dsl.say to, msg
69
+ end
70
+
71
+ it 'provides a JID accessor' do
72
+ @client.expects :jid
73
+ @dsl.jid
74
+ end
75
+
76
+ it 'provides a disco helper for items' do
77
+ what, who, where = :items, 'me@me.com', 'my/node'
78
+ Blather::Stanza::Disco::DiscoItems.stubs(:next_id).returns 0
79
+ @client.expects(:temporary_handler).with '0'
80
+ expected_stanza = Blather::Stanza::Disco::DiscoItems.new
81
+ expected_stanza.to = who
82
+ expected_stanza.node = where
83
+ @client.expects(:write).with expected_stanza
84
+ @dsl.discover what, who, where
85
+ end
86
+
87
+ it 'provides a disco helper for info' do
88
+ what, who, where = :info, 'me@me.com', 'my/node'
89
+ Blather::Stanza::Disco::DiscoInfo.stubs(:next_id).returns 0
90
+ @client.expects(:temporary_handler).with '0'
91
+ expected_stanza = Blather::Stanza::Disco::DiscoInfo.new
92
+ expected_stanza.to = who
93
+ expected_stanza.node = where
94
+ @client.expects(:write).with expected_stanza
95
+ @dsl.discover what, who, where
96
+ end
97
+
98
+ Blather::Stanza.handler_list.each do |handler_method|
99
+ it "provides a helper method for #{handler_method}" do
100
+ guards = [:chat?, {:body => 'exit'}]
101
+ @client.expects(:register_handler).with handler_method, *guards
102
+ @dsl.__send__(handler_method, *guards)
103
+ end
104
+ end
105
+ end
@@ -11,7 +11,7 @@ describe 'Blather::Stream::Client' do
11
11
 
12
12
  def mocked_server(times = nil, &block)
13
13
  @client ||= mock()
14
- @client.stubs(:stopped) unless @client.respond_to?(:stopped)
14
+ @client.stubs(:unbind) unless @client.respond_to?(:unbind)
15
15
  @client.stubs(:jid=) unless @client.respond_to?(:jid=)
16
16
 
17
17
  MockServer.any_instance.expects(:receive_data).send(*(times ? [:times, times] : [:at_least, 1])).with &block
@@ -58,7 +58,7 @@ describe 'Blather::Stream::Client' do
58
58
 
59
59
  it 'sends stanzas to the client when the stream is ready' do
60
60
  @client = mock()
61
- @client.expects(:call).with do |n|
61
+ @client.expects(:receive_data).with do |n|
62
62
  EM.stop
63
63
  n.kind_of?(Stanza::Message) && @stream.ready?.must_equal(true)
64
64
  end
@@ -72,7 +72,7 @@ describe 'Blather::Stream::Client' do
72
72
 
73
73
  it 'puts itself in the stopped state and calls @client.stopped when stopped' do
74
74
  @client = mock()
75
- @client.expects(:stopped).at_least_once
75
+ @client.expects(:unbind).at_least_once
76
76
 
77
77
  started = false
78
78
  mocked_server(2) do |val, server|
@@ -95,8 +95,8 @@ describe 'Blather::Stream::Client' do
95
95
  it 'will be in the negotiating state during feature negotiations' do
96
96
  state = nil
97
97
  @client = mock()
98
- @client.stubs(:stream_started)
99
- @client.expects(:call).with do |n|
98
+ @client.stubs(:post_init)
99
+ @client.expects(:receive_data).with do |n|
100
100
  EM.stop
101
101
  state.must_equal(:negotiated) && @stream.negotiating?.must_equal(false)
102
102
  end
@@ -154,7 +154,7 @@ describe 'Blather::Stream::Client' do
154
154
 
155
155
  it 'sends client an error on stream:error' do
156
156
  @client = mock()
157
- @client.expects(:call).with do |v|
157
+ @client.expects(:receive_data).with do |v|
158
158
  v.name.must_equal :conflict
159
159
  v.text.must_equal 'Already signed in'
160
160
  v.to_s.must_equal "Stream Error (conflict): #{v.text}"
@@ -216,7 +216,7 @@ describe 'Blather::Stream::Client' do
216
216
  it 'will fail if TLS negotiation fails' do
217
217
  state = nil
218
218
  @client = mock()
219
- @client.expects(:call).with { |v| v.must_be_kind_of TLSFailure }
219
+ @client.expects(:receive_data).with { |v| v.must_be_kind_of TLSFailure }
220
220
  mocked_server(3) do |val, server|
221
221
  case state
222
222
  when nil
@@ -245,7 +245,7 @@ describe 'Blather::Stream::Client' do
245
245
  it 'will fail if a bad node comes through TLS negotiations' do
246
246
  state = nil
247
247
  @client = mock()
248
- @client.expects(:call).with do |v|
248
+ @client.expects(:receive_data).with do |v|
249
249
  v.must_be_kind_of UnknownResponse
250
250
  v.node.element_name.must_equal 'foo-bar'
251
251
  end
@@ -400,7 +400,7 @@ describe 'Blather::Stream::Client' do
400
400
  it 'tries each possible mechanism until it fails completely' do
401
401
  state = nil
402
402
  @client = mock()
403
- @client.expects(:call).with do |n|
403
+ @client.expects(:receive_data).with do |n|
404
404
  n.must_be_kind_of(SASLError)
405
405
  n.name.must_equal :not_authorized
406
406
  end
@@ -473,7 +473,7 @@ describe 'Blather::Stream::Client' do
473
473
 
474
474
  it 'sends client an error when an unknown mechanism is sent' do
475
475
  @client = mock()
476
- @client.expects(:call).with { |v| v.must_be_kind_of(Stream::SASL::UnknownMechanism) }
476
+ @client.expects(:receive_data).with { |v| v.must_be_kind_of(Stream::SASL::UnknownMechanism) }
477
477
  started = false
478
478
  mocked_server(2) do |val, server|
479
479
  if !started
@@ -500,7 +500,7 @@ describe 'Blather::Stream::Client' do
500
500
  ].each do |error_type|
501
501
  it "fails on #{error_type}" do
502
502
  @client = mock()
503
- @client.expects(:call).with do |n|
503
+ @client.expects(:receive_data).with do |n|
504
504
  n.name.must_equal error_type.gsub('-','_').to_sym
505
505
  end
506
506
  state = nil
@@ -532,7 +532,7 @@ describe 'Blather::Stream::Client' do
532
532
 
533
533
  it 'fails when an unkown node comes through during SASL negotiation' do
534
534
  @client = mock()
535
- @client.expects(:call).with do |n|
535
+ @client.expects(:receive_data).with do |n|
536
536
  n.must_be_instance_of UnknownResponse
537
537
  n.node.element_name.must_equal 'foo-bar'
538
538
  end
@@ -629,7 +629,7 @@ describe 'Blather::Stream::Client' do
629
629
  it 'will return an error if resource binding errors out' do
630
630
  state = nil
631
631
  @client = mock()
632
- @client.expects(:call).with do |n|
632
+ @client.expects(:receive_data).with do |n|
633
633
  n.name.must_equal :bad_request
634
634
  end
635
635
  mocked_server(3) do |val, server|
@@ -660,7 +660,7 @@ describe 'Blather::Stream::Client' do
660
660
  it 'will return an error if an unkown node comes through during resouce binding' do
661
661
  state = nil
662
662
  @client = mock()
663
- @client.expects(:call).with do |n|
663
+ @client.expects(:receive_data).with do |n|
664
664
  n.must_be_instance_of UnknownResponse
665
665
  n.node.element_name.must_equal 'foo-bar'
666
666
  end
@@ -692,7 +692,7 @@ describe 'Blather::Stream::Client' do
692
692
  it 'will establish a session if requested' do
693
693
  state = nil
694
694
  @client = mock()
695
- @client.expects(:stream_started)
695
+ @client.expects(:post_init)
696
696
 
697
697
  mocked_server(3) do |val, server|
698
698
  case state
@@ -723,7 +723,7 @@ describe 'Blather::Stream::Client' do
723
723
  it 'will return an error if session establishment errors out' do
724
724
  state = nil
725
725
  @client = mock()
726
- @client.expects(:call).with do |n|
726
+ @client.expects(:receive_data).with do |n|
727
727
  n.name.must_equal :internal_server_error
728
728
  end
729
729
  mocked_server(3) do |val, server|
@@ -754,7 +754,7 @@ describe 'Blather::Stream::Client' do
754
754
  it 'will return an error if an unknown node come through during session establishment' do
755
755
  state = nil
756
756
  @client = mock()
757
- @client.expects(:call).with do |n|
757
+ @client.expects(:receive_data).with do |n|
758
758
  n.must_be_instance_of UnknownResponse
759
759
  n.node.element_name.must_equal 'foo-bar'
760
760
  end
@@ -785,7 +785,7 @@ describe 'Blather::Stream::Client' do
785
785
 
786
786
  it 'sends client an error on parse error' do
787
787
  @client = mock()
788
- @client.expects(:call).with do |v|
788
+ @client.expects(:receive_data).with do |v|
789
789
  v.must_be_kind_of ParseError
790
790
  v.message.must_match(/generate\-parse\-error/)
791
791
  end
@@ -11,7 +11,7 @@ describe 'Blather::Stream::Component' do
11
11
 
12
12
  def mocked_server(times = nil, &block)
13
13
  @client ||= mock()
14
- @client.stubs(:stopped) unless @client.respond_to?(:stopped)
14
+ @client.stubs(:unbind) unless @client.respond_to?(:unbind)
15
15
  @client.stubs(:jid=) unless @client.respond_to?(:jid=)
16
16
 
17
17
  MockServer.any_instance.expects(:receive_data).send(*(times ? [:times, times] : [:at_least, 1])).with &block
@@ -60,8 +60,8 @@ describe 'Blather::Stream::Component' do
60
60
  end
61
61
 
62
62
  it 'sends stanzas to the client when the stream is ready' do
63
- @client = mock(:stream_started)
64
- @client.expects(:call).with do |n|
63
+ @client = mock(:post_init)
64
+ @client.expects(:receive_data).with do |n|
65
65
  EM.stop
66
66
  n.kind_of?(Stanza::Message) && @stream.ready?.must_equal(true)
67
67
  end
data/spec/spec_helper.rb CHANGED
@@ -1,4 +1,7 @@
1
- require File.expand_path(File.join(File.dirname(__FILE__), *%w[.. lib blather]))
1
+ $:.unshift File.expand_path(File.join(File.dirname(__FILE__), '..'))
2
+ $:.unshift File.expand_path(File.join(File.dirname(__FILE__), *%w[.. lib]))
3
+
4
+ require 'blather'
2
5
  require 'rubygems'
3
6
  require 'minitest/spec'
4
7
  require 'mocha'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sprsquish-blather
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeff Smick
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-05-12 00:00:00 -07:00
12
+ date: 2009-05-14 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -30,7 +30,7 @@ dependencies:
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 1.1.3
33
+ version: 1.1.2
34
34
  version:
35
35
  description:
36
36
  email: sprsquish@gmail.com
@@ -44,6 +44,7 @@ extra_rdoc_files:
44
44
  files:
45
45
  - examples/drb_client.rb
46
46
  - examples/echo.rb
47
+ - examples/ping_pong.rb
47
48
  - examples/print_heirarchy.rb
48
49
  - ext/extconf.rb
49
50
  - ext/push_parser.c
@@ -110,6 +111,8 @@ signing_key:
110
111
  specification_version: 3
111
112
  summary: An evented XMPP library written on EventMachine and libxml-ruby
112
113
  test_files:
114
+ - spec/blather/client/client_spec.rb
115
+ - spec/blather/client/dsl_spec.rb
113
116
  - spec/blather/core_ext/libxml_spec.rb
114
117
  - spec/blather/errors/sasl_error_spec.rb
115
118
  - spec/blather/errors/stanza_error_spec.rb