261 lines
11 KiB
Python
261 lines
11 KiB
Python
# #region Test.Assistant.DatasetReviewExtended [C:2] [TYPE Module] [SEMANTICS test,assistant,dataset,review,edge]
|
|
# @BRIEF Edge-case coverage for _dataset_review.py and _dataset_review_dispatch.py uncovered lines.
|
|
# @RELATION BINDS_TO -> [AssistantDatasetReview]
|
|
# @RELATION BINDS_TO -> [AssistantDatasetReviewDispatch]
|
|
|
|
from pathlib import Path
|
|
import sys
|
|
|
|
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
|
|
|
|
from datetime import UTC, datetime
|
|
from unittest.mock import MagicMock, AsyncMock, patch
|
|
import pytest
|
|
from fastapi import HTTPException
|
|
|
|
|
|
class TestMatchDatasetReviewFieldExtended:
|
|
"""_match_dataset_review_field — field_name and verbose_name matching paths."""
|
|
|
|
def test_match_by_field_name(self):
|
|
from src.api.routes.assistant._dataset_review import _match_dataset_review_field
|
|
|
|
ctx = {"semantic_fields": [{"field_id": "fld-1", "field_name": "region", "verbose_name": "Region"}]}
|
|
result = _match_dataset_review_field(ctx, "update the region field please")
|
|
assert result is not None
|
|
assert result["field_name"] == "region"
|
|
|
|
def test_match_by_verbose_name(self):
|
|
from src.api.routes.assistant._dataset_review import _match_dataset_review_field
|
|
|
|
ctx = {"semantic_fields": [{"field_id": "fld-1", "field_name": "rgn", "verbose_name": "Region Name"}]}
|
|
result = _match_dataset_review_field(ctx, "change Region Name")
|
|
assert result is not None
|
|
assert result["verbose_name"] == "Region Name"
|
|
|
|
def test_match_by_field_id_lowercase_in_message(self):
|
|
"""Match field when field_id appears in message text."""
|
|
from src.api.routes.assistant._dataset_review import _match_dataset_review_field
|
|
|
|
ctx = {"semantic_fields": [{"field_id": "FLD-001", "field_name": "sales", "verbose_name": "Sales"}]}
|
|
# field_id "fld-001" does not appear in message — should try field_name/verbose
|
|
result = _match_dataset_review_field(ctx, "about sales data")
|
|
assert result is not None
|
|
assert result["field_name"] == "sales"
|
|
|
|
|
|
class TestPlanDatasetReviewIntentExtended:
|
|
"""_plan_dataset_review_intent — uncovered branches."""
|
|
|
|
@pytest.fixture
|
|
def context(self):
|
|
return {
|
|
"session_id": "sess-1",
|
|
"version": 5,
|
|
"readiness_state": "REVIEW_READY",
|
|
"recommended_action": "REVIEW_DOCUMENTATION",
|
|
"mappings": [],
|
|
"findings": [{"code": "TEST"}],
|
|
"imported_filters": [{"filter_name": "region"}],
|
|
"preview": {"preview_status": "missing"},
|
|
"semantic_fields": [
|
|
{
|
|
"field_id": "fld-1",
|
|
"field_name": "region",
|
|
"verbose_name": "Region",
|
|
"candidates": [{"candidate_id": "cand-1"}],
|
|
}
|
|
],
|
|
}
|
|
|
|
def test_set_field_semantics_accept_candidate(self, context):
|
|
"""Accept first candidate via message keywords."""
|
|
from src.api.routes.assistant._dataset_review import _plan_dataset_review_intent
|
|
result = _plan_dataset_review_intent(
|
|
'apply field semantics target:field=fld-1 accept candidate',
|
|
context,
|
|
)
|
|
assert result is not None
|
|
assert result["operation"] == "dataset_review_set_field_semantics"
|
|
assert result["entities"]["candidate_id"] == "cand-1"
|
|
|
|
def test_set_field_semantics_with_label(self, context):
|
|
"""Set verbose_name via 'label' keyword (matches regex alternation)."""
|
|
from src.api.routes.assistant._dataset_review import _plan_dataset_review_intent
|
|
result = _plan_dataset_review_intent(
|
|
'set field semantics target:field=fld-1 label="Country"',
|
|
context,
|
|
)
|
|
assert result is not None
|
|
assert result["entities"]["verbose_name"] == "Country"
|
|
|
|
def test_set_field_semantics_with_description(self, context):
|
|
"""Set description via 'desc' keyword."""
|
|
from src.api.routes.assistant._dataset_review import _plan_dataset_review_intent
|
|
result = _plan_dataset_review_intent(
|
|
'set field semantics target:field=fld-1 desc="The country field"',
|
|
context,
|
|
)
|
|
assert result is not None
|
|
assert result["entities"]["description"] == "The country field"
|
|
|
|
def test_set_field_semantics_no_attributes_returns_none(self, context):
|
|
"""No candidate, verbose_name, description, or display_format returns None."""
|
|
from src.api.routes.assistant._dataset_review import _plan_dataset_review_intent
|
|
result = _plan_dataset_review_intent(
|
|
'set field semantics target:field=fld-1',
|
|
context,
|
|
)
|
|
assert result is None
|
|
|
|
def test_set_field_semantics_with_lock(self, context):
|
|
"""Lock field when message contains lock keywords."""
|
|
from src.api.routes.assistant._dataset_review import _plan_dataset_review_intent
|
|
result = _plan_dataset_review_intent(
|
|
'update semantic field target:field=fld-1 lock it desc="Locked"',
|
|
context,
|
|
)
|
|
assert result is not None
|
|
assert result["entities"]["lock_field"] is True
|
|
|
|
|
|
class TestDispatchDatasetReviewIntentExtended:
|
|
"""_dispatch_dataset_review_intent — uncovered branches."""
|
|
|
|
@pytest.fixture
|
|
def user(self):
|
|
from src.schemas.auth import User
|
|
return User(id="user-1", username="test", email="test@x.com", is_active=True,
|
|
auth_source="internal", created_at=datetime.now(UTC),
|
|
last_login=datetime.now(UTC), roles=[])
|
|
|
|
@pytest.fixture
|
|
def config_manager(self):
|
|
cm = MagicMock()
|
|
cm.get_environments = MagicMock(return_value=[])
|
|
return cm
|
|
|
|
def _make_session_mock(self, **overrides):
|
|
from src.models.dataset_review import ReadinessState, RecommendedAction, SessionStatus, SessionPhase
|
|
s = MagicMock()
|
|
s.session_id = overrides.get("session_id", "sess-1")
|
|
s.version = overrides.get("version", 5)
|
|
s.user_id = overrides.get("user_id", "user-1")
|
|
s.readiness_state = overrides.get("readiness_state",
|
|
MagicMock(value=ReadinessState.MAPPING_REVIEW_NEEDED.value))
|
|
s.recommended_action = overrides.get("recommended_action",
|
|
MagicMock(value=RecommendedAction.REVIEW_DOCUMENTATION.value))
|
|
s.execution_mappings = overrides.get("execution_mappings", [])
|
|
s.semantic_fields = overrides.get("semantic_fields", [])
|
|
s.last_activity_at = None
|
|
return s
|
|
|
|
def _make_mapping_mock(self, **overrides):
|
|
from src.models.dataset_review import ApprovalState
|
|
m = MagicMock()
|
|
m.mapping_id = overrides.get("mapping_id", "m-1")
|
|
m.approval_state = overrides.get("approval_state", ApprovalState.PENDING)
|
|
m.requires_explicit_approval = overrides.get("requires_explicit_approval", True)
|
|
return m
|
|
|
|
def _make_field_mock(self, **overrides):
|
|
f = MagicMock()
|
|
f.field_id = overrides.get("field_id", "fld-1")
|
|
f.field_name = overrides.get("field_name", "region")
|
|
return f
|
|
|
|
def test_approve_mappings_updates_readiness_state(self, user, config_manager):
|
|
"""When MAPPING_REVIEW_NEEDED, recommended_action changes to GENERATE_SQL_PREVIEW."""
|
|
from src.api.routes.assistant._dataset_review_dispatch import _dispatch_dataset_review_intent
|
|
from src.models.dataset_review import ApprovalState
|
|
|
|
mapping = self._make_mapping_mock(mapping_id="m-1", approval_state=ApprovalState.PENDING,
|
|
requires_explicit_approval=True)
|
|
session = self._make_session_mock(execution_mappings=[mapping], user_id="user-1")
|
|
|
|
intent = {
|
|
"operation": "dataset_review_approve_mappings",
|
|
"entities": {
|
|
"dataset_review_session_id": "sess-1",
|
|
"session_version": 5,
|
|
"mapping_ids": ["m-1"],
|
|
},
|
|
}
|
|
|
|
db = MagicMock()
|
|
with patch("src.api.routes.assistant._dataset_review_dispatch.DatasetReviewSessionRepository") as mock_repo_cls:
|
|
mock_repo = MagicMock()
|
|
mock_repo_cls.return_value = mock_repo
|
|
mock_repo.load_session_detail.return_value = session
|
|
mock_repo.require_session_version.return_value = None
|
|
mock_repo.bump_session_version.return_value = None
|
|
mock_repo.db = MagicMock()
|
|
mock_repo.event_logger = MagicMock()
|
|
|
|
text, task_id, actions = (
|
|
__import__("asyncio").run(_dispatch_dataset_review_intent(intent, user, config_manager, db))
|
|
)
|
|
assert "Approved" in text
|
|
|
|
def test_set_field_semantics_value_error(self, user, config_manager):
|
|
"""ValueError from _update_semantic_field_state becomes 400."""
|
|
from src.api.routes.assistant._dataset_review_dispatch import _dispatch_dataset_review_intent
|
|
|
|
field = self._make_field_mock()
|
|
session = self._make_session_mock(semantic_fields=[field], user_id="user-1")
|
|
|
|
intent = {
|
|
"operation": "dataset_review_set_field_semantics",
|
|
"entities": {
|
|
"dataset_review_session_id": "sess-1",
|
|
"session_version": 5,
|
|
"field_id": "fld-1",
|
|
"lock_field": True,
|
|
},
|
|
}
|
|
|
|
db = MagicMock()
|
|
with patch("src.api.routes.assistant._dataset_review_dispatch.DatasetReviewSessionRepository") as mock_repo_cls:
|
|
mock_repo = MagicMock()
|
|
mock_repo_cls.return_value = mock_repo
|
|
mock_repo.load_session_detail.return_value = session
|
|
mock_repo.require_session_version.return_value = None
|
|
|
|
with patch("src.api.routes.assistant._dataset_review_dispatch._update_semantic_field_state",
|
|
side_effect=ValueError("Invalid field value")):
|
|
with pytest.raises(HTTPException) as exc:
|
|
__import__("asyncio").run(
|
|
_dispatch_dataset_review_intent(intent, user, config_manager, db)
|
|
)
|
|
assert exc.value.status_code == 400
|
|
assert "Invalid" in str(exc.value.detail)
|
|
|
|
def test_set_field_semantics_field_not_found(self, user, config_manager):
|
|
"""Field not found in session raises 404."""
|
|
from src.api.routes.assistant._dataset_review_dispatch import _dispatch_dataset_review_intent
|
|
|
|
session = self._make_session_mock(semantic_fields=[], user_id="user-1")
|
|
|
|
intent = {
|
|
"operation": "dataset_review_set_field_semantics",
|
|
"entities": {
|
|
"dataset_review_session_id": "sess-1",
|
|
"session_version": 5,
|
|
"field_id": "nonexistent",
|
|
},
|
|
}
|
|
|
|
db = MagicMock()
|
|
with patch("src.api.routes.assistant._dataset_review_dispatch.DatasetReviewSessionRepository") as mock_repo_cls:
|
|
mock_repo = MagicMock()
|
|
mock_repo_cls.return_value = mock_repo
|
|
mock_repo.load_session_detail.return_value = session
|
|
mock_repo.require_session_version.return_value = None
|
|
|
|
with pytest.raises(HTTPException) as exc:
|
|
__import__("asyncio").run(
|
|
_dispatch_dataset_review_intent(intent, user, config_manager, db)
|
|
)
|
|
assert exc.value.status_code == 404
|
|
# #endregion Test.Assistant.DatasetReviewExtended
|