mirror of
https://github.com/smittix/intercept.git
synced 2026-07-04 15:53:39 -07:00
fix: stabilize test suite and repair frontend/backend wiring
- meshcore pin >=2.3.0 (EventType.STATS_CORE floor); setup.sh derives optional packages from requirements.txt; Python 3.10 warning - agent-mode wifi clients proxy route + bare-array response handling - remove dead AIS/ACARS/VDL2 SPA wiring and orphaned partials/CSS - agent TLE download to data/tle/ (was littering repo root as gp.php) - gate deferred background init off under pytest (mock-pollution race) - complete Popen mocks (context manager protocol, communicate tuples) - real pipe fds in weather-sat decoder tests (fd 10/11 collision caused 10s SQLite stalls); satellite tests no longer rewrite data/satellites.py - register 'live' pytest marker, excluded by default - update stale test assertions to current APIs Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+142
-132
@@ -25,10 +25,12 @@ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
# Fixtures
|
||||
# =============================================================================
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mode_manager():
|
||||
"""Create a fresh ModeManager instance for testing."""
|
||||
from intercept_agent import ModeManager
|
||||
|
||||
manager = ModeManager()
|
||||
yield manager
|
||||
# Cleanup: stop all modes
|
||||
@@ -40,15 +42,21 @@ def mode_manager():
|
||||
@pytest.fixture
|
||||
def mock_subprocess():
|
||||
"""Mock subprocess.Popen for controlled testing."""
|
||||
with patch('subprocess.Popen') as mock_popen:
|
||||
with patch("subprocess.Popen") as mock_popen:
|
||||
mock_proc = MagicMock()
|
||||
mock_proc.poll.return_value = None # Process is running
|
||||
mock_proc.stdout = MagicMock()
|
||||
mock_proc.stderr = MagicMock()
|
||||
mock_proc.stderr.read.return_value = b''
|
||||
mock_proc.stderr.read.return_value = b""
|
||||
mock_proc.stdin = MagicMock()
|
||||
mock_proc.pid = 12345
|
||||
mock_proc.wait.return_value = 0
|
||||
# subprocess.run() is built on Popen: it enters the context manager
|
||||
# and unpacks communicate(); detect_capabilities() calls it with
|
||||
# text=True, so return strings
|
||||
mock_proc.communicate.return_value = ("", "")
|
||||
mock_proc.returncode = 0
|
||||
mock_proc.__enter__.return_value = mock_proc
|
||||
mock_popen.return_value = mock_proc
|
||||
yield mock_popen, mock_proc
|
||||
|
||||
@@ -57,19 +65,19 @@ def mock_subprocess():
|
||||
def mock_tools():
|
||||
"""Mock tool availability checks."""
|
||||
tools = {
|
||||
'rtl_433': '/usr/bin/rtl_433',
|
||||
'rtl_fm': '/usr/bin/rtl_fm',
|
||||
'dump1090': '/usr/bin/dump1090',
|
||||
'multimon-ng': '/usr/bin/multimon-ng',
|
||||
'airodump-ng': '/usr/sbin/airodump-ng',
|
||||
'acarsdec': '/usr/bin/acarsdec',
|
||||
'AIS-catcher': '/usr/bin/AIS-catcher',
|
||||
'direwolf': '/usr/bin/direwolf',
|
||||
'rtlamr': '/usr/bin/rtlamr',
|
||||
'rtl_tcp': '/usr/bin/rtl_tcp',
|
||||
'bluetoothctl': '/usr/bin/bluetoothctl',
|
||||
"rtl_433": "/usr/bin/rtl_433",
|
||||
"rtl_fm": "/usr/bin/rtl_fm",
|
||||
"dump1090": "/usr/bin/dump1090",
|
||||
"multimon-ng": "/usr/bin/multimon-ng",
|
||||
"airodump-ng": "/usr/sbin/airodump-ng",
|
||||
"acarsdec": "/usr/bin/acarsdec",
|
||||
"AIS-catcher": "/usr/bin/AIS-catcher",
|
||||
"direwolf": "/usr/bin/direwolf",
|
||||
"rtlamr": "/usr/bin/rtlamr",
|
||||
"rtl_tcp": "/usr/bin/rtl_tcp",
|
||||
"bluetoothctl": "/usr/bin/bluetoothctl",
|
||||
}
|
||||
with patch('shutil.which', side_effect=lambda x: tools.get(x)):
|
||||
with patch("shutil.which", side_effect=lambda x: tools.get(x)):
|
||||
yield tools
|
||||
|
||||
|
||||
@@ -77,8 +85,8 @@ def mock_tools():
|
||||
# SDR Mode List
|
||||
# =============================================================================
|
||||
|
||||
SDR_MODES = ['sensor', 'adsb', 'pager', 'ais', 'acars', 'aprs', 'rtlamr', 'dsc', 'listening_post']
|
||||
NON_SDR_MODES = ['wifi', 'bluetooth', 'tscm', 'satellite']
|
||||
SDR_MODES = ["sensor", "adsb", "pager", "ais", "acars", "aprs", "rtlamr", "dsc", "listening_post"]
|
||||
NON_SDR_MODES = ["wifi", "bluetooth", "tscm", "satellite"]
|
||||
ALL_MODES = SDR_MODES + NON_SDR_MODES
|
||||
|
||||
|
||||
@@ -86,6 +94,7 @@ ALL_MODES = SDR_MODES + NON_SDR_MODES
|
||||
# Mode Lifecycle Tests
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class TestModeLifecycle:
|
||||
"""Test start/stop lifecycle for all modes."""
|
||||
|
||||
@@ -94,99 +103,88 @@ class TestModeLifecycle:
|
||||
mock_popen, mock_proc = mock_subprocess
|
||||
|
||||
# Start
|
||||
result = mode_manager.start_mode('sensor', {'frequency': '433.92', 'device': '0'})
|
||||
assert result['status'] == 'started'
|
||||
assert 'sensor' in mode_manager.running_modes
|
||||
result = mode_manager.start_mode("sensor", {"frequency": "433.92", "device": "0"})
|
||||
assert result["status"] == "started"
|
||||
assert "sensor" in mode_manager.running_modes
|
||||
|
||||
# Stop
|
||||
result = mode_manager.stop_mode('sensor')
|
||||
assert result['status'] == 'stopped'
|
||||
assert 'sensor' not in mode_manager.running_modes
|
||||
result = mode_manager.stop_mode("sensor")
|
||||
assert result["status"] == "stopped"
|
||||
assert "sensor" not in mode_manager.running_modes
|
||||
|
||||
def test_adsb_mode_lifecycle(self, mode_manager, mock_subprocess, mock_tools):
|
||||
"""ADS-B mode should start and stop cleanly."""
|
||||
mock_popen, mock_proc = mock_subprocess
|
||||
|
||||
# Mock socket for SBS connection check
|
||||
with patch('socket.socket') as mock_socket:
|
||||
with patch("socket.socket") as mock_socket:
|
||||
mock_sock = MagicMock()
|
||||
mock_sock.connect_ex.return_value = 1 # Port not in use
|
||||
mock_socket.return_value = mock_sock
|
||||
|
||||
result = mode_manager.start_mode('adsb', {'device': '0', 'gain': '40'})
|
||||
result = mode_manager.start_mode("adsb", {"device": "0", "gain": "40"})
|
||||
# May fail due to SBS port check, but shouldn't crash
|
||||
assert result['status'] in ['started', 'error']
|
||||
assert result["status"] in ["started", "error"]
|
||||
|
||||
def test_pager_mode_lifecycle(self, mode_manager, mock_subprocess, mock_tools):
|
||||
"""Pager mode should start and stop cleanly."""
|
||||
mock_popen, mock_proc = mock_subprocess
|
||||
|
||||
result = mode_manager.start_mode('pager', {
|
||||
'frequency': '929.6125',
|
||||
'protocols': ['POCSAG512', 'POCSAG1200']
|
||||
})
|
||||
assert result['status'] == 'started'
|
||||
assert 'pager' in mode_manager.running_modes
|
||||
result = mode_manager.start_mode("pager", {"frequency": "929.6125", "protocols": ["POCSAG512", "POCSAG1200"]})
|
||||
assert result["status"] == "started"
|
||||
assert "pager" in mode_manager.running_modes
|
||||
|
||||
result = mode_manager.stop_mode('pager')
|
||||
assert result['status'] == 'stopped'
|
||||
result = mode_manager.stop_mode("pager")
|
||||
assert result["status"] == "stopped"
|
||||
|
||||
def test_wifi_mode_lifecycle(self, mode_manager, mock_subprocess, mock_tools):
|
||||
"""WiFi mode should start and stop cleanly."""
|
||||
mock_popen, mock_proc = mock_subprocess
|
||||
|
||||
# Mock glob for CSV file detection
|
||||
with patch('glob.glob', return_value=[]), patch('tempfile.mkdtemp', return_value='/tmp/test'):
|
||||
result = mode_manager.start_mode('wifi', {
|
||||
'interface': 'wlan0',
|
||||
'scan_type': 'quick'
|
||||
})
|
||||
with patch("glob.glob", return_value=[]), patch("tempfile.mkdtemp", return_value="/tmp/test"):
|
||||
result = mode_manager.start_mode("wifi", {"interface": "wlan0", "scan_type": "quick"})
|
||||
# Quick scan returns data directly
|
||||
assert result['status'] in ['started', 'error', 'success']
|
||||
assert result["status"] in ["started", "error", "success"]
|
||||
|
||||
def test_bluetooth_mode_lifecycle(self, mode_manager, mock_subprocess, mock_tools):
|
||||
"""Bluetooth mode should start and stop cleanly."""
|
||||
mock_popen, mock_proc = mock_subprocess
|
||||
|
||||
result = mode_manager.start_mode('bluetooth', {'adapter': 'hci0'})
|
||||
assert result['status'] == 'started'
|
||||
assert 'bluetooth' in mode_manager.running_modes
|
||||
result = mode_manager.start_mode("bluetooth", {"adapter": "hci0"})
|
||||
assert result["status"] == "started"
|
||||
assert "bluetooth" in mode_manager.running_modes
|
||||
|
||||
# Give thread time to start
|
||||
time.sleep(0.1)
|
||||
|
||||
result = mode_manager.stop_mode('bluetooth')
|
||||
assert result['status'] == 'stopped'
|
||||
result = mode_manager.stop_mode("bluetooth")
|
||||
assert result["status"] == "stopped"
|
||||
|
||||
def test_satellite_mode_lifecycle(self, mode_manager):
|
||||
"""Satellite mode should work without SDR."""
|
||||
# Satellite mode is computational only
|
||||
result = mode_manager.start_mode('satellite', {
|
||||
'lat': 33.5,
|
||||
'lon': -82.1,
|
||||
'min_elevation': 10
|
||||
})
|
||||
assert result['status'] in ['started', 'error'] # May fail if skyfield not installed
|
||||
# Patch the predictor loop — the real one downloads TLEs from
|
||||
# CelesTrak and keeps computing passes after the test finishes
|
||||
with patch.object(type(mode_manager), "_satellite_predictor", MagicMock()):
|
||||
result = mode_manager.start_mode("satellite", {"lat": 33.5, "lon": -82.1, "min_elevation": 10})
|
||||
assert result["status"] in ["started", "error"] # May fail if skyfield not installed
|
||||
|
||||
def test_tscm_mode_lifecycle(self, mode_manager, mock_subprocess, mock_tools):
|
||||
"""TSCM mode should start and stop cleanly."""
|
||||
mock_popen, mock_proc = mock_subprocess
|
||||
|
||||
result = mode_manager.start_mode('tscm', {
|
||||
'wifi': True,
|
||||
'bluetooth': True,
|
||||
'rf': False
|
||||
})
|
||||
assert result['status'] == 'started'
|
||||
result = mode_manager.start_mode("tscm", {"wifi": True, "bluetooth": True, "rf": False})
|
||||
assert result["status"] == "started"
|
||||
|
||||
result = mode_manager.stop_mode('tscm')
|
||||
assert result['status'] == 'stopped'
|
||||
result = mode_manager.stop_mode("tscm")
|
||||
assert result["status"] == "stopped"
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# SDR Conflict Detection Tests
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class TestSDRConflictDetection:
|
||||
"""Test SDR device conflict detection."""
|
||||
|
||||
@@ -195,25 +193,25 @@ class TestSDRConflictDetection:
|
||||
mock_popen, mock_proc = mock_subprocess
|
||||
|
||||
# Start sensor on device 0
|
||||
result1 = mode_manager.start_mode('sensor', {'device': '0'})
|
||||
assert result1['status'] == 'started'
|
||||
result1 = mode_manager.start_mode("sensor", {"device": "0"})
|
||||
assert result1["status"] == "started"
|
||||
|
||||
# Try to start pager on device 0 - should fail
|
||||
result2 = mode_manager.start_mode('pager', {'device': '0'})
|
||||
assert result2['status'] == 'error'
|
||||
assert 'in use' in result2['message'].lower()
|
||||
result2 = mode_manager.start_mode("pager", {"device": "0"})
|
||||
assert result2["status"] == "error"
|
||||
assert "in use" in result2["message"].lower()
|
||||
|
||||
def test_different_device_no_conflict(self, mode_manager, mock_subprocess, mock_tools):
|
||||
"""Starting SDR modes on different devices should work."""
|
||||
mock_popen, mock_proc = mock_subprocess
|
||||
|
||||
# Start sensor on device 0
|
||||
result1 = mode_manager.start_mode('sensor', {'device': '0'})
|
||||
assert result1['status'] == 'started'
|
||||
result1 = mode_manager.start_mode("sensor", {"device": "0"})
|
||||
assert result1["status"] == "started"
|
||||
|
||||
# Start pager on device 1 - should work
|
||||
result2 = mode_manager.start_mode('pager', {'device': '1'})
|
||||
assert result2['status'] == 'started'
|
||||
result2 = mode_manager.start_mode("pager", {"device": "1"})
|
||||
assert result2["status"] == "started"
|
||||
|
||||
assert len(mode_manager.running_modes) == 2
|
||||
|
||||
@@ -222,12 +220,12 @@ class TestSDRConflictDetection:
|
||||
mock_popen, mock_proc = mock_subprocess
|
||||
|
||||
# Start sensor (SDR)
|
||||
result1 = mode_manager.start_mode('sensor', {'device': '0'})
|
||||
assert result1['status'] == 'started'
|
||||
result1 = mode_manager.start_mode("sensor", {"device": "0"})
|
||||
assert result1["status"] == "started"
|
||||
|
||||
# Start bluetooth (non-SDR) - should work
|
||||
result2 = mode_manager.start_mode('bluetooth', {'adapter': 'hci0'})
|
||||
assert result2['status'] == 'started'
|
||||
result2 = mode_manager.start_mode("bluetooth", {"adapter": "hci0"})
|
||||
assert result2["status"] == "started"
|
||||
|
||||
assert len(mode_manager.running_modes) == 2
|
||||
|
||||
@@ -239,10 +237,10 @@ class TestSDRConflictDetection:
|
||||
assert mode_manager.get_sdr_in_use(0) is None
|
||||
|
||||
# Start sensor
|
||||
mode_manager.start_mode('sensor', {'device': '0'})
|
||||
mode_manager.start_mode("sensor", {"device": "0"})
|
||||
|
||||
# Device 0 now in use by sensor
|
||||
assert mode_manager.get_sdr_in_use(0) == 'sensor'
|
||||
assert mode_manager.get_sdr_in_use(0) == "sensor"
|
||||
assert mode_manager.get_sdr_in_use(1) is None
|
||||
|
||||
|
||||
@@ -250,67 +248,73 @@ class TestSDRConflictDetection:
|
||||
# Process Verification Tests
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class TestProcessVerification:
|
||||
"""Test process startup verification."""
|
||||
|
||||
def test_immediate_process_exit_detected(self, mode_manager, mock_tools):
|
||||
"""Process that exits immediately should return error."""
|
||||
with patch('subprocess.Popen') as mock_popen:
|
||||
with patch("subprocess.Popen") as mock_popen:
|
||||
mock_proc = MagicMock()
|
||||
mock_proc.poll.return_value = 1 # Process exited
|
||||
mock_proc.stderr.read.return_value = b'device busy'
|
||||
mock_proc.stderr.read.return_value = b"device busy"
|
||||
mock_proc.communicate.return_value = ("", "")
|
||||
mock_proc.__enter__.return_value = mock_proc
|
||||
mock_popen.return_value = mock_proc
|
||||
|
||||
result = mode_manager.start_mode('sensor', {'device': '0'})
|
||||
assert result['status'] == 'error'
|
||||
assert 'sensor' not in mode_manager.running_modes
|
||||
result = mode_manager.start_mode("sensor", {"device": "0"})
|
||||
assert result["status"] == "error"
|
||||
assert "sensor" not in mode_manager.running_modes
|
||||
|
||||
def test_running_process_accepted(self, mode_manager, mock_subprocess, mock_tools):
|
||||
"""Process that stays running should be accepted."""
|
||||
mock_popen, mock_proc = mock_subprocess
|
||||
mock_proc.poll.return_value = None # Still running
|
||||
|
||||
result = mode_manager.start_mode('sensor', {'device': '0'})
|
||||
assert result['status'] == 'started'
|
||||
assert 'sensor' in mode_manager.running_modes
|
||||
result = mode_manager.start_mode("sensor", {"device": "0"})
|
||||
assert result["status"] == "started"
|
||||
assert "sensor" in mode_manager.running_modes
|
||||
|
||||
def test_error_message_from_stderr(self, mode_manager, mock_tools):
|
||||
"""Error message should include stderr output."""
|
||||
with patch('subprocess.Popen') as mock_popen:
|
||||
with patch("subprocess.Popen") as mock_popen:
|
||||
mock_proc = MagicMock()
|
||||
mock_proc.poll.return_value = 1
|
||||
mock_proc.stderr.read.return_value = b'usb_claim_interface error -6'
|
||||
mock_proc.stderr.read.return_value = b"usb_claim_interface error -6"
|
||||
mock_proc.communicate.return_value = ("", "")
|
||||
mock_proc.__enter__.return_value = mock_proc
|
||||
mock_popen.return_value = mock_proc
|
||||
|
||||
result = mode_manager.start_mode('sensor', {'device': '0'})
|
||||
assert result['status'] == 'error'
|
||||
assert 'usb_claim_interface' in result['message'] or 'failed' in result['message'].lower()
|
||||
result = mode_manager.start_mode("sensor", {"device": "0"})
|
||||
assert result["status"] == "error"
|
||||
assert "usb_claim_interface" in result["message"] or "failed" in result["message"].lower()
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Data Snapshot Tests
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class TestDataSnapshots:
|
||||
"""Test data snapshot operations."""
|
||||
|
||||
def test_get_mode_data_empty(self, mode_manager):
|
||||
"""get_mode_data for non-running mode should return empty."""
|
||||
result = mode_manager.get_mode_data('sensor')
|
||||
assert result['mode'] == 'sensor'
|
||||
result = mode_manager.get_mode_data("sensor")
|
||||
assert result["mode"] == "sensor"
|
||||
# Mode not running - should have empty data or 'running' field
|
||||
assert result.get('running') is False or result.get('data') == [] or 'status' in result
|
||||
assert result.get("running") is False or result.get("data") == [] or "status" in result
|
||||
|
||||
def test_get_mode_data_running(self, mode_manager, mock_subprocess, mock_tools):
|
||||
"""get_mode_data for running mode should return status."""
|
||||
mock_popen, mock_proc = mock_subprocess
|
||||
|
||||
mode_manager.start_mode('sensor', {'device': '0'})
|
||||
result = mode_manager.get_mode_data('sensor')
|
||||
mode_manager.start_mode("sensor", {"device": "0"})
|
||||
result = mode_manager.get_mode_data("sensor")
|
||||
|
||||
assert result['mode'] == 'sensor'
|
||||
assert result["mode"] == "sensor"
|
||||
# Mode is running - should indicate running status
|
||||
assert result.get('running') is True or 'data' in result or 'status' in result
|
||||
assert result.get("running") is True or "data" in result or "status" in result
|
||||
|
||||
def test_data_queue_limit(self, mode_manager):
|
||||
"""Data queues should respect max size limits."""
|
||||
@@ -321,7 +325,7 @@ class TestDataSnapshots:
|
||||
for i in range(150):
|
||||
if test_queue.full():
|
||||
test_queue.get_nowait() # Remove old item
|
||||
test_queue.put_nowait({'index': i})
|
||||
test_queue.put_nowait({"index": i})
|
||||
|
||||
assert test_queue.qsize() <= 100
|
||||
|
||||
@@ -330,68 +334,73 @@ class TestDataSnapshots:
|
||||
# Mode Status Tests
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class TestModeStatus:
|
||||
"""Test mode status reporting."""
|
||||
|
||||
def test_status_includes_all_modes(self, mode_manager):
|
||||
"""Status should include all running modes."""
|
||||
status = mode_manager.get_status()
|
||||
assert 'running_modes' in status
|
||||
assert 'running_modes_detail' in status
|
||||
assert isinstance(status['running_modes'], list)
|
||||
assert "running_modes" in status
|
||||
assert "running_modes_detail" in status
|
||||
assert isinstance(status["running_modes"], list)
|
||||
|
||||
def test_running_modes_detail_includes_device(self, mode_manager, mock_subprocess, mock_tools):
|
||||
"""Running modes detail should include device info."""
|
||||
mock_popen, mock_proc = mock_subprocess
|
||||
|
||||
mode_manager.start_mode('sensor', {'device': '0'})
|
||||
mode_manager.start_mode("sensor", {"device": "0"})
|
||||
status = mode_manager.get_status()
|
||||
|
||||
assert 'sensor' in status['running_modes_detail']
|
||||
detail = status['running_modes_detail']['sensor']
|
||||
assert 'device' in detail or 'params' in detail
|
||||
assert "sensor" in status["running_modes_detail"]
|
||||
detail = status["running_modes_detail"]["sensor"]
|
||||
assert "device" in detail or "params" in detail
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Error Handling Tests
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class TestErrorHandling:
|
||||
"""Test error handling scenarios."""
|
||||
|
||||
def test_missing_tool_returns_error(self, mode_manager):
|
||||
"""Mode should fail gracefully if required tool is missing."""
|
||||
with patch('shutil.which', return_value=None):
|
||||
result = mode_manager.start_mode('sensor', {'device': '0'})
|
||||
assert result['status'] == 'error'
|
||||
# get_tool_path checks Homebrew paths via os.path.isfile before
|
||||
# shutil.which, so patch it too or installed tools are still found
|
||||
with patch("utils.dependencies.get_tool_path", return_value=None), patch("shutil.which", return_value=None):
|
||||
result = mode_manager.start_mode("sensor", {"device": "0"})
|
||||
assert result["status"] == "error"
|
||||
# Error message may vary - check for common patterns
|
||||
msg = result['message'].lower()
|
||||
assert 'not found' in msg or 'not available' in msg or 'missing' in msg
|
||||
msg = result["message"].lower()
|
||||
assert "not found" in msg or "not available" in msg or "missing" in msg
|
||||
|
||||
def test_invalid_mode_returns_error(self, mode_manager):
|
||||
"""Invalid mode name should return error."""
|
||||
result = mode_manager.start_mode('invalid_mode', {})
|
||||
assert result['status'] == 'error'
|
||||
result = mode_manager.start_mode("invalid_mode", {})
|
||||
assert result["status"] == "error"
|
||||
|
||||
def test_double_start_returns_already_running(self, mode_manager, mock_subprocess, mock_tools):
|
||||
"""Starting already-running mode should return appropriate status."""
|
||||
mock_popen, mock_proc = mock_subprocess
|
||||
|
||||
mode_manager.start_mode('sensor', {'device': '0'})
|
||||
result = mode_manager.start_mode('sensor', {'device': '0'})
|
||||
mode_manager.start_mode("sensor", {"device": "0"})
|
||||
result = mode_manager.start_mode("sensor", {"device": "0"})
|
||||
|
||||
assert result['status'] in ['already_running', 'error']
|
||||
assert result["status"] in ["already_running", "error"]
|
||||
|
||||
def test_stop_non_running_mode(self, mode_manager):
|
||||
"""Stopping non-running mode should handle gracefully."""
|
||||
result = mode_manager.stop_mode('sensor')
|
||||
assert result['status'] in ['stopped', 'not_running']
|
||||
result = mode_manager.stop_mode("sensor")
|
||||
assert result["status"] in ["stopped", "not_running"]
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Cleanup Tests
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class TestCleanup:
|
||||
"""Test mode cleanup on stop."""
|
||||
|
||||
@@ -399,8 +408,8 @@ class TestCleanup:
|
||||
"""Processes should be terminated when mode is stopped."""
|
||||
mock_popen, mock_proc = mock_subprocess
|
||||
|
||||
mode_manager.start_mode('sensor', {'device': '0'})
|
||||
mode_manager.stop_mode('sensor')
|
||||
mode_manager.start_mode("sensor", {"device": "0"})
|
||||
mode_manager.stop_mode("sensor")
|
||||
|
||||
# Verify terminate was called
|
||||
mock_proc.terminate.assert_called()
|
||||
@@ -409,20 +418,20 @@ class TestCleanup:
|
||||
"""Output threads should be stopped when mode is stopped."""
|
||||
mock_popen, mock_proc = mock_subprocess
|
||||
|
||||
mode_manager.start_mode('bluetooth', {'adapter': 'hci0'})
|
||||
mode_manager.start_mode("bluetooth", {"adapter": "hci0"})
|
||||
time.sleep(0.1) # Let thread start
|
||||
|
||||
mode_manager.stop_mode('bluetooth')
|
||||
mode_manager.stop_mode("bluetooth")
|
||||
|
||||
# Thread should no longer be in output_threads or should be stopped
|
||||
assert 'bluetooth' not in mode_manager.output_threads or \
|
||||
not mode_manager.output_threads['bluetooth'].is_alive()
|
||||
assert "bluetooth" not in mode_manager.output_threads or not mode_manager.output_threads["bluetooth"].is_alive()
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Multi-Mode Tests
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class TestMultiMode:
|
||||
"""Test multiple modes running simultaneously."""
|
||||
|
||||
@@ -430,19 +439,19 @@ class TestMultiMode:
|
||||
"""Multiple non-SDR modes should run simultaneously."""
|
||||
mock_popen, mock_proc = mock_subprocess
|
||||
|
||||
result1 = mode_manager.start_mode('bluetooth', {'adapter': 'hci0'})
|
||||
result2 = mode_manager.start_mode('tscm', {'wifi': True, 'bluetooth': False})
|
||||
result1 = mode_manager.start_mode("bluetooth", {"adapter": "hci0"})
|
||||
result2 = mode_manager.start_mode("tscm", {"wifi": True, "bluetooth": False})
|
||||
|
||||
assert result1['status'] == 'started'
|
||||
assert result2['status'] == 'started'
|
||||
assert result1["status"] == "started"
|
||||
assert result2["status"] == "started"
|
||||
assert len(mode_manager.running_modes) == 2
|
||||
|
||||
def test_stop_all_modes(self, mode_manager, mock_subprocess, mock_tools):
|
||||
"""All modes should stop cleanly."""
|
||||
mock_popen, mock_proc = mock_subprocess
|
||||
|
||||
mode_manager.start_mode('sensor', {'device': '0'})
|
||||
mode_manager.start_mode('bluetooth', {'adapter': 'hci0'})
|
||||
mode_manager.start_mode("sensor", {"device": "0"})
|
||||
mode_manager.start_mode("bluetooth", {"adapter": "hci0"})
|
||||
|
||||
# Stop all
|
||||
for mode in list(mode_manager.running_modes.keys()):
|
||||
@@ -455,26 +464,27 @@ class TestMultiMode:
|
||||
# GPS Integration Tests
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class TestGPSIntegration:
|
||||
"""Test GPS coordinate integration."""
|
||||
|
||||
def test_status_includes_gps_flag(self, mode_manager):
|
||||
"""Status should indicate GPS availability."""
|
||||
status = mode_manager.get_status()
|
||||
assert 'gps' in status
|
||||
assert "gps" in status
|
||||
|
||||
def test_mode_start_includes_gps_flag(self, mode_manager, mock_subprocess, mock_tools):
|
||||
"""Mode start response should include GPS status."""
|
||||
mock_popen, mock_proc = mock_subprocess
|
||||
|
||||
result = mode_manager.start_mode('sensor', {'device': '0'})
|
||||
if result['status'] == 'started':
|
||||
assert 'gps_enabled' in result
|
||||
result = mode_manager.start_mode("sensor", {"device": "0"})
|
||||
if result["status"] == "started":
|
||||
assert "gps_enabled" in result
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Run Tests
|
||||
# =============================================================================
|
||||
|
||||
if __name__ == '__main__':
|
||||
pytest.main([__file__, '-v'])
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-v"])
|
||||
|
||||
Reference in New Issue
Block a user