fargo 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,5 @@
1
+ require 'active_support/core_ext/numeric/time'
2
+
1
3
  module Fargo
2
4
  module Supports
3
5
  module NickList
@@ -33,9 +35,14 @@ module Fargo
33
35
  @nick_info[nick]
34
36
  end
35
37
 
38
+ # This query must be up to date so remove any cached information we have
39
+ # about the nick so we can get a fresh copy
36
40
  def nick_has_slot? nick
37
- # This query must be up to date so remove any cached information we have
38
- # about the nick so we can get a fresh copy
41
+ if @search_result_slots[nick] &&
42
+ @search_result_slots[nick][:updated_at] + 10.minutes > Time.now
43
+ return @search_result_slots[nick][:slots] > 0
44
+ end
45
+
39
46
  @nick_info.try :delete, nick
40
47
  info = info nick
41
48
 
@@ -54,23 +61,29 @@ module Fargo
54
61
  def initialize_nick_lists
55
62
  @nicks = []
56
63
  @nick_info = Hash.new{ |h, k| h[k] = {} }
64
+ @search_result_slots = {}
57
65
 
58
66
  channel.subscribe do |type, map|
59
67
  case type
60
68
  when :hello
61
- @nicks << map[:who] unless @nicks.include? map[:who]
69
+ @nicks << map[:nick] unless @nicks.include? map[:nick]
62
70
  when :myinfo
63
71
  @nick_info[map[:nick]] = map
64
72
  when :nick_list
65
73
  @nicks = map[:nicks]
66
74
  when :quit
67
- @nicks.delete map[:who]
68
- @nick_info.delete map[:who]
75
+ @nicks.delete map[:nick]
76
+ @nick_info.delete map[:nick]
69
77
  when :hub_disconnected
70
78
  @nicks.clear
71
79
  @nick_info.clear
72
80
  when :userip
73
81
  map[:users].each_pair{ |nick, ip| @nick_info[nick][:ip] = ip }
82
+ when :search_result
83
+ @search_result_slots[map[:nick]] = {
84
+ :slots => map[:open_slots],
85
+ :updated_at => Time.now
86
+ }
74
87
  end
75
88
  end
76
89
  end
@@ -10,7 +10,7 @@ module Fargo
10
10
  def connect_with nick
11
11
  @connection_timeouts[nick] = EventMachine::Timer.new(10) do
12
12
  @connection_timeouts.delete(nick)
13
- channel.push [:connection_timeout, {:nick => nick}]
13
+ channel << [:connection_timeout, {:nick => nick}]
14
14
  end
15
15
 
16
16
  if config.passive
@@ -46,7 +46,7 @@ module Fargo
46
46
  channel.subscribe do |type, hash|
47
47
  if type == :hub_disconnected
48
48
  nicks_connected_with.each{ |n| disconnect_from n }
49
- elsif type == :download_disconnected
49
+ elsif type == :peer_disconnected
50
50
  @connection_cache.delete hash[:nick]
51
51
  elsif type == :download_opened
52
52
  @connection_timeouts.delete(hash[:nick]).try(:cancel)
@@ -2,9 +2,10 @@ require 'bzip2'
2
2
  require 'libxml'
3
3
 
4
4
  module Fargo
5
+ class Listing < Struct.new(:tth, :size, :name, :nick, :mtime, :root); end
6
+
5
7
  module Supports
6
- module FileList
7
- class Listing < Struct.new(:tth, :size, :name, :nick); end
8
+ module RemoteFileList
8
9
 
9
10
  extend ActiveSupport::Concern
10
11
 
@@ -61,7 +62,7 @@ module Fargo
61
62
  parse_file_list list, nick
62
63
  end
63
64
 
64
- private
65
+ protected
65
66
 
66
67
  def parse_file_list file, nick
67
68
  if file && File.exists?(file)
@@ -95,8 +96,6 @@ module Fargo
95
96
  list
96
97
  end
97
98
 
98
- protected
99
-
100
99
  def initialize_file_lists
101
100
  @file_list = {}
102
101
  @getting_file_list = {}
@@ -7,15 +7,6 @@ module Fargo
7
7
  set_callback :initialization, :after, :initialize_search_caches
8
8
  end
9
9
 
10
- # see parser#@@search for what's passed in
11
- #
12
- # searches this client's files based on those options and returns an array
13
- # of SearchResult(s)
14
- def search_files options
15
- # TODO: implement me
16
- []
17
- end
18
-
19
10
  def search_hub query
