"""Tests for Home Assistant source host classification. The HA source host is user-supplied and stored as ``host:port`` (e.g. ``192.168.1.100:8123``). Before the WebSocket runtime connects to it, the route layer gates the host with the shared LAN classifier so an (authenticated or default-anonymous) caller cannot weaponise it into a public network-scan oracle — mirroring the LED device providers. """ import pytest from ledgrab.api.routes.home_assistant import _validate_ha_host @pytest.mark.parametrize( "host", [ "127.0.0.1:8123", "10.0.0.5:8123", "192.168.1.100:8123", "172.16.0.10", # no port "homeassistant.local:8123", # mDNS label "hass", # bare hostname "[fe80::1]:8123", # bracketed IPv6 link-local + port "[fc00::1]:8123", # bracketed IPv6 ULA + port "169.254.169.254:80", # link-local — allowed by the shared LAN policy "", # empty passes (upstream schema requires non-empty) None, # update path may pass None (host unchanged) ], ) def test_validate_ha_host_accepts_lan(host) -> None: """Loopback / private / link-local / hostnames must not raise. Link-local (169.254/16, fe80::/10) is part of the shared ``validate_lan_host`` LAN policy used by every LED device provider, so HA reuses it unchanged rather than hand-rolling a stricter variant. """ _validate_ha_host(host) @pytest.mark.parametrize( "host", [ "1.1.1.1:8123", # public IPv4 + port "8.8.8.8", # public IPv4, no port "[2606:4700:4700::1111]:8123", # public IPv6 + port ], ) def test_validate_ha_host_rejects_public(host) -> None: """Genuinely-public IPs (the network-scan-oracle risk) are rejected.""" with pytest.raises(ValueError, match="LedGrab is LAN-only"): _validate_ha_host(host)