242 lines
9.8 KiB
Python
242 lines
9.8 KiB
Python
# #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
|