mirror of
https://github.com/craigerl/aprsd.git
synced 2026-06-05 15:44:40 -04:00
Added unit tests
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
from aprsd.packets.filters.dupe_filter import DupePacketFilter
|
||||
from tests import fake
|
||||
|
||||
|
||||
class TestDupePacketFilter(unittest.TestCase):
|
||||
"""Unit tests for the DupePacketFilter class."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test fixtures."""
|
||||
self.filter = DupePacketFilter()
|
||||
from oslo_config import cfg
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.packet_dupe_timeout = 60
|
||||
|
||||
def test_filter_ack_packet(self):
|
||||
"""Test filter() with AckPacket (should always pass)."""
|
||||
packet = fake.fake_ack_packet()
|
||||
result = self.filter.filter(packet)
|
||||
self.assertEqual(result, packet)
|
||||
|
||||
def test_filter_new_packet(self):
|
||||
"""Test filter() with new packet."""
|
||||
packet = fake.fake_packet(msg_number='123')
|
||||
|
||||
with mock.patch(
|
||||
'aprsd.packets.filters.dupe_filter.packets.PacketList'
|
||||
) as mock_list:
|
||||
mock_list_instance = mock.MagicMock()
|
||||
mock_list_instance.find.side_effect = KeyError('Not found')
|
||||
mock_list.return_value = mock_list_instance
|
||||
|
||||
result = self.filter.filter(packet)
|
||||
self.assertEqual(result, packet)
|
||||
|
||||
def test_filter_packet_no_msgno(self):
|
||||
"""Test filter() with packet without msgNo."""
|
||||
packet = fake.fake_packet()
|
||||
packet.msgNo = None
|
||||
|
||||
with mock.patch(
|
||||
'aprsd.packets.filters.dupe_filter.packets.PacketList'
|
||||
) as mock_list:
|
||||
mock_list_instance = mock.MagicMock()
|
||||
found_packet = fake.fake_packet()
|
||||
mock_list_instance.find.return_value = found_packet
|
||||
mock_list.return_value = mock_list_instance
|
||||
|
||||
# Should pass even if found (no msgNo = can't detect dupe)
|
||||
result = self.filter.filter(packet)
|
||||
self.assertEqual(result, packet)
|
||||
|
||||
def test_filter_unprocessed_duplicate(self):
|
||||
"""Test filter() with duplicate but unprocessed packet."""
|
||||
packet = fake.fake_packet(msg_number='123')
|
||||
packet.processed = False
|
||||
|
||||
with mock.patch(
|
||||
'aprsd.packets.filters.dupe_filter.packets.PacketList'
|
||||
) as mock_list:
|
||||
mock_list_instance = mock.MagicMock()
|
||||
found_packet = fake.fake_packet(msg_number='123')
|
||||
mock_list_instance.find.return_value = found_packet
|
||||
mock_list.return_value = mock_list_instance
|
||||
|
||||
result = self.filter.filter(packet)
|
||||
self.assertEqual(result, packet)
|
||||
|
||||
def test_filter_duplicate_within_timeout(self):
|
||||
"""Test filter() with duplicate within timeout."""
|
||||
from oslo_config import cfg
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.packet_dupe_timeout = 60
|
||||
|
||||
packet = fake.fake_packet(msg_number='123')
|
||||
packet.processed = True
|
||||
packet.timestamp = 1000
|
||||
|
||||
with mock.patch(
|
||||
'aprsd.packets.filters.dupe_filter.packets.PacketList'
|
||||
) as mock_list:
|
||||
mock_list_instance = mock.MagicMock()
|
||||
found_packet = fake.fake_packet(msg_number='123')
|
||||
found_packet.timestamp = 1050 # Within 60 second timeout
|
||||
mock_list_instance.find.return_value = found_packet
|
||||
mock_list.return_value = mock_list_instance
|
||||
|
||||
with mock.patch('aprsd.packets.filters.dupe_filter.LOG') as mock_log:
|
||||
result = self.filter.filter(packet)
|
||||
self.assertIsNone(result) # Should be dropped
|
||||
mock_log.warning.assert_called()
|
||||
|
||||
def test_filter_duplicate_after_timeout(self):
|
||||
"""Test filter() with duplicate after timeout."""
|
||||
from oslo_config import cfg
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.packet_dupe_timeout = 60
|
||||
|
||||
packet = fake.fake_packet(msg_number='123')
|
||||
packet.processed = True
|
||||
packet.timestamp = 2000
|
||||
|
||||
with mock.patch(
|
||||
'aprsd.packets.filters.dupe_filter.packets.PacketList'
|
||||
) as mock_list:
|
||||
mock_list_instance = mock.MagicMock()
|
||||
found_packet = fake.fake_packet(msg_number='123')
|
||||
found_packet.timestamp = 1000 # More than 60 seconds ago
|
||||
mock_list_instance.find.return_value = found_packet
|
||||
mock_list.return_value = mock_list_instance
|
||||
|
||||
with mock.patch('aprsd.packets.filters.dupe_filter.LOG') as mock_log:
|
||||
result = self.filter.filter(packet)
|
||||
self.assertEqual(result, packet) # Should pass
|
||||
mock_log.warning.assert_called()
|
||||
@@ -0,0 +1,87 @@
|
||||
import unittest
|
||||
|
||||
from aprsd.packets.filters.packet_type import PacketTypeFilter
|
||||
from tests import fake
|
||||
|
||||
|
||||
class TestPacketTypeFilter(unittest.TestCase):
|
||||
"""Unit tests for the PacketTypeFilter class."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test fixtures."""
|
||||
# Reset singleton instance
|
||||
PacketTypeFilter._instance = None
|
||||
self.filter = PacketTypeFilter()
|
||||
|
||||
def tearDown(self):
|
||||
"""Clean up after tests."""
|
||||
PacketTypeFilter._instance = None
|
||||
|
||||
def test_singleton_pattern(self):
|
||||
"""Test that PacketTypeFilter is a singleton."""
|
||||
filter1 = PacketTypeFilter()
|
||||
filter2 = PacketTypeFilter()
|
||||
self.assertIs(filter1, filter2)
|
||||
|
||||
def test_init(self):
|
||||
"""Test initialization."""
|
||||
# Default allow_list includes Packet base class
|
||||
from aprsd import packets
|
||||
|
||||
self.assertIn(packets.Packet, self.filter.allow_list)
|
||||
|
||||
def test_set_allow_list(self):
|
||||
"""Test set_allow_list() method."""
|
||||
from aprsd import packets as aprsd_packets
|
||||
|
||||
filter_list = ['MessagePacket', 'AckPacket']
|
||||
self.filter.set_allow_list(filter_list)
|
||||
|
||||
self.assertEqual(len(self.filter.allow_list), 2)
|
||||
self.assertIn(aprsd_packets.MessagePacket, self.filter.allow_list)
|
||||
self.assertIn(aprsd_packets.AckPacket, self.filter.allow_list)
|
||||
|
||||
def test_filter_no_allow_list(self):
|
||||
"""Test filter() with no allow list (all packets pass)."""
|
||||
packet = fake.fake_packet()
|
||||
result = self.filter.filter(packet)
|
||||
self.assertEqual(result, packet)
|
||||
|
||||
def test_filter_allowed_type(self):
|
||||
"""Test filter() with allowed packet type."""
|
||||
self.filter.set_allow_list(['MessagePacket'])
|
||||
packet = fake.fake_packet()
|
||||
|
||||
result = self.filter.filter(packet)
|
||||
self.assertEqual(result, packet)
|
||||
|
||||
def test_filter_not_allowed_type(self):
|
||||
"""Test filter() with not allowed packet type."""
|
||||
self.filter.set_allow_list(['AckPacket'])
|
||||
packet = fake.fake_packet() # MessagePacket
|
||||
|
||||
result = self.filter.filter(packet)
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_filter_multiple_types(self):
|
||||
"""Test filter() with multiple allowed types."""
|
||||
self.filter.set_allow_list(['MessagePacket', 'AckPacket', 'BeaconPacket'])
|
||||
|
||||
message_packet = fake.fake_packet()
|
||||
ack_packet = fake.fake_ack_packet()
|
||||
|
||||
result1 = self.filter.filter(message_packet)
|
||||
result2 = self.filter.filter(ack_packet)
|
||||
|
||||
self.assertEqual(result1, message_packet)
|
||||
self.assertEqual(result2, ack_packet)
|
||||
|
||||
def test_filter_subclass(self):
|
||||
"""Test filter() with subclass of allowed type."""
|
||||
# Set allow list to base Packet class
|
||||
self.filter.set_allow_list(['Packet'])
|
||||
|
||||
# All packet types should pass
|
||||
message_packet = fake.fake_packet()
|
||||
result = self.filter.filter(message_packet)
|
||||
self.assertEqual(result, message_packet)
|
||||
@@ -0,0 +1,387 @@
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
from aprsd.packets import collector
|
||||
from tests import fake
|
||||
|
||||
|
||||
class MockPacketMonitor:
|
||||
"""Mock implementation of PacketMonitor for testing."""
|
||||
|
||||
_instance = None
|
||||
|
||||
def __init__(self, name='MockMonitor'):
|
||||
self.name = name
|
||||
self.rx_called = False
|
||||
self.tx_called = False
|
||||
self.flush_called = False
|
||||
self.load_called = False
|
||||
|
||||
def __call__(self):
|
||||
"""Make it callable like a singleton."""
|
||||
if self._instance is None:
|
||||
self._instance = self
|
||||
return self._instance
|
||||
|
||||
def rx(self, packet):
|
||||
self.rx_called = True
|
||||
self.rx_packet = packet
|
||||
|
||||
def tx(self, packet):
|
||||
self.tx_called = True
|
||||
self.tx_packet = packet
|
||||
|
||||
def flush(self):
|
||||
self.flush_called = True
|
||||
|
||||
def load(self):
|
||||
self.load_called = True
|
||||
|
||||
|
||||
class TestPacketMonitorProtocol(unittest.TestCase):
|
||||
"""Test that PacketMonitor protocol is properly defined."""
|
||||
|
||||
def test_protocol_definition(self):
|
||||
"""Test that PacketMonitor is a Protocol."""
|
||||
from aprsd.packets.collector import PacketMonitor
|
||||
|
||||
# Protocol with @runtime_checkable should have this attribute
|
||||
# But it's a Protocol, not a runtime_checkable Protocol necessarily
|
||||
# Let's just check it exists
|
||||
self.assertTrue(
|
||||
hasattr(PacketMonitor, '__protocol_attrs__')
|
||||
or hasattr(PacketMonitor, '__annotations__'),
|
||||
)
|
||||
|
||||
|
||||
class TestPacketCollector(unittest.TestCase):
|
||||
"""Unit tests for the PacketCollector class."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test fixtures."""
|
||||
# Reset singleton instance
|
||||
collector.PacketCollector._instance = None
|
||||
# Clear monitors to start fresh
|
||||
pc = collector.PacketCollector()
|
||||
pc.monitors = []
|
||||
|
||||
def tearDown(self):
|
||||
"""Clean up after tests."""
|
||||
collector.PacketCollector._instance = None
|
||||
|
||||
def test_singleton_pattern(self):
|
||||
"""Test that PacketCollector is a singleton."""
|
||||
collector1 = collector.PacketCollector()
|
||||
collector2 = collector.PacketCollector()
|
||||
self.assertIs(collector1, collector2)
|
||||
|
||||
def test_init(self):
|
||||
"""Test initialization."""
|
||||
pc = collector.PacketCollector()
|
||||
# After setUp, monitors should be empty
|
||||
self.assertEqual(len(pc.monitors), 0)
|
||||
|
||||
def test_register(self):
|
||||
"""Test register() method."""
|
||||
pc = collector.PacketCollector()
|
||||
# Create a callable class
|
||||
|
||||
class TestMonitor:
|
||||
_instance = None
|
||||
|
||||
def __call__(self):
|
||||
if self._instance is None:
|
||||
self._instance = self
|
||||
return self._instance
|
||||
|
||||
def rx(self, packet):
|
||||
pass
|
||||
|
||||
def tx(self, packet):
|
||||
pass
|
||||
|
||||
def flush(self):
|
||||
pass
|
||||
|
||||
def load(self):
|
||||
pass
|
||||
|
||||
monitor_class = TestMonitor()
|
||||
pc.register(monitor_class)
|
||||
self.assertIn(monitor_class, pc.monitors)
|
||||
|
||||
def test_register_non_protocol(self):
|
||||
"""Test register() raises TypeError for non-protocol objects."""
|
||||
pc = collector.PacketCollector()
|
||||
non_monitor = object()
|
||||
|
||||
with self.assertRaises(TypeError):
|
||||
pc.register(non_monitor)
|
||||
|
||||
def test_unregister(self):
|
||||
"""Test unregister() method."""
|
||||
pc = collector.PacketCollector()
|
||||
# Create a callable class
|
||||
|
||||
class TestMonitor:
|
||||
_instance = None
|
||||
|
||||
def __call__(self):
|
||||
if self._instance is None:
|
||||
self._instance = self
|
||||
return self._instance
|
||||
|
||||
def rx(self, packet):
|
||||
pass
|
||||
|
||||
def tx(self, packet):
|
||||
pass
|
||||
|
||||
def flush(self):
|
||||
pass
|
||||
|
||||
def load(self):
|
||||
pass
|
||||
|
||||
monitor_class = TestMonitor()
|
||||
pc.register(monitor_class)
|
||||
pc.unregister(monitor_class)
|
||||
self.assertNotIn(monitor_class, pc.monitors)
|
||||
|
||||
def test_unregister_non_protocol(self):
|
||||
"""Test unregister() raises TypeError for non-protocol objects."""
|
||||
pc = collector.PacketCollector()
|
||||
non_monitor = object()
|
||||
|
||||
with self.assertRaises(TypeError):
|
||||
pc.unregister(non_monitor)
|
||||
|
||||
def test_rx(self):
|
||||
"""Test rx() method."""
|
||||
pc = collector.PacketCollector()
|
||||
# Create callable monitor classes
|
||||
monitor1 = MockPacketMonitor('Monitor1')
|
||||
monitor2 = MockPacketMonitor('Monitor2')
|
||||
pc.register(monitor1)
|
||||
pc.register(monitor2)
|
||||
|
||||
packet = fake.fake_packet()
|
||||
pc.rx(packet)
|
||||
|
||||
self.assertTrue(monitor1().rx_called)
|
||||
self.assertTrue(monitor2().rx_called)
|
||||
self.assertEqual(monitor1().rx_packet, packet)
|
||||
self.assertEqual(monitor2().rx_packet, packet)
|
||||
|
||||
def test_rx_with_exception(self):
|
||||
"""Test rx() handles exceptions gracefully."""
|
||||
pc = collector.PacketCollector()
|
||||
|
||||
class FailingMonitor:
|
||||
_instance = None
|
||||
|
||||
def __call__(self):
|
||||
if self._instance is None:
|
||||
self._instance = self
|
||||
return self._instance
|
||||
|
||||
def rx(self, packet):
|
||||
raise Exception('Monitor error')
|
||||
|
||||
def tx(self, packet):
|
||||
pass
|
||||
|
||||
def flush(self):
|
||||
pass
|
||||
|
||||
def load(self):
|
||||
pass
|
||||
|
||||
monitor = FailingMonitor()
|
||||
pc.register(monitor)
|
||||
|
||||
packet = fake.fake_packet()
|
||||
# Should not raise exception
|
||||
with mock.patch('aprsd.packets.collector.LOG') as mock_log:
|
||||
pc.rx(packet)
|
||||
mock_log.error.assert_called()
|
||||
|
||||
def test_tx(self):
|
||||
"""Test tx() method."""
|
||||
pc = collector.PacketCollector()
|
||||
monitor1 = MockPacketMonitor('Monitor1')
|
||||
monitor2 = MockPacketMonitor('Monitor2')
|
||||
pc.register(monitor1)
|
||||
pc.register(monitor2)
|
||||
|
||||
packet = fake.fake_packet()
|
||||
pc.tx(packet)
|
||||
|
||||
self.assertTrue(monitor1().tx_called)
|
||||
self.assertTrue(monitor2().tx_called)
|
||||
self.assertEqual(monitor1().tx_packet, packet)
|
||||
self.assertEqual(monitor2().tx_packet, packet)
|
||||
|
||||
def test_tx_with_exception(self):
|
||||
"""Test tx() handles exceptions gracefully."""
|
||||
pc = collector.PacketCollector()
|
||||
|
||||
class FailingMonitor:
|
||||
_instance = None
|
||||
|
||||
def __call__(self):
|
||||
if self._instance is None:
|
||||
self._instance = self
|
||||
return self._instance
|
||||
|
||||
def rx(self, packet):
|
||||
pass
|
||||
|
||||
def tx(self, packet):
|
||||
raise Exception('Monitor error')
|
||||
|
||||
def flush(self):
|
||||
pass
|
||||
|
||||
def load(self):
|
||||
pass
|
||||
|
||||
monitor = FailingMonitor()
|
||||
pc.register(monitor)
|
||||
|
||||
packet = fake.fake_packet()
|
||||
# Should not raise exception
|
||||
with mock.patch('aprsd.packets.collector.LOG') as mock_log:
|
||||
pc.tx(packet)
|
||||
mock_log.error.assert_called()
|
||||
|
||||
def test_flush(self):
|
||||
"""Test flush() method."""
|
||||
pc = collector.PacketCollector()
|
||||
monitor1 = MockPacketMonitor('Monitor1')
|
||||
monitor2 = MockPacketMonitor('Monitor2')
|
||||
pc.register(monitor1)
|
||||
pc.register(monitor2)
|
||||
|
||||
pc.flush()
|
||||
|
||||
self.assertTrue(monitor1().flush_called)
|
||||
self.assertTrue(monitor2().flush_called)
|
||||
|
||||
def test_flush_with_exception(self):
|
||||
"""Test flush() handles exceptions gracefully."""
|
||||
pc = collector.PacketCollector()
|
||||
|
||||
class FailingMonitor:
|
||||
_instance = None
|
||||
|
||||
def __call__(self):
|
||||
if self._instance is None:
|
||||
self._instance = self
|
||||
return self._instance
|
||||
|
||||
def rx(self, packet):
|
||||
pass
|
||||
|
||||
def tx(self, packet):
|
||||
pass
|
||||
|
||||
def flush(self):
|
||||
raise Exception('Monitor error')
|
||||
|
||||
def load(self):
|
||||
pass
|
||||
|
||||
monitor = FailingMonitor()
|
||||
pc.register(monitor)
|
||||
|
||||
# Should not raise exception
|
||||
with mock.patch('aprsd.packets.collector.LOG') as mock_log:
|
||||
pc.flush()
|
||||
mock_log.error.assert_called()
|
||||
|
||||
def test_load(self):
|
||||
"""Test load() method."""
|
||||
pc = collector.PacketCollector()
|
||||
monitor1 = MockPacketMonitor('Monitor1')
|
||||
monitor2 = MockPacketMonitor('Monitor2')
|
||||
pc.register(monitor1)
|
||||
pc.register(monitor2)
|
||||
|
||||
pc.load()
|
||||
|
||||
self.assertTrue(monitor1().load_called)
|
||||
self.assertTrue(monitor2().load_called)
|
||||
|
||||
def test_load_with_exception(self):
|
||||
"""Test load() handles exceptions gracefully."""
|
||||
pc = collector.PacketCollector()
|
||||
|
||||
class FailingMonitor:
|
||||
_instance = None
|
||||
|
||||
def __call__(self):
|
||||
if self._instance is None:
|
||||
self._instance = self
|
||||
return self._instance
|
||||
|
||||
def rx(self, packet):
|
||||
pass
|
||||
|
||||
def tx(self, packet):
|
||||
pass
|
||||
|
||||
def flush(self):
|
||||
pass
|
||||
|
||||
def load(self):
|
||||
raise Exception('Monitor error')
|
||||
|
||||
monitor = FailingMonitor()
|
||||
pc.register(monitor)
|
||||
|
||||
# Should not raise exception
|
||||
with mock.patch('aprsd.packets.collector.LOG') as mock_log:
|
||||
pc.load()
|
||||
mock_log.error.assert_called()
|
||||
|
||||
def test_multiple_monitors(self):
|
||||
"""Test multiple monitors are called in order."""
|
||||
pc = collector.PacketCollector()
|
||||
call_order = []
|
||||
|
||||
class OrderedMonitor:
|
||||
_instance = None
|
||||
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
def __call__(self):
|
||||
if self._instance is None:
|
||||
self._instance = self
|
||||
return self._instance
|
||||
|
||||
def rx(self, packet):
|
||||
call_order.append(self.name)
|
||||
|
||||
def tx(self, packet):
|
||||
pass
|
||||
|
||||
def flush(self):
|
||||
pass
|
||||
|
||||
def load(self):
|
||||
pass
|
||||
|
||||
monitor1 = OrderedMonitor('Monitor1')
|
||||
monitor2 = OrderedMonitor('Monitor2')
|
||||
monitor3 = OrderedMonitor('Monitor3')
|
||||
|
||||
pc.register(monitor1)
|
||||
pc.register(monitor2)
|
||||
pc.register(monitor3)
|
||||
|
||||
packet = fake.fake_packet()
|
||||
pc.rx(packet)
|
||||
|
||||
self.assertEqual(call_order, ['Monitor1', 'Monitor2', 'Monitor3'])
|
||||
@@ -0,0 +1,245 @@
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
from aprsd.packets import filter
|
||||
from tests import fake
|
||||
|
||||
|
||||
class MockPacketFilter:
|
||||
"""Mock implementation of PacketFilterProtocol for testing."""
|
||||
|
||||
def __init__(self, name='MockFilter', should_pass=True):
|
||||
self.name = name
|
||||
self.should_pass = should_pass
|
||||
self.filter_called = False
|
||||
|
||||
def filter(self, packet):
|
||||
self.filter_called = True
|
||||
self.filtered_packet = packet
|
||||
return packet if self.should_pass else None
|
||||
|
||||
|
||||
class TestPacketFilter(unittest.TestCase):
|
||||
"""Unit tests for the PacketFilter class."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test fixtures."""
|
||||
# Reset singleton instance
|
||||
filter.PacketFilter._instance = None
|
||||
# Clear filters to start fresh
|
||||
pf = filter.PacketFilter()
|
||||
pf.filters = {}
|
||||
|
||||
def tearDown(self):
|
||||
"""Clean up after tests."""
|
||||
filter.PacketFilter._instance = None
|
||||
|
||||
def test_singleton_pattern(self):
|
||||
"""Test that PacketFilter is a singleton."""
|
||||
pf1 = filter.PacketFilter()
|
||||
pf2 = filter.PacketFilter()
|
||||
self.assertIs(pf1, pf2)
|
||||
|
||||
def test_init(self):
|
||||
"""Test initialization."""
|
||||
pf = filter.PacketFilter()
|
||||
# After setUp, filters should be empty
|
||||
self.assertEqual(pf.filters, {})
|
||||
|
||||
def test_register(self):
|
||||
"""Test register() method."""
|
||||
pf = filter.PacketFilter()
|
||||
|
||||
class FilterClass:
|
||||
def filter(self, packet):
|
||||
return packet
|
||||
|
||||
pf.register(FilterClass)
|
||||
self.assertIn(FilterClass, pf.filters)
|
||||
self.assertIsInstance(pf.filters[FilterClass], FilterClass)
|
||||
|
||||
def test_register_non_protocol(self):
|
||||
"""Test register() raises TypeError for non-protocol objects."""
|
||||
pf = filter.PacketFilter()
|
||||
non_filter = object()
|
||||
|
||||
with self.assertRaises(TypeError):
|
||||
pf.register(non_filter)
|
||||
|
||||
def test_register_duplicate(self):
|
||||
"""Test register() doesn't create duplicate instances."""
|
||||
pf = filter.PacketFilter()
|
||||
|
||||
class FilterClass:
|
||||
def filter(self, packet):
|
||||
return packet
|
||||
|
||||
pf.register(FilterClass)
|
||||
instance1 = pf.filters[FilterClass]
|
||||
|
||||
pf.register(FilterClass)
|
||||
instance2 = pf.filters[FilterClass]
|
||||
|
||||
self.assertIs(instance1, instance2)
|
||||
|
||||
def test_unregister(self):
|
||||
"""Test unregister() method."""
|
||||
pf = filter.PacketFilter()
|
||||
|
||||
class FilterClass:
|
||||
def filter(self, packet):
|
||||
return packet
|
||||
|
||||
pf.register(FilterClass)
|
||||
pf.unregister(FilterClass)
|
||||
self.assertNotIn(FilterClass, pf.filters)
|
||||
|
||||
def test_unregister_non_protocol(self):
|
||||
"""Test unregister() raises TypeError for non-protocol objects."""
|
||||
pf = filter.PacketFilter()
|
||||
non_filter = object()
|
||||
|
||||
with self.assertRaises(TypeError):
|
||||
pf.unregister(non_filter)
|
||||
|
||||
def test_filter_passes(self):
|
||||
"""Test filter() when all filters pass."""
|
||||
pf = filter.PacketFilter()
|
||||
|
||||
class Filter1:
|
||||
def filter(self, packet):
|
||||
return packet
|
||||
|
||||
class Filter2:
|
||||
def filter(self, packet):
|
||||
return packet
|
||||
|
||||
pf.register(Filter1)
|
||||
pf.register(Filter2)
|
||||
|
||||
packet = fake.fake_packet()
|
||||
result = pf.filter(packet)
|
||||
|
||||
self.assertEqual(result, packet)
|
||||
|
||||
def test_filter_drops(self):
|
||||
"""Test filter() when a filter drops the packet."""
|
||||
pf = filter.PacketFilter()
|
||||
|
||||
class Filter1:
|
||||
def filter(self, packet):
|
||||
return packet
|
||||
|
||||
class Filter2:
|
||||
def filter(self, packet):
|
||||
return None # Drops packet
|
||||
|
||||
pf.register(Filter1)
|
||||
pf.register(Filter2)
|
||||
|
||||
packet = fake.fake_packet()
|
||||
result = pf.filter(packet)
|
||||
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_filter_order(self):
|
||||
"""Test filters are called in registration order."""
|
||||
pf = filter.PacketFilter()
|
||||
call_order = []
|
||||
|
||||
class Filter1:
|
||||
def filter(self, packet):
|
||||
call_order.append('Filter1')
|
||||
return packet
|
||||
|
||||
class Filter2:
|
||||
def filter(self, packet):
|
||||
call_order.append('Filter2')
|
||||
return packet
|
||||
|
||||
class Filter3:
|
||||
def filter(self, packet):
|
||||
call_order.append('Filter3')
|
||||
return packet
|
||||
|
||||
pf.register(Filter1)
|
||||
pf.register(Filter2)
|
||||
pf.register(Filter3)
|
||||
|
||||
packet = fake.fake_packet()
|
||||
pf.filter(packet)
|
||||
|
||||
self.assertEqual(call_order, ['Filter1', 'Filter2', 'Filter3'])
|
||||
|
||||
def test_filter_stops_on_drop(self):
|
||||
"""Test filter() stops processing when packet is dropped."""
|
||||
pf = filter.PacketFilter()
|
||||
call_order = []
|
||||
|
||||
class Filter1:
|
||||
def filter(self, packet):
|
||||
call_order.append('Filter1')
|
||||
return packet
|
||||
|
||||
class Filter2:
|
||||
def filter(self, packet):
|
||||
call_order.append('Filter2')
|
||||
return None # Drops
|
||||
|
||||
class Filter3:
|
||||
def filter(self, packet):
|
||||
call_order.append('Filter3')
|
||||
return packet
|
||||
|
||||
pf.register(Filter1)
|
||||
pf.register(Filter2)
|
||||
pf.register(Filter3)
|
||||
|
||||
packet = fake.fake_packet()
|
||||
result = pf.filter(packet)
|
||||
|
||||
self.assertIsNone(result)
|
||||
# Filter3 should not be called
|
||||
self.assertEqual(call_order, ['Filter1', 'Filter2'])
|
||||
|
||||
def test_filter_with_exception(self):
|
||||
"""Test filter() handles exceptions gracefully."""
|
||||
pf = filter.PacketFilter()
|
||||
|
||||
class FailingFilter:
|
||||
def filter(self, packet):
|
||||
raise Exception('Filter error')
|
||||
|
||||
pf.register(FailingFilter)
|
||||
|
||||
packet = fake.fake_packet()
|
||||
# Should not raise exception
|
||||
with mock.patch('aprsd.packets.filter.LOG') as mock_log:
|
||||
pf.filter(packet)
|
||||
mock_log.error.assert_called()
|
||||
|
||||
def test_filter_empty(self):
|
||||
"""Test filter() with no registered filters."""
|
||||
pf = filter.PacketFilter()
|
||||
packet = fake.fake_packet()
|
||||
|
||||
result = pf.filter(packet)
|
||||
# When no filters, packet should pass through
|
||||
self.assertEqual(result, packet)
|
||||
|
||||
def test_filter_typo_in_log(self):
|
||||
"""Test that the typo in filter error logging doesn't break."""
|
||||
pf = filter.PacketFilter()
|
||||
|
||||
class FailingFilter:
|
||||
def filter(self, packet):
|
||||
raise Exception('Filter error')
|
||||
|
||||
pf.register(FailingFilter)
|
||||
|
||||
packet = fake.fake_packet()
|
||||
# Should handle the typo gracefully (__clas__ instead of __class__)
|
||||
with mock.patch('aprsd.packets.filter.LOG') as mock_log:
|
||||
pf.filter(packet)
|
||||
# Should log error even with typo
|
||||
self.assertTrue(mock_log.error.called)
|
||||
@@ -0,0 +1,193 @@
|
||||
import unittest
|
||||
from collections import OrderedDict
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from aprsd.packets import packet_list
|
||||
from tests import fake
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class TestPacketList(unittest.TestCase):
|
||||
"""Unit tests for the PacketList class."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test fixtures."""
|
||||
# Reset singleton instance and class variables
|
||||
packet_list.PacketList._instance = None
|
||||
packet_list.PacketList._total_rx = 0
|
||||
packet_list.PacketList._total_tx = 0
|
||||
# Mock config
|
||||
CONF.packet_list_maxlen = 100
|
||||
# Create fresh instance and reset data
|
||||
pl = packet_list.PacketList()
|
||||
pl.data = {'types': {}, 'packets': OrderedDict()}
|
||||
pl._total_rx = 0
|
||||
pl._total_tx = 0
|
||||
|
||||
def tearDown(self):
|
||||
"""Clean up after tests."""
|
||||
packet_list.PacketList._instance = None
|
||||
packet_list.PacketList._total_rx = 0
|
||||
packet_list.PacketList._total_tx = 0
|
||||
|
||||
def test_singleton_pattern(self):
|
||||
"""Test that PacketList is a singleton."""
|
||||
pl1 = packet_list.PacketList()
|
||||
pl2 = packet_list.PacketList()
|
||||
self.assertIs(pl1, pl2)
|
||||
|
||||
def test_init(self):
|
||||
"""Test initialization."""
|
||||
pl = packet_list.PacketList()
|
||||
self.assertEqual(pl.maxlen, 100)
|
||||
self.assertIn('types', pl.data)
|
||||
self.assertIn('packets', pl.data)
|
||||
|
||||
def test_rx(self):
|
||||
"""Test rx() method."""
|
||||
pl = packet_list.PacketList()
|
||||
packet = fake.fake_packet()
|
||||
|
||||
initial_rx = pl._total_rx
|
||||
pl.rx(packet)
|
||||
|
||||
self.assertEqual(pl._total_rx, initial_rx + 1)
|
||||
self.assertIn(packet.key, pl.data['packets'])
|
||||
self.assertIn(packet.__class__.__name__, pl.data['types'])
|
||||
|
||||
def test_tx(self):
|
||||
"""Test tx() method."""
|
||||
pl = packet_list.PacketList()
|
||||
packet = fake.fake_packet()
|
||||
|
||||
initial_tx = pl._total_tx
|
||||
pl.tx(packet)
|
||||
|
||||
self.assertEqual(pl._total_tx, initial_tx + 1)
|
||||
self.assertIn(packet.key, pl.data['packets'])
|
||||
self.assertIn(packet.__class__.__name__, pl.data['types'])
|
||||
|
||||
def test_add(self):
|
||||
"""Test add() method."""
|
||||
pl = packet_list.PacketList()
|
||||
packet = fake.fake_packet()
|
||||
|
||||
pl.add(packet)
|
||||
self.assertIn(packet.key, pl.data['packets'])
|
||||
|
||||
def test_find(self):
|
||||
"""Test find() method."""
|
||||
pl = packet_list.PacketList()
|
||||
packet = fake.fake_packet()
|
||||
pl.add(packet)
|
||||
|
||||
found = pl.find(packet)
|
||||
self.assertEqual(found, packet)
|
||||
|
||||
def test_len(self):
|
||||
"""Test __len__() method."""
|
||||
pl = packet_list.PacketList()
|
||||
self.assertEqual(len(pl), 0)
|
||||
|
||||
packet1 = fake.fake_packet(fromcall='TEST1')
|
||||
pl.add(packet1)
|
||||
self.assertEqual(len(pl), 1)
|
||||
|
||||
packet2 = fake.fake_packet(fromcall='TEST2', message='different')
|
||||
pl.add(packet2)
|
||||
self.assertEqual(len(pl), 2)
|
||||
|
||||
def test_total_rx(self):
|
||||
"""Test total_rx() method."""
|
||||
pl = packet_list.PacketList()
|
||||
pl.rx(fake.fake_packet())
|
||||
pl.rx(fake.fake_packet(message='test2'))
|
||||
|
||||
self.assertEqual(pl.total_rx(), 2)
|
||||
|
||||
def test_total_tx(self):
|
||||
"""Test total_tx() method."""
|
||||
pl = packet_list.PacketList()
|
||||
pl.tx(fake.fake_packet())
|
||||
pl.tx(fake.fake_packet(message='test2'))
|
||||
|
||||
self.assertEqual(pl.total_tx(), 2)
|
||||
|
||||
def test_maxlen_enforcement(self):
|
||||
"""Test that maxlen is enforced."""
|
||||
CONF.packet_list_maxlen = 3
|
||||
packet_list.PacketList._instance = None
|
||||
packet_list.PacketList._total_rx = 0
|
||||
packet_list.PacketList._total_tx = 0
|
||||
pl = packet_list.PacketList()
|
||||
pl.data = {'types': {}, 'packets': OrderedDict()}
|
||||
pl._total_rx = 0
|
||||
pl._total_tx = 0
|
||||
|
||||
# Add more than maxlen with different keys
|
||||
for i in range(5):
|
||||
packet = fake.fake_packet(fromcall=f'TEST{i}', message=f'test{i}')
|
||||
pl.add(packet)
|
||||
|
||||
# Should only have maxlen packets
|
||||
self.assertEqual(len(pl), 3)
|
||||
# Oldest should be removed
|
||||
self.assertNotIn(fake.fake_packet(message='test0').key, pl.data['packets'])
|
||||
|
||||
def test_duplicate_packet(self):
|
||||
"""Test that duplicate packets move to end."""
|
||||
pl = packet_list.PacketList()
|
||||
packet = fake.fake_packet(message='test')
|
||||
|
||||
pl.add(packet)
|
||||
# Add different packet
|
||||
pl.add(fake.fake_packet(message='other'))
|
||||
# Add original packet again
|
||||
pl.add(packet)
|
||||
|
||||
# Original packet should be at end
|
||||
keys = list(pl.data['packets'].keys())
|
||||
self.assertEqual(keys[-1], packet.key)
|
||||
|
||||
def test_stats(self):
|
||||
"""Test stats() method."""
|
||||
pl = packet_list.PacketList()
|
||||
pl.rx(fake.fake_packet())
|
||||
pl.tx(fake.fake_packet(message='test2'))
|
||||
|
||||
stats = pl.stats()
|
||||
self.assertIn('rx', stats)
|
||||
self.assertIn('tx', stats)
|
||||
self.assertIn('total_tracked', stats)
|
||||
self.assertIn('types', stats)
|
||||
self.assertEqual(stats['rx'], 1)
|
||||
self.assertEqual(stats['tx'], 1)
|
||||
|
||||
def test_stats_serializable(self):
|
||||
"""Test stats() with serializable=True."""
|
||||
pl = packet_list.PacketList()
|
||||
pl.rx(fake.fake_packet())
|
||||
|
||||
stats = pl.stats(serializable=True)
|
||||
# Note: packets in stats are not JSON serializable by default
|
||||
# This test just verifies the method accepts the parameter
|
||||
self.assertIsInstance(stats, dict)
|
||||
self.assertIn('rx', stats)
|
||||
|
||||
def test_type_stats(self):
|
||||
"""Test that type statistics are tracked."""
|
||||
pl = packet_list.PacketList()
|
||||
packet1 = fake.fake_packet()
|
||||
packet2 = fake.fake_packet(message='test2')
|
||||
|
||||
pl.rx(packet1)
|
||||
pl.rx(packet2)
|
||||
pl.tx(packet1)
|
||||
|
||||
stats = pl.stats()
|
||||
packet_type = packet1.__class__.__name__
|
||||
self.assertIn(packet_type, stats['types'])
|
||||
self.assertEqual(stats['types'][packet_type]['rx'], 2)
|
||||
self.assertEqual(stats['types'][packet_type]['tx'], 1)
|
||||
@@ -0,0 +1,113 @@
|
||||
import datetime
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
from aprsd.packets import seen_list
|
||||
from tests import fake
|
||||
|
||||
|
||||
class TestSeenList(unittest.TestCase):
|
||||
"""Unit tests for the SeenList class."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test fixtures."""
|
||||
# Reset singleton instance
|
||||
seen_list.SeenList._instance = None
|
||||
|
||||
def tearDown(self):
|
||||
"""Clean up after tests."""
|
||||
seen_list.SeenList._instance = None
|
||||
|
||||
def test_singleton_pattern(self):
|
||||
"""Test that SeenList is a singleton."""
|
||||
sl1 = seen_list.SeenList()
|
||||
sl2 = seen_list.SeenList()
|
||||
self.assertIs(sl1, sl2)
|
||||
|
||||
def test_init(self):
|
||||
"""Test initialization."""
|
||||
sl = seen_list.SeenList()
|
||||
self.assertEqual(sl.data, {})
|
||||
|
||||
def test_stats(self):
|
||||
"""Test stats() method."""
|
||||
sl = seen_list.SeenList()
|
||||
stats = sl.stats()
|
||||
self.assertIsInstance(stats, dict)
|
||||
|
||||
stats_serializable = sl.stats(serializable=True)
|
||||
self.assertIsInstance(stats_serializable, dict)
|
||||
|
||||
def test_rx(self):
|
||||
"""Test rx() method."""
|
||||
sl = seen_list.SeenList()
|
||||
packet = fake.fake_packet(fromcall='TEST1')
|
||||
|
||||
sl.rx(packet)
|
||||
|
||||
self.assertIn('TEST1', sl.data)
|
||||
self.assertIn('last', sl.data['TEST1'])
|
||||
self.assertIn('count', sl.data['TEST1'])
|
||||
self.assertEqual(sl.data['TEST1']['count'], 1)
|
||||
self.assertIsInstance(sl.data['TEST1']['last'], datetime.datetime)
|
||||
|
||||
def test_rx_multiple(self):
|
||||
"""Test rx() with multiple packets from same callsign."""
|
||||
sl = seen_list.SeenList()
|
||||
packet1 = fake.fake_packet(fromcall='TEST2')
|
||||
packet2 = fake.fake_packet(fromcall='TEST2', message='different')
|
||||
|
||||
sl.rx(packet1)
|
||||
sl.rx(packet2)
|
||||
|
||||
self.assertEqual(sl.data['TEST2']['count'], 2)
|
||||
|
||||
def test_rx_different_callsigns(self):
|
||||
"""Test rx() with different callsigns."""
|
||||
sl = seen_list.SeenList()
|
||||
packet1 = fake.fake_packet(fromcall='TEST3')
|
||||
packet2 = fake.fake_packet(fromcall='TEST4')
|
||||
|
||||
sl.rx(packet1)
|
||||
sl.rx(packet2)
|
||||
|
||||
self.assertIn('TEST3', sl.data)
|
||||
self.assertIn('TEST4', sl.data)
|
||||
self.assertEqual(sl.data['TEST3']['count'], 1)
|
||||
self.assertEqual(sl.data['TEST4']['count'], 1)
|
||||
|
||||
def test_rx_no_from_call(self):
|
||||
"""Test rx() with packet missing from_call."""
|
||||
sl = seen_list.SeenList()
|
||||
|
||||
class PacketWithoutFrom:
|
||||
from_call = None
|
||||
|
||||
packet = PacketWithoutFrom()
|
||||
|
||||
with mock.patch('aprsd.packets.seen_list.LOG') as mock_log:
|
||||
sl.rx(packet)
|
||||
mock_log.warning.assert_called()
|
||||
self.assertEqual(len(sl.data), 0)
|
||||
|
||||
def test_tx(self):
|
||||
"""Test tx() method (should be no-op)."""
|
||||
sl = seen_list.SeenList()
|
||||
packet = fake.fake_packet()
|
||||
|
||||
# Should not raise exception
|
||||
sl.tx(packet)
|
||||
# Should not add to data
|
||||
self.assertEqual(len(sl.data), 0)
|
||||
|
||||
def test_stats_with_data(self):
|
||||
"""Test stats() with data."""
|
||||
sl = seen_list.SeenList()
|
||||
sl.rx(fake.fake_packet(fromcall='TEST5'))
|
||||
sl.rx(fake.fake_packet(fromcall='TEST6'))
|
||||
|
||||
stats = sl.stats()
|
||||
self.assertIn('TEST5', stats)
|
||||
self.assertIn('TEST6', stats)
|
||||
self.assertIn('last', stats['TEST5'])
|
||||
self.assertIn('count', stats['TEST5'])
|
||||
@@ -0,0 +1,220 @@
|
||||
import unittest
|
||||
|
||||
from aprsd.packets import tracker
|
||||
from tests import fake
|
||||
|
||||
|
||||
class TestPacketTrack(unittest.TestCase):
|
||||
"""Unit tests for the PacketTrack class."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test fixtures."""
|
||||
# Reset singleton instance
|
||||
tracker.PacketTrack._instance = None
|
||||
tracker.PacketTrack.data = {}
|
||||
tracker.PacketTrack.total_tracked = 0
|
||||
|
||||
def tearDown(self):
|
||||
"""Clean up after tests."""
|
||||
tracker.PacketTrack._instance = None
|
||||
tracker.PacketTrack.data = {}
|
||||
tracker.PacketTrack.total_tracked = 0
|
||||
|
||||
def test_singleton_pattern(self):
|
||||
"""Test that PacketTrack is a singleton."""
|
||||
pt1 = tracker.PacketTrack()
|
||||
pt2 = tracker.PacketTrack()
|
||||
self.assertIs(pt1, pt2)
|
||||
|
||||
def test_init(self):
|
||||
"""Test initialization."""
|
||||
pt = tracker.PacketTrack()
|
||||
self.assertIsInstance(pt.data, dict)
|
||||
self.assertIsNotNone(pt._start_time)
|
||||
|
||||
def test_getitem(self):
|
||||
"""Test __getitem__() method."""
|
||||
pt = tracker.PacketTrack()
|
||||
packet = fake.fake_packet(msg_number='123')
|
||||
pt.tx(packet)
|
||||
|
||||
result = pt['123']
|
||||
self.assertEqual(result, packet)
|
||||
|
||||
def test_iter(self):
|
||||
"""Test __iter__() method."""
|
||||
pt = tracker.PacketTrack()
|
||||
packet1 = fake.fake_packet(msg_number='123')
|
||||
packet2 = fake.fake_packet(msg_number='456')
|
||||
pt.tx(packet1)
|
||||
pt.tx(packet2)
|
||||
|
||||
keys = list(iter(pt))
|
||||
self.assertIn('123', keys)
|
||||
self.assertIn('456', keys)
|
||||
|
||||
def test_keys(self):
|
||||
"""Test keys() method."""
|
||||
pt = tracker.PacketTrack()
|
||||
packet1 = fake.fake_packet(msg_number='123')
|
||||
packet2 = fake.fake_packet(msg_number='456')
|
||||
pt.tx(packet1)
|
||||
pt.tx(packet2)
|
||||
|
||||
keys = list(pt.keys())
|
||||
self.assertIn('123', keys)
|
||||
self.assertIn('456', keys)
|
||||
|
||||
def test_items(self):
|
||||
"""Test items() method."""
|
||||
pt = tracker.PacketTrack()
|
||||
packet = fake.fake_packet(msg_number='123')
|
||||
pt.tx(packet)
|
||||
|
||||
items = list(pt.items())
|
||||
self.assertEqual(len(items), 1)
|
||||
self.assertEqual(items[0][0], '123')
|
||||
self.assertEqual(items[0][1], packet)
|
||||
|
||||
def test_values(self):
|
||||
"""Test values() method."""
|
||||
pt = tracker.PacketTrack()
|
||||
packet1 = fake.fake_packet(msg_number='123')
|
||||
packet2 = fake.fake_packet(msg_number='456')
|
||||
pt.tx(packet1)
|
||||
pt.tx(packet2)
|
||||
|
||||
values = list(pt.values())
|
||||
self.assertEqual(len(values), 2)
|
||||
self.assertIn(packet1, values)
|
||||
self.assertIn(packet2, values)
|
||||
|
||||
def test_tx(self):
|
||||
"""Test tx() method."""
|
||||
pt = tracker.PacketTrack()
|
||||
packet = fake.fake_packet(msg_number='123')
|
||||
initial_total = pt.total_tracked
|
||||
|
||||
pt.tx(packet)
|
||||
|
||||
self.assertIn('123', pt.data)
|
||||
self.assertEqual(pt.data['123'], packet)
|
||||
self.assertEqual(pt.total_tracked, initial_total + 1)
|
||||
self.assertEqual(packet.send_count, 0)
|
||||
|
||||
def test_rx_ack_packet(self):
|
||||
"""Test rx() with AckPacket."""
|
||||
pt = tracker.PacketTrack()
|
||||
packet = fake.fake_packet(msg_number='123')
|
||||
pt.tx(packet)
|
||||
|
||||
ack = fake.fake_ack_packet()
|
||||
ack.msgNo = '123'
|
||||
pt.rx(ack)
|
||||
|
||||
self.assertNotIn('123', pt.data)
|
||||
|
||||
def test_rx_reject_packet(self):
|
||||
"""Test rx() with RejectPacket."""
|
||||
from aprsd.packets import core
|
||||
|
||||
pt = tracker.PacketTrack()
|
||||
packet = fake.fake_packet(msg_number='123')
|
||||
pt.tx(packet)
|
||||
|
||||
# Create a proper RejectPacket
|
||||
reject_pkt = core.RejectPacket(from_call='TEST', to_call='TEST', msgNo='123')
|
||||
pt.rx(reject_pkt)
|
||||
|
||||
self.assertNotIn('123', pt.data)
|
||||
|
||||
def test_rx_piggyback_ack(self):
|
||||
"""Test rx() with piggyback ACK."""
|
||||
pt = tracker.PacketTrack()
|
||||
packet = fake.fake_packet(msg_number='123')
|
||||
pt.tx(packet)
|
||||
|
||||
piggyback = fake.fake_packet()
|
||||
piggyback.ackMsgNo = '123'
|
||||
pt.rx(piggyback)
|
||||
|
||||
self.assertNotIn('123', pt.data)
|
||||
|
||||
def test_rx_no_match(self):
|
||||
"""Test rx() with packet that doesn't match tracked packet."""
|
||||
pt = tracker.PacketTrack()
|
||||
packet = fake.fake_packet(msg_number='123')
|
||||
pt.tx(packet)
|
||||
|
||||
ack = fake.fake_ack_packet()
|
||||
ack.msgNo = '999' # Different msgNo
|
||||
pt.rx(ack)
|
||||
|
||||
# Should still have original packet
|
||||
self.assertIn('123', pt.data)
|
||||
|
||||
def test_remove(self):
|
||||
"""Test remove() method."""
|
||||
pt = tracker.PacketTrack()
|
||||
packet = fake.fake_packet(msg_number='123')
|
||||
pt.tx(packet)
|
||||
|
||||
pt.remove('123')
|
||||
self.assertNotIn('123', pt.data)
|
||||
|
||||
def test_remove_nonexistent(self):
|
||||
"""Test remove() with nonexistent key."""
|
||||
pt = tracker.PacketTrack()
|
||||
# Should not raise exception
|
||||
pt.remove('nonexistent')
|
||||
|
||||
def test_stats(self):
|
||||
"""Test stats() method."""
|
||||
pt = tracker.PacketTrack()
|
||||
packet = fake.fake_packet(msg_number='123')
|
||||
packet.retry_count = 3
|
||||
packet.last_send_time = 1000
|
||||
pt.tx(packet)
|
||||
# Note: tx() resets send_count to 0
|
||||
|
||||
stats = pt.stats()
|
||||
self.assertIn('total_tracked', stats)
|
||||
self.assertIn('packets', stats)
|
||||
self.assertIn('123', stats['packets'])
|
||||
self.assertEqual(stats['packets']['123']['send_count'], 0)
|
||||
self.assertEqual(stats['packets']['123']['retry_count'], 3)
|
||||
|
||||
def test_stats_serializable(self):
|
||||
"""Test stats() with serializable=True."""
|
||||
pt = tracker.PacketTrack()
|
||||
packet = fake.fake_packet(msg_number='123')
|
||||
pt.tx(packet)
|
||||
|
||||
stats = pt.stats(serializable=True)
|
||||
# Should be JSON serializable
|
||||
import json
|
||||
|
||||
json.dumps(stats) # Should not raise exception
|
||||
|
||||
def test_get(self):
|
||||
"""Test get() method from ObjectStoreMixin."""
|
||||
pt = tracker.PacketTrack()
|
||||
packet = fake.fake_packet(msg_number='123')
|
||||
pt.tx(packet)
|
||||
|
||||
result = pt.get('123')
|
||||
self.assertEqual(result, packet)
|
||||
|
||||
result = pt.get('nonexistent')
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_len(self):
|
||||
"""Test __len__() method."""
|
||||
pt = tracker.PacketTrack()
|
||||
self.assertEqual(len(pt), 0)
|
||||
|
||||
pt.tx(fake.fake_packet(msg_number='123'))
|
||||
self.assertEqual(len(pt), 1)
|
||||
|
||||
pt.tx(fake.fake_packet(msg_number='456'))
|
||||
self.assertEqual(len(pt), 2)
|
||||
@@ -0,0 +1,197 @@
|
||||
import datetime
|
||||
import unittest
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from aprsd.packets import watch_list
|
||||
from tests import fake
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class TestWatchList(unittest.TestCase):
|
||||
"""Unit tests for the WatchList class."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test fixtures."""
|
||||
# Reset singleton instance
|
||||
watch_list.WatchList._instance = None
|
||||
# Mock config
|
||||
CONF.watch_list.enabled = True
|
||||
CONF.watch_list.callsigns = ['TEST*']
|
||||
CONF.watch_list.alert_time_seconds = 300
|
||||
|
||||
def tearDown(self):
|
||||
"""Clean up after tests."""
|
||||
watch_list.WatchList._instance = None
|
||||
|
||||
def test_singleton_pattern(self):
|
||||
"""Test that WatchList is a singleton."""
|
||||
wl1 = watch_list.WatchList()
|
||||
wl2 = watch_list.WatchList()
|
||||
self.assertIs(wl1, wl2)
|
||||
|
||||
def test_init(self):
|
||||
"""Test initialization."""
|
||||
wl = watch_list.WatchList()
|
||||
self.assertIsInstance(wl.data, dict)
|
||||
|
||||
def test_update_from_conf(self):
|
||||
"""Test _update_from_conf() method."""
|
||||
CONF.watch_list.enabled = True
|
||||
CONF.watch_list.callsigns = ['TEST1*', 'TEST2*']
|
||||
watch_list.WatchList._instance = None
|
||||
|
||||
wl = watch_list.WatchList()
|
||||
# Should have entries for TEST1 and TEST2 (without *)
|
||||
self.assertIn('TEST1', wl.data)
|
||||
self.assertIn('TEST2', wl.data)
|
||||
|
||||
def test_stats(self):
|
||||
"""Test stats() method."""
|
||||
wl = watch_list.WatchList()
|
||||
stats = wl.stats()
|
||||
self.assertIsInstance(stats, dict)
|
||||
|
||||
stats_serializable = wl.stats(serializable=True)
|
||||
self.assertIsInstance(stats_serializable, dict)
|
||||
|
||||
def test_is_enabled(self):
|
||||
"""Test is_enabled() method."""
|
||||
wl = watch_list.WatchList()
|
||||
CONF.watch_list.enabled = True
|
||||
self.assertTrue(wl.is_enabled())
|
||||
|
||||
CONF.watch_list.enabled = False
|
||||
self.assertFalse(wl.is_enabled())
|
||||
|
||||
def test_callsign_in_watchlist(self):
|
||||
"""Test callsign_in_watchlist() method."""
|
||||
wl = watch_list.WatchList()
|
||||
CONF.watch_list.callsigns = ['TEST1*']
|
||||
watch_list.WatchList._instance = None
|
||||
wl = watch_list.WatchList()
|
||||
|
||||
self.assertTrue(wl.callsign_in_watchlist('TEST1'))
|
||||
self.assertFalse(wl.callsign_in_watchlist('NOTINLIST'))
|
||||
|
||||
def test_rx(self):
|
||||
"""Test rx() method."""
|
||||
wl = watch_list.WatchList()
|
||||
CONF.watch_list.callsigns = ['TEST1*']
|
||||
watch_list.WatchList._instance = None
|
||||
wl = watch_list.WatchList()
|
||||
|
||||
packet = fake.fake_packet(fromcall='TEST1')
|
||||
wl.rx(packet)
|
||||
|
||||
# WatchList should track packets
|
||||
self.assertIn('TEST1', wl.data)
|
||||
self.assertIsNotNone(wl.data['TEST1']['last'])
|
||||
self.assertEqual(wl.data['TEST1']['packet'], packet)
|
||||
|
||||
def test_rx_not_in_watchlist(self):
|
||||
"""Test rx() with callsign not in watchlist."""
|
||||
wl = watch_list.WatchList()
|
||||
CONF.watch_list.callsigns = ['TEST1*']
|
||||
watch_list.WatchList._instance = None
|
||||
wl = watch_list.WatchList()
|
||||
|
||||
packet = fake.fake_packet(fromcall='NOTINLIST')
|
||||
wl.rx(packet)
|
||||
|
||||
# Should not add to data
|
||||
self.assertNotIn('NOTINLIST', wl.data)
|
||||
|
||||
def test_rx_multiple(self):
|
||||
"""Test rx() with multiple packets."""
|
||||
wl = watch_list.WatchList()
|
||||
CONF.watch_list.callsigns = ['TEST2*']
|
||||
watch_list.WatchList._instance = None
|
||||
wl = watch_list.WatchList()
|
||||
|
||||
packet1 = fake.fake_packet(fromcall='TEST2')
|
||||
packet2 = fake.fake_packet(fromcall='TEST2', message='different')
|
||||
|
||||
wl.rx(packet1)
|
||||
wl.rx(packet2)
|
||||
|
||||
# Should track both, last packet should be packet2
|
||||
self.assertIn('TEST2', wl.data)
|
||||
self.assertEqual(wl.data['TEST2']['packet'], packet2)
|
||||
|
||||
def test_tx(self):
|
||||
"""Test tx() method (should be no-op)."""
|
||||
wl = watch_list.WatchList()
|
||||
packet = fake.fake_packet()
|
||||
|
||||
# Should not raise exception
|
||||
wl.tx(packet)
|
||||
|
||||
def test_last_seen(self):
|
||||
"""Test last_seen() method."""
|
||||
wl = watch_list.WatchList()
|
||||
CONF.watch_list.callsigns = ['TEST3*']
|
||||
watch_list.WatchList._instance = None
|
||||
wl = watch_list.WatchList()
|
||||
|
||||
packet = fake.fake_packet(fromcall='TEST3')
|
||||
wl.rx(packet)
|
||||
|
||||
last_seen = wl.last_seen('TEST3')
|
||||
self.assertIsNotNone(last_seen)
|
||||
self.assertIsInstance(last_seen, datetime.datetime)
|
||||
|
||||
self.assertIsNone(wl.last_seen('NOTINLIST'))
|
||||
|
||||
def test_age(self):
|
||||
"""Test age() method."""
|
||||
wl = watch_list.WatchList()
|
||||
CONF.watch_list.callsigns = ['TEST4*']
|
||||
watch_list.WatchList._instance = None
|
||||
wl = watch_list.WatchList()
|
||||
|
||||
packet = fake.fake_packet(fromcall='TEST4')
|
||||
wl.rx(packet)
|
||||
|
||||
age = wl.age('TEST4')
|
||||
self.assertIsNotNone(age)
|
||||
self.assertIsInstance(age, str)
|
||||
|
||||
self.assertIsNone(wl.age('NOTINLIST'))
|
||||
|
||||
def test_max_delta(self):
|
||||
"""Test max_delta() method."""
|
||||
wl = watch_list.WatchList()
|
||||
|
||||
delta = wl.max_delta(seconds=300)
|
||||
self.assertIsInstance(delta, datetime.timedelta)
|
||||
self.assertEqual(delta.total_seconds(), 300)
|
||||
|
||||
# Test with config default
|
||||
delta = wl.max_delta()
|
||||
self.assertIsInstance(delta, datetime.timedelta)
|
||||
|
||||
def test_is_old(self):
|
||||
"""Test is_old() method."""
|
||||
wl = watch_list.WatchList()
|
||||
CONF.watch_list.callsigns = ['TEST5*']
|
||||
CONF.watch_list.alert_time_seconds = 60
|
||||
watch_list.WatchList._instance = None
|
||||
wl = watch_list.WatchList()
|
||||
|
||||
# Not in watchlist
|
||||
self.assertFalse(wl.is_old('NOTINLIST'))
|
||||
|
||||
# In watchlist but no last seen
|
||||
self.assertFalse(wl.is_old('TEST5'))
|
||||
|
||||
# Add packet
|
||||
packet = fake.fake_packet(fromcall='TEST5')
|
||||
wl.rx(packet)
|
||||
|
||||
# Should not be old immediately
|
||||
self.assertFalse(wl.is_old('TEST5'))
|
||||
|
||||
# Test with custom seconds
|
||||
self.assertFalse(wl.is_old('TEST5', seconds=3600))
|
||||
Reference in New Issue
Block a user