Files
MoviePilot/tests/test_system_utils.py
InfinityPacer 0c53fb86fd feat(deps): add uv-backed package installer (#5987)
* feat(deps): add uv-backed package installer

* feat(deps): support package cache root
2026-06-23 13:36:15 +08:00

175 lines
6.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import subprocess
import tempfile
from unittest import TestCase
from unittest.mock import patch
from app.helper.system import SystemHelper
from app.core.config import settings
from app.utils.system import SystemUtils
class SystemUtilsTest(TestCase):
def test_execute_with_subprocess_keeps_stdout_when_command_fails(self):
"""
命令失败时如果原因只写入 stdout也需要回传给调用方用于错误提示。
"""
error = subprocess.CalledProcessError(
returncode=1,
cmd=["pip", "check"],
output="demo requires pkg>=2, but you have pkg 1\n",
stderr="",
)
with patch("app.utils.system.subprocess.run", side_effect=error):
success, message = SystemUtils.execute_with_subprocess(["pip", "check"])
self.assertFalse(success)
self.assertIn("返回码1", message)
self.assertIn("标准输出demo requires pkg>=2, but you have pkg 1", message)
def test_execute_with_subprocess_reports_empty_failure_output(self):
"""
命令失败且没有任何输出时,给出明确占位信息,避免错误原因看起来被截断。
"""
error = subprocess.CalledProcessError(
returncode=2,
cmd=["pip", "check"],
output="",
stderr="",
)
with patch("app.utils.system.subprocess.run", side_effect=error):
success, message = SystemUtils.execute_with_subprocess(["pip", "check"])
self.assertFalse(success)
self.assertIn("返回码2", message)
self.assertIn("无标准输出或错误输出", message)
class SystemHelperRestartTest(TestCase):
def test_docker_restart_policy_marks_intent_before_sigterm(self):
"""
Docker 内置重启走优雅退出时,应写入意图标记,避免 entrypoint 误进入 doctor 保活。
"""
with tempfile.TemporaryDirectory() as temp_dir:
original_config_dir = settings.CONFIG_DIR
original_intent_file = SystemHelper._SystemHelper__docker_restart_intent_file
settings.CONFIG_DIR = temp_dir
SystemHelper._SystemHelper__docker_restart_intent_file = (
settings.TEMP_PATH / "moviepilot.intentional_restart"
)
try:
with patch("app.helper.system.SystemUtils.is_docker", return_value=True), \
patch.object(SystemHelper, "_check_restart_policy", return_value=True), \
patch.object(SystemHelper, "_start_graceful_shutdown_monitor"), \
patch("app.helper.system.os.kill") as kill_mock:
ret, msg = SystemHelper.restart()
self.assertTrue(ret)
self.assertEqual(msg, "")
self.assertTrue((settings.TEMP_PATH / "moviepilot.intentional_restart").exists())
kill_mock.assert_called_once()
finally:
SystemHelper._SystemHelper__docker_restart_intent_file = original_intent_file
settings.CONFIG_DIR = original_config_dir
def test_execute_with_subprocess_passes_env_to_subprocess():
with patch("app.utils.system.subprocess.run") as run_mock:
run_mock.return_value.stdout = "ok"
run_mock.return_value.stderr = ""
success, message = SystemUtils.execute_with_subprocess(
["pip", "check"],
env={"PIP_CACHE_DIR": "/config/.cache/pip"},
)
assert success
assert message == "ok"
assert run_mock.call_args.kwargs["env"]["PIP_CACHE_DIR"] == "/config/.cache/pip"
def test_execute_with_subprocess_uses_safe_command_in_failure_message():
error = subprocess.CalledProcessError(
returncode=1,
cmd=["pip", "install", "-i", "https://user:pass@mirror.example/simple"],
output="",
stderr="failed",
)
command = ["pip", "install", "-i", "https://user:pass@mirror.example/simple"]
with patch("app.utils.system.subprocess.run", side_effect=error) as run_mock:
success, message = SystemUtils.execute_with_subprocess(
command,
safe_command=["pip", "install", "-i", "https://mirror.example/simple"],
)
assert not success
assert "https://mirror.example/simple" in message
assert "user:pass" not in message
assert run_mock.call_args.args[0] == command
def test_execute_with_subprocess_redacts_userinfo_from_stdout_and_stderr():
error = subprocess.CalledProcessError(
returncode=1,
cmd=["pip", "install"],
output="Looking in indexes: https://user:pass@mirror.example/simple",
stderr="Proxy failed: http://proxy_user:proxy_pass@proxy.example:7890",
)
with patch("app.utils.system.subprocess.run", side_effect=error):
success, message = SystemUtils.execute_with_subprocess(["pip", "install"])
assert not success
assert "https://mirror.example/simple" in message
assert "http://proxy.example:7890" in message
assert "user:pass" not in message
assert "proxy_user:proxy_pass" not in message
def test_execute_with_subprocess_redacts_userinfo_from_non_http_scheme():
error = subprocess.CalledProcessError(
returncode=1,
cmd=["pip", "install"],
output="Proxy failed: socks5://proxy_user:proxy_pass@proxy.example:7890",
stderr="Resolved direct URL: git+https://git_user:git_pass@example.com/org/repo.git",
)
with patch("app.utils.system.subprocess.run", side_effect=error):
success, message = SystemUtils.execute_with_subprocess(["pip", "install"])
assert not success
assert "socks5://proxy.example:7890" in message
assert "git+https://example.com/org/repo.git" in message
assert "proxy_user:proxy_pass" not in message
assert "git_user:git_pass" not in message
def test_execute_with_subprocess_redacts_success_output_userinfo():
with patch("app.utils.system.subprocess.run") as run_mock:
run_mock.return_value.stdout = "Using https://user:pass@mirror.example/simple\n"
run_mock.return_value.stderr = "Proxy socks5://proxy_user:proxy_pass@proxy.example:7890\n"
success, message = SystemUtils.execute_with_subprocess(["pip", "install"])
assert success
assert "https://mirror.example/simple" in message
assert "socks5://proxy.example:7890" in message
assert "user:pass" not in message
assert "proxy_user:proxy_pass" not in message
def test_execute_with_subprocess_redacts_unknown_error_userinfo_and_invalid_port():
with patch(
"app.utils.system.subprocess.run",
side_effect=RuntimeError("bad url https://user:pass@example.com:notaport/simple"),
):
success, message = SystemUtils.execute_with_subprocess(["pip", "install"])
assert not success
assert "https://example.com:notaport/simple" in message
assert "user:pass" not in message