from tools.resource_tools import register_resource_tools # type: ignore
import pytest
import sys
from pathlib import Path
import pytest
import types
# locate server src dynamically to avoid hardcoded layout assumptions
ROOT = Path(__file__).resolve().parents[1]
candidates = [
ROOT / "UnityMcpBridge" / "UnityMcpServer~" / "src",
ROOT / "UnityMcpServer~" / "src",
]
SRC = next((p for p in candidates if p.exists()), None)
if SRC is None:
searched = "\n".join(str(p) for p in candidates)
pytest.skip(
"Unity MCP server source not found. Tried:\n" + searched,
allow_module_level=True,
)
sys.path.insert(0, str(SRC))
class DummyMCP:
def __init__(self):
self._tools = {}
def tool(self, *args, **kwargs): # accept kwargs like description
def deco(fn):
self._tools[fn.__name__] = fn
return fn
return deco
@pytest.fixture()
def resource_tools():
mcp = DummyMCP()
register_resource_tools(mcp)
return mcp._tools
def test_resource_list_filters_and_rejects_traversal(resource_tools, tmp_path, monkeypatch):
# Create fake project structure
proj = tmp_path
assets = proj / "Assets" / "Scripts"
assets.mkdir(parents=True)
(assets / "A.cs").write_text("// a", encoding="utf-8")
(assets / "B.txt").write_text("b", encoding="utf-8")
outside = tmp_path / "Outside.cs"
outside.write_text("// outside", encoding="utf-8")
# Symlink attempting to escape
sneaky_link = assets / "link_out"
try:
sneaky_link.symlink_to(outside)
except Exception:
# Some platforms may not allow symlinks in tests; ignore
pass
list_resources = resource_tools["list_resources"]
# Only .cs under Assets should be listed
import asyncio
resp = asyncio.get_event_loop().run_until_complete(
list_resources(ctx=None, pattern="*.cs", under="Assets",
limit=50, project_root=str(proj))
)
assert resp["success"] is True
uris = resp["data"]["uris"]
assert any(u.endswith("Assets/Scripts/A.cs") for u in uris)
assert not any(u.endswith("B.txt") for u in uris)
assert not any(u.endswith("Outside.cs") for u in uris)
def test_resource_list_rejects_outside_paths(resource_tools, tmp_path):
proj = tmp_path
# under points outside Assets
list_resources = resource_tools["list_resources"]
import asyncio
resp = asyncio.get_event_loop().run_until_complete(
list_resources(ctx=None, pattern="*.cs", under="..",
limit=10, project_root=str(proj))
)
assert resp["success"] is False
assert "Assets" in resp.get(
"error", "") or "under project root" in resp.get("error", "")