mirror of
https://github.com/blw1138/Zordon.git
synced 2026-06-09 13:39:24 -05:00
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
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
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 == {}
|
||||
Reference in New Issue
Block a user