aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/wikiget/validations.py23
-rw-r--r--src/wikiget/wikiget.py9
-rw-r--r--tests/conftest.py33
-rw-r--r--tests/test_client.py50
-rw-r--r--tests/test_dl.py90
-rw-r--r--tests/test_file_class.py40
-rw-r--r--tests/test_logging.py4
-rw-r--r--tests/test_parse.py117
-rw-r--r--tests/test_validations.py53
-rw-r--r--tests/test_wikiget_cli.py14
10 files changed, 321 insertions, 112 deletions
diff --git a/src/wikiget/validations.py b/src/wikiget/validations.py
index ee73b87..18c1f86 100644
--- a/src/wikiget/validations.py
+++ b/src/wikiget/validations.py
@@ -25,10 +25,11 @@ from wikiget import BLOCKSIZE
def valid_file(search_string: str) -> re.Match | None:
- """
- Determines if the given string contains a valid file name, defined as a string
- ending with a '.' and at least one character, beginning with 'File:' or 'Image:',
- the standard file prefixes in MediaWiki.
+ """Determines if the given string contains a valid file name
+
+ A valid file name is a string that begins with 'File:' or 'Image:' (the standard
+ file prefixes in MediaWiki), includes a period, and has at least one character
+ following the period, like 'File:Example.jpg' or 'Image:Example.svg'.
:param search_string: string to validate
:type search_string: str
@@ -42,10 +43,10 @@ def valid_file(search_string: str) -> re.Match | None:
def valid_site(search_string: str) -> re.Match | None:
- """
- Determines if the given string contains a valid site name, defined as a string
- ending with 'wikipedia.org' or 'wikimedia.org'. This covers all subdomains of those
- domains.
+ """Determines if the given string contains a valid site name
+
+ A valid site name is a string ending with 'wikipedia.org' or 'wikimedia.org'. This
+ covers all subdomains of those domains.
Currently unused since any site is accepted as input, and we rely on the user to
ensure the site has a compatible API.
@@ -60,8 +61,10 @@ def valid_site(search_string: str) -> re.Match | None:
def verify_hash(filename: str) -> str:
- """
- Calculates the SHA1 hash of the given file for comparison with a known value.
+ """Calculates the SHA1 hash of the given file for comparison with a known value.
+
+ Despite being insecure, SHA1 is used since that's what the MediaWiki API returns for
+ the file hash.
:param filename: name of the file to calculate a hash for
:type filename: str
diff --git a/src/wikiget/wikiget.py b/src/wikiget/wikiget.py
index 06dc458..ca211af 100644
--- a/src/wikiget/wikiget.py
+++ b/src/wikiget/wikiget.py
@@ -27,6 +27,13 @@ from wikiget.logging import configure_logging
def parse_args(argv: list[str]) -> argparse.Namespace:
+ """Parse the given argument list.
+
+ :param argv: a list of arguments in string form
+ :type argv: list[str]
+ :return: a Namespace containing the arguments and their values
+ :rtype: argparse.Namespace
+ """
parser = argparse.ArgumentParser(
description="""
A tool for downloading files from MediaWiki sites using the file name or
@@ -120,7 +127,7 @@ def parse_args(argv: list[str]) -> argparse.Namespace:
def cli() -> None:
- # setup our environment
+ """Set up the command-line environment and start the download process."""
args = parse_args(sys.argv[1:])
configure_logging(verbosity=args.verbose, logfile=args.logfile, quiet=args.quiet)
diff --git a/tests/conftest.py b/tests/conftest.py
index cda7dd3..5fccfc0 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -15,13 +15,44 @@
# You should have received a copy of the GNU General Public License
# along with Wikiget. If not, see <https://www.gnu.org/licenses/>.
+"""Define fixtures used across all tests in this folder."""
+
import pytest
import requests_mock as rm
+from wikiget.file import File
+
+
+@pytest.fixture()
+def file_with_name() -> File:
+ """Create a test File with only a filename.
+
+ A File object created with only a name should set its destination property to
+ the same value and its site property to the program's default site.
+
+ :return: File object created using a filename
+ :rtype: File
+ """
+ return File("foobar.jpg")
+
+
+@pytest.fixture()
+def file_with_name_and_dest() -> File:
+ """Create a test File with a name and destination.
+
+ :return: File object created with name and dest
+ :rtype: File
+ """
+ return File(name="foobar.jpg", dest="bazqux.jpg")
+
@pytest.fixture()
def _mock_get(requests_mock: rm.Mocker) -> None:
- # fake the download request for File:Example.jpg
+ """Fake the download request for the true URL of File:Example.jpg.
+
+ :param requests_mock: a requests_mock Mocker object
+ :type requests_mock: rm.Mocker
+ """
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 4cbf702..dae63f5 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with Wikiget. If not, see <https://www.gnu.org/licenses/>.
+"""Define tests related to the wikiget.client module."""
+
import logging
from unittest.mock import MagicMock, patch, sentinel
@@ -28,14 +30,14 @@ from wikiget.wikiget import parse_args
class TestConnectSite:
- # this message is logged when the level is at INFO or below
+ """Define tests related to wikiget.client.connect_to_site."""
+
+ # this message is logged when the level is at INFO or below;
+ # defined here for ease of maintenance
info_msg = f"Connecting to {DEFAULT_SITE}"
def test_connect_to_site(self, caplog: pytest.LogCaptureFixture) -> None:
- """
- The connect_to_site function should create an info log message recording the
- name of the site we're connecting to.
- """
+ """Test that an info log message is created with the name of the site."""
caplog.set_level(logging.INFO)
args = parse_args(["File:Example.jpg"])
@@ -47,7 +49,8 @@ class TestConnectSite:
]
def test_connect_to_site_with_creds(self, caplog: pytest.LogCaptureFixture) -> None:
- """
+ """Test that an info log message is created when credentials are used.
+
If a username and password are provided, connect_to_site should use them to
log in to the site.
"""
@@ -68,9 +71,10 @@ class TestConnectSite:
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.
+ """Test that the correct log messages are created if ConnectionError is raised.
+
+ In addition to the info-level site connection message, there should be error
+ and debug level messages with details about the problem.
"""
caplog.set_level(logging.DEBUG)
args = parse_args(["File:Example.jpg"])
@@ -88,9 +92,10 @@ class TestConnectSite:
]
def test_connect_to_site_http_error(self, caplog: pytest.LogCaptureFixture) -> None:
- """
- The connect_to_site function should log the correct messages if an HTTPError
- exception is raised.
+ """Test that the correct log messages are created if HTTPError is raised.
+
+ In addition to the info-level site connection message, there should be error
+ and debug level messages with details about the problem.
"""
caplog.set_level(logging.DEBUG)
args = parse_args(["File:Example.jpg"])
@@ -114,9 +119,10 @@ class TestConnectSite:
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.
+ """Test that log messages are created if other exceptions are raised.
+
+ When an exception other than ConnectionError or HTTPError is raised, an
+ error-level log message should be created.
"""
args = parse_args(["File:Example.jpg"])
@@ -130,11 +136,10 @@ class TestConnectSite:
class TestQueryApi:
+ """Define tests related to wikiget.client.query_api."""
+
def test_query_api(self) -> None:
- """
- The query_api function should return an Image object when given a name and a
- valid Site.
- """
+ """Test that query_api returns the expected Image object."""
# 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
@@ -147,7 +152,8 @@ class TestQueryApi:
assert image == sentinel.mock_image
def test_query_api_error(self, caplog: pytest.LogCaptureFixture) -> None:
- """
+ """Test that the correct log messages are created when APIError is raised.
+
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.
"""
@@ -155,6 +161,10 @@ class TestQueryApi:
mock_site = MagicMock()
mock_site.images = MagicMock()
+ # Normally, APIError is raised during the processing of the API call that
+ # creates the site.images attribute. Since we're faking all of that, the
+ # exception needs to be raised elsewhere, so that it's caught when query_api
+ # tries to read the items in site.images.
mock_site.images.__getitem__.side_effect = APIError(
"error code", "error info", "error kwargs"
)
diff --git a/tests/test_dl.py b/tests/test_dl.py
index c9f26dc..d7d5d77 100644
--- a/tests/test_dl.py
+++ b/tests/test_dl.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with Wikiget. If not, see <https://www.gnu.org/licenses/>.
+"""Define tests related to the wikiget.dl module."""
+
import logging
from pathlib import Path
from unittest.mock import MagicMock, Mock, patch
@@ -30,6 +32,8 @@ from wikiget.wikiget import parse_args
class TestPrepDownload:
+ """Define tests related to wikiget.dl.prep_download."""
+
@patch("wikiget.dl.query_api")
@patch("wikiget.dl.connect_to_site")
def test_prep_download(
@@ -51,7 +55,8 @@ class TestPrepDownload:
assert file == expected_file
def test_prep_download_with_existing_file(self, tmp_path: Path) -> None:
- """
+ """Test that an exception is raised when the download file already exists.
+
Attempting to download a file with the same destination name as an existing file
should raise a FileExistsError.
"""
@@ -63,9 +68,11 @@ class TestPrepDownload:
class TestProcessDownload:
+ """Define tests related to wikiget.dl.process_download."""
+
@patch("wikiget.dl.batch_download")
def test_process_batch_download(self, mock_batch_download: MagicMock) -> None:
- """A successful batch download should not return any errors."""
+ """A successful batch download should have an exit code of zero (no errors)."""
mock_batch_download.return_value = 0
args = parse_args(["-a", "batch.txt"])
@@ -77,9 +84,9 @@ class TestProcessDownload:
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.
+ """A batch download with errors should have a non-zero exit code.
+
+ Additionally, it should create a log message containing the number of errors.
"""
mock_batch_download.return_value = 4
@@ -94,7 +101,7 @@ class TestProcessDownload:
def test_process_single_download(
self, mock_download: MagicMock, mock_prep_download: MagicMock
) -> None:
- """A successful download should not return any errors."""
+ """A successful download should have an exit code of zero (no errors)."""
mock_download.return_value = 0
mock_prep_download.return_value = File("Example.jpg")
@@ -121,9 +128,7 @@ class TestProcessDownload:
def test_process_single_download_parse_error(
self, mock_prep_download: MagicMock, caplog: pytest.LogCaptureFixture
) -> None:
- """
- If process_download catches a ParseError, it should create an error log message.
- """
+ """If ParseError is raised, it should create an error log message."""
mock_prep_download.side_effect = ParseError("error message")
args = parse_args(["File:Example.jpg"])
@@ -136,10 +141,7 @@ class TestProcessDownload:
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.
- """
+ """If FileExistsError is raised, it should create a warning log message."""
mock_prep_download.side_effect = FileExistsError("warning message")
args = parse_args(["File:Example.jpg"])
@@ -154,9 +156,7 @@ class TestProcessDownload:
def test_process_single_download_other_error(
self, mock_prep_download: MagicMock
) -> None:
- """
- If process_download catches any other errors, it should return 1.
- """
+ """If any other errors occur, an exit code of 1 should be returned."""
mock_prep_download.side_effect = requests.ConnectionError
args = parse_args(["File:Example.jpg"])
@@ -167,6 +167,8 @@ class TestProcessDownload:
class TestBatchDownload:
+ """Define tests related to wikiget.dl.batch_download."""
+
@patch("wikiget.dl.download")
@patch("wikiget.dl.prep_download")
@patch("wikiget.dl.read_batch_file")
@@ -177,6 +179,11 @@ class TestBatchDownload:
mock_download: MagicMock,
caplog: pytest.LogCaptureFixture,
) -> None:
+ """Test that no errors are returned for a successful batch download.
+
+ Additionally, a log message should be created for each line in the batch file
+ and should contain the line number and contents.
+ """
caplog.set_level(logging.INFO)
# set dummy return values for read_batch_file() and download()
@@ -198,10 +205,7 @@ class TestBatchDownload:
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.
- """
+ """Test that an OSError results in an error log message and program exit."""
mock_read_batch_file.side_effect = OSError("error message")
args = parse_args(["-a", "batch.txt"])
@@ -221,6 +225,11 @@ class TestBatchDownload:
mock_prep_download: MagicMock,
caplog: pytest.LogCaptureFixture,
) -> None:
+ """Test that a warning log message is created if ParseError is raised.
+
+ The resulting log message should contain the relevant line where the problem
+ ocurred.
+ """
mock_read_batch_file.return_value = {1: "File:Example.jpg"}
mock_prep_download.side_effect = ParseError("warning message")
@@ -242,6 +251,7 @@ class TestBatchDownload:
mock_prep_download: MagicMock,
caplog: pytest.LogCaptureFixture,
) -> None:
+ """Test that a warning log message is created if the download file exists."""
mock_read_batch_file.return_value = {1: "File:Example.jpg"}
mock_prep_download.side_effect = FileExistsError("warning message")
@@ -263,6 +273,11 @@ class TestBatchDownload:
mock_prep_download: MagicMock,
caplog: pytest.LogCaptureFixture,
) -> None:
+ """Test that a warning log message is created if there are problems downloading.
+
+ The log message should also contain the line number and contents of the line
+ that caused the error.
+ """
mock_read_batch_file.return_value = {1: "File:Example.jpg"}
mock_prep_download.side_effect = requests.ConnectionError
@@ -283,8 +298,17 @@ class TestBatchDownload:
@pytest.mark.usefixtures("_mock_get")
class TestDownload:
+ """Define tests related to wikiget.dl.download."""
+
@pytest.fixture()
def mock_file(self, tmp_path: Path) -> File:
+ """Create a mock File object to test against.
+
+ :param tmp_path: temporary path object
+ :type tmp_path: Path
+ :return: mock File object
+ :rtype: File
+ """
file = File(name="Example.jpg", dest=str(tmp_path / "Example.jpg"))
file.image = Mock()
file.image.exists = True
@@ -299,6 +323,12 @@ class TestDownload:
return file
def test_download(self, mock_file: File, caplog: pytest.LogCaptureFixture) -> None:
+ """Test that the correct log messages are created when downloading a file.
+
+ There should be a series of info-level messages containing the filename, size,
+ site name, actual URL, and SHA1 hash, along with a message noting the successful
+ download.
+ """
caplog.set_level(logging.INFO)
with patch("wikiget.dl.verify_hash") as mock_verify_hash:
@@ -339,6 +369,10 @@ class TestDownload:
def test_download_with_output(
self, mock_file: File, caplog: pytest.LogCaptureFixture
) -> None:
+ """Test that the correct log messages are created when downloading a file.
+
+ When an output name is specified, the log messages should reflect that.
+ """
caplog.set_level(logging.INFO)
tmp_file = mock_file.dest
@@ -364,6 +398,7 @@ class TestDownload:
def test_download_dry_run(
self, mock_file: File, caplog: pytest.LogCaptureFixture
) -> None:
+ """Test that a dry run creates a log message saying so."""
caplog.set_level(logging.INFO)
args = parse_args(["-n", "File:Example.jpg"])
@@ -378,6 +413,11 @@ class TestDownload:
def test_download_os_error(
self, mock_file: File, caplog: pytest.LogCaptureFixture
) -> None:
+ """Test what happens when an OSError is raised during download.
+
+ If the downloaded file cannot be created, an error log message should be created
+ with details on the exception.
+ """
with patch("wikiget.dl.Path.open") as mock_open:
mock_open.side_effect = OSError("write error")
args = parse_args(["File:Example.jpg"])
@@ -395,6 +435,11 @@ class TestDownload:
def test_download_verify_os_error(
self, mock_file: File, caplog: pytest.LogCaptureFixture
) -> None:
+ """Test what happens when an OSError is raised during verification.
+
+ If the downloaded file cannot be read in order to calculate its hash, an error
+ log message should be created with details on the exception.
+ """
with patch("wikiget.dl.verify_hash") as mock_verify_hash:
mock_verify_hash.side_effect = OSError("read error")
args = parse_args(["File:Example.jpg"])
@@ -412,6 +457,10 @@ class TestDownload:
def test_download_verify_hash_mismatch(
self, mock_file: File, caplog: pytest.LogCaptureFixture
) -> None:
+ """Test what happens when the downloaded file hash and server hash don't match.
+
+ An error log message should be created if there's a hash mismatch.
+ """
with patch("wikiget.dl.verify_hash") as mock_verify_hash:
mock_verify_hash.return_value = "mismatch"
args = parse_args(["File:Example.jpg"])
@@ -429,6 +478,7 @@ class TestDownload:
def test_download_nonexistent_file(
self, mock_file: File, caplog: pytest.LogCaptureFixture
) -> None:
+ """Test that a warning message is logged if no file info was returned."""
mock_file.image.exists = False
args = parse_args(["File:Example.jpg"])
diff --git a/tests/test_file_class.py b/tests/test_file_class.py
index ee25f1c..8e68239 100644
--- a/tests/test_file_class.py
+++ b/tests/test_file_class.py
@@ -15,57 +15,61 @@
# 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
+"""Define tests related to the wikiget.file module."""
from wikiget import DEFAULT_SITE
from wikiget.file import File
class TestFileClass:
- @pytest.fixture()
- def file_with_name(self) -> File:
- """
- A File object created with only a name should set its destination property to
- the same value and its site property to the program's default site.
- """
- return File("foobar.jpg")
+ """Define tests related to wikiget.file.File creation."""
def test_file_with_name(self, file_with_name: File) -> None:
+ """The file name attribute should equal what was passed in."""
assert file_with_name.name == "foobar.jpg"
def test_file_with_name_dest(self, file_with_name: File) -> None:
+ """The file dest attribute should be the same as the name."""
assert file_with_name.dest == file_with_name.name
def test_file_with_name_site(self, file_with_name: File) -> None:
+ """The file site attribute should equal the default site."""
assert file_with_name.site == DEFAULT_SITE
- @pytest.fixture()
- def file_with_name_and_dest(self) -> File:
- """
- A File object created with a name and destination should set those properties
- accordingly; they should not be the same value.
- """
- return File("foobar.jpg", dest="bazqux.jpg")
-
def test_file_with_name_and_dest(self, file_with_name_and_dest: File) -> None:
+ """The file dest attribute should equal what was passed in."""
assert file_with_name_and_dest.dest == "bazqux.jpg"
def test_name_and_dest_are_different(self, file_with_name_and_dest: File) -> None:
+ """The file name and dest attributes should not be the same."""
assert file_with_name_and_dest.dest != file_with_name_and_dest.name
def test_file_with_name_and_site(self) -> None:
- """
+ """Test the attributes of a File created with a name and site.
+
A File object created with a name and site should set those properties
accordingly and not use the program's default site.
"""
file = File("foobar.jpg", site="en.wikipedia.org")
assert file.site == "en.wikipedia.org"
+
+class TestFileComparison:
+ """Define tests related to wikiget.file.File comparisons."""
+
def test_file_equality(self, file_with_name: File) -> None:
+ """Test that two similar Files equal each other."""
assert File(name="foobar.jpg") == file_with_name
def test_file_inequality(self, file_with_name: File) -> None:
+ """Test that two dissimilar Files do not equal each other."""
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
+ """Test what happens when a File is compared with a different object.
+
+ The equality comparison should return NotImplemented when comparing non-Files
+ with Files.
+ """
+ not_a_file = {"name": "foobar.jpg", "dest": "foobar.jpg"}
+ assert file_with_name.__eq__(not_a_file) == NotImplemented
diff --git a/tests/test_logging.py b/tests/test_logging.py
index 5d3aa4c..2fd95cd 100644
--- a/tests/test_logging.py
+++ b/tests/test_logging.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with Wikiget. If not, see <https://www.gnu.org/licenses/>.
+"""Define tests related to the wikiget.logging module."""
+
import logging
from pathlib import Path
@@ -25,6 +27,8 @@ from wikiget.wikiget import parse_args
class TestLogging:
+ """Define tests related to wikiget.logging.configure_logging and FileLogAdapter."""
+
logger = logging.getLogger()
def test_custom_log_adapter(self, caplog: pytest.LogCaptureFixture) -> None:
diff --git a/tests/test_parse.py b/tests/test_parse.py
index 7ef182c..fbbd1b7 100644
--- a/tests/test_parse.py
+++ b/tests/test_parse.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with Wikiget. If not, see <https://www.gnu.org/licenses/>.
+"""Define tests related to the wikiget.parse module."""
+
from __future__ import annotations
import io
@@ -34,46 +36,69 @@ if TYPE_CHECKING:
class TestGetDest:
+ """Define tests related to wikiget.parse.get_dest."""
+
@pytest.fixture()
def file_with_filename(self) -> File:
- """
- When a filename is passed to get_dest, it should create a File object with the
- correct name and dest and the default site.
+ """Create a File object with a given filename.
+
+ When only the filename is given as an argument, the dest attribute will be set
+ to the same value as the filename and the default site will be used.
+
+ :return: a File object created using a filename
+ :rtype: File
"""
args = parse_args(["File:Example.jpg"])
return get_dest(args.FILE, args)
def test_get_dest_name_with_filename(self, file_with_filename: File) -> None:
+ """Test that the file's name attribute is set correctly."""
assert file_with_filename.name == "Example.jpg"
def test_get_dest_with_filename(self, file_with_filename: File) -> None:
+ """Test that the file's dest attribute is set correctly.
+
+ Unless otherwise specified, it should match the filename.
+ """
assert file_with_filename.dest == "Example.jpg"
def test_get_dest_site_with_filename(self, file_with_filename: File) -> None:
+ """Test that the file's site attribute is set correctly.
+
+ Unless otherwise specified, it should be the default site.
+ """
assert file_with_filename.site == "commons.wikimedia.org"
@pytest.fixture()
def file_with_url(self) -> File:
- """
- When a URL is passed to get_dest, it should create a File object with the
- correct name and dest and the site from the URL.
+ """Create a File object with a given URL.
+
+ When a URL is passed to get_dest, it will create a File object with the
+ filename and site parsed from the URL.
+
+ :return: a File object created using a URL
+ :rtype: File
"""
args = parse_args(["https://en.wikipedia.org/wiki/File:Example.jpg"])
return get_dest(args.FILE, args)
def test_get_dest_name_with_url(self, file_with_url: File) -> None:
+ """Test that the file's name attribute is set correctly."""
assert file_with_url.name == "Example.jpg"
def test_get_dest_with_url(self, file_with_url: File) -> None:
+ """Test that the file's dest attribute is set correctly."""
assert file_with_url.dest == "Example.jpg"
def test_get_dest_site_with_url(self, file_with_url: File) -> None:
+ """Test that the file's site attribute is set correctly.
+
+ The site should be what was parsed from the URL, not the default site.
+ """
assert file_with_url.site == "en.wikipedia.org"
def test_get_dest_with_bad_filename(self) -> None:
- """
- The get_dest function should raise a ParseError if the filename is invalid.
- """
+ """Test that a ParseError exception is raised if the filename is invalid."""
args = parse_args(["Example.jpg"])
with pytest.raises(ParseError):
_ = get_dest(args.FILE, args)
@@ -81,7 +106,8 @@ class TestGetDest:
def test_get_dest_with_different_site(
self, caplog: pytest.LogCaptureFixture
) -> None:
- """
+ """Test that a warning log message is created.
+
If a URL is passed to get_dest and a site is also given on the command line,
the site in the URL should be used and a warning log message created.
"""
@@ -97,10 +123,16 @@ class TestGetDest:
class TestReadBatchFile:
+ """Define tests related to wikiget.parse.read_batch_file."""
+
@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.
+ """Create and process a test batch file with three lines.
+
+ :param tmp_path: temporary path object
+ :type tmp_path: Path
+ :return: dictionary representation of the input file
+ :rtype: dict[int, str]
"""
tmp_file = tmp_path / "batch.txt"
tmp_file.write_text("File:Foo.jpg\nFile:Bar.jpg\nFile:Baz.jpg\n")
@@ -109,7 +141,8 @@ class TestReadBatchFile:
def test_batch_file_log(
self, caplog: pytest.LogCaptureFixture, tmp_path: Path
) -> None:
- """
+ """Test that reading a batch file creates an info log message.
+
Reading in a batch file should create an info log message containing the name
of the batch file.
"""
@@ -120,24 +153,26 @@ class TestReadBatchFile:
assert f"Using file '{tmp_file}' for batch download" in caplog.text
def test_batch_file_length(self, dl_dict: dict[int, str]) -> None:
- """
- The processed batch dict should have the same number of items as lines in the
- batch file.
- """
+ """Test that the batch dict has the same number of lines as the batch file."""
assert len(dl_dict) == 3
def test_batch_file_contents(self, dl_dict: dict[int, str]) -> None:
+ """Test that the batch dict has the correct line numbers and filenames.
+
+ The processed batch dict should have the batch file's line numbers and filenames
+ as keys and values, respectively.
"""
- The processed batch dict should have the correct line numbers and filenames as
- keys and values, respectively.
- """
- expected_list = {1: "File:Foo.jpg", 2: "File:Bar.jpg", 3: "File:Baz.jpg"}
- assert dl_dict == expected_list
+ expected_dict = {1: "File:Foo.jpg", 2: "File:Bar.jpg", 3: "File:Baz.jpg"}
+ assert dl_dict == expected_dict
@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.
+ """Pass three lines of filenames from stdin to read_batch_file to create a dict.
+
+ :param monkeypatch: Pytest monkeypatch helper
+ :type monkeypatch: pytest.MonkeyPatch
+ :return: dictionary representation of the input
+ :rtype: dict[int, str]
"""
monkeypatch.setattr(
"sys.stdin", io.StringIO("File:Foo.jpg\nFile:Bar.jpg\nFile:Baz.jpg\n")
@@ -147,34 +182,38 @@ class TestReadBatchFile:
def test_batch_stdin_log(
self, caplog: pytest.LogCaptureFixture, monkeypatch: pytest.MonkeyPatch
) -> None:
- """
- Using stdin for batch processing should create an info log message saying so.
- """
+ """Test that using stdin for batch processing creates an info log message."""
caplog.set_level(logging.INFO)
monkeypatch.setattr("sys.stdin", io.StringIO("File:Foo.jpg\n"))
_ = read_batch_file("-")
assert "Using stdin for batch download" in caplog.text
def test_batch_stdin_length(self, dl_dict_stdin: dict[int, str]) -> None:
- """
- The processed batch dict should have the same number of items as lines in the
- input.
+ """Test that the batch dict has the correct number of items.
+
+ The dict should contain the same number of items as lines in the input.
"""
assert len(dl_dict_stdin) == 3
def test_batch_stdin_contents(self, dl_dict_stdin: dict[int, str]) -> None:
- """
- The processed batch dict should have the correct line numbers and filenames as
- keys and values, respectively.
+ """Test that the batch dict has the correct keys and values.
+
+ The line numbers and filenames from the input should be the keys and values,
+ respectively.
"""
expected_list = {1: "File:Foo.jpg", 2: "File:Bar.jpg", 3: "File:Baz.jpg"}
assert dl_dict_stdin == expected_list
@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
- commented out and another of which is blank, and return a dictionary.
+ """Create and process a test batch file with four lines.
+
+ In addition to filenames, one line is commented out and another line is blank.
+
+ :param tmp_path: temporary path object
+ :type tmp_path: Path
+ :return: dictionary representation of the input file
+ :rtype: dict[int, str]
"""
tmp_file = tmp_path / "batch.txt"
tmp_file.write_text("File:Foo.jpg\n\n#File:Bar.jpg\nFile:Baz.jpg\n")
@@ -183,7 +222,8 @@ class TestReadBatchFile:
def test_batch_file_with_comment_length(
self, dl_dict_with_comment: dict[int, str]
) -> None:
- """
+ """Test the length of the dict created from a file with comments.
+
The processed batch dict should contain the same number of items as uncommented
and non-blank lines in the input.
"""
@@ -192,7 +232,8 @@ class TestReadBatchFile:
def test_batch_file_with_comment_contents(
self, dl_dict_with_comment: dict[int, str]
) -> None:
- """
+ """Test that the batch dict has the correct keys and values.
+
The processed batch dict should have the correct line numbers and filenames as
keys and values, respectively, skipping any commented or blank lines.
"""
diff --git a/tests/test_validations.py b/tests/test_validations.py
index 5263cdc..161d102 100644
--- a/tests/test_validations.py
+++ b/tests/test_validations.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with Wikiget. If not, see <https://www.gnu.org/licenses/>.
+"""Define tests related to the wikiget.validations module."""
+
from __future__ import annotations
from typing import TYPE_CHECKING
@@ -29,6 +31,8 @@ if TYPE_CHECKING:
class TestSiteInput:
+ """Define tests related to wikiget.validations.valid_site."""
+
@pytest.fixture(
params=[
"example.com",
@@ -38,6 +42,13 @@ class TestSiteInput:
],
)
def invalid_input(self, request: pytest.FixtureRequest) -> Match | None:
+ """Return the results of checking various invalid site names.
+
+ :param request: Pytest request object containing parameter values
+ :type request: pytest.FixtureRequest
+ :return: a Match object for the site or None if there was no match
+ :rtype: Match | None
+ """
return valid_site(request.param)
@pytest.fixture(
@@ -49,6 +60,13 @@ class TestSiteInput:
],
)
def valid_input(self, request: pytest.FixtureRequest) -> Match | None:
+ """Return the results of checking various valid site names.
+
+ :param request: Pytest request object containing parameter values
+ :type request: pytest.FixtureRequest
+ :return: a Match object for the site or None if there was no match
+ :rtype: Match | None
+ """
return valid_site(request.param)
def test_invalid_site_input(self, invalid_input: None) -> None:
@@ -61,28 +79,40 @@ class TestSiteInput:
class TestFileRegex:
+ """Define tests related to the regex matching in wikiget.validations.valid_file."""
+
@pytest.fixture()
def file_match(self) -> Match | None:
- """
- File regex should return a match object with match groups corresponding
- to the file prefix and name.
+ """Return the results of processing a filename.
+
+ The match object returned will have match groups corresponding to the file
+ prefix and name.
+
+ :return: a Match object for the filename or None if there was no match
+ :rtype: Match | None
"""
return valid_file("File:Example.jpg")
def test_file_match_exists(self, file_match: Match) -> None:
+ """Test that a Match object was returned."""
assert file_match is not None
def test_file_match_entire_match(self, file_match: Match) -> None:
+ """Test that the the first match group equals the expected value."""
assert file_match.group(0) == "File:Example.jpg"
def test_file_match_first_group(self, file_match: Match) -> None:
+ """Test that the second match group equals the expected value."""
assert file_match.group(1) == "File:"
def test_file_match_second_group(self, file_match: Match) -> None:
+ """Test that the third match group equals the expected value."""
assert file_match.group(2) == "Example.jpg"
class TestFileInput:
+ """Tests related to wikiget.validations.valid_site."""
+
@pytest.fixture(
params=[
"file:example",
@@ -92,6 +122,13 @@ class TestFileInput:
],
)
def invalid_input(self, request: pytest.FixtureRequest) -> Match | None:
+ """Return the results of checking various invalid filenames.
+
+ :param request: Pytest request object containing parameter values
+ :type request: pytest.FixtureRequest
+ :return: a Match object for the filename or None if there was no match
+ :rtype: Match | None
+ """
return valid_file(request.param)
@pytest.fixture(
@@ -105,6 +142,13 @@ class TestFileInput:
],
)
def valid_input(self, request: pytest.FixtureRequest) -> Match | None:
+ """Return the results of checking various valid filenames.
+
+ :param request: Pytest request object containing parameter values
+ :type request: pytest.FixtureRequest
+ :return: a Match object for the filename or None if there was no match
+ :rtype: Match | None
+ """
return valid_file(request.param)
def test_invalid_file_input(self, invalid_input: None) -> None:
@@ -117,12 +161,15 @@ class TestFileInput:
class TestVerifyHash:
+ """Define tests related to wikiget.validations.verify_hash."""
+
def test_verify_hash(self, tmp_path: Path) -> None:
"""Confirm that verify_hash returns the proper SHA1 hash."""
file_name = "testfile"
file_contents = "foobar"
file_sha1 = "8843d7f92416211de9ebb963ff4ce28125932878"
+ # create a temporary file with known contents
tmp_file = tmp_path / file_name
tmp_file.write_text(file_contents)
diff --git a/tests/test_wikiget_cli.py b/tests/test_wikiget_cli.py
index 0306579..c0f09a9 100644
--- a/tests/test_wikiget_cli.py
+++ b/tests/test_wikiget_cli.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with Wikiget. If not, see <https://www.gnu.org/licenses/>.
+"""Define tests related to the wikiget.wikiget module."""
+
from unittest.mock import MagicMock, patch
import pytest
@@ -24,7 +26,10 @@ from wikiget.wikiget import cli
class TestCli:
- def test_cli_no_params(self, monkeypatch: pytest.MonkeyPatch) -> None:
+ """Define tests related to wikiget.wikiget.cli."""
+
+ def test_cli_no_args(self, monkeypatch: pytest.MonkeyPatch) -> None:
+ """If no arguments are passed, the program should exit with code 2."""
with monkeypatch.context() as m:
m.setattr("sys.argv", ["wikiget"])
with pytest.raises(SystemExit) as e:
@@ -35,6 +40,7 @@ class TestCli:
def test_cli_completed_successfully(
self, mock_process_download: MagicMock, monkeypatch: pytest.MonkeyPatch
) -> None:
+ """If everything is successful, the program should exit with code 0."""
# a successful call to process_download returns 0
mock_process_download.return_value = 0
@@ -49,6 +55,7 @@ class TestCli:
def test_cli_completed_with_problems(
self, mock_process_download: MagicMock, monkeypatch: pytest.MonkeyPatch
) -> None:
+ """If there are problems during execution, the exit code should be 1."""
# an unsuccessful call to process_download returns 1
mock_process_download.return_value = 1
@@ -66,6 +73,11 @@ class TestCli:
monkeypatch: pytest.MonkeyPatch,
caplog: pytest.LogCaptureFixture,
) -> None:
+ """When program execution starts, it should create the right log messages.
+
+ There should be an info log record with the program version as well as a debug
+ record with the program's user agent.
+ """
# a successful call to process_download returns 0
mock_process_download.return_value = 0