"""Conventions API routes: GET map+health, PUT commit-back, POST restore.""" from __future__ import annotations from http import HTTPStatus from typing import TYPE_CHECKING, cast from uuid import UUID, uuid4 import pytest_asyncio from fastapi import FastAPI from httpx import ASGITransport, AsyncClient from roboco.api.deps import get_agent_context, get_db from roboco.api.routes.project import router as project_router from roboco.db.tables import AgentTable from roboco.models import AgentRole, AgentStatus from roboco.models.permissions import AgentContext if TYPE_CHECKING: from collections.abc import AsyncIterator from sqlalchemy.ext.asyncio import AsyncSession _HDR = {"X-Agent-ID": str(uuid4()), "X-Agent-Role": "main_pm"} @pytest_asyncio.fixture async def client(db_session: AsyncSession) -> AsyncIterator[AsyncClient]: agent = AgentTable( id=uuid4(), name="MainPM", slug=f"main-pm-{uuid4().hex[:8]}", role=AgentRole.MAIN_PM, team=None, status=AgentStatus.ACTIVE, model_config={}, system_prompt="pm", capabilities=[], permissions={}, metrics={}, ) db_session.add(agent) await db_session.flush() app = FastAPI() app.include_router(project_router, prefix="/api/projects") async def _override_db() -> AsyncIterator[AsyncSession]: yield db_session async def _override_agent() -> AgentContext: return AgentContext( agent_id=cast("UUID", agent.id), role=AgentRole.MAIN_PM, team=None ) app.dependency_overrides[get_db] = _override_db app.dependency_overrides[get_agent_context] = _override_agent transport = ASGITransport(app=app) async with AsyncClient(transport=transport, base_url="http://test") as c: yield c app.dependency_overrides.clear() async def _make_project(client: AsyncClient) -> str: resp = await client.post( "/api/projects", headers=_HDR, json={ "name": f"Project {uuid4().hex[:6]}", "slug": f"proj-{uuid4().hex[:6]}", "git_url": "https://github.com/example/foo.git", "default_branch": "master", "assigned_cell": "backend", }, ) assert resp.status_code == HTTPStatus.CREATED return str(resp.json()["id"]) async def test_get_conventions_returns_map_and_health(client: AsyncClient) -> None: project_id = await _make_project(client) resp = await client.get(f"/api/projects/{project_id}/conventions", headers=_HDR) assert resp.status_code == HTTPStatus.OK body = resp.json() assert body["standard"]["rules"]["no_lint_suppressions"]["level"] == "block" assert body["health"]["status"] in {"missing", "unknown", "ok", "degraded"} async def test_put_conventions_commits_back(client: AsyncClient) -> None: project_id = await _make_project(client) resp = await client.put( f"/api/projects/{project_id}/conventions", headers=_HDR, json={ "version": 1, "languages": ["python"], "modules": [ {"path": "app/models", "purpose": "models", "forbidden": ["route"]} ], "rules": {"no_inline_comments": {"level": "warn"}}, "custom": [], "waivers": [], }, ) assert resp.status_code == HTTPStatus.OK # No workspace on the test project → PR not opened, but the call succeeds. assert resp.json()["created"] is False async def test_restore_conventions(client: AsyncClient) -> None: project_id = await _make_project(client) resp = await client.post( f"/api/projects/{project_id}/conventions/restore", headers=_HDR ) assert resp.status_code == HTTPStatus.OK assert "branch" in resp.json() async def test_get_conventions_unknown_project_404(client: AsyncClient) -> None: resp = await client.get(f"/api/projects/{uuid4()}/conventions", headers=_HDR) assert resp.status_code == HTTPStatus.NOT_FOUND