# #region Test.DatasetReviewSemanticModels [C:3] [TYPE Module] [SEMANTICS test,sqlalchemy,dataset,review,semantic,model] # @BRIEF Verify SemanticSource, SemanticFieldEntry, and SemanticCandidate SQLAlchemy models — construction, defaults, FK constraints, relationships. # @RELATION BINDS_TO -> [DatasetReviewSemanticModels] # @TEST_EDGE: missing_session_id -> FK violation on semantic source # @TEST_EDGE: cascade_delete -> Session delete cascades to semantic entries # @TEST_EDGE: field_lock -> is_locked defaults to False # @TEST_EDGE: candidate_status_transition -> Status defaults to PROPOSED import uuid from datetime import UTC, datetime from sqlalchemy import create_engine, text from sqlalchemy.orm import Session as SASession import pytest from src.models.dataset_review_pkg._session_models import DatasetReviewSession from src.models.dataset_review_pkg._semantic_models import ( Base, SemanticCandidate, SemanticFieldEntry, SemanticSource, ) from src.models.dataset_review_pkg._enums import ( CandidateMatchType, CandidateStatus, FieldKind, FieldProvenance, SemanticSourceStatus, SemanticSourceType, TrustLevel, ) from src.models.mapping import Environment @pytest.fixture(scope="function") def db_session(): """Fresh in-memory SQLite with FK enforcement using single connection.""" engine = create_engine("sqlite:///:memory:", connect_args={"check_same_thread": False}) conn = engine.connect() conn.execute(text("PRAGMA foreign_keys=ON")) # Create the raw users table FIRST — multiple model tables FK-reference it conn.execute(text("CREATE TABLE IF NOT EXISTS users (id VARCHAR PRIMARY KEY)")) from src.models.mapping import Base as MappingBase MappingBase.metadata.create_all(conn) Base.metadata.create_all(conn) conn.commit() session = SASession(bind=conn) env = Environment(id="env-1", name="Test", url="https://example.com", credentials_id="cred-1") session.add(env) session.execute(text("INSERT OR IGNORE INTO users (id) VALUES ('user-1')")) session.commit() yield session session.close() conn.close() def _create_session(db_session) -> DatasetReviewSession: """Helper to create a valid session.""" rev = DatasetReviewSession( user_id="user-1", environment_id="env-1", source_kind="superset", source_input="test", dataset_ref="ds_1", ) db_session.add(rev) db_session.flush() return rev class TestSemanticSource: """Verify SemanticSource model.""" def test_create_minimal(self, db_session): """Minimal required fields produce valid semantic source.""" session = _create_session(db_session) src = SemanticSource( session_id=session.session_id, source_type=SemanticSourceType.AI_GENERATED, source_ref="ref-1", source_version="v1", display_name="AI Suggestions", trust_level=TrustLevel.GENERATED, ) db_session.add(src) db_session.flush() assert src.source_id is not None assert isinstance(uuid.UUID(src.source_id), uuid.UUID) assert src.source_type == SemanticSourceType.AI_GENERATED assert src.trust_level == TrustLevel.GENERATED assert src.status == SemanticSourceStatus.AVAILABLE assert src.schema_overlap_score is None assert src.created_at is not None def test_all_fields(self, db_session): """All fields populated.""" session = _create_session(db_session) src = SemanticSource( session_id=session.session_id, source_type=SemanticSourceType.UPLOADED_FILE, source_ref="file.csv", source_version="v2", display_name="Uploaded Dictionary", trust_level=TrustLevel.TRUSTED, schema_overlap_score=0.85, status=SemanticSourceStatus.APPLIED, ) db_session.add(src) db_session.flush() assert src.schema_overlap_score == 0.85 assert src.status == SemanticSourceStatus.APPLIED def test_all_source_types(self, db_session): """All SemanticSourceType values accepted.""" session = _create_session(db_session) for st in SemanticSourceType: src = SemanticSource( session_id=session.session_id, source_type=st, source_ref="r", source_version="v", display_name="D", trust_level=TrustLevel.CANDIDATE, ) db_session.add(src) db_session.flush() assert db_session.query(SemanticSource).count() == len(SemanticSourceType) def test_all_trust_levels(self, db_session): """All TrustLevel values accepted.""" session = _create_session(db_session) for tl in TrustLevel: src = SemanticSource( session_id=session.session_id, source_type=SemanticSourceType.REFERENCE_DATASET, source_ref="r", source_version="v", display_name="D", trust_level=tl, ) db_session.add(src) db_session.flush() assert db_session.query(SemanticSource).count() == len(TrustLevel) def test_relationship(self, db_session): """Navigate source -> session.""" session = _create_session(db_session) src = SemanticSource( session_id=session.session_id, source_type=SemanticSourceType.NEIGHBOR_DATASET, source_ref="r", source_version="v", display_name="D", trust_level=TrustLevel.RECOMMENDED, ) db_session.add(src) db_session.flush() assert src.session.session_id == session.session_id assert src.session in db_session class TestSemanticFieldEntry: """Verify SemanticFieldEntry model.""" def test_create_minimal(self, db_session): """Minimal required fields.""" session = _create_session(db_session) entry = SemanticFieldEntry( session_id=session.session_id, field_name="revenue", field_kind=FieldKind.METRIC, last_changed_by="user-1", ) db_session.add(entry) db_session.flush() assert entry.field_id is not None assert entry.field_name == "revenue" assert entry.field_kind == FieldKind.METRIC assert entry.provenance == FieldProvenance.UNRESOLVED assert entry.is_locked is False assert entry.has_conflict is False assert entry.needs_review is True assert entry.last_changed_by == "user-1" assert entry.created_at is not None assert entry.updated_at is not None # Nullable fields assert entry.verbose_name is None assert entry.description is None assert entry.display_format is None assert entry.source_id is None assert entry.source_version is None assert entry.confidence_rank is None assert entry.user_feedback is None def test_all_fields(self, db_session): """All fields populated.""" session = _create_session(db_session) entry = SemanticFieldEntry( session_id=session.session_id, field_name="orders_count", field_kind=FieldKind.METRIC, verbose_name="Orders Count", description="Total number of orders", display_format=".0f", provenance=FieldProvenance.DICTIONARY_EXACT, source_id="src-1", source_version="v3", confidence_rank=1, is_locked=True, has_conflict=False, needs_review=False, last_changed_by="user-2", user_feedback="Looks correct", ) db_session.add(entry) db_session.flush() assert entry.verbose_name == "Orders Count" assert entry.description == "Total number of orders" assert entry.provenance == FieldProvenance.DICTIONARY_EXACT assert entry.is_locked is True assert entry.user_feedback == "Looks correct" def test_cascade_delete(self, db_session): """Session delete cascades to field entries.""" session = _create_session(db_session) entry = SemanticFieldEntry( session_id=session.session_id, field_name="col_a", field_kind=FieldKind.COLUMN, last_changed_by="user-1", ) db_session.add(entry) db_session.flush() db_session.delete(session) db_session.flush() assert db_session.query(SemanticFieldEntry).count() == 0 def test_relationship(self, db_session): """Navigate entry -> session.""" session = _create_session(db_session) entry = SemanticFieldEntry( session_id=session.session_id, field_name="col_a", field_kind=FieldKind.COLUMN, last_changed_by="user-1", ) db_session.add(entry) db_session.flush() assert entry.session is session class TestSemanticCandidate: """Verify SemanticCandidate model.""" def test_create_minimal(self, db_session): """Minimal required fields.""" session = _create_session(db_session) entry = self._create_field_entry(db_session, session) cand = SemanticCandidate( field_id=entry.field_id, candidate_rank=1, match_type=CandidateMatchType.EXACT, confidence_score=0.95, ) db_session.add(cand) db_session.flush() assert cand.candidate_id is not None assert cand.candidate_rank == 1 assert cand.match_type == CandidateMatchType.EXACT assert cand.confidence_score == 0.95 assert cand.status == CandidateStatus.PROPOSED assert cand.created_at is not None assert cand.proposed_verbose_name is None assert cand.proposed_description is None def test_all_fields(self, db_session): """All candidate fields populated.""" session = _create_session(db_session) entry = self._create_field_entry(db_session, session) cand = SemanticCandidate( field_id=entry.field_id, source_id="src-2", candidate_rank=1, match_type=CandidateMatchType.FUZZY, confidence_score=0.72, proposed_verbose_name="Customer Name", proposed_description="Full customer name", proposed_display_format="text", status=CandidateStatus.ACCEPTED, ) db_session.add(cand) db_session.flush() assert cand.proposed_verbose_name == "Customer Name" assert cand.status == CandidateStatus.ACCEPTED assert cand.source_id == "src-2" def test_relationship_field(self, db_session): """Navigate candidate -> field -> session.""" session = _create_session(db_session) entry = self._create_field_entry(db_session, session) cand = SemanticCandidate( field_id=entry.field_id, candidate_rank=1, match_type=CandidateMatchType.GENERATED, confidence_score=0.5, ) db_session.add(cand) db_session.flush() assert cand.field is entry assert cand.field.session is session def test_cascade_field_delete(self, db_session): """Field entry delete cascades to candidates.""" session = _create_session(db_session) entry = self._create_field_entry(db_session, session) cand = SemanticCandidate( field_id=entry.field_id, candidate_rank=1, match_type=CandidateMatchType.EXACT, confidence_score=0.9, ) db_session.add(cand) db_session.flush() db_session.delete(entry) db_session.flush() assert db_session.query(SemanticCandidate).count() == 0 def test_multiple_candidates(self, db_session): """Field entry can have multiple candidates.""" session = _create_session(db_session) entry = self._create_field_entry(db_session, session) for i, mt in enumerate(CandidateMatchType): cand = SemanticCandidate( field_id=entry.field_id, candidate_rank=i + 1, match_type=mt, confidence_score=1.0 - (i * 0.1), ) db_session.add(cand) db_session.flush() assert len(entry.candidates) == len(CandidateMatchType) def _create_field_entry(self, db_session, session) -> SemanticFieldEntry: entry = SemanticFieldEntry( session_id=session.session_id, field_name="test_col", field_kind=FieldKind.COLUMN, last_changed_by="user-1", ) db_session.add(entry) db_session.flush() return entry # #endregion Test.DatasetReviewSemanticModels