feat(trash): exclude trashed rows from events/projects/milestones/rulebooks/embeddings reads + filtering tests

This commit is contained in:
2026-05-28 20:33:40 -04:00
parent e7f214fc80
commit eb41e772cd
6 changed files with 152 additions and 9 deletions
+100
View File
@@ -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)