mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
fix(notifications): task titles and agent slugs replace raw UUIDs (#616)
* fix(notifications): task titles and agent slugs replace raw UUIDs
Notification producers interpolated raw task/agent UUIDs into subjects and
bodies ('Task 68e1e4db-... unblocked', 'handed back to 00000000-...-0004').
A tiny notification_text helper (task_display: title-first with a #id8
fallback; agent_display: identity-map slug first, DB lookup fallback) now
feeds every producer: all 13 NotificationService methods, the 7
delivery-service bodies whose subjects were already title-based, the
substitute-PM ad-hoc insert, and the orchestrator/choreographer callers,
which thread the task row's title one call deeper. Fixes the literal
'cell_pm' role string sent as an agent slug in the merge-conflict
notification. Tool-call examples like unblock('<uuid>') keep the raw id on
purpose — agents need it.
* test(notifications): board-review subject assertion matches the humanized format
---------
Co-authored-by: Renn F <rennf93@users.noreply.github.com>
This commit is contained in:
@@ -159,7 +159,8 @@ async def test_send_blocker_notification(svc: NotificationService) -> None:
|
||||
from_agent="system",
|
||||
to_pm="cell-pm",
|
||||
)
|
||||
assert any("Task t1" in row.subject for row in db.added)
|
||||
# No task_title passed → falls back to the short-id display, not the raw id.
|
||||
assert any("Task #t1" in row.subject for row in db.added)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
"""Unit coverage for the shared notification-text helpers.
|
||||
|
||||
``task_display``/``agent_display`` are the producer-side fix so a human
|
||||
reading a notification (panel, bell, Telegram) sees a task title / agent
|
||||
slug instead of a raw UUID — see ``roboco/services/notification_text.py``.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
from roboco.foundation.identity import AGENTS
|
||||
from roboco.services.notification_text import agent_display, task_display
|
||||
|
||||
|
||||
class _Task:
|
||||
def __init__(self, title: str | None) -> None:
|
||||
self.title = title
|
||||
|
||||
|
||||
def test_task_display_prefers_title_from_row() -> None:
|
||||
task_id = uuid4()
|
||||
display = task_display(_Task("Fix login bug"), task_id)
|
||||
assert display == f"'Fix login bug' (#{str(task_id)[:8]})"
|
||||
|
||||
|
||||
def test_task_display_accepts_bare_title_string() -> None:
|
||||
task_id = uuid4()
|
||||
display = task_display("Fix login bug", task_id)
|
||||
assert display == f"'Fix login bug' (#{str(task_id)[:8]})"
|
||||
|
||||
|
||||
def test_task_display_truncates_long_titles() -> None:
|
||||
long_title = "x" * 100
|
||||
task_id = uuid4()
|
||||
display = task_display(long_title, task_id)
|
||||
assert display == f"'{'x' * 40}' (#{str(task_id)[:8]})"
|
||||
|
||||
|
||||
def test_task_display_falls_back_to_short_id_when_no_title() -> None:
|
||||
task_id = uuid4()
|
||||
assert task_display(None, task_id) == f"#{str(task_id)[:8]}"
|
||||
assert task_display(_Task(None), task_id) == f"#{str(task_id)[:8]}"
|
||||
assert task_display(_Task(""), task_id) == f"#{str(task_id)[:8]}"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_agent_display_resolves_via_static_map() -> None:
|
||||
"""A fixed-roster agent's UUID resolves to its slug with zero DB I/O."""
|
||||
row = AGENTS["be-dev-1"]
|
||||
assert await agent_display(row.uuid) == "be-dev-1"
|
||||
assert await agent_display(str(row.uuid)) == "be-dev-1"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_agent_display_falls_back_to_db_for_unknown_uuid() -> None:
|
||||
"""A UUID absent from the static map resolves via ``get_agent_slug`` when
|
||||
a db session is supplied."""
|
||||
unknown_uuid = uuid4()
|
||||
db: Any = MagicMock()
|
||||
result = MagicMock()
|
||||
result.scalar_one_or_none.return_value = "fresh-agent"
|
||||
db.execute = AsyncMock(return_value=result)
|
||||
|
||||
assert await agent_display(unknown_uuid, db) == "fresh-agent"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_agent_display_raw_fallback_without_db() -> None:
|
||||
"""No static-map hit + no db session ⇒ the raw value passes through."""
|
||||
unknown_uuid = uuid4()
|
||||
assert await agent_display(unknown_uuid) == str(unknown_uuid)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_agent_display_raw_fallback_for_non_uuid_slug() -> None:
|
||||
"""A plain slug string that isn't in the UUID-keyed map passes through
|
||||
unchanged (e.g. a value that's already a friendly slug)."""
|
||||
assert await agent_display("some-slug") == "some-slug"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_agent_display_passes_through_none() -> None:
|
||||
"""None stays None — "unassigned"/"its owner" wording stays at call sites."""
|
||||
assert await agent_display(None) is None
|
||||
Reference in New Issue
Block a user