test_downloader.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. #!/usr/bin/env python3
  2. # coding=utf-8
  3. import os
  4. import re
  5. from pathlib import Path
  6. from unittest.mock import MagicMock, patch
  7. import praw.models
  8. import pytest
  9. from bdfr.__main__ import setup_logging
  10. from bdfr.configuration import Configuration
  11. from bdfr.connector import RedditConnector
  12. from bdfr.downloader import RedditDownloader
  13. @pytest.fixture()
  14. def args() -> Configuration:
  15. args = Configuration()
  16. args.time_format = 'ISO'
  17. return args
  18. @pytest.fixture()
  19. def downloader_mock(args: Configuration):
  20. downloader_mock = MagicMock()
  21. downloader_mock.args = args
  22. downloader_mock._sanitise_subreddit_name = RedditConnector.sanitise_subreddit_name
  23. downloader_mock._split_args_input = RedditConnector.split_args_input
  24. downloader_mock.master_hash_list = {}
  25. return downloader_mock
  26. @pytest.mark.parametrize(('test_ids', 'test_excluded', 'expected_len'), (
  27. (('aaaaaa',), (), 1),
  28. (('aaaaaa',), ('aaaaaa',), 0),
  29. ((), ('aaaaaa',), 0),
  30. (('aaaaaa', 'bbbbbb'), ('aaaaaa',), 1),
  31. (('aaaaaa', 'bbbbbb', 'cccccc'), ('aaaaaa',), 2),
  32. ))
  33. @patch('bdfr.site_downloaders.download_factory.DownloadFactory.pull_lever')
  34. def test_excluded_ids(
  35. mock_function: MagicMock,
  36. test_ids: tuple[str],
  37. test_excluded: tuple[str],
  38. expected_len: int,
  39. downloader_mock: MagicMock,
  40. ):
  41. downloader_mock.excluded_submission_ids = test_excluded
  42. mock_function.return_value = MagicMock()
  43. mock_function.return_value.__name__ = 'test'
  44. test_submissions = []
  45. for test_id in test_ids:
  46. m = MagicMock()
  47. m.id = test_id
  48. m.subreddit.display_name.return_value = 'https://www.example.com/'
  49. m.__class__ = praw.models.Submission
  50. test_submissions.append(m)
  51. downloader_mock.reddit_lists = [test_submissions]
  52. for submission in test_submissions:
  53. RedditDownloader._download_submission(downloader_mock, submission)
  54. assert mock_function.call_count == expected_len
  55. @pytest.mark.online
  56. @pytest.mark.reddit
  57. @pytest.mark.parametrize('test_submission_id', (
  58. 'm1hqw6',
  59. ))
  60. def test_mark_hard_link(
  61. test_submission_id: str,
  62. downloader_mock: MagicMock,
  63. tmp_path: Path,
  64. reddit_instance: praw.Reddit
  65. ):
  66. downloader_mock.reddit_instance = reddit_instance
  67. downloader_mock.args.make_hard_links = True
  68. downloader_mock.download_directory = tmp_path
  69. downloader_mock.args.folder_scheme = ''
  70. downloader_mock.args.file_scheme = '{POSTID}'
  71. downloader_mock.file_name_formatter = RedditConnector.create_file_name_formatter(downloader_mock)
  72. submission = downloader_mock.reddit_instance.submission(id=test_submission_id)
  73. original = Path(tmp_path, f'{test_submission_id}.png')
  74. RedditDownloader._download_submission(downloader_mock, submission)
  75. assert original.exists()
  76. downloader_mock.args.file_scheme = 'test2_{POSTID}'
  77. downloader_mock.file_name_formatter = RedditConnector.create_file_name_formatter(downloader_mock)
  78. RedditDownloader._download_submission(downloader_mock, submission)
  79. test_file_1_stats = original.stat()
  80. test_file_2_inode = Path(tmp_path, f'test2_{test_submission_id}.png').stat().st_ino
  81. assert test_file_1_stats.st_nlink == 2
  82. assert test_file_1_stats.st_ino == test_file_2_inode
  83. @pytest.mark.online
  84. @pytest.mark.reddit
  85. @pytest.mark.parametrize(('test_submission_id', 'test_creation_date'), (
  86. ('ndzz50', 1621204841.0),
  87. ))
  88. def test_file_creation_date(
  89. test_submission_id: str,
  90. test_creation_date: float,
  91. downloader_mock: MagicMock,
  92. tmp_path: Path,
  93. reddit_instance: praw.Reddit
  94. ):
  95. downloader_mock.reddit_instance = reddit_instance
  96. downloader_mock.download_directory = tmp_path
  97. downloader_mock.args.folder_scheme = ''
  98. downloader_mock.args.file_scheme = '{POSTID}'
  99. downloader_mock.file_name_formatter = RedditConnector.create_file_name_formatter(downloader_mock)
  100. submission = downloader_mock.reddit_instance.submission(id=test_submission_id)
  101. RedditDownloader._download_submission(downloader_mock, submission)
  102. for file_path in Path(tmp_path).iterdir():
  103. file_stats = os.stat(file_path)
  104. assert file_stats.st_mtime == test_creation_date
  105. def test_search_existing_files():
  106. results = RedditDownloader.scan_existing_files(Path('.'))
  107. assert len(results.keys()) != 0
  108. @pytest.mark.online
  109. @pytest.mark.reddit
  110. @pytest.mark.parametrize(('test_submission_id', 'test_hash'), (
  111. ('m1hqw6', 'a912af8905ae468e0121e9940f797ad7'),
  112. ))
  113. def test_download_submission_hash_exists(
  114. test_submission_id: str,
  115. test_hash: str,
  116. downloader_mock: MagicMock,
  117. reddit_instance: praw.Reddit,
  118. tmp_path: Path,
  119. capsys: pytest.CaptureFixture
  120. ):
  121. setup_logging(3)
  122. downloader_mock.reddit_instance = reddit_instance
  123. downloader_mock.download_filter.check_url.return_value = True
  124. downloader_mock.args.folder_scheme = ''
  125. downloader_mock.args.no_dupes = True
  126. downloader_mock.file_name_formatter = RedditConnector.create_file_name_formatter(downloader_mock)
  127. downloader_mock.download_directory = tmp_path
  128. downloader_mock.master_hash_list = {test_hash: None}
  129. submission = downloader_mock.reddit_instance.submission(id=test_submission_id)
  130. RedditDownloader._download_submission(downloader_mock, submission)
  131. folder_contents = list(tmp_path.iterdir())
  132. output = capsys.readouterr()
  133. assert len(folder_contents) == 0
  134. assert re.search(r'Resource hash .*? downloaded elsewhere', output.out)
  135. @pytest.mark.online
  136. @pytest.mark.reddit
  137. def test_download_submission_file_exists(
  138. downloader_mock: MagicMock,
  139. reddit_instance: praw.Reddit,
  140. tmp_path: Path,
  141. capsys: pytest.CaptureFixture
  142. ):
  143. setup_logging(3)
  144. downloader_mock.reddit_instance = reddit_instance
  145. downloader_mock.download_filter.check_url.return_value = True
  146. downloader_mock.args.folder_scheme = ''
  147. downloader_mock.file_name_formatter = RedditConnector.create_file_name_formatter(downloader_mock)
  148. downloader_mock.download_directory = tmp_path
  149. submission = downloader_mock.reddit_instance.submission(id='m1hqw6')
  150. Path(tmp_path, 'Arneeman_Metagaming isn\'t always a bad thing_m1hqw6.png').touch()
  151. RedditDownloader._download_submission(downloader_mock, submission)
  152. folder_contents = list(tmp_path.iterdir())
  153. output = capsys.readouterr()
  154. assert len(folder_contents) == 1
  155. assert 'Arneeman_Metagaming isn\'t always a bad thing_m1hqw6.png'\
  156. ' from submission m1hqw6 already exists' in output.out
  157. @pytest.mark.online
  158. @pytest.mark.reddit
  159. @pytest.mark.parametrize(('test_submission_id', 'expected_files_len'), (
  160. ('ljyy27', 4),
  161. ))
  162. def test_download_submission(
  163. test_submission_id: str,
  164. expected_files_len: int,
  165. downloader_mock: MagicMock,
  166. reddit_instance: praw.Reddit,
  167. tmp_path: Path):
  168. downloader_mock.reddit_instance = reddit_instance
  169. downloader_mock.download_filter.check_url.return_value = True
  170. downloader_mock.args.folder_scheme = ''
  171. downloader_mock.file_name_formatter = RedditConnector.create_file_name_formatter(downloader_mock)
  172. downloader_mock.download_directory = tmp_path
  173. submission = downloader_mock.reddit_instance.submission(id=test_submission_id)
  174. RedditDownloader._download_submission(downloader_mock, submission)
  175. folder_contents = list(tmp_path.iterdir())
  176. assert len(folder_contents) == expected_files_len
  177. @pytest.mark.online
  178. @pytest.mark.reddit
  179. @pytest.mark.parametrize(('test_submission_id', 'min_score'), (
  180. ('ljyy27', 1),
  181. ))
  182. def test_download_submission_min_score_above(
  183. test_submission_id: str,
  184. min_score: int,
  185. downloader_mock: MagicMock,
  186. reddit_instance: praw.Reddit,
  187. tmp_path: Path,
  188. capsys: pytest.CaptureFixture,
  189. ):
  190. setup_logging(3)
  191. downloader_mock.reddit_instance = reddit_instance
  192. downloader_mock.download_filter.check_url.return_value = True
  193. downloader_mock.args.folder_scheme = ''
  194. downloader_mock.args.min_score = min_score
  195. downloader_mock.file_name_formatter = RedditConnector.create_file_name_formatter(downloader_mock)
  196. downloader_mock.download_directory = tmp_path
  197. submission = downloader_mock.reddit_instance.submission(id=test_submission_id)
  198. RedditDownloader._download_submission(downloader_mock, submission)
  199. output = capsys.readouterr()
  200. assert 'filtered due to score' not in output.out
  201. @pytest.mark.online
  202. @pytest.mark.reddit
  203. @pytest.mark.parametrize(('test_submission_id', 'min_score'), (
  204. ('ljyy27', 25),
  205. ))
  206. def test_download_submission_min_score_below(
  207. test_submission_id: str,
  208. min_score: int,
  209. downloader_mock: MagicMock,
  210. reddit_instance: praw.Reddit,
  211. tmp_path: Path,
  212. capsys: pytest.CaptureFixture,
  213. ):
  214. setup_logging(3)
  215. downloader_mock.reddit_instance = reddit_instance
  216. downloader_mock.download_filter.check_url.return_value = True
  217. downloader_mock.args.folder_scheme = ''
  218. downloader_mock.args.min_score = min_score
  219. downloader_mock.file_name_formatter = RedditConnector.create_file_name_formatter(downloader_mock)
  220. downloader_mock.download_directory = tmp_path
  221. submission = downloader_mock.reddit_instance.submission(id=test_submission_id)
  222. RedditDownloader._download_submission(downloader_mock, submission)
  223. output = capsys.readouterr()
  224. assert 'filtered due to score' in output.out
  225. @pytest.mark.online
  226. @pytest.mark.reddit
  227. @pytest.mark.parametrize(('test_submission_id', 'max_score'), (
  228. ('ljyy27', 25),
  229. ))
  230. def test_download_submission_max_score_below(
  231. test_submission_id: str,
  232. max_score: int,
  233. downloader_mock: MagicMock,
  234. reddit_instance: praw.Reddit,
  235. tmp_path: Path,
  236. capsys: pytest.CaptureFixture,
  237. ):
  238. setup_logging(3)
  239. downloader_mock.reddit_instance = reddit_instance
  240. downloader_mock.download_filter.check_url.return_value = True
  241. downloader_mock.args.folder_scheme = ''
  242. downloader_mock.args.max_score = max_score
  243. downloader_mock.file_name_formatter = RedditConnector.create_file_name_formatter(downloader_mock)
  244. downloader_mock.download_directory = tmp_path
  245. submission = downloader_mock.reddit_instance.submission(id=test_submission_id)
  246. RedditDownloader._download_submission(downloader_mock, submission)
  247. output = capsys.readouterr()
  248. assert 'filtered due to score' not in output.out
  249. @pytest.mark.online
  250. @pytest.mark.reddit
  251. @pytest.mark.parametrize(('test_submission_id', 'max_score'), (
  252. ('ljyy27', 1),
  253. ))
  254. def test_download_submission_max_score_above(
  255. test_submission_id: str,
  256. max_score: int,
  257. downloader_mock: MagicMock,
  258. reddit_instance: praw.Reddit,
  259. tmp_path: Path,
  260. capsys: pytest.CaptureFixture,
  261. ):
  262. setup_logging(3)
  263. downloader_mock.reddit_instance = reddit_instance
  264. downloader_mock.download_filter.check_url.return_value = True
  265. downloader_mock.args.folder_scheme = ''
  266. downloader_mock.args.max_score = max_score
  267. downloader_mock.file_name_formatter = RedditConnector.create_file_name_formatter(downloader_mock)
  268. downloader_mock.download_directory = tmp_path
  269. submission = downloader_mock.reddit_instance.submission(id=test_submission_id)
  270. RedditDownloader._download_submission(downloader_mock, submission)
  271. output = capsys.readouterr()
  272. assert 'filtered due to score' in output.out