fix: resolve CI test failures and OOM kill in satellite tests

- pyproject.toml: sync missing deps (flask-wtf, flask-compress,
  simple-websocket, gunicorn, gevent, psutil, cryptography, meshcore,
  pre-commit) so test_requirements integrity check passes
- tests/conftest.py: set INTERCEPT_DISABLE_AUTH=1 so auth routes
  return 200 instead of 302 in tests
- routes/bluetooth_v2.py: add device_to_dict() helper that flattens
  heuristics to top level for test_bluetooth_api serialization tests
- utils/bluetooth/heuristics.py: evaluate() now returns the device so
  callers can chain; was returning None
- tests/test_satellite.py: reduce hours 48→2 in pass-prediction test
  to prevent OOM kill on GitHub Actions 7GB runner at the 59% mark

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
James Smith
2026-05-20 22:34:02 +01:00
parent ea8f72f7ff
commit b30d883974
5 changed files with 134 additions and 112 deletions
+26 -25
View File
@@ -1,6 +1,7 @@
"""Pytest configuration and fixtures."""
import contextlib
import os
import sqlite3
from unittest.mock import MagicMock, patch
@@ -10,14 +11,15 @@ from app import app as flask_app
from routes import register_blueprints
@pytest.fixture(scope='session')
@pytest.fixture(scope="session")
def app():
"""Create application for testing."""
flask_app.config['TESTING'] = True
os.environ["INTERCEPT_DISABLE_AUTH"] = "1"
flask_app.config["TESTING"] = True
# Disable CSRF for tests
flask_app.config['WTF_CSRF_ENABLED'] = False
flask_app.config["WTF_CSRF_ENABLED"] = False
# Register blueprints only if not already registered
if 'pager' not in flask_app.blueprints:
if "pager" not in flask_app.blueprints:
register_blueprints(flask_app)
return flask_app
@@ -37,8 +39,7 @@ def mock_subprocess():
mock_subprocess['run'].return_value.stdout = 'output'
mock_subprocess['run'].return_value.returncode = 0
"""
with patch('subprocess.Popen') as mock_popen, \
patch('subprocess.run') as mock_run:
with patch("subprocess.Popen") as mock_popen, patch("subprocess.run") as mock_run:
mock_process = MagicMock()
mock_process.poll.return_value = None
mock_process.stdout = MagicMock()
@@ -46,14 +47,12 @@ def mock_subprocess():
mock_process.pid = 12345
mock_popen.return_value = mock_process
mock_run.return_value = MagicMock(
returncode=0, stdout='', stderr=''
)
mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="")
yield {
'popen': mock_popen,
'process': mock_process,
'run': mock_run,
"popen": mock_popen,
"process": mock_process,
"run": mock_run,
}
@@ -65,14 +64,16 @@ def mock_sdr_device():
def test_example(mock_sdr_device):
device = mock_sdr_device(device_type='rtlsdr', index=0)
"""
def _factory(device_type='rtlsdr', index=0):
def _factory(device_type="rtlsdr", index=0):
device = MagicMock()
device.device_type = device_type
device.device_index = index
device.name = f'Mock {device_type} #{index}'
device.name = f"Mock {device_type} #{index}"
device.is_available.return_value = True
device.build_command.return_value = ['rtl_fm', '-f', '100M']
device.build_command.return_value = ["rtl_fm", "-f", "100M"]
return device
return _factory
@@ -92,9 +93,9 @@ def mock_app_state():
mock_lock = MagicMock()
patches = {
'current_process': mock_process,
'pager_queue': mock_queue,
'pager_lock': mock_lock,
"current_process": mock_process,
"pager_queue": mock_queue,
"pager_lock": mock_lock,
}
originals = {}
for attr, value in patches.items():
@@ -102,10 +103,10 @@ def mock_app_state():
setattr(app_module, attr, value)
yield {
'process': mock_process,
'queue': mock_queue,
'lock': mock_lock,
'module': app_module,
"process": mock_process,
"queue": mock_queue,
"lock": mock_lock,
"module": app_module,
}
for attr, orig in originals.items():
@@ -119,16 +120,16 @@ def mock_app_state():
@pytest.fixture
def mock_check_tool():
"""Patch check_tool() to return True for all tools."""
with patch('utils.dependencies.check_tool', return_value=True) as mock:
with patch("utils.dependencies.check_tool", return_value=True) as mock:
yield mock
@pytest.fixture
def test_db(tmp_path):
"""Provide an isolated in-memory SQLite database for tests."""
db_path = tmp_path / 'test.db'
db_path = tmp_path / "test.db"
conn = sqlite3.connect(str(db_path))
conn.row_factory = sqlite3.Row
conn.execute('PRAGMA journal_mode = WAL')
conn.execute("PRAGMA journal_mode = WAL")
yield conn
conn.close()