Skip to content

Commit

Permalink
Merge pull request #252 from dsgnr/test_docstrings
Browse files Browse the repository at this point in the history
Add docstrings to pytests
  • Loading branch information
dsgnr authored Nov 8, 2024
2 parents 8235bf5 + 87bf458 commit e78ddda
Show file tree
Hide file tree
Showing 9 changed files with 61 additions and 43 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/api-code-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ jobs:
run: poetry install --no-interaction

- name: Test
run: pylint api
run: pylint api tests
isort:
runs-on: ubuntu-latest
steps:
Expand Down
16 changes: 10 additions & 6 deletions backend/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
"""Fixtures for testing the application routes and helper functions"""
from unittest.mock import patch

import pytest
Expand All @@ -14,46 +15,49 @@
INVALID_HOST = "foo"
LOCALHOST_IPV4 = "127.0.0.1"


# Define a fixture to initialize the app with routes
@pytest.fixture
def client():
"""Fixture to provide a test client for app requests."""
return TestClient(app)


# Fixture for creating a mock Request object
@pytest.fixture
def mock_request_path():
"""Fixture to create a mock Request object with a specified path."""
return Request(scope={"method": "GET", "path": "/test-path"})


@pytest.fixture
def mock_socket():
"""Fixture to mock socket.socket calls."""
with patch("socket.socket") as sock:
yield sock


# Mock request headers
@pytest.fixture
def mock_request():
class MockRequest:
"""Fixture to create a mock request with customizable headers."""
class MockRequest: # pylint: disable=too-few-public-methods
"""The MockRequest class"""
def __init__(self, headers):
self.headers = headers

return MockRequest


# Mocking the helper functions
@pytest.fixture
def mock_is_ip_address(mocker):
"""Fixture to mock the is_ip_address helper function."""
return mocker.patch("app.helpers.query.is_ip_address")


@pytest.fixture
def mock_is_address_valid(mocker):
"""Fixture to mock the is_address_valid helper function."""
return mocker.patch("app.helpers.query.is_address_valid")


@pytest.fixture
def mock_is_valid_hostname(mocker):
"""Fixture to mock the is_valid_hostname helper function."""
return mocker.patch("app.helpers.query.is_valid_hostname")
25 changes: 16 additions & 9 deletions backend/tests/test_api_routes.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
"""Tests for API routes"""
from litestar.status_codes import HTTP_200_OK, HTTP_400_BAD_REQUEST

from .conftest import HEADER_REAL_IP, INVALID_HOST, VALID_DOMAIN


def test_route_health_check(client):
"""Test health check route returns status 200 and 'true' response text."""
response = client.get("/healthz")
assert response.status_code == HTTP_200_OK
assert response.text == "true"

def test_my_ip_endpoint(client, mocker):
"""Test my_ip endpoint returns correct requester IP."""
mock_get_requester = mocker.patch(
"app.routes.v2.get_requester", return_value=HEADER_REAL_IP
)
Expand All @@ -14,6 +22,7 @@ def test_my_ip_endpoint(client, mocker):


def test_get_port_check_endpoint_with_hostname(client, mocker):
"""Test port check endpoint with hostname returns expected status."""
mock_get_requester = mocker.patch(
"app.routes.v2.get_requester", return_value=HEADER_REAL_IP
)
Expand All @@ -29,7 +38,7 @@ def test_get_port_check_endpoint_with_hostname(client, mocker):


def test_get_port_check_endpoint_with_me(client, mocker):
# Use the correct path to mock 'get_requester' and 'query_ipv4'
"""Test port check endpoint with 'me' parameter uses get_requester."""
mock_get_requester = mocker.patch(
"app.routes.v2.get_requester", return_value=HEADER_REAL_IP
)
Expand All @@ -49,6 +58,7 @@ def test_get_port_check_endpoint_with_me(client, mocker):


