Files
roboco/tests/unit/services/test_work_session.py
T
Renn F 3441e37120 [sweep] strip Fxxx audit-ID tokens + trim bloated comments/docstrings + add behavior-change docs
Post-audit sweep over the 135 audit-fix commits since 19a474d3:

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.
2026-06-29 01:25:40 +02:00

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()