mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-04 23:17:20 +08:00
feat(system): add staged release updates
This commit is contained in:
+116
-69
@@ -1,9 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import hashlib
|
||||
import json
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from types import ModuleType, SimpleNamespace
|
||||
@@ -15,12 +16,8 @@ MODULE_PATH = Path(__file__).resolve().parents[1] / "app" / "cli.py"
|
||||
|
||||
class _DummySystemHelper:
|
||||
@staticmethod
|
||||
def consume_one_shot_update_mode():
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def get_auto_update_mode():
|
||||
return "false"
|
||||
def consume_one_shot_dev_update():
|
||||
return False
|
||||
|
||||
|
||||
def load_cli_module():
|
||||
@@ -39,6 +36,7 @@ def load_cli_module():
|
||||
PROXY_HOST="",
|
||||
PIP_PROXY="",
|
||||
GITHUB_TOKEN="",
|
||||
MOVIEPILOT_AUTO_UPDATE="false",
|
||||
PROXY={},
|
||||
REPO_GITHUB_HEADERS=lambda _repo: {},
|
||||
)
|
||||
@@ -84,74 +82,123 @@ def load_cli_module():
|
||||
return module
|
||||
|
||||
|
||||
class CliAutoUpdateTests(unittest.TestCase):
|
||||
def test_resolve_auto_update_targets_only_queries_backend_release(self):
|
||||
module = load_cli_module()
|
||||
def test_resolve_auto_update_targets_keeps_dev_branch_tracking():
|
||||
module = load_cli_module()
|
||||
with patch.object(module, "_git_current_branch", return_value="v3"):
|
||||
assert module._resolve_auto_update_targets("dev") == "latest"
|
||||
assert module._resolve_auto_update_targets("release") is None
|
||||
|
||||
with patch.object(module, "_latest_release_tag", return_value="v2.10.12") as latest_mock:
|
||||
backend_ref = module._resolve_auto_update_targets("release")
|
||||
|
||||
latest_mock.assert_called_once_with(
|
||||
module.BACKEND_RELEASES_API,
|
||||
repo="jxxghp/MoviePilot",
|
||||
prefix="v2",
|
||||
)
|
||||
self.assertEqual(backend_ref, "v2.10.12")
|
||||
def test_one_shot_dev_update_overrides_disabled_default():
|
||||
module = load_cli_module()
|
||||
module.settings.MOVIEPILOT_AUTO_UPDATE = "false"
|
||||
|
||||
def test_best_effort_auto_update_does_not_pass_frontend_version_override(self):
|
||||
module = load_cli_module()
|
||||
run_result = SimpleNamespace(returncode=0, stdout="ok")
|
||||
with patch.object(
|
||||
module.SystemHelper, "consume_one_shot_dev_update", return_value=True
|
||||
):
|
||||
assert module._auto_update_mode() == "dev"
|
||||
|
||||
with patch.object(module, "_auto_update_mode", return_value="release"), patch.object(
|
||||
module, "_resolve_auto_update_targets", return_value="v2.10.12"
|
||||
), patch.object(module.subprocess, "run", return_value=run_result) as run_mock, patch.object(
|
||||
module.click, "echo"
|
||||
):
|
||||
module._best_effort_auto_update()
|
||||
|
||||
command = run_mock.call_args.args[0]
|
||||
self.assertEqual(command[1:5], [str(module._repo_root() / "scripts" / "local_setup.py"), "update", "all", "--ref"])
|
||||
self.assertNotIn("--frontend-version", command)
|
||||
def test_release_mode_does_not_update_during_start():
|
||||
module = load_cli_module()
|
||||
with patch.object(module, "_auto_update_mode", return_value="release"), patch.object(
|
||||
module.subprocess, "run"
|
||||
) as run_mock:
|
||||
module._best_effort_auto_update()
|
||||
run_mock.assert_not_called()
|
||||
|
||||
def test_best_effort_auto_update_passes_package_env_and_overrides_proxy(self):
|
||||
module = load_cli_module()
|
||||
module.settings.PROXY_HOST = "http://proxy.example:7890"
|
||||
module.settings.PIP_PROXY = "https://mirror.example/simple"
|
||||
run_result = SimpleNamespace(returncode=0, stdout="ok")
|
||||
|
||||
with patch.dict(module.os.environ, {"HTTPS_PROXY": "http://old.example:8080"}, clear=True), patch.object(
|
||||
module, "_auto_update_mode", return_value="release"
|
||||
), patch.object(module, "_resolve_auto_update_targets", return_value="v2.10.12"), patch.object(
|
||||
module.subprocess, "run", return_value=run_result
|
||||
) as run_mock, patch.object(
|
||||
module.click, "echo"
|
||||
):
|
||||
module._best_effort_auto_update()
|
||||
|
||||
env = run_mock.call_args.kwargs["env"]
|
||||
self.assertEqual(env["HTTPS_PROXY"], "http://proxy.example:7890")
|
||||
self.assertEqual(env["PIP_PROXY"], "https://mirror.example/simple")
|
||||
self.assertEqual(env["PACKAGE_CACHE_ROOT"], str(module.settings.PACKAGE_CACHE_PATH))
|
||||
self.assertEqual(env["UV_CACHE_DIR"], str(module.settings.PACKAGE_CACHE_PATH / "uv"))
|
||||
|
||||
def test_best_effort_auto_update_derives_tool_cache_from_existing_root(self):
|
||||
module = load_cli_module()
|
||||
run_result = SimpleNamespace(returncode=0, stdout="ok")
|
||||
package_cache_root = Path("/custom/package-cache-root")
|
||||
|
||||
with patch.dict(
|
||||
module.os.environ,
|
||||
def test_prepared_release_uses_downloaded_package_before_dev_mode():
|
||||
module = load_cli_module()
|
||||
module.PREPARED_UPDATE_ROOT.mkdir(parents=True)
|
||||
backend = module.PREPARED_UPDATE_ROOT / "backend.zip"
|
||||
frontend = module.PREPARED_UPDATE_ROOT / "frontend.zip"
|
||||
backend.write_bytes(b"backend")
|
||||
frontend.write_bytes(b"frontend")
|
||||
module.PREPARED_UPDATE_MANIFEST.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"PACKAGE_CACHE_ROOT": str(package_cache_root),
|
||||
},
|
||||
clear=True,
|
||||
), patch.object(module, "_auto_update_mode", return_value="release"), patch.object(
|
||||
module, "_resolve_auto_update_targets", return_value="v2.10.12"
|
||||
), patch.object(module.subprocess, "run", return_value=run_result) as run_mock, patch.object(
|
||||
module.click, "echo"
|
||||
):
|
||||
module._best_effort_auto_update()
|
||||
"version": "v3.1.0",
|
||||
"frontend_version": "v3.1.0",
|
||||
"backend_archive": str(backend),
|
||||
"frontend_archive": str(frontend),
|
||||
"backend_sha256": hashlib.sha256(backend.read_bytes()).hexdigest(),
|
||||
"frontend_sha256": hashlib.sha256(frontend.read_bytes()).hexdigest(),
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
run_result = SimpleNamespace(returncode=0, stdout="ok")
|
||||
|
||||
env = run_mock.call_args.kwargs["env"]
|
||||
self.assertEqual(env["PACKAGE_CACHE_ROOT"], str(package_cache_root))
|
||||
self.assertEqual(env["UV_CACHE_DIR"], str(package_cache_root / "uv"))
|
||||
with patch.object(module, "_auto_update_mode", return_value="dev") as mode, patch.object(
|
||||
module.subprocess, "run", return_value=run_result
|
||||
) as run_mock, patch.object(module.click, "echo"):
|
||||
module._best_effort_auto_update()
|
||||
|
||||
command = run_mock.call_args.args[0]
|
||||
assert "--offline-backend" in command
|
||||
assert command[command.index("--frontend-archive") + 1] == str(frontend)
|
||||
assert not module.PREPARED_UPDATE_MANIFEST.exists()
|
||||
mode.assert_not_called()
|
||||
|
||||
|
||||
def test_best_effort_auto_update_does_not_pass_frontend_version_override():
|
||||
module = load_cli_module()
|
||||
run_result = SimpleNamespace(returncode=0, stdout="ok")
|
||||
|
||||
with patch.object(module, "_auto_update_mode", return_value="dev"), patch.object(
|
||||
module, "_resolve_auto_update_targets", return_value="latest"
|
||||
), patch.object(module.subprocess, "run", return_value=run_result) as run_mock, patch.object(
|
||||
module.click, "echo"
|
||||
):
|
||||
module._best_effort_auto_update()
|
||||
|
||||
command = run_mock.call_args.args[0]
|
||||
assert command[1:5] == [
|
||||
str(module._repo_root() / "scripts" / "local_setup.py"),
|
||||
"update",
|
||||
"all",
|
||||
"--ref",
|
||||
]
|
||||
assert "--frontend-version" not in command
|
||||
|
||||
|
||||
def test_best_effort_auto_update_passes_package_env_and_overrides_proxy():
|
||||
module = load_cli_module()
|
||||
module.settings.PROXY_HOST = "http://proxy.example:7890"
|
||||
module.settings.PIP_PROXY = "https://mirror.example/simple"
|
||||
run_result = SimpleNamespace(returncode=0, stdout="ok")
|
||||
|
||||
with patch.dict(module.os.environ, {"HTTPS_PROXY": "http://old.example:8080"}, clear=True), patch.object(
|
||||
module, "_auto_update_mode", return_value="dev"
|
||||
), patch.object(module, "_resolve_auto_update_targets", return_value="latest"), patch.object(
|
||||
module.subprocess, "run", return_value=run_result
|
||||
) as run_mock, patch.object(module.click, "echo"):
|
||||
module._best_effort_auto_update()
|
||||
|
||||
env = run_mock.call_args.kwargs["env"]
|
||||
assert env["HTTPS_PROXY"] == "http://proxy.example:7890"
|
||||
assert env["PIP_PROXY"] == "https://mirror.example/simple"
|
||||
assert env["PACKAGE_CACHE_ROOT"] == str(module.settings.PACKAGE_CACHE_PATH)
|
||||
assert env["UV_CACHE_DIR"] == str(module.settings.PACKAGE_CACHE_PATH / "uv")
|
||||
|
||||
|
||||
def test_best_effort_auto_update_derives_tool_cache_from_existing_root():
|
||||
module = load_cli_module()
|
||||
run_result = SimpleNamespace(returncode=0, stdout="ok")
|
||||
package_cache_root = Path("/custom/package-cache-root")
|
||||
|
||||
with patch.dict(
|
||||
module.os.environ,
|
||||
{"PACKAGE_CACHE_ROOT": str(package_cache_root)},
|
||||
clear=True,
|
||||
), patch.object(module, "_auto_update_mode", return_value="dev"), patch.object(
|
||||
module, "_resolve_auto_update_targets", return_value="latest"
|
||||
), patch.object(module.subprocess, "run", return_value=run_result) as run_mock, patch.object(
|
||||
module.click, "echo"
|
||||
):
|
||||
module._best_effort_auto_update()
|
||||
|
||||
env = run_mock.call_args.kwargs["env"]
|
||||
assert env["PACKAGE_CACHE_ROOT"] == str(package_cache_root)
|
||||
assert env["UV_CACHE_DIR"] == str(package_cache_root / "uv")
|
||||
|
||||
Reference in New Issue
Block a user