From e359e128a25862b2da6c60a28eceebe0c1b35dba Mon Sep 17 00:00:00 2001 From: ATTY Lionel Date: Tue, 20 Aug 2024 13:34:48 +0200 Subject: [PATCH] ci: enhance coverage + refactoring on unit tests Signed-off-by: ATTY Lionel --- .../services/globe_emoji_with_geoip.py | 14 ++-- poetry.lock | 19 ++++- pyproject.toml | 1 + setup.cfg | 1 + tests/test_get_external_ipv4_errors.py | 37 ++++++++++ tests/test_globe_emoji_with_geoip.py | 4 +- tests/test_globe_emoji_with_geoip_errors.py | 74 ++++++++++++++++++- 7 files changed, 141 insertions(+), 9 deletions(-) create mode 100644 tests/test_get_external_ipv4_errors.py diff --git a/cruft_helloworld/services/globe_emoji_with_geoip.py b/cruft_helloworld/services/globe_emoji_with_geoip.py index 71dbe50..0a6dea6 100644 --- a/cruft_helloworld/services/globe_emoji_with_geoip.py +++ b/cruft_helloworld/services/globe_emoji_with_geoip.py @@ -25,18 +25,20 @@ def get_external_ipv4( try: # https://requests.readthedocs.io/en/master/user/quickstart/#timeouts raw = requests.get(URI_TO_DDG_API, timeout=default_timeout) - answer: str = raw.json().get("Answer", "") + json_response = raw.json() + answer: str = json_response.get("Answer", "") logger.debug("request(%s).Answer -> %s", URI_TO_DDG_API, answer) # https://regex101.com/r/NMdWXw/1 regex = ( r"Your IP address is (?P[0-9]*\.[0-9]+\.[0-9]+\.[0-9]+) in.*" ) match = re.match(regex, answer) - if match is None: - logger.error(f"Can't extract ip_address from: {answer}") - else: - # https://docs.python.org/3/library/ipaddress.html#ipaddress.ip_address - external_ip = ip_address(match["ip_address"]) + assert match, f"Can't extract ip_address from: {answer}" + + # https://docs.python.org/3/library/ipaddress.html#ipaddress.ip_address + external_ip = ip_address(match["ip_address"]) + except AssertionError as err: + logger.error(str(err)) except requests.exceptions.Timeout: logger.error( "Timeout (=%.5f) occurred on request: requests.get(%s)!", diff --git a/poetry.lock b/poetry.lock index 1977696..8bfd146 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1763,6 +1763,23 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "requests-mock" +version = "1.12.1" +description = "Mock out responses from the requests package" +optional = false +python-versions = ">=3.5" +files = [ + {file = "requests-mock-1.12.1.tar.gz", hash = "sha256:e9e12e333b525156e82a3c852f22016b9158220d2f47454de9cae8a77d371401"}, + {file = "requests_mock-1.12.1-py2.py3-none-any.whl", hash = "sha256:b1e37054004cdd5e56c84454cc7df12b25f90f382159087f4b6915aaeef39563"}, +] + +[package.dependencies] +requests = ">=2.22,<3" + +[package.extras] +fixture = ["fixtures"] + [[package]] name = "rich" version = "13.7.1" @@ -2087,4 +2104,4 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", [metadata] lock-version = "2.0" python-versions = ">=3.9, <4.0" -content-hash = "9cfae30050a7315b46f3dc9f8a1d2a2e9e41328b56166c1b46fec25ef4155854" +content-hash = "2d44b7b59c5c5281537832b3707f7b60d99df54a3c5bd81afd2a1273a66f82c4" diff --git a/pyproject.toml b/pyproject.toml index 5264815..36c8eaf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,6 +81,7 @@ ipython = "*" [tool.poetry.group.dev.dependencies] pytest-mock = "^3.14.0" +requests-mock = "^1.12.1" [tool.black] line-length = 88 diff --git a/setup.cfg b/setup.cfg index ebd934a..c2c20da 100644 --- a/setup.cfg +++ b/setup.cfg @@ -25,6 +25,7 @@ branch = True include = cruft_helloworld/* omit = */tests/* + cruft_helloworld/tools/click_default_group.py [coverage:report] show_missing = True diff --git a/tests/test_get_external_ipv4_errors.py b/tests/test_get_external_ipv4_errors.py new file mode 100644 index 0000000..6514baa --- /dev/null +++ b/tests/test_get_external_ipv4_errors.py @@ -0,0 +1,37 @@ +import logging + +from cruft_helloworld import __project_name__ +from cruft_helloworld.services.globe_emoji_with_geoip import ( + URI_TO_DDG_API, + get_external_ipv4, +) + + +def test_cant_extract_ip_address_error(requests_mock, caplog): + mock_external_ipv4 = "Your IP address is dummy_ip in" + mock_request_response = f'{{"Answer": "{mock_external_ipv4}"}}' + requests_mock.get(URI_TO_DDG_API, text=mock_request_response) + + with caplog.at_level(logging.ERROR): + assert get_external_ipv4() is None + + records = [record for record in caplog.records if __project_name__ in record.name] + assert len(records) == 1, records + assert records[0].levelname == "ERROR" + assert records[0].message == f"Can't extract ip_address from: {mock_external_ipv4}" + + +def test_not_valid_json_request_response_error(requests_mock, caplog): + mock_request_response = "" + requests_mock.get(URI_TO_DDG_API, text=mock_request_response) + + with caplog.at_level(logging.ERROR): + assert get_external_ipv4() is None + + records = [record for record in caplog.records if __project_name__ in record.name] + assert len(records) == 1, records + assert records[0].levelname == "ERROR" + expected_msg_error = ( + f"Can't perform internet request: requests.get({URI_TO_DDG_API})!" + ) + assert records[0].message == expected_msg_error diff --git a/tests/test_globe_emoji_with_geoip.py b/tests/test_globe_emoji_with_geoip.py index 7aa6948..fdaf632 100644 --- a/tests/test_globe_emoji_with_geoip.py +++ b/tests/test_globe_emoji_with_geoip.py @@ -97,7 +97,9 @@ def test_get_external_ipv4(): Basic test for validate the external ip address retrieve (from DuckDuckGo JSON API) => 4 digits separate by '.' """ - assert len(list(map(int, format(get_external_ipv4()).split(".")))) == 4 + external_ipv4 = get_external_ipv4() + assert external_ipv4 + assert len(list(map(int, format(external_ipv4).split(".")))) == 4 @pytest.mark.use_internet diff --git a/tests/test_globe_emoji_with_geoip_errors.py b/tests/test_globe_emoji_with_geoip_errors.py index 6366f84..a2596d2 100644 --- a/tests/test_globe_emoji_with_geoip_errors.py +++ b/tests/test_globe_emoji_with_geoip_errors.py @@ -2,6 +2,7 @@ import pytest import requests +from geoip import IPInfo from cruft_helloworld import __project_name__ from cruft_helloworld.services.globe_emoji_with_geoip import ( @@ -48,7 +49,78 @@ def test_find_globe_emoji_with_errors( records = [record for record in caplog.records if __project_name__ in record.name] assert len(records) == 2, records assert records[0].levelname == "ERROR" - # error_msg = f"Can't perform internet request: requests.get({URI_TO_DDG_API})!" assert records[0].message == error_log_msg assert records[1].levelname == "DEBUG" assert records[1].message == "No external IP found" + + +def test_geolite2_lookup_return_no_match_error( + requests_mock, + mocker, + caplog, + default_iso_code_continent_emoji, +): + mock_external_ipv4 = "0.0.0.0" + mock_request_answer = f"Your IP address is {mock_external_ipv4} in" + mock_request_response = f'{{"Answer": "{mock_request_answer}"}}' + requests_mock.get(URI_TO_DDG_API, text=mock_request_response) + + mocker.patch( + "cruft_helloworld.services.globe_emoji_with_geoip.geolite2.lookup", + return_value=None, + ) + + with caplog.at_level(logging.DEBUG): + result_emoji = find_globe_emoji_from_external_ip() + expected_emoji = default_iso_code_continent_emoji.value + assert result_emoji == expected_emoji + + records = [record for record in caplog.records if __project_name__ in record.name] + assert len(records) == 2, records + assert records[0].levelname == "DEBUG" + expected_debug_msg = f"request({URI_TO_DDG_API}).Answer -> {mock_request_answer}" + assert records[0].message == expected_debug_msg + assert records[1].levelname == "DEBUG" + expected_debug_msg = ( + f"Can't provides information about the located IP = {mock_external_ipv4}" + ) + assert records[1].message == expected_debug_msg + + +def test_geolite2_lookup_return_an_unknown_match_continent_error( + requests_mock, + mocker, + caplog, + default_iso_code_continent_emoji, +): + mock_external_ipv4 = "0.0.0.0" + mock_request_answer = f"Your IP address is {mock_external_ipv4} in" + mock_request_response = f'{{"Answer": "{mock_request_answer}"}}' + requests_mock.get(URI_TO_DDG_API, text=mock_request_response) + + unknown_code_continent = "UK" + mock_ipinfo = IPInfo( + ip=mock_external_ipv4, data={"continent": {"code": unknown_code_continent}} + ) + mocker.patch( + "cruft_helloworld.services.globe_emoji_with_geoip.geolite2.lookup", + return_value=mock_ipinfo, + ) + + with caplog.at_level(logging.DEBUG): + result_emoji = find_globe_emoji_from_external_ip() + expected_emoji = default_iso_code_continent_emoji.value + assert result_emoji == expected_emoji + + records = [record for record in caplog.records if __project_name__ in record.name] + assert len(records) == 3, records + assert records[0].levelname == "DEBUG" + expected_debug_msg = f"request({URI_TO_DDG_API}).Answer -> {mock_request_answer}" + assert records[0].message == expected_debug_msg + assert records[1].levelname == "DEBUG" + assert records[1].message == f"Match result from geolite = {mock_ipinfo}" + assert records[2].levelname == "ERROR" + assert records[2].message == ( + f"'{unknown_code_continent}' is not a valid ISO Code continent emoji " + f"- Hint: Need to update `tools.enums.IsoCodeContinentEmoji`" + )