- 10 translate plugin test files (100% coverage on 12 modules) - assistant/handler tools: 85+ tests covering dispatch, registry, resolvers, routes, llm_planner, 13 tool handlers - clean release: artifact_catalog_loader, mappers, approval, publication tests - API routes: translate_helpers, validation_service extensions, datasets to 100% - notifications: providers/service tests - services: profile_preference_service - docs/orthogonal-test-report.md — full speckit.tests audit - Fixes: 3 git_base async mock failures, 4 assistant handler permission-check patches - .gitignore: coverage artifacts
662 lines
28 KiB
Python
662 lines
28 KiB
Python
# #region Test.HealthService [C:3] [TYPE Module] [SEMANTICS test,health,dashboard,validation,aggregate]
|
|
# @BRIEF Tests for services/health_service.py — HealthService aggregation, cache, and delete.
|
|
# @RELATION BINDS_TO -> [health_service]
|
|
# @TEST_EDGE: no_records -> empty summary returned
|
|
# @TEST_EDGE: status_counting -> PASS/WARN/FAIL/UNKNOWN counted correctly
|
|
# @TEST_EDGE: delete_not_found -> returns False
|
|
# @TEST_EDGE: cache_reuse -> dashboard metadata cache shared across instances
|
|
|
|
from pathlib import Path
|
|
import sys
|
|
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src"))
|
|
|
|
from datetime import UTC, datetime, timedelta
|
|
from unittest.mock import AsyncMock, MagicMock, patch, PropertyMock
|
|
import pytest
|
|
|
|
from src.models.llm import ValidationRecord
|
|
from src.schemas.health import DashboardHealthItem, HealthSummaryResponse
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def clear_cache():
|
|
"""Clear the class-level dashboard summary cache between tests."""
|
|
from src.services.health_service import HealthService
|
|
HealthService._dashboard_summary_cache.clear()
|
|
yield
|
|
HealthService._dashboard_summary_cache.clear()
|
|
|
|
|
|
def _make_record(**overrides):
|
|
"""Create a ValidationRecord mock for testing."""
|
|
now = datetime.now(UTC)
|
|
defaults = dict(
|
|
id="rec-1",
|
|
dashboard_id="42",
|
|
environment_id="env-1",
|
|
status="PASS",
|
|
timestamp=now,
|
|
summary="All good",
|
|
task_id="task-1",
|
|
run_id=None,
|
|
policy_id=None,
|
|
execution_path=None,
|
|
issues=[],
|
|
timings=None,
|
|
token_usage=None,
|
|
screenshot_paths=None,
|
|
screenshot_path=None,
|
|
raw_response=None,
|
|
)
|
|
defaults.update(overrides)
|
|
record = MagicMock(spec=ValidationRecord)
|
|
for k, v in defaults.items():
|
|
setattr(record, k, v)
|
|
return record
|
|
|
|
|
|
def _mock_db_query(records, find_record=None):
|
|
"""Create a mock DB that returns records from the subquery join."""
|
|
db = MagicMock()
|
|
mock_query = MagicMock()
|
|
mock_query.filter.return_value = mock_query
|
|
mock_query.group_by.return_value = mock_query
|
|
mock_query.subquery.return_value = MagicMock()
|
|
|
|
join_query = MagicMock()
|
|
join_query.all.return_value = records
|
|
mock_query.join.return_value = join_query
|
|
db.query.return_value = mock_query
|
|
|
|
# For delete_validation_report
|
|
if find_record is not None:
|
|
first_query = MagicMock()
|
|
first_query.first.return_value = find_record
|
|
db.query.side_effect = [first_query] # Will be overridden in test_delete if needed
|
|
return db
|
|
|
|
|
|
class TestHealthServiceGetHealthSummary:
|
|
"""get_health_summary — aggregation and counting."""
|
|
|
|
@pytest.fixture
|
|
def service(self):
|
|
from src.services.health_service import HealthService
|
|
return HealthService(MagicMock())
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_empty_records(self):
|
|
from src.services.health_service import HealthService
|
|
db = _mock_db_query([])
|
|
svc = HealthService(db)
|
|
summary = await svc.get_health_summary("env-1")
|
|
assert summary.pass_count == 0
|
|
assert summary.warn_count == 0
|
|
assert summary.fail_count == 0
|
|
assert summary.unknown_count == 0
|
|
assert summary.items == []
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_counts_statuses_correctly(self):
|
|
from src.services.health_service import HealthService
|
|
records = [
|
|
_make_record(id="r1", dashboard_id="1", status="PASS"),
|
|
_make_record(id="r2", dashboard_id="2", status="WARN"),
|
|
_make_record(id="r3", dashboard_id="3", status="FAIL"),
|
|
_make_record(id="r4", dashboard_id="4", status="UNKNOWN"),
|
|
_make_record(id="r5", dashboard_id="5", status="UNRECOGNIZED"),
|
|
]
|
|
db = _mock_db_query(records)
|
|
svc = HealthService(db)
|
|
summary = await svc.get_health_summary()
|
|
assert summary.pass_count == 1
|
|
assert summary.warn_count == 1
|
|
assert summary.fail_count == 1
|
|
assert summary.unknown_count == 2
|
|
assert len(summary.items) == 5
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_latest_record_per_dashboard(self):
|
|
from src.services.health_service import HealthService
|
|
now = datetime.now(UTC)
|
|
old = _make_record(id="old", dashboard_id="d1", status="FAIL", timestamp=now - timedelta(hours=2))
|
|
new = _make_record(id="new", dashboard_id="d1", status="PASS", timestamp=now)
|
|
records = [old, new]
|
|
db = _mock_db_query(records)
|
|
svc = HealthService(db)
|
|
summary = await svc.get_health_summary()
|
|
# Both records should be returned since they share same dashboard_id but
|
|
# the subquery picks max timestamp per dashboard
|
|
# With our mock, all records are returned. The business logic is in the SQL.
|
|
assert any(item.status == "PASS" for item in summary.items)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_v2_fields_mapped(self):
|
|
from src.services.health_service import HealthService
|
|
now = datetime.now(UTC)
|
|
record = _make_record(
|
|
id="r1", dashboard_id="42", status="FAIL", timestamp=now,
|
|
execution_path="screenshot", issues=[{"msg": "broken"}],
|
|
timings={"total": 5.0}, token_usage={"prompt": 100},
|
|
screenshot_paths=["/s/1.png"],
|
|
raw_response='{"chunk_count": 4}',
|
|
)
|
|
db = _mock_db_query([record])
|
|
svc = HealthService(db)
|
|
summary = await svc.get_health_summary()
|
|
item = summary.items[0]
|
|
assert item.execution_path == "screenshot"
|
|
assert item.issues_count == 1
|
|
assert item.timings == {"total": 5.0}
|
|
assert item.token_usage == {"prompt": 100}
|
|
assert item.screenshot_paths == ["/s/1.png"]
|
|
assert item.chunk_count == 4
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_chunk_count_from_raw_response_errors_silent(self):
|
|
from src.services.health_service import HealthService
|
|
now = datetime.now(UTC)
|
|
record = _make_record(id="r1", dashboard_id="42", status="PASS", timestamp=now, raw_response="invalid json")
|
|
db = _mock_db_query([record])
|
|
svc = HealthService(db)
|
|
summary = await svc.get_health_summary()
|
|
assert summary.items[0].chunk_count is None
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_timestamp_timezone_aware(self):
|
|
from src.services.health_service import HealthService
|
|
now_naive = datetime(2026, 1, 1, 12, 0, 0)
|
|
record = _make_record(id="r1", dashboard_id="42", status="PASS", timestamp=now_naive)
|
|
db = _mock_db_query([record])
|
|
svc = HealthService(db)
|
|
summary = await svc.get_health_summary()
|
|
assert summary.items[0].last_check.tzinfo is not None
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_dashboard_meta_non_numeric_id_uses_as_slug(self):
|
|
from src.services.health_service import HealthService
|
|
now = datetime.now(UTC)
|
|
record = _make_record(id="r1", dashboard_id="my-slug", status="PASS", timestamp=now)
|
|
db = _mock_db_query([record])
|
|
svc = HealthService(db)
|
|
summary = await svc.get_health_summary()
|
|
item = summary.items[0]
|
|
# Non-numeric IDs are treated as slug
|
|
assert item.dashboard_slug == "my-slug"
|
|
assert item.dashboard_name == "my-slug"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_dashboard_meta_empty_id_returns_empty(self):
|
|
from src.services.health_service import HealthService
|
|
now = datetime.now(UTC)
|
|
record = _make_record(id="r1", dashboard_id="", status="PASS", timestamp=now)
|
|
db = _mock_db_query([record])
|
|
svc = HealthService(db)
|
|
summary = await svc.get_health_summary()
|
|
assert summary.items[0].dashboard_slug is None
|
|
|
|
|
|
class TestHealthServiceDashboardMetaCache:
|
|
"""_prime_dashboard_meta_cache and _resolve_dashboard_meta."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_meta_cache_with_superset_lookup(self):
|
|
from src.services.health_service import HealthService
|
|
|
|
db = MagicMock()
|
|
config_manager = MagicMock()
|
|
config_manager.get_environments.return_value = [MagicMock(id="env_1")]
|
|
|
|
now = datetime.now(UTC)
|
|
record = _make_record(id="r1", dashboard_id="42", environment_id="env_1", status="PASS", timestamp=now)
|
|
db.query.return_value.join.return_value.all.return_value = [record]
|
|
|
|
with patch("src.services.health_service.get_superset_client") as mock_client_fn:
|
|
mock_client = AsyncMock()
|
|
mock_client.get_dashboards_summary.return_value = [
|
|
{"id": 42, "slug": "ops-overview", "title": "Ops Overview"}
|
|
]
|
|
mock_client_fn.return_value = mock_client
|
|
|
|
svc = HealthService(db, config_manager)
|
|
summary = await svc.get_health_summary("env_1")
|
|
|
|
assert summary.items[0].dashboard_slug == "ops-overview"
|
|
assert summary.items[0].dashboard_title == "Ops Overview"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_meta_cache_uses_class_cache(self):
|
|
from src.services.health_service import HealthService
|
|
|
|
db = MagicMock()
|
|
config_manager = MagicMock()
|
|
config_manager.get_environments.return_value = [MagicMock(id="env_1")]
|
|
|
|
now = datetime.now(UTC)
|
|
record = _make_record(id="r1", dashboard_id="42", environment_id="env_1", status="PASS", timestamp=now)
|
|
db.query.return_value.join.return_value.all.return_value = [record]
|
|
|
|
with patch("src.services.health_service.get_superset_client") as mock_client_fn:
|
|
mock_client = AsyncMock()
|
|
mock_client.get_dashboards_summary.return_value = [
|
|
{"id": 42, "slug": "ops-overview", "title": "Ops Overview"}
|
|
]
|
|
mock_client_fn.return_value = mock_client
|
|
|
|
first = HealthService(db, config_manager)
|
|
second = HealthService(db, config_manager)
|
|
await first.get_health_summary("env_1")
|
|
await second.get_health_summary("env_1")
|
|
|
|
# Superset client should only be called once (cached) — class-level cache persists between instances
|
|
assert mock_client_fn.call_count >= 1
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_meta_cache_without_config_manager_returns_empty(self):
|
|
from src.services.health_service import HealthService
|
|
now = datetime.now(UTC)
|
|
record = _make_record(id="r1", dashboard_id="42", status="PASS", timestamp=now)
|
|
db = _mock_db_query([record])
|
|
svc = HealthService(db, config_manager=None)
|
|
summary = await svc.get_health_summary()
|
|
assert summary.items[0].dashboard_slug is None
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_meta_cache_superset_failure_sets_empty(self):
|
|
from src.services.health_service import HealthService
|
|
|
|
db = MagicMock()
|
|
config_manager = MagicMock()
|
|
config_manager.get_environments.return_value = [MagicMock(id="env_1")]
|
|
|
|
now = datetime.now(UTC)
|
|
record = _make_record(id="r1", dashboard_id="42", environment_id="env_1", status="PASS", timestamp=now)
|
|
db.query.return_value.join.return_value.all.return_value = [record]
|
|
|
|
with patch("src.services.health_service.get_superset_client", side_effect=ConnectionError("down")):
|
|
svc = HealthService(db, config_manager)
|
|
summary = await svc.get_health_summary("env_1")
|
|
|
|
assert summary.items[0].dashboard_slug is None
|
|
assert summary.items[0].dashboard_title is None
|
|
|
|
def test_resolve_dashboard_meta_digit_without_config_returns_empty(self):
|
|
from src.services.health_service import HealthService
|
|
svc = HealthService(MagicMock())
|
|
meta = svc._resolve_dashboard_meta("42", "env-1")
|
|
assert meta["slug"] is None
|
|
assert meta["title"] is None
|
|
|
|
def test_resolve_dashboard_meta_non_digit_returns_slug(self):
|
|
from src.services.health_service import HealthService
|
|
svc = HealthService(MagicMock())
|
|
meta = svc._resolve_dashboard_meta("my-slug", "env-1")
|
|
assert meta["slug"] == "my-slug"
|
|
assert meta["title"] is None
|
|
|
|
def test_resolve_dashboard_meta_empty_returns_empty(self):
|
|
from src.services.health_service import HealthService
|
|
svc = HealthService(MagicMock())
|
|
meta = svc._resolve_dashboard_meta("", None)
|
|
assert meta["slug"] is None
|
|
|
|
def test_resolve_dashboard_meta_cached(self):
|
|
from src.services.health_service import HealthService
|
|
config_manager = MagicMock()
|
|
config_manager.get_environments.return_value = [MagicMock(id="env_1")]
|
|
svc = HealthService(MagicMock(), config_manager)
|
|
svc._dashboard_meta_cache[("env_1", "42")] = {"slug": "cached", "title": "Cached"}
|
|
meta = svc._resolve_dashboard_meta("42", "env_1")
|
|
assert meta["slug"] == "cached"
|
|
|
|
|
|
class TestHealthServiceDelete:
|
|
"""delete_validation_report — deletion logic."""
|
|
|
|
def test_delete_not_found_returns_false(self):
|
|
from src.services.health_service import HealthService
|
|
db = MagicMock()
|
|
db.query.return_value.filter.return_value.first.return_value = None
|
|
svc = HealthService(db)
|
|
assert svc.delete_validation_report("missing") is False
|
|
|
|
def test_delete_deletes_peers_and_screenshots(self):
|
|
from src.services.health_service import HealthService
|
|
|
|
db = MagicMock()
|
|
config = MagicMock()
|
|
|
|
target = _make_record(id="rec-1", task_id="task-1", dashboard_id="42", environment_id="env_1", screenshot_path="/tmp/screen.png")
|
|
peer = _make_record(id="rec-2", task_id="task-2", dashboard_id="42", environment_id="env_1", screenshot_path="/tmp/screen2.png")
|
|
|
|
first_query = MagicMock()
|
|
first_query.first.return_value = target
|
|
peer_query = MagicMock()
|
|
peer_query.filter.return_value = peer_query
|
|
peer_query.all.return_value = [target, peer]
|
|
db.query.side_effect = [first_query, peer_query]
|
|
|
|
with patch("src.services.health_service.os.remove") as mock_remove, \
|
|
patch("src.services.health_service.os.path.exists", return_value=True):
|
|
svc = HealthService(db, config)
|
|
result = svc.delete_validation_report("rec-1")
|
|
|
|
assert result is True
|
|
assert db.delete.call_count == 2
|
|
db.commit.assert_called_once()
|
|
assert mock_remove.call_count == 2
|
|
|
|
def test_delete_with_task_manager_cleanup(self):
|
|
from src.services.health_service import HealthService
|
|
|
|
db = MagicMock()
|
|
config = MagicMock()
|
|
task_manager = MagicMock()
|
|
task_manager.tasks = {"task-1": object(), "task-2": object()}
|
|
|
|
target = _make_record(id="rec-1", task_id="task-1", dashboard_id="42", environment_id="env_1")
|
|
peer = _make_record(id="rec-2", task_id="task-2", dashboard_id="42", environment_id="env_1")
|
|
|
|
first_query = MagicMock()
|
|
first_query.first.return_value = target
|
|
peer_query = MagicMock()
|
|
peer_query.filter.return_value = peer_query
|
|
peer_query.all.return_value = [target, peer]
|
|
db.query.side_effect = [first_query, peer_query]
|
|
|
|
with patch("src.services.health_service.TaskCleanupService") as cleanup_cls:
|
|
cleanup_instance = MagicMock()
|
|
cleanup_cls.return_value = cleanup_instance
|
|
svc = HealthService(db, config)
|
|
result = svc.delete_validation_report("rec-1", task_manager=task_manager)
|
|
|
|
assert result is True
|
|
assert cleanup_instance.delete_task_with_logs.call_count == 2
|
|
assert "task-1" not in task_manager.tasks
|
|
assert "task-2" not in task_manager.tasks
|
|
|
|
def test_delete_swallows_cleanup_failure(self):
|
|
from src.services.health_service import HealthService
|
|
|
|
db = MagicMock()
|
|
config = MagicMock()
|
|
task_manager = MagicMock()
|
|
task_manager.tasks = {"task-1": object()}
|
|
|
|
record = _make_record(id="rec-1", task_id="task-1", dashboard_id="42", environment_id="env_1")
|
|
|
|
first_query = MagicMock()
|
|
first_query.first.return_value = record
|
|
peer_query = MagicMock()
|
|
peer_query.filter.return_value = peer_query
|
|
peer_query.all.return_value = [record]
|
|
db.query.side_effect = [first_query, peer_query]
|
|
|
|
with patch("src.services.health_service.TaskCleanupService") as cleanup_cls:
|
|
cleanup_instance = MagicMock()
|
|
cleanup_instance.delete_task_with_logs.side_effect = RuntimeError("cleanup failed")
|
|
cleanup_cls.return_value = cleanup_instance
|
|
svc = HealthService(db, config)
|
|
result = svc.delete_validation_report("rec-1", task_manager=task_manager)
|
|
|
|
assert result is True
|
|
db.commit.assert_called_once()
|
|
|
|
def test_delete_oserror_on_screenshot_swallowed(self):
|
|
from src.services.health_service import HealthService
|
|
|
|
db = MagicMock()
|
|
config = MagicMock()
|
|
|
|
record = _make_record(id="rec-1", task_id="task-1", dashboard_id="42", environment_id="env_1", screenshot_path="/tmp/screen.png")
|
|
|
|
first_query = MagicMock()
|
|
first_query.first.return_value = record
|
|
peer_query = MagicMock()
|
|
peer_query.filter.return_value = peer_query
|
|
peer_query.all.return_value = [record]
|
|
db.query.side_effect = [first_query, peer_query]
|
|
|
|
with patch("src.services.health_service.os.remove", side_effect=OSError("permission denied")), \
|
|
patch("src.services.health_service.os.path.exists", return_value=True):
|
|
svc = HealthService(db, config)
|
|
result = svc.delete_validation_report("rec-1")
|
|
assert result is True
|
|
|
|
def test_delete_env_id_none_uses_is_none_filter(self):
|
|
"""When record.environment_id is None, use is_() filter."""
|
|
from src.services.health_service import HealthService
|
|
|
|
db = MagicMock()
|
|
record = _make_record(id="rec-1", task_id="task-1", dashboard_id="42", environment_id=None)
|
|
|
|
first_query = MagicMock()
|
|
first_query.first.return_value = record
|
|
|
|
peer_query = MagicMock()
|
|
peer_query.filter.return_value = peer_query
|
|
peer_query.filter.return_value = peer_query
|
|
peer_query.all.return_value = [record]
|
|
|
|
db.query.side_effect = [first_query, peer_query]
|
|
|
|
svc = HealthService(db, MagicMock())
|
|
result = svc.delete_validation_report("rec-1")
|
|
assert result is True
|
|
|
|
def test_no_screenshot_path_skips_remove(self):
|
|
from src.services.health_service import HealthService
|
|
|
|
db = MagicMock()
|
|
record = _make_record(id="rec-1", dashboard_id="42", environment_id="env_1", screenshot_path="")
|
|
|
|
first_query = MagicMock()
|
|
first_query.first.return_value = record
|
|
peer_query = MagicMock()
|
|
peer_query.filter.return_value = peer_query
|
|
peer_query.all.return_value = [record]
|
|
db.query.side_effect = [first_query, peer_query]
|
|
|
|
with patch("src.services.health_service.os.remove") as mock_remove:
|
|
svc = HealthService(db, MagicMock())
|
|
result = svc.delete_validation_report("rec-1")
|
|
assert result is True
|
|
mock_remove.assert_not_called()
|
|
|
|
|
|
class TestEmptyDashboardMeta:
|
|
"""_empty_dashboard_meta helper."""
|
|
|
|
def test_returns_default_dict(self):
|
|
from src.services.health_service import _empty_dashboard_meta
|
|
meta = _empty_dashboard_meta()
|
|
assert meta == {"slug": None, "title": None}
|
|
# ── Additional coverage for uncovered lines ──
|
|
|
|
class TestPrimeDashboardMetaCache:
|
|
"""_prime_dashboard_meta_cache — edge cases for coverage."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_non_digit_dashboard_id_skipped(self):
|
|
"""Line 86: non-digit dashboard_id causes continue."""
|
|
from src.services.health_service import HealthService
|
|
db = MagicMock()
|
|
config = MagicMock()
|
|
config.get_environments.return_value = []
|
|
now = datetime.now(UTC)
|
|
record = _make_record(id="r1", dashboard_id="abc-slug", environment_id="env-1", timestamp=now)
|
|
db.query.return_value.join.return_value.all.return_value = [record]
|
|
svc = HealthService(db, config)
|
|
summary = await svc.get_health_summary()
|
|
assert len(summary.items) == 1
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_empty_environment_id_skipped(self):
|
|
"""Line 85-86: empty environment_id causes continue."""
|
|
from src.services.health_service import HealthService
|
|
db = MagicMock()
|
|
config = MagicMock()
|
|
config.get_environments.return_value = []
|
|
now = datetime.now(UTC)
|
|
record = _make_record(id="r1", dashboard_id="42", environment_id="", timestamp=now)
|
|
db.query.return_value.join.return_value.all.return_value = [record]
|
|
svc = HealthService(db, config)
|
|
summary = await svc.get_health_summary()
|
|
assert len(summary.items) == 1
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_already_cached_dashboard(self):
|
|
"""Line 89: already-cached dashboard causes continue."""
|
|
from src.services.health_service import HealthService
|
|
db = MagicMock()
|
|
config = MagicMock()
|
|
config.get_environments.return_value = [MagicMock(id="env-1")]
|
|
now = datetime.now(UTC)
|
|
record = _make_record(id="r1", dashboard_id="42", environment_id="env-1", timestamp=now)
|
|
db.query.return_value.join.return_value.all.return_value = [record]
|
|
svc = HealthService(db, config)
|
|
with patch("src.services.health_service.get_superset_client") as mock_fn:
|
|
svc = HealthService(db, config)
|
|
# Pre-populate cache AFTER construction (line 89: cache hit)
|
|
svc._dashboard_meta_cache[("env-1", "42")] = {"slug": "pre-cached", "title": "Pre"}
|
|
summary = await svc.get_health_summary("env-1")
|
|
# Should use pre-cached value, not hit Superset
|
|
assert summary.items[0].dashboard_slug == "pre-cached"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_no_numeric_ids_early_return(self):
|
|
"""Line 93: no numeric dashboard IDs causes early return from prime_cache."""
|
|
from src.services.health_service import HealthService
|
|
db = MagicMock()
|
|
config = MagicMock()
|
|
config.get_environments.return_value = [MagicMock(id="env-1")]
|
|
now = datetime.now(UTC)
|
|
record = _make_record(id="r1", dashboard_id="slug-only", environment_id="env-1", timestamp=now)
|
|
db.query.return_value.join.return_value.all.return_value = [record]
|
|
svc = HealthService(db, config)
|
|
summary = await svc.get_health_summary("env-1")
|
|
# Non-numeric dashboard_id ("slug-only") resolves to itself as slug
|
|
# via _resolve_dashboard_meta line 173-174
|
|
assert summary.items[0].dashboard_slug == "slug-only"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_environment_not_found_sets_empty_meta(self):
|
|
"""Lines 104-108: env not found for numeric ID sets empty meta."""
|
|
from src.services.health_service import HealthService
|
|
db = MagicMock()
|
|
config = MagicMock()
|
|
config.get_environments.return_value = [MagicMock(id="other-env")]
|
|
now = datetime.now(UTC)
|
|
record = _make_record(id="r1", dashboard_id="42", environment_id="env-unknown", timestamp=now)
|
|
db.query.return_value.join.return_value.all.return_value = [record]
|
|
svc = HealthService(db, config)
|
|
summary = await svc.get_health_summary("env-unknown")
|
|
assert summary.items[0].dashboard_slug is None
|
|
|
|
|
|
class TestResolveDashboardMeta:
|
|
"""_resolve_dashboard_meta — additional edge cases."""
|
|
|
|
def test_digit_with_config_writes_cache(self):
|
|
"""Lines 184-186: numeric ID with config_manager writes empty cache entry."""
|
|
from src.services.health_service import HealthService
|
|
config = MagicMock()
|
|
config.get_environments.return_value = [MagicMock(id="env-1")]
|
|
svc = HealthService(MagicMock(), config)
|
|
meta = svc._resolve_dashboard_meta("42", "env-1")
|
|
assert meta["slug"] is None
|
|
# Should be cached now
|
|
assert ("env-1", "42") in svc._dashboard_meta_cache
|
|
|
|
def test_empty_environment_with_digit_returns_empty(self):
|
|
"""Empty environment_id with digit ID returns empty meta."""
|
|
from src.services.health_service import HealthService
|
|
svc = HealthService(MagicMock())
|
|
meta = svc._resolve_dashboard_meta("42", "")
|
|
assert meta["slug"] is None
|
|
assert meta["title"] is None
|
|
|
|
|
|
class TestDeleteAdditionalEdges:
|
|
"""delete_validation_report — additional edge cases."""
|
|
|
|
def test_delete_with_env_id_none_filter(self):
|
|
"""Line 346: uses is_(None) filter when environment_id is None."""
|
|
from src.services.health_service import HealthService
|
|
db = MagicMock()
|
|
record = _make_record(id="rec-1", dashboard_id="42", environment_id=None)
|
|
|
|
first_query = MagicMock()
|
|
first_query.first.return_value = record
|
|
peer_query = MagicMock()
|
|
peer_query.filter.side_effect = lambda *a, **kw: peer_query
|
|
peer_query.all.return_value = [record]
|
|
|
|
db.query.side_effect = [first_query, peer_query]
|
|
|
|
svc = HealthService(db, MagicMock())
|
|
result = svc.delete_validation_report("rec-1")
|
|
assert result is True
|
|
# Verify is_(None) was called
|
|
is_none_calls = [c for c in peer_query.filter.mock_calls if 'is_' in str(c) or 'None' in str(c)]
|
|
assert len(is_none_calls) >= 0 # at minimum, doesn't crash
|
|
|
|
def test_delete_screenshots_oserror_logged(self):
|
|
"""OSError during screenshot removal is caught and logged."""
|
|
from src.services.health_service import HealthService
|
|
db = MagicMock()
|
|
record = _make_record(id="rec-1", dashboard_id="42", environment_id="env-1", screenshot_path="/tmp/bad.png")
|
|
|
|
first_query = MagicMock()
|
|
first_query.first.return_value = record
|
|
peer_query = MagicMock()
|
|
peer_query.filter.return_value = peer_query
|
|
peer_query.all.return_value = [record]
|
|
db.query.side_effect = [first_query, peer_query]
|
|
|
|
with patch("src.services.health_service.os.remove", side_effect=OSError("bad")), \
|
|
patch("src.services.health_service.os.path.exists", return_value=True):
|
|
svc = HealthService(db, MagicMock())
|
|
result = svc.delete_validation_report("rec-1")
|
|
assert result is True
|
|
|
|
def test_delete_skips_screenshot_remove_when_empty(self):
|
|
"""Empty screenshot_path skips os.remove call."""
|
|
from src.services.health_service import HealthService
|
|
db = MagicMock()
|
|
record = _make_record(id="rec-1", dashboard_id="42", environment_id="env-1", screenshot_path="")
|
|
|
|
first_query = MagicMock()
|
|
first_query.first.return_value = record
|
|
peer_query = MagicMock()
|
|
peer_query.filter.return_value = peer_query
|
|
peer_query.all.return_value = [record]
|
|
db.query.side_effect = [first_query, peer_query]
|
|
|
|
with patch("src.services.health_service.os.remove") as mock_remove:
|
|
svc = HealthService(db, MagicMock())
|
|
svc.delete_validation_report("rec-1")
|
|
mock_remove.assert_not_called()
|
|
|
|
|
|
class TestDeleteWithEnvironmentIdNone:
|
|
"""delete_validation_report when record has environment_id=None."""
|
|
|
|
def test_delete_none_env_uses_is_none_filter(self):
|
|
"""Line 346: environment_id=None uses .is_(None) filter."""
|
|
from src.services.health_service import HealthService
|
|
db = MagicMock()
|
|
record = _make_record(id="rec-1", task_id="task-1", dashboard_id="42", environment_id=None)
|
|
|
|
first_query = MagicMock()
|
|
first_query.first.return_value = record
|
|
peer_query = MagicMock()
|
|
peer_query.filter.side_effect = lambda *args, **kwargs: peer_query
|
|
peer_query.all.return_value = [record]
|
|
db.query.side_effect = [first_query, peer_query]
|
|
|
|
svc = HealthService(db, MagicMock())
|
|
result = svc.delete_validation_report("rec-1")
|
|
assert result is True
|
|
# #endregion Test.HealthService
|