def test_query_post_endpoint_v1(client, mocker):
"""Test v1 query endpoint with valid data returns correct status."""
mock_query_ipv4 = mocker.patch(
"app.routes.v2.query_ipv4",
return_value=[{"port": 80, "status": True}, {"port": 443, "status": False}],
Expand All @@ -67,8 +77,7 @@ def test_query_post_endpoint_v1(client, mocker):


def test_query_post_endpoint_invalid_port_v1(client):
# Validate behavior with invalid port,
# should raise a validation error from APISchema
"""Test v1 query endpoint raises error with invalid port number."""
request_data = {"host": VALID_DOMAIN, "ports": [80, 70000]} # Invalid port range

path = "/api/v1/query"
Expand All @@ -81,8 +90,7 @@ def test_query_post_endpoint_invalid_port_v1(client):


def test_query_post_endpoint_invalid_hostname_v1(client):
# Validate behavior with invalid hostname,
# should raise a validation error from APISchema
"""Test v1 query endpoint raises error with invalid hostname."""
request_data = {"host": INVALID_HOST, "ports": [80]} # Invalid host
path = "/api/v1/query"
response = client.post(path, json=request_data)
Expand All @@ -95,6 +103,7 @@ def test_query_post_endpoint_invalid_hostname_v1(client):


def test_query_post_endpoint_v2(client, mocker):
"""Test v2 query endpoint with valid data returns correct status."""
mock_query_ipv4 = mocker.patch(
"app.routes.v2.query_ipv4",
return_value=[{"port": 80, "status": True}, {"port": 443, "status": False}],
Expand All @@ -113,8 +122,7 @@ def test_query_post_endpoint_v2(client, mocker):


def test_query_post_endpoint_invalid_port_v2(client):
# Validate behavior with invalid port,
# should raise a validation error from APISchema
"""Test v2 query endpoint raises error with invalid port number."""
request_data = {"host": VALID_DOMAIN, "ports": [80, 70000]} # Invalid port range

path = "/api/query"
Expand All @@ -127,8 +135,7 @@ def test_query_post_endpoint_invalid_port_v2(client):


def test_query_post_endpoint_invalid_hostname_v2(client):
# Validate behavior with invalid hostname,
# should raise a validation error from APISchema
"""Test v2 query endpoint raises error with invalid hostname."""
request_data = {"host": INVALID_HOST, "ports": [80]} # Invalid host
path = "/api/query"
response = client.post(path, json=request_data)
Expand Down
7 changes: 4 additions & 3 deletions backend/tests/test_exception_handlers.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
"""Tests for exception handlers"""
from litestar import MediaType
from litestar.exceptions import ValidationException
from litestar.status_codes import HTTP_400_BAD_REQUEST
Expand All @@ -10,8 +11,8 @@
)


# Test for validation_exception_handler
def test_validation_exception_handler(mock_request_path):
"""Test for validation_exception_handler"""
# Create a ValidationException with sample details
exc = ValidationException(detail="Invalid data provided", extra={"field": "value"})

Expand All @@ -28,8 +29,8 @@ def test_validation_exception_handler(mock_request_path):
}


# Test for json_api_exception_handler
def test_json_api_exception_handler(mock_request_path):
"""Test for json_api_exception_handler"""
# Create a JsonAPIException with sample details
exc = JsonAPIException(key="username", message="This field is required")

Expand All @@ -49,8 +50,8 @@ def test_json_api_exception_handler(mock_request_path):
]


# Test for text_value_error_exception_handler
def test_text_value_error_exception_handler(mock_request_path):
"""Test for text_value_error_exception_handler"""
# Create a ValueError with a sample message
exc = ValueError("An unexpected value error occurred")

Expand Down
8 changes: 8 additions & 0 deletions backend/tests/test_get_requester.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,29 @@
"""Tests for get_requester"""
import pytest

from api.app.helpers.query import get_requester


def test_get_requester_with_cf_connecting_ip(mock_request):
"""Test get_requester with 'cf-connecting-ip' header."""
request = mock_request({"cf-connecting-ip": "192.168.1.1"})
assert get_requester(request) == "192.168.1.1"


def test_get_requester_with_do_connecting_ip(mock_request):
"""Test get_requester with 'do-connecting-ip' header."""
request = mock_request({"do-connecting-ip": "192.168.1.2"})
assert get_requester(request) == "192.168.1.2"


def test_get_requester_with_x_real_ip(mock_request):
"""Test get_requester with 'x-real-ip' header."""
request = mock_request({"x-real-ip": "192.168.1.3"})
assert get_requester(request) == "192.168.1.3"


