1
0
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:
2025-12-09 17:20:23 -05:00
parent 2b2dbb114b
commit d0dfaa42e6
35 changed files with 5426 additions and 48 deletions
View File
+120
View File
@@ -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()
+87
View File
@@ -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)
+387
View File
@@ -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'])
+245
View File
@@ -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)
+193
View File
@@ -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)
+113
View File
@@ -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'])
+220
View File
@@ -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)
+197
View File
@@ -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))