"""Unit tests for filesystem tool (non-AI operations)."""
import pytest
from navi.tools.filesystem import FilesystemTool, _check_path
from navi.tools.base import ToolResult
class TestCheckPath:
@pytest.fixture(autouse=True)
def _allow_all(self, monkeypatch):
import navi.tools.filesystem as _fs_mod
monkeypatch.setattr(_fs_mod.settings, "fs_allowed_paths", "*")
def test_resolves_relative(self):
p = _check_path(".")
assert p is not None
assert p.is_dir()
def test_expands_tilde(self):
p = _check_path("~")
assert p is not None
def test_rejects_empty(self):
# Empty path resolves to cwd, so _check_path returns a Path, not None
assert _check_path("") is not None
def test_restricted_paths(self, monkeypatch, tmp_path):
import navi.tools.filesystem as _fs_mod
allowed = tmp_path / "allowed"
allowed.mkdir()
blocked = tmp_path / "blocked"
blocked.mkdir()
monkeypatch.setattr(_fs_mod.settings, "fs_allowed_paths", str(allowed))
assert _check_path(str(allowed / "file.txt")) is not None
assert _check_path(str(blocked / "file.txt")) is None
class TestFilesystemToolBasic:
@pytest.fixture(autouse=True)
def _allow_all(self, monkeypatch):
import navi.tools.filesystem as _fs_mod
monkeypatch.setattr(_fs_mod.settings, "fs_allowed_paths", "*")
@pytest.fixture
def tool(self):
return FilesystemTool()
async def test_read_file(self, tool, tmp_path):
f = tmp_path / "hello.txt"
f.write_text("world")
result = await tool.execute({"action": "read", "path": str(f)})
assert result.success
assert "world" in result.output
async def test_read_missing(self, tool, tmp_path):
f = tmp_path / "missing.txt"
result = await tool.execute({"action": "read", "path": str(f)})
assert not result.success
assert "not found" in result.output.lower() or "does not exist" in result.output.lower()
async def test_write_file(self, tool, tmp_path):
f = tmp_path / "write.txt"
result = await tool.execute({"action": "write", "path": str(f), "content": "data"})
assert result.success
assert f.read_text() == "data"
async def test_list_dir(self, tool, tmp_path):
(tmp_path / "a.txt").write_text("a")
(tmp_path / "b.txt").write_text("b")
result = await tool.execute({"action": "list", "path": str(tmp_path)})
assert result.success
assert "a.txt" in result.output
assert "b.txt" in result.output
async def test_exists_true(self, tool, tmp_path):
f = tmp_path / "exists.txt"
f.write_text("")
result = await tool.execute({"action": "exists", "path": str(f)})
assert result.success
assert "true" in result.output.lower() or "exists" in result.output.lower()
async def test_exists_false(self, tool, tmp_path):
f = tmp_path / "no.txt"
result = await tool.execute({"action": "exists", "path": str(f)})
# exists returns success=True because the check itself succeeded;
# the answer is in the output text
assert result.success
assert "false" in result.output.lower()
async def test_delete_file(self, tool, tmp_path):
f = tmp_path / "del.txt"
f.write_text("bye")
result = await tool.execute({"action": "delete", "path": str(f)})
assert result.success
assert not f.exists()
async def test_info_file(self, tool, tmp_path):
f = tmp_path / "info.txt"
f.write_text("content")
result = await tool.execute({"action": "info", "path": str(f)})
assert result.success
assert "info.txt" in result.output
async def test_append_file(self, tool, tmp_path):
f = tmp_path / "append.txt"
f.write_text("first")
result = await tool.execute({"action": "append", "path": str(f), "content": "-second"})
assert result.success
assert f.read_text() == "first-second"