test_execution_engine, test_mcp_server_code_execution test_mcp_server_files_search test_mcp_server_web_search müssen noch gemacht werden
217 lines
8.3 KiB
Python
217 lines
8.3 KiB
Python
"""Tests for FileManager (backend/managers/file_manager.py)."""
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
import pytest
|
|
from unittest.mock import patch
|
|
|
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
|
|
from backend.managers.file_manager import FileManager
|
|
|
|
|
|
# ── Fixtures ──────────────────────────────────────────────────────────────────
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def mock_streamlit():
|
|
"""Suppress all st.error / st.warning calls — they require a running Streamlit app."""
|
|
with patch("backend.managers.file_manager.st"):
|
|
yield
|
|
|
|
|
|
@pytest.fixture
|
|
def fm(tmp_path):
|
|
return FileManager(base_path=tmp_path)
|
|
|
|
|
|
# ── create_folder ─────────────────────────────────────────────────────────────
|
|
|
|
class TestCreateFolder:
|
|
def test_creates_folder_successfully(self, fm, tmp_path):
|
|
result = fm.create_folder("", "myfolder")
|
|
assert result is True
|
|
assert (tmp_path / "myfolder").is_dir()
|
|
|
|
def test_empty_name_returns_false(self, fm):
|
|
assert fm.create_folder("", "") is False
|
|
|
|
def test_slash_in_name_returns_false(self, fm):
|
|
assert fm.create_folder("", "a/b") is False
|
|
|
|
def test_backslash_in_name_returns_false(self, fm):
|
|
assert fm.create_folder("", "a\\b") is False
|
|
|
|
def test_duplicate_folder_returns_false(self, fm, tmp_path):
|
|
(tmp_path / "existing").mkdir()
|
|
assert fm.create_folder("", "existing") is False
|
|
|
|
def test_path_traversal_returns_false(self, fm):
|
|
assert fm.create_folder("../../", "evil") is False
|
|
|
|
def test_nested_folder_created_inside_base(self, fm, tmp_path):
|
|
(tmp_path / "sub").mkdir()
|
|
result = fm.create_folder("sub", "child")
|
|
assert result is True
|
|
assert (tmp_path / "sub" / "child").is_dir()
|
|
|
|
|
|
# ── create_file ───────────────────────────────────────────────────────────────
|
|
|
|
class TestCreateFile:
|
|
def test_creates_file_successfully(self, fm, tmp_path):
|
|
result = fm.create_file("", "test.py")
|
|
assert result is True
|
|
assert (tmp_path / "test.py").is_file()
|
|
|
|
def test_empty_name_returns_false(self, fm):
|
|
assert fm.create_file("", "") is False
|
|
|
|
def test_whitespace_only_name_returns_false(self, fm):
|
|
assert fm.create_file("", " ") is False
|
|
|
|
def test_no_extension_defaults_to_txt(self, fm, tmp_path):
|
|
fm.create_file("", "notes")
|
|
assert (tmp_path / "notes.txt").is_file()
|
|
|
|
def test_duplicate_file_returns_false(self, fm, tmp_path):
|
|
(tmp_path / "existing.py").touch()
|
|
assert fm.create_file("", "existing.py") is False
|
|
|
|
def test_path_traversal_returns_false(self, fm):
|
|
assert fm.create_file("../../", "evil.py") is False
|
|
|
|
|
|
# ── read_file ─────────────────────────────────────────────────────────────────
|
|
|
|
class TestReadFile:
|
|
def test_reads_file_content(self, fm, tmp_path):
|
|
f = tmp_path / "hello.py"
|
|
f.write_text("print('hello')")
|
|
assert fm.read_file(f) == "print('hello')"
|
|
|
|
def test_nonexistent_file_returns_empty_string(self, fm, tmp_path):
|
|
assert fm.read_file(tmp_path / "ghost.py") == ""
|
|
|
|
def test_directory_path_returns_empty_string(self, fm, tmp_path):
|
|
sub = tmp_path / "subdir"
|
|
sub.mkdir()
|
|
assert fm.read_file(sub) == ""
|
|
|
|
def test_file_outside_workspace_returns_empty_string(self, fm, tmp_path):
|
|
outside = tmp_path.parent / "outside.py"
|
|
outside.write_text("secret")
|
|
result = fm.read_file(outside)
|
|
assert result == ""
|
|
outside.unlink()
|
|
|
|
|
|
# ── save_file ─────────────────────────────────────────────────────────────────
|
|
|
|
class TestSaveFile:
|
|
def test_saves_content_to_file(self, fm, tmp_path):
|
|
f = tmp_path / "output.py"
|
|
f.touch()
|
|
result = fm.save_file(str(f), "x = 1")
|
|
assert result is True
|
|
assert f.read_text() == "x = 1"
|
|
|
|
def test_overwrites_existing_content(self, fm, tmp_path):
|
|
f = tmp_path / "script.py"
|
|
f.write_text("old content")
|
|
fm.save_file(str(f), "new content")
|
|
assert f.read_text() == "new content"
|
|
|
|
def test_path_traversal_returns_false(self, fm, tmp_path):
|
|
outside = str(tmp_path.parent / "evil.py")
|
|
assert fm.save_file(outside, "bad") is False
|
|
|
|
|
|
# ── rename_file ───────────────────────────────────────────────────────────────
|
|
|
|
class TestRenameFile:
|
|
def test_renames_file_successfully(self, fm, tmp_path):
|
|
(tmp_path / "old.py").touch()
|
|
result = fm.rename_file("old.py", "new")
|
|
assert result is True
|
|
assert (tmp_path / "new.py").exists()
|
|
assert not (tmp_path / "old.py").exists()
|
|
|
|
def test_preserves_original_extension(self, fm, tmp_path):
|
|
(tmp_path / "script.py").touch()
|
|
fm.rename_file("script.py", "renamed.txt")
|
|
assert (tmp_path / "renamed.py").exists()
|
|
|
|
def test_empty_new_name_returns_false(self, fm, tmp_path):
|
|
(tmp_path / "file.py").touch()
|
|
assert fm.rename_file("file.py", "") is False
|
|
|
|
def test_nonexistent_file_returns_false(self, fm):
|
|
assert fm.rename_file("ghost.py", "new_name") is False
|
|
|
|
def test_path_traversal_returns_false(self, fm):
|
|
assert fm.rename_file("../../evil.py", "new_name") is False
|
|
|
|
|
|
# ── delete_file ───────────────────────────────────────────────────────────────
|
|
|
|
class TestDeleteFile:
|
|
def test_deletes_file_successfully(self, fm, tmp_path):
|
|
f = tmp_path / "todelete.py"
|
|
f.touch()
|
|
result = fm.delete_file("todelete.py")
|
|
assert result is True
|
|
assert not f.exists()
|
|
|
|
def test_nonexistent_file_returns_false(self, fm):
|
|
assert fm.delete_file("ghost.py") is False
|
|
|
|
def test_path_traversal_returns_false(self, fm):
|
|
assert fm.delete_file("../../evil.py") is False
|
|
|
|
|
|
# ── delete_folder ─────────────────────────────────────────────────────────────
|
|
|
|
class TestDeleteFolder:
|
|
def test_deletes_folder_and_contents(self, fm, tmp_path):
|
|
sub = tmp_path / "todelete"
|
|
sub.mkdir()
|
|
(sub / "file.py").touch()
|
|
result = fm.delete_folder("todelete")
|
|
assert result is True
|
|
assert not sub.exists()
|
|
|
|
def test_nonexistent_folder_returns_false(self, fm):
|
|
assert fm.delete_folder("ghost") is False
|
|
|
|
def test_path_traversal_returns_false(self, fm):
|
|
assert fm.delete_folder("../../") is False
|
|
|
|
|
|
# ── get_file_tree ─────────────────────────────────────────────────────────────
|
|
|
|
class TestGetFileTree:
|
|
def test_empty_workspace_returns_empty_dict(self, fm):
|
|
assert fm.get_file_tree() == {}
|
|
|
|
def test_file_is_represented_as_none(self, fm, tmp_path):
|
|
(tmp_path / "main.py").touch()
|
|
tree = fm.get_file_tree()
|
|
assert tree["main.py"] is None
|
|
|
|
def test_directory_is_represented_as_dict(self, fm, tmp_path):
|
|
(tmp_path / "src").mkdir()
|
|
tree = fm.get_file_tree()
|
|
assert isinstance(tree["src"], dict)
|
|
|
|
def test_nested_structure_is_correct(self, fm, tmp_path):
|
|
(tmp_path / "src").mkdir()
|
|
(tmp_path / "src" / "app.py").touch()
|
|
tree = fm.get_file_tree()
|
|
assert tree["src"]["app.py"] is None
|
|
|
|
def test_entries_are_sorted(self, fm, tmp_path):
|
|
(tmp_path / "z_file.py").touch()
|
|
(tmp_path / "a_file.py").touch()
|
|
keys = list(fm.get_file_tree().keys())
|
|
assert keys == sorted(keys)
|