Files
roboco/tests/unit/services/test_workspace_worktree_paths.py
T

83 lines
3.1 KiB
Python
Raw Normal View History

2026-06-29 11:32:34 +02:00
"""Per-task git-worktree path model (F123 fix, Phase A prep).
The coordinator PM exemption lets a PM hold multiple in_progress roots, but the
clone is one checkout — so switching roots' branches clobbers the working tree
(live on NAS: main-pm ping-ponged 03f80432 <-> c80e19ff on one clone). The fix:
each task gets its own working tree under ``{clone_root}/.worktrees/{task-short}/``
via ``git worktree add``. These tests pin the path layout BEFORE the helpers are
wired into the claim/spawn flow (Phase B).
"""
from __future__ import annotations
from typing import TYPE_CHECKING, cast
from unittest.mock import AsyncMock, MagicMock
import pytest
from roboco.models.base import Team
from roboco.services.workspace import WorkspaceError, WorkspaceService
if TYPE_CHECKING:
from pathlib import Path
def _service(root: Path) -> WorkspaceService:
svc = WorkspaceService(MagicMock())
svc.session = AsyncMock()
svc.root = root
return svc
def test_get_clone_root_path_equals_get_workspace_path(tmp_path: Path) -> None:
# The clone root IS the existing per-agent workspace path; the new helper
# is a named alias so call sites can express intent (clone-root vs worktree).
svc = _service(tmp_path)
clone = svc.get_clone_root_path("guard-core", Team.BACKEND, "be-dev-1")
assert clone == svc.get_workspace_path("guard-core", Team.BACKEND, "be-dev-1")
assert clone == tmp_path / "guard-core" / "backend" / "be-dev-1"
def test_get_worktree_path_lays_out_under_clone_root(tmp_path: Path) -> None:
svc = _service(tmp_path)
wt = svc.get_worktree_path("guard-core", Team.BACKEND, "be-dev-1", "a3c40fe7")
clone = svc.get_clone_root_path("guard-core", Team.BACKEND, "be-dev-1")
assert wt == clone / ".worktrees" / "a3c40fe7"
# And expressed from the workspaces root:
assert (
wt
== tmp_path / "guard-core" / "backend" / "be-dev-1" / ".worktrees" / "a3c40fe7"
)
def test_get_worktree_path_rejects_none_team(tmp_path: Path) -> None:
# Mirrors get_workspace_path's guard: a None team would produce a literal
# "None" segment and a broken path.
svc = _service(tmp_path)
with pytest.raises(WorkspaceError):
svc.get_worktree_path(
"guard-core", cast("Team | str", None), "be-dev-1", "a3c40fe7"
)
def test_get_worktree_path_accepts_string_team(tmp_path: Path) -> None:
svc = _service(tmp_path)
wt = svc.get_worktree_path("guard-core", "backend", "be-dev-1", "a3c40fe7")
assert (
wt
== tmp_path / "guard-core" / "backend" / "be-dev-1" / ".worktrees" / "a3c40fe7"
)
def test_get_worktree_path_per_task_isolation(tmp_path: Path) -> None:
# Two tasks of the same agent get DISTINCT worktree dirs (the F123 point:
# each root its own checkout, never shared).
svc = _service(tmp_path)
a = svc.get_worktree_path("guard-core", Team.BACKEND, "be-dev-1", "a3c40fe7")
b = svc.get_worktree_path("guard-core", Team.BACKEND, "be-dev-1", "8e460893")
assert a != b
assert (
a.parent
== b.parent
== tmp_path / "guard-core" / "backend" / "be-dev-1" / ".worktrees"
)