From dda7e584dbdbb9c4e7c29277053c59852c31b847 Mon Sep 17 00:00:00 2001 From: Horizontal42 <75383519+Horizontal42@users.noreply.github.com> Date: Wed, 19 Aug 2026 17:18:59 +0300 Subject: [PATCH] Add comprehensive test suite for collect_local_net in netinfo.py. (#19) This addresses the missing coverage for local network parameter discovery. Tests cover happy paths, CGNAT scenarios, and cases with no network components. The additions improve reliability and prevent regression of core host capability detection. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --- tests/test_netinfo.py | 104 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/tests/test_netinfo.py b/tests/test_netinfo.py index e52a039..53f0edd 100644 --- a/tests/test_netinfo.py +++ b/tests/test_netinfo.py @@ -199,3 +199,107 @@ Network Destination Netmask Gateway Interface Metric def test_windows_default_gateway_returns_none_when_no_default_route_exists(): assert _parse_windows_default_gateway("Active Routes:\nNone\n", socket.AF_INET) is None + +from dataclasses import dataclass + +@dataclass +class MockAddr: + family: int + address: str + +@dataclass +class MockStats: + mtu: int + +from unittest.mock import patch +from netsleuth.netinfo import collect_local_net +from netsleuth.models import LocalNet + +@patch("netsleuth.netinfo.primary_interface_ip") +@patch("netsleuth.netinfo.psutil.net_if_addrs") +@patch("netsleuth.netinfo.psutil.net_if_stats") +@patch("netsleuth.netinfo._default_gateway") +@patch("netsleuth.netinfo._resolvers_per_adapter") +def test_collect_local_net_happy_path(mock_resolvers, mock_gw, mock_stats, mock_addrs, mock_ip): + # Setup mocks + mock_ip.side_effect = ["192.168.1.50", "2001:db8::1"] + mock_addrs.return_value = { + "eth0": [MockAddr(family=socket.AF_INET, address="192.168.1.50"), MockAddr(family=socket.AF_INET6, address="2001:db8::1")] + } + mock_stats.return_value = {"eth0": MockStats(mtu=1500)} + mock_gw.side_effect = ["192.168.1.1", "fe80::1"] + mock_resolvers.return_value = {"eth0": ["1.1.1.1", "8.8.8.8"]} + + result = collect_local_net() + + assert result.iface_name == "eth0" + assert result.local_ipv4 == "192.168.1.50" + assert result.local_ipv6 == "2001:db8::1" + assert result.iface_mtu == 1500 + assert result.default_gateway_v4 == "192.168.1.1" + assert result.default_gateway_v6 == "fe80::1" + assert result.dns_servers_per_adapter == {"eth0": ["1.1.1.1", "8.8.8.8"]} + assert result.is_dual_stack is True + assert result.cgnat is False + assert result.cgnat_evidence is None + +@patch("netsleuth.netinfo.primary_interface_ip") +@patch("netsleuth.netinfo.psutil.net_if_addrs") +@patch("netsleuth.netinfo.psutil.net_if_stats") +@patch("netsleuth.netinfo._default_gateway") +@patch("netsleuth.netinfo._resolvers_per_adapter") +def test_collect_local_net_cgnat_on_local_ip(mock_resolvers, mock_gw, mock_stats, mock_addrs, mock_ip): + mock_ip.side_effect = ["100.64.1.2", None] + mock_addrs.return_value = { + "eth0": [MockAddr(family=socket.AF_INET, address="100.64.1.2")] + } + mock_stats.return_value = {"eth0": MockStats(mtu=1500)} + mock_gw.side_effect = ["100.64.1.1", None] + mock_resolvers.return_value = {} + + result = collect_local_net() + + assert result.cgnat is True + assert "100.64.1.2" in result.cgnat_evidence + +@patch("netsleuth.netinfo.primary_interface_ip") +@patch("netsleuth.netinfo.psutil.net_if_addrs") +@patch("netsleuth.netinfo.psutil.net_if_stats") +@patch("netsleuth.netinfo._default_gateway") +@patch("netsleuth.netinfo._resolvers_per_adapter") +def test_collect_local_net_cgnat_on_gateway(mock_resolvers, mock_gw, mock_stats, mock_addrs, mock_ip): + mock_ip.side_effect = ["192.168.1.50", None] + mock_addrs.return_value = { + "eth0": [MockAddr(family=socket.AF_INET, address="192.168.1.50")] + } + mock_stats.return_value = {"eth0": MockStats(mtu=1500)} + mock_gw.side_effect = ["100.64.1.1", None] + mock_resolvers.return_value = {} + + result = collect_local_net() + + assert result.cgnat is True + assert "100.64.1.1" in result.cgnat_evidence + +@patch("netsleuth.netinfo.primary_interface_ip") +@patch("netsleuth.netinfo.psutil.net_if_addrs") +@patch("netsleuth.netinfo.psutil.net_if_stats") +@patch("netsleuth.netinfo._default_gateway") +@patch("netsleuth.netinfo._resolvers_per_adapter") +def test_collect_local_net_no_network(mock_resolvers, mock_gw, mock_stats, mock_addrs, mock_ip): + mock_ip.side_effect = [None, None] + mock_addrs.return_value = {} + mock_stats.return_value = {} + mock_gw.side_effect = [None, None] + mock_resolvers.return_value = {} + + result = collect_local_net() + + assert result.iface_name is None + assert result.local_ipv4 is None + assert result.local_ipv6 is None + assert result.iface_mtu is None + assert result.default_gateway_v4 is None + assert result.default_gateway_v6 is None + assert result.is_dual_stack is False + assert result.cgnat is False