82 lines
2.6 KiB
Python
82 lines
2.6 KiB
Python
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))
|
|
|
|
from tools.resource_tools import register_resource_tools # type: ignore
|
|
|
|
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", "")
|