20
11
  raise ConnectionError.new('Not connected Yet!') unless connected?
21
12
 
@@ -55,8 +46,8 @@ module Fargo
55
46
  protected
56
47
 
57
48
  def normalize search
58
- unless search.is_a? Fargo::Search
59
- search = Fargo::Search.new :query => search
49
+ unless search.is_a? Search
50
+ search = Search.new :query => search
60
51
  end
61
52
 
62
53
  search
@@ -68,8 +59,10 @@ module Fargo
68
59
 
69
60
  channel.subscribe do |type, map|
70
61
  if type == :search_result
62
+ map[:tth] = map[:hub] if map[:hub] =~ /^TTH:/
63
+
71
64
  @searches.keys.each do |search|
72
- if @search_objects[search].matches_result?(map)
65
+ if @search_objects[search].matches?(map)
73
66
  @searches[search] << map
74
67
  end
75
68
  end
@@ -1,14 +1,33 @@
1
+ require 'active_support/core_ext/module/synchronization'
2
+
1
3
  module Fargo
2
4
  module Supports
3
5
  module Uploads
4
- # TODO: write this module...
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ set_callback :initialization, :after, :initialize_upload_locks
10
+ end
11
+
12
+ def open_upload_slots
13
+ [config.upload_slots - @taken_slots, 0].max
14
+ end
5
15
 
6
- def open_slots
7
- 0
16
+ def take_slot!
17
+ @taken_slots += 1
8
18
  end
9
19
 
10
- def share_size
11
- 5368709121 # 5 GB + 1 byte
20
+ def release_slot!
21
+ @taken_slots -= 1
22
+ end
23
+
24
+ synchronize :take_slot!, :release_slot!, :with => :@upload_slot_lock
25
+
26
+ protected
27
+
28
+ def initialize_upload_locks
29
+ @upload_slot_lock = Mutex.new
30
+ @taken_slots = 0
12
31
  end
13
32
 
14
33
  end
data/lib/fargo/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Fargo
2
- VERSION = '0.2.0'
2
+ VERSION = '0.3.0'
3
3
  end
data/lib/fargo.rb CHANGED
@@ -2,6 +2,7 @@ require 'fileutils'
2
2
  require 'eventmachine'
3
3
  require 'active_support/dependencies/autoload'
4
4
  require 'active_support/core_ext/module/attribute_accessors'
5
+ require 'active_support/core_ext/module/delegation'
5
6
  require 'active_support/buffered_logger'
6
7
  require 'active_support/concern'
7
8
  require 'active_support/configurable'
@@ -11,7 +12,7 @@ module Fargo
11
12
 
12
13
  class ConnectionException < RuntimeError; end
13
14
 
14
- mattr_accessor:logger
15
+ mattr_accessor :logger
15
16
  self.logger = ActiveSupport::BufferedLogger.new STDOUT
16
17
 
17
18
  autoload :Utils
@@ -21,6 +22,8 @@ module Fargo
21
22
  autoload :SearchResult
22
23
  autoload :VERSION
23
24
  autoload :TTH
25
+ autoload :Listing, 'fargo/supports/remote_file_list'
26
+ autoload :Download, 'fargo/supports/downloads'
24
27
 
25
28
  module Supports
26
29
  extend ActiveSupport::Autoload
@@ -32,15 +35,21 @@ module Fargo
32
35
  autoload :Downloads
33
36
  autoload :Persistence
34
37
  autoload :Timeout
35
- autoload :FileList
38
+ autoload :RemoteFileList
39
+ autoload :LocalFileList
36
40
  end
37
41
 
38
42
  module Protocol
39
43
  extend ActiveSupport::Autoload
40
44
 
41
45
  autoload :DC
42
- autoload :Download
46
+ autoload :Peer
47
+ autoload :PeerDownload
48
+ autoload :PeerUpload
43
49
  autoload :Hub
44
50
  end
45
51
 
52
+ class << self
53
+ delegate :config, :configure, :to => Client
54
+ end
46
55
  end
