From af8fcad53aa8ef2233c52df35e70ce9b291cd78b Mon Sep 17 00:00:00 2001 From: Luke Tainton Date: Sun, 26 Jun 2022 00:15:27 +0100 Subject: [PATCH] Added checks to ensure API responses are as expected (#7) * Added checks to ensure API responses are as expected * Add tests for broken IP/prefix API responses * Add tests for bad IP/prefix status codes --- .github/workflows/ci-branch-main.yml | 1 + .github/workflows/ci-development.yml | 1 + .github/workflows/ci-pull-request.yml | 1 + CHANGELOG.md | 5 +++ app/_version.py | 2 +- app/ip_info.py | 17 ++++++++--- app/main.py | 9 +++++- requirements-dev.txt | 1 + tests/test_ip_info.py | 44 +++++++++++++++++++++++++++ 9 files changed, 75 insertions(+), 6 deletions(-) create mode 100644 CHANGELOG.md diff --git a/.github/workflows/ci-branch-main.yml b/.github/workflows/ci-branch-main.yml index c59412b..6040e95 100644 --- a/.github/workflows/ci-branch-main.yml +++ b/.github/workflows/ci-branch-main.yml @@ -7,6 +7,7 @@ on: paths-ignore: - 'README.md' - 'LICENSE.md' + - 'CHANGELOG.md' - '.gitignore' - 'renovate.json' diff --git a/.github/workflows/ci-development.yml b/.github/workflows/ci-development.yml index f31d988..382f942 100644 --- a/.github/workflows/ci-development.yml +++ b/.github/workflows/ci-development.yml @@ -7,6 +7,7 @@ on: paths-ignore: - 'README.md' - 'LICENSE.md' + - 'CHANGELOG.md' - '.gitignore' - 'renovate.json' diff --git a/.github/workflows/ci-pull-request.yml b/.github/workflows/ci-pull-request.yml index 7de9d84..c4728b2 100644 --- a/.github/workflows/ci-pull-request.yml +++ b/.github/workflows/ci-pull-request.yml @@ -4,6 +4,7 @@ on: paths-ignore: - 'README.md' - 'LICENSE.md' + - 'CHANGELOG.md' - '.gitignore' - 'renovate.json' diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..b6cc0c3 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +# 1.1 +- Added checks to ensure API responses are as expected + +# 1.0 +- Initial release diff --git a/app/_version.py b/app/_version.py index c51437b..71a2ee0 100644 --- a/app/_version.py +++ b/app/_version.py @@ -2,4 +2,4 @@ """MODULE: Specifies app version.""" -VERSION = "1.0" +VERSION = "1.1" diff --git a/app/ip_info.py b/app/ip_info.py index ade9dde..9e41e1a 100644 --- a/app/ip_info.py +++ b/app/ip_info.py @@ -9,8 +9,13 @@ import requests def get_ip_information(ipv4_address: ipaddress.IPv4Address) -> dict: """Retrieves information about a given IPv4 address from IP-API.com.""" api_endpoint = f"http://ip-api.com/json/{ipv4_address}" - resp = requests.get(api_endpoint).json() - return resp + try: + resp = requests.get(api_endpoint) + resp.raise_for_status() + ret = resp.json() if resp.json().get("status") == "success" else None + except (requests.exceptions.JSONDecodeError, requests.exceptions.HTTPError): + ret = None + return ret def get_autonomous_system_number(as_info: str) -> str: @@ -22,7 +27,11 @@ def get_autonomous_system_number(as_info: str) -> str: def get_prefix_information(autonomous_system: int) -> list: """Retrieves prefix information about a given autonomous system.""" api_endpoint = f"https://api.hackertarget.com/aslookup/?q={str(autonomous_system)}" - resp = requests.get(api_endpoint).text - prefixes = resp.split("\n") + try: + resp = requests.get(api_endpoint) + resp.raise_for_status() + except requests.exceptions.HTTPError: + return None + prefixes = resp.text.split("\n") prefixes.pop(0) return prefixes diff --git a/app/main.py b/app/main.py index 898afb8..6223415 100644 --- a/app/main.py +++ b/app/main.py @@ -38,6 +38,9 @@ def main(): # Get information from API ip_info = get_ip_information(ip_address) + if not ip_info: + print("ERROR: could not retrieve IP information from API.") + sys.exit(1) as_number = get_autonomous_system_number(ip_info.get("as")) # Assemble list for table generation @@ -56,7 +59,11 @@ def main(): # If wanted, get prefix information if args.prefixes: prefix_info = get_prefix_information(as_number) - table_data.append(["Prefixes", generate_prefix_string(prefix_info)]) + if not prefix_info: + print("ERROR: could not retrieve prefix information from API.") + sys.exit(1) + else: + table_data.append(["Prefixes", generate_prefix_string(prefix_info)]) print_table(table_data) diff --git a/requirements-dev.txt b/requirements-dev.txt index fcf0dd6..df0ec7f 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -2,3 +2,4 @@ black coverage pylint pytest +requests-mock diff --git a/tests/test_ip_info.py b/tests/test_ip_info.py index ea84846..32906bf 100644 --- a/tests/test_ip_info.py +++ b/tests/test_ip_info.py @@ -2,6 +2,8 @@ """MODULE: Provides test cases for app/ip_info.py.""" +import requests_mock + from app.ip_info import ( # pragma: no cover get_ip_information, get_autonomous_system_number, @@ -16,6 +18,24 @@ def test_get_ip_information() -> None: assert ip_info.get("status") == "success" and ip_info.get("query") == test_query +def test_get_ip_information_broken_api_response() -> None: + """TEST: ensure that None is returned if the IP API response is broken.""" + test_query = "1.2.3.4" + with requests_mock.Mocker() as mocker: + mocker.get(f"http://ip-api.com/json/{test_query}", text="error") + resp = get_ip_information(test_query) + assert not resp + + +def test_get_ip_information_bad_response() -> None: + """TEST: ensure that None is returned if the IP API returns code 404.""" + test_query = "1.2.3.4" + with requests_mock.Mocker() as mocker: + mocker.get(f"http://ip-api.com/json/{test_query}", status_code=404) + resp = get_ip_information(test_query) + assert not resp + + def test_get_autonomous_system_number() -> None: """TEST: ensure that AS information is parsed into AS number correctly.""" as_info = "AS5089 Virgin Media Limited" @@ -28,3 +48,27 @@ def test_get_prefix_information() -> None: autonomous_system = "AS109" prefixes = get_prefix_information(autonomous_system) assert "144.254.0.0/16" in prefixes + + +def test_get_prefix_information_broken_api_response() -> None: + """TEST: ensure that None is returned if the prefix API response is broken.""" + autonomous_system = "AS109" + with requests_mock.Mocker() as mocker: + mocker.get( + f"https://api.hackertarget.com/aslookup/?q={str(autonomous_system)}", + text="error", + ) + resp = get_prefix_information(autonomous_system) + assert not resp + + +def test_get_prefix_information_bad_response() -> None: + """TEST: ensure that None is returned if the prefix API returns code 404.""" + autonomous_system = "AS109" + with requests_mock.Mocker() as mocker: + mocker.get( + f"https://api.hackertarget.com/aslookup/?q={str(autonomous_system)}", + status_code=404, + ) + resp = get_prefix_information(autonomous_system) + assert not resp