214 lines
8.8 KiB
Python
214 lines
8.8 KiB
Python
import os
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from core.file_manager import (
|
|
FileManagerError,
|
|
FileManagerUnavailableError,
|
|
UnsupportedFileManagerPlatformError,
|
|
normalize_file_manager_path,
|
|
open_in_file_manager,
|
|
)
|
|
|
|
|
|
class FileManagerTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.temp_dir = tempfile.TemporaryDirectory()
|
|
self.directory = os.path.join(self.temp_dir.name, "models with spaces")
|
|
os.makedirs(self.directory)
|
|
self.file_path = os.path.join(self.directory, "módel.safetensors")
|
|
with open(self.file_path, "wb") as handle:
|
|
handle.write(b"test")
|
|
|
|
def tearDown(self):
|
|
self.temp_dir.cleanup()
|
|
|
|
@staticmethod
|
|
def successful_process():
|
|
process = MagicMock()
|
|
process.wait.return_value = 0
|
|
return process
|
|
|
|
def test_rejects_empty_non_string_and_null_byte_paths(self):
|
|
for value in ("", " ", None, {"path": "model"}):
|
|
with self.subTest(value=value), self.assertRaises(ValueError):
|
|
normalize_file_manager_path(value)
|
|
with self.assertRaises(ValueError):
|
|
normalize_file_manager_path("bad\x00path")
|
|
|
|
def test_preserves_an_existing_posix_path_with_literal_backslash(self):
|
|
with patch("core.file_manager.os.name", "posix"), patch(
|
|
"core.file_manager._normalized_absolute_path",
|
|
side_effect=["/models/literal\\name", "/models/literal/name"],
|
|
), patch(
|
|
"core.file_manager.os.path.exists",
|
|
side_effect=[True],
|
|
):
|
|
result = normalize_file_manager_path("/models/literal\\name")
|
|
|
|
self.assertEqual(result, "/models/literal\\name")
|
|
|
|
def test_recovers_frontend_backslash_separators_on_posix(self):
|
|
with patch("core.file_manager.os.name", "posix"), patch(
|
|
"core.file_manager._normalized_absolute_path",
|
|
side_effect=["/models/pony\\styles", "/models/pony/styles"],
|
|
), patch(
|
|
"core.file_manager.os.path.exists",
|
|
side_effect=[False, True],
|
|
):
|
|
result = normalize_file_manager_path("/models/pony\\styles")
|
|
|
|
self.assertEqual(result, "/models/pony/styles")
|
|
|
|
@patch("core.file_manager.subprocess.Popen")
|
|
@patch("core.file_manager.shutil.which")
|
|
def test_windows_reveals_a_file_and_handles_unicode(self, mock_which, mock_popen):
|
|
mock_which.return_value = r"C:\Windows\explorer.exe"
|
|
mock_popen.return_value = self.successful_process()
|
|
|
|
result = open_in_file_manager(self.file_path, system="Windows")
|
|
|
|
command = mock_popen.call_args.args[0]
|
|
self.assertEqual(command, [r"C:\Windows\explorer.exe", "/select,", os.path.realpath(self.file_path)])
|
|
self.assertFalse(mock_popen.call_args.kwargs["shell"])
|
|
self.assertTrue(result["selected"])
|
|
|
|
@patch("core.file_manager.subprocess.Popen")
|
|
@patch("core.file_manager.shutil.which")
|
|
def test_windows_opens_a_directory_without_select(self, mock_which, mock_popen):
|
|
mock_which.return_value = r"C:\Windows\explorer.exe"
|
|
mock_popen.return_value = self.successful_process()
|
|
|
|
result = open_in_file_manager(self.directory, system="Windows")
|
|
|
|
command = mock_popen.call_args.args[0]
|
|
self.assertEqual(command, [r"C:\Windows\explorer.exe", os.path.realpath(self.directory)])
|
|
self.assertFalse(result["selected"])
|
|
|
|
@patch("core.file_manager.subprocess.Popen")
|
|
@patch("core.file_manager.shutil.which")
|
|
def test_windows_does_not_treat_explorer_exit_code_one_as_failure(
|
|
self, mock_which, mock_popen
|
|
):
|
|
mock_which.return_value = r"C:\Windows\explorer.exe"
|
|
process = MagicMock()
|
|
process.wait.return_value = 1
|
|
mock_popen.return_value = process
|
|
|
|
result = open_in_file_manager(self.file_path, system="Windows")
|
|
|
|
process.wait.assert_not_called()
|
|
self.assertEqual(result["launcher"], "explorer")
|
|
|
|
@patch("core.file_manager.subprocess.Popen")
|
|
@patch("core.file_manager.shutil.which", return_value="/usr/bin/open")
|
|
def test_macos_reveals_a_file(self, _mock_which, mock_popen):
|
|
mock_popen.return_value = self.successful_process()
|
|
|
|
result = open_in_file_manager(self.file_path, system="Darwin")
|
|
|
|
self.assertEqual(
|
|
mock_popen.call_args.args[0],
|
|
["/usr/bin/open", "-R", os.path.realpath(self.file_path)],
|
|
)
|
|
self.assertTrue(result["selected"])
|
|
|
|
@patch("core.file_manager.subprocess.Popen")
|
|
@patch("core.file_manager.shutil.which", return_value="/usr/bin/open")
|
|
def test_macos_opens_a_directory(self, _mock_which, mock_popen):
|
|
mock_popen.return_value = self.successful_process()
|
|
|
|
open_in_file_manager(self.directory, system="Darwin")
|
|
|
|
self.assertEqual(
|
|
mock_popen.call_args.args[0],
|
|
["/usr/bin/open", os.path.realpath(self.directory)],
|
|
)
|
|
|
|
@patch("core.file_manager.subprocess.Popen")
|
|
@patch("core.file_manager.shutil.which")
|
|
def test_linux_opens_the_parent_directory_with_xdg_open(self, mock_which, mock_popen):
|
|
mock_which.side_effect = lambda name: "/usr/bin/xdg-open" if name == "xdg-open" else None
|
|
mock_popen.return_value = self.successful_process()
|
|
|
|
result = open_in_file_manager(self.file_path, system="Linux")
|
|
|
|
self.assertEqual(
|
|
mock_popen.call_args.args[0],
|
|
["/usr/bin/xdg-open", os.path.realpath(self.directory)],
|
|
)
|
|
self.assertFalse(result["selected"])
|
|
|
|
@patch("core.file_manager.subprocess.Popen")
|
|
@patch("core.file_manager.shutil.which")
|
|
def test_linux_falls_back_to_gio_for_a_directory(self, mock_which, mock_popen):
|
|
mock_which.side_effect = lambda name: "/usr/bin/gio" if name == "gio" else None
|
|
mock_popen.return_value = self.successful_process()
|
|
|
|
result = open_in_file_manager(self.directory, system="Linux")
|
|
|
|
self.assertEqual(
|
|
mock_popen.call_args.args[0],
|
|
["/usr/bin/gio", "open", os.path.realpath(self.directory)],
|
|
)
|
|
self.assertEqual(result["launcher"], "gio")
|
|
|
|
@patch("core.file_manager.shutil.which", return_value=None)
|
|
def test_missing_launcher_has_a_specific_error(self, _mock_which):
|
|
with self.assertRaises(FileManagerUnavailableError):
|
|
open_in_file_manager(self.directory, system="Linux")
|
|
|
|
def test_missing_and_special_paths_are_rejected(self):
|
|
with self.assertRaises(FileNotFoundError):
|
|
open_in_file_manager(os.path.join(self.temp_dir.name, "missing"), system="Linux")
|
|
|
|
with patch("core.file_manager.os.path.exists", return_value=True), patch(
|
|
"core.file_manager.os.path.isfile", return_value=False
|
|
), patch(
|
|
"core.file_manager.os.path.isdir", return_value=False
|
|
), self.assertRaises(FileManagerError):
|
|
open_in_file_manager(self.directory, system="Linux")
|
|
|
|
def test_unsupported_platform_has_a_specific_error(self):
|
|
with self.assertRaises(UnsupportedFileManagerPlatformError):
|
|
open_in_file_manager(self.directory, system="FreeBSD")
|
|
|
|
@patch("core.file_manager.subprocess.Popen", side_effect=OSError("launch failed"))
|
|
@patch("core.file_manager.shutil.which", return_value="/usr/bin/xdg-open")
|
|
def test_spawn_failure_is_wrapped(self, _mock_which, _mock_popen):
|
|
with self.assertRaisesRegex(FileManagerError, "launch failed"):
|
|
open_in_file_manager(self.directory, system="Linux")
|
|
|
|
@patch("core.file_manager.subprocess.Popen")
|
|
@patch("core.file_manager.shutil.which", return_value="/usr/bin/xdg-open")
|
|
def test_fast_nonzero_launcher_exit_is_reported(self, _mock_which, mock_popen):
|
|
process = MagicMock()
|
|
process.wait.return_value = 3
|
|
mock_popen.return_value = process
|
|
|
|
with self.assertRaisesRegex(FileManagerError, "code 3"):
|
|
open_in_file_manager(self.directory, system="Linux")
|
|
|
|
@patch("core.file_manager.subprocess.Popen")
|
|
@patch("core.file_manager.shutil.which", return_value="/usr/bin/xdg-open")
|
|
def test_launcher_wait_failure_is_wrapped(self, _mock_which, mock_popen):
|
|
process = MagicMock()
|
|
process.wait.side_effect = OSError("wait failed")
|
|
mock_popen.return_value = process
|
|
|
|
with self.assertRaisesRegex(FileManagerError, "wait failed"):
|
|
open_in_file_manager(self.directory, system="Linux")
|
|
|
|
@patch("core.file_manager.subprocess.Popen")
|
|
@patch("core.file_manager.shutil.which", return_value="/usr/bin/xdg-open")
|
|
def test_long_running_launcher_is_treated_as_started(self, _mock_which, mock_popen):
|
|
process = MagicMock()
|
|
process.wait.side_effect = subprocess.TimeoutExpired("xdg-open", 2.0)
|
|
mock_popen.return_value = process
|
|
|
|
result = open_in_file_manager(self.directory, system="Linux")
|
|
|
|
self.assertEqual(result["launcher"], "xdg-open")
|