sprsquish-blather 0.3.0 → 0.3.1

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