mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
Post-audit sweep over the 135 audit-fix commits since19a474d3: 1. Stripped every # Fxxx: audit-ID token from comments AND every Fxxx token from docstring openings across 211 blocks / ~626 lines. The CEO flagged these twice: audit-issue IDs in code confuse future devs/agents. The descriptive text is preserved; only the Fxxx token is removed (and bloated narrative blocks trimmed to 1-3 lines keeping the one non-obvious invariant). 2. Trimmed bloated comments/docstrings to the concise standard (1-3 lines). 3. Added missing behavior-change docs for the audit-fix batch: prompts/roles (documenter, pr_reviewer, qa), user-facing docs (api auth, websockets, agent-gateway, megatask, merge-model, task-lifecycle, grok, resilience, conventions, panel, security, troubleshooting), and the RAG corpus (cell-pm, main-pm, pr-reviewer, qa roles; conventions; messaging-tools; escalation; megatask; task-claiming workflows). Comment/docstring/prose ONLY — zero code-line edits (verified: the diff contains no def/class/return/if/for/await/assignment/call lines). Gates green: ruff format + ruff check clean, mypy clean on roboco/. The only pytest failures are the pre-existing sync_branch tracing-decision gap (B1,250be5c2) — not sweep-caused and tracked separately.
162 lines
5.6 KiB
Python
162 lines
5.6 KiB
Python
"""Unit tests for WorkSessionService gateway-backfill methods."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import UTC, datetime
|
|
from unittest.mock import AsyncMock, MagicMock
|
|
from uuid import uuid4
|
|
|
|
import pytest
|
|
from roboco.models.work_session import WorkSessionStatus
|
|
from roboco.services.work_session import WorkSessionService
|
|
|
|
|
|
def _service() -> WorkSessionService:
|
|
session = MagicMock()
|
|
session.execute = AsyncMock()
|
|
session.flush = AsyncMock()
|
|
return WorkSessionService(session)
|
|
|
|
|
|
def _bind(svc: WorkSessionService, name: str, value: object) -> None:
|
|
object.__setattr__(svc, name, value)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_files_changed_returns_files_modified_list() -> None:
|
|
svc = _service()
|
|
fake_session = MagicMock(files_modified=["roboco/api/app.py", "README.md"])
|
|
_bind(svc, "get", AsyncMock(return_value=fake_session))
|
|
out = await svc.files_changed(uuid4())
|
|
assert out == ["roboco/api/app.py", "README.md"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_files_changed_returns_empty_list_when_session_missing() -> None:
|
|
svc = _service()
|
|
_bind(svc, "get", AsyncMock(return_value=None))
|
|
assert await svc.files_changed(uuid4()) == []
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_files_changed_handles_none_files_modified() -> None:
|
|
svc = _service()
|
|
fake_session = MagicMock(files_modified=None)
|
|
_bind(svc, "get", AsyncMock(return_value=fake_session))
|
|
assert await svc.files_changed(uuid4()) == []
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_has_unpushed_commits_false_when_no_commits() -> None:
|
|
svc = _service()
|
|
fake_session = MagicMock(commits=[], pr_number=None)
|
|
_bind(svc, "get", AsyncMock(return_value=fake_session))
|
|
assert await svc.has_unpushed_commits(uuid4()) is False
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_has_unpushed_commits_true_when_commits_but_no_pr() -> None:
|
|
svc = _service()
|
|
fake_session = MagicMock(commits=["abc", "def"], pr_number=None)
|
|
_bind(svc, "get", AsyncMock(return_value=fake_session))
|
|
assert await svc.has_unpushed_commits(uuid4()) is True
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_has_unpushed_commits_false_when_pr_exists() -> None:
|
|
svc = _service()
|
|
fake_session = MagicMock(commits=["abc"], pr_number=42)
|
|
_bind(svc, "get", AsyncMock(return_value=fake_session))
|
|
assert await svc.has_unpushed_commits(uuid4()) is False
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_has_unpushed_commits_false_when_session_missing() -> None:
|
|
svc = _service()
|
|
_bind(svc, "get", AsyncMock(return_value=None))
|
|
assert await svc.has_unpushed_commits(uuid4()) is False
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# merge_pr must be idempotent + active-guarded like close()/complete()
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _merge_service() -> tuple[WorkSessionService, MagicMock]:
|
|
"""A service whose session flush is observable. Returns ``(svc, session)`` so
|
|
the tests can assert that a guarded no-op never reached ``flush``."""
|
|
session = MagicMock()
|
|
session.flush = AsyncMock()
|
|
svc = WorkSessionService(session)
|
|
return svc, session
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_merge_pr_completes_active_session() -> None:
|
|
"""Happy path: an ACTIVE session is recorded as merged + COMPLETED."""
|
|
|
|
original_merger = uuid4()
|
|
ws = MagicMock(
|
|
status=WorkSessionStatus.ACTIVE, pr_number=42, branch_name="feature/x"
|
|
)
|
|
svc, session = _merge_service()
|
|
_bind(svc, "get", AsyncMock(return_value=ws))
|
|
|
|
result = await svc.merge_pr(uuid4(), original_merger)
|
|
|
|
assert result is ws
|
|
assert ws.pr_status == "merged"
|
|
assert ws.merged_by == original_merger
|
|
assert ws.pr_merged_at is not None
|
|
assert ws.status == WorkSessionStatus.COMPLETED
|
|
session.flush.assert_awaited_once()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_merge_pr_idempotent_on_already_completed_preserves_audit_trail() -> None:
|
|
"""A retried merge after a successful-but-unconfirmed GitHub merge must NOT
|
|
overwrite the original ``merged_by`` / ``pr_merged_at`` — the merge audit
|
|
trail is preserved. Mirrors close()'s idempotency guard."""
|
|
|
|
original_merger = uuid4()
|
|
original_ts = datetime(2026, 6, 1, 12, 0, tzinfo=UTC)
|
|
retry_merger = uuid4()
|
|
ws = MagicMock(
|
|
status=WorkSessionStatus.COMPLETED,
|
|
pr_number=42,
|
|
merged_by=original_merger,
|
|
pr_merged_at=original_ts,
|
|
pr_status="merged",
|
|
)
|
|
svc, session = _merge_service()
|
|
_bind(svc, "get", AsyncMock(return_value=ws))
|
|
|
|
result = await svc.merge_pr(uuid4(), retry_merger)
|
|
|
|
assert result is ws
|
|
# Audit trail untouched — retry did not overwrite the original merger/ts.
|
|
assert ws.merged_by == original_merger
|
|
assert ws.pr_merged_at == original_ts
|
|
assert ws.status == WorkSessionStatus.COMPLETED
|
|
# No mutation happened, so flush must not have run.
|
|
session.flush.assert_not_awaited()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_merge_pr_does_not_resurrect_abandoned_session() -> None:
|
|
"""``merge_pr`` on an ABANDONED session must NOT flip it to COMPLETED — that
|
|
would silently undo the single-active invariant's abandonment and make
|
|
discarded work look like a successful merge."""
|
|
|
|
ws = MagicMock(status=WorkSessionStatus.ABANDONED, pr_number=42, merged_by=None)
|
|
svc, session = _merge_service()
|
|
_bind(svc, "get", AsyncMock(return_value=ws))
|
|
|
|
result = await svc.merge_pr(uuid4(), uuid4())
|
|
|
|
assert result is ws
|
|
# Not resurrected — stays abandoned, no merger recorded.
|
|
assert ws.status == WorkSessionStatus.ABANDONED
|
|
assert ws.merged_by is None
|
|
session.flush.assert_not_awaited()
|