ftpmock 0.0.0 → 0.1.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.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/.rubocop.yml +60 -2
- data/.travis.yml +19 -2
- data/Gemfile +1 -1
- data/Gemfile.lock +30 -11
- data/README.md +136 -12
- data/ftpmock.gemspec +6 -1
- data/images/ftpmock.png +0 -0
- data/images/ruby.png +0 -0
- data/lib/ftpmock.rb +19 -1
- data/lib/ftpmock/core/cache.rb +186 -0
- data/lib/ftpmock/core/configuration.rb +33 -0
- data/lib/ftpmock/helpers/get_helper.rb +46 -0
- data/lib/ftpmock/helpers/list_helper.rb +53 -0
- data/lib/ftpmock/helpers/path_helper.rb +24 -0
- data/lib/ftpmock/helpers/put_helper.rb +39 -0
- data/lib/ftpmock/proxies/method_missing_mixin.rb +9 -0
- data/lib/ftpmock/proxies/net_ftp_proxy.rb +287 -0
- data/lib/ftpmock/proxies/net_sftp_proxy.rb +50 -0
- data/lib/ftpmock/utils/color_utils.rb +27 -0
- data/lib/ftpmock/utils/string_utils.rb +29 -0
- data/lib/ftpmock/utils/verbose_utils.rb +19 -0
- data/lib/ftpmock/version.rb +1 -1
- metadata +74 -3
@@ -0,0 +1,33 @@
|
|
1
|
+
module Ftpmock
|
2
|
+
module_function
|
3
|
+
|
4
|
+
def configure
|
5
|
+
yield configuration
|
6
|
+
end
|
7
|
+
|
8
|
+
def configuration
|
9
|
+
@configuration ||= Configuration.new
|
10
|
+
end
|
11
|
+
|
12
|
+
class Configuration
|
13
|
+
attr_writer :path
|
14
|
+
attr_accessor :verbose
|
15
|
+
|
16
|
+
def initialize(path: nil, verbose: true)
|
17
|
+
@path = path
|
18
|
+
@verbose = verbose
|
19
|
+
end
|
20
|
+
|
21
|
+
def path
|
22
|
+
@path ||= "#{test_dir}/ftp_records"
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_dir
|
26
|
+
rspec? ? 'spec' : 'test'
|
27
|
+
end
|
28
|
+
|
29
|
+
def rspec?
|
30
|
+
defined?(RSpec)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Ftpmock
|
2
|
+
module GetHelper
|
3
|
+
module_function
|
4
|
+
|
5
|
+
# reads from cache to localfile
|
6
|
+
#
|
7
|
+
# true/false
|
8
|
+
def read(cache_path, chdir, remotefile, localfile)
|
9
|
+
# chdir =
|
10
|
+
remotefile = PathHelper.join(PathHelper.simplify(chdir), remotefile).to_s
|
11
|
+
# localfile = PathHelper.join(chdir, localfile).to_s
|
12
|
+
|
13
|
+
cached_path = path_for(cache_path, remotefile)
|
14
|
+
|
15
|
+
File.exist?(cached_path) && FileUtils.cp(cached_path, localfile)
|
16
|
+
|
17
|
+
fetched?(localfile)
|
18
|
+
end
|
19
|
+
|
20
|
+
# writes to cache from localfile
|
21
|
+
def write(cache_path, chdir, remotefile, localfile)
|
22
|
+
return false unless File.exist?(localfile)
|
23
|
+
|
24
|
+
# chdir =
|
25
|
+
remotefile = PathHelper.join(PathHelper.simplify(chdir), remotefile).to_s
|
26
|
+
# localfile = PathHelper.join(chdir, localfile).to_s
|
27
|
+
|
28
|
+
cached_path = path_for(cache_path, remotefile)
|
29
|
+
FileUtils.cp(localfile, cached_path)
|
30
|
+
|
31
|
+
File.exist?(cached_path)
|
32
|
+
end
|
33
|
+
|
34
|
+
def fetched?(localfile)
|
35
|
+
File.exist?(localfile)
|
36
|
+
end
|
37
|
+
|
38
|
+
def path_for(cache_path, remotefile)
|
39
|
+
path = cache_path.join('get')
|
40
|
+
remotefile = PathHelper.simplify(remotefile)
|
41
|
+
ret = path.join(remotefile)
|
42
|
+
FileUtils.mkdir_p(ret.dirname.to_s)
|
43
|
+
ret
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'psych'
|
2
|
+
|
3
|
+
module Ftpmock
|
4
|
+
module ListHelper
|
5
|
+
module_function
|
6
|
+
|
7
|
+
def read(cache_path, chdir, key)
|
8
|
+
key = PathHelper.join(chdir, key).to_s if chdir
|
9
|
+
dataset = read_dataset(cache_path)
|
10
|
+
dataset[key]
|
11
|
+
end
|
12
|
+
|
13
|
+
def write(cache_path, chdir, key, list)
|
14
|
+
key = PathHelper.join(chdir, key).to_s if chdir
|
15
|
+
dataset = read_dataset(cache_path)
|
16
|
+
dataset[key] = list
|
17
|
+
write_dataset(cache_path, dataset)
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
def read_dataset(cache_path)
|
22
|
+
path = path_for(cache_path)
|
23
|
+
string = path.read
|
24
|
+
load(string)
|
25
|
+
end
|
26
|
+
|
27
|
+
def write_dataset(cache_path, dataset)
|
28
|
+
path = path_for(cache_path)
|
29
|
+
content = dump(dataset)
|
30
|
+
path.write(content)
|
31
|
+
end
|
32
|
+
|
33
|
+
def path_for(cache_path)
|
34
|
+
cache_path = PathHelper.clean(cache_path)
|
35
|
+
cache_path.exist? || FileUtils.mkdir_p(cache_path)
|
36
|
+
path = PathHelper.clean("#{cache_path}/list.yml")
|
37
|
+
path.exist? || path.write(dump(new_list))
|
38
|
+
path
|
39
|
+
end
|
40
|
+
|
41
|
+
def new_list
|
42
|
+
{}
|
43
|
+
end
|
44
|
+
|
45
|
+
def load(string)
|
46
|
+
Psych.load(string)
|
47
|
+
end
|
48
|
+
|
49
|
+
def dump(data)
|
50
|
+
Psych.dump(data)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Ftpmock
|
2
|
+
module PathHelper
|
3
|
+
module_function
|
4
|
+
|
5
|
+
def clean(path)
|
6
|
+
path = path.to_s
|
7
|
+
path = path[1..-1] if path[0] == '/'
|
8
|
+
path = Pathname(path).cleanpath
|
9
|
+
return Pathname('') if path.to_s == '.'
|
10
|
+
|
11
|
+
path
|
12
|
+
end
|
13
|
+
|
14
|
+
def join(path_a, path_b)
|
15
|
+
clean Pathname(path_a.to_s).join(path_b.to_s)
|
16
|
+
end
|
17
|
+
|
18
|
+
def simplify(path)
|
19
|
+
path.to_s
|
20
|
+
.tr('/', '-')
|
21
|
+
.tr('..', '__')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Ftpmock
|
2
|
+
module PutHelper
|
3
|
+
module_function
|
4
|
+
|
5
|
+
def cached?(cache_path, remotefile)
|
6
|
+
path = path_for(cache_path, remotefile)
|
7
|
+
path.exist?
|
8
|
+
end
|
9
|
+
|
10
|
+
def exist?(localfile)
|
11
|
+
File.exist?(localfile)
|
12
|
+
end
|
13
|
+
|
14
|
+
def write(cache_path, localfile, remotefile)
|
15
|
+
path = path_for(cache_path, remotefile)
|
16
|
+
FileUtils.cp(localfile, path)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Array
|
20
|
+
def compare(cache_path, localfile, remotefile)
|
21
|
+
return [] unless cached?(cache_path, remotefile)
|
22
|
+
|
23
|
+
path = path_for(cache_path, remotefile)
|
24
|
+
diff = StringUtils.diff(localfile, path)
|
25
|
+
diff.split("\n")
|
26
|
+
end
|
27
|
+
|
28
|
+
# def expire(cache_path, remotefile)
|
29
|
+
# path = path_for(cache_path, remotefile)
|
30
|
+
# path.exist? && path.delete
|
31
|
+
# end
|
32
|
+
|
33
|
+
def path_for(cache_path, remotefile)
|
34
|
+
path = cache_path.join('put')
|
35
|
+
FileUtils.mkdir_p(path)
|
36
|
+
path.join(remotefile.tr('/', '-'))
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,287 @@
|
|
1
|
+
# https://apidock.com/ruby/Net/FTP
|
2
|
+
require 'net/ftp'
|
3
|
+
|
4
|
+
module Ftpmock
|
5
|
+
class NetFtpProxy
|
6
|
+
# Stubbers
|
7
|
+
|
8
|
+
Real = begin
|
9
|
+
Net::FTP
|
10
|
+
rescue NameError
|
11
|
+
nil
|
12
|
+
end
|
13
|
+
|
14
|
+
# inspired by https://github.com/bblimke/webmock/blob/master/lib/webmock/http_lib_caches/net_http.rb
|
15
|
+
def self.on!
|
16
|
+
unless Real
|
17
|
+
yield
|
18
|
+
return
|
19
|
+
end
|
20
|
+
|
21
|
+
Net.send(:remove_const, :FTP)
|
22
|
+
Net.const_set(:FTP, self)
|
23
|
+
if block_given?
|
24
|
+
yield
|
25
|
+
off!
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.off!
|
30
|
+
return unless Real
|
31
|
+
|
32
|
+
Net.send(:remove_const, :FTP)
|
33
|
+
Net.const_set(:FTP, Real)
|
34
|
+
end
|
35
|
+
|
36
|
+
PORT = 21
|
37
|
+
|
38
|
+
# Instance Methods
|
39
|
+
|
40
|
+
# inspired by https://apidock.com/ruby/v2_6_3/Net/FTP/open/class
|
41
|
+
def self.open(host, *args)
|
42
|
+
if block_given?
|
43
|
+
proxy = new(host, *args)
|
44
|
+
begin
|
45
|
+
yield proxy
|
46
|
+
ensure
|
47
|
+
proxy.close
|
48
|
+
end
|
49
|
+
else
|
50
|
+
new(host, *args)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# inspired by https://apidock.com/ruby/v2_6_3/Net/FTP/new/class
|
55
|
+
def initialize(host = nil, *args)
|
56
|
+
@options = args.last.is_a?(Hash) ? args.pop : {}
|
57
|
+
@host = host
|
58
|
+
@configuration = @options[:configuration] || Ftpmock.configuration
|
59
|
+
|
60
|
+
if args.size == 2
|
61
|
+
@options[:username] ||= args[0]
|
62
|
+
@options[:password] ||= args[1]
|
63
|
+
end
|
64
|
+
|
65
|
+
if host
|
66
|
+
connect(host, @options[:port] || PORT)
|
67
|
+
login(@options[:username], @options[:password]) if @options[:username]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def real
|
72
|
+
@real ||= Real.new
|
73
|
+
end
|
74
|
+
|
75
|
+
attr_writer :cache, :real
|
76
|
+
attr_reader :configuration,
|
77
|
+
:host,
|
78
|
+
:port,
|
79
|
+
:username,
|
80
|
+
:password
|
81
|
+
|
82
|
+
# connection methods
|
83
|
+
|
84
|
+
def connect(host, port = PORT)
|
85
|
+
@real_connected = false
|
86
|
+
@host = host
|
87
|
+
@port = port
|
88
|
+
|
89
|
+
StringUtils.all_present?(host, port) || _raise_not_connected
|
90
|
+
|
91
|
+
@cache_connected = true
|
92
|
+
true
|
93
|
+
end
|
94
|
+
|
95
|
+
def login(username, password)
|
96
|
+
@cache = nil
|
97
|
+
@real_logged = false
|
98
|
+
@username = username
|
99
|
+
@password = password
|
100
|
+
|
101
|
+
_init_cache
|
102
|
+
|
103
|
+
@cache_logged = true
|
104
|
+
true
|
105
|
+
end
|
106
|
+
|
107
|
+
def _real_connect_and_login
|
108
|
+
@cache_connected || _raise_not_connected
|
109
|
+
@cache_logged || _raise_not_connected
|
110
|
+
|
111
|
+
@real_connected || real.connect(*_real_connect_args)
|
112
|
+
@real_connected = true
|
113
|
+
|
114
|
+
@real_logged || real.login(*_real_login_args)
|
115
|
+
@real_logged = true
|
116
|
+
|
117
|
+
if @chdir
|
118
|
+
@real.chdir(@chdir)
|
119
|
+
@chdir = nil
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def _real_connect_args
|
124
|
+
[host, port]
|
125
|
+
end
|
126
|
+
|
127
|
+
def _real_login_args
|
128
|
+
[username, password].select { |string| StringUtils.present?(string) }
|
129
|
+
end
|
130
|
+
|
131
|
+
def _init_cache
|
132
|
+
credentials = [host, port, username, password]
|
133
|
+
@cache = Cache.new(configuration, credentials)
|
134
|
+
end
|
135
|
+
|
136
|
+
def cache
|
137
|
+
@cache || _raise_not_connected
|
138
|
+
end
|
139
|
+
|
140
|
+
def _raise_not_connected
|
141
|
+
raise(Net::FTPConnectionError, 'not connected')
|
142
|
+
end
|
143
|
+
|
144
|
+
# directory methods
|
145
|
+
|
146
|
+
# https://docs.ruby-lang.org/en/2.0.0/Net/FTP.html#method-i-chdir
|
147
|
+
def chdir(dirname = nil)
|
148
|
+
cache.chdir(dirname)
|
149
|
+
end
|
150
|
+
|
151
|
+
def pwd
|
152
|
+
chdir
|
153
|
+
end
|
154
|
+
|
155
|
+
def getdir
|
156
|
+
chdir
|
157
|
+
end
|
158
|
+
|
159
|
+
# list methods
|
160
|
+
|
161
|
+
def list(*args)
|
162
|
+
cache.list(*args) do
|
163
|
+
_real_connect_and_login
|
164
|
+
real.list(*args)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# get methods
|
169
|
+
|
170
|
+
def get(remotefile, localfile = File.basename(remotefile))
|
171
|
+
cache.get(remotefile, localfile) do
|
172
|
+
_real_connect_and_login
|
173
|
+
real.get(remotefile, localfile)
|
174
|
+
end
|
175
|
+
|
176
|
+
true
|
177
|
+
end
|
178
|
+
|
179
|
+
def gettextfile(remotefile, localfile = File.basename(remotefile))
|
180
|
+
cache.get(remotefile, localfile) do
|
181
|
+
_real_connect_and_login
|
182
|
+
real.gettextfile(remotefile, localfile)
|
183
|
+
end
|
184
|
+
|
185
|
+
true
|
186
|
+
end
|
187
|
+
|
188
|
+
def getbinaryfile(remotefile, localfile = File.basename(remotefile))
|
189
|
+
cache.get(remotefile, localfile) do
|
190
|
+
_real_connect_and_login
|
191
|
+
real.getbinaryfile(remotefile, localfile)
|
192
|
+
end
|
193
|
+
|
194
|
+
true
|
195
|
+
end
|
196
|
+
|
197
|
+
# put methods
|
198
|
+
|
199
|
+
# TODO: block
|
200
|
+
# https://docs.ruby-lang.org/en/2.0.0/Net/FTP.html#method-i-put
|
201
|
+
def put(localfile, remotefile = File.basename(localfile))
|
202
|
+
cache.put(localfile, remotefile) do
|
203
|
+
_real_connect_and_login
|
204
|
+
real.put(localfile, remotefile)
|
205
|
+
end
|
206
|
+
|
207
|
+
true
|
208
|
+
end
|
209
|
+
|
210
|
+
# TODO: block
|
211
|
+
def puttextfile(localfile, remotefile = File.basename(localfile))
|
212
|
+
cache.put(localfile, remotefile) do
|
213
|
+
_real_connect_and_login
|
214
|
+
real.puttextfile(localfile, remotefile)
|
215
|
+
end
|
216
|
+
|
217
|
+
true
|
218
|
+
end
|
219
|
+
|
220
|
+
# TODO: block
|
221
|
+
def putbinaryfile(localfile, remotefile = File.basename(localfile))
|
222
|
+
cache.put(localfile, remotefile) do
|
223
|
+
_real_connect_and_login
|
224
|
+
real.putbinaryfile(localfile, remotefile)
|
225
|
+
end
|
226
|
+
|
227
|
+
true
|
228
|
+
end
|
229
|
+
|
230
|
+
# TODO: Methods Not Implemented
|
231
|
+
|
232
|
+
# abort
|
233
|
+
# acct
|
234
|
+
# binary
|
235
|
+
# binary=
|
236
|
+
# close
|
237
|
+
# closed?
|
238
|
+
# debug_mode
|
239
|
+
# debug_mode=
|
240
|
+
# delete
|
241
|
+
# help
|
242
|
+
# last_response
|
243
|
+
# last_response_code
|
244
|
+
# mdtm
|
245
|
+
# mkdir
|
246
|
+
# mlsd
|
247
|
+
# mlst
|
248
|
+
# mon_enter
|
249
|
+
# mon_exit
|
250
|
+
# mon_locked?
|
251
|
+
# mon_owned?
|
252
|
+
# mon_synchronize
|
253
|
+
# mon_try_enter
|
254
|
+
# mtime
|
255
|
+
# new_cond
|
256
|
+
# nlst
|
257
|
+
# noop
|
258
|
+
# open_timeout
|
259
|
+
# open_timeout=
|
260
|
+
# passive
|
261
|
+
# passive=
|
262
|
+
# quit
|
263
|
+
# read_timeout
|
264
|
+
# read_timeout=
|
265
|
+
# rename
|
266
|
+
# resume
|
267
|
+
# resume=
|
268
|
+
# retrbinary
|
269
|
+
# retrlines
|
270
|
+
# return_code
|
271
|
+
# return_code=
|
272
|
+
# rmdir
|
273
|
+
# sendcmd
|
274
|
+
# set_socket
|
275
|
+
# site
|
276
|
+
# size
|
277
|
+
# ssl_handshake_timeout
|
278
|
+
# ssl_handshake_timeout=
|
279
|
+
# status
|
280
|
+
# storbinary
|
281
|
+
# storlines
|
282
|
+
# system
|
283
|
+
# voidcmd
|
284
|
+
# welcome
|
285
|
+
include MethodMissingMixin
|
286
|
+
end
|
287
|
+
end
|