71 lines
2.6 KiB
Python
71 lines
2.6 KiB
Python
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
|
||
|
|
from codexpi.project_paths import ProjectPathError, browse_directories, create_directory, resolve_project_file, validate_project_path
|
||
|
|
|
||
|
|
|
||
|
|
def test_registered_project_must_be_under_allowed_root(app_env):
|
||
|
|
_, _, projects, forbidden = app_env
|
||
|
|
valid = projects / "valid"
|
||
|
|
valid.mkdir()
|
||
|
|
assert validate_project_path(str(valid), (projects,), (forbidden,)) == valid.resolve()
|
||
|
|
with pytest.raises(ProjectPathError):
|
||
|
|
validate_project_path(str(forbidden), (projects,), (forbidden,))
|
||
|
|
|
||
|
|
|
||
|
|
def test_directory_browser_starts_above_allowed_root_and_hides_forbidden(tmp_path: Path):
|
||
|
|
home = tmp_path / "home"
|
||
|
|
allowed = home / "config"
|
||
|
|
project = allowed / "NASFLIX"
|
||
|
|
forbidden = allowed / "private"
|
||
|
|
project.mkdir(parents=True)
|
||
|
|
forbidden.mkdir()
|
||
|
|
(home / "other-user").mkdir()
|
||
|
|
|
||
|
|
root = browse_directories(str(home), (allowed,), (forbidden,), home)
|
||
|
|
assert root["selectable"] is False
|
||
|
|
assert root["directories"] == [{"name": "config", "path": str(allowed), "selectable": True}]
|
||
|
|
|
||
|
|
listing = browse_directories(str(allowed), (allowed,), (forbidden,), home)
|
||
|
|
assert [item["name"] for item in listing["directories"]] == ["NASFLIX"]
|
||
|
|
assert listing["parent"] == str(home)
|
||
|
|
|
||
|
|
|
||
|
|
def test_directory_browser_creates_and_selects_empty_project_folder(tmp_path: Path):
|
||
|
|
home = tmp_path / "home"
|
||
|
|
allowed = home / "config"
|
||
|
|
allowed.mkdir(parents=True)
|
||
|
|
|
||
|
|
result = create_directory(str(allowed), "Nouveau projet", (allowed,), (), home)
|
||
|
|
|
||
|
|
assert result["path"] == str(allowed / "Nouveau projet")
|
||
|
|
assert result["selectable"] is True
|
||
|
|
assert result["directories"] == []
|
||
|
|
with pytest.raises(ProjectPathError):
|
||
|
|
create_directory(str(allowed), "../escape", (allowed,), (), home)
|
||
|
|
|
||
|
|
|
||
|
|
def test_api_rejects_forbidden_project(logged_in):
|
||
|
|
client, _, _, forbidden, headers = logged_in
|
||
|
|
response = client.post("/api/projects", headers=headers, json={"name": "bad", "path": str(forbidden)})
|
||
|
|
assert response.status_code == 400
|
||
|
|
|
||
|
|
|
||
|
|
def test_path_traversal_is_rejected(tmp_path: Path):
|
||
|
|
root = tmp_path / "project"
|
||
|
|
root.mkdir()
|
||
|
|
outside = tmp_path / "secret.txt"
|
||
|
|
outside.write_text("secret")
|
||
|
|
with pytest.raises(ProjectPathError):
|
||
|
|
resolve_project_file(root, "../secret.txt")
|
||
|
|
|
||
|
|
|
||
|
|
def test_project_operations_use_ids(logged_in):
|
||
|
|
client, _, projects, _, headers = logged_in
|
||
|
|
repo = projects / "repo"
|
||
|
|
repo.mkdir()
|
||
|
|
created = client.post("/api/projects", headers=headers, json={"name": "repo", "path": str(repo)})
|
||
|
|
assert created.status_code == 201
|
||
|
|
assert client.get("/api/projects/not-a-uuid/git").status_code == 404
|