fake_ftp 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.codeclimate.yml +16 -0
- data/.gitignore +1 -0
- data/.rspec +2 -0
- data/.rubocop.yml +27 -0
- data/.rubocop_todo.yml +7 -0
- data/.simplecov +6 -0
- data/.travis.yml +27 -2
- data/CHANGELOG.md +136 -0
- data/Gemfile +9 -7
- data/Guardfile +5 -4
- data/LICENSE.md +20 -0
- data/README.md +30 -63
- data/Rakefile +12 -7
- data/fake_ftp.gemspec +16 -17
- data/lib/fake_ftp.rb +3 -2
- data/lib/fake_ftp/file.rb +11 -5
- data/lib/fake_ftp/server.rb +138 -261
- data/lib/fake_ftp/server_commands.rb +6 -0
- data/lib/fake_ftp/server_commands/acct.rb +11 -0
- data/lib/fake_ftp/server_commands/cdup.rb +11 -0
- data/lib/fake_ftp/server_commands/cwd.rb +13 -0
- data/lib/fake_ftp/server_commands/dele.rb +25 -0
- data/lib/fake_ftp/server_commands/list.rb +39 -0
- data/lib/fake_ftp/server_commands/mdtm.rb +17 -0
- data/lib/fake_ftp/server_commands/mkd.rb +11 -0
- data/lib/fake_ftp/server_commands/nlst.rb +32 -0
- data/lib/fake_ftp/server_commands/pass.rb +11 -0
- data/lib/fake_ftp/server_commands/pasv.rb +15 -0
- data/lib/fake_ftp/server_commands/port.rb +23 -0
- data/lib/fake_ftp/server_commands/pwd.rb +11 -0
- data/lib/fake_ftp/server_commands/quit.rb +13 -0
- data/lib/fake_ftp/server_commands/retr.rb +32 -0
- data/lib/fake_ftp/server_commands/rnfr.rb +18 -0
- data/lib/fake_ftp/server_commands/rnto.rb +22 -0
- data/lib/fake_ftp/server_commands/site.rb +11 -0
- data/lib/fake_ftp/server_commands/size.rb +11 -0
- data/lib/fake_ftp/server_commands/stor.rb +30 -0
- data/lib/fake_ftp/server_commands/type.rb +18 -0
- data/lib/fake_ftp/server_commands/user.rb +12 -0
- data/lib/fake_ftp/server_commands/wat.rb +33 -0
- data/lib/fake_ftp/version.rb +3 -1
- data/spec/functional/server_spec.rb +459 -358
- data/spec/integration/server_spec.rb +39 -29
- data/spec/models/fake_ftp/file_spec.rb +22 -21
- data/spec/models/fake_ftp/server_spec.rb +33 -32
- data/spec/spec_helper.rb +98 -14
- metadata +32 -3
@@ -1,10 +1,13 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'net/ftp'
|
3
4
|
|
4
|
-
describe FakeFtp::Server, 'with ftp client' do
|
5
|
-
let(:server) { FakeFtp::Server.new(
|
6
|
-
let(:client) { Net::FTP.new }
|
7
|
-
let(:text_filename)
|
5
|
+
describe FakeFtp::Server, 'with ftp client', integration: true do
|
6
|
+
let(:server) { FakeFtp::Server.new(21_212, 21_213, absolute: true) }
|
7
|
+
let(:client) { Net::FTP.new(nil, debug_mode: ENV['DEBUG'] == '1') }
|
8
|
+
let(:text_filename) do
|
9
|
+
File.expand_path('../../fixtures/text_file.txt', __FILE__)
|
10
|
+
end
|
8
11
|
|
9
12
|
before { server.start }
|
10
13
|
|
@@ -14,29 +17,29 @@ describe FakeFtp::Server, 'with ftp client' do
|
|
14
17
|
end
|
15
18
|
|
16
19
|
it 'should accept connections' do
|
17
|
-
expect { client.connect('127.0.0.1',
|
20
|
+
expect { client.connect('127.0.0.1', 21_212) }.to_not raise_error
|
18
21
|
end
|
19
22
|
|
20
|
-
context
|
21
|
-
before { client.connect(
|
23
|
+
context 'with client' do
|
24
|
+
before { client.connect('127.0.0.1', 21_212) }
|
22
25
|
|
23
|
-
it
|
26
|
+
it 'should allow anonymous authentication' do
|
24
27
|
expect { client.login }.to_not raise_error
|
25
28
|
end
|
26
29
|
|
27
|
-
it
|
30
|
+
it 'should allow named authentication' do
|
28
31
|
expect { client.login('someone', 'password') }.to_not raise_error
|
29
32
|
end
|
30
33
|
|
31
|
-
it
|
34
|
+
it 'should allow client to quit' do
|
32
35
|
expect { client.login('someone', 'password') }.to_not raise_error
|
33
36
|
expect { client.quit }.to_not raise_error
|
34
37
|
end
|
35
38
|
|
36
|
-
it
|
37
|
-
filename = 'someone'
|
39
|
+
it 'should allow mtime' do
|
40
|
+
filename = '/pub/someone'
|
38
41
|
time = Time.now
|
39
|
-
server.add_file(filename,
|
42
|
+
server.add_file(filename, 'some data', time)
|
40
43
|
|
41
44
|
client.passive = false
|
42
45
|
mtime = client.mtime(filename)
|
@@ -47,35 +50,42 @@ describe FakeFtp::Server, 'with ftp client' do
|
|
47
50
|
expect(mtime.to_s).to eql(time.to_s)
|
48
51
|
end
|
49
52
|
|
50
|
-
it
|
53
|
+
it 'should put files using PASV' do
|
51
54
|
expect(File.stat(text_filename).size).to eql(20)
|
52
55
|
|
53
56
|
client.passive = true
|
54
57
|
expect { client.put(text_filename) }.to_not raise_error
|
55
58
|
|
56
|
-
expect(server.files).to include('text_file.txt')
|
57
|
-
expect(server.file('text_file.txt').bytes).to eql(20)
|
58
|
-
expect(server.file('text_file.txt')).to be_passive
|
59
|
-
expect(server.file('text_file.txt')).to_not be_active
|
59
|
+
expect(server.files).to include('/pub/text_file.txt')
|
60
|
+
expect(server.file('/pub/text_file.txt').bytes).to eql(20)
|
61
|
+
expect(server.file('/pub/text_file.txt')).to be_passive
|
62
|
+
expect(server.file('/pub/text_file.txt')).to_not be_active
|
60
63
|
end
|
61
64
|
|
62
|
-
it
|
65
|
+
it 'should put files using active' do
|
63
66
|
expect(File.stat(text_filename).size).to eql(20)
|
64
67
|
|
65
68
|
client.passive = false
|
66
69
|
expect { client.put(text_filename) }.to_not raise_error
|
67
70
|
|
68
|
-
expect(server.files).to include('text_file.txt')
|
69
|
-
expect(server.file('text_file.txt').bytes).to eql(20)
|
70
|
-
expect(server.file('text_file.txt')).to_not be_passive
|
71
|
-
expect(server.file('text_file.txt')).to be_active
|
71
|
+
expect(server.files).to include('/pub/text_file.txt')
|
72
|
+
expect(server.file('/pub/text_file.txt').bytes).to eql(20)
|
73
|
+
expect(server.file('/pub/text_file.txt')).to_not be_passive
|
74
|
+
expect(server.file('/pub/text_file.txt')).to be_active
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'should allow client to execute SITE command' do
|
78
|
+
expect { client.site('umask') }.to_not raise_error
|
72
79
|
end
|
73
80
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
expect
|
81
|
+
it 'should be able to delete files added using put' do
|
82
|
+
expect(File.stat(text_filename).size).to eql(20)
|
83
|
+
|
84
|
+
client.passive = false
|
85
|
+
expect { client.put(text_filename) }.to_not raise_error
|
86
|
+
expect(server.files).to include('/pub/text_file.txt')
|
87
|
+
expect { client.delete(text_filename) }.to_not raise_error
|
88
|
+
expect(server.files).to_not include('/pub/text_file.txt')
|
79
89
|
end
|
80
90
|
end
|
81
91
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
describe FakeFtp::File do
|
4
4
|
context 'attributes' do
|
@@ -6,59 +6,60 @@ describe FakeFtp::File do
|
|
6
6
|
@file = FakeFtp::File.new
|
7
7
|
end
|
8
8
|
|
9
|
-
it
|
10
|
-
@file.name =
|
11
|
-
expect(@file.name).to eql(
|
9
|
+
it 'has a name attribute' do
|
10
|
+
@file.name = 'some name'
|
11
|
+
expect(@file.name).to eql('some name')
|
12
12
|
end
|
13
13
|
|
14
|
-
it
|
14
|
+
it 'has a last_modified_time attribute' do
|
15
15
|
now = Time.now
|
16
16
|
@file.last_modified_time = now
|
17
17
|
expect(@file.last_modified_time).to eql(now)
|
18
18
|
end
|
19
19
|
|
20
|
-
it
|
20
|
+
it 'has a bytes attribute' do
|
21
21
|
@file.bytes = 87
|
22
22
|
expect(@file.bytes).to eql(87)
|
23
23
|
end
|
24
24
|
|
25
|
-
it
|
25
|
+
it 'has a data attribute' do
|
26
26
|
@file.data = 'some data'
|
27
27
|
expect(@file.data).to eql('some data')
|
28
|
-
expect(@file.bytes).to eql(
|
28
|
+
expect(@file.bytes).to eql(9)
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
32
|
context 'setup' do
|
33
|
-
it
|
33
|
+
it 'can be initialized without attributes' do
|
34
34
|
file = FakeFtp::File.new
|
35
35
|
expect(file.name).to be_nil
|
36
36
|
expect(file.bytes).to be_nil
|
37
37
|
expect(file.instance_variable_get(:@type)).to be_nil
|
38
38
|
end
|
39
39
|
|
40
|
-
it
|
40
|
+
it 'can be initialized with name' do
|
41
41
|
file = FakeFtp::File.new('filename')
|
42
42
|
expect(file.name).to eql('filename')
|
43
43
|
expect(file.bytes).to be_nil
|
44
44
|
expect(file.instance_variable_get(:@type)).to be_nil
|
45
45
|
end
|
46
46
|
|
47
|
-
it
|
47
|
+
it 'can be initialized with name and bytes' do
|
48
48
|
file = FakeFtp::File.new('filename', 104)
|
49
49
|
expect(file.name).to eql('filename')
|
50
50
|
expect(file.bytes).to eql(104)
|
51
51
|
expect(file.instance_variable_get(:@type)).to be_nil
|
52
52
|
end
|
53
53
|
|
54
|
-
it
|
54
|
+
it 'can be initialized with name and bytes and type' do
|
55
55
|
file = FakeFtp::File.new('filename', 104, :passive)
|
56
56
|
expect(file.name).to eql('filename')
|
57
57
|
expect(file.bytes).to eql(104)
|
58
58
|
expect(file.instance_variable_get(:@type)).to eql(:passive)
|
59
59
|
end
|
60
60
|
|
61
|
-
it
|
61
|
+
it 'can be initialized with name and bytes and type ' \
|
62
|
+
'and last_modified_time' do
|
62
63
|
time = Time.now
|
63
64
|
file = FakeFtp::File.new('filename', 104, :passive, time)
|
64
65
|
expect(file.name).to eql('filename')
|
@@ -73,14 +74,14 @@ describe FakeFtp::File do
|
|
73
74
|
@file = FakeFtp::File.new
|
74
75
|
end
|
75
76
|
|
76
|
-
it
|
77
|
+
it 'should be true if type is :passive' do
|
77
78
|
@file.type = :passive
|
78
|
-
expect(@file.passive?).to
|
79
|
+
expect(@file.passive?).to be true
|
79
80
|
end
|
80
81
|
|
81
|
-
it
|
82
|
+
it 'should be false if type is :active' do
|
82
83
|
@file.type = :active
|
83
|
-
expect(@file.passive?).to
|
84
|
+
expect(@file.passive?).to be false
|
84
85
|
end
|
85
86
|
end
|
86
87
|
|
@@ -89,14 +90,14 @@ describe FakeFtp::File do
|
|
89
90
|
@file = FakeFtp::File.new
|
90
91
|
end
|
91
92
|
|
92
|
-
it
|
93
|
+
it 'should be true if type is :active' do
|
93
94
|
@file.type = :active
|
94
|
-
expect(@file.active?).to
|
95
|
+
expect(@file.active?).to be true
|
95
96
|
end
|
96
97
|
|
97
|
-
it
|
98
|
+
it 'should be false if type is :passive' do
|
98
99
|
@file.type = :passive
|
99
|
-
expect(@file.active?).to
|
100
|
+
expect(@file.active?).to be false
|
100
101
|
end
|
101
102
|
end
|
102
103
|
end
|
@@ -1,76 +1,77 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
describe FakeFtp::Server, 'setup' do
|
4
|
-
it
|
5
|
-
server = FakeFtp::Server.new(
|
6
|
-
expect(server.port).to eql(
|
4
|
+
it 'starts a server on port n' do
|
5
|
+
server = FakeFtp::Server.new(21_212)
|
6
|
+
expect(server.port).to eql(21_212)
|
7
7
|
end
|
8
8
|
|
9
|
-
it
|
9
|
+
it 'should defaults to port 21' do
|
10
10
|
server = FakeFtp::Server.new
|
11
11
|
expect(server.port).to eql(21)
|
12
12
|
end
|
13
13
|
|
14
|
-
it
|
15
|
-
server = FakeFtp::Server.new(
|
16
|
-
expect(server.passive_port).to eql(
|
14
|
+
it 'starts a passive server on port p' do
|
15
|
+
server = FakeFtp::Server.new(21_212, 21_213)
|
16
|
+
expect(server.passive_port).to eql(21_213)
|
17
17
|
end
|
18
18
|
|
19
|
-
it
|
20
|
-
server = FakeFtp::Server.new(
|
21
|
-
expect(server.
|
19
|
+
it 'should start and stop' do
|
20
|
+
server = FakeFtp::Server.new(21_212)
|
21
|
+
expect(server.running?).to be false
|
22
22
|
server.start
|
23
|
-
expect(server.
|
23
|
+
expect(server.running?).to be true
|
24
24
|
server.stop
|
25
|
-
expect(server.
|
25
|
+
expect(server.running?).to be false
|
26
26
|
end
|
27
27
|
|
28
|
-
it
|
29
|
-
server = FakeFtp::Server.new(
|
28
|
+
it 'should default :mode to :active' do
|
29
|
+
server = FakeFtp::Server.new(21_212, 21_213)
|
30
30
|
expect(server.mode).to eql(:active)
|
31
31
|
end
|
32
32
|
|
33
|
-
it
|
34
|
-
server = FakeFtp::Server.new(
|
35
|
-
expect(server.
|
33
|
+
it 'should start and stop passive port' do
|
34
|
+
server = FakeFtp::Server.new(21_212, 21_213)
|
35
|
+
expect(server.running?(21_213)).to be false
|
36
36
|
server.start
|
37
|
-
expect(server.
|
37
|
+
expect(server.running?(21_213)).to be true
|
38
38
|
server.stop
|
39
|
-
expect(server.
|
39
|
+
expect(server.running?(21_213)).to be false
|
40
40
|
end
|
41
41
|
|
42
|
-
it
|
43
|
-
server = FakeFtp::Server.new(
|
42
|
+
it 'should raise if attempting to use a bound port' do
|
43
|
+
server = FakeFtp::Server.new(21_212)
|
44
44
|
server.start
|
45
|
-
expect { FakeFtp::Server.new(
|
45
|
+
expect { FakeFtp::Server.new(21_212) }
|
46
|
+
.to raise_error(Errno::EADDRINUSE, 'Address already in use - 21212')
|
46
47
|
server.stop
|
47
48
|
end
|
48
49
|
|
49
|
-
it
|
50
|
-
server = FakeFtp::Server.new(
|
50
|
+
it 'should raise if attempting to use a bound passive_port' do
|
51
|
+
server = FakeFtp::Server.new(21_212, 21_213)
|
51
52
|
server.start
|
52
|
-
expect { FakeFtp::Server.new(
|
53
|
+
expect { FakeFtp::Server.new(21_214, 21_213) }
|
54
|
+
.to raise_error(Errno::EADDRINUSE, 'Address already in use - 21213')
|
53
55
|
server.stop
|
54
56
|
end
|
55
57
|
end
|
56
58
|
|
57
59
|
describe FakeFtp::Server, 'files' do
|
58
60
|
let(:file) { FakeFtp::File.new('filename', 34) }
|
59
|
-
let(:server) { FakeFtp::Server.new(
|
61
|
+
let(:server) { FakeFtp::Server.new(21_212) }
|
60
62
|
|
61
|
-
before { server.instance_variable_set(:@
|
63
|
+
before { server.instance_variable_set(:@store, '/pub/filename' => file) }
|
62
64
|
|
63
|
-
it
|
65
|
+
it 'returns filenames from :files' do
|
64
66
|
expect(server.files).to include('filename')
|
65
67
|
end
|
66
68
|
|
67
|
-
it
|
69
|
+
it 'can be accessed with :file' do
|
68
70
|
expect(server.file('filename')).to eql(file)
|
69
71
|
end
|
70
72
|
|
71
|
-
it
|
73
|
+
it 'can reset files' do
|
72
74
|
server.reset
|
73
75
|
expect(server.files).to eql([])
|
74
76
|
end
|
75
77
|
end
|
76
|
-
|
data/spec/spec_helper.rb
CHANGED
@@ -1,18 +1,102 @@
|
|
1
|
-
|
2
|
-
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
-
$LOAD_PATH.unshift(File.dirname(__FILE__), '**','*')
|
1
|
+
# frozen_string_literal: true
|
4
2
|
|
5
|
-
|
6
|
-
require 'bundler/setup'
|
3
|
+
$LOAD_PATH.unshift(File.expand_path('../lib', __FILE__))
|
7
4
|
|
5
|
+
require 'simplecov'
|
8
6
|
require 'rspec'
|
9
|
-
require 'fake_ftp'
|
10
|
-
|
11
|
-
RSpec.configure do |
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
7
|
+
require 'fake_ftp'
|
8
|
+
|
9
|
+
RSpec.configure do |c|
|
10
|
+
c.filter_run_excluding(
|
11
|
+
functional: ENV['FUNCTIONAL_SPECS'] != '1',
|
12
|
+
integration: ENV['INTEGRATION_SPECS'] != '1'
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
module SpecHelper
|
17
|
+
def gets_with_timeout(client, timeout: 5, endwith: "\r\n", chunk: 1024)
|
18
|
+
outer_caller = caller(0..1).last.to_s
|
19
|
+
start = Time.now
|
20
|
+
buf = ''
|
21
|
+
loop do
|
22
|
+
if Time.now - start >= timeout
|
23
|
+
raise Timeout::Error, "client=#{client} timeout=#{timeout}s " \
|
24
|
+
"buf=#{buf.inspect} caller=#{outer_caller.inspect}"
|
25
|
+
end
|
26
|
+
bytes = client.read_nonblock(chunk, exception: false)
|
27
|
+
return buf if bytes.nil?
|
28
|
+
buf += bytes unless bytes == :wait_readable
|
29
|
+
return buf if buf.end_with?(endwith)
|
30
|
+
end
|
31
|
+
buf
|
32
|
+
end
|
33
|
+
|
34
|
+
module_function :gets_with_timeout
|
35
|
+
|
36
|
+
def local_addr_bits(port)
|
37
|
+
[
|
38
|
+
127, 0, 0, 1,
|
39
|
+
port / 256,
|
40
|
+
port % 256
|
41
|
+
].map(&:to_s).join(',')
|
42
|
+
end
|
43
|
+
|
44
|
+
module_function :local_addr_bits
|
45
|
+
|
46
|
+
def statline(ftp_file)
|
47
|
+
%W[
|
48
|
+
-rw-r--r--
|
49
|
+
1
|
50
|
+
owner
|
51
|
+
group
|
52
|
+
10
|
53
|
+
#{ftp_file.created.strftime('%b %d %H:%M')}
|
54
|
+
#{ftp_file.name}
|
55
|
+
].join("\t")
|
56
|
+
end
|
57
|
+
|
58
|
+
module_function :statline
|
59
|
+
|
60
|
+
class FakeDataServer
|
61
|
+
def initialize(port)
|
62
|
+
@port = port
|
63
|
+
end
|
64
|
+
|
65
|
+
attr_reader :port
|
66
|
+
|
67
|
+
def addr_bits
|
68
|
+
::SpecHelper.local_addr_bits(port)
|
69
|
+
end
|
70
|
+
|
71
|
+
def start
|
72
|
+
server
|
73
|
+
end
|
74
|
+
|
75
|
+
def stop
|
76
|
+
server.close
|
77
|
+
end
|
78
|
+
|
79
|
+
def handler_sock
|
80
|
+
@handler_sock ||= wait_for_handler_sock
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def wait_for_handler_sock
|
86
|
+
sock = nil
|
87
|
+
|
88
|
+
while sock.nil? || sock == :wait_readable
|
89
|
+
sleep 0.01
|
90
|
+
sock = server.accept_nonblock(exception: false)
|
91
|
+
end
|
92
|
+
|
93
|
+
sock
|
94
|
+
end
|
95
|
+
|
96
|
+
def server
|
97
|
+
@server ||= TCPServer.new('127.0.0.1', port).tap do |srv|
|
98
|
+
srv.sync = true
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
18
102
|
end
|