fake_ftp 0.1.0 → 0.1.1
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.
- checksums.yaml +4 -4
- data/.rspec +2 -0
- data/.travis.yml +2 -2
- data/CONTRIBUTORS.md +19 -0
- data/{README.markdown → README.md} +43 -11
- data/fake_ftp.gemspec +1 -0
- data/lib/fake_ftp/server.rb +6 -4
- data/lib/fake_ftp/version.rb +1 -1
- data/spec/functional/server_spec.rb +468 -0
- data/spec/integration/server_spec.rb +81 -0
- data/spec/models/fake_ftp/server_spec.rb +0 -547
- data/spec/spec_helper.rb +6 -1
- metadata +17 -11
- data/.rvmrc +0 -1
- data/Gemfile.travis +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 50a4678ec926f8b3ac68aa2db5670987c7c19558
|
4
|
+
data.tar.gz: caf6eef42de1e306f19860b56265b8e64ced3040
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 70a2926c93a0aff84dc95d52ea0312809bac104dd5d885eeac968932c5b82336d98115d0770ff20db0ad58efce8df62c55bc55376c2991bd9db12ff7680b4018
|
7
|
+
data.tar.gz: d7b0cd6eb3d31c651aab69cca842bbb6f6da82e7487184e2dea56d75c4aa80e5cb641e31146776873ad0d1b574e0c0960ed84750755dcd8e68f172439aea623d
|
data/.rspec
ADDED
data/.travis.yml
CHANGED
data/CONTRIBUTORS.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Contributors
|
2
|
+
============
|
3
|
+
|
4
|
+
* Eric Saxby - @sax
|
5
|
+
|
6
|
+
* Ben Ashford
|
7
|
+
* Colin Shield
|
8
|
+
* Eirik Lied
|
9
|
+
* Eric Oestrich
|
10
|
+
* Jacob Maine
|
11
|
+
* John Maxwell
|
12
|
+
* Kevin Thompson
|
13
|
+
* liehann
|
14
|
+
* mdalton
|
15
|
+
* Nick Rowe
|
16
|
+
* Pranas Kiziela
|
17
|
+
* Puneet Goyal
|
18
|
+
* Steve Thompson
|
19
|
+
* Thomas Sonntag
|
@@ -3,17 +3,23 @@ FakeFtp
|
|
3
3
|
|
4
4
|
[](http://travis-ci.org/livinginthepast/fake_ftp)
|
5
5
|
|
6
|
-
This is a gem that allows you to test FTP implementations in ruby. It is
|
7
|
-
that can be bound to any arbitrary
|
6
|
+
This is a gem that allows you to test FTP implementations in ruby. It is
|
7
|
+
a minimal single-client FTP server that can be bound to any arbitrary
|
8
|
+
port on localhost.
|
9
|
+
|
8
10
|
|
9
11
|
## Why?
|
10
12
|
|
11
|
-
We want to ensure that our code works, in a way that is agnostic to the
|
13
|
+
We want to ensure that our code works, in a way that is agnostic to the
|
14
|
+
implementation used (unlike with stubs or mocks).
|
15
|
+
|
12
16
|
|
13
17
|
## How
|
14
18
|
|
15
|
-
FakeFtp is a simple FTP server that fakes out enough of the protocol to
|
16
|
-
|
19
|
+
FakeFtp is a simple FTP server that fakes out enough of the protocol to
|
20
|
+
get us by, allowing us to test that files get to their intended destination
|
21
|
+
rather than testing how our code does so.
|
22
|
+
|
17
23
|
|
18
24
|
## Usage
|
19
25
|
|
@@ -66,17 +72,43 @@ server.stop
|
|
66
72
|
|
67
73
|
Note that many FTP clients default to active, unless specified otherwise.
|
68
74
|
|
75
|
+
|
76
|
+
## Caveats
|
77
|
+
|
78
|
+
This is *not* a real FTP server and should not be treated as one. The goal
|
79
|
+
of this gem is not to create a thread-safe multi-client implementation.
|
80
|
+
It is best used to unit test models that generate files and transfer
|
81
|
+
them to an FTP server.
|
82
|
+
|
83
|
+
As such, there are some things that won't be accepted upstream from pull
|
84
|
+
requests:
|
85
|
+
* simultaneous multi-client code
|
86
|
+
* support for long term file persistence
|
87
|
+
* binding to arbitrary IPs
|
88
|
+
* global state beyond that required to pass the minimum required to
|
89
|
+
generate passing tests
|
90
|
+
|
91
|
+
|
92
|
+
## Recommendations for testing patterns
|
93
|
+
|
94
|
+
*Separate configuration from code.* Do not hard code the IP address,
|
95
|
+
FQDN or port of an FTP server in your classes. It introduces fragility
|
96
|
+
into your tests. Also, the default FTP port of 21 is a privileged port,
|
97
|
+
and should be avoided.
|
98
|
+
|
99
|
+
*Separate the code that generates files from the code that uploads
|
100
|
+
files.* You tests will run much more quickly if you only try to upload
|
101
|
+
small files. If you have tests showing that you generate correct files
|
102
|
+
from your data, then you can trust that. Why do you need to upload a 20M
|
103
|
+
file in your tests if you can stub out your file generation method and
|
104
|
+
test file upload against 10 bytes? Fast fast fast.
|
105
|
+
|
106
|
+
|
69
107
|
## References
|
70
108
|
|
71
109
|
* http://rubyforge.org/projects/ftpd/ - a simple ftp daemon written by Chris Wanstrath
|
72
110
|
* http://ruby-doc.org/stdlib/libdoc/gserver/rdoc/index.html - a generic server in the Ruby standard library, by John W Small
|
73
111
|
|
74
|
-
## Contributors
|
75
|
-
|
76
|
-
* Eric Saxby
|
77
|
-
* Colin Shield
|
78
|
-
* liehann (https://github.com/liehann)
|
79
|
-
|
80
112
|
## License
|
81
113
|
|
82
114
|
The MIT License
|
data/fake_ftp.gemspec
CHANGED
@@ -11,6 +11,7 @@ Gem::Specification.new do |s|
|
|
11
11
|
s.homepage = "http://rubygems.org/gems/fake_ftp"
|
12
12
|
s.summary = %q{Creates a fake FTP server for use in testing}
|
13
13
|
s.description = %q{Testing FTP? Use this!}
|
14
|
+
s.license = 'MIT'
|
14
15
|
|
15
16
|
s.required_rubygems_version = ">= 1.3.6"
|
16
17
|
|
data/lib/fake_ftp/server.rb
CHANGED
@@ -34,8 +34,8 @@ module FakeFtp
|
|
34
34
|
def initialize(control_port = 21, data_port = nil, options = {})
|
35
35
|
self.port = control_port
|
36
36
|
self.passive_port = data_port
|
37
|
-
raise(Errno::EADDRINUSE, "#{port}") if is_running?
|
38
|
-
raise(Errno::EADDRINUSE, "#{passive_port}") if passive_port && is_running?(passive_port)
|
37
|
+
raise(Errno::EADDRINUSE, "#{port}") if !control_port.zero? && is_running?
|
38
|
+
raise(Errno::EADDRINUSE, "#{passive_port}") if passive_port && !passive_port.zero? && is_running?(passive_port)
|
39
39
|
@connection = nil
|
40
40
|
@options = options
|
41
41
|
@files = []
|
@@ -62,6 +62,7 @@ module FakeFtp
|
|
62
62
|
def start
|
63
63
|
@started = true
|
64
64
|
@server = ::TCPServer.new('127.0.0.1', port)
|
65
|
+
@port = @server.addr[1]
|
65
66
|
@thread = Thread.new do
|
66
67
|
while @started
|
67
68
|
@client = @server.accept rescue nil
|
@@ -87,6 +88,7 @@ module FakeFtp
|
|
87
88
|
|
88
89
|
if passive_port
|
89
90
|
@data_server = ::TCPServer.new('127.0.0.1', passive_port)
|
91
|
+
@passive_port = @data_server.addr[1]
|
90
92
|
end
|
91
93
|
end
|
92
94
|
|
@@ -162,9 +164,9 @@ module FakeFtp
|
|
162
164
|
end
|
163
165
|
end
|
164
166
|
files = files.map do |f|
|
165
|
-
"-rw-r--r--\t1\towner\tgroup\t#{f.bytes}\t#{f.created.strftime('%b %d %H:%M')}\t#{f.name}"
|
167
|
+
"-rw-r--r--\t1\towner\tgroup\t#{f.bytes}\t#{f.created.strftime('%b %d %H:%M')}\t#{f.name}\n"
|
166
168
|
end
|
167
|
-
data_client.write(files.join
|
169
|
+
data_client.write(files.join)
|
168
170
|
data_client.close
|
169
171
|
@active_connection = nil
|
170
172
|
|
data/lib/fake_ftp/version.rb
CHANGED
@@ -0,0 +1,468 @@
|
|
1
|
+
require "spec_helper.rb"
|
2
|
+
|
3
|
+
describe FakeFtp::Server, 'commands' do
|
4
|
+
let(:server) { FakeFtp::Server.new(21212, 21213) }
|
5
|
+
let(:client) { TCPSocket.open('127.0.0.1', 21212) }
|
6
|
+
|
7
|
+
before { server.start }
|
8
|
+
|
9
|
+
after {
|
10
|
+
client.close
|
11
|
+
server.stop
|
12
|
+
}
|
13
|
+
|
14
|
+
context 'general' do
|
15
|
+
it "should accept connections" do
|
16
|
+
expect(client.gets).to eql("220 Can has FTP?\r\n")
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should get unknown command response when nothing is sent" do
|
20
|
+
client.gets
|
21
|
+
client.puts
|
22
|
+
expect(client.gets).to eql("500 Unknown command\r\n")
|
23
|
+
end
|
24
|
+
|
25
|
+
it "accepts QUIT" do
|
26
|
+
client.gets
|
27
|
+
client.puts "QUIT"
|
28
|
+
expect(client.gets).to eql("221 OMG bye!\r\n")
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should accept multiple commands in one session" do
|
32
|
+
client.gets
|
33
|
+
client.puts "USER thing"
|
34
|
+
client.gets
|
35
|
+
client.puts "PASS thing"
|
36
|
+
client.gets
|
37
|
+
client.puts "ACCT thing"
|
38
|
+
client.gets
|
39
|
+
client.puts "USER thing"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'passive' do
|
44
|
+
it "accepts PASV" do
|
45
|
+
expect(server.mode).to eql(:active)
|
46
|
+
client.gets
|
47
|
+
client.puts "PASV"
|
48
|
+
expect(client.gets).to eql("227 Entering Passive Mode (127,0,0,1,82,221)\r\n")
|
49
|
+
expect(server.mode).to eql(:passive)
|
50
|
+
end
|
51
|
+
|
52
|
+
it "responds with correct PASV port" do
|
53
|
+
server.stop
|
54
|
+
server.passive_port = 21111
|
55
|
+
server.start
|
56
|
+
client.gets
|
57
|
+
client.puts "PASV"
|
58
|
+
expect(client.gets).to eql("227 Entering Passive Mode (127,0,0,1,82,119)\r\n")
|
59
|
+
end
|
60
|
+
|
61
|
+
it "does not accept PASV if no port set" do
|
62
|
+
server.stop
|
63
|
+
server.passive_port = nil
|
64
|
+
server.start
|
65
|
+
client.gets
|
66
|
+
client.puts "PASV"
|
67
|
+
expect(client.gets).to eql("502 Aww hell no, use Active\r\n")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context 'active' do
|
72
|
+
let!(:data_server) { ::TCPServer.new('127.0.0.1', 21216) }
|
73
|
+
|
74
|
+
before :each do
|
75
|
+
client.gets
|
76
|
+
|
77
|
+
@data_connection = Thread.new do
|
78
|
+
@server_client = data_server.accept
|
79
|
+
expect(@server_client).to_not be_nil
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
after :each do
|
84
|
+
data_server.close
|
85
|
+
end
|
86
|
+
|
87
|
+
it "accepts PORT and connects to port" do
|
88
|
+
client.puts "PORT 127,0,0,1,82,224"
|
89
|
+
expect(client.gets).to eql("200 Okay\r\n")
|
90
|
+
|
91
|
+
@data_connection.join
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should switch to :active on port command" do
|
95
|
+
expect(server.mode).to eql(:active)
|
96
|
+
client.puts 'PASV'
|
97
|
+
client.gets
|
98
|
+
expect(server.mode).to eql(:passive)
|
99
|
+
|
100
|
+
client.puts "PORT 127,0,0,1,82,224"
|
101
|
+
expect(client.gets).to eql("200 Okay\r\n")
|
102
|
+
|
103
|
+
@data_connection.join
|
104
|
+
|
105
|
+
expect(server.mode).to eql(:active)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
context 'authentication commands' do
|
110
|
+
before :each do
|
111
|
+
client.gets ## connection successful response
|
112
|
+
end
|
113
|
+
|
114
|
+
it "accepts USER" do
|
115
|
+
client.puts "USER some_dude"
|
116
|
+
expect(client.gets).to eql("331 send your password\r\n")
|
117
|
+
end
|
118
|
+
|
119
|
+
it "accepts anonymous USER" do
|
120
|
+
client.puts "USER anonymous"
|
121
|
+
expect(client.gets).to eql("230 logged in\r\n")
|
122
|
+
end
|
123
|
+
|
124
|
+
it "accepts PASS" do
|
125
|
+
client.puts "PASS password"
|
126
|
+
expect(client.gets).to eql("230 logged in\r\n")
|
127
|
+
end
|
128
|
+
|
129
|
+
it "accepts ACCT" do
|
130
|
+
client.puts "ACCT"
|
131
|
+
expect(client.gets).to eql("230 WHATEVER!\r\n")
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
context 'directory commands' do
|
136
|
+
before :each do
|
137
|
+
client.gets ## connection successful response
|
138
|
+
end
|
139
|
+
|
140
|
+
it "returns directory on PWD" do
|
141
|
+
client.puts "PWD"
|
142
|
+
expect(client.gets).to eql("257 \"/pub\" is current directory\r\n")
|
143
|
+
end
|
144
|
+
|
145
|
+
it "says OK to any CWD, CDUP, without doing anything" do
|
146
|
+
client.puts "CWD somewhere/else"
|
147
|
+
expect(client.gets).to eql("250 OK!\r\n")
|
148
|
+
client.puts "CDUP"
|
149
|
+
expect(client.gets).to eql("250 OK!\r\n")
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
context 'file commands' do
|
154
|
+
before :each do
|
155
|
+
client.gets ## connection successful response
|
156
|
+
end
|
157
|
+
|
158
|
+
it "accepts TYPE ascii" do
|
159
|
+
client.puts "TYPE A"
|
160
|
+
expect(client.gets).to eql("200 Type set to A.\r\n")
|
161
|
+
end
|
162
|
+
|
163
|
+
it "accepts TYPE image" do
|
164
|
+
client.puts "TYPE I"
|
165
|
+
expect(client.gets).to eql("200 Type set to I.\r\n")
|
166
|
+
end
|
167
|
+
|
168
|
+
it "does not accept TYPEs other than ascii or image" do
|
169
|
+
client.puts "TYPE E"
|
170
|
+
expect(client.gets).to eql("504 We don't allow those\r\n")
|
171
|
+
client.puts "TYPE N"
|
172
|
+
expect(client.gets).to eql("504 We don't allow those\r\n")
|
173
|
+
client.puts "TYPE T"
|
174
|
+
expect(client.gets).to eql("504 We don't allow those\r\n")
|
175
|
+
client.puts "TYPE C"
|
176
|
+
expect(client.gets).to eql("504 We don't allow those\r\n")
|
177
|
+
client.puts "TYPE L"
|
178
|
+
expect(client.gets).to eql("504 We don't allow those\r\n")
|
179
|
+
end
|
180
|
+
|
181
|
+
context 'passive' do
|
182
|
+
let(:data_client) { TCPSocket.open('127.0.0.1', 21213) }
|
183
|
+
|
184
|
+
before :each do
|
185
|
+
client.puts 'PASV'
|
186
|
+
expect(client.gets).to eql("227 Entering Passive Mode (127,0,0,1,82,221)\r\n")
|
187
|
+
end
|
188
|
+
|
189
|
+
it "accepts STOR with filename" do
|
190
|
+
client.puts "STOR some_file"
|
191
|
+
expect(client.gets).to eql("125 Do it!\r\n")
|
192
|
+
data_client.puts "1234567890"
|
193
|
+
data_client.close
|
194
|
+
expect(client.gets).to eql("226 Did it!\r\n")
|
195
|
+
expect(server.files).to include('some_file')
|
196
|
+
expect(server.file('some_file').bytes).to eql(10)
|
197
|
+
expect(server.file('some_file').data).to eql("1234567890")
|
198
|
+
end
|
199
|
+
|
200
|
+
it "accepts STOR with filename and trailing newline" do
|
201
|
+
client.puts "STOR some_file"
|
202
|
+
client.gets
|
203
|
+
# puts tries to be smart and only write a single \n
|
204
|
+
data_client.puts "1234567890\n\n"
|
205
|
+
data_client.close
|
206
|
+
expect(client.gets).to eql("226 Did it!\r\n")
|
207
|
+
expect(server.files).to include('some_file')
|
208
|
+
expect(server.file('some_file').bytes).to eql(11)
|
209
|
+
expect(server.file('some_file').data).to eql("1234567890\n")
|
210
|
+
end
|
211
|
+
|
212
|
+
it "accepts STOR with filename and long file" do
|
213
|
+
client.puts "STOR some_file"
|
214
|
+
expect(client.gets).to eql("125 Do it!\r\n")
|
215
|
+
data_client.puts("1234567890" * 10_000)
|
216
|
+
data_client.close
|
217
|
+
expect(client.gets).to eql("226 Did it!\r\n")
|
218
|
+
expect(server.files).to include('some_file')
|
219
|
+
end
|
220
|
+
|
221
|
+
it "accepts STOR with streams" do
|
222
|
+
client.puts "STOR some_file"
|
223
|
+
expect(client.gets).to eql("125 Do it!\r\n")
|
224
|
+
data_client.write "1234567890"
|
225
|
+
data_client.flush
|
226
|
+
data_client.write "1234567890"
|
227
|
+
data_client.flush
|
228
|
+
data_client.close
|
229
|
+
expect(client.gets).to eql("226 Did it!\r\n")
|
230
|
+
expect(server.file('some_file').data).to eql("12345678901234567890")
|
231
|
+
end
|
232
|
+
|
233
|
+
it "does not accept RETR without a filename" do
|
234
|
+
client.puts "RETR"
|
235
|
+
expect(client.gets).to eql("501 No filename given\r\n")
|
236
|
+
end
|
237
|
+
|
238
|
+
it "does not serve files that do not exist" do
|
239
|
+
client.puts "RETR some_file"
|
240
|
+
expect(client.gets).to eql("550 File not found\r\n")
|
241
|
+
end
|
242
|
+
|
243
|
+
it "accepts RETR with a filename" do
|
244
|
+
server.add_file('some_file', '1234567890')
|
245
|
+
client.puts "RETR some_file"
|
246
|
+
expect(client.gets).to eql("150 File status ok, about to open data connection\r\n")
|
247
|
+
data = data_client.read(1024)
|
248
|
+
data_client.close
|
249
|
+
expect(data).to eql('1234567890')
|
250
|
+
expect(client.gets).to eql("226 File transferred\r\n")
|
251
|
+
end
|
252
|
+
|
253
|
+
it "accepts DELE with a filename" do
|
254
|
+
server.add_file('some_file', '1234567890')
|
255
|
+
client.puts "DELE some_file"
|
256
|
+
expect(client.gets).to eql("250 Delete operation successful.\r\n")
|
257
|
+
expect(server.files).to_not include('some_file')
|
258
|
+
end
|
259
|
+
|
260
|
+
it "gives error message when trying to delete a file that does not exist" do
|
261
|
+
client.puts "DELE non_existing_file"
|
262
|
+
expect(client.gets).to eql("550 Delete operation failed.\r\n")
|
263
|
+
end
|
264
|
+
|
265
|
+
it "accepts a LIST command" do
|
266
|
+
server.add_file('some_file', '1234567890')
|
267
|
+
server.add_file('another_file', '1234567890')
|
268
|
+
client.puts "LIST"
|
269
|
+
expect(client.gets).to eql("150 Listing status ok, about to open data connection\r\n")
|
270
|
+
data = data_client.read(2048)
|
271
|
+
data_client.close
|
272
|
+
expect(data).to eql([
|
273
|
+
"-rw-r--r--\t1\towner\tgroup\t10\t#{server.file('some_file').created.strftime('%b %d %H:%M')}\tsome_file\n",
|
274
|
+
"-rw-r--r--\t1\towner\tgroup\t10\t#{server.file('another_file').created.strftime('%b %d %H:%M')}\tanother_file\n",
|
275
|
+
].join)
|
276
|
+
expect(client.gets).to eql("226 List information transferred\r\n")
|
277
|
+
end
|
278
|
+
|
279
|
+
it "accepts a LIST command with a wildcard argument" do
|
280
|
+
files = ['test.jpg', 'test-2.jpg', 'test.txt']
|
281
|
+
files.each do |file|
|
282
|
+
server.add_file(file, '1234567890')
|
283
|
+
end
|
284
|
+
|
285
|
+
client.puts "LIST *.jpg"
|
286
|
+
expect(client.gets).to eql("150 Listing status ok, about to open data connection\r\n")
|
287
|
+
|
288
|
+
data = data_client.read(2048)
|
289
|
+
data_client.close
|
290
|
+
expect(data).to eql(files[0,2].map do |file|
|
291
|
+
"-rw-r--r--\t1\towner\tgroup\t10\t#{server.file(file).created.strftime('%b %d %H:%M')}\t#{file}\n"
|
292
|
+
end.join)
|
293
|
+
expect(client.gets).to eql("226 List information transferred\r\n")
|
294
|
+
end
|
295
|
+
|
296
|
+
it "accepts a LIST command with multiple wildcard arguments" do
|
297
|
+
files = ['test.jpg', 'test.gif', 'test.txt']
|
298
|
+
files.each do |file|
|
299
|
+
server.add_file(file, '1234567890')
|
300
|
+
end
|
301
|
+
|
302
|
+
client.puts "LIST *.jpg *.gif"
|
303
|
+
expect(client.gets).to eql("150 Listing status ok, about to open data connection\r\n")
|
304
|
+
|
305
|
+
data = data_client.read(2048)
|
306
|
+
data_client.close
|
307
|
+
expect(data).to eql(files[0,2].map do |file|
|
308
|
+
"-rw-r--r--\t1\towner\tgroup\t10\t#{server.file(file).created.strftime('%b %d %H:%M')}\t#{file}\n"
|
309
|
+
end.join)
|
310
|
+
expect(client.gets).to eql("226 List information transferred\r\n")
|
311
|
+
end
|
312
|
+
|
313
|
+
it "accepts an NLST command" do
|
314
|
+
server.add_file('some_file', '1234567890')
|
315
|
+
server.add_file('another_file', '1234567890')
|
316
|
+
client.puts "NLST"
|
317
|
+
expect(client.gets).to eql("150 Listing status ok, about to open data connection\r\n")
|
318
|
+
data = data_client.read(1024)
|
319
|
+
data_client.close
|
320
|
+
expect(data).to eql("some_file\nanother_file")
|
321
|
+
expect(client.gets).to eql("226 List information transferred\r\n")
|
322
|
+
end
|
323
|
+
|
324
|
+
it "should allow mdtm" do
|
325
|
+
filename = "file.txt"
|
326
|
+
now = Time.now
|
327
|
+
server.add_file(filename, "some dummy content", now)
|
328
|
+
client.puts "MDTM #{filename}"
|
329
|
+
expect(client.gets).to eql("213 #{now.strftime("%Y%m%d%H%M%S")}\r\n")
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
context 'active' do
|
334
|
+
let!(:data_server) { ::TCPServer.new('127.0.0.1', 21216) }
|
335
|
+
|
336
|
+
before :each do
|
337
|
+
@data_connection = Thread.new do
|
338
|
+
@server_client = data_server.accept
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
after :each do
|
343
|
+
data_server.close
|
344
|
+
@data_connection = nil
|
345
|
+
end
|
346
|
+
|
347
|
+
it 'creates a directory on MKD' do
|
348
|
+
client.puts "MKD some_dir"
|
349
|
+
expect(client.gets).to eql("257 OK!\r\n")
|
350
|
+
end
|
351
|
+
|
352
|
+
it 'should save the directory after you CWD' do
|
353
|
+
client.puts "CWD /somewhere/else"
|
354
|
+
expect(client.gets).to eql("250 OK!\r\n")
|
355
|
+
client.puts "PWD"
|
356
|
+
expect(client.gets).to eql("257 \"/somewhere/else\" is current directory\r\n")
|
357
|
+
end
|
358
|
+
|
359
|
+
it 'CWD should add a / to the beginning of the directory' do
|
360
|
+
client.puts "CWD somewhere/else"
|
361
|
+
expect(client.gets).to eql("250 OK!\r\n")
|
362
|
+
client.puts "PWD"
|
363
|
+
expect(client.gets).to eql("257 \"/somewhere/else\" is current directory\r\n")
|
364
|
+
end
|
365
|
+
|
366
|
+
it 'should not change the directory on CDUP' do
|
367
|
+
client.puts "CDUP"
|
368
|
+
expect(client.gets).to eql("250 OK!\r\n")
|
369
|
+
client.puts "PWD"
|
370
|
+
expect(client.gets).to eql("257 \"/pub\" is current directory\r\n")
|
371
|
+
end
|
372
|
+
|
373
|
+
it "sends error message if no PORT received" do
|
374
|
+
client.puts "STOR some_file"
|
375
|
+
expect(client.gets).to eql("425 Ain't no data port!\r\n")
|
376
|
+
end
|
377
|
+
|
378
|
+
it "accepts STOR with filename" do
|
379
|
+
client.puts "PORT 127,0,0,1,82,224"
|
380
|
+
expect(client.gets).to eql("200 Okay\r\n")
|
381
|
+
|
382
|
+
client.puts "STOR some_other_file"
|
383
|
+
expect(client.gets).to eql("125 Do it!\r\n")
|
384
|
+
|
385
|
+
@data_connection.join
|
386
|
+
@server_client.print "12345"
|
387
|
+
@server_client.close
|
388
|
+
|
389
|
+
expect(client.gets).to eql("226 Did it!\r\n")
|
390
|
+
expect(server.files).to include('some_other_file')
|
391
|
+
expect(server.file('some_other_file').bytes).to eql(5)
|
392
|
+
end
|
393
|
+
|
394
|
+
it "accepts RETR with a filename" do
|
395
|
+
client.puts "PORT 127,0,0,1,82,224"
|
396
|
+
expect(client.gets).to eql("200 Okay\r\n")
|
397
|
+
|
398
|
+
server.add_file('some_file', '1234567890')
|
399
|
+
client.puts "RETR some_file"
|
400
|
+
expect(client.gets).to eql("150 File status ok, about to open data connection\r\n")
|
401
|
+
|
402
|
+
@data_connection.join
|
403
|
+
data = @server_client.read(1024)
|
404
|
+
@server_client.close
|
405
|
+
|
406
|
+
expect(data).to eql('1234567890')
|
407
|
+
expect(client.gets).to eql("226 File transferred\r\n")
|
408
|
+
end
|
409
|
+
|
410
|
+
it "accepts RNFR without filename" do
|
411
|
+
client.puts "RNFR"
|
412
|
+
expect(client.gets).to eql("501 Send path name.\r\n")
|
413
|
+
end
|
414
|
+
|
415
|
+
it "accepts RNTO without RNFR" do
|
416
|
+
client.puts "RNTO some_other_file"
|
417
|
+
expect(client.gets).to eql("503 Send RNFR first.\r\n")
|
418
|
+
end
|
419
|
+
|
420
|
+
it "accepts RNTO and RNFR without filename" do
|
421
|
+
client.puts "RNFR from_file"
|
422
|
+
expect(client.gets).to eql("350 Send RNTO to complete rename.\r\n")
|
423
|
+
|
424
|
+
client.puts "RNTO"
|
425
|
+
expect(client.gets).to eql("501 Send path name.\r\n")
|
426
|
+
end
|
427
|
+
|
428
|
+
it "accepts RNTO and RNFR for not existing file" do
|
429
|
+
client.puts "RNFR from_file"
|
430
|
+
expect(client.gets).to eql("350 Send RNTO to complete rename.\r\n")
|
431
|
+
|
432
|
+
client.puts "RNTO to_file"
|
433
|
+
expect(client.gets).to eql("550 File not found.\r\n")
|
434
|
+
end
|
435
|
+
|
436
|
+
it "accepts RNTO and RNFR" do
|
437
|
+
server.add_file('from_file', '1234567890')
|
438
|
+
|
439
|
+
client.puts "RNFR from_file"
|
440
|
+
expect(client.gets).to eql("350 Send RNTO to complete rename.\r\n")
|
441
|
+
|
442
|
+
client.puts "RNTO to_file"
|
443
|
+
expect(client.gets).to eql("250 Path renamed.\r\n")
|
444
|
+
|
445
|
+
expect(server.files).to include('to_file')
|
446
|
+
expect(server.files).to_not include('from_file')
|
447
|
+
end
|
448
|
+
|
449
|
+
it "accepts an NLST command" do
|
450
|
+
client.puts "PORT 127,0,0,1,82,224"
|
451
|
+
expect(client.gets).to eql("200 Okay\r\n")
|
452
|
+
|
453
|
+
server.add_file('some_file', '1234567890')
|
454
|
+
server.add_file('another_file', '1234567890')
|
455
|
+
client.puts "NLST"
|
456
|
+
expect(client.gets).to eql("150 Listing status ok, about to open data connection\r\n")
|
457
|
+
|
458
|
+
@data_connection.join
|
459
|
+
data = @server_client.read(1024)
|
460
|
+
@server_client.close
|
461
|
+
|
462
|
+
expect(data).to eql("some_file\nanother_file")
|
463
|
+
expect(client.gets).to eql("226 List information transferred\r\n")
|
464
|
+
end
|
465
|
+
end
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|