fix: repository collaborator access and stale findings persistence issues

This commit is contained in:
2026-03-16 23:43:37 +03:00
parent f4416c3ebb
commit c957207bce
2 changed files with 69 additions and 7 deletions

View File

@@ -14,7 +14,9 @@ from src.models.dataset_review import (
FindingArea, FindingArea,
FindingSeverity, FindingSeverity,
ReadinessState, ReadinessState,
RecommendedAction RecommendedAction,
SessionCollaborator,
SessionCollaboratorRole
) )
from src.services.dataset_review.repositories.session_repository import DatasetReviewSessionRepository from src.services.dataset_review.repositories.session_repository import DatasetReviewSessionRepository
@@ -75,6 +77,32 @@ def test_load_session_detail_ownership(db_session):
loaded_wrong = repo.load_session_detail(session.session_id, "wrong_user") loaded_wrong = repo.load_session_detail(session.session_id, "wrong_user")
assert loaded_wrong is None assert loaded_wrong is None
def test_load_session_detail_collaborator(db_session):
# @PURPOSE: Verify collaborator access in detail loading.
repo = DatasetReviewSessionRepository(db_session)
session = DatasetReviewSession(
user_id="user1", environment_id="env1", source_kind="superset_link",
source_input="http://link", dataset_ref="dataset1"
)
repo.create_session(session)
# Add collaborator
collab_user = User(id="collab1", username="collab", email="c@e.com", password_hash="p")
db_session.add(collab_user)
collaborator = SessionCollaborator(
session_id=session.session_id,
user_id="collab1",
role=SessionCollaboratorRole.REVIEWER
)
db_session.add(collaborator)
db_session.commit()
# Collaborator access
loaded = repo.load_session_detail(session.session_id, "collab1")
assert loaded is not None
assert loaded.session_id == session.session_id
def test_save_preview_marks_stale(db_session): def test_save_preview_marks_stale(db_session):
# @PURPOSE: Verify that saving a new preview marks old ones as stale. # @PURPOSE: Verify that saving a new preview marks old ones as stale.
repo = DatasetReviewSessionRepository(db_session) repo = DatasetReviewSessionRepository(db_session)
@@ -128,6 +156,23 @@ def test_save_profile_and_findings(db_session):
assert len(updated_session.findings) == 1 assert len(updated_session.findings) == 1
assert updated_session.findings[0].code == "ERR1" assert updated_session.findings[0].code == "ERR1"
# Verify removal of old findings
new_finding = ValidationFinding(
session_id=session.session_id,
area=FindingArea.DATASET_PROFILE,
severity=FindingSeverity.WARNING,
code="WARN1",
title="Warning",
message="Something"
)
repo.save_profile_and_findings(session.session_id, "user1", profile, [new_finding])
db_session.expire_all()
final_session = repo.load_session_detail(session.session_id, "user1")
assert len(final_session.findings) == 1
assert final_session.findings[0].code == "WARN1"
def test_save_run_context(db_session): def test_save_run_context(db_session):
# @PURPOSE: Verify saving of run context. # @PURPOSE: Verify saving of run context.
repo = DatasetReviewSessionRepository(db_session) repo = DatasetReviewSessionRepository(db_session)

View File

@@ -10,13 +10,15 @@
# @POST: session aggregate reads are structurally consistent and writes preserve ownership and version semantics. # @POST: session aggregate reads are structurally consistent and writes preserve ownership and version semantics.
from typing import Optional, List from typing import Optional, List
from sqlalchemy import or_
from sqlalchemy.orm import Session, joinedload from sqlalchemy.orm import Session, joinedload
from src.models.dataset_review import ( from src.models.dataset_review import (
DatasetReviewSession, DatasetReviewSession,
DatasetProfile, DatasetProfile,
ValidationFinding, ValidationFinding,
CompiledPreview, CompiledPreview,
DatasetRunContext DatasetRunContext,
SessionCollaborator
) )
from src.core.logger import belief_scope from src.core.logger import belief_scope
@@ -44,8 +46,9 @@ class DatasetReviewSessionRepository:
@PRE: user_id must match session owner or authorized collaborator. @PRE: user_id must match session owner or authorized collaborator.
""" """
with belief_scope("DatasetReviewSessionRepository.load_session_detail"): with belief_scope("DatasetReviewSessionRepository.load_session_detail"):
# Note: We check user_id to enforce the ownership_scope invariant. # Check if user is owner or collaborator
return self.db.query(DatasetReviewSession)\ return self.db.query(DatasetReviewSession)\
.outerjoin(SessionCollaborator, DatasetReviewSession.session_id == SessionCollaborator.session_id)\
.options( .options(
joinedload(DatasetReviewSession.profile), joinedload(DatasetReviewSession.profile),
joinedload(DatasetReviewSession.findings), joinedload(DatasetReviewSession.findings),
@@ -60,7 +63,12 @@ class DatasetReviewSessionRepository:
joinedload(DatasetReviewSession.run_contexts) joinedload(DatasetReviewSession.run_contexts)
)\ )\
.filter(DatasetReviewSession.session_id == session_id)\ .filter(DatasetReviewSession.session_id == session_id)\
.filter(DatasetReviewSession.user_id == user_id)\ .filter(
or_(
DatasetReviewSession.user_id == user_id,
SessionCollaborator.user_id == user_id
)
)\
.first() .first()
def save_profile_and_findings(self, session_id: str, user_id: str, profile: DatasetProfile, findings: List[ValidationFinding]) -> DatasetReviewSession: def save_profile_and_findings(self, session_id: str, user_id: str, profile: DatasetProfile, findings: List[ValidationFinding]) -> DatasetReviewSession:
@@ -77,12 +85,21 @@ class DatasetReviewSessionRepository:
raise ValueError("Session not found or access denied") raise ValueError("Session not found or access denied")
if profile: if profile:
# Ensure we update existing profile by session_id if it exists
existing_profile = self.db.query(DatasetProfile).filter_by(session_id=session_id).first()
if existing_profile:
profile.profile_id = existing_profile.profile_id
self.db.merge(profile) self.db.merge(profile)
# For findings, we might want to sync them (remove old ones if not in new list, or update) # Remove old findings for this session to avoid stale data
# Simplest for now: add/merge findings self.db.query(ValidationFinding).filter(
ValidationFinding.session_id == session_id
).delete()
# Add new findings
for finding in findings: for finding in findings:
self.db.merge(finding) finding.session_id = session_id
self.db.add(finding)
self.db.commit() self.db.commit()
return self.load_session_detail(session_id, user_id) return self.load_session_detail(session_id, user_id)