mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
83 lines
3.1 KiB
Python
83 lines
3.1 KiB
Python
"""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"
|
||
|
|
)
|