|
@@ -0,0 +1,276 @@
|
|
1
|
+import io
|
|
2
|
+import os
|
|
3
|
+import pytest
|
|
4
|
+import requests
|
|
5
|
+import subprocess
|
|
6
|
+import tarfile
|
|
7
|
+import time
|
|
8
|
+import urllib3
|
|
9
|
+
|
|
10
|
+# crudly written to learn more about pytest and to have a base for refactoring
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+@pytest.fixture
|
|
14
|
+def run_servefile():
|
|
15
|
+ instances = []
|
|
16
|
+
|
|
17
|
+ def _run_servefile(args, **kwargs):
|
|
18
|
+ if not isinstance(args, list):
|
|
19
|
+ args = [args]
|
|
20
|
+ print("running with args", args)
|
|
21
|
+ p = subprocess.Popen(['servefile'] + args, **kwargs)
|
|
22
|
+ time.sleep(kwargs.get('timeout', 0.3))
|
|
23
|
+ instances.append(p)
|
|
24
|
+
|
|
25
|
+ return p
|
|
26
|
+
|
|
27
|
+ yield _run_servefile
|
|
28
|
+
|
|
29
|
+ for instance in instances:
|
|
30
|
+ try:
|
|
31
|
+ instance.terminate()
|
|
32
|
+ except OSError:
|
|
33
|
+ pass
|
|
34
|
+ instance.wait()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+@pytest.fixture
|
|
38
|
+def datadir(tmp_path):
|
|
39
|
+ def _datadir(data, path=None):
|
|
40
|
+ path = path or tmp_path
|
|
41
|
+ for k, v in data.items():
|
|
42
|
+ if isinstance(v, dict):
|
|
43
|
+ new_path = path / k
|
|
44
|
+ new_path.mkdir()
|
|
45
|
+ _datadir(v, new_path)
|
|
46
|
+ else:
|
|
47
|
+ if hasattr(v, 'decode'):
|
|
48
|
+ v = v.decode() # python2 compability
|
|
49
|
+ (path / k).write_text(v)
|
|
50
|
+
|
|
51
|
+ return path
|
|
52
|
+ return _datadir
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+def make_request(path='/', host='localhost', port=8080, method='get', protocol='http', **kwargs):
|
|
56
|
+ url = '{}://{}:{}{}'.format(protocol, host, port, path)
|
|
57
|
+ print('Calling {} on {} with {}'.format(method, url, kwargs))
|
|
58
|
+ r = getattr(requests, method)(url, **kwargs)
|
|
59
|
+
|
|
60
|
+ return r
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+def check_download(expected_data=None, path='/', fname=None, status_code=200, **kwargs):
|
|
64
|
+ if fname is None:
|
|
65
|
+ fname = os.path.basename(path)
|
|
66
|
+ r = make_request(path, **kwargs)
|
|
67
|
+ assert r.status_code == 200
|
|
68
|
+ assert r.text == expected_data
|
|
69
|
+ assert r.headers.get('Content-Type') == 'application/octet-stream'
|
|
70
|
+ if fname:
|
|
71
|
+ assert r.headers.get('Content-Disposition') == 'attachment; filename="{}"'.format(fname)
|
|
72
|
+ assert r.headers.get('Content-Transfer-Encoding') == 'binary'
|
|
73
|
+
|
|
74
|
+ return r # for additional tests
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+def test_version(run_servefile):
|
|
78
|
+ # we expect the version on stdout (python3.4+) or stderr(python2.6-3.3)
|
|
79
|
+ s = run_servefile('--version', stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
80
|
+ s.wait()
|
|
81
|
+ version = s.stdout.readline().decode().strip()
|
|
82
|
+
|
|
83
|
+ # hardcode version as string until servefile is a module
|
|
84
|
+ assert version == 'servefile 0.4.4'
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+def test_correct_headers(run_servefile, datadir):
|
|
88
|
+ data = "NOOT NOOT"
|
|
89
|
+ p = datadir({'testfile': data}) / 'testfile'
|
|
90
|
+ run_servefile(str(p))
|
|
91
|
+
|
|
92
|
+ r = make_request()
|
|
93
|
+ assert r.status_code == 200
|
|
94
|
+ assert r.headers.get('Content-Type') == 'application/octet-stream'
|
|
95
|
+ assert r.headers.get('Content-Disposition') == 'attachment; filename="testfile"'
|
|
96
|
+ assert r.headers.get('Content-Transfer-Encoding') == 'binary'
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+def test_redirect_and_download(run_servefile, datadir):
|
|
100
|
+ data = "NOOT NOOT"
|
|
101
|
+ p = datadir({'testfile': data}) / 'testfile'
|
|
102
|
+ run_servefile(str(p))
|
|
103
|
+
|
|
104
|
+ # redirect
|
|
105
|
+ r = make_request(allow_redirects=False)
|
|
106
|
+ assert r.status_code == 302
|
|
107
|
+ assert r.headers.get('Location') == '/testfile'
|
|
108
|
+
|
|
109
|
+ # normal download
|
|
110
|
+ check_download(data, fname='testfile')
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+def test_specify_port(run_servefile, datadir):
|
|
114
|
+ data = "NOOT NOOT"
|
|
115
|
+ p = datadir({'testfile': data}) / 'testfile'
|
|
116
|
+ run_servefile([str(p), '-p', '8081'])
|
|
117
|
+
|
|
118
|
+ check_download(data, fname='testfile', port=8081)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+def test_big_download(run_servefile, datadir):
|
|
122
|
+ # test with about 10 mb of data
|
|
123
|
+ data = "x" * (10 * 1024 ** 2)
|
|
124
|
+ p = datadir({'testfile': data}) / 'testfile'
|
|
125
|
+ run_servefile(str(p))
|
|
126
|
+
|
|
127
|
+ check_download(data, fname='testfile')
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+def test_authentication(run_servefile, datadir):
|
|
131
|
+ data = "NOOT NOOT"
|
|
132
|
+ p = datadir({'testfile': data}) / 'testfile'
|
|
133
|
+
|
|
134
|
+ run_servefile([str(p), '-a', 'user:password'])
|
|
135
|
+ for auth in [('foo', 'bar'), ('user', 'wrong'), ('unknown', 'password')]:
|
|
136
|
+ r = make_request(auth=auth)
|
|
137
|
+ assert '401 - Unauthorized' in r.text
|
|
138
|
+ assert r.status_code == 401
|
|
139
|
+
|
|
140
|
+ check_download(data, fname='testfile', auth=('user', 'password'))
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+def test_serve_directory(run_servefile, datadir):
|
|
144
|
+ d = {
|
|
145
|
+ 'foo': {'kratzbaum': 'cat', 'I like Cats!': 'kitteh', '&&&&&&&': 'wheee'},
|
|
146
|
+ 'bar': {'thisisaverylongfilenamefortestingthatthisstillworksproperly': 'jup!'},
|
|
147
|
+ 'noot': 'still data in here',
|
|
148
|
+ 'bigfile': 'x' * (10 * 1024 ** 2),
|
|
149
|
+ }
|
|
150
|
+ p = datadir(d)
|
|
151
|
+ run_servefile([str(p), '-l'])
|
|
152
|
+
|
|
153
|
+ # check if all files are in directory listing
|
|
154
|
+ # (could be made more sophisticated with beautifulsoup)
|
|
155
|
+ for path in '/', '/../':
|
|
156
|
+ r = make_request(path)
|
|
157
|
+ for k in d:
|
|
158
|
+ assert k in r.text
|
|
159
|
+
|
|
160
|
+ for fname, content in d['foo'].items():
|
|
161
|
+ check_download(content, '/foo/' + fname)
|
|
162
|
+
|
|
163
|
+ r = make_request('/unknown')
|
|
164
|
+ assert r.status_code == 404
|
|
165
|
+
|
|
166
|
+ # download
|
|
167
|
+ check_download('jup!', '/bar/thisisaverylongfilenamefortestingthatthisstillworksproperly')
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+def test_upload(run_servefile, tmp_path):
|
|
171
|
+ data = ('this is my live now\n'
|
|
172
|
+ 'uploading strings to servers\n'
|
|
173
|
+ 'so very joyful')
|
|
174
|
+ uploaddir = tmp_path / 'upload'
|
|
175
|
+ # check that uploaddir does not exist before servefile is started
|
|
176
|
+ assert not uploaddir.is_dir()
|
|
177
|
+
|
|
178
|
+ run_servefile(['-u', str(uploaddir)])
|
|
179
|
+
|
|
180
|
+ # check that servefile created the directory
|
|
181
|
+ assert uploaddir.is_dir()
|
|
182
|
+
|
|
183
|
+ # check upload form present
|
|
184
|
+ r = make_request()
|
|
185
|
+ assert r.status_code == 200
|
|
186
|
+ assert 'multipart/form-data' in r.text
|
|
187
|
+
|
|
188
|
+ # upload file
|
|
189
|
+ files = {'file': ('haiku.txt', data)}
|
|
190
|
+ r = make_request(method='post', files=files)
|
|
191
|
+ assert 'Thanks' in r.text
|
|
192
|
+ assert r.status_code == 200
|
|
193
|
+ with open(str(uploaddir / 'haiku.txt')) as f:
|
|
194
|
+ assert f.read() == data
|
|
195
|
+
|
|
196
|
+ # upload file AGAIN!! (and check it is available unter a different name)
|
|
197
|
+ files = {'file': ('haiku.txt', data)}
|
|
198
|
+ r = make_request(method='post', files=files)
|
|
199
|
+ assert r.status_code == 200
|
|
200
|
+ with open(str(uploaddir / 'haiku.txt(1)')) as f:
|
|
201
|
+ assert f.read() == data
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+def test_upload_size_limit(run_servefile, tmp_path):
|
|
205
|
+ uploaddir = tmp_path / 'upload'
|
|
206
|
+ run_servefile(['-s', '2kb', '-u', str(uploaddir)])
|
|
207
|
+
|
|
208
|
+ # upload file that is too big
|
|
209
|
+ files = {'file': ('toobig', "x" * 2049)}
|
|
210
|
+ r = make_request(method='post', files=files)
|
|
211
|
+ assert 'Your file was too big' in r.text
|
|
212
|
+ assert r.status_code == 413
|
|
213
|
+ assert not (uploaddir / 'toobig').exists()
|
|
214
|
+
|
|
215
|
+ # upload file that should fit
|
|
216
|
+ # the size has to be smaller than 2kb, as the sent size also includes mime-headers
|
|
217
|
+ files = {'file': ('justright', "x" * 1900)}
|
|
218
|
+ r = make_request(method='post', files=files)
|
|
219
|
+ assert r.status_code == 200
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+def test_tar_mode(run_servefile, datadir):
|
|
223
|
+ d = {
|
|
224
|
+ 'foo': {
|
|
225
|
+ 'bar': 'hello testmode my old friend',
|
|
226
|
+ 'baz': 'you came to test me once again',
|
|
227
|
+ }
|
|
228
|
+ }
|
|
229
|
+ p = datadir(d)
|
|
230
|
+ run_servefile(['-t', str(p / 'foo')])
|
|
231
|
+
|
|
232
|
+ # test redirect?
|
|
233
|
+
|
|
234
|
+ # test contents of tar file
|
|
235
|
+ r = make_request()
|
|
236
|
+ assert r.status_code == 200
|
|
237
|
+ tar = tarfile.open(fileobj=io.BytesIO(r.content))
|
|
238
|
+ assert len(tar.getmembers()) == 3
|
|
239
|
+ assert tar.getmember('foo').isdir()
|
|
240
|
+ for filename, content in d['foo'].items():
|
|
241
|
+ info = tar.getmember('foo/{}'.format(filename))
|
|
242
|
+ assert info.isfile
|
|
243
|
+ assert tar.extractfile(info.path).read().decode() == content
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+def test_tar_compression(run_servefile, datadir):
|
|
247
|
+ d = {'foo': 'blubb'}
|
|
248
|
+ p = datadir(d)
|
|
249
|
+ run_servefile(['-c', 'gzip', '-t', str(p / 'foo')])
|
|
250
|
+
|
|
251
|
+ r = make_request()
|
|
252
|
+ assert r.status_code == 200
|
|
253
|
+ tar = tarfile.open(fileobj=io.BytesIO(r.content), mode='r:gz')
|
|
254
|
+ assert len(tar.getmembers()) == 1
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+def test_https(run_servefile, datadir):
|
|
258
|
+ data = "NOOT NOOT"
|
|
259
|
+ p = datadir({'testfile': data}) / 'testfile'
|
|
260
|
+ run_servefile(['--ssl', str(p)])
|
|
261
|
+ time.sleep(0.2) # time for generating ssl certificates
|
|
262
|
+
|
|
263
|
+ # fingerprint = None
|
|
264
|
+ # while not fingerprint:
|
|
265
|
+ # line = s.stdout.readline()
|
|
266
|
+ # print(line)
|
|
267
|
+ # # if we find this line we went too far...
|
|
268
|
+ # assert not line.startswith("Some addresses this file will be available at")
|
|
269
|
+
|
|
270
|
+ # if line.startswith("SHA1 fingerprint"):
|
|
271
|
+ # fingerprint = line.replace("SHA1 fingerprint: ", "").strip()
|
|
272
|
+ # break
|
|
273
|
+
|
|
274
|
+ # assert fingerprint
|
|
275
|
+ urllib3.disable_warnings()
|
|
276
|
+ check_download(data, protocol='https', verify=False)
|