import pytest from unittest.mock import AsyncMock, patch, MagicMock @pytest.mark.asyncio async def test_get_or_create_profile_note_returns_body(): """get_profile_body() should return empty string when no profile note exists.""" with patch("fabledassistant.services.briefing_profile.list_notes", new_callable=AsyncMock) as mock_list: mock_list.return_value = ([], 0) from fabledassistant.services.briefing_profile import get_profile_body body = await get_profile_body(user_id=1) assert body == "" def test_format_task_for_briefing(): """format_task() should return a compact single-line string.""" from fabledassistant.services.briefing_pipeline import format_task task = {"title": "Write design doc", "status": "in_progress", "due_date": "2026-03-12", "priority": "high"} result = format_task(task) assert "Write design doc" in result assert "in_progress" in result or "In Progress" in result def test_compute_task_snapshot_hash(): """compute_task_hash should return a stable SHA-256 hex string.""" from fabledassistant.services.briefing_pipeline import compute_task_hash task = {"status": "todo", "priority": "high", "due_date": "2026-03-25", "title": "Write spec"} h = compute_task_hash(task) assert len(h) == 64 # SHA-256 hex assert h == compute_task_hash(task) assert h != compute_task_hash({**task, "status": "done"}) @pytest.mark.asyncio async def test_split_changed_tasks_all_new(): """split_changed_tasks should return all tasks as changed when no snapshot exists.""" from fabledassistant.services.briefing_pipeline import split_changed_tasks tasks = [ {"task_id": 1, "title": "A", "status": "todo", "priority": "none", "due_date": None}, ] with patch( "fabledassistant.services.briefing_pipeline.async_session" ) as mock_cls: mock_session = AsyncMock() mock_session.__aenter__ = AsyncMock(return_value=mock_session) mock_session.__aexit__ = AsyncMock(return_value=False) mock_session.execute = AsyncMock(return_value=MagicMock( scalars=MagicMock(return_value=MagicMock(all=MagicMock(return_value=[]))) )) mock_cls.return_value = mock_session changed, unchanged_count = await split_changed_tasks(user_id=1, tasks=tasks) assert len(changed) == 1 assert unchanged_count == 0 @pytest.mark.asyncio async def test_post_message_accepts_metadata(): """post_message should accept an optional metadata dict and store it.""" from unittest.mock import AsyncMock, patch, MagicMock mock_msg = MagicMock() mock_msg.id = 1 with patch("fabledassistant.services.briefing_conversations.async_session") as mock_session_cls: mock_session = AsyncMock() mock_session.__aenter__ = AsyncMock(return_value=mock_session) mock_session.__aexit__ = AsyncMock(return_value=False) mock_session.add = MagicMock() mock_session.get = AsyncMock(return_value=None) mock_session.commit = AsyncMock() mock_session.refresh = AsyncMock() mock_session_cls.return_value = mock_session from fabledassistant.services.briefing_conversations import post_message metadata = {"weather": {"location": "Berlin"}, "rss_item_ids": [1, 2]} await post_message(1, "assistant", "Hello", metadata=metadata) # Verify Message was constructed with msg_metadata call_args = mock_session.add.call_args[0][0] assert call_args.msg_metadata == metadata