273 lines
9.4 KiB
Python
273 lines
9.4 KiB
Python
# [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
|
|
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
|
|
@pytest.mark.asyncio
|
|
async def test_get_dashboards_with_status():
|
|
with patch("src.services.resource_service.SupersetClient") as mock_client, \
|
|
patch("src.services.resource_service.GitService"):
|
|
|
|
from src.services.resource_service import ResourceService
|
|
|
|
service = ResourceService()
|
|
|
|
# Mock Superset response
|
|
mock_client.return_value.get_dashboards_summary.return_value = [
|
|
{"id": 1, "title": "Dashboard 1", "slug": "dash-1"},
|
|
{"id": 2, "title": "Dashboard 2", "slug": "dash-2"}
|
|
]
|
|
|
|
# Mock tasks
|
|
task_prod_old = MagicMock()
|
|
task_prod_old.id = "task-123"
|
|
task_prod_old.plugin_id = "llm_dashboard_validation"
|
|
task_prod_old.status = "SUCCESS"
|
|
task_prod_old.params = {"dashboard_id": "1", "environment_id": "prod"}
|
|
task_prod_old.started_at = datetime(2024, 1, 1, 10, 0, 0)
|
|
|
|
task_prod_new = MagicMock()
|
|
task_prod_new.id = "task-124"
|
|
task_prod_new.plugin_id = "llm_dashboard_validation"
|
|
task_prod_new.status = "TaskStatus.FAILED"
|
|
task_prod_new.params = {"dashboard_id": "1", "environment_id": "prod"}
|
|
task_prod_new.result = {"status": "FAIL"}
|
|
task_prod_new.started_at = datetime(2024, 1, 1, 12, 0, 0)
|
|
|
|
task_other_env = MagicMock()
|
|
task_other_env.id = "task-200"
|
|
task_other_env.plugin_id = "llm_dashboard_validation"
|
|
task_other_env.status = "SUCCESS"
|
|
task_other_env.params = {"dashboard_id": "1", "environment_id": "stage"}
|
|
task_other_env.started_at = datetime(2024, 1, 1, 13, 0, 0)
|
|
|
|
env = MagicMock()
|
|
env.id = "prod"
|
|
|
|
result = await service.get_dashboards_with_status(
|
|
env,
|
|
[task_prod_old, task_prod_new, task_other_env],
|
|
)
|
|
|
|
assert len(result) == 2
|
|
assert result[0]["id"] == 1
|
|
assert "git_status" in result[0]
|
|
assert "last_task" in result[0]
|
|
assert result[0]["last_task"]["task_id"] == "task-124"
|
|
assert result[0]["last_task"]["status"] == "FAILED"
|
|
assert result[0]["last_task"]["validation_status"] == "FAIL"
|
|
|
|
|
|
# [/DEF:test_get_dashboards_with_status:Function]
|
|
|
|
|
|
# [DEF:test_get_datasets_with_status:Function]
|
|
# @TEST: get_datasets_with_status returns datasets with task status
|
|
# @PRE: SupersetClient returns dataset list
|
|
# @POST: Each dataset has last_task field
|
|
@pytest.mark.asyncio
|
|
async def test_get_datasets_with_status():
|
|
with patch("src.services.resource_service.SupersetClient") as mock_client:
|
|
|
|
from src.services.resource_service import ResourceService
|
|
|
|
service = ResourceService()
|
|
|
|
# Mock Superset response
|
|
mock_client.return_value.get_datasets_summary.return_value = [
|
|
{"id": 1, "table_name": "users", "schema": "public", "database": "app"},
|
|
{"id": 2, "table_name": "orders", "schema": "public", "database": "app"}
|
|
]
|
|
|
|
# Mock tasks
|
|
mock_task = MagicMock()
|
|
mock_task.id = "task-456"
|
|
mock_task.status = "RUNNING"
|
|
mock_task.params = {"resource_id": "dataset-1"}
|
|
mock_task.created_at = datetime.now()
|
|
|
|
env = MagicMock()
|
|
env.id = "prod"
|
|
|
|
result = await service.get_datasets_with_status(env, [mock_task])
|
|
|
|
assert len(result) == 2
|
|
assert result[0]["table_name"] == "users"
|
|
assert "last_task" in result[0]
|
|
assert result[0]["last_task"]["task_id"] == "task-456"
|
|
assert result[0]["last_task"]["status"] == "RUNNING"
|
|
|
|
|
|
# [/DEF:test_get_datasets_with_status:Function]
|
|
|
|
|
|
# [DEF:test_get_activity_summary:Function]
|
|
# @TEST: get_activity_summary returns active count and recent tasks
|
|
# @PRE: tasks list provided
|
|
# @POST: Returns dict with active_count and recent_tasks
|
|
def test_get_activity_summary():
|
|
from src.services.resource_service import ResourceService
|
|
|
|
service = ResourceService()
|
|
|
|
# Create mock tasks
|
|
task1 = MagicMock()
|
|
task1.id = "task-1"
|
|
task1.status = "RUNNING"
|
|
task1.params = {"resource_name": "Dashboard 1", "resource_type": "dashboard"}
|
|
task1.created_at = datetime(2024, 1, 1, 10, 0, 0)
|
|
|
|
task2 = MagicMock()
|
|
task2.id = "task-2"
|
|
task2.status = "SUCCESS"
|
|
task2.params = {"resource_name": "Dataset 1", "resource_type": "dataset"}
|
|
task2.created_at = datetime(2024, 1, 1, 9, 0, 0)
|
|
|
|
task3 = MagicMock()
|
|
task3.id = "task-3"
|
|
task3.status = "WAITING_INPUT"
|
|
task3.params = {"resource_name": "Dashboard 2", "resource_type": "dashboard"}
|
|
task3.created_at = datetime(2024, 1, 1, 8, 0, 0)
|
|
|
|
result = service.get_activity_summary([task1, task2, task3])
|
|
|
|
assert result["active_count"] == 2 # RUNNING + WAITING_INPUT
|
|
assert len(result["recent_tasks"]) == 3
|
|
|
|
|
|
# [/DEF:test_get_activity_summary:Function]
|
|
|
|
|
|
# [DEF:test_get_git_status_for_dashboard_no_repo:Function]
|
|
# @TEST: _get_git_status_for_dashboard returns None when no repo exists
|
|
# @PRE: GitService returns None for repo
|
|
# @POST: Returns None
|
|
def test_get_git_status_for_dashboard_no_repo():
|
|
with patch("src.services.resource_service.GitService") as mock_git:
|
|
|
|
from src.services.resource_service import ResourceService
|
|
|
|
service = ResourceService()
|
|
mock_git.return_value.get_repo.return_value = None
|
|
|
|
result = service._get_git_status_for_dashboard(123)
|
|
|
|
assert result is not None
|
|
assert result['sync_status'] == 'NO_REPO'
|
|
assert result['has_repo'] is False
|
|
|
|
|
|
# [/DEF:test_get_git_status_for_dashboard_no_repo:Function]
|
|
|
|
|
|
# [DEF:test_get_last_task_for_resource:Function]
|
|
# @TEST: _get_last_task_for_resource returns most recent task for resource
|
|
# @PRE: tasks list with matching resource_id
|
|
# @POST: Returns task summary with task_id and status
|
|
def test_get_last_task_for_resource():
|
|
from src.services.resource_service import ResourceService
|
|
|
|
service = ResourceService()
|
|
|
|
# Create mock tasks
|
|
task1 = MagicMock()
|
|
task1.id = "task-old"
|
|
task1.status = "SUCCESS"
|
|
task1.params = {"resource_id": "dashboard-1"}
|
|
task1.created_at = datetime(2024, 1, 1, 10, 0, 0)
|
|
|
|
task2 = MagicMock()
|
|
task2.id = "task-new"
|
|
task2.status = "RUNNING"
|
|
task2.params = {"resource_id": "dashboard-1"}
|
|
task2.created_at = datetime(2024, 1, 1, 12, 0, 0)
|
|
|
|
result = service._get_last_task_for_resource("dashboard-1", [task1, task2])
|
|
|
|
assert result is not None
|
|
assert result["task_id"] == "task-new" # Most recent
|
|
assert result["status"] == "RUNNING"
|
|
|
|
|
|
# [/DEF:test_get_last_task_for_resource:Function]
|
|
|
|
|
|
# [DEF:test_extract_resource_name_from_task:Function]
|
|
# @TEST: _extract_resource_name_from_task extracts name from params
|
|
# @PRE: task has resource_name in params
|
|
# @POST: Returns resource name or fallback
|
|
def test_extract_resource_name_from_task():
|
|
from src.services.resource_service import ResourceService
|
|
|
|
service = ResourceService()
|
|
|
|
# Task with resource_name
|
|
task = MagicMock()
|
|
task.id = "task-123"
|
|
task.params = {"resource_name": "My Dashboard"}
|
|
|
|
result = service._extract_resource_name_from_task(task)
|
|
assert result == "My Dashboard"
|
|
|
|
# Task without resource_name
|
|
task2 = MagicMock()
|
|
task2.id = "task-456"
|
|
task2.params = {}
|
|
|
|
result2 = service._extract_resource_name_from_task(task2)
|
|
assert "task-456" in result2
|
|
|
|
|
|
# [/DEF:test_extract_resource_name_from_task:Function]
|
|
|
|
|
|
# [DEF:test_get_last_task_for_resource_empty_tasks:Function]
|
|
# @TEST: _get_last_task_for_resource returns None for empty tasks list
|
|
# @PRE: tasks is empty list
|
|
# @POST: Returns None
|
|
def test_get_last_task_for_resource_empty_tasks():
|
|
from src.services.resource_service import ResourceService
|
|
|
|
service = ResourceService()
|
|
|
|
result = service._get_last_task_for_resource("dashboard-1", [])
|
|
assert result is None
|
|
# [/DEF:test_get_last_task_for_resource_empty_tasks:Function]
|
|
|
|
|
|
# [DEF:test_get_last_task_for_resource_no_match:Function]
|
|
# @TEST: _get_last_task_for_resource returns None when no tasks match resource_id
|
|
# @PRE: tasks list has no matching resource_id
|
|
# @POST: Returns None
|
|
def test_get_last_task_for_resource_no_match():
|
|
from src.services.resource_service import ResourceService
|
|
|
|
service = ResourceService()
|
|
|
|
task = MagicMock()
|
|
task.id = "task-999"
|
|
task.status = "SUCCESS"
|
|
task.params = {"resource_id": "dashboard-99"}
|
|
task.created_at = datetime(2024, 1, 1, 10, 0, 0)
|
|
|
|
result = service._get_last_task_for_resource("dashboard-1", [task])
|
|
assert result is None
|
|
# [/DEF:test_get_last_task_for_resource_no_match:Function]
|
|
|
|
|
|
# [/DEF:backend.src.services.__tests__.test_resource_service:Module]
|