feat(trash): exclude trashed rows from events/projects/milestones/rulebooks/embeddings reads + filtering tests
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
"""Verify live-read paths exclude trashed rows.
|
||||
|
||||
Strategy: run each service read with a mocked session that CAPTURES the
|
||||
compiled SQL of every statement passed to execute, then assert the
|
||||
'deleted_at IS NULL' filter is present. No real DB.
|
||||
"""
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
from datetime import datetime, timezone
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def _capturing_session(captured: list[str]):
|
||||
s = AsyncMock()
|
||||
s.__aenter__ = AsyncMock(return_value=s)
|
||||
s.__aexit__ = AsyncMock(return_value=False)
|
||||
s.commit = AsyncMock()
|
||||
s.scalar = AsyncMock(return_value=0)
|
||||
|
||||
async def _exec(stmt, *a, **k):
|
||||
captured.append(str(stmt))
|
||||
r = MagicMock()
|
||||
r.scalars.return_value.all.return_value = []
|
||||
r.scalars.return_value.first.return_value = None
|
||||
r.scalar_one_or_none.return_value = None
|
||||
r.first.return_value = None
|
||||
r.all.return_value = []
|
||||
r.fetchall.return_value = []
|
||||
return r
|
||||
|
||||
s.execute = AsyncMock(side_effect=_exec)
|
||||
return s
|
||||
|
||||
|
||||
def _has_filter(captured: list[str]) -> bool:
|
||||
return any("deleted_at IS NULL" in s for s in captured)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_note_excludes_trashed():
|
||||
cap: list[str] = []
|
||||
with patch("fabledassistant.services.notes.async_session") as cls:
|
||||
cls.return_value = _capturing_session(cap)
|
||||
from fabledassistant.services.notes import get_note
|
||||
await get_note(1, 5)
|
||||
assert _has_filter(cap)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_notes_excludes_trashed():
|
||||
cap: list[str] = []
|
||||
with patch("fabledassistant.services.notes.async_session") as cls:
|
||||
cls.return_value = _capturing_session(cap)
|
||||
from fabledassistant.services.notes import list_notes
|
||||
await list_notes(1)
|
||||
assert _has_filter(cap)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_projects_excludes_trashed():
|
||||
cap: list[str] = []
|
||||
with patch("fabledassistant.services.projects.async_session") as cls:
|
||||
cls.return_value = _capturing_session(cap)
|
||||
from fabledassistant.services.projects import list_projects
|
||||
await list_projects(1)
|
||||
assert _has_filter(cap)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_events_excludes_trashed():
|
||||
cap: list[str] = []
|
||||
with patch("fabledassistant.services.events.async_session") as cls:
|
||||
cls.return_value = _capturing_session(cap)
|
||||
from fabledassistant.services.events import list_events
|
||||
await list_events(
|
||||
1,
|
||||
datetime(2026, 5, 1, tzinfo=timezone.utc),
|
||||
datetime(2026, 5, 31, tzinfo=timezone.utc),
|
||||
)
|
||||
assert _has_filter(cap)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_query_knowledge_excludes_trashed():
|
||||
cap: list[str] = []
|
||||
with patch("fabledassistant.services.knowledge.async_session") as cls:
|
||||
cls.return_value = _capturing_session(cap)
|
||||
from fabledassistant.services.knowledge import query_knowledge
|
||||
await query_knowledge(user_id=1, note_type=None, tags=[], sort="modified", q=None, limit=10, offset=0)
|
||||
assert _has_filter(cap)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_applicable_rules_excludes_trashed():
|
||||
cap: list[str] = []
|
||||
with patch("fabledassistant.services.rulebooks.async_session") as cls:
|
||||
cls.return_value = _capturing_session(cap)
|
||||
from fabledassistant.services.rulebooks import get_applicable_rules
|
||||
await get_applicable_rules(project_id=3, user_id=1)
|
||||
assert _has_filter(cap)
|
||||
Reference in New Issue
Block a user