@@ -0,0 +1,198 @@
1
+ require 'spec_helper'
2
+
3
+ describe Fargo::Parser do
4
+
5
+ let(:helper) { helper_object described_class }
6
+
7
+ it "correctly parses info strings" do
8
+ helper.parse_message(
9
+ "$MyINFO $ALL notdan <++ V:0.75,M:A,H:1/0/0,S:1,Dt:1.2.6/W>$ $Morewood A-D\001$$90932631814$"
10
+ ).should == {
11
+ :type => :myinfo,
12
+ :nick => 'notdan',
13
+ :speed => 'Morewood A-D',
14
+ :email => '',
15
+ :sharesize => 90932631814,
16
+ :interest => '<++ V:0.75,M:A,H:1/0/0,S:1,Dt:1.2.6/W>'
17
+ }
18
+ end
19
+
20
+ it "correctly parses hello commands" do
21
+ helper.parse_message("$Hello notdan").should == {
22
+ :type => :hello,
23
+ :nick => 'notdan'
24
+ }
25
+ end
26
+
27
+ it "correctly parses the quit command" do
28
+ helper.parse_message("$Quit ghostafaria").should == {
29
+ :type => :quit,
30
+ :nick => 'ghostafaria'
31
+ }
32
+ end
33
+
34
+ it "correctly parses search commands" do
35
+ helper.parse_message(
36
+ "$Search 128.237.66.53:36565 F?T?0?1?west$wing").should == {
37
+ :type => :search,
38
+ :restrict_size => false,
39
+ :is_minimum_size => false,
40
+ :pattern => 'west$wing',
41
+ :filetype => 1,
42
+ :address => '128.237.66.53',
43
+ :port => 36565,
44
+ :size => 0
45
+ }
46
+ end
47
+
48
+ describe 'search results' do
49
+ context 'active searches' do
50
+ it "correctly parses search results which are files" do
51
+ helper.parse_message(
52
+ "$SR notas 02 - Heroes.mp3\0053001347 "+
53
+ "2/4\005TTH:VZWDTXHBSBSW7UMKCFHNEHXWCSWWYGU4KTIL76A " +
54
+ "(127.0.0.1:7314)").should == {
55
+ :type => :search_result,
56
+ :open_slots => 2,
57
+ :slots => 4,
58
+ :hub => 'TTH:VZWDTXHBSBSW7UMKCFHNEHXWCSWWYGU4KTIL76A',
59
+ :file => '02 - Heroes.mp3',
60
+ :address => '127.0.0.1',
61
+ :port => 7314,
62
+ :size => 3001347,
63
+ :nick => 'notas'
64
+ }
65
+ end
66
+
67
+ it "correctly parses search results which are directories" do
68
+ helper.parse_message(
69
+ "$SR notas Music 2/4\005" +
70
+ "TTH:EAAABGHTP4AAACAAAAAAAAAAACIOWCMZ6N7QAAA (127.0.0.1:7314)"
71
+ ).should == {
72
+ :type => :search_result,
73
+ :open_slots => 2,
74
+ :slots => 4,
75
+ :hub => 'TTH:EAAABGHTP4AAACAAAAAAAAAAACIOWCMZ6N7QAAA',
76
+ :dir => 'Music',
77
+ :address => '127.0.0.1',
78
+ :port => 7314,
79
+ :nick => 'notas'
80
+ }
81
+ end
82
+ end
83
+
84
+ context 'passive searches' do
85
+ it "correctly parses search results which are files" do
86
+ helper.parse_message(
87
+ "$SR notas 02 - Heroes.mp3\0053001347 "+
88
+ "2/4\005TTH:VZWDTXHBSBSW7UMKCFHNEHXWCSWWYGU4KTIL76A " +
89
+ "(127.0.0.1:7314)\005user2").should == {
90
+ :type => :search_result,
91
+ :open_slots => 2,
92
+ :slots => 4,
93
+ :hub => 'TTH:VZWDTXHBSBSW7UMKCFHNEHXWCSWWYGU4KTIL76A',
94
+ :file => '02 - Heroes.mp3',
95
+ :address => '127.0.0.1',
96
+ :port => 7314,
97
+ :size => 3001347,
98
+ :nick => 'notas'
99
+ }
100
+ end
101
+
102
+ it "correctly parses search results which are directories" do
103
+ helper.parse_message(
104
+ "$SR notas Music 2/4\005" +
105
+ "TTH:EAAABGHTP4AAACAAAAAAAAAAACIOWCMZ6N7QAAA (127.0.0.1:7314)\005nick"
106
+ ).should == {
107
+ :type => :search_result,
108
+ :open_slots => 2,
109
+ :slots => 4,
110
+ :hub => 'TTH:EAAABGHTP4AAACAAAAAAAAAAACIOWCMZ6N7QAAA',
111
+ :dir => 'Music',
112
+ :address => '127.0.0.1',
113
+ :port => 7314,
114
+ :nick => 'notas'
115
+ }
116
+ end
117
+ end
118
+ end
119
+
120
+ describe 'the ADC draft' do
121
+ it 'correctly parses the ADCSND command with ZL1' do
122
+ helper.parse_message(
123
+ "$ADCSND file TTH/PPUROLR2WSYTGPLCM3KV4V6LJC36SCTFQJFDJKA 0 1154 ZL1"
124
+ ).should == {
125
+ :type => :adcsnd,
126
+ :offset => 0,
127
+ :file => 'TTH/PPUROLR2WSYTGPLCM3KV4V6LJC36SCTFQJFDJKA',
128
+ :size => 1154,
129
+ :zlib => true,
130
+ :kind => 'file'
131
+ }
132
+ end
133
+
134
+ it 'correctly parses the ADCSND command without ZL1' do
135
+ helper.parse_message(
136
+ "$ADCSND file TTH/PPUROLR2WSYTGPLCM3KV4V6LJC36SCTFQJFDJKA 4 1154"
137
+ ).should == {
138
+ :type => :adcsnd,
139
+ :offset => 4,
140
+ :file => 'TTH/PPUROLR2WSYTGPLCM3KV4V6LJC36SCTFQJFDJKA',
141
+ :size => 1154,
142
+ :zlib => false,
143
+ :kind => 'file'
144
+ }
145
+ end
146
+
147
+ it 'correctly parses the ADCGET command with ZL1' do
148
+ helper.parse_message(
149
+ "$ADCGET file TTH/PPUROLR2WSYTGPLCM3KV4V6LJC36SCTFQJFDJKA 0 1154 ZL1"
150
+ ).should == {
151
+ :type => :adcget,
152
+ :offset => 0,
153
+ :file => 'TTH/PPUROLR2WSYTGPLCM3KV4V6LJC36SCTFQJFDJKA',
154
+ :size => 1154,
155
+ :zlib => true,
156
+ :kind => 'file'
157
+ }
158
+ end
159
+
160
+ it 'correctly parses the ADCGET command without ZL1' do
161
+ helper.parse_message(
162
+ "$ADCGET file TTH/PPUROLR2WSYTGPLCM3KV4V6LJC36SCTFQJFDJKA 4 1154"
163
+ ).should == {
164
+ :type => :adcget,
165
+ :offset => 4,
166
+ :file => 'TTH/PPUROLR2WSYTGPLCM3KV4V6LJC36SCTFQJFDJKA',
167
+ :size => 1154,
168
+ :zlib => false,
169
+ :kind => 'file'
170
+ }
171
+ end
172
+ end
173
+
174
+ it "parses a chat message correctly" do
175
+ helper.parse_message('<fargo> hello there!').should == {
176
+ :type => :chat,
177
+ :from => 'fargo',
178
+ :text => 'hello there!'
179
+ }
180
+ end
181
+
182
+ it "doesn't die on something which it does not match" do
183
+ helper.parse_message('$NoMatch here').should == {
184
+ :type => :mystery,
185
+ :text => '$NoMatch here'
186
+ }
187
+
188
+ helper.parse_message('NoMatch here').should == {
189
+ :type => :mystery,
190
+ :text => 'NoMatch here'
191
+ }
192
+
193
+ helper.parse_message('').should == {
194
+ :type => :mystery,
195
+ :text => ''
196
+ }
197
+ end
198
+ end
@@ -0,0 +1,77 @@
1
+ require 'spec_helper'
2
+
3
+ describe Fargo::Protocol::DC do
4
+ let(:conn) { helper_object described_class }
5
+ let(:client) { Fargo::Client.new }
6
+
7
+ before :each do
8
+ conn.post_init
9
+ end
10
+
11
+ context "receiving data" do
12
+ it "parses the received data and passes it along to :receive_message" do
13
+ conn.should_receive(:receive_message).with(:hello,
14
+ :type => :hello, :nick => 'fargo')
15
+
16
+ conn.receive_data '$Hello fargo|'
17
+ end
18
+
19
+ it "handles receiving two chunks of data at once" do
20
+ conn.should_receive(:receive_message).with(:hello,
21
+ :type => :hello, :nick => 'fargo').ordered
22
+ conn.should_receive(:receive_message).with(:hello,
23
+ :type => :hello, :nick => 'foobar').ordered
24
+
25
+ conn.receive_data '$Hello fargo|$Hello foobar|'
26
+ end
27
+
28
+ it "handles receiving data in partial chunks" do
29
+ conn.should_receive(:receive_message).with(:hello,
30
+ :type => :hello, :nick => 'fargo').ordered
31
+ conn.should_receive(:receive_message).with(:hello,
32
+ :type => :hello, :nick => 'foobar').ordered
33
+
34
+ conn.receive_data '$Hello far'
35
+ conn.receive_data 'go|$Hello'
36
+ conn.receive_data ' foobar|'
37
+ end
38
+
39
+ it "doesn't call unnecessary methods when the data hasn't been received" do
40
+ conn.should_receive(:receive_message).with(:hello,
41
+ :type => :hello, :nick => 'fargo').ordered
42
+
43
+ conn.receive_data '$Hello fargo|$Hello fooba'
44
+ end
45
+
46
+ it "passes all binary data to :receive_data_chunk when necessary" do
47
+ conn.should_receive(:receive_message).with(:hello,
48
+ :type => :hello, :nick => 'fargo'){ |*args|
49
+ conn.stub(:parse_data?).and_return false
50
+ conn.should_receive(:receive_data_chunk).with('asdf')
51
+ }
52
+
53
+ conn.receive_data '$Hello fargo|'
54
+ conn.receive_data 'asdf'
55
+ end
56
+ end
57
+
58
+ context "sending data" do
59
+ it "appends a $ to all commands sent with a pipe at the end" do
60
+ conn.should_receive(:send_data).with('$Foo|')
61
+ conn.send_message 'Foo'
62
+ end
63
+
64
+ it "allows for arguments to be specified as well" do
65
+ conn.should_receive(:send_data).with('$Foo bar baz|')
66
+ conn.send_message 'Foo', 'bar baz'
67
+ end
68
+ end
69
+
70
+ it "alerts the client when it's been disconnected" do
71
+ conn.client = client
72
+ client.channel.should_receive(:<<).with(
73
+ [:dc_disconnected, instance_of(Hash)])
74
+
75
+ conn.unbind
76
+ end
77
+ end
@@ -0,0 +1,155 @@
1
+ require 'spec_helper'
2
+
3
+ describe Fargo::Protocol::Hub do
4
+ let(:conn) {
5
+ helper_object(described_class).tap do |conn|
6
+ conn.client = Fargo::Client.new
7
+ conn.post_init
8
+ end
9
+ }
10
+ include Fargo::TTH
11
+
12
+ context "searches" do
13
+ let(:file1) { Fargo.config.download_dir + '/file12' }
14
+ let(:file2) { Fargo.config.download_dir + '/file23' }
15
+ let(:file3) { Fargo.config.download_dir + '/file34' }
16
+
17
+ before :each do
18
+ File.open(file1, 'w'){ |f| f << 'garbage' }
19
+ File.open(file2, 'w'){ |f| f << 'garbage' }
20
+ File.open(file3, 'w'){ |f| f << 'garbage' }
21
+ conn.client = Fargo::Client.new
22
+ conn.client.stub(:nicks).and_return ['foobar']
23
+ conn.client.share_directory Fargo.config.download_dir
24
+ conn.post_init
25
+ end
26
+
27
+ it "searches results and sends the results to the hub" do
28
+ conn.should_receive(:send_message).with('SR',
29
+ "fargo tmp\\file12\0057 4/4\005TTH:#{file_tth file1} " +
30
+ "(127.0.0.1:7314)\005foobar")
31
+
32
+ query = Fargo::Search.new :query => 'file1'
33
+ conn.receive_data "$Search Hub:foobar #{query}|"
34
+ end
35
+
36
+ it "sends all hits to the hub" do
37
+ conn.should_receive(:send_message).with('SR',
38
+ "fargo tmp\\file23\0057 4/4\005TTH:#{file_tth file2} " +
39
+ "(127.0.0.1:7314)\005foobar")
40
+
41
+ conn.should_receive(:send_message).with('SR',
42
+ "fargo tmp\\file34\0057 4/4\005TTH:#{file_tth file3} " +
43
+ "(127.0.0.1:7314)\005foobar")
44
+
45
+ query = Fargo::Search.new :query => '3'
46
+ conn.receive_data "$Search Hub:foobar #{query}|"
47
+ end
48
+
49
+ it "sends active hits via EventMachine" do
50
+ stub = double 'connection'
51
+ stub.should_receive(:send_datagram).with('$SR ' +
52
+ "fargo tmp\\file12\0057 4/4\005TTH:#{file_tth file1} " +
53
+ "(127.0.0.1:7314)|", '127.0.0.1', 7000).ordered
54
+ stub.should_receive(:close_connection_after_writing).ordered
55
+
56
+ EventMachine.stub(:open_datagram_socket).with('0.0.0.0', 0).
57
+ and_return stub
58
+
59
+ query = Fargo::Search.new :query => 'file1'
60
+ conn.receive_data "$Search 127.0.0.1:7000 #{query}|"
61
+ end
62
+ end
63
+
64
+ context "$RevConnectToMe" do
65
+ it "responds with $ConnectToMe when the client is active" do
66
+ conn.client.config.passive = false
67
+ conn.client.config.address = '1.2.3.4'
68
+ conn.client.config.active_port = 728
69
+ conn.should_receive(:send_data).with('$ConnectToMe foobar 1.2.3.4:728|')
70
+
71
+ conn.receive_data '$RevConnectToMe foobar fargo|'
72
+ end
73
+
74
+ it "responds with $RevConnectToMe when the client is passive" do
75
+ conn.client.config.passive = true
76
+ conn.should_receive(:send_data).with('$RevConnectToMe fargo foobar|')
77
+
78
+ conn.receive_data '$RevConnectToMe foobar fargo|'
79
+ end
80
+ end
81
+
82
+ it "connects to a client when receiving $ConnectToMe" do
83
+ EventMachine.should_receive(:connect).with('1.2.3.4', 500,
84
+ Fargo::Protocol::Peer)
85
+
86
+ conn.receive_data '$ConnectToMe fargo 1.2.3.4:500|'
87
+ end
88
+
89
+ it "proxies specifically unhandled messages to the client" do
90
+ conn.client.channel.should_receive(:<<).with [:nick_list, instance_of(Hash)]
91
+
92
+ conn.receive_data '$NickList a$$b|'
93
+ end
94
+
95
+ it "says that :hub_disconnected when the hub disconnects" do
96
+ conn.client.channel.should_receive(:<<).with(
97
+ [:hub_disconnected, instance_of(Hash)])
98
+
99
+ conn.unbind
100
+ end
101
+
102
+ context "the hub handshake" do
103
+ before :each do
104
+ Fargo.configure do |config|
105
+ config.nick = 'fargo'
106
+ config.speed = 'DSL'
107
+ config.override_share_size = nil
108
+ config.email = 'asdf'
109
+ config.passive = false
110
+ config.upload_slots = 5
111
+ end
112
+ end
113
+
114
+ it "replies with a valid key when the lock is sent" do
115
+ conn.should_receive(:send_data).with "$Key 4\220/%DCN000%/|"
116
+
117
+ conn.receive_data "$Lock FOO Pk=BAR|"
118
+ end
119
+
120
+ it "tries to validate the client's nick when the $HubName is received" do
121
+ conn.should_receive(:send_data).with "$ValidateNick fargo|"
122
+
123
+ conn.receive_data '$HubName foobar|'
124
+ end
125
+
126
+ it "sends $Version, $MyInfo, and $GetNickList upon confirmation of nick" do
127
+ conn.should_receive(:send_data).with('$Version 1,0091|').ordered
128
+ conn.should_receive(:send_data).with(
129
+ "$MyINFO $ALL fargo <fargo V:#{Fargo::VERSION},M:A,H:1/0/0,S:5," +
130
+ "Dt:1.2.6/W>$ $DSL\001$asdf$0$|").ordered
131
+ conn.should_receive(:send_data).with('$GetNickList|').ordered
132
+
133
+ conn.receive_data '$Hello fargo|'
134
+ end
135
+
136
+ it "sends the configured password when requested" do
137
+ conn.client.config.password = 'foobar'
138
+ conn.should_receive(:send_data).with('$MyPass foobar|')
139
+
140
+ conn.receive_data '$GetPass|'
141
+ end
142
+
143
+ it "disconnects when the password was bad" do
144
+ conn.should_receive(:close_connection_after_writing)
145
+
146
+ conn.receive_data '$BadPass|'
147
+ end
148
+
149
+ it "disconnects when the hub is full" do
150
+ conn.should_receive(:close_connection_after_writing)
151
+
152
+ conn.receive_data '$HubIsFull|'
153
+ end
154
+ end
155
+ end