fix(soft-delete): filter trashed rows across read/write paths
Drift-audit Group 3 (soft-delete lifecycle gaps). Trashed rows were leaking into reads and being mutated/resurrected by writes: - update SELECTs now exclude trashed rows: update_milestone, update_project, update_event, and get_milestone_in_project (the latter backs all four milestone routes). Mutating a trashed row silently persisted and reappeared on restore. - MCP get_recent (notes/projects/events) and list_tags now filter deleted_at IS NULL, so trashed items stop surfacing in the agent's bootstrap context and tag counts. - convert_task_to_note clears recurrence_rule + recurrence_next_spawn_at so a demoted note can't spawn children via the (now-live) sweep. - caldav pull skips locally-trashed events (by caldav_uid) instead of resurrecting them via update or creating a duplicate live copy. - trash _cascade now stamps the FULL sub-task subtree (iterative descent), not just direct children, so deeply nested sub-tasks restore as one batch. Test updated for the new descent query. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -21,15 +21,18 @@ def _exists_result(found=True):
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_note_returns_batch_and_commits():
|
||||
session = _make_mock_session()
|
||||
# 1 execute for _exists_alive, then 2 for the note cascade (sub-tasks + self)
|
||||
session.execute = AsyncMock(side_effect=[_exists_result(True), MagicMock(), MagicMock()])
|
||||
# exists-check, then the subtree descent: one child-lookup (no children
|
||||
# here) + one _set stamping the whole subtree.
|
||||
no_children = MagicMock()
|
||||
no_children.scalars.return_value.all.return_value = []
|
||||
session.execute = AsyncMock(side_effect=[_exists_result(True), no_children, MagicMock()])
|
||||
with patch("fabledassistant.services.trash.async_session") as cls:
|
||||
cls.return_value = session
|
||||
from fabledassistant.services.trash import delete
|
||||
batch = await delete(user_id=1, entity_type="note", entity_id=5)
|
||||
assert isinstance(batch, str) and len(batch) > 0
|
||||
assert session.commit.called
|
||||
# exists-check + 2 cascade updates
|
||||
# exists-check + subtree-descent query + subtree _set
|
||||
assert session.execute.await_count == 3
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user