Files
intercept/tests/test_correlation.py
Smittix b5547d3fa9 Add Listening Post, improve setup and documentation
- Add Listening Post mode with frequency scanner and audio monitoring
- Add dependency warning for aircraft dashboard listen feature
- Auto-restart audio when switching frequencies
- Fix toolbar overflow on aircraft dashboard custom frequency
- Update setup script with full macOS/Debian support
- Clean up README and documentation for clarity
- Add sox and dump1090 to Dockerfile
- Add comprehensive tool reference to HARDWARE.md
- Add correlation, settings, and database utilities
- Add new test files for routes, validation, correlation, database

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 17:34:53 +00:00

341 lines
10 KiB
Python

"""Tests for device correlation engine."""
import pytest
from datetime import datetime, timedelta
from unittest.mock import patch, MagicMock
class TestDeviceCorrelator:
"""Tests for DeviceCorrelator class."""
def test_correlate_same_oui(self):
"""Test correlation detects same OUI."""
from utils.correlation import DeviceCorrelator
correlator = DeviceCorrelator(time_window_seconds=60)
wifi_devices = {
'AA:BB:CC:11:22:33': {
'first_seen': datetime.now(),
'last_seen': datetime.now(),
'essid': 'TestNetwork',
'power': -65
}
}
bt_devices = {
'AA:BB:CC:44:55:66': {
'first_seen': datetime.now(),
'last_seen': datetime.now(),
'name': 'TestPhone',
'rssi': -60
}
}
correlations = correlator.correlate(wifi_devices, bt_devices)
assert len(correlations) >= 1
assert correlations[0]['wifi_mac'] == 'AA:BB:CC:11:22:33'
assert correlations[0]['bt_mac'] == 'AA:BB:CC:44:55:66'
assert correlations[0]['confidence'] > 0
def test_correlate_timing(self):
"""Test correlation considers timing."""
from utils.correlation import DeviceCorrelator
correlator = DeviceCorrelator(time_window_seconds=30)
now = datetime.now()
# Devices appearing at the same time
wifi_devices = {
'11:22:33:44:55:66': {
'first_seen': now,
'last_seen': now,
'essid': 'Network1'
}
}
bt_devices = {
'77:88:99:AA:BB:CC': {
'first_seen': now,
'last_seen': now,
'name': 'Device1'
}
}
correlations = correlator.correlate(wifi_devices, bt_devices)
# Should have some confidence from timing correlation
if correlations:
assert correlations[0]['confidence'] > 0
def test_correlate_no_overlap(self):
"""Test no correlation when devices don't overlap."""
from utils.correlation import DeviceCorrelator
correlator = DeviceCorrelator(
time_window_seconds=30,
min_confidence=0.6
)
now = datetime.now()
old = now - timedelta(hours=1)
wifi_devices = {
'11:22:33:44:55:66': {
'first_seen': old,
'last_seen': old,
'essid': 'OldNetwork'
}
}
bt_devices = {
'77:88:99:AA:BB:CC': {
'first_seen': now,
'last_seen': now,
'name': 'NewDevice'
}
}
correlations = correlator.correlate(wifi_devices, bt_devices)
# With high min_confidence and no OUI match, should be empty
assert len(correlations) == 0
def test_correlate_manufacturer_match(self):
"""Test correlation boosts confidence for same manufacturer."""
from utils.correlation import DeviceCorrelator
correlator = DeviceCorrelator(time_window_seconds=60)
now = datetime.now()
wifi_devices = {
'11:22:33:44:55:66': {
'first_seen': now,
'last_seen': now,
'manufacturer': 'Apple',
'essid': 'Network'
}
}
bt_devices = {
'77:88:99:AA:BB:CC': {
'first_seen': now,
'last_seen': now,
'manufacturer': 'Apple',
'name': 'iPhone'
}
}
correlations = correlator.correlate(wifi_devices, bt_devices)
# Should have correlation with bonus for manufacturer match
assert len(correlations) >= 1
def test_correlate_empty_inputs(self):
"""Test correlation handles empty inputs."""
from utils.correlation import DeviceCorrelator
correlator = DeviceCorrelator()
# Empty WiFi
assert correlator.correlate({}, {'AA:BB:CC:DD:EE:FF': {}}) == []
# Empty Bluetooth
assert correlator.correlate({'AA:BB:CC:DD:EE:FF': {}}, {}) == []
# Both empty
assert correlator.correlate({}, {}) == []
def test_correlate_sorting(self):
"""Test correlations are sorted by confidence."""
from utils.correlation import DeviceCorrelator
correlator = DeviceCorrelator(
time_window_seconds=60,
min_confidence=0.0
)
now = datetime.now()
wifi_devices = {
'AA:BB:CC:11:11:11': {
'first_seen': now,
'last_seen': now,
'manufacturer': 'Apple'
},
'11:22:33:44:55:66': {
'first_seen': now,
'last_seen': now
}
}
bt_devices = {
'AA:BB:CC:22:22:22': {
'first_seen': now,
'last_seen': now,
'manufacturer': 'Apple'
},
'77:88:99:AA:BB:CC': {
'first_seen': now,
'last_seen': now
}
}
correlations = correlator.correlate(wifi_devices, bt_devices)
if len(correlations) >= 2:
# Should be sorted by confidence (highest first)
assert correlations[0]['confidence'] >= correlations[1]['confidence']
class TestGetCorrelations:
"""Tests for get_correlations function."""
@patch('utils.correlation.correlator')
@patch('utils.correlation.db_get_correlations')
def test_get_correlations_live(self, mock_db, mock_correlator):
"""Test get_correlations with live data."""
from utils.correlation import get_correlations
mock_correlator.correlate.return_value = [
{
'wifi_mac': 'AA:AA:AA:AA:AA:AA',
'bt_mac': 'BB:BB:BB:BB:BB:BB',
'confidence': 0.8
}
]
mock_db.return_value = []
wifi = {'AA:AA:AA:AA:AA:AA': {}}
bt = {'BB:BB:BB:BB:BB:BB': {}}
results = get_correlations(
wifi_devices=wifi,
bt_devices=bt,
include_historical=False
)
assert len(results) == 1
mock_correlator.correlate.assert_called_once()
@patch('utils.correlation.correlator')
@patch('utils.correlation.db_get_correlations')
def test_get_correlations_historical(self, mock_db, mock_correlator):
"""Test get_correlations includes historical data."""
from utils.correlation import get_correlations
mock_correlator.correlate.return_value = []
mock_db.return_value = [
{
'wifi_mac': 'CC:CC:CC:CC:CC:CC',
'bt_mac': 'DD:DD:DD:DD:DD:DD',
'confidence': 0.7,
'first_seen': '2024-01-01',
'last_seen': '2024-01-02'
}
]
results = get_correlations(
wifi_devices={},
bt_devices={},
include_historical=True
)
assert len(results) == 1
assert results[0]['wifi_mac'] == 'CC:CC:CC:CC:CC:CC'
@patch('utils.correlation.correlator')
@patch('utils.correlation.db_get_correlations')
def test_get_correlations_deduplication(self, mock_db, mock_correlator):
"""Test get_correlations deduplicates live and historical."""
from utils.correlation import get_correlations
# Same correlation from both sources
mock_correlator.correlate.return_value = [
{
'wifi_mac': 'AA:AA:AA:AA:AA:AA',
'bt_mac': 'BB:BB:BB:BB:BB:BB',
'confidence': 0.8
}
]
mock_db.return_value = [
{
'wifi_mac': 'AA:AA:AA:AA:AA:AA',
'bt_mac': 'BB:BB:BB:BB:BB:BB',
'confidence': 0.7,
'first_seen': '2024-01-01',
'last_seen': '2024-01-02'
}
]
wifi = {'AA:AA:AA:AA:AA:AA': {}}
bt = {'BB:BB:BB:BB:BB:BB': {}}
results = get_correlations(
wifi_devices=wifi,
bt_devices=bt,
include_historical=True
)
# Should deduplicate - only one entry for the same device pair
matching = [r for r in results
if r['wifi_mac'] == 'AA:AA:AA:AA:AA:AA']
assert len(matching) == 1
class TestCorrelationReason:
"""Tests for correlation reason generation."""
def test_reason_same_oui(self):
"""Test reason includes OUI match."""
from utils.correlation import DeviceCorrelator
correlator = DeviceCorrelator()
now = datetime.now()
wifi_devices = {
'AA:BB:CC:11:22:33': {
'first_seen': now,
'last_seen': now
}
}
bt_devices = {
'AA:BB:CC:44:55:66': {
'first_seen': now,
'last_seen': now
}
}
correlations = correlator.correlate(wifi_devices, bt_devices)
if correlations:
assert 'OUI' in correlations[0]['reason'] or 'same' in correlations[0]['reason'].lower()
def test_reason_timing(self):
"""Test reason includes timing information."""
from utils.correlation import DeviceCorrelator
correlator = DeviceCorrelator(time_window_seconds=60)
now = datetime.now()
wifi_devices = {
'11:22:33:44:55:66': {
'first_seen': now,
'last_seen': now
}
}
bt_devices = {
'77:88:99:AA:BB:CC': {
'first_seen': now + timedelta(seconds=5),
'last_seen': now + timedelta(seconds=5)
}
}
correlations = correlator.correlate(wifi_devices, bt_devices)
# If correlation found, should mention timing
if correlations and correlations[0]['confidence'] > 0.3:
assert 'appeared' in correlations[0]['reason'] or 'timing' in correlations[0]['reason']