From 50d2a0e9c0706ce5ee75f428a574f4abd2a29fc4 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Thu, 28 May 2026 08:21:59 -0400 Subject: [PATCH] =?UTF-8?q?feat(plan):=20services/notes=20=E2=80=94=20task?= =?UTF-8?q?=5Fkind=20create=20param=20+=20list=20filter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/fabledassistant/services/notes.py | 7 ++++ tests/test_services_notes_task_kind.py | 47 ++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 tests/test_services_notes_task_kind.py diff --git a/src/fabledassistant/services/notes.py b/src/fabledassistant/services/notes.py index 493a5c8..4ef525f 100644 --- a/src/fabledassistant/services/notes.py +++ b/src/fabledassistant/services/notes.py @@ -64,6 +64,7 @@ async def create_note( recurrence_rule: dict | None = None, note_type: str = "note", entity_meta: dict | None = None, + task_kind: str = "work", ) -> Note: # Auto-populate project_id from milestone when not explicitly provided if milestone_id is not None and project_id is None: @@ -92,6 +93,7 @@ async def create_note( recurrence_rule=recurrence_rule, note_type=note_type, entity_meta=entity_meta, + task_kind=task_kind, ) session.add(note) await session.commit() @@ -124,6 +126,7 @@ async def list_notes( milestone_id: int | None = None, milestone_ids: list[int] | None = None, parent_id: int | None = None, + task_kind: str | None = None, no_project: bool = False, exclude_paused_projects: bool = False, sort: str = "updated_at", @@ -197,6 +200,10 @@ async def list_notes( query = query.where(Note.parent_id == parent_id) count_query = count_query.where(Note.parent_id == parent_id) + if task_kind is not None: + query = query.where(Note.task_kind == task_kind) + count_query = count_query.where(Note.task_kind == task_kind) + if no_project: query = query.where(Note.project_id.is_(None)) count_query = count_query.where(Note.project_id.is_(None)) diff --git a/tests/test_services_notes_task_kind.py b/tests/test_services_notes_task_kind.py new file mode 100644 index 0000000..d1eeb12 --- /dev/null +++ b/tests/test_services_notes_task_kind.py @@ -0,0 +1,47 @@ +"""task_kind create + list-filter behavior in services/notes.py.""" +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest + + +def _make_mock_session(): + s = AsyncMock() + s.__aenter__ = AsyncMock(return_value=s) + s.__aexit__ = AsyncMock(return_value=False) + s.add = MagicMock() + s.commit = AsyncMock() + s.refresh = AsyncMock() + return s + + +@pytest.mark.asyncio +async def test_create_note_passes_task_kind_to_model(): + mock_session = _make_mock_session() + captured = {} + + def _capture_add(obj): + captured["task_kind"] = getattr(obj, "task_kind", "MISSING") + + mock_session.add = MagicMock(side_effect=_capture_add) + with patch("fabledassistant.services.notes.async_session") as mock_cls, \ + patch("fabledassistant.services.notes._maybe_reactivate_project", AsyncMock()): + mock_cls.return_value = mock_session + from fabledassistant.services.notes import create_note + await create_note(user_id=1, title="P", status="todo", task_kind="plan") + assert captured["task_kind"] == "plan" + + +@pytest.mark.asyncio +async def test_list_notes_filters_by_task_kind(): + mock_session = _make_mock_session() + mock_session.scalar = AsyncMock(return_value=0) + exec_result = MagicMock() + exec_result.scalars.return_value.all.return_value = [] + mock_session.execute = AsyncMock(return_value=exec_result) + with patch("fabledassistant.services.notes.async_session") as mock_cls: + mock_cls.return_value = mock_session + from fabledassistant.services.notes import list_notes + # Should not raise; task_kind is a recognized kwarg. + rows, total = await list_notes(user_id=1, is_task=True, task_kind="plan") + assert rows == [] + assert total == 0