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.
@@ -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,9 @@
1
+ module Ftpmock
2
+ module MethodMissingMixin
3
+ def method_missing(name, *_args)
4
+ msg = " Missing method `#{self.class.name}##{name}`. Ignoring"
5
+ configuration.verbose && VerboseUtils.warn(msg)
6
+ nil
7
+ end
8
+ end
9
+ 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