# #region Test.DatasetReview.SessionRepository [C:4] [TYPE Module] [SEMANTICS test, session, repository, dataset, review] # @BRIEF Tests for DatasetReviewSessionRepository — persistence and retrieval. # @RELATION BINDS_TO -> [DatasetReviewSessionRepository] import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent / "src")) from datetime import UTC, datetime from unittest.mock import MagicMock, patch import pytest from sqlalchemy.orm.exc import StaleDataError from sqlalchemy.orm import Session from src.services.dataset_review.repositories.session_repository import ( DatasetReviewSessionRepository, DatasetReviewSessionVersionConflictError, ) from src.models.dataset_review import ( DatasetReviewSession, DatasetProfile, ValidationFinding, CompiledPreview, DatasetRunContext, ImportedFilter, TemplateVariable, ExecutionMapping, SessionCollaborator, SemanticFieldEntry, ClarificationSession, ClarificationQuestion, ) @pytest.fixture def mock_db(): return MagicMock(spec=Session) @pytest.fixture def repo(mock_db): return DatasetReviewSessionRepository(mock_db) class TestInit: def test_creates_event_logger(self, mock_db): repo = DatasetReviewSessionRepository(mock_db) assert repo.db is mock_db assert repo.event_logger is not None class TestGetOwnedSession: def test_found(self, repo, mock_db): mock_session = MagicMock(spec=DatasetReviewSession) mock_db.query.return_value.filter.return_value.first.return_value = mock_session result = repo._get_owned_session("s1", "u1") assert result is mock_session def test_not_found_raises(self, repo, mock_db): mock_db.query.return_value.filter.return_value.first.return_value = None with pytest.raises(ValueError, match="Session not found"): repo._get_owned_session("s1", "u1") class TestCreateSession: def test_creates_and_refreshes(self, repo, mock_db): session = MagicMock(spec=DatasetReviewSession) result = repo.create_session(session) mock_db.add.assert_called_once_with(session) mock_db.commit.assert_called_once() mock_db.refresh.assert_called_once_with(session) assert result is session class TestRequireSessionVersion: def test_version_matches(self, repo): session = MagicMock(spec=DatasetReviewSession) session.version = 3 result = repo.require_session_version(session, 3) assert result is session def test_version_conflict(self, repo): session = MagicMock(spec=DatasetReviewSession) session.version = 2 with pytest.raises(DatasetReviewSessionVersionConflictError) as exc: repo.require_session_version(session, 5) assert exc.value.expected_version == 5 assert exc.value.actual_version == 2 def test_version_none(self, repo): session = MagicMock(spec=DatasetReviewSession) session.version = None result = repo.require_session_version(session, 0) assert result is session class TestBumpSessionVersion: def test_bumps_version(self, repo): session = MagicMock(spec=DatasetReviewSession) session.version = 2 result = repo.bump_session_version(session) assert result == 3 assert session.version == 3 def test_bumps_from_none(self, repo): session = MagicMock(spec=DatasetReviewSession) session.version = None result = repo.bump_session_version(session) assert result == 1 assert session.version == 1 class TestCommitSessionMutation: def test_commit_success_no_refresh_targets(self, repo, mock_db): session = MagicMock(spec=DatasetReviewSession) session.version = 1 result = repo.commit_session_mutation(session) mock_db.commit.assert_called_once() mock_db.refresh.assert_called_once_with(session) assert result is session def test_commit_with_refresh_targets(self, repo, mock_db): session = MagicMock(spec=DatasetReviewSession) session.version = 1 target = MagicMock() result = repo.commit_session_mutation(session, refresh_targets=[target]) assert mock_db.refresh.call_count == 2 # session + target assert result is session def test_commit_stale_data_error(self, repo, mock_db): session = MagicMock(spec=DatasetReviewSession) session.version = 2 session.session_id = "s1" mock_db.commit.side_effect = StaleDataError("stale") mock_db.query.return_value.filter.return_value.first.return_value = (5,) with pytest.raises(DatasetReviewSessionVersionConflictError) as exc: repo.commit_session_mutation(session, expected_version=2) mock_db.rollback.assert_called_once() assert exc.value.expected_version == 2 assert exc.value.actual_version == 5 def test_commit_stale_no_row(self, repo, mock_db): session = MagicMock(spec=DatasetReviewSession) session.version = 2 session.session_id = "s1" mock_db.commit.side_effect = StaleDataError("stale") mock_db.query.return_value.filter.return_value.first.return_value = None with pytest.raises(DatasetReviewSessionVersionConflictError) as exc: repo.commit_session_mutation(session, expected_version=2) assert exc.value.actual_version == 0 def test_commit_custom_expected_version(self, repo, mock_db): session = MagicMock(spec=DatasetReviewSession) session.version = 99 result = repo.commit_session_mutation(session, expected_version=5) # bump goes from 99 to 100, but expected_version=5 is used for error reporting mock_db.commit.assert_called_once() assert result is session class TestLoadSessionDetail: def test_found(self, repo, mock_db): mock_session = MagicMock(spec=DatasetReviewSession) mock_db.query.return_value.outerjoin.return_value.options.return_value.filter.return_value.filter.return_value.first.return_value = mock_session result = repo.load_session_detail("s1", "u1") assert result is mock_session def test_not_found(self, repo, mock_db): mock_db.query.return_value.outerjoin.return_value.options.return_value.filter.return_value.filter.return_value.first.return_value = None result = repo.load_session_detail("s1", "u1") assert result is None class TestSaveDelegates: def test_save_profile_and_findings(self, repo, mock_db): session = MagicMock(spec=DatasetReviewSession) mock_db.query.return_value.filter.return_value.first.return_value = session profile = MagicMock(spec=DatasetProfile) findings = [MagicMock(spec=ValidationFinding)] with patch("src.services.dataset_review.repositories.repository_pkg._mutations.save_profile_and_findings") as mock_save: mock_save.return_value = session result = repo.save_profile_and_findings("s1", "u1", profile, findings) mock_save.assert_called_once() assert result is session def test_save_recovery_state(self, repo, mock_db): session = MagicMock(spec=DatasetReviewSession) mock_db.query.return_value.filter.return_value.first.return_value = session imported_filters = [MagicMock(spec=ImportedFilter)] template_variables = [MagicMock(spec=TemplateVariable)] execution_mappings = [MagicMock(spec=ExecutionMapping)] with patch("src.services.dataset_review.repositories.repository_pkg._mutations.save_recovery_state") as mock_save: mock_save.return_value = session result = repo.save_recovery_state("s1", "u1", imported_filters, template_variables, execution_mappings) mock_save.assert_called_once() assert result is session def test_save_preview(self, repo, mock_db): session = MagicMock(spec=DatasetReviewSession) mock_db.query.return_value.filter.return_value.first.return_value = session preview = MagicMock(spec=CompiledPreview) with patch("src.services.dataset_review.repositories.repository_pkg._mutations.save_preview") as mock_save: mock_save.return_value = preview result = repo.save_preview("s1", "u1", preview) mock_save.assert_called_once() assert result is preview def test_save_run_context(self, repo, mock_db): session = MagicMock(spec=DatasetReviewSession) mock_db.query.return_value.filter.return_value.first.return_value = session run_context = MagicMock(spec=DatasetRunContext) with patch("src.services.dataset_review.repositories.repository_pkg._mutations.save_run_context") as mock_save: mock_save.return_value = run_context result = repo.save_run_context("s1", "u1", run_context) mock_save.assert_called_once() assert result is run_context class TestListSessionsForUser: def test_returns_list(self, repo, mock_db): mock_sessions = [MagicMock(spec=DatasetReviewSession), MagicMock(spec=DatasetReviewSession)] mock_db.query.return_value.filter.return_value.order_by.return_value.all.return_value = mock_sessions result = repo.list_sessions_for_user("u1") assert len(result) == 2 assert result == mock_sessions def test_empty(self, repo, mock_db): mock_db.query.return_value.filter.return_value.order_by.return_value.all.return_value = [] result = repo.list_sessions_for_user("u1") assert result == [] class TestVersionConflictError: def test_str(self): exc = DatasetReviewSessionVersionConflictError("s1", 1, 5) assert str(exc) == "Session version conflict: expected 1, actual 5" assert exc.session_id == "s1" assert exc.expected_version == 1 assert exc.actual_version == 5 # #endregion Test.DatasetReview.SessionRepository