mirror of
https://github.com/ipnet-mesh/meshcore-hub.git
synced 2026-08-07 01:13:11 +02:00
Disable e2e tests by default and fix mypy errors
- Add --e2e flag to pytest to run e2e tests - E2E tests skip by default with clear message - Fix type annotations in webhook.py for mypy compliance - Add proper type hints for comparison operations
This commit is contained in:
@@ -76,7 +76,7 @@ class WebhookConfig:
|
||||
# Parse expression: $.path operator value
|
||||
# Supports: ==, !=, >, <, >=, <=, exists, not exists
|
||||
# Note: >= and <= must come before > and < in the alternation
|
||||
pattern = r'^\$\.([a-zA-Z0-9_.]+)\s+(==|!=|>=|<=|>|<|exists|not exists)\s*(.*)$'
|
||||
pattern = r"^\$\.([a-zA-Z0-9_.]+)\s+(==|!=|>=|<=|>|<|exists|not exists)\s*(.*)$"
|
||||
match = re.match(pattern, expr)
|
||||
|
||||
if not match:
|
||||
@@ -88,8 +88,8 @@ class WebhookConfig:
|
||||
value_str = match.group(3).strip() if match.group(3) else None
|
||||
|
||||
# Navigate the path
|
||||
current = payload
|
||||
for part in path.split('.'):
|
||||
current: Any = payload
|
||||
for part in path.split("."):
|
||||
if isinstance(current, dict) and part in current:
|
||||
current = current[part]
|
||||
else:
|
||||
@@ -109,8 +109,9 @@ class WebhookConfig:
|
||||
return False
|
||||
|
||||
# Handle quoted strings
|
||||
compare_value: Any
|
||||
if value_str.startswith('"') and value_str.endswith('"'):
|
||||
compare_value: Any = value_str[1:-1]
|
||||
compare_value = value_str[1:-1]
|
||||
elif value_str.startswith("'") and value_str.endswith("'"):
|
||||
compare_value = value_str[1:-1]
|
||||
elif value_str == "null":
|
||||
@@ -131,17 +132,17 @@ class WebhookConfig:
|
||||
# Perform comparison
|
||||
try:
|
||||
if operator == "==":
|
||||
return current == compare_value
|
||||
return bool(current == compare_value)
|
||||
elif operator == "!=":
|
||||
return current != compare_value
|
||||
return bool(current != compare_value)
|
||||
elif operator == ">":
|
||||
return current > compare_value
|
||||
return bool(current > compare_value)
|
||||
elif operator == "<":
|
||||
return current < compare_value
|
||||
return bool(current < compare_value)
|
||||
elif operator == ">=":
|
||||
return current >= compare_value
|
||||
return bool(current >= compare_value)
|
||||
elif operator == "<=":
|
||||
return current <= compare_value
|
||||
return bool(current <= compare_value)
|
||||
except TypeError:
|
||||
return False
|
||||
|
||||
@@ -250,14 +251,21 @@ class WebhookDispatcher:
|
||||
if tasks:
|
||||
task_results = await asyncio.gather(*tasks, return_exceptions=True)
|
||||
for webhook, result in zip(
|
||||
[w for w in self.webhooks if w.enabled and w.matches_event(event_type, payload)],
|
||||
[
|
||||
w
|
||||
for w in self.webhooks
|
||||
if w.enabled and w.matches_event(event_type, payload)
|
||||
],
|
||||
task_results,
|
||||
):
|
||||
if isinstance(result, Exception):
|
||||
results[webhook.name] = False
|
||||
logger.error(f"Webhook {webhook.name} failed: {result}")
|
||||
else:
|
||||
elif isinstance(result, bool):
|
||||
results[webhook.name] = result
|
||||
else:
|
||||
# Should not happen, but handle gracefully
|
||||
results[webhook.name] = False
|
||||
|
||||
return results
|
||||
|
||||
@@ -319,7 +327,7 @@ class WebhookDispatcher:
|
||||
|
||||
# Retry with backoff (but not after the last attempt)
|
||||
if attempt < webhook.max_retries:
|
||||
backoff = webhook.retry_backoff * (2 ** attempt)
|
||||
backoff = webhook.retry_backoff * (2**attempt)
|
||||
logger.info(
|
||||
f"Retrying webhook {webhook.name} in {backoff}s "
|
||||
f"(attempt {attempt + 2}/{webhook.max_retries + 1})"
|
||||
@@ -387,7 +395,9 @@ def create_webhook_dispatcher_from_config(
|
||||
# Synchronous wrapper for use in non-async handlers
|
||||
_dispatcher: Optional[WebhookDispatcher] = None
|
||||
_dispatch_queue: list[tuple[str, dict[str, Any], Optional[str]]] = []
|
||||
_dispatch_callback: Optional[Callable[[str, dict[str, Any], Optional[str]], None]] = None
|
||||
_dispatch_callback: Optional[Callable[[str, dict[str, Any], Optional[str]], None]] = (
|
||||
None
|
||||
)
|
||||
|
||||
|
||||
def set_dispatch_callback(
|
||||
|
||||
@@ -33,7 +33,9 @@ class HealthStatus:
|
||||
|
||||
healthy: bool
|
||||
component: str
|
||||
timestamp: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
|
||||
timestamp: str = field(
|
||||
default_factory=lambda: datetime.now(timezone.utc).isoformat()
|
||||
)
|
||||
details: dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
@@ -153,7 +155,9 @@ def read_health_status(component: str) -> Optional[HealthStatus]:
|
||||
return None
|
||||
|
||||
|
||||
def check_health(component: str, stale_threshold: int = HEALTH_STALE_THRESHOLD) -> tuple[bool, str]:
|
||||
def check_health(
|
||||
component: str, stale_threshold: int = HEALTH_STALE_THRESHOLD
|
||||
) -> tuple[bool, str]:
|
||||
"""Check health status for a component.
|
||||
|
||||
Args:
|
||||
|
||||
+49
-1
@@ -1,4 +1,14 @@
|
||||
"""Fixtures for end-to-end tests."""
|
||||
"""Fixtures for end-to-end tests.
|
||||
|
||||
These tests require Docker Compose services to be running.
|
||||
They are disabled by default and can be run with:
|
||||
|
||||
pytest -m e2e tests/e2e/
|
||||
|
||||
Or with the --e2e flag:
|
||||
|
||||
pytest --e2e tests/e2e/
|
||||
"""
|
||||
|
||||
import os
|
||||
import time
|
||||
@@ -7,6 +17,44 @@ from typing import Generator
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
|
||||
def pytest_configure(config: pytest.Config) -> None:
|
||||
"""Register the e2e marker."""
|
||||
config.addinivalue_line(
|
||||
"markers",
|
||||
"e2e: mark test as end-to-end test requiring Docker services",
|
||||
)
|
||||
|
||||
|
||||
def pytest_collection_modifyitems(
|
||||
config: pytest.Config, items: list[pytest.Item]
|
||||
) -> None:
|
||||
"""Auto-mark all tests in this directory as e2e and skip if --e2e not provided."""
|
||||
# Check if e2e tests should run
|
||||
run_e2e = config.getoption("--e2e", default=False)
|
||||
|
||||
skip_e2e = pytest.mark.skip(
|
||||
reason="E2E tests disabled by default. Use --e2e to run them."
|
||||
)
|
||||
|
||||
for item in items:
|
||||
# Mark all tests in e2e directory
|
||||
if "e2e" in str(item.fspath):
|
||||
item.add_marker(pytest.mark.e2e)
|
||||
if not run_e2e:
|
||||
item.add_marker(skip_e2e)
|
||||
|
||||
|
||||
def pytest_addoption(parser: pytest.Parser) -> None:
|
||||
"""Add --e2e option to pytest."""
|
||||
parser.addoption(
|
||||
"--e2e",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Run end-to-end tests (requires Docker services)",
|
||||
)
|
||||
|
||||
|
||||
# E2E test configuration
|
||||
E2E_API_URL = os.environ.get("E2E_API_URL", "http://localhost:18000")
|
||||
E2E_WEB_URL = os.environ.get("E2E_WEB_URL", "http://localhost:18080")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Tests for the webhook dispatcher module."""
|
||||
|
||||
import asyncio
|
||||
from typing import Any
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import httpx
|
||||
@@ -312,9 +312,7 @@ class TestWebhookDispatcher:
|
||||
mock_response = AsyncMock()
|
||||
mock_response.status_code = 500
|
||||
|
||||
with patch.object(
|
||||
dispatcher._client, "post", return_value=mock_response
|
||||
):
|
||||
with patch.object(dispatcher._client, "post", return_value=mock_response):
|
||||
result = await dispatcher.dispatch("event", {"data": "test"})
|
||||
assert result == {"error-webhook": False}
|
||||
|
||||
@@ -403,7 +401,7 @@ class TestWebhookDispatcherFactory:
|
||||
|
||||
def test_create_from_config(self):
|
||||
"""Test creating dispatcher from configuration."""
|
||||
config = [
|
||||
config: list[dict[str, Any]] = [
|
||||
{
|
||||
"name": "webhook-1",
|
||||
"url": "https://example.com/webhook1",
|
||||
|
||||
Reference in New Issue
Block a user