mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
85 lines
2.7 KiB
Python
85 lines
2.7 KiB
Python
"""Assembly integrity at submit_up — every completed child's work must be in
|
|||
|
|
the assembled branch (incident #11).
|
||
|
|
|
||
|
|
Live break (2026-07-02, S6 cell PR #183): revert subtask 3b9cc162 COMPLETED,
|
||
|
|
but its commit never landed on the assembled cell branch — the reviewer
|
||
|
|
re-flagged the exact violation the revert fixed, spawning another revision
|
||
|
|
cycle. The gate verifies patch-equivalence (rebase-safe) per completed child
|
||
|
|
and refuses the submit naming the children whose work is missing.
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from typing import Any
|
||
|
|
from unittest.mock import AsyncMock, MagicMock
|
||
|
|
from uuid import uuid4
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
from roboco.services.gateway.choreographer import Choreographer, ChoreographerDeps
|
||
|
|
|
||
|
|
|
||
|
|
def _make_deps(**overrides: Any) -> ChoreographerDeps:
|
||
|
|
base: dict[str, Any] = {
|
||
|
|
"task": AsyncMock(),
|
||
|
|
"work_session": AsyncMock(),
|
||
|
|
"git": AsyncMock(),
|
||
|
|
"a2a": AsyncMock(),
|
||
|
|
"journal": AsyncMock(),
|
||
|
|
"audit": AsyncMock(),
|
||
|
|
"evidence_repo": AsyncMock(),
|
||
|
|
}
|
||
|
|
base.update(overrides)
|
||
|
|
return ChoreographerDeps(**base)
|
||
|
|
|
||
|
|
|
||
|
|
def _cell_task() -> MagicMock:
|
||
|
|
return MagicMock(
|
||
|
|
id=uuid4(), branch_name="feature/frontend/root--cell", team="frontend"
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_integrity_passes_when_all_children_merged() -> None:
|
||
|
|
git = AsyncMock()
|
||
|
|
git.unmerged_child_commits.return_value = []
|
||
|
|
c = Choreographer(_make_deps(git=git))
|
||
|
|
env = await c._assembly_integrity_guard(_cell_task(), verb="submit_up")
|
||
|
|
assert env is None
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_integrity_rejects_naming_missing_children() -> None:
|
||
|
|
git = AsyncMock()
|
||
|
|
git.unmerged_child_commits.return_value = [
|
||
|
|
{
|
||
|
|
"task_id": "3b9cc162",
|
||
|
|
"title": "Revert stats.json artifact",
|
||
|
|
"unmerged": 1,
|
||
|
|
}
|
||
|
|
]
|
||
|
|
c = Choreographer(_make_deps(git=git))
|
||
|
|
env = await c._assembly_integrity_guard(_cell_task(), verb="submit_up")
|
||
|
|
assert env is not None
|
||
|
|
body = env.as_dict()
|
||
|
|
assert body["error"] == "invalid_state"
|
||
|
|
assert "Revert stats.json artifact" in body["message"]
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_integrity_fails_open_on_git_error() -> None:
|
||
|
|
git = AsyncMock()
|
||
|
|
git.unmerged_child_commits.side_effect = RuntimeError("git sad")
|
||
|
|
c = Choreographer(_make_deps(git=git))
|
||
|
|
env = await c._assembly_integrity_guard(_cell_task(), verb="submit_up")
|
||
|
|
assert env is None
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_integrity_skips_branchless_task() -> None:
|
||
|
|
git = AsyncMock()
|
||
|
|
c = Choreographer(_make_deps(git=git))
|
||
|
|
t = MagicMock(id=uuid4(), branch_name=None)
|
||
|
|
env = await c._assembly_integrity_guard(t, verb="submit_up")
|
||
|
|
assert env is None
|
||
|
|
git.unmerged_child_commits.assert_not_awaited()
|