diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/conftest.py | 28 | ||||
| -rw-r--r-- | tests/test_client.py | 160 | ||||
| -rw-r--r-- | tests/test_dl.py | 398 | ||||
| -rw-r--r-- | tests/test_file_class.py | 13 | ||||
| -rw-r--r-- | tests/test_logging.py | 110 | ||||
| -rw-r--r-- | tests/test_parse.py | 10 | ||||
| -rw-r--r-- | tests/test_validations.py | 26 | ||||
| -rw-r--r-- | tests/test_wikiget_cli.py | 87 |
8 files changed, 653 insertions, 179 deletions
diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..94fc053 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,28 @@ +# wikiget - CLI tool for downloading files from Wikimedia sites +# Copyright (C) 2023 Cody Logan +# SPDX-License-Identifier: GPL-3.0-or-later +# +# Wikiget is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Wikiget is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Wikiget. If not, see <https://www.gnu.org/licenses/>. + +import pytest +import requests_mock as rm + + +@pytest.fixture +def mock_get(requests_mock: rm.Mocker) -> None: + # fake the download request for File:Example.jpg + requests_mock.get( + "https://upload.wikimedia.org/wikipedia/commons/a/a9/Example.jpg", + text="data", + ) diff --git a/tests/test_client.py b/tests/test_client.py index 9b1b8a4..4cbf702 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -16,40 +16,160 @@ # along with Wikiget. If not, see <https://www.gnu.org/licenses/>. import logging -from unittest.mock import MagicMock, patch +from unittest.mock import MagicMock, patch, sentinel import pytest +from mwclient import APIError, InvalidResponse +from requests import ConnectionError, HTTPError -from wikiget import USER_AGENT +from wikiget import DEFAULT_SITE from wikiget.client import connect_to_site, query_api from wikiget.wikiget import parse_args -# TODO: don't hit the actual API when doing tests -class TestQueryApi: - @patch("mwclient.Site.__new__") - def test_connect_to_site( - self, mock_site: MagicMock, caplog: pytest.LogCaptureFixture - ) -> None: +class TestConnectSite: + # this message is logged when the level is at INFO or below + info_msg = f"Connecting to {DEFAULT_SITE}" + + def test_connect_to_site(self, caplog: pytest.LogCaptureFixture) -> None: """ - The connect_to_site function should create a debug log message recording the + The connect_to_site function should create an info log message recording the name of the site we're connecting to. """ + caplog.set_level(logging.INFO) + args = parse_args(["File:Example.jpg"]) + + with patch("wikiget.client.Site"): + _ = connect_to_site(DEFAULT_SITE, args) + + assert caplog.record_tuples == [ + ("wikiget.client", logging.INFO, self.info_msg), + ] + + def test_connect_to_site_with_creds(self, caplog: pytest.LogCaptureFixture) -> None: + """ + If a username and password are provided, connect_to_site should use them to + log in to the site. + """ + caplog.set_level(logging.INFO) + args = parse_args(["-u", "username", "-p", "password", "File:Example.jpg"]) + + with patch("wikiget.client.Site"): + _ = connect_to_site(DEFAULT_SITE, args) + + # TODO: it should be possible to test if Site.login was called, making the log + # message unnecessary + assert caplog.record_tuples[1] == ( + "wikiget.client", + logging.INFO, + "Attempting to authenticate with credentials", + ) + + def test_connect_to_site_connection_error( + self, caplog: pytest.LogCaptureFixture + ) -> None: + """ + The connect_to_site function should log the correct messages if a + ConnectionError exception is raised. + """ caplog.set_level(logging.DEBUG) - mock_site.return_value = MagicMock() args = parse_args(["File:Example.jpg"]) - _ = connect_to_site("commons.wikimedia.org", args) - assert mock_site.called - assert "Connecting to commons.wikimedia.org" in caplog.text - @pytest.mark.skip(reason="skip tests that query a live API") - def test_query_api(self, caplog: pytest.LogCaptureFixture) -> None: + with patch("wikiget.client.Site") as mock_site: + mock_site.side_effect = ConnectionError("connection error message") + with pytest.raises(ConnectionError): + _ = connect_to_site(DEFAULT_SITE, args) + + assert "Could not connect to specified site" in caplog.text + assert caplog.record_tuples == [ + ("wikiget.client", logging.INFO, self.info_msg), + ("wikiget.client", logging.ERROR, "Could not connect to specified site"), + ("wikiget.client", logging.DEBUG, "connection error message"), + ] + + def test_connect_to_site_http_error(self, caplog: pytest.LogCaptureFixture) -> None: """ - The query_api function should create a debug log message containing the user - agent we're sending to the API. + The connect_to_site function should log the correct messages if an HTTPError + exception is raised. """ caplog.set_level(logging.DEBUG) args = parse_args(["File:Example.jpg"]) - site = connect_to_site("commons.wikimedia.org", args) - _ = query_api("Example.jpg", site) - assert USER_AGENT in caplog.text + + with patch("wikiget.client.Site") as mock_site: + mock_site.side_effect = HTTPError + with pytest.raises(HTTPError): + _ = connect_to_site(DEFAULT_SITE, args) + + assert caplog.record_tuples == [ + ("wikiget.client", logging.INFO, self.info_msg), + ( + "wikiget.client", + logging.ERROR, + "Could not find the specified wiki's api.php. " + "Check the value of --path.", + ), + ("wikiget.client", logging.DEBUG, ""), + ] + + def test_connect_to_site_other_error( + self, caplog: pytest.LogCaptureFixture + ) -> None: + """ + The connect_to_site function should log an error if some other exception type + is raised. + """ + args = parse_args(["File:Example.jpg"]) + + with patch("wikiget.client.Site") as mock_site: + mock_site.side_effect = InvalidResponse + with pytest.raises(InvalidResponse): + _ = connect_to_site("commons.wikimedia.org", args) + + for record in caplog.records: + assert record.levelname == "ERROR" + + +class TestQueryApi: + def test_query_api(self) -> None: + """ + The query_api function should return an Image object when given a name and a + valid Site. + """ + # These mock objects represent Site and Image objects that the real program + # would have created using the MediaWiki API. The Site.images attribute is + # normally populated during Site init, but since we're not doing that, a mock + # dict is created for query_api to parse. + mock_site = MagicMock() + mock_site.images = {"Example.jpg": sentinel.mock_image} + + image = query_api("Example.jpg", mock_site) + + assert image == sentinel.mock_image + + def test_query_api_error(self, caplog: pytest.LogCaptureFixture) -> None: + """ + The query_api function should log an error if an APIError exception is caught, + as well as debug log entries with additional information about the error. + """ + caplog.set_level(logging.DEBUG) + + mock_site = MagicMock() + mock_site.images = MagicMock() + mock_site.images.__getitem__.side_effect = APIError( + "error code", "error info", "error kwargs" + ) + + with pytest.raises(APIError): + _ = query_api("Example.jpg", mock_site) + + assert caplog.record_tuples == [ + ( + "wikiget.client", + logging.ERROR, + "Access denied. Try providing credentials with " + "--username and --password.", + ), + ("wikiget.client", logging.DEBUG, "error code"), + ("wikiget.client", logging.DEBUG, "error info"), + ("wikiget.client", logging.DEBUG, "error kwargs"), + ] diff --git a/tests/test_dl.py b/tests/test_dl.py index a57c3c1..d39c352 100644 --- a/tests/test_dl.py +++ b/tests/test_dl.py @@ -15,26 +15,40 @@ # You should have received a copy of the GNU General Public License # along with Wikiget. If not, see <https://www.gnu.org/licenses/>. +import logging from pathlib import Path -from unittest.mock import MagicMock, patch +from unittest.mock import MagicMock, Mock, patch import pytest +import requests +from mwclient import Site -from wikiget.dl import prep_download, process_download +from wikiget.dl import batch_download, download, prep_download, process_download +from wikiget.exceptions import ParseError from wikiget.file import File from wikiget.wikiget import parse_args -# TODO: don't hit the actual API when doing tests class TestPrepDownload: - @pytest.mark.skip(reason="skip tests that query a live API") - def test_prep_download(self) -> None: - """ - The prep_download function should create a file object. - """ + @patch("wikiget.dl.query_api") + @patch("wikiget.dl.connect_to_site") + def test_prep_download( + self, mock_connect_to_site: MagicMock, mock_query_api: MagicMock + ) -> None: + """The prep_download function should create the expected file object.""" + mock_site = Mock() + mock_image = Mock() + + mock_connect_to_site.return_value = mock_site + mock_query_api.return_value = mock_image + + expected_file = File(name="Example.jpg") + expected_file.image = mock_image + args = parse_args(["File:Example.jpg"]) file = prep_download(args.FILE, args) - assert file is not None + + assert file == expected_file def test_prep_download_with_existing_file(self, tmp_path: Path) -> None: """ @@ -50,59 +64,381 @@ class TestPrepDownload: class TestProcessDownload: @patch("wikiget.dl.batch_download") - def test_batch_download(self, mock_batch_download: MagicMock) -> None: - """ - A successful batch download should not return any errors. - """ - args = parse_args(["-a", "batch.txt"]) + def test_process_batch_download(self, mock_batch_download: MagicMock) -> None: + """A successful batch download should not return any errors.""" mock_batch_download.return_value = 0 + + args = parse_args(["-a", "batch.txt"]) exit_code = process_download(args) - assert mock_batch_download.called + assert exit_code == 0 @patch("wikiget.dl.batch_download") - def test_batch_download_with_errors( + def test_process_batch_download_with_errors( self, mock_batch_download: MagicMock, caplog: pytest.LogCaptureFixture ) -> None: """ Any errors during batch download should create a log message containing the number of errors and result in a non-zero exit code. """ - args = parse_args(["-a", "batch.txt"]) mock_batch_download.return_value = 4 + + args = parse_args(["-a", "batch.txt"]) exit_code = process_download(args) - assert mock_batch_download.called + assert exit_code == 1 assert "4 problems encountered during batch processing" in caplog.text @patch("wikiget.dl.prep_download") @patch("wikiget.dl.download") - def test_single_download( + def test_process_single_download( self, mock_download: MagicMock, mock_prep_download: MagicMock ) -> None: - """ - A successful download should not return any errors. - """ - args = parse_args(["File:Example.jpg"]) + """A successful download should not return any errors.""" mock_download.return_value = 0 - mock_prep_download.return_value = MagicMock(File) + mock_prep_download.return_value = File("Example.jpg") + + args = parse_args(["File:Example.jpg"]) exit_code = process_download(args) - assert mock_prep_download.called - assert mock_download.called + assert exit_code == 0 @patch("wikiget.dl.prep_download") @patch("wikiget.dl.download") - def test_single_download_with_errors( + def test_process_single_download_with_errors( self, mock_download: MagicMock, mock_prep_download: MagicMock ) -> None: + """Any errors during download should result in a non-zero exit code.""" + mock_download.return_value = 1 + mock_prep_download.return_value = File("Example.jpg") + + args = parse_args(["File:Example.jpg"]) + exit_code = process_download(args) + + assert exit_code == 1 + + @patch("wikiget.dl.prep_download") + def test_process_single_download_parse_error( + self, mock_prep_download: MagicMock, caplog: pytest.LogCaptureFixture + ) -> None: """ - Any errors during download should result in a non-zero exit code. + If process_download catches a ParseError, it should create an error log message. """ + mock_prep_download.side_effect = ParseError("error message") + + args = parse_args(["File:Example.jpg"]) + _ = process_download(args) + + assert mock_prep_download.called + assert caplog.record_tuples == [("wikiget.dl", logging.ERROR, "error message")] + + @patch("wikiget.dl.prep_download") + def test_process_single_download_file_exists_error( + self, mock_prep_download: MagicMock, caplog: pytest.LogCaptureFixture + ) -> None: + """ + If process_download catches a FileExistsError, it should create a warning log + message. + """ + mock_prep_download.side_effect = FileExistsError("warning message") + + args = parse_args(["File:Example.jpg"]) + _ = process_download(args) + + assert mock_prep_download.called + assert caplog.record_tuples == [ + ("wikiget.dl", logging.WARNING, "warning message"), + ] + + @patch("wikiget.dl.prep_download") + def test_process_single_download_other_error( + self, mock_prep_download: MagicMock + ) -> None: + """ + If process_download catches any other errors, it should return 1. + """ + mock_prep_download.side_effect = requests.ConnectionError + args = parse_args(["File:Example.jpg"]) - mock_download.return_value = 1 - mock_prep_download.return_value = MagicMock(File) exit_code = process_download(args) + assert mock_prep_download.called - assert mock_download.called assert exit_code == 1 + + +class TestBatchDownload: + @patch("wikiget.dl.download") + @patch("wikiget.dl.prep_download") + @patch("wikiget.dl.read_batch_file") + def test_batch_download( + self, + mock_read_batch_file: MagicMock, + mock_prep_download: MagicMock, + mock_download: MagicMock, + caplog: pytest.LogCaptureFixture, + ) -> None: + caplog.set_level(logging.INFO) + + # set dummy return values for read_batch_file() and download() + mock_read_batch_file.return_value = {1: "File:Example.jpg"} + mock_download.return_value = 0 + + args = parse_args(["-a", "batch.txt"]) + errors = batch_download(args) + + assert mock_read_batch_file.called + assert mock_prep_download.called + assert mock_download.called + assert caplog.record_tuples == [ + ("wikiget.dl", logging.INFO, "Processing 'File:Example.jpg' at line 1") + ] + assert errors == 0 + + @patch("wikiget.dl.read_batch_file") + def test_batch_download_os_error( + self, mock_read_batch_file: MagicMock, caplog: pytest.LogCaptureFixture + ) -> None: + """ + If batch_download catches an OSError, it should print an error log message + and exit the program. + """ + mock_read_batch_file.side_effect = OSError("error message") + + args = parse_args(["-a", "batch.txt"]) + with pytest.raises(SystemExit): + _ = batch_download(args) + + assert mock_read_batch_file.called + assert caplog.record_tuples == [ + ("wikiget.dl", logging.ERROR, "File could not be read: error message"), + ] + + @patch("wikiget.dl.prep_download") + @patch("wikiget.dl.read_batch_file") + def test_batch_download_parse_error( + self, + mock_read_batch_file: MagicMock, + mock_prep_download: MagicMock, + caplog: pytest.LogCaptureFixture, + ) -> None: + mock_read_batch_file.return_value = {1: "File:Example.jpg"} + mock_prep_download.side_effect = ParseError("warning message") + + args = parse_args(["-a", "batch.txt"]) + errors = batch_download(args) + + assert mock_read_batch_file.called + assert mock_prep_download.called + assert caplog.record_tuples == [ + ("wikiget.dl", logging.WARNING, "warning message (line 1)"), + ] + assert errors == 1 + + @patch("wikiget.dl.prep_download") + @patch("wikiget.dl.read_batch_file") + def test_batch_download_file_exists_error( + self, + mock_read_batch_file: MagicMock, + mock_prep_download: MagicMock, + caplog: pytest.LogCaptureFixture, + ) -> None: + mock_read_batch_file.return_value = {1: "File:Example.jpg"} + mock_prep_download.side_effect = FileExistsError("warning message") + + args = parse_args(["-a", "batch.txt"]) + errors = batch_download(args) + + assert mock_read_batch_file.called + assert mock_prep_download.called + assert caplog.record_tuples == [ + ("wikiget.dl", logging.WARNING, "warning message"), + ] + assert errors == 1 + + @patch("wikiget.dl.prep_download") + @patch("wikiget.dl.read_batch_file") + def test_batch_download_other_error( + self, + mock_read_batch_file: MagicMock, + mock_prep_download: MagicMock, + caplog: pytest.LogCaptureFixture, + ) -> None: + mock_read_batch_file.return_value = {1: "File:Example.jpg"} + mock_prep_download.side_effect = requests.ConnectionError + + args = parse_args(["-a", "batch.txt"]) + errors = batch_download(args) + + assert mock_read_batch_file.called + assert mock_prep_download.called + assert caplog.record_tuples == [ + ( + "wikiget.dl", + logging.WARNING, + "Unable to download 'File:Example.jpg' (line 1) due to an error", + ), + ] + assert errors == 1 + + +@pytest.mark.usefixtures("mock_get") +class TestDownload: + @pytest.fixture + def mock_file(self, tmp_path: Path) -> File: + file = File(name="Example.jpg", dest=str(tmp_path / "Example.jpg")) + file.image = Mock() + file.image.exists = True + file.image.imageinfo = { + "url": "https://upload.wikimedia.org/wikipedia/commons/a/a9/Example.jpg", + "size": 9022, + "sha1": "d01b79a6781c72ac9bfff93e5e2cfbeef4efc840", + } + file.image.site = MagicMock(Site) + file.image.site.host = "commons.wikimedia.org" + file.image.site.connection = requests.Session() + return file + + def test_download(self, mock_file: File, caplog: pytest.LogCaptureFixture) -> None: + caplog.set_level(logging.INFO) + + with patch("wikiget.dl.verify_hash") as mock_verify_hash: + mock_verify_hash.return_value = "d01b79a6781c72ac9bfff93e5e2cfbeef4efc840" + args = parse_args(["File:Example.jpg"]) + errors = download(mock_file, args) + + assert caplog.record_tuples == [ + ( + "wikiget.dl", + logging.INFO, + "[Example.jpg] Downloading 'Example.jpg' (9022 bytes) from " + "commons.wikimedia.org", + ), + ( + "wikiget.dl", + logging.INFO, + "[Example.jpg] " + "https://upload.wikimedia.org/wikipedia/commons/a/a9/Example.jpg", + ), + ( + "wikiget.dl", + logging.INFO, + "[Example.jpg] Remote file SHA1 is " + "d01b79a6781c72ac9bfff93e5e2cfbeef4efc840", + ), + ( + "wikiget.dl", + logging.INFO, + "[Example.jpg] Local file SHA1 is " + "d01b79a6781c72ac9bfff93e5e2cfbeef4efc840", + ), + ("wikiget.dl", logging.INFO, "[Example.jpg] Hashes match!"), + ("wikiget.dl", logging.INFO, "[Example.jpg] 'Example.jpg' downloaded"), + ] + assert errors == 0 + + def test_download_with_output( + self, mock_file: File, caplog: pytest.LogCaptureFixture + ) -> None: + caplog.set_level(logging.INFO) + + tmp_file = mock_file.dest + + with patch("wikiget.dl.verify_hash") as mock_verify_hash: + mock_verify_hash.return_value = "d01b79a6781c72ac9bfff93e5e2cfbeef4efc840" + args = parse_args(["-o", tmp_file, "File:Example.jpg"]) + errors = download(mock_file, args) + + assert caplog.record_tuples[0] == ( + "wikiget.dl", + logging.INFO, + "[Example.jpg] Downloading 'Example.jpg' (9022 bytes) from " + f"commons.wikimedia.org to '{tmp_file}'", + ) + assert caplog.record_tuples[5] == ( + "wikiget.dl", + logging.INFO, + f"[Example.jpg] 'Example.jpg' downloaded to '{tmp_file}'", + ) + assert errors == 0 + + def test_download_dry_run( + self, mock_file: File, caplog: pytest.LogCaptureFixture + ) -> None: + caplog.set_level(logging.INFO) + + args = parse_args(["-n", "File:Example.jpg"]) + errors = download(mock_file, args) + + # ignore first two log records since we tested for those earlier + assert caplog.record_tuples[2:] == [ + ("wikiget.dl", logging.WARNING, "[Example.jpg] Dry run; download skipped"), + ] + assert errors == 0 + + def test_download_os_error( + self, mock_file: File, caplog: pytest.LogCaptureFixture + ) -> None: + with patch("wikiget.dl.open") as mock_open: + mock_open.side_effect = OSError("write error") + args = parse_args(["File:Example.jpg"]) + errors = download(mock_file, args) + + assert caplog.record_tuples == [ + ( + "wikiget.dl", + logging.ERROR, + "[Example.jpg] File could not be written: write error", + ), + ] + assert errors == 1 + + def test_download_verify_os_error( + self, mock_file: File, caplog: pytest.LogCaptureFixture + ) -> None: + with patch("wikiget.dl.verify_hash") as mock_verify_hash: + mock_verify_hash.side_effect = OSError("read error") + args = parse_args(["File:Example.jpg"]) + errors = download(mock_file, args) + + assert caplog.record_tuples == [ + ( + "wikiget.dl", + logging.ERROR, + "[Example.jpg] File downloaded but could not be verified: read error", + ) + ] + assert errors == 1 + + def test_download_verify_hash_mismatch( + self, mock_file: File, caplog: pytest.LogCaptureFixture + ) -> None: + with patch("wikiget.dl.verify_hash") as mock_verify_hash: + mock_verify_hash.return_value = "mismatch" + args = parse_args(["File:Example.jpg"]) + errors = download(mock_file, args) + + assert caplog.record_tuples == [ + ( + "wikiget.dl", + logging.ERROR, + "[Example.jpg] Hash mismatch! Downloaded file may be corrupt.", + ) + ] + assert errors == 1 + + def test_download_nonexistent_file( + self, mock_file: File, caplog: pytest.LogCaptureFixture + ) -> None: + mock_file.image.exists = False + + args = parse_args(["File:Example.jpg"]) + errors = download(mock_file, args) + + assert caplog.record_tuples == [ + ( + "wikiget.dl", + logging.WARNING, + "[Example.jpg] Target does not appear to be a valid file", + ), + ] + assert errors == 1 diff --git a/tests/test_file_class.py b/tests/test_file_class.py index b935efc..42f5667 100644 --- a/tests/test_file_class.py +++ b/tests/test_file_class.py @@ -22,7 +22,7 @@ from wikiget.file import File class TestFileClass: - @pytest.fixture(scope="class") + @pytest.fixture def file_with_name(self) -> File: """ A File object created with only a name should set its destination property to @@ -39,7 +39,7 @@ class TestFileClass: def test_file_with_name_site(self, file_with_name: File) -> None: assert file_with_name.site == DEFAULT_SITE - @pytest.fixture(scope="class") + @pytest.fixture def file_with_name_and_dest(self) -> File: """ A File object created with a name and destination should set those properties @@ -60,3 +60,12 @@ class TestFileClass: """ file = File("foobar.jpg", site="en.wikipedia.org") assert file.site == "en.wikipedia.org" + + def test_file_equality(self, file_with_name: File) -> None: + assert File(name="foobar.jpg") == file_with_name + + def test_file_inequality(self, file_with_name: File) -> None: + assert File(name="foobaz.jpg", dest="output.jpg") != file_with_name + + def test_file_comparison_with_non_file(self, file_with_name: File) -> None: + assert file_with_name.__eq__({"name": "foobar.jpg"}) == NotImplemented diff --git a/tests/test_logging.py b/tests/test_logging.py index b189c28..55a3397 100644 --- a/tests/test_logging.py +++ b/tests/test_logging.py @@ -23,67 +23,51 @@ from pytest import LogCaptureFixture from wikiget.logging import FileLogAdapter, configure_logging from wikiget.wikiget import parse_args -logger = logging.getLogger() - -def test_custom_log_adapter(caplog: LogCaptureFixture) -> None: - """ - The custom log adapter should prepend the filename to log messages. - """ - args = parse_args(["File:Example.jpg"]) - configure_logging(args.verbose, args.logfile, quiet=args.quiet) - adapter = FileLogAdapter(logger, {"filename": "Example.jpg"}) - adapter.warning("test log") - assert "[Example.jpg] test log" in caplog.text - - -def test_file_logging(tmp_path: Path) -> None: - """ - Logging to a file should create the file in the specified location. - """ - logfile_location = tmp_path / "test.log" - args = parse_args(["File:Example.jpg", "-l", str(logfile_location)]) - configure_logging(args.verbose, args.logfile, quiet=args.quiet) - assert logfile_location.is_file() - - -def test_default_logging() -> None: - """ - The default log level should be set to WARNING. - """ - args = parse_args(["File:Example.jpg"]) - configure_logging(args.verbose, args.logfile, quiet=args.quiet) - # each call of configure_logging() adds a new handler to the logger, so we need to - # grab the most recently added one to test - handler = logger.handlers[-1] - assert handler.level == logging.WARNING - - -def test_verbose_logging() -> None: - """ - When -v is passed, the log level should be set to INFO. - """ - args = parse_args(["File:Example.jpg", "-v"]) - configure_logging(args.verbose, args.logfile, quiet=args.quiet) - handler = logger.handlers[-1] - assert handler.level == logging.INFO - - -def test_very_verbose_logging() -> None: - """ - When -vv is passed, the log level should be set to DEBUG. - """ - args = parse_args(["File:Example.jpg", "-vv"]) - configure_logging(args.verbose, args.logfile, quiet=args.quiet) - handler = logger.handlers[-1] - assert handler.level == logging.DEBUG - - -def test_quiet_logging() -> None: - """ - When -q is passed, the log level should be set to ERROR. - """ - args = parse_args(["File:Example.jpg", "-q"]) - configure_logging(args.verbose, args.logfile, quiet=args.quiet) - handler = logger.handlers[-1] - assert handler.level == logging.ERROR +class TestLogging: + logger = logging.getLogger() + + def test_custom_log_adapter(self, caplog: LogCaptureFixture) -> None: + """The custom log adapter should prepend the filename to log messages.""" + args = parse_args(["File:Example.jpg"]) + configure_logging(args.verbose, args.logfile, quiet=args.quiet) + adapter = FileLogAdapter(self.logger, {"filename": "Example.jpg"}) + adapter.warning("test log") + assert "[Example.jpg] test log" in caplog.text + + def test_file_logging(self, tmp_path: Path) -> None: + """Logging to a file should create the file in the specified location.""" + logfile_location = tmp_path / "test.log" + args = parse_args(["File:Example.jpg", "-l", str(logfile_location)]) + configure_logging(args.verbose, args.logfile, quiet=args.quiet) + assert logfile_location.is_file() + + def test_default_logging(self) -> None: + """The default log level should be set to WARNING.""" + args = parse_args(["File:Example.jpg"]) + configure_logging(args.verbose, args.logfile, quiet=args.quiet) + # each call of configure_logging() adds a new handler to the logger, so we need + # to grab the most recently added one to test + handler = self.logger.handlers[-1] + assert handler.level == logging.WARNING + + def test_verbose_logging(self) -> None: + """When -v is passed, the log level should be set to INFO.""" + args = parse_args(["File:Example.jpg", "-v"]) + configure_logging(args.verbose, args.logfile, quiet=args.quiet) + handler = self.logger.handlers[-1] + assert handler.level == logging.INFO + + def test_very_verbose_logging(self) -> None: + """When -vv is passed, the log level should be set to DEBUG.""" + args = parse_args(["File:Example.jpg", "-vv"]) + configure_logging(args.verbose, args.logfile, quiet=args.quiet) + handler = self.logger.handlers[-1] + assert handler.level == logging.DEBUG + + def test_quiet_logging(self) -> None: + """When -q is passed, the log level should be set to ERROR.""" + args = parse_args(["File:Example.jpg", "-q"]) + configure_logging(args.verbose, args.logfile, quiet=args.quiet) + handler = self.logger.handlers[-1] + assert handler.level == logging.ERROR diff --git a/tests/test_parse.py b/tests/test_parse.py index e767597..e0ac707 100644 --- a/tests/test_parse.py +++ b/tests/test_parse.py @@ -29,7 +29,7 @@ from wikiget.wikiget import parse_args class TestGetDest: - @pytest.fixture(scope="class") + @pytest.fixture def file_with_filename(self) -> File: """ When a filename is passed to get_dest, it should create a File object with the @@ -47,7 +47,7 @@ class TestGetDest: def test_get_dest_site_with_filename(self, file_with_filename: File) -> None: assert file_with_filename.site == "commons.wikimedia.org" - @pytest.fixture(scope="class") + @pytest.fixture def file_with_url(self) -> File: """ When a URL is passed to get_dest, it should create a File object with the @@ -92,7 +92,7 @@ class TestGetDest: class TestReadBatchFile: - @pytest.fixture() + @pytest.fixture def dl_dict(self, tmp_path: Path) -> Dict[int, str]: """ Create and process a test batch file with three lines, returning a dictionary. @@ -129,7 +129,7 @@ class TestReadBatchFile: expected_list = {1: "File:Foo.jpg", 2: "File:Bar.jpg", 3: "File:Baz.jpg"} assert dl_dict == expected_list - @pytest.fixture() + @pytest.fixture def dl_dict_stdin(self, monkeypatch: pytest.MonkeyPatch) -> Dict[int, str]: """ Pass three lines of filenames from stdin to read_batch_file and return a dict. @@ -165,7 +165,7 @@ class TestReadBatchFile: expected_list = {1: "File:Foo.jpg", 2: "File:Bar.jpg", 3: "File:Baz.jpg"} assert dl_dict_stdin == expected_list - @pytest.fixture() + @pytest.fixture def dl_dict_with_comment(self, tmp_path: Path) -> Dict[int, str]: """ Create and process a test batch file with four lines, one of which is diff --git a/tests/test_validations.py b/tests/test_validations.py index 30e59a3..b3976b6 100644 --- a/tests/test_validations.py +++ b/tests/test_validations.py @@ -26,7 +26,6 @@ from wikiget.validations import valid_file, valid_site, verify_hash class TestSiteInput: @pytest.fixture( - scope="class", params=[ "example.com", "vim.wikia.com", @@ -38,7 +37,6 @@ class TestSiteInput: return valid_site(request.param) @pytest.fixture( - scope="class", params=[ "en.wikipedia.org", "commons.wikimedia.org", @@ -50,20 +48,16 @@ class TestSiteInput: return valid_site(request.param) def test_invalid_site_input(self, invalid_input: None) -> None: - """ - Invalid site strings should not return regex match objects. - """ + """Invalid site strings should not return regex match objects.""" assert invalid_input is None def test_valid_site_input(self, valid_input: Match) -> None: - """ - Valid site strings should return regex match objects. - """ + """Valid site strings should return regex match objects.""" assert valid_input is not None class TestFileRegex: - @pytest.fixture(scope="class") + @pytest.fixture def file_match(self) -> Optional[Match]: """ File regex should return a match object with match groups corresponding @@ -86,7 +80,6 @@ class TestFileRegex: class TestFileInput: @pytest.fixture( - scope="class", params=[ "file:example", "example.jpg", @@ -98,7 +91,6 @@ class TestFileInput: return valid_file(request.param) @pytest.fixture( - scope="class", params=[ "Image:example.jpg", "file:example.jpg", @@ -112,23 +104,17 @@ class TestFileInput: return valid_file(request.param) def test_invalid_file_input(self, invalid_input: None) -> None: - """ - Invalid file strings should not return regex match objects. - """ + """Invalid file strings should not return regex match objects.""" assert invalid_input is None def test_valid_file_input(self, valid_input: Match) -> None: - """ - Valid file strings should return regex match objects. - """ + """Valid file strings should return regex match objects.""" assert valid_input is not None class TestVerifyHash: def test_verify_hash(self, tmp_path: Path) -> None: - """ - Confirm that verify_hash returns the proper SHA1 hash. - """ + """Confirm that verify_hash returns the proper SHA1 hash.""" file_name = "testfile" file_contents = "foobar" file_sha1 = "8843d7f92416211de9ebb963ff4ce28125932878" diff --git a/tests/test_wikiget_cli.py b/tests/test_wikiget_cli.py index 99d113e..0306579 100644 --- a/tests/test_wikiget_cli.py +++ b/tests/test_wikiget_cli.py @@ -23,47 +23,58 @@ from wikiget import USER_AGENT, __version__ from wikiget.wikiget import cli -def test_cli_no_params(monkeypatch: pytest.MonkeyPatch): - monkeypatch.setattr("sys.argv", ["wikiget"]) - with pytest.raises(SystemExit) as e: - cli() - assert e.value.code == 2 +class TestCli: + def test_cli_no_params(self, monkeypatch: pytest.MonkeyPatch) -> None: + with monkeypatch.context() as m: + m.setattr("sys.argv", ["wikiget"]) + with pytest.raises(SystemExit) as e: + cli() + assert e.value.code == 2 + @patch("wikiget.wikiget.process_download") + def test_cli_completed_successfully( + self, mock_process_download: MagicMock, monkeypatch: pytest.MonkeyPatch + ) -> None: + # a successful call to process_download returns 0 + mock_process_download.return_value = 0 -@patch("wikiget.wikiget.process_download") -def test_cli_completed_successfully( - mock_process_download: MagicMock, monkeypatch: pytest.MonkeyPatch -) -> None: - monkeypatch.setattr("sys.argv", ["wikiget", "File:Example.jpg"]) - mock_process_download.return_value = 0 - with pytest.raises(SystemExit) as e: - cli() - assert mock_process_download.called - assert e.value.code == 0 + with monkeypatch.context() as m: + m.setattr("sys.argv", ["wikiget", "File:Example.jpg"]) + with pytest.raises(SystemExit) as e: + cli() + assert e.value.code == 0 -@patch("wikiget.wikiget.process_download") -def test_cli_completed_with_problems( - mock_process_download: MagicMock, monkeypatch: pytest.MonkeyPatch -) -> None: - monkeypatch.setattr("sys.argv", ["wikiget", "File:Example.jpg"]) - mock_process_download.return_value = 1 - with pytest.raises(SystemExit) as e: - cli() - assert mock_process_download.called - assert e.value.code == 1 + @patch("wikiget.wikiget.process_download") + def test_cli_completed_with_problems( + self, mock_process_download: MagicMock, monkeypatch: pytest.MonkeyPatch + ) -> None: + # an unsuccessful call to process_download returns 1 + mock_process_download.return_value = 1 + with monkeypatch.context() as m: + m.setattr("sys.argv", ["wikiget", "File:Example.jpg"]) -@patch("wikiget.wikiget.process_download") -def test_cli_logs( - mock_process_download: MagicMock, - monkeypatch: pytest.MonkeyPatch, - caplog: pytest.LogCaptureFixture, -) -> None: - monkeypatch.setattr("sys.argv", ["wikiget", "File:Example.jpg"]) - mock_process_download.return_value = 0 - with pytest.raises(SystemExit): - cli() - assert mock_process_download.called - assert f"Starting download session using wikiget {__version__}" in caplog.text - assert USER_AGENT in caplog.text + with pytest.raises(SystemExit) as e: + cli() + assert e.value.code == 1 + + @patch("wikiget.wikiget.process_download") + def test_cli_logs( + self, + mock_process_download: MagicMock, + monkeypatch: pytest.MonkeyPatch, + caplog: pytest.LogCaptureFixture, + ) -> None: + # a successful call to process_download returns 0 + mock_process_download.return_value = 0 + + with monkeypatch.context() as m: + m.setattr("sys.argv", ["wikiget", "File:Example.jpg"]) + + with pytest.raises(SystemExit): + cli() + assert ( + f"Starting download session using wikiget {__version__}" in caplog.text + ) + assert USER_AGENT in caplog.text |
