mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
Fix the Prompter tests against the current schema and SDK
The Prompter feature's integration tests were never executed by CI — the PR checks run CodeQL and secret-scanning, not the pytest suite — so they carried bugs that only surfaced once the feature was integrated and the full quality gate ran against it: - The project fixture set a `git_branch` field the model doesn't have (it is `default_branch`) and omitted the required `assigned_cell` / `created_by` columns, so every confirm-flow test errored at setup. - The tests patched the Anthropic client's `messages.create` at the class level, but the SDK exposes `messages` as a cached_property, so that target can't be resolved. Route the call through a single `_create_message` seam on PrompterService (behavior-identical) and patch that instead. Also drop `confirm_draft` below the cyclomatic-complexity threshold by extracting the override-merge, UUID-field, and enum-coercion helpers (behavior-preserving; the prompter test suite passes unchanged).
This commit is contained in:
+60
-46
@@ -136,6 +136,15 @@ class PrompterService:
|
|||||||
self._client = AsyncAnthropic(api_key=api_key)
|
self._client = AsyncAnthropic(api_key=api_key)
|
||||||
return self._client
|
return self._client
|
||||||
|
|
||||||
|
async def _create_message(self, **kwargs: Any) -> Any:
|
||||||
|
"""Single seam for the Anthropic ``messages.create`` call.
|
||||||
|
|
||||||
|
The SDK exposes ``messages`` as a cached_property, so it can't be
|
||||||
|
patched at the client-class level; tests substitute this method.
|
||||||
|
"""
|
||||||
|
client = self._get_client()
|
||||||
|
return await client.messages.create(**kwargs)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _session(self) -> AsyncSession:
|
def _session(self) -> AsyncSession:
|
||||||
"""Return DB session, raising if not configured."""
|
"""Return DB session, raising if not configured."""
|
||||||
@@ -287,40 +296,13 @@ class PrompterService:
|
|||||||
session_rec = await self._get_session(session_id, agent_id)
|
session_rec = await self._get_session(session_id, agent_id)
|
||||||
ov = confirm_overrides or ConfirmOverrides()
|
ov = confirm_overrides or ConfirmOverrides()
|
||||||
|
|
||||||
# Get or generate the draft
|
# Get or generate the draft, then merge confirm-time overrides
|
||||||
draft_record = await self.get_or_generate_draft(session_id, agent_id)
|
draft_record = await self.get_or_generate_draft(session_id, agent_id)
|
||||||
draft_data: dict[str, Any] = dict(draft_record.draft_data)
|
draft_data: dict[str, Any] = dict(draft_record.draft_data)
|
||||||
|
self._apply_overrides(draft_data, ov)
|
||||||
|
|
||||||
# Apply overrides
|
resolved_project_id = self._resolve_uuid_field(draft_data, "project_id")
|
||||||
if ov.project_id is not None:
|
resolved_product_id = self._resolve_uuid_field(draft_data, "product_id")
|
||||||
draft_data["project_id"] = str(ov.project_id)
|
|
||||||
if ov.product_id is not None:
|
|
||||||
draft_data["product_id"] = str(ov.product_id)
|
|
||||||
if ov.assigned_to is not None:
|
|
||||||
draft_data["assigned_to"] = ov.assigned_to
|
|
||||||
if ov.extra:
|
|
||||||
draft_data.update(ov.extra)
|
|
||||||
|
|
||||||
# Resolve project/product IDs
|
|
||||||
resolved_project_id: UUID | None = None
|
|
||||||
resolved_product_id: UUID | None = None
|
|
||||||
if draft_data.get("project_id"):
|
|
||||||
try:
|
|
||||||
resolved_project_id = UUID(str(draft_data["project_id"]))
|
|
||||||
except ValueError as exc:
|
|
||||||
raise ValidationError(
|
|
||||||
message=f"Invalid project_id UUID: {draft_data['project_id']}",
|
|
||||||
field="project_id",
|
|
||||||
) from exc
|
|
||||||
if draft_data.get("product_id"):
|
|
||||||
try:
|
|
||||||
resolved_product_id = UUID(str(draft_data["product_id"]))
|
|
||||||
except ValueError as exc:
|
|
||||||
raise ValidationError(
|
|
||||||
message=f"Invalid product_id UUID: {draft_data['product_id']}",
|
|
||||||
field="product_id",
|
|
||||||
) from exc
|
|
||||||
|
|
||||||
if resolved_project_id is None and resolved_product_id is None:
|
if resolved_project_id is None and resolved_product_id is None:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
message=(
|
message=(
|
||||||
@@ -330,17 +312,7 @@ class PrompterService:
|
|||||||
field="project_id",
|
field="project_id",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Validate and coerce required fields
|
team, task_type, nature, complexity = self._coerce_draft_enums(draft_data)
|
||||||
try:
|
|
||||||
team = Team(draft_data["team"])
|
|
||||||
task_type = TaskType(draft_data["task_type"])
|
|
||||||
nature = TaskNature(draft_data["nature"])
|
|
||||||
complexity = Complexity(draft_data["estimated_complexity"])
|
|
||||||
except (KeyError, ValueError) as exc:
|
|
||||||
raise ValidationError(
|
|
||||||
message=f"Draft has invalid or missing required fields: {exc}",
|
|
||||||
field="draft",
|
|
||||||
) from exc
|
|
||||||
|
|
||||||
# Resolve assigned_to as UUID if possible
|
# Resolve assigned_to as UUID if possible
|
||||||
resolved_assigned_to: UUID | None = None
|
resolved_assigned_to: UUID | None = None
|
||||||
@@ -385,6 +357,50 @@ class PrompterService:
|
|||||||
)
|
)
|
||||||
return task.id # type: ignore[return-value]
|
return task.id # type: ignore[return-value]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _apply_overrides(draft_data: dict[str, Any], ov: ConfirmOverrides) -> None:
|
||||||
|
"""Merge confirm-time overrides onto the draft data in place."""
|
||||||
|
if ov.project_id is not None:
|
||||||
|
draft_data["project_id"] = str(ov.project_id)
|
||||||
|
if ov.product_id is not None:
|
||||||
|
draft_data["product_id"] = str(ov.product_id)
|
||||||
|
if ov.assigned_to is not None:
|
||||||
|
draft_data["assigned_to"] = ov.assigned_to
|
||||||
|
if ov.extra:
|
||||||
|
draft_data.update(ov.extra)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _resolve_uuid_field(draft_data: dict[str, Any], key: str) -> UUID | None:
|
||||||
|
"""Parse ``draft_data[key]`` as a UUID; None if absent, raises if malformed."""
|
||||||
|
raw = draft_data.get(key)
|
||||||
|
if not raw:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return UUID(str(raw))
|
||||||
|
except ValueError as exc:
|
||||||
|
raise ValidationError(
|
||||||
|
message=f"Invalid {key} UUID: {raw}",
|
||||||
|
field=key,
|
||||||
|
) from exc
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _coerce_draft_enums(
|
||||||
|
draft_data: dict[str, Any],
|
||||||
|
) -> tuple[Team, TaskType, TaskNature, Complexity]:
|
||||||
|
"""Coerce the draft's required enum fields, raising on missing/invalid."""
|
||||||
|
try:
|
||||||
|
return (
|
||||||
|
Team(draft_data["team"]),
|
||||||
|
TaskType(draft_data["task_type"]),
|
||||||
|
TaskNature(draft_data["nature"]),
|
||||||
|
Complexity(draft_data["estimated_complexity"]),
|
||||||
|
)
|
||||||
|
except (KeyError, ValueError) as exc:
|
||||||
|
raise ValidationError(
|
||||||
|
message=f"Draft has invalid or missing required fields: {exc}",
|
||||||
|
field="draft",
|
||||||
|
) from exc
|
||||||
|
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
# Private helpers (session-based)
|
# Private helpers (session-based)
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
@@ -426,10 +442,9 @@ class PrompterService:
|
|||||||
max_tokens: int = 2048,
|
max_tokens: int = 2048,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Call the LLM for a chat response. Returns {message, draft_ready}."""
|
"""Call the LLM for a chat response. Returns {message, draft_ready}."""
|
||||||
client = self._get_client()
|
|
||||||
user_prompt = _build_chat_prompt(messages, context)
|
user_prompt = _build_chat_prompt(messages, context)
|
||||||
try:
|
try:
|
||||||
response = await client.messages.create(
|
response = await self._create_message(
|
||||||
model=model,
|
model=model,
|
||||||
max_tokens=max_tokens,
|
max_tokens=max_tokens,
|
||||||
system=_PROMPTER_SYSTEM_PROMPT,
|
system=_PROMPTER_SYSTEM_PROMPT,
|
||||||
@@ -456,10 +471,9 @@ class PrompterService:
|
|||||||
max_tokens: int = 4096,
|
max_tokens: int = 4096,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Call the LLM to generate a structured draft. Returns {draft, reasoning}."""
|
"""Call the LLM to generate a structured draft. Returns {draft, reasoning}."""
|
||||||
client = self._get_client()
|
|
||||||
user_prompt = _build_draft_prompt(messages, context)
|
user_prompt = _build_draft_prompt(messages, context)
|
||||||
try:
|
try:
|
||||||
response = await client.messages.create(
|
response = await self._create_message(
|
||||||
model=model,
|
model=model,
|
||||||
max_tokens=max_tokens,
|
max_tokens=max_tokens,
|
||||||
system=_DRAFT_SYSTEM_PROMPT,
|
system=_DRAFT_SYSTEM_PROMPT,
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ from httpx import ASGITransport, AsyncClient
|
|||||||
from roboco.api.deps import get_agent_context, get_db
|
from roboco.api.deps import get_agent_context, get_db
|
||||||
from roboco.api.routes.prompter import router as prompter_router
|
from roboco.api.routes.prompter import router as prompter_router
|
||||||
from roboco.db.tables import AgentTable, ProjectTable
|
from roboco.db.tables import AgentTable, ProjectTable
|
||||||
from roboco.models.base import AgentRole, AgentStatus
|
from roboco.models.base import AgentRole, AgentStatus, Team
|
||||||
from roboco.models.permissions import AgentContext
|
from roboco.models.permissions import AgentContext
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -85,12 +85,30 @@ async def prompter_client(
|
|||||||
@pytest_asyncio.fixture
|
@pytest_asyncio.fixture
|
||||||
async def project_fixture(db_session: AsyncSession) -> ProjectTable:
|
async def project_fixture(db_session: AsyncSession) -> ProjectTable:
|
||||||
"""Create a minimal project for task creation in confirm tests."""
|
"""Create a minimal project for task creation in confirm tests."""
|
||||||
|
creator = AgentTable(
|
||||||
|
id=uuid4(),
|
||||||
|
name="ProjectCreator",
|
||||||
|
slug=f"proj-creator-{uuid4().hex[:8]}",
|
||||||
|
role=AgentRole.MAIN_PM,
|
||||||
|
team=Team.BACKEND,
|
||||||
|
status=AgentStatus.ACTIVE,
|
||||||
|
model_config={},
|
||||||
|
system_prompt="pm",
|
||||||
|
capabilities=[],
|
||||||
|
permissions={},
|
||||||
|
metrics={},
|
||||||
|
)
|
||||||
|
db_session.add(creator)
|
||||||
|
await db_session.flush()
|
||||||
|
|
||||||
project = ProjectTable(
|
project = ProjectTable(
|
||||||
id=uuid4(),
|
id=uuid4(),
|
||||||
name="Test Project",
|
name="Test Project",
|
||||||
slug=f"test-project-{uuid4().hex[:8]}",
|
slug=f"test-project-{uuid4().hex[:8]}",
|
||||||
git_url="https://github.com/test/repo.git",
|
git_url="https://github.com/test/repo.git",
|
||||||
git_branch="main",
|
default_branch="main",
|
||||||
|
assigned_cell=Team.BACKEND,
|
||||||
|
created_by=creator.id,
|
||||||
)
|
)
|
||||||
db_session.add(project)
|
db_session.add(project)
|
||||||
await db_session.flush()
|
await db_session.flush()
|
||||||
@@ -149,7 +167,7 @@ async def test_send_message_success(prompter_client: dict) -> None:
|
|||||||
mock_response.content = [MagicMock(text="Great! Let's gather requirements.")]
|
mock_response.content = [MagicMock(text="Great! Let's gather requirements.")]
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"roboco.services.prompter.AsyncAnthropic.messages.create",
|
"roboco.services.prompter.PrompterService._create_message",
|
||||||
new_callable=AsyncMock,
|
new_callable=AsyncMock,
|
||||||
return_value=mock_response,
|
return_value=mock_response,
|
||||||
):
|
):
|
||||||
@@ -187,7 +205,7 @@ async def test_send_message_marks_draft_ready(prompter_client: dict) -> None:
|
|||||||
]
|
]
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"roboco.services.prompter.AsyncAnthropic.messages.create",
|
"roboco.services.prompter.PrompterService._create_message",
|
||||||
new_callable=AsyncMock,
|
new_callable=AsyncMock,
|
||||||
return_value=mock_response,
|
return_value=mock_response,
|
||||||
):
|
):
|
||||||
@@ -243,7 +261,7 @@ async def test_get_draft_generates_from_conversation(prompter_client: dict) -> N
|
|||||||
draft_response.content = [MagicMock(text=json.dumps(draft_json))]
|
draft_response.content = [MagicMock(text=json.dumps(draft_json))]
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"roboco.services.prompter.AsyncAnthropic.messages.create",
|
"roboco.services.prompter.PrompterService._create_message",
|
||||||
new_callable=AsyncMock,
|
new_callable=AsyncMock,
|
||||||
return_value=chat_response,
|
return_value=chat_response,
|
||||||
):
|
):
|
||||||
@@ -254,7 +272,7 @@ async def test_get_draft_generates_from_conversation(prompter_client: dict) -> N
|
|||||||
)
|
)
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"roboco.services.prompter.AsyncAnthropic.messages.create",
|
"roboco.services.prompter.PrompterService._create_message",
|
||||||
new_callable=AsyncMock,
|
new_callable=AsyncMock,
|
||||||
return_value=draft_response,
|
return_value=draft_response,
|
||||||
):
|
):
|
||||||
@@ -296,7 +314,7 @@ async def test_get_draft_cached(prompter_client: dict) -> None:
|
|||||||
draft_response.content = [MagicMock(text=json.dumps(draft_json))]
|
draft_response.content = [MagicMock(text=json.dumps(draft_json))]
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"roboco.services.prompter.AsyncAnthropic.messages.create",
|
"roboco.services.prompter.PrompterService._create_message",
|
||||||
new_callable=AsyncMock,
|
new_callable=AsyncMock,
|
||||||
return_value=chat_response,
|
return_value=chat_response,
|
||||||
):
|
):
|
||||||
@@ -314,7 +332,7 @@ async def test_get_draft_cached(prompter_client: dict) -> None:
|
|||||||
return draft_response
|
return draft_response
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"roboco.services.prompter.AsyncAnthropic.messages.create",
|
"roboco.services.prompter.PrompterService._create_message",
|
||||||
side_effect=_mock_create,
|
side_effect=_mock_create,
|
||||||
):
|
):
|
||||||
await client.get(f"/api/prompter/sessions/{session_id}/draft", headers=_HDR)
|
await client.get(f"/api/prompter/sessions/{session_id}/draft", headers=_HDR)
|
||||||
@@ -370,7 +388,7 @@ async def test_confirm_draft_creates_task(
|
|||||||
draft_response.content = [MagicMock(text=json.dumps(draft_json))]
|
draft_response.content = [MagicMock(text=json.dumps(draft_json))]
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"roboco.services.prompter.AsyncAnthropic.messages.create",
|
"roboco.services.prompter.PrompterService._create_message",
|
||||||
new_callable=AsyncMock,
|
new_callable=AsyncMock,
|
||||||
return_value=chat_response,
|
return_value=chat_response,
|
||||||
):
|
):
|
||||||
@@ -381,7 +399,7 @@ async def test_confirm_draft_creates_task(
|
|||||||
)
|
)
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"roboco.services.prompter.AsyncAnthropic.messages.create",
|
"roboco.services.prompter.PrompterService._create_message",
|
||||||
new_callable=AsyncMock,
|
new_callable=AsyncMock,
|
||||||
return_value=draft_response,
|
return_value=draft_response,
|
||||||
):
|
):
|
||||||
@@ -426,7 +444,7 @@ async def test_confirm_draft_requires_project_or_product(
|
|||||||
draft_response.content = [MagicMock(text=json.dumps(draft_json))]
|
draft_response.content = [MagicMock(text=json.dumps(draft_json))]
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"roboco.services.prompter.AsyncAnthropic.messages.create",
|
"roboco.services.prompter.PrompterService._create_message",
|
||||||
new_callable=AsyncMock,
|
new_callable=AsyncMock,
|
||||||
return_value=chat_response,
|
return_value=chat_response,
|
||||||
):
|
):
|
||||||
@@ -437,7 +455,7 @@ async def test_confirm_draft_requires_project_or_product(
|
|||||||
)
|
)
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"roboco.services.prompter.AsyncAnthropic.messages.create",
|
"roboco.services.prompter.PrompterService._create_message",
|
||||||
new_callable=AsyncMock,
|
new_callable=AsyncMock,
|
||||||
return_value=draft_response,
|
return_value=draft_response,
|
||||||
):
|
):
|
||||||
@@ -477,7 +495,7 @@ async def test_full_happy_path(
|
|||||||
]
|
]
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"roboco.services.prompter.AsyncAnthropic.messages.create",
|
"roboco.services.prompter.PrompterService._create_message",
|
||||||
new_callable=AsyncMock,
|
new_callable=AsyncMock,
|
||||||
return_value=chat_mock,
|
return_value=chat_mock,
|
||||||
):
|
):
|
||||||
@@ -493,7 +511,7 @@ async def test_full_happy_path(
|
|||||||
MagicMock(text="I have enough information to draft a task now.")
|
MagicMock(text="I have enough information to draft a task now.")
|
||||||
]
|
]
|
||||||
with patch(
|
with patch(
|
||||||
"roboco.services.prompter.AsyncAnthropic.messages.create",
|
"roboco.services.prompter.PrompterService._create_message",
|
||||||
new_callable=AsyncMock,
|
new_callable=AsyncMock,
|
||||||
return_value=chat_mock2,
|
return_value=chat_mock2,
|
||||||
):
|
):
|
||||||
@@ -524,7 +542,7 @@ async def test_full_happy_path(
|
|||||||
draft_mock.content = [MagicMock(text=json.dumps(draft_json))]
|
draft_mock.content = [MagicMock(text=json.dumps(draft_json))]
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"roboco.services.prompter.AsyncAnthropic.messages.create",
|
"roboco.services.prompter.PrompterService._create_message",
|
||||||
new_callable=AsyncMock,
|
new_callable=AsyncMock,
|
||||||
return_value=draft_mock,
|
return_value=draft_mock,
|
||||||
):
|
):
|
||||||
@@ -560,7 +578,7 @@ async def test_prompter_chat_success(prompter_client: dict) -> None:
|
|||||||
mock_response.content = [MagicMock(text="Great! Let's gather requirements.")]
|
mock_response.content = [MagicMock(text="Great! Let's gather requirements.")]
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"roboco.services.prompter.AsyncAnthropic.messages.create",
|
"roboco.services.prompter.PrompterService._create_message",
|
||||||
new_callable=AsyncMock,
|
new_callable=AsyncMock,
|
||||||
return_value=mock_response,
|
return_value=mock_response,
|
||||||
):
|
):
|
||||||
@@ -593,7 +611,7 @@ async def test_prompter_chat_draft_ready(prompter_client: dict) -> None:
|
|||||||
]
|
]
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"roboco.services.prompter.AsyncAnthropic.messages.create",
|
"roboco.services.prompter.PrompterService._create_message",
|
||||||
new_callable=AsyncMock,
|
new_callable=AsyncMock,
|
||||||
return_value=mock_response,
|
return_value=mock_response,
|
||||||
):
|
):
|
||||||
@@ -619,7 +637,7 @@ async def test_prompter_chat_llm_failure(prompter_client: dict) -> None:
|
|||||||
client = prompter_client["client"]
|
client = prompter_client["client"]
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"roboco.services.prompter.AsyncAnthropic.messages.create",
|
"roboco.services.prompter.PrompterService._create_message",
|
||||||
new_callable=AsyncMock,
|
new_callable=AsyncMock,
|
||||||
side_effect=Exception("Anthropic API unavailable"),
|
side_effect=Exception("Anthropic API unavailable"),
|
||||||
):
|
):
|
||||||
@@ -658,7 +676,7 @@ async def test_prompter_draft_success(prompter_client: dict) -> None:
|
|||||||
mock_response.content = [MagicMock(text=json.dumps(draft_json))]
|
mock_response.content = [MagicMock(text=json.dumps(draft_json))]
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"roboco.services.prompter.AsyncAnthropic.messages.create",
|
"roboco.services.prompter.PrompterService._create_message",
|
||||||
new_callable=AsyncMock,
|
new_callable=AsyncMock,
|
||||||
return_value=mock_response,
|
return_value=mock_response,
|
||||||
):
|
):
|
||||||
@@ -688,7 +706,7 @@ async def test_prompter_draft_invalid_json_from_llm(prompter_client: dict) -> No
|
|||||||
mock_response.content = [MagicMock(text="not valid json")]
|
mock_response.content = [MagicMock(text="not valid json")]
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"roboco.services.prompter.AsyncAnthropic.messages.create",
|
"roboco.services.prompter.PrompterService._create_message",
|
||||||
new_callable=AsyncMock,
|
new_callable=AsyncMock,
|
||||||
return_value=mock_response,
|
return_value=mock_response,
|
||||||
):
|
):
|
||||||
@@ -718,7 +736,7 @@ async def test_prompter_draft_schema_mismatch(prompter_client: dict) -> None:
|
|||||||
mock_response.content = [MagicMock(text=json.dumps(bad_draft))]
|
mock_response.content = [MagicMock(text=json.dumps(bad_draft))]
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"roboco.services.prompter.AsyncAnthropic.messages.create",
|
"roboco.services.prompter.PrompterService._create_message",
|
||||||
new_callable=AsyncMock,
|
new_callable=AsyncMock,
|
||||||
return_value=mock_response,
|
return_value=mock_response,
|
||||||
):
|
):
|
||||||
@@ -740,7 +758,7 @@ async def test_prompter_draft_llm_failure(prompter_client: dict) -> None:
|
|||||||
client = prompter_client["client"]
|
client = prompter_client["client"]
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"roboco.services.prompter.AsyncAnthropic.messages.create",
|
"roboco.services.prompter.PrompterService._create_message",
|
||||||
new_callable=AsyncMock,
|
new_callable=AsyncMock,
|
||||||
side_effect=Exception("Anthropic API unavailable"),
|
side_effect=Exception("Anthropic API unavailable"),
|
||||||
):
|
):
|
||||||
|
|||||||
Reference in New Issue
Block a user