300 lines
12 KiB
Python
300 lines
12 KiB
Python
# #region Test.DatasetsMixin [C:3] [TYPE Module] [SEMANTICS test, dataset, superset, crud, detail]
|
|
# @BRIEF Tests for core/superset_client/_datasets.py — SupersetDatasetsMixin.
|
|
# @RELATION BINDS_TO -> [SupersetDatasetsMixin]
|
|
# @TEST_EDGE: missing_param -> Null query param handled
|
|
# @TEST_EDGE: empty_result -> Empty dataset list returns 0 results
|
|
# @TEST_EDGE: external_api_error -> Exception in API call caught and logged
|
|
|
|
from pathlib import Path
|
|
import sys
|
|
|
|
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src"))
|
|
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
|
|
class TestDatasetsMixin:
|
|
"""Test all methods of SupersetDatasetsMixin."""
|
|
|
|
@pytest.fixture
|
|
def mixin(self):
|
|
from src.core.superset_client._datasets import SupersetDatasetsMixin
|
|
|
|
m = SupersetDatasetsMixin()
|
|
m.client = MagicMock()
|
|
m.client.request = AsyncMock()
|
|
m._validate_query_params = MagicMock(return_value={"page": 0, "page_size": 100})
|
|
m._fetch_all_pages = AsyncMock(return_value=[{"id": 1}, {"id": 2}])
|
|
return m
|
|
|
|
# ── get_datasets ──
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_datasets_success(self, mixin):
|
|
total, data = await mixin.get_datasets(query={"columns": ["id"]})
|
|
assert total == 2
|
|
assert len(data) == 2
|
|
mixin._validate_query_params.assert_called_once_with({"columns": ["id"]})
|
|
mixin._fetch_all_pages.assert_awaited_once()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_datasets_no_query(self, mixin):
|
|
total, data = await mixin.get_datasets()
|
|
assert total == 2
|
|
mixin._validate_query_params.assert_called_once_with(None)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_datasets_empty_result(self, mixin):
|
|
mixin._fetch_all_pages = AsyncMock(return_value=[])
|
|
total, data = await mixin.get_datasets()
|
|
assert total == 0
|
|
assert data == []
|
|
|
|
# ── get_datasets_summary ──
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_datasets_summary(self, mixin):
|
|
mixin.get_datasets = AsyncMock(
|
|
return_value=(
|
|
2,
|
|
[
|
|
{
|
|
"id": 1,
|
|
"table_name": "users",
|
|
"schema": "public",
|
|
"database": {"database_name": "Postgres"},
|
|
},
|
|
{
|
|
"id": 2,
|
|
"table_name": "orders",
|
|
"schema": "public",
|
|
"database": {"database_name": "Postgres"},
|
|
},
|
|
],
|
|
)
|
|
)
|
|
result = await mixin.get_datasets_summary()
|
|
assert len(result) == 2
|
|
assert result[0]["table_name"] == "users"
|
|
assert result[0]["database"] == "Postgres"
|
|
assert result[1]["schema"] == "public"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_datasets_summary_missing_database(self, mixin):
|
|
mixin.get_datasets = AsyncMock(
|
|
return_value=(
|
|
1,
|
|
[{"id": 1, "table_name": "t", "schema": "public"}],
|
|
)
|
|
)
|
|
result = await mixin.get_datasets_summary()
|
|
assert result[0]["database"] == "Unknown"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_datasets_summary_empty(self, mixin):
|
|
mixin.get_datasets = AsyncMock(return_value=(0, []))
|
|
result = await mixin.get_datasets_summary()
|
|
assert result == []
|
|
|
|
# ── get_dataset_linked_dashboard_count ──
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_dataset_linked_dashboard_count_direct_key(self, mixin):
|
|
mixin.client.request.return_value = {"dashboards": [{"id": 1}, {"id": 2}]}
|
|
result = await mixin.get_dataset_linked_dashboard_count(1)
|
|
assert result == 2
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_dataset_linked_dashboard_count_result_key(self, mixin):
|
|
mixin.client.request.return_value = {"result": {"dashboards": [{"id": 1}]}}
|
|
result = await mixin.get_dataset_linked_dashboard_count(1)
|
|
assert result == 1
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_dataset_linked_dashboard_count_empty(self, mixin):
|
|
mixin.client.request.return_value = {"result": {"dashboards": []}}
|
|
result = await mixin.get_dataset_linked_dashboard_count(1)
|
|
assert result == 0
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_dataset_linked_dashboard_count_non_dict(self, mixin):
|
|
mixin.client.request.return_value = "not_a_dict"
|
|
result = await mixin.get_dataset_linked_dashboard_count(1)
|
|
assert result == 0
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_dataset_linked_dashboard_count_error(self, mixin):
|
|
mixin.client.request.side_effect = Exception("API error")
|
|
with patch("src.core.superset_client._datasets.app_logger") as mock_log:
|
|
result = await mixin.get_dataset_linked_dashboard_count(1)
|
|
assert result == 0
|
|
mock_log.warning.assert_called_once()
|
|
|
|
# ── get_dataset_detail ──
|
|
|
|
@pytest.fixture
|
|
def detail_mixin(self):
|
|
from src.core.superset_client._datasets import SupersetDatasetsMixin
|
|
|
|
m = SupersetDatasetsMixin()
|
|
m.client = MagicMock()
|
|
m.client.request = AsyncMock()
|
|
m.get_dataset = AsyncMock()
|
|
return m
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_dataset_detail_full(self, detail_mixin):
|
|
detail_mixin.get_dataset.return_value = {
|
|
"result": {
|
|
"id": 1,
|
|
"table_name": "users",
|
|
"schema": "public",
|
|
"database": {"id": 1, "backend": "postgresql", "database_name": "Postgres"},
|
|
"database_id": 1,
|
|
"description": "User table",
|
|
"columns": [
|
|
{"id": 1, "column_name": "name", "type": "text", "is_dttm": False, "is_active": True, "description": ""},
|
|
{"id": 2, "column_name": "created", "type": "timestamp", "is_dttm": True, "is_active": True, "description": ""},
|
|
],
|
|
"metrics": [
|
|
{"id": 1, "metric_name": "count", "expression": "COUNT(*)", "verbose_name": "Count", "description": "", "metric_type": "count"},
|
|
],
|
|
"sql": "SELECT * FROM users",
|
|
"is_sqllab_view": False,
|
|
"created_on": "2024-01-01",
|
|
"changed_on": "2024-01-02",
|
|
}
|
|
}
|
|
detail_mixin.client.request.return_value = {
|
|
"dashboards": {"result": [{"id": 10, "dashboard_title": "Main", "slug": "main"}]}
|
|
}
|
|
result = await detail_mixin.get_dataset_detail(1)
|
|
assert result["id"] == 1
|
|
assert result["table_name"] == "users"
|
|
assert len(result["columns"]) == 2
|
|
assert result["columns"][0]["name"] == "name"
|
|
assert result["columns"][1]["is_dttm"] is True
|
|
assert len(result["metrics"]) == 1
|
|
assert result["metrics"][0]["metric_name"] == "count"
|
|
assert result["linked_dashboard_count"] == 1
|
|
assert result["database_backend"] == "postgresql"
|
|
assert result["is_sqllab_view"] is False
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_dataset_detail_no_result_key(self, detail_mixin):
|
|
detail_mixin.get_dataset.return_value = {
|
|
"id": 1,
|
|
"table_name": "no_result",
|
|
"columns": [],
|
|
"metrics": [],
|
|
}
|
|
detail_mixin.client.request.return_value = {}
|
|
result = await detail_mixin.get_dataset_detail(1)
|
|
assert result["table_name"] == "no_result"
|
|
assert result["columns"] == []
|
|
assert result["metrics"] == []
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_dataset_detail_related_objects_error(self, detail_mixin):
|
|
detail_mixin.get_dataset.return_value = {
|
|
"result": {
|
|
"id": 1,
|
|
"table_name": "test",
|
|
"columns": [],
|
|
"metrics": [],
|
|
}
|
|
}
|
|
detail_mixin.client.request.side_effect = Exception("Connection error")
|
|
with patch("src.core.superset_client._datasets.app_logger") as mock_log:
|
|
result = await detail_mixin.get_dataset_detail(1)
|
|
assert result["linked_dashboards"] == []
|
|
mock_log.warning.assert_called_once()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_dataset_detail_related_objects_has_dashboards_direct(self, detail_mixin):
|
|
detail_mixin.get_dataset.return_value = {
|
|
"result": {
|
|
"id": 1,
|
|
"table_name": "test",
|
|
"columns": [],
|
|
"metrics": [],
|
|
"database": {},
|
|
}
|
|
}
|
|
detail_mixin.client.request.return_value = {"dashboards": [{"id": 5, "dashboard_title": "Dash"}]}
|
|
result = await detail_mixin.get_dataset_detail(1)
|
|
assert result["linked_dashboard_count"] == 1
|
|
assert result["linked_dashboards"][0]["id"] == 5
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_dataset_detail_related_objects_with_int_list(self, detail_mixin):
|
|
detail_mixin.get_dataset.return_value = {
|
|
"result": {
|
|
"id": 1,
|
|
"table_name": "test",
|
|
"columns": [],
|
|
"metrics": [],
|
|
}
|
|
}
|
|
detail_mixin.client.request.return_value = {"dashboards": [5, 6]}
|
|
result = await detail_mixin.get_dataset_detail(1)
|
|
assert result["linked_dashboard_count"] == 2
|
|
assert result["linked_dashboards"][0]["id"] == 5
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_dataset_detail_as_bool_variants(self, detail_mixin):
|
|
detail_mixin.get_dataset.return_value = {
|
|
"result": {
|
|
"id": 1,
|
|
"table_name": "test",
|
|
"columns": [
|
|
{"id": 1, "column_name": "a", "is_dttm": "1", "is_active": "true"},
|
|
{"id": 2, "column_name": "b", "is_dttm": "yes", "is_active": "on"},
|
|
{"id": 3, "column_name": "c", "is_dttm": None, "is_active": None},
|
|
{"id": 4, "column_name": "d", "is_dttm": 1, "is_active": 0},
|
|
],
|
|
"metrics": [],
|
|
"database": {"backend": "mysql"},
|
|
}
|
|
}
|
|
detail_mixin.client.request.return_value = {}
|
|
result = await detail_mixin.get_dataset_detail(1)
|
|
assert result["columns"][0]["is_dttm"] is True
|
|
assert result["columns"][1]["is_dttm"] is True
|
|
assert result["columns"][2]["is_dttm"] is False
|
|
assert result["columns"][3]["is_dttm"] is True
|
|
assert result["columns"][0]["is_active"] is True
|
|
assert result["columns"][3]["is_active"] is False
|
|
|
|
# ── get_dataset ──
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_dataset_success(self, mixin):
|
|
mixin.client.request.return_value = {"result": {"id": 1, "table_name": "test"}}
|
|
result = await mixin.get_dataset(1)
|
|
assert result["result"]["id"] == 1
|
|
|
|
# ── update_dataset ──
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_update_dataset_success(self, mixin):
|
|
mixin.client.request.return_value = {"result": "ok"}
|
|
result = await mixin.update_dataset(1, {"columns": []}, override_columns=True)
|
|
mixin.client.request.assert_awaited_once()
|
|
assert result["result"] == "ok"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_update_dataset_no_override(self, mixin):
|
|
mixin.client.request.return_value = {"result": "ok"}
|
|
result = await mixin.update_dataset(1, {"columns": []})
|
|
assert result["result"] == "ok"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_update_dataset_with_json_data(self, mixin):
|
|
mixin.client.request.return_value = {"result": "ok"}
|
|
result = await mixin.update_dataset(1, {"table_name": "new_name"}, override_columns=False)
|
|
assert result["result"] == "ok"
|
|
# #endregion Test.DatasetsMixin
|