176 lines
5.0 KiB
Python
176 lines
5.0 KiB
Python
|
|
# 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 == []
|