Files
intercept/tests/test_correlation.py
T
James Smith 96172ca593 style: apply ruff-format to entire codebase
First-time run of ruff-format via pre-commit hook normalises quote
style, trailing commas, and whitespace across 188 Python files.
No logic changes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-05 14:48:11 +01:00

247 lines
8.9 KiB
Python

"""Tests for device correlation engine."""
from datetime import datetime, timedelta
from unittest.mock import patch
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"]