mirror of
https://github.com/smittix/intercept.git
synced 2026-05-29 20:29:27 -07:00
Add distributed agent architecture for multi-node signal intelligence
Features: - Standalone agent server (intercept_agent.py) for remote sensor nodes - Controller API blueprint for agent management and data aggregation - Push mechanism for agents to send data to controller - Pull mechanism for controller to proxy requests to agents - Multi-agent SSE stream for combined data view - Agent management page at /controller/manage - Agent selector dropdown in main UI - GPS integration for location tagging - API key authentication for secure agent communication - Integration with Intercept's dependency checking system New files: - intercept_agent.py: Remote agent HTTP server - intercept_agent.cfg: Agent configuration template - routes/controller.py: Controller API endpoints - utils/agent_client.py: HTTP client for agents - utils/trilateration.py: Multi-agent position calculation - static/js/core/agents.js: Frontend agent management - templates/agents.html: Agent management page - docs/DISTRIBUTED_AGENTS.md: System documentation Modified: - app.py: Register controller blueprint - utils/database.py: Add agents and push_payloads tables - templates/index.html: Add agent selector section
This commit is contained in:
648
tests/test_agent.py
Normal file
648
tests/test_agent.py
Normal file
@@ -0,0 +1,648 @@
|
||||
"""
|
||||
Tests for Intercept Agent components.
|
||||
|
||||
Tests cover:
|
||||
- AgentConfig parsing
|
||||
- AgentClient HTTP operations
|
||||
- Database agent CRUD operations
|
||||
- GPS integration
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import pytest
|
||||
import tempfile
|
||||
from unittest.mock import Mock, patch, MagicMock
|
||||
|
||||
import sys
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from utils.agent_client import (
|
||||
AgentClient, AgentHTTPError, AgentConnectionError, create_client_from_agent
|
||||
)
|
||||
from utils.database import (
|
||||
init_db, get_db_path, create_agent, get_agent, get_agent_by_name,
|
||||
list_agents, update_agent, delete_agent, store_push_payload,
|
||||
get_recent_payloads, cleanup_old_payloads
|
||||
)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# AgentConfig Tests
|
||||
# =============================================================================
|
||||
|
||||
class TestAgentConfig:
|
||||
"""Tests for AgentConfig class."""
|
||||
|
||||
def test_default_values(self):
|
||||
"""AgentConfig should have sensible defaults."""
|
||||
from intercept_agent import AgentConfig
|
||||
config = AgentConfig()
|
||||
|
||||
assert config.port == 8020
|
||||
assert config.allow_cors is False
|
||||
assert config.push_enabled is False
|
||||
assert config.push_interval == 5
|
||||
assert config.controller_url == ''
|
||||
assert 'adsb' in config.modes_enabled
|
||||
assert 'wifi' in config.modes_enabled
|
||||
assert config.modes_enabled['adsb'] is True
|
||||
|
||||
def test_load_from_file_valid(self):
|
||||
"""AgentConfig should load from valid INI file."""
|
||||
from intercept_agent import AgentConfig
|
||||
|
||||
config_content = """
|
||||
[agent]
|
||||
name = test-sensor
|
||||
port = 8025
|
||||
allowed_ips = 192.168.1.0/24, 10.0.0.1
|
||||
allow_cors = true
|
||||
|
||||
[controller]
|
||||
url = http://192.168.1.100:5050
|
||||
api_key = secret123
|
||||
push_enabled = true
|
||||
push_interval = 10
|
||||
|
||||
[modes]
|
||||
pager = false
|
||||
adsb = true
|
||||
wifi = true
|
||||
bluetooth = false
|
||||
"""
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.cfg', delete=False) as f:
|
||||
f.write(config_content)
|
||||
config_path = f.name
|
||||
|
||||
try:
|
||||
config = AgentConfig()
|
||||
result = config.load_from_file(config_path)
|
||||
|
||||
assert result is True
|
||||
assert config.name == 'test-sensor'
|
||||
assert config.port == 8025
|
||||
assert '192.168.1.0/24' in config.allowed_ips
|
||||
assert config.allow_cors is True
|
||||
assert config.controller_url == 'http://192.168.1.100:5050'
|
||||
assert config.controller_api_key == 'secret123'
|
||||
assert config.push_enabled is True
|
||||
assert config.push_interval == 10
|
||||
assert config.modes_enabled['pager'] is False
|
||||
assert config.modes_enabled['adsb'] is True
|
||||
assert config.modes_enabled['bluetooth'] is False
|
||||
finally:
|
||||
os.unlink(config_path)
|
||||
|
||||
def test_load_from_file_missing(self):
|
||||
"""AgentConfig should handle missing file gracefully."""
|
||||
from intercept_agent import AgentConfig
|
||||
config = AgentConfig()
|
||||
result = config.load_from_file('/nonexistent/path.cfg')
|
||||
assert result is False
|
||||
|
||||
def test_to_dict(self):
|
||||
"""AgentConfig should convert to dictionary."""
|
||||
from intercept_agent import AgentConfig
|
||||
config = AgentConfig()
|
||||
config.name = 'test'
|
||||
config.port = 9000
|
||||
|
||||
d = config.to_dict()
|
||||
|
||||
assert d['name'] == 'test'
|
||||
assert d['port'] == 9000
|
||||
assert 'modes_enabled' in d
|
||||
assert isinstance(d['modes_enabled'], dict)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# AgentClient Tests
|
||||
# =============================================================================
|
||||
|
||||
class TestAgentClient:
|
||||
"""Tests for AgentClient HTTP operations."""
|
||||
|
||||
def test_init(self):
|
||||
"""AgentClient should initialize correctly."""
|
||||
client = AgentClient('http://192.168.1.50:8020', api_key='secret')
|
||||
assert client.base_url == 'http://192.168.1.50:8020'
|
||||
assert client.api_key == 'secret'
|
||||
assert client.timeout == 60.0
|
||||
|
||||
def test_init_strips_trailing_slash(self):
|
||||
"""AgentClient should strip trailing slash from URL."""
|
||||
client = AgentClient('http://192.168.1.50:8020/')
|
||||
assert client.base_url == 'http://192.168.1.50:8020'
|
||||
|
||||
def test_headers_without_api_key(self):
|
||||
"""Headers should not include API key if not provided."""
|
||||
client = AgentClient('http://localhost:8020')
|
||||
headers = client._headers()
|
||||
assert 'X-API-Key' not in headers
|
||||
assert 'Content-Type' in headers
|
||||
|
||||
def test_headers_with_api_key(self):
|
||||
"""Headers should include API key if provided."""
|
||||
client = AgentClient('http://localhost:8020', api_key='test-key')
|
||||
headers = client._headers()
|
||||
assert headers['X-API-Key'] == 'test-key'
|
||||
|
||||
@patch('utils.agent_client.requests.get')
|
||||
def test_get_capabilities(self, mock_get):
|
||||
"""get_capabilities should parse JSON response."""
|
||||
mock_response = Mock()
|
||||
mock_response.json.return_value = {
|
||||
'modes': {'adsb': True, 'wifi': True},
|
||||
'devices': [{'name': 'RTL-SDR'}],
|
||||
'agent_version': '1.0.0'
|
||||
}
|
||||
mock_response.content = b'{}'
|
||||
mock_response.raise_for_status = Mock()
|
||||
mock_get.return_value = mock_response
|
||||
|
||||
client = AgentClient('http://localhost:8020')
|
||||
caps = client.get_capabilities()
|
||||
|
||||
assert caps['modes']['adsb'] is True
|
||||
assert len(caps['devices']) == 1
|
||||
mock_get.assert_called_once()
|
||||
|
||||
@patch('utils.agent_client.requests.get')
|
||||
def test_get_status(self, mock_get):
|
||||
"""get_status should return status dict."""
|
||||
mock_response = Mock()
|
||||
mock_response.json.return_value = {
|
||||
'running_modes': ['adsb', 'sensor'],
|
||||
'uptime': 3600,
|
||||
'push_enabled': True
|
||||
}
|
||||
mock_response.content = b'{}'
|
||||
mock_response.raise_for_status = Mock()
|
||||
mock_get.return_value = mock_response
|
||||
|
||||
client = AgentClient('http://localhost:8020')
|
||||
status = client.get_status()
|
||||
|
||||
assert 'adsb' in status['running_modes']
|
||||
assert status['uptime'] == 3600
|
||||
|
||||
@patch('utils.agent_client.requests.get')
|
||||
def test_health_check_healthy(self, mock_get):
|
||||
"""health_check should return True for healthy agent."""
|
||||
mock_response = Mock()
|
||||
mock_response.json.return_value = {'status': 'healthy'}
|
||||
mock_response.content = b'{}'
|
||||
mock_response.raise_for_status = Mock()
|
||||
mock_get.return_value = mock_response
|
||||
|
||||
client = AgentClient('http://localhost:8020')
|
||||
assert client.health_check() is True
|
||||
|
||||
@patch('utils.agent_client.requests.get')
|
||||
def test_health_check_unhealthy(self, mock_get):
|
||||
"""health_check should return False for connection error."""
|
||||
import requests
|
||||
mock_get.side_effect = requests.ConnectionError("Connection refused")
|
||||
|
||||
client = AgentClient('http://localhost:8020')
|
||||
assert client.health_check() is False
|
||||
|
||||
@patch('utils.agent_client.requests.post')
|
||||
def test_start_mode(self, mock_post):
|
||||
"""start_mode should POST to correct endpoint."""
|
||||
mock_response = Mock()
|
||||
mock_response.json.return_value = {'status': 'started', 'mode': 'adsb'}
|
||||
mock_response.content = b'{}'
|
||||
mock_response.raise_for_status = Mock()
|
||||
mock_post.return_value = mock_response
|
||||
|
||||
client = AgentClient('http://localhost:8020')
|
||||
result = client.start_mode('adsb', {'device_index': 0})
|
||||
|
||||
assert result['status'] == 'started'
|
||||
mock_post.assert_called_once()
|
||||
call_url = mock_post.call_args[0][0]
|
||||
assert '/adsb/start' in call_url
|
||||
|
||||
@patch('utils.agent_client.requests.post')
|
||||
def test_stop_mode(self, mock_post):
|
||||
"""stop_mode should POST to stop endpoint."""
|
||||
mock_response = Mock()
|
||||
mock_response.json.return_value = {'status': 'stopped'}
|
||||
mock_response.content = b'{}'
|
||||
mock_response.raise_for_status = Mock()
|
||||
mock_post.return_value = mock_response
|
||||
|
||||
client = AgentClient('http://localhost:8020')
|
||||
result = client.stop_mode('wifi')
|
||||
|
||||
assert result['status'] == 'stopped'
|
||||
|
||||
@patch('utils.agent_client.requests.get')
|
||||
def test_get_mode_data(self, mock_get):
|
||||
"""get_mode_data should return data snapshot."""
|
||||
mock_response = Mock()
|
||||
mock_response.json.return_value = {
|
||||
'mode': 'adsb',
|
||||
'data': [
|
||||
{'icao': 'ABC123', 'altitude': 35000},
|
||||
{'icao': 'DEF456', 'altitude': 28000}
|
||||
]
|
||||
}
|
||||
mock_response.content = b'{}'
|
||||
mock_response.raise_for_status = Mock()
|
||||
mock_get.return_value = mock_response
|
||||
|
||||
client = AgentClient('http://localhost:8020')
|
||||
result = client.get_mode_data('adsb')
|
||||
|
||||
assert len(result['data']) == 2
|
||||
assert result['data'][0]['icao'] == 'ABC123'
|
||||
|
||||
@patch('utils.agent_client.requests.get')
|
||||
def test_connection_error_handling(self, mock_get):
|
||||
"""Client should raise AgentConnectionError on connection failure."""
|
||||
import requests
|
||||
mock_get.side_effect = requests.ConnectionError("Connection refused")
|
||||
|
||||
client = AgentClient('http://localhost:8020')
|
||||
|
||||
with pytest.raises(AgentConnectionError) as exc_info:
|
||||
client.get_capabilities()
|
||||
assert 'Cannot connect' in str(exc_info.value)
|
||||
|
||||
@patch('utils.agent_client.requests.get')
|
||||
def test_timeout_error_handling(self, mock_get):
|
||||
"""Client should raise AgentConnectionError on timeout."""
|
||||
import requests
|
||||
mock_get.side_effect = requests.Timeout("Request timed out")
|
||||
|
||||
client = AgentClient('http://localhost:8020', timeout=5.0)
|
||||
|
||||
with pytest.raises(AgentConnectionError) as exc_info:
|
||||
client.get_status()
|
||||
assert 'timed out' in str(exc_info.value)
|
||||
|
||||
@patch('utils.agent_client.requests.get')
|
||||
def test_http_error_handling(self, mock_get):
|
||||
"""Client should raise AgentHTTPError on HTTP errors."""
|
||||
import requests
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 500
|
||||
mock_response.raise_for_status.side_effect = requests.HTTPError(response=mock_response)
|
||||
mock_get.return_value = mock_response
|
||||
|
||||
client = AgentClient('http://localhost:8020')
|
||||
|
||||
with pytest.raises(AgentHTTPError) as exc_info:
|
||||
client.get_capabilities()
|
||||
assert exc_info.value.status_code == 500
|
||||
|
||||
def test_create_client_from_agent(self):
|
||||
"""create_client_from_agent should create configured client."""
|
||||
agent = {
|
||||
'id': 1,
|
||||
'name': 'test-agent',
|
||||
'base_url': 'http://192.168.1.50:8020',
|
||||
'api_key': 'secret123'
|
||||
}
|
||||
|
||||
client = create_client_from_agent(agent)
|
||||
|
||||
assert client.base_url == 'http://192.168.1.50:8020'
|
||||
assert client.api_key == 'secret123'
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Database Agent CRUD Tests
|
||||
# =============================================================================
|
||||
|
||||
class TestDatabaseAgentCRUD:
|
||||
"""Tests for database agent operations."""
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup_db(self, tmp_path):
|
||||
"""Set up a temporary database for each test."""
|
||||
import utils.database as db_module
|
||||
|
||||
# Create temp database
|
||||
test_db_path = tmp_path / 'test.db'
|
||||
original_db_path = db_module.DB_PATH
|
||||
db_module.DB_PATH = test_db_path
|
||||
db_module.DB_DIR = tmp_path
|
||||
|
||||
# Clear any existing connection
|
||||
if hasattr(db_module._local, 'connection') and db_module._local.connection:
|
||||
db_module._local.connection.close()
|
||||
db_module._local.connection = None
|
||||
|
||||
# Initialize schema
|
||||
init_db()
|
||||
|
||||
yield
|
||||
|
||||
# Cleanup
|
||||
if hasattr(db_module._local, 'connection') and db_module._local.connection:
|
||||
db_module._local.connection.close()
|
||||
db_module._local.connection = None
|
||||
db_module.DB_PATH = original_db_path
|
||||
|
||||
def test_create_agent(self):
|
||||
"""create_agent should insert new agent."""
|
||||
agent_id = create_agent(
|
||||
name='sensor-1',
|
||||
base_url='http://192.168.1.50:8020',
|
||||
api_key='secret',
|
||||
description='Test sensor node'
|
||||
)
|
||||
|
||||
assert agent_id is not None
|
||||
assert agent_id > 0
|
||||
|
||||
def test_get_agent(self):
|
||||
"""get_agent should retrieve agent by ID."""
|
||||
agent_id = create_agent(
|
||||
name='sensor-1',
|
||||
base_url='http://192.168.1.50:8020'
|
||||
)
|
||||
|
||||
agent = get_agent(agent_id)
|
||||
|
||||
assert agent is not None
|
||||
assert agent['name'] == 'sensor-1'
|
||||
assert agent['base_url'] == 'http://192.168.1.50:8020'
|
||||
assert agent['is_active'] is True
|
||||
|
||||
def test_get_agent_not_found(self):
|
||||
"""get_agent should return None for missing agent."""
|
||||
agent = get_agent(99999)
|
||||
assert agent is None
|
||||
|
||||
def test_get_agent_by_name(self):
|
||||
"""get_agent_by_name should find agent by name."""
|
||||
create_agent(name='unique-sensor', base_url='http://localhost:8020')
|
||||
|
||||
agent = get_agent_by_name('unique-sensor')
|
||||
|
||||
assert agent is not None
|
||||
assert agent['name'] == 'unique-sensor'
|
||||
|
||||
def test_get_agent_by_name_not_found(self):
|
||||
"""get_agent_by_name should return None for missing name."""
|
||||
agent = get_agent_by_name('nonexistent-sensor')
|
||||
assert agent is None
|
||||
|
||||
def test_list_agents(self):
|
||||
"""list_agents should return all active agents."""
|
||||
create_agent(name='sensor-1', base_url='http://192.168.1.51:8020')
|
||||
create_agent(name='sensor-2', base_url='http://192.168.1.52:8020')
|
||||
create_agent(name='sensor-3', base_url='http://192.168.1.53:8020')
|
||||
|
||||
agents = list_agents()
|
||||
|
||||
assert len(agents) >= 3
|
||||
names = [a['name'] for a in agents]
|
||||
assert 'sensor-1' in names
|
||||
assert 'sensor-2' in names
|
||||
|
||||
def test_list_agents_active_only(self):
|
||||
"""list_agents should filter inactive agents by default."""
|
||||
agent_id = create_agent(name='inactive-sensor', base_url='http://localhost:8020')
|
||||
update_agent(agent_id, is_active=False)
|
||||
|
||||
agents = list_agents(active_only=True)
|
||||
|
||||
names = [a['name'] for a in agents]
|
||||
assert 'inactive-sensor' not in names
|
||||
|
||||
def test_update_agent(self):
|
||||
"""update_agent should modify agent fields."""
|
||||
agent_id = create_agent(name='sensor-1', base_url='http://localhost:8020')
|
||||
|
||||
result = update_agent(
|
||||
agent_id,
|
||||
base_url='http://192.168.1.100:8020',
|
||||
description='Updated description'
|
||||
)
|
||||
|
||||
assert result is True
|
||||
|
||||
agent = get_agent(agent_id)
|
||||
assert agent['base_url'] == 'http://192.168.1.100:8020'
|
||||
assert agent['description'] == 'Updated description'
|
||||
|
||||
def test_update_agent_capabilities(self):
|
||||
"""update_agent should update capabilities JSON."""
|
||||
agent_id = create_agent(name='sensor-1', base_url='http://localhost:8020')
|
||||
|
||||
caps = {'adsb': True, 'wifi': True, 'bluetooth': False}
|
||||
update_agent(agent_id, capabilities=caps)
|
||||
|
||||
agent = get_agent(agent_id)
|
||||
assert agent['capabilities']['adsb'] is True
|
||||
assert agent['capabilities']['bluetooth'] is False
|
||||
|
||||
def test_update_agent_gps_coords(self):
|
||||
"""update_agent should update GPS coordinates."""
|
||||
agent_id = create_agent(name='sensor-1', base_url='http://localhost:8020')
|
||||
|
||||
gps = {'lat': 40.7128, 'lon': -74.0060, 'altitude': 10}
|
||||
update_agent(agent_id, gps_coords=gps)
|
||||
|
||||
agent = get_agent(agent_id)
|
||||
assert agent['gps_coords']['lat'] == 40.7128
|
||||
assert agent['gps_coords']['lon'] == -74.0060
|
||||
|
||||
def test_delete_agent(self):
|
||||
"""delete_agent should remove agent and payloads."""
|
||||
agent_id = create_agent(name='to-delete', base_url='http://localhost:8020')
|
||||
|
||||
# Add a payload
|
||||
store_push_payload(agent_id, 'adsb', {'aircraft': []})
|
||||
|
||||
# Delete
|
||||
result = delete_agent(agent_id)
|
||||
|
||||
assert result is True
|
||||
assert get_agent(agent_id) is None
|
||||
|
||||
def test_delete_agent_not_found(self):
|
||||
"""delete_agent should return False for missing agent."""
|
||||
result = delete_agent(99999)
|
||||
assert result is False
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Database Push Payload Tests
|
||||
# =============================================================================
|
||||
|
||||
class TestDatabasePayloads:
|
||||
"""Tests for push payload storage."""
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup_db(self, tmp_path):
|
||||
"""Set up a temporary database for each test."""
|
||||
import utils.database as db_module
|
||||
|
||||
test_db_path = tmp_path / 'test.db'
|
||||
original_db_path = db_module.DB_PATH
|
||||
db_module.DB_PATH = test_db_path
|
||||
db_module.DB_DIR = tmp_path
|
||||
|
||||
if hasattr(db_module._local, 'connection') and db_module._local.connection:
|
||||
db_module._local.connection.close()
|
||||
db_module._local.connection = None
|
||||
|
||||
init_db()
|
||||
|
||||
yield
|
||||
|
||||
if hasattr(db_module._local, 'connection') and db_module._local.connection:
|
||||
db_module._local.connection.close()
|
||||
db_module._local.connection = None
|
||||
db_module.DB_PATH = original_db_path
|
||||
|
||||
def test_store_push_payload(self):
|
||||
"""store_push_payload should insert payload."""
|
||||
agent_id = create_agent(name='sensor-1', base_url='http://localhost:8020')
|
||||
|
||||
payload = {'aircraft': [{'icao': 'ABC123', 'altitude': 35000}]}
|
||||
payload_id = store_push_payload(agent_id, 'adsb', payload, 'rtlsdr0')
|
||||
|
||||
assert payload_id > 0
|
||||
|
||||
def test_get_recent_payloads(self):
|
||||
"""get_recent_payloads should return stored payloads."""
|
||||
agent_id = create_agent(name='sensor-1', base_url='http://localhost:8020')
|
||||
|
||||
store_push_payload(agent_id, 'adsb', {'aircraft': [{'icao': 'A'}]})
|
||||
store_push_payload(agent_id, 'adsb', {'aircraft': [{'icao': 'B'}]})
|
||||
store_push_payload(agent_id, 'wifi', {'networks': []})
|
||||
|
||||
# Get all
|
||||
payloads = get_recent_payloads(agent_id=agent_id)
|
||||
assert len(payloads) == 3
|
||||
|
||||
# Filter by scan_type
|
||||
adsb_payloads = get_recent_payloads(agent_id=agent_id, scan_type='adsb')
|
||||
assert len(adsb_payloads) == 2
|
||||
|
||||
def test_get_recent_payloads_includes_agent_name(self):
|
||||
"""Payloads should include agent name."""
|
||||
agent_id = create_agent(name='my-sensor', base_url='http://localhost:8020')
|
||||
store_push_payload(agent_id, 'sensor', {'temperature': 22.5})
|
||||
|
||||
payloads = get_recent_payloads(agent_id=agent_id)
|
||||
|
||||
assert len(payloads) > 0
|
||||
assert payloads[0]['agent_name'] == 'my-sensor'
|
||||
|
||||
def test_get_recent_payloads_limit(self):
|
||||
"""get_recent_payloads should respect limit."""
|
||||
agent_id = create_agent(name='sensor-1', base_url='http://localhost:8020')
|
||||
|
||||
for i in range(10):
|
||||
store_push_payload(agent_id, 'sensor', {'temp': i})
|
||||
|
||||
payloads = get_recent_payloads(agent_id=agent_id, limit=5)
|
||||
assert len(payloads) == 5
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Integration Tests
|
||||
# =============================================================================
|
||||
|
||||
class TestAgentClientIntegration:
|
||||
"""Integration tests using mock agent server."""
|
||||
|
||||
@pytest.fixture
|
||||
def mock_agent(self):
|
||||
"""Start mock agent server for testing."""
|
||||
from tests.mock_agent import app as mock_app
|
||||
import threading
|
||||
|
||||
# Run mock agent in background
|
||||
mock_app.config['TESTING'] = True
|
||||
# Using Flask's test client instead of actual server
|
||||
return mock_app.test_client()
|
||||
|
||||
def test_mock_agent_capabilities(self, mock_agent):
|
||||
"""Mock agent should return capabilities."""
|
||||
response = mock_agent.get('/capabilities')
|
||||
assert response.status_code == 200
|
||||
|
||||
data = json.loads(response.data)
|
||||
assert 'modes' in data
|
||||
assert data['modes']['adsb'] is True
|
||||
|
||||
def test_mock_agent_start_stop_mode(self, mock_agent):
|
||||
"""Mock agent should start/stop modes."""
|
||||
# Start
|
||||
response = mock_agent.post('/adsb/start', json={})
|
||||
assert response.status_code == 200
|
||||
data = json.loads(response.data)
|
||||
assert data['status'] == 'started'
|
||||
|
||||
# Check status
|
||||
response = mock_agent.get('/status')
|
||||
data = json.loads(response.data)
|
||||
assert 'adsb' in data['running_modes']
|
||||
|
||||
# Stop
|
||||
response = mock_agent.post('/adsb/stop', json={})
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_mock_agent_data(self, mock_agent):
|
||||
"""Mock agent should return data when mode is running."""
|
||||
# Start mode first
|
||||
mock_agent.post('/adsb/start', json={})
|
||||
|
||||
response = mock_agent.get('/adsb/data')
|
||||
assert response.status_code == 200
|
||||
|
||||
data = json.loads(response.data)
|
||||
assert 'data' in data
|
||||
# Data should be a list of aircraft
|
||||
assert isinstance(data['data'], list)
|
||||
|
||||
# Cleanup
|
||||
mock_agent.post('/adsb/stop', json={})
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# GPS Manager Tests
|
||||
# =============================================================================
|
||||
|
||||
class TestGPSManager:
|
||||
"""Tests for GPS integration in agent."""
|
||||
|
||||
def test_gps_manager_init(self):
|
||||
"""GPSManager should initialize without error."""
|
||||
from intercept_agent import GPSManager
|
||||
gps = GPSManager()
|
||||
assert gps.position is None
|
||||
assert gps._running is False
|
||||
|
||||
def test_gps_manager_position_format(self):
|
||||
"""GPSManager position should have correct format when set."""
|
||||
from intercept_agent import GPSManager
|
||||
|
||||
gps = GPSManager()
|
||||
|
||||
# Simulate a position update
|
||||
class MockPosition:
|
||||
latitude = 40.7128
|
||||
longitude = -74.0060
|
||||
altitude = 10.5
|
||||
speed = 0.0
|
||||
heading = 180.0
|
||||
fix_quality = 2
|
||||
|
||||
gps._position = MockPosition()
|
||||
pos = gps.position
|
||||
|
||||
assert pos is not None
|
||||
assert pos['lat'] == 40.7128
|
||||
assert pos['lon'] == -74.0060
|
||||
assert pos['altitude'] == 10.5
|
||||
Reference in New Issue
Block a user