docs: update CLI command syntax across workflow documentation
Some checks failed
CLI Tests / test-cli (push) Has been cancelled
Security Scanning / security-scan (push) Has been cancelled
Integration Tests / test-service-integration (push) Has been cancelled
Python Tests / test-python (push) Has been cancelled
Documentation Validation / validate-docs (push) Has been cancelled
API Endpoint Tests / test-api-endpoints (push) Has been cancelled

- Updated marketplace commands: `marketplace --action` → `market` subcommands
- Updated wallet commands: direct flags → `wallet` subcommands
- Updated AI commands: `ai-submit`, `ai-status` → `ai submit`, `ai status`
- Updated blockchain commands: `chain` → `blockchain info`
- Standardized command structure across all workflow files
- Affected files: MULTI_NODE_MASTER_INDEX.md, TEST_MASTER_INDEX.md, multi-node-blockchain-marketplace
This commit is contained in:
aitbc
2026-04-08 12:10:21 +02:00
parent ef4a1c0e87
commit 40ddf89b9c
251 changed files with 3555 additions and 61407 deletions

View File

@@ -0,0 +1,218 @@
"""
Unit tests for coordinator API metrics collection and alert delivery.
Tests MetricsCollector, AlertDispatcher, and build_live_metrics_payload
without requiring full app startup or database.
"""
import asyncio
from unittest.mock import patch
import pytest
from app.utils.alerting import AlertDispatcher
from app.utils.metrics import MetricsCollector, build_live_metrics_payload
class TestMetricsCollector:
"""Test MetricsCollector behavior and alert threshold evaluation."""
def test_metrics_collector_initial_state(self):
"""Verify collector starts with zeroed metrics."""
collector = MetricsCollector()
metrics = collector.get_metrics()
assert metrics["api_requests"] == 0
assert metrics["api_errors"] == 0
assert metrics["cache_hits"] == 0
assert metrics["cache_misses"] == 0
assert metrics["database_queries"] == 0
assert metrics["database_errors"] == 0
def test_metrics_collector_records_api_metrics(self):
"""Verify API request, error, and response time tracking."""
collector = MetricsCollector()
collector.record_api_request(error=False, response_time_ms=100.0)
collector.record_api_request(error=True, response_time_ms=200.0)
collector.record_api_request(error=False, response_time_ms=50.0)
metrics = collector.get_metrics()
assert metrics["api_requests"] == 3
assert metrics["api_errors"] == 1
assert len(metrics["api_response_times"]) == 3
assert sum(metrics["api_response_times"]) == 0.35
def test_metrics_collector_calculates_error_rate(self):
"""Verify error rate percentage calculation."""
collector = MetricsCollector()
for _ in range(10):
collector.record_api_request(error=False, response_time_ms=100.0)
collector.record_api_request(error=True, response_time_ms=100.0)
metrics = collector.get_metrics()
assert metrics["error_rate_percent"] == pytest.approx(9.09, rel=0.01)
def test_metrics_collector_calculates_avg_response_time(self):
"""Verify average response time calculation."""
collector = MetricsCollector()
collector.record_api_request(error=False, response_time_ms=100.0)
collector.record_api_request(error=False, response_time_ms=200.0)
metrics = collector.get_metrics()
assert metrics["avg_response_time_ms"] == 150.0
def test_metrics_collector_cache_hit_rate(self):
"""Verify cache hit rate calculation."""
collector = MetricsCollector()
collector.update_cache_stats({"hits": 7, "misses": 3})
metrics = collector.get_metrics()
assert metrics["cache_hit_rate_percent"] == 70.0
def test_metrics_collector_alert_thresholds(self):
"""Verify alert threshold evaluation for error rate and response time."""
collector = MetricsCollector()
collector.record_api_request(error=False, response_time_ms=100.0)
alerts = collector.get_alert_states()
assert alerts["error_rate"]["triggered"] is False
assert alerts["avg_response_time"]["triggered"] is False
for _ in range(20):
collector.record_api_request(error=True, response_time_ms=100.0)
alerts = collector.get_alert_states()
assert alerts["error_rate"]["triggered"] is True
assert alerts["error_rate"]["value"] > 1.0
def test_metrics_collector_reset(self):
"""Verify metrics can be reset to initial state."""
collector = MetricsCollector()
collector.record_api_request(error=False, response_time_ms=100.0)
collector.record_database_query(error=False)
collector.update_cache_stats({"hits": 5, "misses": 5})
collector.reset_metrics()
metrics = collector.get_metrics()
assert metrics["api_requests"] == 0
assert metrics["database_queries"] == 0
assert metrics["cache_hits"] == 0
assert metrics["cache_misses"] == 0
class TestAlertDispatcher:
"""Test AlertDispatcher cooldown suppression and history recording."""
def test_alert_dispatcher_initial_state(self):
"""Verify dispatcher starts with empty history and no last sent timestamps."""
dispatcher = AlertDispatcher(cooldown_seconds=300)
assert len(dispatcher.get_recent_alerts()) == 0
def test_alert_dispatcher_records_history(self):
"""Verify dispatched alerts are recorded in history."""
dispatcher = AlertDispatcher(cooldown_seconds=0)
alerts = {
"test_alert": {"triggered": True, "status": "critical", "value": 95.0, "threshold": 90.0}
}
dispatcher.dispatch(alerts)
history = dispatcher.get_recent_alerts()
assert len(history) == 1
assert history[0]["severity"] == "critical"
assert history[0]["delivery_status"] == "sent"
def test_alert_dispatcher_cooldown_suppression(self):
"""Verify alerts are suppressed during cooldown period."""
dispatcher = AlertDispatcher(cooldown_seconds=10)
alerts = {
"test_alert": {"triggered": True, "status": "critical", "value": 95.0, "threshold": 90.0}
}
result1 = dispatcher.dispatch(alerts)
assert result1["triggered_count"] == 1
assert len(result1["sent"]) == 1
assert len(result1["suppressed"]) == 0
result2 = dispatcher.dispatch(alerts)
assert result2["triggered_count"] == 1
assert len(result2["sent"]) == 0
assert len(result2["suppressed"]) == 1
def test_alert_dispatcher_history_filter_by_severity(self):
"""Verify history can be filtered by severity."""
dispatcher = AlertDispatcher(cooldown_seconds=0)
dispatcher.dispatch({"alert1": {"triggered": True, "status": "critical", "value": 95.0, "threshold": 90.0}})
dispatcher.dispatch({"alert2": {"triggered": True, "status": "warning", "value": 85.0, "threshold": 80.0}})
critical_alerts = dispatcher.get_recent_alerts(severity="critical")
warning_alerts = dispatcher.get_recent_alerts(severity="warning")
assert len(critical_alerts) == 1
assert len(warning_alerts) == 1
assert critical_alerts[0]["severity"] == "critical"
assert warning_alerts[0]["severity"] == "warning"
def test_alert_dispatcher_history_limit(self):
"""Verify history respects the limit parameter."""
dispatcher = AlertDispatcher(cooldown_seconds=0, max_history=10)
for i in range(5):
dispatcher.dispatch({f"alert{i}": {"triggered": True, "status": "critical", "value": 95.0, "threshold": 90.0}})
assert len(dispatcher.get_recent_alerts(limit=3)) == 3
assert len(dispatcher.get_recent_alerts(limit=10)) == 5
def test_alert_dispatcher_reset_history(self):
"""Verify history can be cleared."""
dispatcher = AlertDispatcher(cooldown_seconds=0)
dispatcher.dispatch({"alert1": {"triggered": True, "status": "critical", "value": 95.0, "threshold": 90.0}})
dispatcher.reset_history()
assert len(dispatcher.get_recent_alerts()) == 0
@patch.dict("os.environ", {}, clear=True)
def test_alert_dispatcher_log_fallback(self):
"""Verify alert falls back to log when webhook URL is not configured."""
dispatcher = AlertDispatcher(cooldown_seconds=0)
alerts = {"test_alert": {"triggered": True, "status": "critical", "value": 95.0, "threshold": 90.0}}
result = dispatcher.dispatch(alerts)
assert result["channel"] == "log"
assert len(result["sent"]) == 1
class TestBuildLiveMetricsPayload:
"""Test the shared metrics payload builder used by /v1/metrics endpoint."""
def test_build_live_metrics_payload_basic(self):
"""Verify payload builder returns metrics with cache stats."""
collector = MetricsCollector()
cache_stats = {"hits": 8, "misses": 2}
payload = build_live_metrics_payload(cache_stats=cache_stats, collector=collector)
assert "cache_hits" in payload
assert "cache_misses" in payload
assert payload["cache_hits"] == 8
assert payload["cache_misses"] == 2
assert payload["cache_hit_rate_percent"] == 80.0
def test_build_live_metrics_payload_with_dispatcher(self):
"""Verify payload builder includes alert delivery results when dispatcher is provided."""
collector = MetricsCollector()
dispatcher = AlertDispatcher(cooldown_seconds=0)
cache_stats = {"hits": 5, "misses": 5}
payload = build_live_metrics_payload(cache_stats=cache_stats, dispatcher=dispatcher, collector=collector)
assert "alert_delivery" in payload
assert "triggered_count" in payload["alert_delivery"]
assert "channel" in payload["alert_delivery"]
def test_build_live_metrics_payload_uses_global_collector(self):
"""Verify payload builder uses global collector when none is provided."""
cache_stats = {"hits": 3, "misses": 7}
payload = build_live_metrics_payload(cache_stats=cache_stats)
assert "cache_hit_rate_percent" in payload
assert payload["cache_hit_rate_percent"] == 30.0