Files
ss-tools/backend/tests/plugins/test_update_run_status.py
root 632b730fff chore: migrate GRACE-Poly anchors to hierarchical dotted naming
Systematic rename of all semantic anchors (#region, [DEF], @RELATION)
across 1400+ files — backend Python, frontend Svelte/TS, specs, docs:
- Flat anchors become Namespace.Module.Entity
- @RELATION references updated to match new anchor paths
- Zero business logic changes
2026-07-22 11:48:15 +03:00

257 lines
11 KiB
Python

# #region Test.UpdateRunStatus.TestUpdateRunStatus [C:3] [TYPE Module] [SEMANTICS test,validation,run,status,counters]
# @BRIEF Verify _update_run_status computes counters correctly, sets status to 'completed',
# and handles edge cases (missing run, partial records, exception handling).
# @RELATION BINDS_TO -> [Plugin.LLMAnalysisPlugin]
# @TEST_EDGE: missing_run_id -> Returns immediately without DB query
# @TEST_EDGE: no_records_found -> Logs warning and returns
# @TEST_EDGE: partial_records -> Does NOT mark complete when records < dashboard_count
# @TEST_EDGE: exception_in_query -> Logs error, does not crash
# @TEST_INVARIANT: run_status_always_completed -> VERIFIED_BY: test_sets_status_completed_with_all_records
# @TEST_INVARIANT: counters_computed_from_records -> VERIFIED_BY: test_computed_counters_mixed_statuses, test_computed_counters_all_pass
# @TEST_INVARIANT: finished_at_set_on_completion -> VERIFIED_BY: test_sets_finished_at_utc
import sys
from datetime import UTC, datetime, timezone
from pathlib import Path
from unittest.mock import MagicMock, patch
import pytest
# Ensure src is importable (same pattern as test_llm_dashboard_validation_v2.py)
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src"))
from src.plugins.llm_analysis.plugin import _update_run_status
# Minimal mock models matching the ORM shape
class MockValidationRecord:
def __init__(self, status, run_id="run-1"):
self.status = status
self.run_id = run_id
class MockValidationRun:
def __init__(self, id="run-1", dashboard_count=3, status="running"):
self.id = id
self.dashboard_count = dashboard_count
self.status = status
self.finished_at = None
self.pass_count = 0
self.warn_count = 0
self.fail_count = 0
self.unknown_count = 0
def _make_mock_db(records=None, run=None):
"""Create a mock DB session that returns the given records and run."""
mock_db = MagicMock()
mock_query = MagicMock()
mock_db.query.return_value = mock_query
mock_query.filter.return_value = mock_query
mock_query.all.return_value = records or []
mock_query.first.return_value = run
return mock_db
# #region Test.UpdateRunStatus.TestUpdateRunStatusNoop [C:2] [TYPE Class]
# @BRIEF Verify _update_run_status early-exit conditions.
class TestUpdateRunStatusNoop:
"""Verify _update_run_status no-op paths."""
# #region Test.UpdateRunStatus.TestNoneRunIdReturnsEarly [C:2] [TYPE Function]
# @BRIEF When run_id is None, function returns without DB access.
def test_none_run_id_returns_early(self):
mock_db = MagicMock()
_update_run_status(mock_db, None)
mock_db.query.assert_not_called()
# #endregion Test.UpdateRunStatus.TestNoneRunIdReturnsEarly
# #region Test.UpdateRunStatus.TestEmptyRunIdReturnsEarly [C:2] [TYPE Function]
# @BRIEF When run_id is empty string, function returns without DB access.
def test_empty_run_id_returns_early(self):
mock_db = MagicMock()
_update_run_status(mock_db, "")
mock_db.query.assert_not_called()
# #endregion Test.UpdateRunStatus.TestEmptyRunIdReturnsEarly
# #region Test.UpdateRunStatus.TestNoRecordsLogsWarning [C:2] [TYPE Function]
# @BRIEF When no records exist for the run, logs warning and returns.
def test_no_records_logs_warning(self):
mock_db = _make_mock_db(records=[], run=MockValidationRun())
_update_run_status(mock_db, "run-1")
mock_db.commit.assert_not_called()
# #endregion Test.UpdateRunStatus.TestNoRecordsLogsWarning
# #region Test.UpdateRunStatus.TestNoRunFoundLogsWarning [C:2] [TYPE Function]
# @BRIEF When run record not found, logs warning and returns.
def test_no_run_found_logs_warning(self):
mock_db = _make_mock_db(records=[MockValidationRecord("PASS")], run=None)
_update_run_status(mock_db, "run-1")
mock_db.commit.assert_not_called()
# #endregion Test.UpdateRunStatus.TestNoRunFoundLogsWarning
# #endregion Test.UpdateRunStatus.TestUpdateRunStatusNoop
# #region Test.UpdateRunStatus.TestUpdateRunStatusPartial [C:2] [TYPE Class]
# @BRIEF Verify _update_run_status does NOT mark complete when records < dashboard_count.
class TestUpdateRunStatusPartial:
"""Verify partial completion guard."""
# #region Test.UpdateRunStatus.TestPartialRecordsNotMarkedComplete [C:2] [TYPE Function]
# @BRIEF When len(records) < dashboard_count, run stays 'running'.
def test_partial_records_not_marked_complete(self):
run = MockValidationRun(dashboard_count=3, status="running")
records = [MockValidationRecord("PASS"), MockValidationRecord("WARN")] # 2 < 3
mock_db = _make_mock_db(records=records, run=run)
_update_run_status(mock_db, "run-1")
assert run.status == "running" # NOT changed
mock_db.commit.assert_not_called()
# #endregion Test.UpdateRunStatus.TestPartialRecordsNotMarkedComplete
# #region Test.UpdateRunStatus.TestZeroDashboardCountSkipsGuard [C:2] [TYPE Function]
# @BRIEF When dashboard_count is 0 or None, the partial guard is skipped and run completes.
def test_zero_dashboard_count_skips_guard(self):
run = MockValidationRun(dashboard_count=0, status="running")
records = [MockValidationRecord("PASS")]
mock_db = _make_mock_db(records=records, run=run)
_update_run_status(mock_db, "run-1")
assert run.status == "completed"
mock_db.commit.assert_called_once()
# #endregion Test.UpdateRunStatus.TestZeroDashboardCountSkipsGuard
# #endregion Test.UpdateRunStatus.TestUpdateRunStatusPartial
# #region Test.UpdateRunStatus.TestUpdateRunStatusCompletion [C:2] [TYPE Class]
# @BRIEF Verify _update_run_status correctly computes counters and sets status.
class TestUpdateRunStatusCompletion:
"""Verify counter computation and status assignment."""
# #region Test.UpdateRunStatus.TestSetsStatusCompletedWithAllRecords [C:2] [TYPE Function]
# @BRIEF When all expected records exist, run.status is set to 'completed'.
def test_sets_status_completed_with_all_records(self):
run = MockValidationRun(dashboard_count=2, status="running")
records = [MockValidationRecord("PASS"), MockValidationRecord("FAIL")]
mock_db = _make_mock_db(records=records, run=run)
_update_run_status(mock_db, "run-1")
assert run.status == "completed"
# #endregion Test.UpdateRunStatus.TestSetsStatusCompletedWithAllRecords
# #region Test.UpdateRunStatus.TestComputedCountersAllPass [C:2] [TYPE Function]
# @BRIEF All PASS records → pass_count=2, others=0.
def test_computed_counters_all_pass(self):
run = MockValidationRun(dashboard_count=2)
records = [MockValidationRecord("PASS"), MockValidationRecord("PASS")]
mock_db = _make_mock_db(records=records, run=run)
_update_run_status(mock_db, "run-1")
assert run.pass_count == 2
assert run.warn_count == 0
assert run.fail_count == 0
assert run.unknown_count == 0
# #endregion Test.UpdateRunStatus.TestComputedCountersAllPass
# #region Test.UpdateRunStatus.TestComputedCountersMixedStatuses [C:2] [TYPE Function]
# @BRIEF Mixed statuses → correct per-status counters.
def test_computed_counters_mixed_statuses(self):
run = MockValidationRun(dashboard_count=4)
records = [
MockValidationRecord("PASS"),
MockValidationRecord("PASS"),
MockValidationRecord("WARN"),
MockValidationRecord("FAIL"),
]
mock_db = _make_mock_db(records=records, run=run)
_update_run_status(mock_db, "run-1")
assert run.pass_count == 2
assert run.warn_count == 1
assert run.fail_count == 1
assert run.unknown_count == 0
# #endregion Test.UpdateRunStatus.TestComputedCountersMixedStatuses
# #region Test.UpdateRunStatus.TestComputedCountersUnknownStatus [C:2] [TYPE Function]
# @BRIEF UNKNOWN status records are counted correctly.
def test_computed_counters_unknown_status(self):
run = MockValidationRun(dashboard_count=2)
records = [MockValidationRecord("UNKNOWN"), MockValidationRecord("PASS")]
mock_db = _make_mock_db(records=records, run=run)
_update_run_status(mock_db, "run-1")
assert run.unknown_count == 1
assert run.pass_count == 1
# #endregion Test.UpdateRunStatus.TestComputedCountersUnknownStatus
# #region Test.UpdateRunStatus.TestSetsFinishedAt [C:2] [TYPE Function]
# @BRIEF finished_at is set to a datetime on completion.
def test_sets_finished_at(self):
run = MockValidationRun(dashboard_count=1, status="running")
records = [MockValidationRecord("PASS")]
mock_db = _make_mock_db(records=records, run=run)
_update_run_status(mock_db, "run-1")
assert run.finished_at is not None
assert isinstance(run.finished_at, datetime)
# #endregion Test.UpdateRunStatus.TestSetsFinishedAt
# #region Test.UpdateRunStatus.TestDbExceptionLoggedNotRaised [C:2] [TYPE Function]
# @BRIEF DB exception during finalization is logged but not re-raised.
def test_db_exception_logged_not_raised(self):
mock_db = MagicMock()
mock_query = MagicMock()
mock_db.query.return_value = mock_query
mock_query.filter.return_value = mock_query
mock_query.all.side_effect = RuntimeError("DB connection lost")
# Should NOT raise
_update_run_status(mock_db, "run-1")
# #endregion Test.UpdateRunStatus.TestDbExceptionLoggedNotRaised
# #endregion Test.UpdateRunStatus.TestUpdateRunStatusCompletion
# #region Test.UpdateRunStatus.TestExecuteErrorHandling [C:2] [TYPE Class]
# @BRIEF Verify the always-finalize pattern used in execute().
class TestExecuteErrorHandling:
"""Verify _update_run_status is always callable after the loop."""
# #region Test.UpdateRunStatus.TestUpdateRunStatusCalledOnSuccess [C:2] [TYPE Function]
# @BRIEF _update_run_status is called after normal execution with all records.
def test_update_run_status_called_on_success(self):
run = MockValidationRun(dashboard_count=1, status="running")
records = [MockValidationRecord("PASS")]
mock_db = _make_mock_db(records=records, run=run)
_update_run_status(mock_db, "run-1")
mock_db.commit.assert_called_once()
assert run.status == "completed"
# #endregion Test.UpdateRunStatus.TestUpdateRunStatusCalledOnSuccess
# #region Test.UpdateRunStatus.TestUpdateRunStatusHandlesZeroRecords [C:2] [TYPE Function]
# @BRIEF When all dashboards failed (0 records), _update_run_status still runs
# and does not mark the run as complete (partial guard).
def test_update_run_status_handles_zero_records(self):
run = MockValidationRun(dashboard_count=2, status="running")
mock_db = _make_mock_db(records=[], run=run)
_update_run_status(mock_db, "run-1")
# With 0 records and dashboard_count=2, the partial guard prevents completion
assert run.status == "running"
mock_db.commit.assert_not_called()
# #endregion Test.UpdateRunStatus.TestUpdateRunStatusHandlesZeroRecords
# #endregion Test.UpdateRunStatus.TestExecuteErrorHandling
# #endregion Test.UpdateRunStatus.TestUpdateRunStatus