feat(journal): run_for_user orchestrates closeout extraction

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-12 14:34:42 -04:00
parent 552943d6c0
commit 4403026797
2 changed files with 126 additions and 0 deletions
+49
View File
@@ -1,6 +1,10 @@
"""Tests for journal closeout extraction helpers."""
import datetime
from types import SimpleNamespace
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
def _msg(role: str, content: str, kind: str | None = None):
@@ -9,6 +13,29 @@ def _msg(role: str, content: str, kind: str | None = None):
return SimpleNamespace(role=role, content=content, msg_metadata=metadata)
def _fake_conv(conv_id: int = 1):
return SimpleNamespace(id=conv_id)
def _patch_db(messages, conv=None):
"""Build a context manager that fakes async_session() and the two
select() calls (conversation lookup + messages query)."""
session = AsyncMock()
session.execute = AsyncMock()
conv_result = MagicMock()
conv_result.scalar_one_or_none = MagicMock(return_value=conv)
msg_result = MagicMock()
scalars = MagicMock()
scalars.all = MagicMock(return_value=list(messages))
msg_result.scalars = MagicMock(return_value=scalars)
session.execute.side_effect = [conv_result, msg_result]
session_ctx = MagicMock()
session_ctx.__aenter__ = AsyncMock(return_value=session)
session_ctx.__aexit__ = AsyncMock(return_value=None)
return session_ctx
def test_filter_excludes_daily_prep_messages():
from fabledassistant.services.journal_closeout import _filter_messages
@@ -62,3 +89,25 @@ def test_system_prompt_lists_structured_fields_to_exclude():
for field in ("name", "job title", "industry", "expertise", "response style", "tone", "interests"):
assert field in text, f"system prompt should mention '{field}'"
assert "(nothing to note)" in SYSTEM_PROMPT
@pytest.mark.asyncio
async def test_run_for_user_happy_path_appends_bullets():
from fabledassistant.services import journal_closeout
yesterday = datetime.date(2026, 5, 11)
msgs = [
_msg("assistant", "Good morning — here's today.", kind="daily_prep"),
_msg("user", "Skip the news section — I never read it."),
_msg("assistant", "Noted. Will drop it."),
]
with (
patch.object(journal_closeout, "async_session", return_value=_patch_db(msgs, conv=_fake_conv())),
patch.object(journal_closeout, "get_setting", new=AsyncMock(return_value="bg-model")),
patch.object(journal_closeout, "generate_completion", new=AsyncMock(return_value="- User skips news section")),
patch.object(journal_closeout, "append_observations", new=AsyncMock()) as mock_append,
):
await journal_closeout.run_for_user(user_id=42, yesterday=yesterday)
mock_append.assert_awaited_once_with(42, "- User skips news section")