{ "verdict": "APPROVED", "rejection_reason": "NONE", "audit_details": { "target_invoked": true, "pre_conditions_tested": true, "post_conditions_tested": true, "test_data_used": true }, "feedback": "Both test files have successfully passed the audit. The 'task_log_viewer.test.js' suite now correctly imports and mounts the real Svelte component using Test Library, fully eliminating the logic mirror/tautology issue. The 'test_logger.py' suite now properly implements negative tests for the @PRE constraint in 'belief_scope' and fully verifies all @POST effects triggered by 'configure_logger'." }
This commit is contained in:
181
backend/src/services/reports/__tests__/test_report_service.py
Normal file
181
backend/src/services/reports/__tests__/test_report_service.py
Normal file
@@ -0,0 +1,181 @@
|
||||
# [DEF:test_report_service:Module]
|
||||
# @TIER: CRITICAL
|
||||
# @PURPOSE: Unit tests for ReportsService list/detail operations
|
||||
# @LAYER: Domain
|
||||
# @RELATION: TESTS -> backend.src.services.reports.report_service.ReportsService
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
|
||||
|
||||
import pytest
|
||||
from unittest.mock import MagicMock, patch
|
||||
from datetime import datetime, timezone, timedelta
|
||||
|
||||
|
||||
def _make_task(task_id="task-1", plugin_id="superset-backup", status_value="SUCCESS",
|
||||
started_at=None, finished_at=None, result=None, params=None, logs=None):
|
||||
"""Create a mock Task object matching the Task model interface."""
|
||||
from src.core.task_manager.models import Task, TaskStatus
|
||||
task = Task(plugin_id=plugin_id, params=params or {})
|
||||
task.id = task_id
|
||||
task.status = TaskStatus(status_value)
|
||||
task.started_at = started_at or datetime(2024, 1, 15, 10, 0, 0)
|
||||
task.finished_at = finished_at or datetime(2024, 1, 15, 10, 5, 0)
|
||||
task.result = result
|
||||
if logs is not None:
|
||||
task.logs = logs
|
||||
return task
|
||||
|
||||
|
||||
class TestReportsServiceList:
|
||||
"""Tests for ReportsService.list_reports."""
|
||||
|
||||
def _make_service(self, tasks):
|
||||
from src.services.reports.report_service import ReportsService
|
||||
mock_tm = MagicMock()
|
||||
mock_tm.get_all_tasks.return_value = tasks
|
||||
return ReportsService(task_manager=mock_tm)
|
||||
|
||||
def test_empty_tasks_returns_empty_collection(self):
|
||||
from src.models.report import ReportQuery
|
||||
svc = self._make_service([])
|
||||
result = svc.list_reports(ReportQuery())
|
||||
assert result.total == 0
|
||||
assert result.items == []
|
||||
assert result.has_next is False
|
||||
|
||||
def test_single_task_normalized(self):
|
||||
from src.models.report import ReportQuery
|
||||
task = _make_task(result={"summary": "Backup completed"})
|
||||
svc = self._make_service([task])
|
||||
result = svc.list_reports(ReportQuery())
|
||||
assert result.total == 1
|
||||
assert result.items[0].task_id == "task-1"
|
||||
assert result.items[0].summary == "Backup completed"
|
||||
|
||||
def test_pagination_first_page(self):
|
||||
from src.models.report import ReportQuery
|
||||
tasks = [
|
||||
_make_task(task_id=f"task-{i}",
|
||||
finished_at=datetime(2024, 1, 15, 10, i, 0))
|
||||
for i in range(5)
|
||||
]
|
||||
svc = self._make_service(tasks)
|
||||
result = svc.list_reports(ReportQuery(page=1, page_size=2))
|
||||
assert len(result.items) == 2
|
||||
assert result.total == 5
|
||||
assert result.has_next is True
|
||||
|
||||
def test_pagination_last_page(self):
|
||||
from src.models.report import ReportQuery
|
||||
tasks = [
|
||||
_make_task(task_id=f"task-{i}",
|
||||
finished_at=datetime(2024, 1, 15, 10, i, 0))
|
||||
for i in range(5)
|
||||
]
|
||||
svc = self._make_service(tasks)
|
||||
result = svc.list_reports(ReportQuery(page=3, page_size=2))
|
||||
assert len(result.items) == 1
|
||||
assert result.has_next is False
|
||||
|
||||
def test_filter_by_status(self):
|
||||
from src.models.report import ReportQuery, ReportStatus
|
||||
tasks = [
|
||||
_make_task(task_id="ok", status_value="SUCCESS"),
|
||||
_make_task(task_id="fail", status_value="FAILED"),
|
||||
]
|
||||
svc = self._make_service(tasks)
|
||||
result = svc.list_reports(ReportQuery(statuses=[ReportStatus.SUCCESS]))
|
||||
assert result.total == 1
|
||||
assert result.items[0].task_id == "ok"
|
||||
|
||||
def test_filter_by_task_type(self):
|
||||
from src.models.report import ReportQuery, TaskType
|
||||
tasks = [
|
||||
_make_task(task_id="backup", plugin_id="superset-backup"),
|
||||
_make_task(task_id="migrate", plugin_id="superset-migration"),
|
||||
]
|
||||
svc = self._make_service(tasks)
|
||||
result = svc.list_reports(ReportQuery(task_types=[TaskType.BACKUP]))
|
||||
assert result.total == 1
|
||||
assert result.items[0].task_id == "backup"
|
||||
|
||||
def test_search_filter(self):
|
||||
from src.models.report import ReportQuery
|
||||
tasks = [
|
||||
_make_task(task_id="t1", plugin_id="superset-migration",
|
||||
result={"summary": "Migration complete"}),
|
||||
_make_task(task_id="t2", plugin_id="documentation",
|
||||
result={"summary": "Docs generated"}),
|
||||
]
|
||||
svc = self._make_service(tasks)
|
||||
result = svc.list_reports(ReportQuery(search="migration"))
|
||||
assert result.total == 1
|
||||
assert result.items[0].task_id == "t1"
|
||||
|
||||
def test_sort_by_status(self):
|
||||
from src.models.report import ReportQuery
|
||||
tasks = [
|
||||
_make_task(task_id="t1", status_value="SUCCESS"),
|
||||
_make_task(task_id="t2", status_value="FAILED"),
|
||||
]
|
||||
svc = self._make_service(tasks)
|
||||
result = svc.list_reports(ReportQuery(sort_by="status", sort_order="asc"))
|
||||
statuses = [item.status.value for item in result.items]
|
||||
assert statuses == sorted(statuses)
|
||||
|
||||
def test_applied_filters_echoed(self):
|
||||
from src.models.report import ReportQuery
|
||||
query = ReportQuery(page=2, page_size=5)
|
||||
svc = self._make_service([])
|
||||
result = svc.list_reports(query)
|
||||
assert result.applied_filters.page == 2
|
||||
assert result.applied_filters.page_size == 5
|
||||
|
||||
|
||||
class TestReportsServiceDetail:
|
||||
"""Tests for ReportsService.get_report_detail."""
|
||||
|
||||
def _make_service(self, tasks):
|
||||
from src.services.reports.report_service import ReportsService
|
||||
mock_tm = MagicMock()
|
||||
mock_tm.get_all_tasks.return_value = tasks
|
||||
return ReportsService(task_manager=mock_tm)
|
||||
|
||||
def test_detail_found(self):
|
||||
task = _make_task(task_id="detail-task", result={"summary": "Done"})
|
||||
svc = self._make_service([task])
|
||||
detail = svc.get_report_detail("detail-task")
|
||||
assert detail is not None
|
||||
assert detail.report.task_id == "detail-task"
|
||||
|
||||
def test_detail_not_found(self):
|
||||
svc = self._make_service([])
|
||||
detail = svc.get_report_detail("nonexistent")
|
||||
assert detail is None
|
||||
|
||||
def test_detail_includes_timeline(self):
|
||||
task = _make_task(task_id="tl-task",
|
||||
started_at=datetime(2024, 1, 15, 10, 0, 0),
|
||||
finished_at=datetime(2024, 1, 15, 10, 5, 0))
|
||||
svc = self._make_service([task])
|
||||
detail = svc.get_report_detail("tl-task")
|
||||
events = [e["event"] for e in detail.timeline]
|
||||
assert "started" in events
|
||||
assert "updated" in events
|
||||
|
||||
def test_detail_failed_task_has_next_actions(self):
|
||||
task = _make_task(task_id="fail-task", status_value="FAILED")
|
||||
svc = self._make_service([task])
|
||||
detail = svc.get_report_detail("fail-task")
|
||||
assert len(detail.next_actions) > 0
|
||||
|
||||
def test_detail_success_task_no_error_next_actions(self):
|
||||
task = _make_task(task_id="ok-task", status_value="SUCCESS",
|
||||
result={"summary": "All good"})
|
||||
svc = self._make_service([task])
|
||||
detail = svc.get_report_detail("ok-task")
|
||||
assert detail.next_actions == []
|
||||
|
||||
# [/DEF:test_report_service:Module]
|
||||
Reference in New Issue
Block a user