semantic update
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
# [DEF:backend.src.services.__tests__.test_resource_service:Module]
|
||||
# @TIER: STANDARD
|
||||
# @SEMANTICS: resource-service, tests, dashboards, datasets, activity
|
||||
# @PURPOSE: Unit tests for ResourceService
|
||||
# @LAYER: Service
|
||||
# @RELATION: TESTS -> backend.src.services.resource_service
|
||||
# @RELATION: VERIFIES -> ResourceService
|
||||
# @INVARIANT: Resource summaries preserve task linkage and status projection behavior.
|
||||
|
||||
import pytest
|
||||
from unittest.mock import MagicMock, patch, AsyncMock
|
||||
@@ -11,6 +13,7 @@ from datetime import datetime
|
||||
|
||||
|
||||
# [DEF:test_get_dashboards_with_status:Function]
|
||||
# @PURPOSE: Validate dashboard enrichment includes git/task status projections.
|
||||
# @TEST: get_dashboards_with_status returns dashboards with git and task status
|
||||
# @PRE: SupersetClient returns dashboard list
|
||||
# @POST: Each dashboard has git_status and last_task fields
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
# [DEF:backend.tests.test_report_normalizer:Module]
|
||||
# @TIER: CRITICAL
|
||||
# @SEMANTICS: tests, reports, normalizer, fallback
|
||||
# @PURPOSE: Validate unknown task type fallback and partial payload normalization behavior.
|
||||
# @LAYER: Domain (Tests)
|
||||
# @RELATION: TESTS -> backend.src.services.reports.normalizer
|
||||
# @INVARIANT: Unknown plugin types are mapped to canonical unknown task type.
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from src.core.task_manager.models import Task, TaskStatus
|
||||
from src.services.reports.normalizer import normalize_task_report
|
||||
|
||||
|
||||
def test_unknown_type_maps_to_unknown_profile():
|
||||
task = Task(
|
||||
id="unknown-1",
|
||||
plugin_id="custom-unmapped-plugin",
|
||||
status=TaskStatus.FAILED,
|
||||
started_at=datetime.utcnow(),
|
||||
finished_at=datetime.utcnow(),
|
||||
params={},
|
||||
result={"error_message": "Unexpected plugin payload"},
|
||||
)
|
||||
|
||||
report = normalize_task_report(task)
|
||||
|
||||
assert report.task_type.value == "unknown"
|
||||
assert report.summary
|
||||
assert report.error_context is not None
|
||||
|
||||
|
||||
def test_partial_payload_keeps_report_visible_with_placeholders():
|
||||
task = Task(
|
||||
id="partial-1",
|
||||
plugin_id="superset-backup",
|
||||
status=TaskStatus.SUCCESS,
|
||||
started_at=datetime.utcnow(),
|
||||
finished_at=datetime.utcnow(),
|
||||
params={},
|
||||
result=None,
|
||||
)
|
||||
|
||||
report = normalize_task_report(task)
|
||||
|
||||
assert report.task_type.value == "backup"
|
||||
assert report.details is not None
|
||||
assert "result" in report.details
|
||||
|
||||
|
||||
# [/DEF:backend.tests.test_report_normalizer:Module]
|
||||
@@ -9,7 +9,7 @@
|
||||
# @INVARIANT: List responses are deterministic and include applied filter echo metadata.
|
||||
|
||||
# [SECTION: IMPORTS]
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
from typing import List, Optional
|
||||
|
||||
from ...core.task_manager import TaskManager
|
||||
@@ -23,9 +23,14 @@ from .normalizer import normalize_task_report
|
||||
# @TIER: CRITICAL
|
||||
# @PRE: TaskManager dependency is initialized.
|
||||
# @POST: Provides deterministic list/detail report responses.
|
||||
# @INVARIANT: Service methods are read-only over task history source.
|
||||
class ReportsService:
|
||||
# [DEF:__init__:Function]
|
||||
# @TIER: CRITICAL
|
||||
# @PURPOSE: Initialize service with TaskManager dependency.
|
||||
# @PRE: task_manager is a live TaskManager instance.
|
||||
# @POST: self.task_manager is assigned and ready for read operations.
|
||||
# @INVARIANT: Constructor performs no task mutations.
|
||||
# @PARAM: task_manager (TaskManager) - Task manager providing source task history.
|
||||
def __init__(self, task_manager: TaskManager):
|
||||
self.task_manager = task_manager
|
||||
@@ -33,6 +38,9 @@ class ReportsService:
|
||||
|
||||
# [DEF:_load_normalized_reports:Function]
|
||||
# @PURPOSE: Build normalized reports from all available tasks.
|
||||
# @PRE: Task manager returns iterable task history records.
|
||||
# @POST: Returns normalized report list preserving source cardinality.
|
||||
# @INVARIANT: Every returned item is a TaskReport.
|
||||
# @RETURN: List[TaskReport] - Reports sorted later by list logic.
|
||||
def _load_normalized_reports(self) -> List[TaskReport]:
|
||||
tasks = self.task_manager.get_all_tasks()
|
||||
@@ -40,8 +48,40 @@ class ReportsService:
|
||||
return reports
|
||||
# [/DEF:_load_normalized_reports:Function]
|
||||
|
||||
# [DEF:_to_utc_datetime:Function]
|
||||
# @PURPOSE: Normalize naive/aware datetime values to UTC-aware datetime for safe comparisons.
|
||||
# @PRE: value is either datetime or None.
|
||||
# @POST: Returns UTC-aware datetime or None.
|
||||
# @INVARIANT: Naive datetimes are interpreted as UTC to preserve deterministic ordering/filtering.
|
||||
# @PARAM: value (Optional[datetime]) - Source datetime value.
|
||||
# @RETURN: Optional[datetime] - UTC-aware datetime or None.
|
||||
def _to_utc_datetime(self, value: Optional[datetime]) -> Optional[datetime]:
|
||||
if value is None:
|
||||
return None
|
||||
if value.tzinfo is None:
|
||||
return value.replace(tzinfo=timezone.utc)
|
||||
return value.astimezone(timezone.utc)
|
||||
# [/DEF:_to_utc_datetime:Function]
|
||||
|
||||
# [DEF:_datetime_sort_key:Function]
|
||||
# @PURPOSE: Produce stable numeric sort key for report timestamps.
|
||||
# @PRE: report contains updated_at datetime.
|
||||
# @POST: Returns float timestamp suitable for deterministic sorting.
|
||||
# @INVARIANT: Mixed naive/aware datetimes never raise TypeError.
|
||||
# @PARAM: report (TaskReport) - Report item.
|
||||
# @RETURN: float - UTC timestamp key.
|
||||
def _datetime_sort_key(self, report: TaskReport) -> float:
|
||||
updated = self._to_utc_datetime(report.updated_at)
|
||||
if updated is None:
|
||||
return 0.0
|
||||
return updated.timestamp()
|
||||
# [/DEF:_datetime_sort_key:Function]
|
||||
|
||||
# [DEF:_matches_query:Function]
|
||||
# @PURPOSE: Apply query filtering to a report.
|
||||
# @PRE: report and query are normalized schema instances.
|
||||
# @POST: Returns True iff report satisfies all active query filters.
|
||||
# @INVARIANT: Filter evaluation is side-effect free.
|
||||
# @PARAM: report (TaskReport) - Candidate report.
|
||||
# @PARAM: query (ReportQuery) - Applied query.
|
||||
# @RETURN: bool - True if report matches all filters.
|
||||
@@ -50,9 +90,13 @@ class ReportsService:
|
||||
return False
|
||||
if query.statuses and report.status not in query.statuses:
|
||||
return False
|
||||
if query.time_from and report.updated_at < query.time_from:
|
||||
report_updated_at = self._to_utc_datetime(report.updated_at)
|
||||
query_time_from = self._to_utc_datetime(query.time_from)
|
||||
query_time_to = self._to_utc_datetime(query.time_to)
|
||||
|
||||
if query_time_from and report_updated_at and report_updated_at < query_time_from:
|
||||
return False
|
||||
if query.time_to and report.updated_at > query.time_to:
|
||||
if query_time_to and report_updated_at and report_updated_at > query_time_to:
|
||||
return False
|
||||
if query.search:
|
||||
needle = query.search.lower()
|
||||
@@ -64,6 +108,9 @@ class ReportsService:
|
||||
|
||||
# [DEF:_sort_reports:Function]
|
||||
# @PURPOSE: Sort reports deterministically according to query settings.
|
||||
# @PRE: reports contains only TaskReport items.
|
||||
# @POST: Returns reports ordered by selected sort field and order.
|
||||
# @INVARIANT: Sorting criteria are deterministic for equal input.
|
||||
# @PARAM: reports (List[TaskReport]) - Filtered reports.
|
||||
# @PARAM: query (ReportQuery) - Sort config.
|
||||
# @RETURN: List[TaskReport] - Sorted reports.
|
||||
@@ -75,7 +122,7 @@ class ReportsService:
|
||||
elif query.sort_by == "task_type":
|
||||
reports.sort(key=lambda item: item.task_type.value, reverse=reverse)
|
||||
else:
|
||||
reports.sort(key=lambda item: item.updated_at, reverse=reverse)
|
||||
reports.sort(key=self._datetime_sort_key, reverse=reverse)
|
||||
|
||||
return reports
|
||||
# [/DEF:_sort_reports:Function]
|
||||
|
||||
Reference in New Issue
Block a user