From 08a5907bd8b34e2f99a0c74e6756c66134ceb7d3 Mon Sep 17 00:00:00 2001 From: Cody Logan Date: Fri, 3 Nov 2023 10:40:07 -0700 Subject: Move from unittest.mock to pytest's monkeypatch where feasible --- tests/test_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tests/test_client.py') diff --git a/tests/test_client.py b/tests/test_client.py index 9b1b8a4..c34a842 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -25,7 +25,6 @@ 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( @@ -42,6 +41,7 @@ class TestQueryApi: assert mock_site.called assert "Connecting to commons.wikimedia.org" in caplog.text + # TODO: don't hit the actual API when doing tests @pytest.mark.skip(reason="skip tests that query a live API") def test_query_api(self, caplog: pytest.LogCaptureFixture) -> None: """ -- cgit v1.2.3 From f28e25c999169a0ea0dc6dc12895a4b6dfab148f Mon Sep 17 00:00:00 2001 From: Cody Logan Date: Fri, 3 Nov 2023 12:09:42 -0700 Subject: Test for exceptions in connect_to_site --- tests/test_client.py | 81 ++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 69 insertions(+), 12 deletions(-) (limited to 'tests/test_client.py') diff --git a/tests/test_client.py b/tests/test_client.py index c34a842..18af3f9 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -16,30 +16,87 @@ # along with Wikiget. If not, see . import logging -from unittest.mock import MagicMock, patch +from unittest.mock import patch import pytest +from mwclient import InvalidResponse +from requests import ConnectionError, HTTPError -from wikiget import USER_AGENT +from wikiget import DEFAULT_SITE, USER_AGENT from wikiget.client import connect_to_site, query_api from wikiget.wikiget import parse_args -class TestQueryApi: - @patch("mwclient.Site.__new__") - def test_connect_to_site( - self, mock_site: MagicMock, caplog: pytest.LogCaptureFixture - ) -> None: +class TestClient: + 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, f"Connecting to {DEFAULT_SITE}"), + ] + + def test_connect_to_site_connection_error( + self, caplog: pytest.LogCaptureFixture + ) -> None: + """ + The connect_to_site function should log an error if a ConnectionError exception + is raised. + """ + caplog.set_level(logging.DEBUG) + args = parse_args(["File:Example.jpg"]) + 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, f"Connecting to {DEFAULT_SITE}"), + ("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 connect_to_site function should log an error if an HTTPError 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 + 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, f"Connecting to {DEFAULT_SITE}"), + ( + "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" # TODO: don't hit the actual API when doing tests @pytest.mark.skip(reason="skip tests that query a live API") -- cgit v1.2.3 From 5b3fd4383462503c8d9eaae53692e68b159d5536 Mon Sep 17 00:00:00 2001 From: Cody Logan Date: Fri, 3 Nov 2023 16:37:59 -0700 Subject: Revise query_api test to use mock objects --- tests/test_client.py | 48 ++++++++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 22 deletions(-) (limited to 'tests/test_client.py') diff --git a/tests/test_client.py b/tests/test_client.py index 18af3f9..fd47a35 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -16,18 +16,22 @@ # along with Wikiget. If not, see . import logging -from unittest.mock import patch +from unittest.mock import MagicMock, patch import pytest -from mwclient import InvalidResponse +from mwclient import InvalidResponse, Site +from mwclient.image import Image from requests import ConnectionError, HTTPError -from wikiget import DEFAULT_SITE, USER_AGENT +from wikiget import DEFAULT_SITE from wikiget.client import connect_to_site, query_api from wikiget.wikiget import parse_args -class TestClient: +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 an info log message recording the @@ -38,15 +42,15 @@ class TestClient: with patch("wikiget.client.Site"): _ = connect_to_site(DEFAULT_SITE, args) assert caplog.record_tuples == [ - ("wikiget.client", logging.INFO, f"Connecting to {DEFAULT_SITE}"), + ("wikiget.client", logging.INFO, self.info_msg), ] def test_connect_to_site_connection_error( self, caplog: pytest.LogCaptureFixture ) -> None: """ - The connect_to_site function should log an error if a ConnectionError exception - is raised. + The connect_to_site function should log the correct messages if a + ConnectionError exception is raised. """ caplog.set_level(logging.DEBUG) args = parse_args(["File:Example.jpg"]) @@ -56,15 +60,15 @@ class TestClient: _ = connect_to_site(DEFAULT_SITE, args) assert "Could not connect to specified site" in caplog.text assert caplog.record_tuples == [ - ("wikiget.client", logging.INFO, f"Connecting to {DEFAULT_SITE}"), + ("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 connect_to_site function should log an error if an HTTPError exception - is raised. + 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"]) @@ -73,14 +77,14 @@ class TestClient: with pytest.raises(HTTPError): _ = connect_to_site(DEFAULT_SITE, args) assert caplog.record_tuples == [ - ("wikiget.client", logging.INFO, f"Connecting to {DEFAULT_SITE}"), + ("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, "") + ("wikiget.client", logging.DEBUG, ""), ] def test_connect_to_site_other_error( @@ -98,15 +102,15 @@ class TestClient: for record in caplog.records: assert record.levelname == "ERROR" - # TODO: don't hit the actual API when doing tests - @pytest.mark.skip(reason="skip tests that query a live API") - def test_query_api(self, caplog: pytest.LogCaptureFixture) -> None: + +class TestQueryApi: + def test_query_api(self) -> None: """ - The query_api function should create a debug log message containing the user - agent we're sending to the API. + The query_api function should return an Image object when given a name and a + valid Site. """ - 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 + mock_site = MagicMock(Site) + mock_image = MagicMock(Image) + mock_site.images = {"Example.jpg": mock_image} + image = query_api("Example.jpg", mock_site) + assert image == mock_image -- cgit v1.2.3 From 831fb088d6f902bf5da52a4da7f2d5d731d9f72e Mon Sep 17 00:00:00 2001 From: Cody Logan Date: Mon, 6 Nov 2023 11:34:51 -0800 Subject: Add explanatory comment to query_api test --- tests/test_client.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'tests/test_client.py') diff --git a/tests/test_client.py b/tests/test_client.py index fd47a35..45739d3 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -109,8 +109,13 @@ class TestQueryApi: 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(Site) mock_image = MagicMock(Image) mock_site.images = {"Example.jpg": mock_image} + image = query_api("Example.jpg", mock_site) assert image == mock_image -- cgit v1.2.3 From 3d34b09a361dadb50bb4e4ffa18c75928904c30d Mon Sep 17 00:00:00 2001 From: Cody Logan Date: Mon, 6 Nov 2023 12:18:46 -0800 Subject: Simplify mock usage in tests --- tests/test_client.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) (limited to 'tests/test_client.py') diff --git a/tests/test_client.py b/tests/test_client.py index 45739d3..207d9b2 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -16,11 +16,10 @@ # along with Wikiget. If not, see . import logging -from unittest.mock import MagicMock, patch +from unittest.mock import MagicMock, patch, sentinel import pytest -from mwclient import InvalidResponse, Site -from mwclient.image import Image +from mwclient import InvalidResponse from requests import ConnectionError, HTTPError from wikiget import DEFAULT_SITE @@ -113,9 +112,8 @@ class TestQueryApi: # 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(Site) - mock_image = MagicMock(Image) - mock_site.images = {"Example.jpg": mock_image} + mock_site = MagicMock() + mock_site.images = {"Example.jpg": sentinel.mock_image} image = query_api("Example.jpg", mock_site) - assert image == mock_image + assert image == sentinel.mock_image -- cgit v1.2.3 From 14b3f3e4c48183776d3021fa596f30d2a3c1091f Mon Sep 17 00:00:00 2001 From: Cody Logan Date: Tue, 7 Nov 2023 11:24:35 -0800 Subject: Emit a log message when authenticating with a private wiki --- tests/test_client.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) (limited to 'tests/test_client.py') diff --git a/tests/test_client.py b/tests/test_client.py index 207d9b2..e17c17a 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -38,12 +38,33 @@ class TestConnectSite: """ 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: @@ -53,10 +74,12 @@ class TestConnectSite: """ caplog.set_level(logging.DEBUG) args = parse_args(["File:Example.jpg"]) + 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), @@ -71,10 +94,12 @@ class TestConnectSite: """ caplog.set_level(logging.DEBUG) args = parse_args(["File:Example.jpg"]) + 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), ( @@ -94,10 +119,12 @@ class TestConnectSite: 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" @@ -116,4 +143,5 @@ class TestQueryApi: mock_site.images = {"Example.jpg": sentinel.mock_image} image = query_api("Example.jpg", mock_site) + assert image == sentinel.mock_image -- cgit v1.2.3 From afd8bcae61290ed7025cbb6e6da4e8dcd1055e4f Mon Sep 17 00:00:00 2001 From: Cody Logan Date: Tue, 7 Nov 2023 11:35:39 -0800 Subject: Test query_api when an APIError is raised --- tests/test_client.py | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) (limited to 'tests/test_client.py') diff --git a/tests/test_client.py b/tests/test_client.py index e17c17a..4cbf702 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -19,7 +19,7 @@ import logging from unittest.mock import MagicMock, patch, sentinel import pytest -from mwclient import InvalidResponse +from mwclient import APIError, InvalidResponse from requests import ConnectionError, HTTPError from wikiget import DEFAULT_SITE @@ -145,3 +145,31 @@ class TestQueryApi: 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"), + ] -- cgit v1.2.3