tkellem 0.9.0.beta3 → 0.9.0.beta4
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/tkellem/bouncer.rb +14 -30
- data/lib/tkellem/bouncer_connection.rb +58 -42
- data/lib/tkellem/daemon.rb +18 -7
- data/lib/tkellem/irc_server.rb +124 -0
- data/lib/tkellem/migrations/001_init_db.rb +22 -39
- data/lib/tkellem/migrations/002_at_connect_columns.rb +4 -21
- data/lib/tkellem/migrations/003_settings.rb +9 -18
- data/lib/tkellem/models/host.rb +2 -2
- data/lib/tkellem/models/listen_address.rb +1 -13
- data/lib/tkellem/models/network.rb +7 -11
- data/lib/tkellem/models/network_user.rb +6 -22
- data/lib/tkellem/models/setting.rb +3 -3
- data/lib/tkellem/models/user.rb +6 -13
- data/lib/tkellem/plugins/backlog.rb +31 -43
- data/lib/tkellem/socket_server.rb +7 -15
- data/lib/tkellem/tkellem_bot.rb +19 -11
- data/lib/tkellem/tkellem_server.rb +70 -77
- data/lib/tkellem/version.rb +1 -1
- data/lib/tkellem.rb +10 -1
- data/spec/bouncer_connection_spec.rb +37 -0
- data/spec/irc_server_spec.rb +145 -0
- data/spec/spec_helper.rb +17 -4
- data/tkellem.gemspec +5 -6
- metadata +31 -43
- data/lib/tkellem/celluloid_tools.rb +0 -131
@@ -1,13 +1,13 @@
|
|
1
1
|
module Tkellem
|
2
2
|
|
3
|
-
class Setting <
|
3
|
+
class Setting < ActiveRecord::Base
|
4
4
|
def self.get(setting_name)
|
5
|
-
setting =
|
5
|
+
setting = first(:conditions => { :name => setting_name })
|
6
6
|
setting.try(:value)
|
7
7
|
end
|
8
8
|
|
9
9
|
def self.set(setting_name, new_value)
|
10
|
-
setting =
|
10
|
+
setting = first(:conditions => { :name => setting_name })
|
11
11
|
setting.try(:update_attributes, :value => new_value.to_s, :unchanged => false)
|
12
12
|
setting
|
13
13
|
end
|
data/lib/tkellem/models/user.rb
CHANGED
@@ -1,20 +1,13 @@
|
|
1
1
|
module Tkellem
|
2
2
|
|
3
|
-
class User <
|
4
|
-
|
5
|
-
|
6
|
-
one_to_many :network_users, :dependent => :destroy
|
7
|
-
one_to_many :networks, :dependent => :destroy
|
3
|
+
class User < ActiveRecord::Base
|
4
|
+
has_many :network_users, :dependent => :destroy
|
5
|
+
has_many :networks, :dependent => :destroy
|
8
6
|
|
9
7
|
validates_presence_of :username
|
10
8
|
validates_uniqueness_of :username
|
11
9
|
validates_presence_of :role, :in => %w(user admin)
|
12
10
|
|
13
|
-
def before_validation
|
14
|
-
self.role ||= 'user'
|
15
|
-
super
|
16
|
-
end
|
17
|
-
|
18
11
|
# pluggable authentication -- add your own block, which takes |username, password|
|
19
12
|
# parameters. Return a User object if authentication succeeded, or a
|
20
13
|
# false/nil value if auth failed. You can create the user on-the-fly if
|
@@ -25,7 +18,7 @@ class User < Sequel::Model
|
|
25
18
|
# default database-based authentication
|
26
19
|
# TODO: proper password hashing
|
27
20
|
self.authentication_methods << proc do |username, password|
|
28
|
-
user =
|
21
|
+
user = find_by_username(username)
|
29
22
|
user && user.valid_password?(password) && user
|
30
23
|
end
|
31
24
|
|
@@ -38,7 +31,7 @@ class User < Sequel::Model
|
|
38
31
|
end
|
39
32
|
|
40
33
|
def username=(val)
|
41
|
-
|
34
|
+
write_attribute(:username, val.try(:downcase))
|
42
35
|
end
|
43
36
|
|
44
37
|
def name
|
@@ -51,7 +44,7 @@ class User < Sequel::Model
|
|
51
44
|
end
|
52
45
|
|
53
46
|
def password=(password)
|
54
|
-
|
47
|
+
write_attribute(:password, password ? OpenSSL::Digest::SHA1.hexdigest(password) : nil)
|
55
48
|
end
|
56
49
|
|
57
50
|
def admin?
|
@@ -15,11 +15,9 @@ module Tkellem
|
|
15
15
|
# different backlog implementation. Right now, it's always loaded though.
|
16
16
|
class Backlog
|
17
17
|
include Tkellem::EasyLogger
|
18
|
-
include Celluloid
|
19
|
-
|
20
|
-
cattr_accessor :replay_pool
|
21
18
|
|
22
19
|
Bouncer.add_plugin(self)
|
20
|
+
cattr_accessor :instances
|
23
21
|
|
24
22
|
def self.get_instance(bouncer)
|
25
23
|
bouncer.data(self)[:instance] ||= self.new(bouncer)
|
@@ -59,7 +57,7 @@ class Backlog
|
|
59
57
|
# open stream in append-only mode
|
60
58
|
return @streams[ctx] if @streams[ctx]
|
61
59
|
stream = @streams[ctx] = File.open(stream_filename(ctx), 'ab')
|
62
|
-
stream.seek(0,
|
60
|
+
stream.seek(0, IO::SEEK_END)
|
63
61
|
@starting_pos[ctx] = stream.pos
|
64
62
|
stream
|
65
63
|
end
|
@@ -118,46 +116,38 @@ class Backlog
|
|
118
116
|
|
119
117
|
def send_backlog(conn, device)
|
120
118
|
device.each do |ctx_name, pos|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
while line = stream.gets
|
136
|
-
timestamp, msg = parse_line(line, ctx_name)
|
137
|
-
next unless msg
|
138
|
-
privmsg = msg.args.first[0] != '#'[0]
|
139
|
-
if msg.prefix
|
140
|
-
# to this user
|
141
|
-
if privmsg
|
142
|
-
msg.args[0] = bouncer.nick
|
119
|
+
stream = File.open(stream_filename(ctx_name), 'rb')
|
120
|
+
stream.seek(pos)
|
121
|
+
|
122
|
+
while line = stream.gets
|
123
|
+
timestamp, msg = parse_line(line, ctx_name)
|
124
|
+
next unless msg
|
125
|
+
privmsg = msg.args.first[0] != '#'[0]
|
126
|
+
if msg.prefix
|
127
|
+
# to this user
|
128
|
+
if privmsg
|
129
|
+
msg.args[0] = @bouncer.nick
|
130
|
+
else
|
131
|
+
# do nothing, it's good to send
|
132
|
+
end
|
143
133
|
else
|
144
|
-
#
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
# it's a room, we can just replay
|
157
|
-
msg.prefix = bouncer.nick
|
134
|
+
# from this user
|
135
|
+
if privmsg
|
136
|
+
# a one-on-one chat -- every client i've seen doesn't know how to
|
137
|
+
# display messages from themselves here, so we fake it by just
|
138
|
+
# adding an arrow and pretending the other user said it. shame.
|
139
|
+
msg.prefix = msg.args.first
|
140
|
+
msg.args[0] = @bouncer.nick
|
141
|
+
msg.args[-1] = "-> #{msg.args.last}"
|
142
|
+
else
|
143
|
+
# it's a room, we can just replay
|
144
|
+
msg.prefix = @bouncer.nick
|
145
|
+
end
|
158
146
|
end
|
147
|
+
conn.send_msg(msg.with_timestamp(timestamp))
|
159
148
|
end
|
160
|
-
|
149
|
+
|
150
|
+
device[ctx_name] = get_stream(ctx_name).pos
|
161
151
|
end
|
162
152
|
end
|
163
153
|
|
@@ -182,6 +172,4 @@ class BacklogReplay
|
|
182
172
|
end
|
183
173
|
end
|
184
174
|
|
185
|
-
Backlog.replay_pool = BacklogReplay.pool(size: Celluloid.cores * 2)
|
186
|
-
|
187
175
|
end
|
@@ -1,30 +1,26 @@
|
|
1
|
-
require '
|
1
|
+
require 'eventmachine'
|
2
2
|
|
3
3
|
require 'tkellem/tkellem_bot'
|
4
4
|
|
5
5
|
module Tkellem
|
6
6
|
|
7
7
|
# listens on the unix domain socket and executes admin commands
|
8
|
-
|
9
|
-
|
10
|
-
include Celluloid::IO
|
8
|
+
module SocketServer
|
9
|
+
include EM::Protocols::LineText2
|
11
10
|
include Tkellem::EasyLogger
|
12
|
-
include Tkellem::CelluloidTools::LineReader
|
13
11
|
|
14
12
|
def log_name
|
15
13
|
"admin"
|
16
14
|
end
|
17
15
|
|
18
|
-
def
|
19
|
-
|
20
|
-
@delimiter = "\n"
|
21
|
-
async.run
|
16
|
+
def post_init
|
17
|
+
set_delimiter "\n"
|
22
18
|
end
|
23
19
|
|
24
20
|
def receive_line(line)
|
25
21
|
trace "admin socket: #{line}"
|
26
|
-
TkellemBot.run_command(line, nil, nil) do |
|
27
|
-
send_data("#{
|
22
|
+
TkellemBot.run_command(line, nil, nil) do |output|
|
23
|
+
send_data("#{output}\n")
|
28
24
|
end
|
29
25
|
send_data("\0\n")
|
30
26
|
rescue => e
|
@@ -32,10 +28,6 @@ class SocketServer
|
|
32
28
|
e.backtrace.each { |l| send_data("#{l}\n") }
|
33
29
|
send_data("\0\n")
|
34
30
|
end
|
35
|
-
|
36
|
-
def send_data(dat)
|
37
|
-
@socket.write(dat)
|
38
|
-
end
|
39
31
|
end
|
40
32
|
|
41
33
|
end
|
data/lib/tkellem/tkellem_bot.rb
CHANGED
@@ -156,7 +156,7 @@ class TkellemBot
|
|
156
156
|
end
|
157
157
|
|
158
158
|
def modify
|
159
|
-
instance = model.first(find_attributes)
|
159
|
+
instance = model.first(:conditions => find_attributes)
|
160
160
|
new_record = false
|
161
161
|
if instance
|
162
162
|
instance.attributes = attributes
|
@@ -181,7 +181,7 @@ class TkellemBot
|
|
181
181
|
end
|
182
182
|
|
183
183
|
def remove
|
184
|
-
instance = model.first(find_attributes)
|
184
|
+
instance = model.first(:conditions => find_attributes)
|
185
185
|
if instance
|
186
186
|
instance.destroy
|
187
187
|
respond "Removed #{show(instance)}"
|
@@ -258,7 +258,7 @@ class TkellemBot
|
|
258
258
|
|
259
259
|
if opts['username']
|
260
260
|
if Command.admin_user?(user)
|
261
|
-
user = User.
|
261
|
+
user = User.first(:conditions => { :username => opts['username'] })
|
262
262
|
else
|
263
263
|
raise Command::ArgumentError, "Only admins can change other passwords"
|
264
264
|
end
|
@@ -303,7 +303,7 @@ class TkellemBot
|
|
303
303
|
|
304
304
|
def execute
|
305
305
|
if opts['network'].present? # only settable by admins
|
306
|
-
target = Network.
|
306
|
+
target = Network.first(:conditions => ["name = ? AND user_id IS NULL", opts['network'].downcase])
|
307
307
|
else
|
308
308
|
target = network_user
|
309
309
|
end
|
@@ -339,7 +339,7 @@ class TkellemBot
|
|
339
339
|
admin_option('public', '--public', "Create new public network. Once created, public/private status can't be modified.")
|
340
340
|
|
341
341
|
def list
|
342
|
-
public_networks = Network.
|
342
|
+
public_networks = Network.all(:conditions => 'user_id IS NULL')
|
343
343
|
user_networks = user.try(:reload).try(:networks) || []
|
344
344
|
if user_networks.present? && public_networks.present?
|
345
345
|
r "Public networks are prefixed with [P], user-specific networks with [U]."
|
@@ -355,14 +355,15 @@ class TkellemBot
|
|
355
355
|
end
|
356
356
|
|
357
357
|
def execute
|
358
|
+
# TODO: this got gross
|
358
359
|
if args.empty? && !opts['remove']
|
359
360
|
list
|
360
361
|
return
|
361
362
|
end
|
362
363
|
|
363
364
|
if opts['network'].present?
|
364
|
-
target = Network.
|
365
|
-
target ||= Network.
|
365
|
+
target = Network.first(:conditions => ["name = ? AND user_id = ?", opts['network'].downcase, user.try(:id)])
|
366
|
+
target ||= Network.first(:conditions => ["name = ? AND user_id IS NULL", opts['network'].downcase]) if self.class.admin_user?(user)
|
366
367
|
else
|
367
368
|
target = network_user.try(:network)
|
368
369
|
if target && target.public? && !self.class.admin_user?(user)
|
@@ -378,7 +379,7 @@ class TkellemBot
|
|
378
379
|
raise(Command::ArgumentError, "No network found") unless target
|
379
380
|
raise(Command::ArgumentError, "You must explicitly specify the network to remove") unless opts['network']
|
380
381
|
if uri
|
381
|
-
target.hosts.
|
382
|
+
target.hosts.first(:conditions => addr_args).try(:destroy)
|
382
383
|
respond " #{show(target)}"
|
383
384
|
else
|
384
385
|
target.destroy
|
@@ -396,9 +397,16 @@ class TkellemBot
|
|
396
397
|
end
|
397
398
|
end
|
398
399
|
|
399
|
-
|
400
|
-
|
401
|
-
|
400
|
+
target.attributes = { :hosts_attributes => [addr_args] }
|
401
|
+
target.save
|
402
|
+
if target.errors.any?
|
403
|
+
respond "Error:"
|
404
|
+
target.errors.full_messages.each { |m| respond " #{m}" }
|
405
|
+
respond " #{show(target)}"
|
406
|
+
else
|
407
|
+
respond("updated:")
|
408
|
+
respond " #{show(target)}"
|
409
|
+
end
|
402
410
|
end
|
403
411
|
end
|
404
412
|
end
|
@@ -1,62 +1,47 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
3
|
-
require '
|
1
|
+
require 'eventmachine'
|
2
|
+
require 'active_record'
|
3
|
+
require 'rails/observers/activerecord/active_record'
|
4
4
|
|
5
|
-
require 'tkellem/bouncer'
|
6
5
|
require 'tkellem/bouncer_connection'
|
7
|
-
require 'tkellem/
|
6
|
+
require 'tkellem/bouncer'
|
7
|
+
|
8
|
+
require 'tkellem/models/host'
|
9
|
+
require 'tkellem/models/listen_address'
|
10
|
+
require 'tkellem/models/network'
|
11
|
+
require 'tkellem/models/network_user'
|
12
|
+
require 'tkellem/models/setting'
|
13
|
+
require 'tkellem/models/user'
|
8
14
|
|
9
15
|
require 'tkellem/plugins/backlog'
|
10
|
-
|
16
|
+
require 'tkellem/plugins/push_service'
|
11
17
|
|
12
18
|
module Tkellem
|
13
19
|
|
14
20
|
class TkellemServer
|
15
|
-
include Celluloid
|
16
21
|
include Tkellem::EasyLogger
|
17
22
|
|
18
|
-
attr_reader :bouncers
|
19
|
-
|
20
|
-
def self.initialize_database(path)
|
21
|
-
Sequel.extension :migration
|
22
|
-
db = Sequel.connect({
|
23
|
-
:adapter => 'sqlite',
|
24
|
-
:database => path,
|
25
|
-
})
|
26
|
-
migrations_path = File.expand_path("../migrations", __FILE__)
|
27
|
-
Sequel::Migrator.apply(db, migrations_path)
|
28
|
-
|
29
|
-
Sequel::Model.raise_on_save_failure = true
|
30
|
-
# Can't load the models until we've connected to the database and migrated
|
31
|
-
require 'tkellem/models/host'
|
32
|
-
require 'tkellem/models/listen_address'
|
33
|
-
require 'tkellem/models/network'
|
34
|
-
require 'tkellem/models/network_user'
|
35
|
-
require 'tkellem/models/setting'
|
36
|
-
require 'tkellem/models/user'
|
37
|
-
|
38
|
-
db
|
39
|
-
end
|
23
|
+
attr_reader :bouncers
|
40
24
|
|
41
|
-
def initialize
|
42
|
-
Celluloid.logger = Tkellem::EasyLogger.logger
|
43
|
-
@options = options
|
25
|
+
def initialize
|
44
26
|
@listeners = {}
|
45
|
-
@bouncers =
|
27
|
+
@bouncers = {}
|
46
28
|
$tkellem_server = self
|
47
29
|
|
48
|
-
|
49
|
-
|
30
|
+
unless ActiveRecord::Base.connected?
|
31
|
+
ActiveRecord::Base.establish_connection({
|
32
|
+
:adapter => 'sqlite3',
|
33
|
+
:database => File.expand_path("~/.tkellem/tkellem.sqlite3"),
|
34
|
+
})
|
35
|
+
ActiveRecord::Migrator.migrate(File.expand_path("../migrations", __FILE__), nil)
|
36
|
+
end
|
50
37
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
38
|
+
ListenAddress.all.each { |a| listen(a) }
|
39
|
+
NetworkUser.find_each { |nu| add_bouncer(nu) }
|
40
|
+
Observer.forward_to << self
|
41
|
+
end
|
55
42
|
|
56
|
-
|
57
|
-
|
58
|
-
rescue Interrupt
|
59
|
-
end
|
43
|
+
def stop
|
44
|
+
Observer.forward_to.delete(self)
|
60
45
|
end
|
61
46
|
|
62
47
|
# callbacks for AR observer events
|
@@ -73,75 +58,83 @@ class TkellemServer
|
|
73
58
|
case obj
|
74
59
|
when ListenAddress
|
75
60
|
stop_listening(obj)
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
def start_unix_server
|
80
|
-
# This file relies on the models being loaded
|
81
|
-
# TODO: this is gross
|
82
|
-
require 'tkellem/socket_server'
|
83
|
-
CelluloidTools::UnixListener.start(socket_file) do |socket|
|
84
|
-
SocketServer.new(socket)
|
61
|
+
when NetworkUser
|
62
|
+
stop_bouncer(obj)
|
85
63
|
end
|
86
64
|
end
|
87
65
|
|
88
66
|
def listen(listen_address)
|
89
67
|
info "Listening on #{listen_address}"
|
90
68
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
listener = server_class.start(listen_address.address,
|
98
|
-
listen_address.port) do |socket|
|
99
|
-
BouncerConnection.new(self, socket).async.run
|
100
|
-
end
|
101
|
-
|
102
|
-
@listeners[listen_address.id] = listener
|
69
|
+
@listeners[listen_address.id] = EM.start_server(listen_address.address,
|
70
|
+
listen_address.port,
|
71
|
+
BouncerConnection,
|
72
|
+
self,
|
73
|
+
listen_address.ssl)
|
103
74
|
end
|
104
75
|
|
105
76
|
def stop_listening(listen_address)
|
106
77
|
listener = @listeners[listen_address.id]
|
107
78
|
return unless listener
|
108
|
-
listener
|
79
|
+
EM.stop_server(listener)
|
109
80
|
info "No longer listening on #{listen_address}"
|
110
81
|
end
|
111
82
|
|
112
83
|
def add_bouncer(network_user)
|
113
84
|
unless network_user.user && network_user.network
|
114
|
-
info "Terminating orphan network user #{network_user}"
|
85
|
+
info "Terminating orphan network user #{network_user.inspect}"
|
115
86
|
network_user.destroy
|
116
87
|
return
|
117
88
|
end
|
118
|
-
|
119
|
-
|
120
|
-
@bouncers.
|
89
|
+
|
90
|
+
key = bouncers_key(network_user)
|
91
|
+
raise("bouncer already exists: #{key}") if @bouncers.include?(key)
|
92
|
+
@bouncers[key] = Bouncer.new(bouncer)
|
93
|
+
end
|
94
|
+
|
95
|
+
def stop_bouncer(obj)
|
96
|
+
key = bouncers_key(network_user)
|
97
|
+
bouncer = @bouncers.delete(key)
|
98
|
+
if bouncer
|
99
|
+
bouncer.kill!
|
100
|
+
end
|
121
101
|
end
|
122
102
|
|
123
103
|
def find_bouncer(user, network_name)
|
124
104
|
key = [user.id, network_name]
|
125
|
-
bouncer = @bouncers
|
105
|
+
bouncer = @bouncers[key]
|
126
106
|
if !bouncer
|
127
107
|
# find the public network with this name, and attempt to auto-add this user to it
|
128
|
-
network = Network.first({ :user_id => nil, :name => network_name })
|
108
|
+
network = Network.first(:conditions => { :user_id => nil, :name => network_name })
|
129
109
|
if network
|
130
|
-
NetworkUser.create(:user => user, :network => network)
|
110
|
+
NetworkUser.create!(:user => user, :network => network)
|
131
111
|
# AR callback should create the bouncer in sync
|
132
|
-
bouncer = @bouncers
|
112
|
+
bouncer = @bouncers[key]
|
133
113
|
end
|
134
114
|
end
|
135
115
|
bouncer
|
136
116
|
end
|
137
117
|
|
138
|
-
def
|
139
|
-
|
118
|
+
def bouncers_key(network_user)
|
119
|
+
[network_user.user_id, network_user.network.name]
|
140
120
|
end
|
141
121
|
|
142
|
-
|
143
|
-
|
122
|
+
class Observer < ActiveRecord::Observer
|
123
|
+
observe 'Tkellem::ListenAddress', 'Tkellem::NetworkUser'
|
124
|
+
cattr_accessor :forward_to
|
125
|
+
self.forward_to = []
|
126
|
+
|
127
|
+
def after_create(obj)
|
128
|
+
forward_to.each { |f| f.after_create(obj) }
|
129
|
+
end
|
130
|
+
|
131
|
+
def after_destroy(obj)
|
132
|
+
forward_to.each { |f| f.after_destroy(obj) }
|
133
|
+
end
|
144
134
|
end
|
135
|
+
|
136
|
+
ActiveRecord::Base.observers = Observer
|
137
|
+
ActiveRecord::Base.instantiate_observers
|
145
138
|
end
|
146
139
|
|
147
140
|
end
|
data/lib/tkellem/version.rb
CHANGED
data/lib/tkellem.rb
CHANGED
@@ -21,7 +21,7 @@ module Tkellem
|
|
21
21
|
@trace = val
|
22
22
|
end
|
23
23
|
def self.trace
|
24
|
-
@trace || @trace =
|
24
|
+
@trace || @trace = false
|
25
25
|
end
|
26
26
|
|
27
27
|
def log_name
|
@@ -32,6 +32,15 @@ module Tkellem
|
|
32
32
|
puts("TRACE: #{log_name}: #{msg}") if EasyLogger.trace
|
33
33
|
end
|
34
34
|
|
35
|
+
def failsafe(event)
|
36
|
+
yield
|
37
|
+
rescue => e
|
38
|
+
# if the failsafe rescue fails, we're in a really bad state and should probably just die
|
39
|
+
self.error "exception while handling #{event}"
|
40
|
+
self.error e.to_s
|
41
|
+
(e.backtrace || []).each { |line| self.error line }
|
42
|
+
end
|
43
|
+
|
35
44
|
::Logger::Severity.constants.each do |level|
|
36
45
|
next if level == "UNKNOWN"
|
37
46
|
module_eval(<<-EVAL, __FILE__, __LINE__)
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'tkellem/bouncer_connection'
|
3
|
+
|
4
|
+
include Tkellem
|
5
|
+
|
6
|
+
describe BouncerConnection, "connect" do
|
7
|
+
before do
|
8
|
+
@u = User.create(:username => 'speccer')
|
9
|
+
@u.password = 'test123'
|
10
|
+
@u.save
|
11
|
+
@tk = mock(TkellemServer)
|
12
|
+
@b = mock(Bouncer)
|
13
|
+
@bc = em(BouncerConnection).new(@tk, false)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should ignore blank lines" do
|
17
|
+
@bc.should_receive(:error!).never
|
18
|
+
@bc.receive_line("")
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should connect after receiving credentials" do
|
22
|
+
@tk.should_receive(:find_bouncer).with(@u, 'testhost').and_return(@b)
|
23
|
+
@bc.receive_line("NICK speccer")
|
24
|
+
@bc.receive_line("PASS test123")
|
25
|
+
@b.should_receive(:connect_client).with(@bc)
|
26
|
+
@bc.receive_line("USER speccer@testhost")
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should connect when receiving user before pass" do
|
30
|
+
@tk.should_receive(:find_bouncer).with(@u, 'testhost').and_return(@b)
|
31
|
+
@bc.receive_line("USER speccer@testhost")
|
32
|
+
@bc.receive_line("PASS test123")
|
33
|
+
@b.should_receive(:connect_client).with(@bc)
|
34
|
+
@bc.receive_line("NICK speccer")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|