Files
ss-tools/backend/tests/services/dataset_review/test_session_repository.py

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