No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

test_servefile.py 8.6KB

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