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