Files
Zordon/tests/test_zeroconf_server.py
brett 24eb7b5616 Add Unit Tests (#132)
* Add tests and new github workflow

* Add new unit tests

* Add Github CI workflow

* Workflow fix

* Add pytest install to workflow file

* More CI / test updates

* More test cleanup

* Whitespace cleanup and link complexity override

* More whitespace cleanup

* Make lint less strict

* More lint tweaks
2026-06-06 00:02:01 -05:00

160 lines
6.5 KiB
Python

from unittest.mock import MagicMock, patch
import pytest
from src.utilities.zeroconf_server import ZeroconfServer
class TestConfigure:
"""Configuring service parameters."""
def test_configure_sets_attributes(self, zeroconf_server_instance):
with patch('socket.gethostbyname', return_value='192.168.1.1'):
ZeroconfServer.configure('_zordon._tcp.local.', 'test-server', 8080)
assert zeroconf_server_instance.service_type == '_zordon._tcp.local.'
assert zeroconf_server_instance.server_name == 'test-server'
assert zeroconf_server_instance.server_port == 8080
def test_configure_calls_sync_class(self, zeroconf_server_instance):
with patch('socket.gethostbyname', return_value='192.168.1.1'):
ZeroconfServer.configure('_zordon._tcp.local.', 'test-server', 8080)
assert ZeroconfServer.service_type == '_zordon._tcp.local.'
assert ZeroconfServer.server_port == 8080
def test_configure_stops_on_gaierror(self, zeroconf_server_instance):
import socket
with patch('socket.gethostbyname', side_effect=socket.gaierror):
with patch.object(zeroconf_server_instance, '_stop') as mock_stop:
ZeroconfServer.configure('_zordon._tcp.local.', 'test-server', 8080)
mock_stop.assert_called_once()
class TestSyncClass:
"""_sync_class propagates instance attrs to class level."""
def test_sync_class_propagates_all_attrs(self, zeroconf_server_instance):
zeroconf_server_instance.service_type = '_test._tcp.'
zeroconf_server_instance.server_name = 'foo'
zeroconf_server_instance.server_port = 9999
zeroconf_server_instance.server_ip = '10.0.0.1'
zeroconf_server_instance.properties = {'key': 'val'}
ZeroconfServer._sync_class()
assert ZeroconfServer.service_type == '_test._tcp.'
assert ZeroconfServer.server_name == 'foo'
assert ZeroconfServer.server_port == 9999
assert ZeroconfServer.server_ip == '10.0.0.1'
assert ZeroconfServer.properties == {'key': 'val'}
class TestStartStop:
"""Service lifecycle."""
def test_start_raises_without_configure(self, zeroconf_server_instance):
with pytest.raises(RuntimeError, match='configure'):
ZeroconfServer.start()
@patch('src.utilities.zeroconf_server.ServiceBrowser')
def test_start_listen_only_skips_register(self, mock_browser, zeroconf_server_instance):
with patch('socket.gethostbyname', return_value='192.168.1.1'):
ZeroconfServer.configure('_zordon._tcp.local.', 'test', 8080)
with patch.object(zeroconf_server_instance, '_register_service') as mock_register:
ZeroconfServer.start(listen_only=True)
mock_register.assert_not_called()
@patch('src.utilities.zeroconf_server.ServiceBrowser')
def test_start_registers_service(self, mock_browser, zeroconf_server_instance):
with patch('socket.gethostbyname', return_value='192.168.1.1'):
ZeroconfServer.configure('_zordon._tcp.local.', 'test', 8080)
with patch.object(zeroconf_server_instance, '_register_service') as mock_register:
ZeroconfServer.start(listen_only=False)
mock_register.assert_called_once()
def test_stop_unregisters_and_closes(self, zeroconf_server_instance):
zeroconf_server_instance.service_info = MagicMock()
with patch.object(zeroconf_server_instance, '_unregister_service') as mock_unreg:
with patch.object(zeroconf_server_instance.zeroconf, 'close') as mock_close:
ZeroconfServer.stop()
mock_unreg.assert_called_once()
mock_close.assert_called_once()
class TestRegisterService:
"""Service registration with Zeroconf."""
@patch('src.utilities.zeroconf_server.ServiceInfo')
@patch('socket.gethostbyname')
def test_registers_service_info(self, mock_gethostbyname, mock_service_info,
zeroconf_server_instance):
mock_gethostbyname.return_value = '192.168.1.1'
zeroconf_server_instance.service_type = '_zordon._tcp.local.'
zeroconf_server_instance.server_name = 'test'
zeroconf_server_instance.server_port = 8080
zeroconf_server_instance.properties = {}
# Replace real Zeroconf with a mock so we don't actually register
zeroconf_server_instance.zeroconf = MagicMock()
mock_info = MagicMock()
mock_service_info.return_value = mock_info
with patch('socket.inet_aton', return_value=b'\xc0\xa8\x01\x01'):
zeroconf_server_instance._register_service()
zeroconf_server_instance.zeroconf.register_service.assert_called_once_with(mock_info)
assert zeroconf_server_instance.service_info == mock_info
class TestFoundHostnames:
"""Discovery cache."""
def test_found_hostnames_empty_initially(self, zeroconf_server_instance):
result = zeroconf_server_instance._found_hostnames()
assert result == []
@patch('socket.gethostname', return_value='my-machine')
def test_hostnames_sorted_with_local_first(self, mock_hostname, zeroconf_server_instance):
zeroconf_server_instance.client_cache = {
'other-machine': MagicMock(),
'my-machine': MagicMock(),
}
result = zeroconf_server_instance._found_hostnames()
# sort_key returns False (0) for local → sorted first
assert result[0] == 'my-machine'
def test_get_hostname_properties_returns_decoded(self, zeroconf_server_instance):
info = MagicMock()
info.properties = {b'key': b'value', b'num': b'42'}
zeroconf_server_instance.client_cache['server-1'] = info
result = zeroconf_server_instance._get_hostname_properties('server-1')
assert result == {'key': 'value', 'num': '42'}
def test_get_hostname_properties_returns_empty_for_unknown(self, zeroconf_server_instance):
result = zeroconf_server_instance._get_hostname_properties('unknown')
assert result == {}
class TestForwarders:
"""Classmethod forwarders delegate to instance."""
def test_found_hostnames_forwarder(self, zeroconf_server_instance):
zeroconf_server_instance.client_cache['svr'] = MagicMock()
result = ZeroconfServer.found_hostnames()
assert 'svr' in result
def test_get_hostname_properties_forwarder(self, zeroconf_server_instance):
info = MagicMock()
info.properties = {}
zeroconf_server_instance.client_cache['svr'] = info
result = ZeroconfServer.get_hostname_properties('svr')
assert result == {}