mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
74 lines
2.7 KiB
Python
74 lines
2.7 KiB
Python
"""``_project_slug_for`` resolves a product-only coordination root's repo.
|
|||
|
|
|
||
|
|
A normal task carries ``project_id``. A Main-PM coordination root (the only
|
||
|
|
task a root→master PR ever sits on) often carries just a ``product_id`` (the
|
||
|
|
cell→repo map) and no project of its own. Before the fallback, the in-path
|
||
|
|
gate's PR-comment post and the external reviewer's diff fetch both resolved a
|
||
|
|
``None`` slug and silently no-op'd — so the gate verdict never reached the PR
|
||
|
|
(observed live: PR #107 / root fead4372 got pr_fail'd with no PR comment).
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from typing import Any
|
||
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
||
|
|
from uuid import uuid4
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
from roboco.services.gateway.choreographer import Choreographer, ChoreographerDeps
|
||
|
|
|
||
|
|
|
||
|
|
def _make_choreographer() -> Choreographer:
|
||
|
|
task = AsyncMock()
|
||
|
|
task.session = MagicMock()
|
||
|
|
base: dict[str, Any] = {
|
||
|
|
"task": task,
|
||
|
|
"work_session": AsyncMock(),
|
||
|
|
"git": AsyncMock(),
|
||
|
|
"a2a": AsyncMock(),
|
||
|
|
"journal": AsyncMock(),
|
||
|
|
"audit": AsyncMock(),
|
||
|
|
"evidence_repo": AsyncMock(),
|
||
|
|
}
|
||
|
|
return Choreographer(ChoreographerDeps(**base))
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_slug_via_project_id_unchanged() -> None:
|
||
|
|
c = _make_choreographer()
|
||
|
|
proj = MagicMock(slug="be-proj")
|
||
|
|
with patch("roboco.services.project.get_project_service") as gps:
|
||
|
|
gps.return_value.get = AsyncMock(return_value=proj)
|
||
|
|
slug = await c._project_slug_for(MagicMock(project_id=uuid4(), product_id=None))
|
||
|
|
assert slug == "be-proj"
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_slug_falls_back_to_product_when_no_project_id() -> None:
|
||
|
|
c = _make_choreographer()
|
||
|
|
proj = MagicMock(slug="gca-backend")
|
||
|
|
with (
|
||
|
|
patch("roboco.services.project.get_project_service") as gps,
|
||
|
|
patch("roboco.services.product.get_product_service") as gprod,
|
||
|
|
):
|
||
|
|
gps.return_value.get = AsyncMock(return_value=proj)
|
||
|
|
gprod.return_value.distinct_project_ids = AsyncMock(return_value=[uuid4()])
|
||
|
|
slug = await c._project_slug_for(MagicMock(project_id=None, product_id=uuid4()))
|
||
|
|
assert slug == "gca-backend"
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_slug_none_when_no_project_and_no_product() -> None:
|
||
|
|
c = _make_choreographer()
|
||
|
|
slug = await c._project_slug_for(MagicMock(project_id=None, product_id=None))
|
||
|
|
assert slug is None
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_slug_none_when_product_has_no_projects() -> None:
|
||
|
|
c = _make_choreographer()
|
||
|
|
with patch("roboco.services.product.get_product_service") as gprod:
|
||
|
|
gprod.return_value.distinct_project_ids = AsyncMock(return_value=[])
|
||
|
|
slug = await c._project_slug_for(MagicMock(project_id=None, product_id=uuid4()))
|
||
|
|
assert slug is None
|