mirror of
https://github.com/smittix/intercept.git
synced 2026-04-25 07:10:00 -07:00
- 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>
341 lines
10 KiB
Python
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']
|