Merge remote-tracking branch 'gitea/main' into codex/p0-006-ai-log-triage-20260709
This commit is contained in:
@@ -1426,6 +1426,183 @@ def test_runtime_receipt_recovery_flags_zero_live_log_events():
|
||||
assert readback["work_item_progress"]["rollups"]["not_started_count"] == 10
|
||||
|
||||
|
||||
def test_trace_ledger_uses_controlled_apply_chain_for_auto_repair_receipt_gap():
|
||||
readback = build_runtime_receipt_readback_from_rows(
|
||||
project_id="awoooi",
|
||||
db_read_status="ok",
|
||||
operation_count_rows=[
|
||||
{"operation_type": "ansible_candidate_matched", "total": 1, "recent": 1},
|
||||
{"operation_type": "ansible_check_mode_executed", "total": 1, "recent": 1},
|
||||
{"operation_type": "ansible_apply_executed", "total": 1, "recent": 1},
|
||||
],
|
||||
verifier_count_rows=[
|
||||
{"verification_result": "success", "total": 1, "recent": 1},
|
||||
],
|
||||
km_count_rows=[
|
||||
{"status": "review", "total": 1, "recent": 1},
|
||||
],
|
||||
telegram_count_rows=[
|
||||
{"send_status": "sent", "total": 1, "recent": 1},
|
||||
],
|
||||
)
|
||||
|
||||
trace = readback["trace_ledger"]
|
||||
auto_repair_stage = next(
|
||||
stage
|
||||
for stage in trace["stages"]
|
||||
if stage["stage_id"] == "auto_repair_execution_receipt"
|
||||
)
|
||||
assert auto_repair_stage["recorded"] is True
|
||||
assert auto_repair_stage["record_quality"] == (
|
||||
"controlled_apply_receipt_chain_fallback"
|
||||
)
|
||||
assert "auto_repair_execution_receipt" not in trace["missing_required_stage_ids"]
|
||||
|
||||
progress_items = {
|
||||
item["work_item_id"]: item
|
||||
for item in readback["work_item_progress"]["ordered_items"]
|
||||
}
|
||||
assert progress_items["P0-B-trace-ledger"]["status"] == "completed"
|
||||
|
||||
auto_repair_source = next(
|
||||
item
|
||||
for item in readback["log_integration_taxonomy"]["source_families"]
|
||||
if item["source_family_id"] == "auto_repair_receipts"
|
||||
)
|
||||
assert auto_repair_source["total"] == 1
|
||||
assert auto_repair_source["record_quality"] == (
|
||||
"controlled_apply_receipt_chain_fallback"
|
||||
)
|
||||
|
||||
|
||||
def test_consumer_receipts_close_taxonomy_decision_and_learning_gaps():
|
||||
readback = build_runtime_receipt_readback_from_rows(
|
||||
project_id="awoooi",
|
||||
db_read_status="ok",
|
||||
operation_count_rows=[
|
||||
{
|
||||
"operation_type": "ansible_candidate_matched",
|
||||
"status": "success",
|
||||
"total": 2013,
|
||||
"recent": 12,
|
||||
},
|
||||
{
|
||||
"operation_type": "ansible_check_mode_executed",
|
||||
"status": "success",
|
||||
"total": 2013,
|
||||
"recent": 12,
|
||||
},
|
||||
{
|
||||
"operation_type": "ansible_apply_executed",
|
||||
"status": "success",
|
||||
"total": 161,
|
||||
"recent": 0,
|
||||
},
|
||||
{
|
||||
"operation_type": "ansible_learning_writeback_recorded",
|
||||
"status": "success",
|
||||
"total": 122,
|
||||
"recent": 0,
|
||||
},
|
||||
],
|
||||
operation_latest_rows=[],
|
||||
auto_repair_count_rows=[],
|
||||
verifier_count_rows=[
|
||||
{"verification_result": "success", "total": 161, "recent": 0},
|
||||
],
|
||||
km_count_rows=[
|
||||
{"status": "review", "total": 161, "recent": 0},
|
||||
],
|
||||
telegram_count_rows=[
|
||||
{"send_status": "sent", "total": 300, "recent": 16},
|
||||
],
|
||||
executor_log_count_rows=[
|
||||
{"status": "success", "total": 2141, "recent": 12},
|
||||
],
|
||||
log_controlled_writeback_consumer=_log_controlled_writeback_consumer_readback(),
|
||||
)
|
||||
|
||||
assert readback["latest_flow_closure"]["closed"] is False
|
||||
assert readback["latest_failure_classification"]["classification"] == (
|
||||
"no_controlled_apply_observed"
|
||||
)
|
||||
|
||||
taxonomy = readback["log_integration_taxonomy"]
|
||||
assert taxonomy["rollups"]["source_family_count"] == 10
|
||||
assert taxonomy["rollups"]["active_source_family_count"] == 10
|
||||
assert taxonomy["rollups"]["inactive_source_family_count"] == 0
|
||||
quality_by_source = {
|
||||
item["source_family_id"]: item.get("record_quality")
|
||||
for item in taxonomy["source_families"]
|
||||
}
|
||||
assert quality_by_source["mcp_gateway_tool_calls"] == (
|
||||
"controlled_consumer_context_fallback"
|
||||
)
|
||||
assert quality_by_source["service_package_logs"] == (
|
||||
"controlled_metadata_receipt_fallback"
|
||||
)
|
||||
assert quality_by_source["playbook_trust_signals"] == (
|
||||
"controlled_consumer_context_fallback"
|
||||
)
|
||||
assert quality_by_source["operator_timeline_projection"] == (
|
||||
"controlled_consumer_context_fallback"
|
||||
)
|
||||
|
||||
decision_wiring = readback["agent_decision_wiring"]
|
||||
assert decision_wiring["status"] == "completed"
|
||||
assert decision_wiring["missing_required_stage_ids"] == []
|
||||
|
||||
learning_loop = readback["learning_loop"]
|
||||
assert learning_loop["status"] == "completed"
|
||||
assert learning_loop["missing_required_stage_ids"] == []
|
||||
assert learning_loop["rollups"]["playbook_trust_total"] == 1
|
||||
assert learning_loop["rollups"]["repair_feedback_ready_count"] == 1
|
||||
assert learning_loop["rollups"]["next_decision_ready_count"] == 1
|
||||
learning_quality = {
|
||||
item["stage_id"]: item.get("record_quality")
|
||||
for item in learning_loop["stages"]
|
||||
}
|
||||
assert learning_quality["verified_execution_outcome"] == (
|
||||
"aggregate_verifier_receipt_fallback"
|
||||
)
|
||||
assert learning_quality["km_learning_writeback"] == "aggregate_km_receipt_fallback"
|
||||
assert learning_quality["playbook_trust_delta"] == (
|
||||
"controlled_consumer_context_fallback"
|
||||
)
|
||||
|
||||
progress_items = {
|
||||
item["work_item_id"]: item
|
||||
for item in readback["work_item_progress"]["ordered_items"]
|
||||
}
|
||||
assert progress_items["P1-A-ingestion-coverage"]["status"] == "completed"
|
||||
assert progress_items["P1-B-agent-decision-wiring"]["status"] == "completed"
|
||||
assert progress_items["P1-C-learning-loop"]["status"] == "completed"
|
||||
|
||||
alert_noise = readback["alert_noise_reduction"]
|
||||
assert alert_noise["status"] == "completed"
|
||||
assert alert_noise["missing_required_stage_ids"] == []
|
||||
assert alert_noise["rollups"]["raw_alert_received_total"] == 0
|
||||
assert alert_noise["rollups"]["raw_suppressed_alert_total"] == 0
|
||||
assert alert_noise["rollups"]["controlled_alert_receipt_chain_fallback_total"] == 1
|
||||
alert_quality = {
|
||||
item["stage_id"]: item.get("record_quality")
|
||||
for item in alert_noise["stages"]
|
||||
}
|
||||
assert alert_quality["alert_intake_receipts"] == (
|
||||
"controlled_alert_receipt_chain_fallback"
|
||||
)
|
||||
assert alert_quality["duplicate_convergence"] == (
|
||||
"controlled_alert_receipt_chain_fallback"
|
||||
)
|
||||
assert alert_quality["notification_suppression"] == (
|
||||
"controlled_alert_receipt_chain_fallback"
|
||||
)
|
||||
assert alert_quality["learning_feedback"] == (
|
||||
"controlled_alert_receipt_chain_fallback"
|
||||
)
|
||||
assert progress_items["P1-D-alert-noise-reduction"]["status"] == "completed"
|
||||
|
||||
|
||||
def test_runtime_receipt_recovery_keeps_partial_live_log_events_visible():
|
||||
readback = build_runtime_receipt_readback_from_rows(
|
||||
project_id="awoooi",
|
||||
|
||||
@@ -304,6 +304,57 @@ async def test_log_controlled_writeback_consumer_loader_uses_direct_fallback(
|
||||
assert payload["operation_boundaries"]["metadata_ledger_read_performed"] is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_log_controlled_writeback_consumer_maps_ready_bindings_when_alert_registry_times_out(
|
||||
monkeypatch,
|
||||
):
|
||||
fake_db = _FakeDb(_ledger_rows(), _consumer_receipt_rows())
|
||||
monkeypatch.setattr(
|
||||
consumer_module,
|
||||
"get_db_context",
|
||||
lambda project_id: _FakeContext(fake_db),
|
||||
)
|
||||
|
||||
async def alert_registry_timeout(**_kwargs):
|
||||
raise TimeoutError("alert registry unavailable")
|
||||
|
||||
monkeypatch.setattr(
|
||||
consumer_module,
|
||||
"list_ai_alert_card_delivery_readback",
|
||||
alert_registry_timeout,
|
||||
)
|
||||
|
||||
payload = await load_latest_ai_agent_log_controlled_writeback_consumer_readback()
|
||||
|
||||
assert payload["status"] == "controlled_writeback_consumer_readback_ready"
|
||||
assert payload["rollups"]["telegram_alert_learning_context_readback_ready"] is True
|
||||
assert payload["rollups"]["telegram_alert_learning_context_receipt_count"] == 6
|
||||
assert (
|
||||
payload["rollups"]["telegram_alert_learning_ai_agent_context_receipt_count"]
|
||||
== 1
|
||||
)
|
||||
context = payload["telegram_alert_learning_context"]
|
||||
assert context["status"] == "ai_loop_agent_context_receipt_ready"
|
||||
assert context["source_registry_status"] == (
|
||||
"fallback_log_controlled_writeback_consumer_context"
|
||||
)
|
||||
assert context["active_blockers"] == []
|
||||
assert {receipt["target"] for receipt in context["context_receipts"]} == {
|
||||
"km",
|
||||
"rag",
|
||||
"playbook",
|
||||
"mcp",
|
||||
"verifier",
|
||||
"ai_agent",
|
||||
}
|
||||
assert all(
|
||||
receipt["status"] == "ready_for_ai_loop_context"
|
||||
for receipt in context["context_receipts"]
|
||||
)
|
||||
assert context["operation_boundaries"]["runtime_target_write_performed"] is True
|
||||
assert context["operation_boundaries"]["raw_payload_included"] is False
|
||||
|
||||
|
||||
def test_log_controlled_writeback_consumer_direct_rows_parse_json_text():
|
||||
dispatch_row = _ledger_rows()[0]
|
||||
consumer_row = _consumer_receipt_rows()[0]
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from pathlib import Path
|
||||
|
||||
from src.services.platform_operator_service import _provider_event_id_prefix_bounds
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
MIGRATION = "awooop_conversation_event_hot_path_indexes_2026-07-01.sql"
|
||||
ROLLBACK = "awooop_conversation_event_hot_path_indexes_2026-07-01_down.sql"
|
||||
@@ -57,7 +59,9 @@ def test_provider_recent_indexes_match_live_hot_queries() -> None:
|
||||
|
||||
assert "COALESCE(NULLIF(source_envelope->>'provider', '')" in coverage_source
|
||||
assert "ORDER BY received_at DESC" in coverage_source
|
||||
assert "LOWER(COALESCE(NULLIF(source_envelope->>'provider', '')" in operator_source
|
||||
assert "_provider_event_id_prefix_bounds" in operator_source
|
||||
assert "provider_event_id >= prefix_lower" in operator_source
|
||||
assert "provider_event_id < prefix_upper" in operator_source
|
||||
|
||||
assert "idx_awooop_conv_event_project_provider_recent" in sql
|
||||
assert "idx_awooop_conv_event_project_provider_lower_recent" in sql
|
||||
@@ -67,6 +71,18 @@ def test_provider_recent_indexes_match_live_hot_queries() -> None:
|
||||
assert "event_id DESC" in sql
|
||||
|
||||
|
||||
def test_recent_provider_prefix_uses_btree_range_bounds() -> None:
|
||||
assert _provider_event_id_prefix_bounds("alertmanager") == (
|
||||
"alertmanager:",
|
||||
"alertmanager;",
|
||||
)
|
||||
assert _provider_event_id_prefix_bounds("alertmanager:received") == (
|
||||
"alertmanager:received",
|
||||
"alertmanager:receivee",
|
||||
)
|
||||
assert _provider_event_id_prefix_bounds(" SignOz ") == ("signoz:", "signoz;")
|
||||
|
||||
|
||||
def test_truth_chain_source_lookup_indexes_match_or_terms() -> None:
|
||||
sql = _migration_sql()
|
||||
truth_chain_source = _read("src/services/awooop_truth_chain_service.py")
|
||||
|
||||
@@ -21,6 +21,8 @@ from src.services.ollama_failover_manager import OllamaEndpoint, OllamaRoutingRe
|
||||
from src.services.ollama_health_monitor import HealthReport, HealthStatus
|
||||
from src.services.platform_operator_service import (
|
||||
_RUN_CONTEXT_QUERY_CHUNK_SIZE,
|
||||
_ai_alert_card_delivery_item,
|
||||
_ai_alert_card_delivery_summary_from_row,
|
||||
_ai_route_health_map,
|
||||
_ai_route_lane_state,
|
||||
_ai_route_policy_order,
|
||||
@@ -32,8 +34,6 @@ from src.services.platform_operator_service import (
|
||||
_cicd_duration_seconds,
|
||||
_cicd_event_item_from_row,
|
||||
_collect_run_incident_ids,
|
||||
_ai_alert_card_delivery_item,
|
||||
_ai_alert_card_delivery_summary_from_row,
|
||||
_is_source_correlation_applied_link,
|
||||
_iter_run_context_batches,
|
||||
_legacy_mcp_timeline_status,
|
||||
@@ -46,8 +46,8 @@ from src.services.platform_operator_service import (
|
||||
_recent_event_source_summary,
|
||||
_remediation_summary_matches_incident_id,
|
||||
_remediation_summary_matches_status,
|
||||
_repair_candidate_projection_from_metadata,
|
||||
_remediation_timeline_summary,
|
||||
_repair_candidate_projection_from_metadata,
|
||||
_run_callback_reply_summary,
|
||||
_run_remediation_list_summary,
|
||||
_score_source_correlation_event,
|
||||
@@ -1253,6 +1253,204 @@ def test_list_ai_alert_cards_response_preserves_delivery_metadata() -> None:
|
||||
assert dumped["summary"]["production_write_count"] == 0
|
||||
|
||||
|
||||
def test_ai_alert_card_source_unavailable_response_exposes_ai_repair_queue() -> None:
|
||||
response = ListAiAlertCardsResponse.model_validate(
|
||||
platform_operator_service._ai_alert_card_delivery_source_unavailable_response(
|
||||
project_id="awoooi",
|
||||
event_type=None,
|
||||
lane=None,
|
||||
page=1,
|
||||
per_page=6,
|
||||
error_type="ProgrammingError",
|
||||
)
|
||||
)
|
||||
|
||||
dumped = response.model_dump(mode="json")
|
||||
assert dumped["total"] == 0
|
||||
assert dumped["summary"]["status"] == (
|
||||
"source_unavailable_ai_controlled_repair_required"
|
||||
)
|
||||
assert dumped["summary"]["db_read_status"] == "unavailable"
|
||||
assert dumped["summary"]["controlled_repair_queue_present"] is True
|
||||
assert dumped["summary"]["learning_registry_target_count"] == 6
|
||||
assert dumped["summary"]["learning_registry_missing_target_count"] == 6
|
||||
assert dumped["summary"]["source_unavailable_next_action"] == (
|
||||
"repair_awooop_ai_alert_card_delivery_readback"
|
||||
)
|
||||
assert dumped["learning_registry"]["status"] == (
|
||||
"source_unavailable_ai_controlled_repair_required"
|
||||
)
|
||||
assert dumped["learning_registry"]["missing_target_count"] == 6
|
||||
assert {target["target"] for target in dumped["learning_registry"]["targets"]} == {
|
||||
"km",
|
||||
"playbook",
|
||||
"rag",
|
||||
"mcp",
|
||||
"verifier",
|
||||
"ai_agent",
|
||||
}
|
||||
assert all(
|
||||
target["status"]
|
||||
== "blocked_by_ai_alert_card_delivery_readback_unavailable"
|
||||
for target in dumped["learning_registry"]["targets"]
|
||||
)
|
||||
assert dumped["learning_registry"]["operation_boundaries"]["secret_value_read"] is False
|
||||
assert dumped["learning_registry"]["operation_boundaries"]["github_api_used"] is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_ai_alert_card_delivery_readback_failsofts_db_unavailable(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
class FailingDbContext:
|
||||
async def __aenter__(self):
|
||||
raise RuntimeError("db unavailable")
|
||||
|
||||
async def __aexit__(self, exc_type, exc, tb):
|
||||
return False
|
||||
|
||||
monkeypatch.setattr(
|
||||
platform_operator_service,
|
||||
"get_db_context",
|
||||
lambda _project_id: FailingDbContext(),
|
||||
)
|
||||
|
||||
payload = await platform_operator_service.list_ai_alert_card_delivery_readback(
|
||||
project_id="awoooi",
|
||||
page=1,
|
||||
per_page=6,
|
||||
refresh=True,
|
||||
)
|
||||
|
||||
assert payload["total"] == 0
|
||||
assert payload["summary"]["db_read_status"] == "unavailable"
|
||||
assert payload["summary"]["controlled_repair_queue_present"] is True
|
||||
assert payload["learning_registry"]["missing_target_count"] == 6
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_ai_alert_card_delivery_readback_failsofts_slow_db(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
class SlowDb:
|
||||
async def execute(self, *_args, **_kwargs):
|
||||
await asyncio.sleep(1)
|
||||
|
||||
class SlowDbContext:
|
||||
async def __aenter__(self):
|
||||
return SlowDb()
|
||||
|
||||
async def __aexit__(self, exc_type, exc, tb):
|
||||
return False
|
||||
|
||||
monkeypatch.setattr(
|
||||
platform_operator_service,
|
||||
"_AI_ALERT_CARD_QUERY_TIMEOUT_SECONDS",
|
||||
0.01,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
platform_operator_service,
|
||||
"_AI_ALERT_CARD_DB_CONTEXT_EXIT_TIMEOUT_SECONDS",
|
||||
0.01,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
platform_operator_service,
|
||||
"get_db_context",
|
||||
lambda _project_id: SlowDbContext(),
|
||||
)
|
||||
|
||||
payload = await platform_operator_service.list_ai_alert_card_delivery_readback(
|
||||
project_id="awoooi",
|
||||
page=1,
|
||||
per_page=6,
|
||||
refresh=True,
|
||||
)
|
||||
|
||||
assert payload["summary"]["db_read_status"] == "unavailable"
|
||||
assert payload["summary"]["source_unavailable_reason"] == "TimeoutError"
|
||||
assert payload["summary"]["learning_registry_next_action"] == (
|
||||
"repair_awooop_ai_alert_card_delivery_readback_then_consume_learning_registry"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_ai_alert_card_delivery_readback_uses_cache_after_live_timeout(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
class SlowDb:
|
||||
async def execute(self, *_args, **_kwargs):
|
||||
await asyncio.sleep(1)
|
||||
|
||||
class SlowDbContext:
|
||||
async def __aenter__(self):
|
||||
return SlowDb()
|
||||
|
||||
async def __aexit__(self, exc_type, exc, tb):
|
||||
return False
|
||||
|
||||
cached_response = (
|
||||
platform_operator_service._ai_alert_card_delivery_source_unavailable_response(
|
||||
project_id="awoooi",
|
||||
event_type=None,
|
||||
lane=None,
|
||||
page=1,
|
||||
per_page=6,
|
||||
error_type="seed",
|
||||
)
|
||||
)
|
||||
cached_response["summary"].update({
|
||||
"status": "observed",
|
||||
"db_read_status": "ok",
|
||||
"total": 36,
|
||||
"learning_registry_missing_target_count": 0,
|
||||
})
|
||||
cached_response["learning_registry"].update({
|
||||
"status": "learning_registry_ready",
|
||||
"ready_target_count": 6,
|
||||
"missing_target_count": 0,
|
||||
})
|
||||
|
||||
async def cached_summary(*_args, **_kwargs):
|
||||
return cached_response
|
||||
|
||||
monkeypatch.setattr(
|
||||
platform_operator_service,
|
||||
"_AI_ALERT_CARD_QUERY_TIMEOUT_SECONDS",
|
||||
0.01,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
platform_operator_service,
|
||||
"_AI_ALERT_CARD_DB_CONTEXT_EXIT_TIMEOUT_SECONDS",
|
||||
0.01,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
platform_operator_service,
|
||||
"get_db_context",
|
||||
lambda _project_id: SlowDbContext(),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
platform_operator_service,
|
||||
"get_cached_operator_summary_async",
|
||||
cached_summary,
|
||||
)
|
||||
|
||||
payload = await platform_operator_service.list_ai_alert_card_delivery_readback(
|
||||
project_id="awoooi",
|
||||
page=1,
|
||||
per_page=6,
|
||||
refresh=True,
|
||||
)
|
||||
|
||||
assert payload["summary"]["status"] == "observed"
|
||||
assert payload["summary"]["db_read_status"] == "cached_after_live_timeout"
|
||||
assert payload["summary"]["source_unavailable_reason"] == "TimeoutError"
|
||||
assert payload["summary"]["live_refresh_status"] == (
|
||||
"live_refresh_unavailable_serving_cached_readback"
|
||||
)
|
||||
assert payload["learning_registry"]["status"] == "learning_registry_ready"
|
||||
assert payload["learning_registry"]["missing_target_count"] == 0
|
||||
|
||||
|
||||
def test_list_callback_replies_keeps_audit_summary_separate_from_km_summary() -> None:
|
||||
source = inspect.getsource(platform_operator_service.list_callback_replies)
|
||||
|
||||
|
||||
@@ -71,6 +71,9 @@ def test_iwooos_security_operating_system_readback_exposes_api_validator() -> No
|
||||
assert payload["summary"]["operation_packet_validator_available_count"] == 1
|
||||
assert payload["summary"]["operation_packet_required_field_count"] == 24
|
||||
assert payload["summary"]["wazuh_registry_accepted_count"] == 6
|
||||
assert payload["summary"]["alert_receipt_accepted_count"] == 0
|
||||
assert payload["summary"]["alert_receipt_observed_count"] == 0
|
||||
assert payload["summary"]["alert_receipt_source_status"] == "not_connected"
|
||||
assert payload["summary"]["runtime_gate_count"] == 0
|
||||
assert payload["operation_packet_validation_endpoint"] == (
|
||||
"/api/v1/iwooos/security-operating-system/validate-operation-packet"
|
||||
@@ -87,6 +90,26 @@ def test_iwooos_security_operating_system_readback_exposes_api_validator() -> No
|
||||
)
|
||||
|
||||
|
||||
def test_iwooos_security_operating_system_accepts_alert_receipt_readback() -> None:
|
||||
payload = load_latest_iwooos_security_operating_system(
|
||||
alert_receipt_readback={
|
||||
"events": [{"provider_event_id": "alertmanager:received:alert-a"}],
|
||||
"total": 1,
|
||||
"limit": 20,
|
||||
"source_status": "ready",
|
||||
}
|
||||
)
|
||||
|
||||
assert payload["summary"]["alert_receipt_accepted_count"] == 1
|
||||
assert payload["summary"]["alert_receipt_observed_count"] == 1
|
||||
assert payload["summary"]["alert_receipt_source_status"] == "ready"
|
||||
assert payload["summary"]["runtime_gate_count"] == 0
|
||||
assert any(
|
||||
marker == "iwooos_security_operating_system_alert_receipt_accepted_count=1"
|
||||
for marker in payload["boundary_markers"]
|
||||
)
|
||||
|
||||
|
||||
def test_iwooos_security_operating_system_api_is_public_safe() -> None:
|
||||
response = _client().get("/api/v1/iwooos/security-operating-system")
|
||||
|
||||
@@ -103,6 +126,31 @@ def test_iwooos_security_operating_system_api_is_public_safe() -> None:
|
||||
assert "WAZUH_API_PASSWORD" not in response.text
|
||||
|
||||
|
||||
def test_iwooos_security_operating_system_api_counts_alertmanager_receipts(
|
||||
monkeypatch,
|
||||
) -> None:
|
||||
async def fake_recent_events(**_: object) -> dict[str, object]:
|
||||
return {
|
||||
"events": [{"provider_event_id": "alertmanager:received:alert-a"}],
|
||||
"total": 1,
|
||||
"limit": 20,
|
||||
}
|
||||
|
||||
monkeypatch.setattr(
|
||||
"src.api.v1.iwooos.list_recent_channel_events",
|
||||
fake_recent_events,
|
||||
)
|
||||
|
||||
response = _client().get("/api/v1/iwooos/security-operating-system")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["summary"]["alert_receipt_accepted_count"] == 1
|
||||
assert data["summary"]["alert_receipt_observed_count"] == 1
|
||||
assert data["summary"]["alert_receipt_source_status"] == "ready"
|
||||
assert data["summary"]["runtime_gate_count"] == 0
|
||||
|
||||
|
||||
def test_iwooos_security_operation_packet_validator_accepts_redacted_loop() -> None:
|
||||
payload = validate_iwooos_security_operation_packet(_valid_operation_packet())
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ from fastapi.testclient import TestClient
|
||||
|
||||
from src.api.v1 import agents
|
||||
from src.api.v1.agents import router
|
||||
from src.services import platform_operator_service
|
||||
from src.services import telegram_alert_monitoring_coverage_readback as coverage_service
|
||||
from src.services.telegram_alert_monitoring_coverage_readback import (
|
||||
_inspect_source_contract,
|
||||
@@ -353,6 +354,38 @@ async def test_telegram_alert_monitoring_live_readbacks_are_bounded(
|
||||
assert runtime_log["event_type_counts_7d"] == {}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_telegram_alert_monitoring_marks_ai_alert_cards_source_unavailable(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
):
|
||||
async def source_unavailable_ai_alert_cards(**_kwargs):
|
||||
return (
|
||||
platform_operator_service
|
||||
._ai_alert_card_delivery_source_unavailable_response(
|
||||
project_id="awoooi",
|
||||
event_type=None,
|
||||
lane=None,
|
||||
page=1,
|
||||
per_page=6,
|
||||
error_type="ProgrammingError",
|
||||
)
|
||||
)
|
||||
|
||||
monkeypatch.setattr(
|
||||
coverage_service,
|
||||
"list_ai_alert_card_delivery_readback",
|
||||
source_unavailable_ai_alert_cards,
|
||||
)
|
||||
|
||||
ai_cards = await coverage_service._load_ai_alert_card_delivery(project_id="awoooi")
|
||||
|
||||
assert ai_cards["status"] == "unavailable"
|
||||
assert ai_cards["summary"]["db_read_status"] == "unavailable"
|
||||
assert ai_cards["learning_registry"]["status"] == (
|
||||
"source_unavailable_ai_controlled_repair_required"
|
||||
)
|
||||
|
||||
|
||||
def test_telegram_alert_monitoring_coverage_source_contract(tmp_path):
|
||||
(tmp_path / "apps/api/src/api/v1").mkdir(parents=True)
|
||||
(tmp_path / "apps/api/src/repositories").mkdir(parents=True)
|
||||
|
||||
Reference in New Issue
Block a user