fix(iwooos): normalize wazuh receipt timestamps
All checks were successful
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 2m27s
CD Pipeline / build-and-deploy (push) Successful in 6m55s
CD Pipeline / post-deploy-checks (push) Successful in 1m55s
All checks were successful
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 2m27s
CD Pipeline / build-and-deploy (push) Successful in 6m55s
CD Pipeline / post-deploy-checks (push) Successful in 1m55s
This commit is contained in:
@@ -10,7 +10,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
from datetime import datetime
|
from datetime import UTC, datetime
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from sqlalchemy import text
|
from sqlalchemy import text
|
||||||
@@ -484,5 +484,26 @@ def _latest_receipt_at(data: Mapping[str, Any]) -> str | None:
|
|||||||
]
|
]
|
||||||
if not values:
|
if not values:
|
||||||
return None
|
return None
|
||||||
latest = max(values)
|
normalized = [
|
||||||
return latest.isoformat() if isinstance(latest, datetime) else str(latest)
|
parsed
|
||||||
|
for value in values
|
||||||
|
if (parsed := _utc_receipt_datetime(value)) is not None
|
||||||
|
]
|
||||||
|
if normalized:
|
||||||
|
return max(normalized).isoformat()
|
||||||
|
return max(str(value) for value in values)
|
||||||
|
|
||||||
|
|
||||||
|
def _utc_receipt_datetime(value: Any) -> datetime | None:
|
||||||
|
if isinstance(value, datetime):
|
||||||
|
parsed = value
|
||||||
|
elif isinstance(value, str):
|
||||||
|
try:
|
||||||
|
parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
if parsed.tzinfo is None:
|
||||||
|
parsed = parsed.replace(tzinfo=UTC)
|
||||||
|
return parsed.astimezone(UTC)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
from datetime import datetime
|
from datetime import UTC, datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from unittest.mock import AsyncMock, MagicMock
|
from unittest.mock import AsyncMock, MagicMock
|
||||||
|
|
||||||
@@ -208,6 +208,26 @@ def test_wazuh_posture_catalog_and_playbook_are_bounded_no_write() -> None:
|
|||||||
assert wazuh_manager["criticality"] == "P0"
|
assert wazuh_manager["criticality"] == "P0"
|
||||||
|
|
||||||
|
|
||||||
|
def test_wazuh_runtime_readback_normalizes_mixed_receipt_timezones() -> None:
|
||||||
|
row = _complete_row()
|
||||||
|
row["candidate_created_at"] = datetime(2026, 7, 11, 3, 36)
|
||||||
|
row["check_created_at"] = datetime(
|
||||||
|
2026,
|
||||||
|
7,
|
||||||
|
11,
|
||||||
|
3,
|
||||||
|
37,
|
||||||
|
tzinfo=UTC,
|
||||||
|
)
|
||||||
|
|
||||||
|
payload = build_iwooos_wazuh_controlled_executor_runtime_readback(
|
||||||
|
row,
|
||||||
|
executor_enabled=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert payload["latest_receipt_at"] == "2026-07-11T03:37:00+00:00"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_wazuh_posture_scheduler_enqueues_after_mcp_and_log_evidence(
|
async def test_wazuh_posture_scheduler_enqueues_after_mcp_and_log_evidence(
|
||||||
monkeypatch: pytest.MonkeyPatch,
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
|||||||
Reference in New Issue
Block a user