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

57 lines
1.9 KiB
Python
Raw Normal View History

"""``get_workspace_path`` rejects traversal-capable path components.
Slugs/teams are regex- or enum-validated at creation, but every workspace path
is built at this one chokepoint — pin the by-construction guard so a raw
``../`` / absolute / NUL segment can never place a workspace outside the root,
regardless of what upstream validation a future caller forgets.
"""
from __future__ import annotations
from typing import TYPE_CHECKING
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
@pytest.mark.parametrize(
"bad",
["", ".", "..", "../escape", "a/b", "a\\b", "bad\x00slug", "/etc"],
)
def test_rejects_unsafe_project_slug(tmp_path: Path, bad: str) -> None:
svc = _service(tmp_path)
with pytest.raises(WorkspaceError, match="unsafe project slug"):
svc.get_workspace_path(bad, Team.BACKEND, "be-dev-1")
@pytest.mark.parametrize("bad", ["..", "back/end", ""])
def test_rejects_unsafe_team_string(tmp_path: Path, bad: str) -> None:
svc = _service(tmp_path)
with pytest.raises(WorkspaceError, match="unsafe team"):
svc.get_workspace_path("guard-core", bad, "be-dev-1")
@pytest.mark.parametrize("bad", ["..", "../../be-dev-1", "be\x00dev"])
def test_rejects_unsafe_agent_slug(tmp_path: Path, bad: str) -> None:
svc = _service(tmp_path)
with pytest.raises(WorkspaceError, match="unsafe agent slug"):
svc.get_workspace_path("guard-core", Team.BACKEND, bad)
def test_valid_segments_unchanged(tmp_path: Path) -> None:
svc = _service(tmp_path)
path = svc.get_workspace_path("guard-core", Team.BACKEND, "be-dev-1")
assert path == tmp_path / "guard-core" / "backend" / "be-dev-1"