2026-06-03 06:35:03 +02:00
|
|
|
"""Unit tests for POST /api/v1/do/pr_update — route + schema.
|
2026-05-14 04:32:46 +02:00
|
|
|
|
|
|
|
|
Pydantic's model_validator must reject an all-None payload with 422
|
|
|
|
|
before ContentActions ever runs; a valid payload must forward title /
|
|
|
|
|
body / reviewers verbatim.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
from unittest.mock import AsyncMock, MagicMock
|
|
|
|
|
from uuid import uuid4
|
|
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
from fastapi import FastAPI
|
|
|
|
|
from fastapi.testclient import TestClient
|
|
|
|
|
from roboco.api.deps import get_content_actions
|
2026-06-03 06:35:03 +02:00
|
|
|
from roboco.api.routes.v1.do import router
|
2026-05-14 04:32:46 +02:00
|
|
|
from roboco.services.gateway.content_actions import ContentActions
|
|
|
|
|
|
|
|
|
|
_HTTP_200 = 200
|
|
|
|
|
_HTTP_422 = 422
|
|
|
|
|
|
|
|
|
|
_AGENT_ID = str(uuid4())
|
|
|
|
|
_TASK_ID = str(uuid4())
|
|
|
|
|
_HEADERS = {"X-Agent-ID": _AGENT_ID}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _make_envelope(payload: dict | None = None) -> MagicMock:
|
|
|
|
|
env = MagicMock()
|
|
|
|
|
base = {"status": "in_progress", "task_id": _TASK_ID, "next": "continue"}
|
|
|
|
|
if payload:
|
|
|
|
|
base.update(payload)
|
|
|
|
|
env.as_dict.return_value = base
|
|
|
|
|
return env
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _build_app(mock_actions: MagicMock) -> FastAPI:
|
|
|
|
|
app = FastAPI()
|
|
|
|
|
app.include_router(router)
|
|
|
|
|
app.dependency_overrides[get_content_actions] = lambda: mock_actions
|
|
|
|
|
return app
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_pr_update_all_none_returns_422() -> None:
|
|
|
|
|
"""Body with task_id only (no title/body/reviewers) → 422 from validator."""
|
|
|
|
|
mock_actions = MagicMock(spec=ContentActions)
|
|
|
|
|
mock_actions.pr_update = AsyncMock(return_value=_make_envelope())
|
|
|
|
|
client = TestClient(_build_app(mock_actions))
|
|
|
|
|
|
|
|
|
|
resp = client.post(
|
2026-06-03 06:35:03 +02:00
|
|
|
"/api/v1/do/pr_update",
|
2026-05-14 04:32:46 +02:00
|
|
|
json={"task_id": _TASK_ID},
|
|
|
|
|
headers=_HEADERS,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert resp.status_code == _HTTP_422
|
|
|
|
|
mock_actions.pr_update.assert_not_awaited()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_pr_update_title_only_forwards_to_content_actions() -> None:
|
|
|
|
|
"""Body with title only → 200, forwarded to ContentActions.pr_update."""
|
|
|
|
|
mock_actions = MagicMock(spec=ContentActions)
|
|
|
|
|
mock_actions.pr_update = AsyncMock(
|
|
|
|
|
return_value=_make_envelope({"evidence": {"updated_fields": ["title"]}})
|
|
|
|
|
)
|
|
|
|
|
client = TestClient(_build_app(mock_actions))
|
|
|
|
|
|
|
|
|
|
resp = client.post(
|
2026-06-03 06:35:03 +02:00
|
|
|
"/api/v1/do/pr_update",
|
2026-05-14 04:32:46 +02:00
|
|
|
json={"task_id": _TASK_ID, "title": "new title"},
|
|
|
|
|
headers=_HEADERS,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert resp.status_code == _HTTP_200
|
|
|
|
|
mock_actions.pr_update.assert_awaited_once()
|
|
|
|
|
call_kwargs = mock_actions.pr_update.call_args.kwargs
|
|
|
|
|
assert str(call_kwargs["task_id"]) == _TASK_ID
|
|
|
|
|
assert call_kwargs["title"] == "new title"
|
|
|
|
|
assert call_kwargs["body"] is None
|
|
|
|
|
assert call_kwargs["reviewers"] is None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_pr_update_all_fields_forwarded() -> None:
|
|
|
|
|
"""Body with title + body + reviewers → all three forwarded verbatim."""
|
|
|
|
|
mock_actions = MagicMock(spec=ContentActions)
|
|
|
|
|
mock_actions.pr_update = AsyncMock(
|
|
|
|
|
return_value=_make_envelope(
|
|
|
|
|
{"evidence": {"updated_fields": ["title", "body", "reviewers"]}}
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
client = TestClient(_build_app(mock_actions))
|
|
|
|
|
|
|
|
|
|
resp = client.post(
|
2026-06-03 06:35:03 +02:00
|
|
|
"/api/v1/do/pr_update",
|
2026-05-14 04:32:46 +02:00
|
|
|
json={
|
|
|
|
|
"task_id": _TASK_ID,
|
|
|
|
|
"title": "t",
|
|
|
|
|
"body": "b",
|
|
|
|
|
"reviewers": ["be-dev-2", "be-qa"],
|
|
|
|
|
},
|
|
|
|
|
headers=_HEADERS,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert resp.status_code == _HTTP_200
|
|
|
|
|
call_kwargs = mock_actions.pr_update.call_args.kwargs
|
|
|
|
|
assert call_kwargs["title"] == "t"
|
|
|
|
|
assert call_kwargs["body"] == "b"
|
|
|
|
|
assert call_kwargs["reviewers"] == ["be-dev-2", "be-qa"]
|