# #region Test.Assistant.DatasetReview [C:2] [TYPE Module] [SEMANTICS test,assistant,dataset,review,context,intent] # @BRIEF Tests for _dataset_review.py and _dataset_review_dispatch.py — context loading, intent planning, dispatch. # @RELATION BINDS_TO -> [AssistantDatasetReview] # @RELATION BINDS_TO -> [AssistantDatasetReviewDispatch] # @TEST_EDGE: serialize_context -> Session context serialization with all fields # @TEST_EDGE: extract_target -> Target extraction from messages # @TEST_EDGE: match_field -> Field matching by id/name/verbose name # @TEST_EDGE: extract_quoted_segment -> Quoted segment extraction # @TEST_EDGE: plan_intent_approve -> Approve mappings intent # @TEST_EDGE: plan_intent_preview -> Generate SQL preview intent # @TEST_EDGE: plan_intent_semantic -> Set field semantics intent # @TEST_EDGE: plan_intent_context -> Answer context intent # @TEST_EDGE: dispatch_answer_context -> Context-only dispatch # @TEST_EDGE: dispatch_approve_mappings -> Mapping approval dispatch # @TEST_EDGE: dispatch_set_field -> Field semantic update dispatch # @TEST_EDGE: dispatch_generate_preview -> SQL preview generation dispatch 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, PropertyMock import pytest from fastapi import HTTPException # ── Shared helpers ───────────────────────────────────────────────────── def _make_session_mock(**overrides): """Create a mock DatasetReviewSession with realistic defaults.""" 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.dataset_ref = overrides.get("dataset_ref", "dataset_123") s.dataset_id = overrides.get("dataset_id", 123) s.environment_id = overrides.get("environment_id", "env-1") s.user_id = overrides.get("user_id", "user-1") rs = overrides.get("readiness_state", ReadinessState.REVIEW_READY) s.readiness_state = rs if isinstance(rs, MagicMock) else MagicMock(value=rs.value if hasattr(rs, 'value') else rs) s.readiness_state.value = rs.value if hasattr(rs, 'value') else rs ra = overrides.get("recommended_action", RecommendedAction.REVIEW_DOCUMENTATION) s.recommended_action = ra if isinstance(ra, MagicMock) else MagicMock(value=ra.value if hasattr(ra, 'value') else ra) s.recommended_action.value = ra.value if hasattr(ra, 'value') else ra st = overrides.get("status", SessionStatus.ACTIVE) s.status = st if isinstance(st, MagicMock) else MagicMock(value=st.value if hasattr(st, 'value') else st) s.status.value = st.value if hasattr(st, 'value') else st cp = overrides.get("current_phase", SessionPhase.REVIEW) s.current_phase = cp if isinstance(cp, MagicMock) else MagicMock(value=cp.value if hasattr(cp, 'value') else cp) s.current_phase.value = cp.value if hasattr(cp, 'value') else cp s.findings = overrides.get("findings", []) s.imported_filters = overrides.get("imported_filters", []) s.execution_mappings = overrides.get("execution_mappings", []) s.semantic_fields = overrides.get("semantic_fields", []) s.previews = overrides.get("previews", []) s.last_activity_at = overrides.get("last_activity_at", datetime.now(UTC)) return s def _make_finding_mock(finding_id="f-1", code="TEST", severity="WARNING", message="Test finding", resolution_state="OPEN"): f = MagicMock() f.finding_id = finding_id f.code = code f.severity = MagicMock(value=severity) f.message = message f.resolution_state = MagicMock(value=resolution_state) return f def _make_filter_mock(filter_id="fl-1", filter_name="region", display_name="Region", raw_value="US", normalized_value="USA", source="SUPERSET_URL", confidence_state="HIGH", requires_confirmation=False, recovery_status="RECOVERED", notes=None): fl = MagicMock() fl.filter_id = filter_id fl.filter_name = filter_name fl.display_name = display_name fl.raw_value = raw_value fl.raw_value_masked = False fl.normalized_value = normalized_value fl.source = MagicMock(value=source) fl.confidence_state = MagicMock(value=confidence_state) fl.requires_confirmation = requires_confirmation fl.recovery_status = MagicMock(value=recovery_status) fl.notes = notes return fl def _make_mapping_mock(mapping_id="m-1", filter_id="fl-1", variable_id="v-1", mapping_method="DIRECT_MATCH", effective_value="US", approval_state="PENDING", requires_explicit_approval=True): m = MagicMock() m.mapping_id = mapping_id m.filter_id = filter_id m.variable_id = variable_id m.mapping_method = MagicMock(value=mapping_method) m.effective_value = effective_value m.approval_state = MagicMock(value=approval_state) m.requires_explicit_approval = requires_explicit_approval return m def _make_field_mock(field_id="fld-1", field_name="region", verbose_name="Region", description="The region field", display_format="text", provenance="DICTIONARY_EXACT", is_locked=False, needs_review=False, has_conflict=False): f = MagicMock() f.field_id = field_id f.field_name = field_name f.verbose_name = verbose_name f.description = description f.display_format = display_format f.provenance = MagicMock(value=provenance) f.is_locked = is_locked f.needs_review = needs_review f.has_conflict = has_conflict return f class TestSerializeDatasetReviewContext: """_serialize_dataset_review_context — context serialization.""" def test_basic_context(self): from src.api.routes.assistant._dataset_review import _serialize_dataset_review_context session = _make_session_mock() ctx = _serialize_dataset_review_context(session) assert ctx["session_id"] == "sess-1" assert ctx["version"] == 5 assert ctx["dataset_ref"] == "dataset_123" assert ctx["status"] == "active" def test_context_with_findings_and_filters(self): from src.api.routes.assistant._dataset_review import _serialize_dataset_review_context session = _make_session_mock(findings=[_make_finding_mock()], imported_filters=[_make_filter_mock()], execution_mappings=[_make_mapping_mock()]) ctx = _serialize_dataset_review_context(session) assert len(ctx["findings"]) == 1 assert ctx["findings"][0]["finding_id"] == "f-1" assert len(ctx["imported_filters"]) == 1 assert ctx["imported_filters"][0]["filter_name"] == "region" assert len(ctx["mappings"]) == 1 assert ctx["mappings"][0]["mapping_id"] == "m-1" def test_context_with_semantic_fields(self): from src.api.routes.assistant._dataset_review import _serialize_dataset_review_context session = _make_session_mock(semantic_fields=[_make_field_mock()]) ctx = _serialize_dataset_review_context(session) assert len(ctx["semantic_fields"]) == 1 assert ctx["semantic_fields"][0]["field_name"] == "region" def test_context_with_preview(self): from src.api.routes.assistant._dataset_review import _serialize_dataset_review_context preview_mock = MagicMock() preview_mock.preview_id = "pv-1" preview_mock.preview_status = MagicMock(value="READY") preview_mock.preview_fingerprint = "fp-abc" session = _make_session_mock(previews=[preview_mock]) ctx = _serialize_dataset_review_context(session) assert ctx["preview"]["preview_id"] == "pv-1" def test_context_no_previews(self): from src.api.routes.assistant._dataset_review import _serialize_dataset_review_context session = _make_session_mock(previews=[]) ctx = _serialize_dataset_review_context(session) assert ctx["preview"] is None def test_context_without_some_attrs(self): """Handle missing attributes gracefully.""" from src.api.routes.assistant._dataset_review import _serialize_dataset_review_context s = MagicMock() s.session_id = "sess-1" s.version = 0 s.dataset_ref = "ds" s.dataset_id = None s.environment_id = "env-1" # Some attributes may be missing del s.readiness_state s.recommended_action = MagicMock(value="NONE") s.status = MagicMock(value="ACTIVE") s.current_phase = MagicMock(value="REVIEW") s.findings = [] s.imported_filters = [] s.execution_mappings = [] s.semantic_fields = [] s.previews = [] ctx = _serialize_dataset_review_context(s) # Should handle gracefully or raise AttributeError assert ctx["session_id"] == "sess-1" class TestLoadDatasetReviewContext: """_load_dataset_review_context — context loading from repository.""" def test_load_with_valid_session(self): from src.api.routes.assistant._dataset_review import _load_dataset_review_context from src.schemas.auth import User db = MagicMock() user = 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=[]) session = _make_session_mock(user_id="user-1") with patch("src.api.routes.assistant._dataset_review.DatasetReviewSessionRepository") as mock_repo: mock_repo.return_value.load_session_detail.return_value = session result = _load_dataset_review_context("sess-1", user, db) assert result is not None assert result["session_id"] == "sess-1" def test_load_with_none_session_id(self): from src.api.routes.assistant._dataset_review import _load_dataset_review_context from src.schemas.auth import User db = MagicMock() user = 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=[]) result = _load_dataset_review_context(None, user, db) assert result is None def test_load_with_wrong_user_raises_404(self): from src.api.routes.assistant._dataset_review import _load_dataset_review_context from src.schemas.auth import User db = MagicMock() user = 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=[]) # Session belongs to different user session = _make_session_mock(user_id="user-2") with patch("src.api.routes.assistant._dataset_review.DatasetReviewSessionRepository") as mock_repo: mock_repo.return_value.load_session_detail.return_value = session with pytest.raises(HTTPException) as exc: _load_dataset_review_context("sess-1", user, db) assert exc.value.status_code == 404 def test_load_with_missing_session_raises_404(self): from src.api.routes.assistant._dataset_review import _load_dataset_review_context from src.schemas.auth import User db = MagicMock() user = 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=[]) with patch("src.api.routes.assistant._dataset_review.DatasetReviewSessionRepository") as mock_repo: mock_repo.return_value.load_session_detail.return_value = None with pytest.raises(HTTPException) as exc: _load_dataset_review_context("sess-1", user, db) assert exc.value.status_code == 404 class TestExtractDatasetReviewTarget: """_extract_dataset_review_target — target extraction.""" @pytest.mark.parametrize("msg,expected_kind,expected_id", [ ("target:field=abc123", "field", "abc123"), ("focus:field=xyz789", "field", "xyz789"), ("target:mapping=map_1", "mapping", "map_1"), ("target:finding=fnd_1", "finding", "fnd_1"), ("target:filter=flt_1", "filter", "flt_1"), ("focus = finding=abc", "finding", "abc"), ("target = field = def", "field", "def"), ("no target here", None, None), ("", None, None), ]) def test_extract(self, msg, expected_kind, expected_id): from src.api.routes.assistant._dataset_review import _extract_dataset_review_target kind, tid = _extract_dataset_review_target(msg) assert kind == expected_kind assert tid == expected_id class TestMatchDatasetReviewField: """_match_dataset_review_field — field matching.""" def test_match_by_id(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, "target:field=fld-1") assert result is not None assert result["field_id"] == "fld-1" 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, "please update region field") 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": "region", "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_no_match(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, "something completely different") assert result is None def test_empty_fields(self): from src.api.routes.assistant._dataset_review import _match_dataset_review_field ctx = {"semantic_fields": []} result = _match_dataset_review_field(ctx, "region") assert result is None class TestExtractQuotedSegment: """_extract_quoted_segment — quoted command segment extraction.""" @pytest.mark.parametrize("msg,label,expected", [ ('verbose_name="My Label"', "verbose_name", "My Label"), ("verbose_name='My Label'", "verbose_name", "My Label"), ('description="Some desc"', "description", "Some desc"), ('format="text"', "display_format", "text"), ('verbose_name: "Label"', "verbose_name", "Label"), ('verbose_name = "Label"', "verbose_name", "Label"), ("no label here", "verbose_name", None), ]) def test_extract(self, msg, label, expected): from src.api.routes.assistant._dataset_review import _extract_quoted_segment result = _extract_quoted_segment(msg, label) assert result == expected class TestPlanDatasetReviewIntent: """_plan_dataset_review_intent — intent planning from message.""" @pytest.fixture def context(self): return { "session_id": "sess-1", "version": 5, "readiness_state": "REVIEW_READY", "recommended_action": "REVIEW_DOCUMENTATION", "mappings": [ {"mapping_id": "m-1", "requires_explicit_approval": True, "approval_state": "PENDING"}, {"mapping_id": "m-2", "requires_explicit_approval": True, "approval_state": "PENDING"}, ], "findings": [{"code": "TEST"}], "imported_filters": [{"filter_name": "region"}], "preview": {"preview_status": "missing"}, "semantic_fields": [], } def test_approve_mappings(self, context): from src.api.routes.assistant._dataset_review import _plan_dataset_review_intent result = _plan_dataset_review_intent("approve mappings", context) assert result is not None assert result["operation"] == "dataset_review_approve_mappings" assert result["entities"]["mapping_ids"] == ["m-1", "m-2"] assert result["requires_confirmation"] is True def test_approve_mappings_russian(self, context): from src.api.routes.assistant._dataset_review import _plan_dataset_review_intent result = _plan_dataset_review_intent("подтверди маппинг", context) assert result is not None assert result["operation"] == "dataset_review_approve_mappings" def test_generate_sql_preview(self, context): from src.api.routes.assistant._dataset_review import _plan_dataset_review_intent result = _plan_dataset_review_intent("generate sql preview", context) assert result is not None assert result["operation"] == "dataset_review_generate_sql_preview" def test_generate_preview_russian(self, context): from src.api.routes.assistant._dataset_review import _plan_dataset_review_intent result = _plan_dataset_review_intent("сгенерируй превью", context) assert result is not None assert result["operation"] == "dataset_review_generate_sql_preview" def test_set_field_semantics_with_target(self, context): from src.api.routes.assistant._dataset_review import _plan_dataset_review_intent ctx_with_field = dict(context) ctx_with_field["semantic_fields"] = [ {"field_id": "fld-1", "field_name": "region", "verbose_name": "Region", "candidates": []} ] result = _plan_dataset_review_intent('set field semantics target:field=fld-1 verbose_name="Country"', ctx_with_field) assert result is not None assert result["operation"] == "dataset_review_set_field_semantics" assert result["entities"]["verbose_name"] == "Country" def test_set_field_semantics_no_field_returns_none(self, context): from src.api.routes.assistant._dataset_review import _plan_dataset_review_intent # No matching field in message and no target result = _plan_dataset_review_intent("set field semantics", context) assert result is None def test_set_field_semantics_with_lock(self, context): from src.api.routes.assistant._dataset_review import _plan_dataset_review_intent ctx_with_field = dict(context) ctx_with_field["semantic_fields"] = [ {"field_id": "fld-1", "field_name": "region", "verbose_name": "Region", "candidates": []} ] result = _plan_dataset_review_intent('apply field semantics target:field=fld-1 lock it verbose_name="Country"', ctx_with_field) assert result is not None assert result["entities"]["lock_field"] is True def test_answer_context(self, context): from src.api.routes.assistant._dataset_review import _plan_dataset_review_intent result = _plan_dataset_review_intent("what are the filters?", context) assert result is not None assert result["operation"] == "dataset_review_answer_context" assert "readiness_state" in result["entities"]["summary"] assert result["requires_confirmation"] is False def test_answer_context_russian(self, context): from src.api.routes.assistant._dataset_review import _plan_dataset_review_intent result = _plan_dataset_review_intent("фильтр", context) assert result is not None assert result["operation"] == "dataset_review_answer_context" def test_unrelated_message_returns_none(self, context): from src.api.routes.assistant._dataset_review import _plan_dataset_review_intent result = _plan_dataset_review_intent("hello world", context) assert result is None class TestDispatchDatasetReviewIntent: """_dispatch_dataset_review_intent — intent dispatch.""" @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 test_answer_context(self, user, config_manager): from src.api.routes.assistant._dataset_review_dispatch import _dispatch_dataset_review_intent db = MagicMock() intent = { "operation": "dataset_review_answer_context", "entities": { "dataset_review_session_id": "sess-1", "session_version": 5, "summary": "Session sess-1: readiness=READY", }, } text, task_id, actions = ( __import__("asyncio").run(_dispatch_dataset_review_intent(intent, user, config_manager, db)) ) assert "READY" in text assert task_id is None def test_missing_session_id_raises_422(self, user, config_manager): from src.api.routes.assistant._dataset_review_dispatch import _dispatch_dataset_review_intent db = MagicMock() intent = {"operation": "test", "entities": {}} with pytest.raises(HTTPException) as exc: __import__("asyncio").run(_dispatch_dataset_review_intent(intent, user, config_manager, db)) assert exc.value.status_code == 422 def test_unsupported_operation_raises_400(self, user, config_manager): from src.api.routes.assistant._dataset_review_dispatch import _dispatch_dataset_review_intent db = MagicMock() intent = { "operation": "dataset_review_unknown", "entities": {"dataset_review_session_id": "sess-1", "session_version": 5}, } session = _make_session_mock() repository_cls = "src.api.routes.assistant._dataset_review_dispatch.DatasetReviewSessionRepository" with patch(repository_cls) 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() with pytest.raises(HTTPException) as exc: __import__("asyncio").run(_dispatch_dataset_review_intent(intent, user, config_manager, db)) assert exc.value.status_code == 400 def test_approve_mappings_success(self, user, config_manager): from src.api.routes.assistant._dataset_review_dispatch import _dispatch_dataset_review_intent from src.models.dataset_review import ApprovalState, ReadinessState db = MagicMock() mapping = _make_mapping_mock(mapping_id="m-1", filter_id="fl-1", variable_id="v-1", approval_state=ApprovalState.PENDING.value, requires_explicit_approval=True) session = _make_session_mock(execution_mappings=[mapping], readiness_state=ReadinessState.MAPPING_REVIEW_NEEDED, user_id="user-1") intent = { "operation": "dataset_review_approve_mappings", "entities": { "dataset_review_session_id": "sess-1", "session_version": 5, "mapping_ids": ["m-1"], }, } repository_cls = "src.api.routes.assistant._dataset_review_dispatch.DatasetReviewSessionRepository" with patch(repository_cls) 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 assert "1 mapping" in text def test_approve_mappings_no_pending_raises_409(self, user, config_manager): from src.api.routes.assistant._dataset_review_dispatch import _dispatch_dataset_review_intent db = MagicMock() intent = { "operation": "dataset_review_approve_mappings", "entities": { "dataset_review_session_id": "sess-1", "session_version": 5, "mapping_ids": [], }, } with pytest.raises(HTTPException) as exc: __import__("asyncio").run(_dispatch_dataset_review_intent(intent, user, config_manager, db)) assert exc.value.status_code == 409 assert "No pending mappings" in exc.value.detail def test_approve_mappings_no_match_raises_409(self, user, config_manager): from src.api.routes.assistant._dataset_review_dispatch import _dispatch_dataset_review_intent from src.models.dataset_review import ApprovalState db = MagicMock() session = _make_session_mock(execution_mappings=[], user_id="user-1") intent = { "operation": "dataset_review_approve_mappings", "entities": { "dataset_review_session_id": "sess-1", "session_version": 5, "mapping_ids": ["m-nonexistent"], }, } repository_cls = "src.api.routes.assistant._dataset_review_dispatch.DatasetReviewSessionRepository" with patch(repository_cls) 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 == 409 def test_set_field_semantics_success(self, user, config_manager): from src.api.routes.assistant._dataset_review_dispatch import _dispatch_dataset_review_intent field = _make_field_mock() session = _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", "verbose_name": "Country", }, } db = MagicMock() repository_cls = "src.api.routes.assistant._dataset_review_dispatch.DatasetReviewSessionRepository" with patch(repository_cls) 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() with patch("src.api.routes.assistant._dataset_review_dispatch._update_semantic_field_state") as mock_update: mock_update.return_value = None text, task_id, actions = ( __import__("asyncio").run(_dispatch_dataset_review_intent(intent, user, config_manager, db)) ) assert "Updated semantic field" in text def test_set_field_semantics_missing_field_raises_422(self, user, config_manager): from src.api.routes.assistant._dataset_review_dispatch import _dispatch_dataset_review_intent session = _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": "", }, } db = MagicMock() repository_cls = "src.api.routes.assistant._dataset_review_dispatch.DatasetReviewSessionRepository" with patch(repository_cls) 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 == 422 def test_session_not_found_raises_404(self, user, config_manager): from src.api.routes.assistant._dataset_review_dispatch import _dispatch_dataset_review_intent db = MagicMock() intent = { "operation": "dataset_review_approve_mappings", "entities": {"dataset_review_session_id": "sess-1", "session_version": 5, "mapping_ids": ["m-1"]}, } repository_cls = "src.api.routes.assistant._dataset_review_dispatch.DatasetReviewSessionRepository" with patch(repository_cls) as mock_repo_cls: mock_repo = MagicMock() mock_repo_cls.return_value = mock_repo mock_repo.load_session_detail.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 def test_version_conflict_raises_409(self, user, config_manager): from src.api.routes.assistant._dataset_review_dispatch import _dispatch_dataset_review_intent, DatasetReviewSessionVersionConflictError db = MagicMock() intent = { "operation": "dataset_review_approve_mappings", "entities": {"dataset_review_session_id": "sess-1", "session_version": 4, "mapping_ids": ["m-1"]}, } session = _make_session_mock(user_id="user-1") repository_cls = "src.api.routes.assistant._dataset_review_dispatch.DatasetReviewSessionRepository" with patch(repository_cls) 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.side_effect = DatasetReviewSessionVersionConflictError( session_id="sess-1", expected_version=4, actual_version=5 ) with pytest.raises(HTTPException) as exc: __import__("asyncio").run(_dispatch_dataset_review_intent(intent, user, config_manager, db)) assert exc.value.status_code == 409 assert "session_version_conflict" in exc.value.detail["error_code"] def test_generate_sql_preview_success(self, user, config_manager): from src.api.routes.assistant._dataset_review_dispatch import _dispatch_dataset_review_intent session = _make_session_mock(user_id="user-1") db = MagicMock() intent = { "operation": "dataset_review_generate_sql_preview", "entities": { "dataset_review_session_id": "sess-1", "session_version": 5, }, } repository_cls = "src.api.routes.assistant._dataset_review_dispatch.DatasetReviewSessionRepository" orchestrator_cls = "src.api.routes.assistant._dataset_review_dispatch.DatasetReviewOrchestrator" with patch(repository_cls) as mock_repo_cls, patch(orchestrator_cls) as mock_orch_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() mock_orch = MagicMock() mock_orch_cls.return_value = mock_orch preview_result = MagicMock() preview_result.preview = MagicMock() preview_result.preview.preview_status = "READY" mock_orch.prepare_launch_preview.return_value = preview_result text, task_id, actions = ( __import__("asyncio").run(_dispatch_dataset_review_intent(intent, user, config_manager, db)) ) assert "READY" in text class TestDatasetReviewConflictHttpException: """_dataset_review_conflict_http_exception — version conflict error mapping.""" def test_conflict_exception(self): from src.api.routes.assistant._dataset_review_dispatch import ( _dataset_review_conflict_http_exception, DatasetReviewSessionVersionConflictError, ) exc = DatasetReviewSessionVersionConflictError( session_id="sess-1", expected_version=4, actual_version=5 ) http_exc = _dataset_review_conflict_http_exception(exc) assert http_exc.status_code == 409 assert http_exc.detail["error_code"] == "session_version_conflict" assert http_exc.detail["session_id"] == "sess-1" # #endregion Test.Assistant.DatasetReview