"""Unit tests for SafetyManager."""
import os
import tempfile
from pathlib import Path
from unittest.mock import patch, MagicMock
import pytest
from file_system_mcp_server.config import Config
from file_system_mcp_server.safety import SafetyManager
class TestSafetyManager:
"""Test the SafetyManager class."""
def test_init_creates_backup_directory(self):
"""Test that SafetyManager creates backup directory on init."""
with tempfile.TemporaryDirectory() as temp_dir:
backup_dir = Path(temp_dir) / "backups"
config = Config(backup_directory=str(backup_dir))
safety = SafetyManager(config)
assert backup_dir.exists()
assert backup_dir.is_dir()
assert safety.backup_dir == backup_dir
def test_validate_path_allowed(self):
"""Test path validation for allowed paths."""
with tempfile.TemporaryDirectory() as temp_dir:
config = Config(
backup_directory=temp_dir,
protected_paths=["/etc", "/usr"]
)
safety = SafetyManager(config)
# Test allowed path
test_file = Path(temp_dir) / "test.txt"
test_file.touch()
assert safety.validate_path(str(test_file)) is True
def test_validate_path_protected(self):
"""Test path validation for protected paths."""
with tempfile.TemporaryDirectory() as temp_dir:
config = Config(
backup_directory=temp_dir,
protected_paths=[temp_dir] # Protect the temp directory
)
safety = SafetyManager(config)
# Test protected path
test_file = Path(temp_dir) / "test.txt"
assert safety.validate_path(str(test_file)) is False
def test_is_safe_operation_read(self):
"""Test safety check for read operations."""
with tempfile.TemporaryDirectory() as temp_dir:
config = Config(
backup_directory=temp_dir,
max_file_size=1000
)
safety = SafetyManager(config)
# Create a small file
small_file = Path(temp_dir) / "small.txt"
small_file.write_text("small content")
assert safety.is_safe_operation("read", str(small_file)) is True
def test_is_safe_operation_large_file(self):
"""Test safety check for large files."""
with tempfile.TemporaryDirectory() as temp_dir:
config = Config(
backup_directory=temp_dir,
max_file_size=10 # Very small limit
)
safety = SafetyManager(config)
# Create a file larger than the limit
large_file = Path(temp_dir) / "large.txt"
large_file.write_text("this content is longer than 10 bytes")
assert safety.is_safe_operation("read", str(large_file)) is False
def test_create_backup_file(self):
"""Test creating backup of a file."""
with tempfile.TemporaryDirectory() as temp_dir:
config = Config(backup_directory=temp_dir, enable_backups=True)
safety = SafetyManager(config)
# Create source file
source_file = Path(temp_dir) / "source.txt"
source_file.write_text("original content")
# Create backup
backup_path = safety.create_backup(str(source_file))
assert backup_path is not None
backup_file = Path(backup_path)
assert backup_file.exists()
assert backup_file.read_text() == "original content"
assert backup_file.name.startswith("source.txt.")
assert backup_file.name.endswith(".backup")
def test_create_backup_disabled(self):
"""Test backup creation when backups are disabled."""
with tempfile.TemporaryDirectory() as temp_dir:
config = Config(backup_directory=temp_dir, enable_backups=False)
safety = SafetyManager(config)
source_file = Path(temp_dir) / "source.txt"
source_file.write_text("content")
backup_path = safety.create_backup(str(source_file))
assert backup_path is None
def test_create_backup_nonexistent_file(self):
"""Test backup creation for nonexistent file."""
with tempfile.TemporaryDirectory() as temp_dir:
config = Config(backup_directory=temp_dir, enable_backups=True)
safety = SafetyManager(config)
backup_path = safety.create_backup("/nonexistent/file.txt")
assert backup_path is None
def test_restore_from_backup(self):
"""Test restoring file from backup."""
with tempfile.TemporaryDirectory() as temp_dir:
config = Config(backup_directory=temp_dir, enable_backups=True)
safety = SafetyManager(config)
# Create original file and backup
original_file = Path(temp_dir) / "original.txt"
original_file.write_text("original content")
backup_path = safety.create_backup(str(original_file))
assert backup_path is not None
# Modify original file
original_file.write_text("modified content")
# Restore from backup
success = safety.restore_from_backup(backup_path, str(original_file))
assert success is True
assert original_file.read_text() == "original content"
def test_restore_from_nonexistent_backup(self):
"""Test restoring from nonexistent backup."""
with tempfile.TemporaryDirectory() as temp_dir:
config = Config(backup_directory=temp_dir)
safety = SafetyManager(config)
success = safety.restore_from_backup("/nonexistent/backup.txt", "/some/file.txt")
assert success is False
def test_cleanup_old_backups(self):
"""Test cleaning up old backup files."""
with tempfile.TemporaryDirectory() as temp_dir:
config = Config(backup_directory=temp_dir, enable_backups=True)
safety = SafetyManager(config)
# Create some backup files
old_backup = safety.backup_dir / "old.txt.20230101_120000_000.backup"
old_backup.touch()
recent_backup = safety.backup_dir / "recent.txt.20991231_120000_000.backup"
recent_backup.touch()
# Clean up old backups (0 days = clean everything)
cleaned = safety.cleanup_old_backups(max_age_days=0)
assert cleaned >= 1
assert not old_backup.exists()
def test_get_backup_info(self):
"""Test getting backup information."""
with tempfile.TemporaryDirectory() as temp_dir:
config = Config(backup_directory=temp_dir, enable_backups=True)
safety = SafetyManager(config)
# Create original file and backup
original_file = Path(temp_dir) / "test.txt"
original_file.write_text("content")
backup_path = safety.create_backup(str(original_file))
assert backup_path is not None
# Get backup info
backups = safety.get_backup_info(str(original_file))
assert len(backups) == 1
assert backups[0]["path"] == backup_path
assert "created" in backups[0]
assert "size" in backups[0]
assert "modified" in backups[0]
def test_validate_file_extension_all_allowed(self):
"""Test file extension validation when all extensions are allowed."""
with tempfile.TemporaryDirectory() as temp_dir:
config = Config(backup_directory=temp_dir, allowed_extensions=None)
safety = SafetyManager(config)
assert safety.validate_file_extension("test.txt") is True
assert safety.validate_file_extension("test.py") is True
assert safety.validate_file_extension("test.exe") is True
def test_validate_file_extension_restricted(self):
"""Test file extension validation with restrictions."""
with tempfile.TemporaryDirectory() as temp_dir:
config = Config(
backup_directory=temp_dir,
allowed_extensions=[".txt", ".py", ".md"]
)
safety = SafetyManager(config)
assert safety.validate_file_extension("test.txt") is True
assert safety.validate_file_extension("test.py") is True
assert safety.validate_file_extension("test.md") is True
assert safety.validate_file_extension("test.exe") is False
assert safety.validate_file_extension("test.bin") is False
def test_validate_file_extension_case_insensitive(self):
"""Test file extension validation is case insensitive."""
with tempfile.TemporaryDirectory() as temp_dir:
config = Config(
backup_directory=temp_dir,
allowed_extensions=[".txt", ".PY"]
)
safety = SafetyManager(config)
assert safety.validate_file_extension("test.TXT") is True
assert safety.validate_file_extension("test.py") is True
assert safety.validate_file_extension("test.Py") is True