fargo 0.2.0 → 0.3.0

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.
@@ -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