def test_get_requester_prioritizes_known_headers(mock_request):
"""Test get_requester prioritizes 'cf-connecting-ip' over other headers."""
request = mock_request(
{
"cf-connecting-ip": "192.168.1.1",
Expand All @@ -30,17 +35,20 @@ def test_get_requester_prioritizes_known_headers(mock_request):


def test_get_requester_no_known_headers(mock_request):
"""Test get_requester raises error when no known headers are present."""
request = mock_request({"some-other-header": "192.168.1.4"})
with pytest.raises(ValueError, match="The requester IP was not detected"):
get_requester(request)


def test_get_requester_empty_headers(mock_request):
"""Test get_requester raises error when headers are empty."""
request = mock_request({})
with pytest.raises(ValueError, match="The requester IP was not detected"):
get_requester(request)


def test_get_requester_case_insensitivity(mock_request):
"""Test get_requester handles case-insensitive header keys."""
request = mock_request({"Cf-Connecting-Ip": "192.168.1.5"})
assert get_requester(request) == "192.168.1.5"
7 changes: 0 additions & 7 deletions backend/tests/test_health_check.py

This file was deleted.

13 changes: 7 additions & 6 deletions backend/tests/test_hostname.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
"""Tests for is_valid_hostname"""
import socket
from unittest.mock import patch

Expand All @@ -9,25 +10,25 @@


def test_is_valid_hostname_valid():
# Test a valid hostname (e.g., google.com)
"""Test that a valid hostname (e.g., google.com) is correctly identified as valid."""
with patch("socket.gethostbyname", return_value=VALID_PUBLIC_IPV4):
assert is_valid_hostname(VALID_DOMAIN) is True


def test_is_valid_hostname_valid_with_ip():
# Test an IP address (should return True)
"""Test that a valid IP address is correctly identified as valid."""
with patch("socket.gethostbyname", return_value=VALID_PUBLIC_IPV4):
assert is_valid_hostname(VALID_PUBLIC_IPV4) is True


def test_is_valid_hostname_with_scheme():
# Test hostname with a scheme (e.g., http://)
"""Test that a hostname with a scheme (e.g., http://) raises a ValueError."""
with pytest.raises(ValueError, match="The hostname must not have a scheme"):
is_valid_hostname(f"http://{VALID_DOMAIN}")


def test_is_valid_hostname_invalid():
# Test an invalid hostname that cannot be resolved
"""Test that an invalid hostname raises a ValueError."""
with (
patch("socket.gethostbyname", side_effect=socket.gaierror),
pytest.raises(ValueError, match="Hostname does not appear to resolve"),
Expand All @@ -36,12 +37,12 @@ def test_is_valid_hostname_invalid():


def test_is_valid_hostname_empty():
# Test an empty hostname
"""Test that an empty hostname raises a ValueError."""
with pytest.raises(ValueError, match="A hostname must be provided"):
is_valid_hostname("")


def test_is_valid_hostname_localhost():
# Test localhost hostname
"""Test that the `localhost` hostname is correctly identified as valid."""
with patch("socket.gethostbyname", return_value=LOCALHOST_IPV4):
assert is_valid_hostname("localhost") is True
11 changes: 7 additions & 4 deletions backend/tests/test_query_ipv4.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
"""Tests for query_ipv4"""
import socket
from unittest.mock import MagicMock, patch

Expand All @@ -9,7 +10,7 @@


def test_query_ipv4_single_open_port(mock_socket):
# Mock socket connection to return 0, indicating the port is open
"""Mock socket connection to return 0, indicating the port is open"""
mock_sock_instance = MagicMock()
mock_sock_instance.connect_ex.return_value = 0
mock_socket.return_value = mock_sock_instance
Expand All @@ -19,7 +20,7 @@ def test_query_ipv4_single_open_port(mock_socket):


def test_query_ipv4_single_closed_port(mock_socket):
# Mock socket connection to return non-zero, indicating the port is closed
"""Mock socket connection to return non-zero, indicating the port is closed"""
mock_sock_instance = MagicMock()
mock_sock_instance.connect_ex.return_value = 1
mock_socket.return_value = mock_sock_instance
Expand All @@ -29,7 +30,7 @@ def test_query_ipv4_single_closed_port(mock_socket):


def test_query_ipv4_multiple_ports_mixed_status(mock_socket):
# Simulate one open port and one closed port
"""Simulate one open port and one closed port"""
mock_sock_instance = MagicMock()
mock_sock_instance.connect_ex.side_effect = [
0,
Expand All @@ -43,13 +44,14 @@ def test_query_ipv4_multiple_ports_mixed_status(mock_socket):
{"port": ports[1], "status": False},
]


def test_query_ipv4_empty_ports_list():
"""Test query_ipv4 returns empty list when ports list is empty."""
ports = expected_result = []
assert query_ipv4(VALID_PUBLIC_IPV4, ports) == expected_result


def test_query_ipv4_invalid_address():
"""Test query_ipv4 raises JsonAPIException for an invalid hostname."""
with (
patch("socket.gethostbyname", side_effect=socket.gaierror),
pytest.raises(JsonAPIException, match=".*Hostname does not appear to resolve"),
Expand All @@ -58,6 +60,7 @@ def test_query_ipv4_invalid_address():


def test_query_ipv4_valid_address(mock_socket):
"""Test query_ipv4 returns correct status for a valid IP and port."""
mock_sock_instance = MagicMock()
mock_sock_instance.connect_ex.return_value = 0
mock_socket.return_value = mock_sock_instance
Expand Down
Loading

0 comments on commit e78ddda

Please sign in to comment.