mirror of
https://github.com/smittix/intercept.git
synced 2026-06-17 09:59:47 -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:
+53
-67
@@ -8,7 +8,7 @@ import logging
|
||||
|
||||
import requests
|
||||
|
||||
logger = logging.getLogger('intercept.agent_client')
|
||||
logger = logging.getLogger("intercept.agent_client")
|
||||
|
||||
|
||||
class AgentHTTPError(RuntimeError):
|
||||
@@ -21,18 +21,14 @@ class AgentHTTPError(RuntimeError):
|
||||
|
||||
class AgentConnectionError(AgentHTTPError):
|
||||
"""Exception raised when agent is unreachable."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class AgentClient:
|
||||
"""HTTP client for communicating with a remote Intercept agent."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
base_url: str,
|
||||
api_key: str | None = None,
|
||||
timeout: float = 60.0
|
||||
):
|
||||
def __init__(self, base_url: str, api_key: str | None = None, timeout: float = 60.0):
|
||||
"""
|
||||
Initialize agent client.
|
||||
|
||||
@@ -41,15 +37,15 @@ class AgentClient:
|
||||
api_key: Optional API key for authentication
|
||||
timeout: Request timeout in seconds
|
||||
"""
|
||||
self.base_url = base_url.rstrip('/')
|
||||
self.base_url = base_url.rstrip("/")
|
||||
self.api_key = api_key
|
||||
self.timeout = timeout
|
||||
|
||||
def _headers(self) -> dict:
|
||||
"""Get request headers."""
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
headers = {"Content-Type": "application/json"}
|
||||
if self.api_key:
|
||||
headers['X-API-Key'] = self.api_key
|
||||
headers["X-API-Key"] = self.api_key
|
||||
return headers
|
||||
|
||||
def _get(self, path: str, params: dict | None = None) -> dict:
|
||||
@@ -69,12 +65,7 @@ class AgentClient:
|
||||
"""
|
||||
url = f"{self.base_url}{path}"
|
||||
try:
|
||||
response = requests.get(
|
||||
url,
|
||||
headers=self._headers(),
|
||||
params=params,
|
||||
timeout=self.timeout
|
||||
)
|
||||
response = requests.get(url, headers=self._headers(), params=params, timeout=self.timeout)
|
||||
response.raise_for_status()
|
||||
return response.json() if response.content else {}
|
||||
except requests.ConnectionError as e:
|
||||
@@ -86,17 +77,17 @@ class AgentClient:
|
||||
error_msg = f"Agent returned error: {e.response.status_code}"
|
||||
try:
|
||||
error_data = e.response.json()
|
||||
if 'message' in error_data:
|
||||
error_msg = error_data['message']
|
||||
elif 'error' in error_data:
|
||||
error_msg = error_data['error']
|
||||
if "message" in error_data:
|
||||
error_msg = error_data["message"]
|
||||
elif "error" in error_data:
|
||||
error_msg = error_data["error"]
|
||||
except Exception:
|
||||
pass
|
||||
raise AgentHTTPError(error_msg, status_code=e.response.status_code)
|
||||
except requests.RequestException as e:
|
||||
raise AgentHTTPError(f"Request failed: {e}")
|
||||
|
||||
def _post(self, path: str, data: dict | None = None, timeout: float | None = None) -> dict:
|
||||
def _post(self, path: str, data: dict | None = None, timeout: float | None = None) -> dict:
|
||||
"""
|
||||
Perform POST request to agent.
|
||||
|
||||
@@ -111,39 +102,38 @@ class AgentClient:
|
||||
AgentHTTPError: On HTTP errors
|
||||
AgentConnectionError: If agent is unreachable
|
||||
"""
|
||||
url = f"{self.base_url}{path}"
|
||||
request_timeout = self.timeout if timeout is None else timeout
|
||||
try:
|
||||
response = requests.post(
|
||||
url,
|
||||
json=data or {},
|
||||
headers=self._headers(),
|
||||
timeout=request_timeout
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json() if response.content else {}
|
||||
except requests.ConnectionError as e:
|
||||
raise AgentConnectionError(f"Cannot connect to agent at {self.base_url}: {e}")
|
||||
except requests.Timeout:
|
||||
raise AgentConnectionError(f"Request to agent timed out after {request_timeout}s")
|
||||
url = f"{self.base_url}{path}"
|
||||
request_timeout = self.timeout if timeout is None else timeout
|
||||
try:
|
||||
response = requests.post(url, json=data or {}, headers=self._headers(), timeout=request_timeout)
|
||||
response.raise_for_status()
|
||||
return response.json() if response.content else {}
|
||||
except requests.ConnectionError as e:
|
||||
raise AgentConnectionError(f"Cannot connect to agent at {self.base_url}: {e}")
|
||||
except requests.Timeout:
|
||||
raise AgentConnectionError(f"Request to agent timed out after {request_timeout}s")
|
||||
except requests.HTTPError as e:
|
||||
# Try to extract error message from response body
|
||||
error_msg = f"Agent returned error: {e.response.status_code}"
|
||||
try:
|
||||
error_data = e.response.json()
|
||||
if 'message' in error_data:
|
||||
error_msg = error_data['message']
|
||||
elif 'error' in error_data:
|
||||
error_msg = error_data['error']
|
||||
if "message" in error_data:
|
||||
error_msg = error_data["message"]
|
||||
elif "error" in error_data:
|
||||
error_msg = error_data["error"]
|
||||
except Exception:
|
||||
pass
|
||||
raise AgentHTTPError(error_msg, status_code=e.response.status_code)
|
||||
except requests.RequestException as e:
|
||||
raise AgentHTTPError(f"Request failed: {e}")
|
||||
|
||||
def post(self, path: str, data: dict | None = None, timeout: float | None = None) -> dict:
|
||||
"""Public POST method for arbitrary endpoints."""
|
||||
return self._post(path, data, timeout=timeout)
|
||||
def get(self, path: str, params: dict | None = None) -> dict:
|
||||
"""Public GET method for arbitrary endpoints."""
|
||||
return self._get(path, params)
|
||||
|
||||
def post(self, path: str, data: dict | None = None, timeout: float | None = None) -> dict:
|
||||
"""Public POST method for arbitrary endpoints."""
|
||||
return self._post(path, data, timeout=timeout)
|
||||
|
||||
# =========================================================================
|
||||
# Capability & Status
|
||||
@@ -156,7 +146,7 @@ class AgentClient:
|
||||
Returns:
|
||||
Dict with 'modes' (mode -> bool), 'devices' (list), 'agent_version'
|
||||
"""
|
||||
return self._get('/capabilities')
|
||||
return self._get("/capabilities")
|
||||
|
||||
def get_status(self) -> dict:
|
||||
"""
|
||||
@@ -165,7 +155,7 @@ class AgentClient:
|
||||
Returns:
|
||||
Dict with 'running_modes', 'uptime', 'push_enabled', etc.
|
||||
"""
|
||||
return self._get('/status')
|
||||
return self._get("/status")
|
||||
|
||||
def health_check(self) -> bool:
|
||||
"""
|
||||
@@ -175,14 +165,14 @@ class AgentClient:
|
||||
True if agent is reachable and healthy
|
||||
"""
|
||||
try:
|
||||
result = self._get('/health')
|
||||
return result.get('status') == 'healthy'
|
||||
result = self._get("/health")
|
||||
return result.get("status") == "healthy"
|
||||
except (AgentHTTPError, AgentConnectionError):
|
||||
return False
|
||||
|
||||
def get_config(self) -> dict:
|
||||
"""Get agent configuration (non-sensitive fields)."""
|
||||
return self._get('/config')
|
||||
return self._get("/config")
|
||||
|
||||
def update_config(self, **kwargs) -> dict:
|
||||
"""
|
||||
@@ -195,7 +185,7 @@ class AgentClient:
|
||||
Returns:
|
||||
Updated config
|
||||
"""
|
||||
return self._post('/config', kwargs)
|
||||
return self._post("/config", kwargs)
|
||||
|
||||
# =========================================================================
|
||||
# Mode Operations
|
||||
@@ -212,9 +202,9 @@ class AgentClient:
|
||||
Returns:
|
||||
Start result with 'status' field
|
||||
"""
|
||||
return self._post(f'/{mode}/start', params or {})
|
||||
return self._post(f"/{mode}/start", params or {})
|
||||
|
||||
def stop_mode(self, mode: str, timeout: float = 8.0) -> dict:
|
||||
def stop_mode(self, mode: str, timeout: float = 8.0) -> dict:
|
||||
"""
|
||||
Stop a running mode on the agent.
|
||||
|
||||
@@ -224,7 +214,7 @@ class AgentClient:
|
||||
Returns:
|
||||
Stop result with 'status' field
|
||||
"""
|
||||
return self._post(f'/{mode}/stop', timeout=timeout)
|
||||
return self._post(f"/{mode}/stop", timeout=timeout)
|
||||
|
||||
def get_mode_status(self, mode: str) -> dict:
|
||||
"""
|
||||
@@ -236,7 +226,7 @@ class AgentClient:
|
||||
Returns:
|
||||
Mode status with 'running' field
|
||||
"""
|
||||
return self._get(f'/{mode}/status')
|
||||
return self._get(f"/{mode}/status")
|
||||
|
||||
def get_mode_data(self, mode: str) -> dict:
|
||||
"""
|
||||
@@ -248,7 +238,7 @@ class AgentClient:
|
||||
Returns:
|
||||
Data snapshot with 'data' field
|
||||
"""
|
||||
return self._get(f'/{mode}/data')
|
||||
return self._get(f"/{mode}/data")
|
||||
|
||||
# =========================================================================
|
||||
# Convenience Methods
|
||||
@@ -262,17 +252,17 @@ class AgentClient:
|
||||
Dict with capabilities, status, and config
|
||||
"""
|
||||
metadata = {
|
||||
'capabilities': None,
|
||||
'status': None,
|
||||
'config': None,
|
||||
'healthy': False,
|
||||
"capabilities": None,
|
||||
"status": None,
|
||||
"config": None,
|
||||
"healthy": False,
|
||||
}
|
||||
|
||||
try:
|
||||
metadata['capabilities'] = self.get_capabilities()
|
||||
metadata['status'] = self.get_status()
|
||||
metadata['config'] = self.get_config()
|
||||
metadata['healthy'] = True
|
||||
metadata["capabilities"] = self.get_capabilities()
|
||||
metadata["status"] = self.get_status()
|
||||
metadata["config"] = self.get_config()
|
||||
metadata["healthy"] = True
|
||||
except (AgentHTTPError, AgentConnectionError) as e:
|
||||
logger.warning(f"Failed to refresh agent metadata: {e}")
|
||||
|
||||
@@ -292,8 +282,4 @@ def create_client_from_agent(agent: dict) -> AgentClient:
|
||||
Returns:
|
||||
Configured AgentClient
|
||||
"""
|
||||
return AgentClient(
|
||||
base_url=agent['base_url'],
|
||||
api_key=agent.get('api_key'),
|
||||
timeout=60.0
|
||||
)
|
||||
return AgentClient(base_url=agent["base_url"], api_key=agent.get("api_key"), timeout=60.0)
|
||||
|
||||
+2
-13
@@ -35,21 +35,10 @@ def is_meshcore_available() -> bool:
|
||||
return HAS_MESHCORE
|
||||
|
||||
|
||||
# Try to import ContactType for repeater detection
|
||||
try:
|
||||
from meshcore import ContactType as _ContactType
|
||||
|
||||
_REPEATER_TYPE = getattr(_ContactType, "REPEATER", None)
|
||||
except Exception:
|
||||
_ContactType = None
|
||||
_REPEATER_TYPE = None
|
||||
|
||||
|
||||
def _is_repeater_contact(contact_dict: dict) -> bool:
|
||||
"""Return True if this contact is a repeater node."""
|
||||
if _REPEATER_TYPE is not None:
|
||||
return contact_dict.get("type") == _REPEATER_TYPE
|
||||
# Fallback: meshcore repeaters have type==2 by convention
|
||||
# meshcore exports no ContactType enum (checked through 2.3.7);
|
||||
# repeaters have type==2 by library convention
|
||||
return contact_dict.get("type") == 2
|
||||
|
||||
|
||||
|
||||
+421
-366
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user