Files
ss-tools/backend/tests/services/test_run_to_dict.py
2026-06-01 14:40:17 +03:00

155 lines
6.3 KiB
Python

# #region TestRunToDict [C:3] [TYPE Module] [SEMANTICS test,validation,run,dict,duration]
# @BRIEF Verify ValidationTaskService._run_to_dict computes duration_ms correctly,
# including timezone-naive datetime handling and edge cases.
# @RELATION BINDS_TO -> [ValidationTaskService]
# @TEST_EDGE: no_finished_at -> duration_ms is None
# @TEST_EDGE: no_started_at -> duration_ms is None
# @TEST_EDGE: naive_datetime -> Handled correctly (treated as UTC)
# @TEST_EDGE: aware_datetime -> Handled correctly
# @TEST_INVARIANT: duration_ms_computed -> VERIFIED_BY: test_duration_ms_with_aware_datetimes, test_duration_ms_with_naive_datetimes
import sys
from datetime import UTC, datetime, timezone, timedelta
from pathlib import Path
from unittest.mock import MagicMock
import pytest
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src"))
class MockRun:
"""Minimal mock matching ValidationRun ORM attributes."""
def __init__(self, **kwargs):
self.id = kwargs.get("id", "run-1")
self.policy_id = kwargs.get("policy_id", "policy-1")
self.task_id = kwargs.get("task_id", "task-1")
self.status = kwargs.get("status", "completed")
self.trigger = kwargs.get("trigger", "manual")
self.started_at = kwargs.get("started_at")
self.finished_at = kwargs.get("finished_at")
self.dashboard_count = kwargs.get("dashboard_count", 3)
self.pass_count = kwargs.get("pass_count", 2)
self.warn_count = kwargs.get("warn_count", 1)
self.fail_count = kwargs.get("fail_count", 0)
self.unknown_count = kwargs.get("unknown_count", 0)
self.created_at = kwargs.get("created_at", datetime(2025, 1, 15, tzinfo=UTC))
# #region TestRunToDictDuration [C:2] [TYPE Class]
# @BRIEF Verify _run_to_dict duration_ms computation.
class TestRunToDictDuration:
"""Verify _run_to_dict duration_ms field."""
# #region setup [C:1] [TYPE Function]
@pytest.fixture(autouse=True)
def setup_service(self):
from src.services.validation_service import ValidationTaskService
self.mock_db = MagicMock()
self.service = ValidationTaskService(db=self.mock_db)
self.run_to_dict = self.service._run_to_dict
# #endregion setup
# #region test_duration_ms_with_aware_datetimes [C:2] [TYPE Function]
# @BRIEF UTC-aware started_at/finished_at → correct duration_ms.
def test_duration_ms_with_aware_datetimes(self):
started = datetime(2025, 1, 15, 10, 0, 0, tzinfo=UTC)
finished = datetime(2025, 1, 15, 10, 0, 5, tzinfo=UTC) # 5 seconds
run = MockRun(started_at=started, finished_at=finished)
result = self.run_to_dict(run)
assert result["duration_ms"] == 5000
# #endregion test_duration_ms_with_aware_datetimes
# #region test_duration_ms_with_naive_datetimes [C:2] [TYPE Function]
# @BRIEF Naive (no timezone) datetimes → treated as UTC, correct duration.
def test_duration_ms_with_naive_datetimes(self):
started = datetime(2025, 1, 15, 10, 0, 0) # naive
finished = datetime(2025, 1, 15, 10, 2, 30) # naive, 2.5 minutes
run = MockRun(started_at=started, finished_at=finished)
result = self.run_to_dict(run)
assert result["duration_ms"] == 150000 # 2.5 * 60 * 1000
# #endregion test_duration_ms_with_naive_datetimes
# #region test_duration_ms_no_finished_at [C:2] [TYPE Function]
# @BRIEF When finished_at is None, duration_ms is None.
def test_duration_ms_no_finished_at(self):
started = datetime(2025, 1, 15, 10, 0, 0, tzinfo=UTC)
run = MockRun(started_at=started, finished_at=None)
result = self.run_to_dict(run)
assert result["duration_ms"] is None
# #endregion test_duration_ms_no_finished_at
# #region test_duration_ms_no_started_at [C:2] [TYPE Function]
# @BRIEF When started_at is None, duration_ms is None.
def test_duration_ms_no_started_at(self):
finished = datetime(2025, 1, 15, 10, 0, 0, tzinfo=UTC)
run = MockRun(started_at=None, finished_at=finished)
result = self.run_to_dict(run)
assert result["duration_ms"] is None
# #endregion test_duration_ms_no_started_at
# #region test_duration_ms_both_none [C:2] [TYPE Function]
# @BRIEF When both are None, duration_ms is None.
def test_duration_ms_both_none(self):
run = MockRun(started_at=None, finished_at=None)
result = self.run_to_dict(run)
assert result["duration_ms"] is None
# #endregion test_duration_ms_both_none
# #region test_duration_ms_mixed_aware_naive [C:2] [TYPE Function]
# @BRIEF One aware, one naive → still computes correctly (both treated as UTC).
def test_duration_ms_mixed_aware_naive(self):
started = datetime(2025, 1, 15, 10, 0, 0) # naive
finished = datetime(2025, 1, 15, 10, 0, 10, tzinfo=UTC) # aware
run = MockRun(started_at=started, finished_at=finished)
result = self.run_to_dict(run)
assert result["duration_ms"] == 10000
# #endregion test_duration_ms_mixed_aware_naive
# #region test_other_fields_present [C:2] [TYPE Function]
# @BRIEF _run_to_dict returns all expected fields.
def test_other_fields_present(self):
run = MockRun()
result = self.run_to_dict(run)
assert result["id"] == "run-1"
assert result["policy_id"] == "policy-1"
assert result["task_id"] == "task-1"
assert result["status"] == "completed"
assert result["trigger"] == "manual"
assert result["dashboard_count"] == 3
assert result["pass_count"] == 2
assert result["warn_count"] == 1
assert result["fail_count"] == 0
assert result["unknown_count"] == 0
# #endregion test_other_fields_present
# #region test_isoformat_on_dates [C:2] [TYPE Function]
# @BRIEF started_at/finished_at are ISO-formatted strings in output.
def test_isoformat_on_dates(self):
started = datetime(2025, 1, 15, 10, 0, 0, tzinfo=UTC)
run = MockRun(started_at=started, finished_at=started)
result = self.run_to_dict(run)
assert isinstance(result["started_at"], str)
assert isinstance(result["finished_at"], str)
assert "2025-01-15" in result["started_at"]
# #endregion test_isoformat_on_dates
# #endregion TestRunToDictDuration
# #endregion TestRunToDict