feat: implement user message redispatching in onboarding process
This commit is contained in:
@@ -168,6 +168,29 @@ async def start_onboarding(
|
||||
.first(session)
|
||||
)
|
||||
if onboarding:
|
||||
last_user_content: str | None = None
|
||||
messages = onboarding.messages or []
|
||||
if messages:
|
||||
last_message = messages[-1]
|
||||
if isinstance(last_message, dict):
|
||||
last_role = last_message.get("role")
|
||||
content = last_message.get("content")
|
||||
if last_role == "user" and isinstance(content, str) and content:
|
||||
last_user_content = content
|
||||
|
||||
if last_user_content:
|
||||
# Retrigger the agent when the session is waiting on a response.
|
||||
dispatcher = BoardOnboardingMessagingService(session)
|
||||
await dispatcher.dispatch_answer(
|
||||
board=board,
|
||||
onboarding=onboarding,
|
||||
answer_text=last_user_content,
|
||||
correlation_id=f"onboarding.resume:{board.id}:{onboarding.id}",
|
||||
)
|
||||
onboarding.updated_at = utcnow()
|
||||
session.add(onboarding)
|
||||
await session.commit()
|
||||
await session.refresh(onboarding)
|
||||
return onboarding
|
||||
|
||||
dispatcher = BoardOnboardingMessagingService(session)
|
||||
|
||||
175
backend/tests/test_board_onboarding_start_api.py
Normal file
175
backend/tests/test_board_onboarding_start_api.py
Normal file
@@ -0,0 +1,175 @@
|
||||
# ruff: noqa: INP001, S101
|
||||
"""Tests for board onboarding start-session restart behavior."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from types import SimpleNamespace
|
||||
from typing import Any
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
|
||||
from app.api import board_onboarding
|
||||
from app.core.time import utcnow
|
||||
from app.models.board_onboarding import BoardOnboardingSession
|
||||
from app.schemas.board_onboarding import BoardOnboardingStart
|
||||
|
||||
|
||||
@dataclass
|
||||
class _FakeScalarResult:
|
||||
value: object | None
|
||||
|
||||
def first(self) -> object | None:
|
||||
return self.value
|
||||
|
||||
|
||||
@dataclass
|
||||
class _FakeSession:
|
||||
first_value: object | None
|
||||
added: list[object] = field(default_factory=list)
|
||||
committed: int = 0
|
||||
refreshed: list[object] = field(default_factory=list)
|
||||
|
||||
async def exec(self, _statement: object) -> _FakeScalarResult:
|
||||
return _FakeScalarResult(self.first_value)
|
||||
|
||||
def add(self, value: object) -> None:
|
||||
self.added.append(value)
|
||||
|
||||
async def commit(self) -> None:
|
||||
self.committed += 1
|
||||
|
||||
async def refresh(self, value: object) -> None:
|
||||
self.refreshed.append(value)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_start_onboarding_redispatches_when_last_message_is_user(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
board_id = uuid4()
|
||||
onboarding = BoardOnboardingSession(
|
||||
board_id=board_id,
|
||||
session_key="session-key",
|
||||
status="active",
|
||||
messages=[
|
||||
{
|
||||
"role": "user",
|
||||
"content": "I prefer concise updates.",
|
||||
"timestamp": utcnow().isoformat(),
|
||||
},
|
||||
],
|
||||
)
|
||||
session: Any = _FakeSession(first_value=onboarding)
|
||||
board = SimpleNamespace(id=board_id, name="Roadmap", description="Build v1")
|
||||
captured_calls: list[dict[str, object]] = []
|
||||
|
||||
class _FakeMessagingService:
|
||||
def __init__(self, _session: object) -> None:
|
||||
self._session = _session
|
||||
|
||||
async def dispatch_answer(
|
||||
self,
|
||||
*,
|
||||
board: object,
|
||||
onboarding: object,
|
||||
answer_text: str,
|
||||
correlation_id: str,
|
||||
) -> None:
|
||||
captured_calls.append(
|
||||
{
|
||||
"board": board,
|
||||
"onboarding": onboarding,
|
||||
"answer_text": answer_text,
|
||||
"correlation_id": correlation_id,
|
||||
},
|
||||
)
|
||||
|
||||
monkeypatch.setattr(
|
||||
board_onboarding,
|
||||
"BoardOnboardingMessagingService",
|
||||
_FakeMessagingService,
|
||||
)
|
||||
|
||||
before = onboarding.updated_at
|
||||
result = await board_onboarding.start_onboarding(
|
||||
_payload=BoardOnboardingStart(),
|
||||
board=board,
|
||||
session=session,
|
||||
)
|
||||
|
||||
assert result is onboarding
|
||||
assert len(captured_calls) == 1
|
||||
assert captured_calls[0]["answer_text"] == "I prefer concise updates."
|
||||
assert str(captured_calls[0]["correlation_id"]).startswith("onboarding.resume:")
|
||||
assert onboarding.updated_at >= before
|
||||
assert session.added == [onboarding]
|
||||
assert session.committed == 1
|
||||
assert session.refreshed == [onboarding]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_start_onboarding_does_not_redispatch_when_waiting_for_user(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
board_id = uuid4()
|
||||
onboarding = BoardOnboardingSession(
|
||||
board_id=board_id,
|
||||
session_key="session-key",
|
||||
status="active",
|
||||
messages=[
|
||||
{
|
||||
"role": "user",
|
||||
"content": "I prefer concise updates.",
|
||||
"timestamp": utcnow().isoformat(),
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": '{"question":"What is your timezone?","options":[{"id":"1","label":"UTC"}]}',
|
||||
"timestamp": utcnow().isoformat(),
|
||||
},
|
||||
],
|
||||
)
|
||||
session: Any = _FakeSession(first_value=onboarding)
|
||||
board = SimpleNamespace(id=board_id, name="Roadmap", description="Build v1")
|
||||
captured_calls: list[dict[str, object]] = []
|
||||
|
||||
class _FakeMessagingService:
|
||||
def __init__(self, _session: object) -> None:
|
||||
self._session = _session
|
||||
|
||||
async def dispatch_answer(
|
||||
self,
|
||||
*,
|
||||
board: object,
|
||||
onboarding: object,
|
||||
answer_text: str,
|
||||
correlation_id: str,
|
||||
) -> None:
|
||||
captured_calls.append(
|
||||
{
|
||||
"board": board,
|
||||
"onboarding": onboarding,
|
||||
"answer_text": answer_text,
|
||||
"correlation_id": correlation_id,
|
||||
},
|
||||
)
|
||||
|
||||
monkeypatch.setattr(
|
||||
board_onboarding,
|
||||
"BoardOnboardingMessagingService",
|
||||
_FakeMessagingService,
|
||||
)
|
||||
|
||||
result = await board_onboarding.start_onboarding(
|
||||
_payload=BoardOnboardingStart(),
|
||||
board=board,
|
||||
session=session,
|
||||
)
|
||||
|
||||
assert result is onboarding
|
||||
assert captured_calls == []
|
||||
assert session.added == []
|
||||
assert session.committed == 0
|
||||
assert session.refreshed == []
|
||||
Reference in New Issue
Block a user