"""Git integration checks for the sidebar updater.""" import importlib.util import subprocess import tempfile import unittest from pathlib import Path from unittest.mock import Mock, patch UPDATER_PATH = Path(__file__).resolve().parents[1] / "utils" / "updater.py" spec = importlib.util.spec_from_file_location("o1key_updater_under_test", UPDATER_PATH) updater = importlib.util.module_from_spec(spec) spec.loader.exec_module(updater) class UpdaterTests(unittest.TestCase): def setUp(self): self.temp = tempfile.TemporaryDirectory() self.addCleanup(self.temp.cleanup) root = Path(self.temp.name) self.remote = root / "remote.git" self.author = root / "author" self.install = root / "install" self.git(root, "init", "--bare", str(self.remote)) self.git(root, "clone", str(self.remote), str(self.author)) self.git(self.author, "config", "user.email", "test@example.com") self.git(self.author, "config", "user.name", "Updater Test") self.git(self.author, "switch", "-c", "main") (self.author / "requirements.txt").write_text("requests>=2\n", encoding="utf-8") (self.author / "version.txt").write_text("1\n", encoding="utf-8") self.commit_and_push() self.git(root, "clone", "--branch", "main", str(self.remote), str(self.install)) updater.PLUGIN_DIR = self.install def git(self, cwd, *args): return subprocess.run(["git", *args], cwd=cwd, check=True, capture_output=True, text=True).stdout.strip() def commit_and_push(self): self.git(self.author, "add", ".") self.git(self.author, "commit", "-m", "test update") self.git(self.author, "push", "origin", "main") def test_fast_forward_and_requirements_change(self): self.assertFalse(updater.update_package()["updated"]) (self.author / "version.txt").write_text("2\n", encoding="utf-8") (self.author / "requirements.txt").write_text("requests>=3\n", encoding="utf-8") self.commit_and_push() with patch.object(updater, "_install_requirements") as install: result = updater.update_package() install.assert_called_once_with("requests>=3\n") self.assertTrue(result["updated"]) self.assertTrue(result["requirements_changed"]) self.assertEqual((self.install / "version.txt").read_text(encoding="utf-8"), "2\n") def test_dependency_failure_keeps_previous_code(self): old_commit = self.git(self.install, "rev-parse", "HEAD") (self.author / "requirements.txt").write_text("requests>=3\n", encoding="utf-8") (self.author / "version.txt").write_text("2\n", encoding="utf-8") self.commit_and_push() with patch.object(updater, "_install_requirements", side_effect=updater.UpdateError("依赖安装失败")): with self.assertRaisesRegex(updater.UpdateError, "依赖安装失败"): updater.update_package() self.assertEqual(self.git(self.install, "rev-parse", "HEAD"), old_commit) self.assertEqual((self.install / "version.txt").read_text(encoding="utf-8"), "1\n") def test_local_changes_are_preserved(self): (self.install / "version.txt").write_text("local\n", encoding="utf-8") with self.assertRaisesRegex(updater.UpdateError, "本地修改"): updater.update_package() self.assertEqual((self.install / "version.txt").read_text(encoding="utf-8"), "local\n") def test_diverged_branch_is_rejected(self): self.git(self.install, "config", "user.email", "test@example.com") self.git(self.install, "config", "user.name", "Updater Test") (self.install / "version.txt").write_text("local commit\n", encoding="utf-8") self.git(self.install, "add", ".") self.git(self.install, "commit", "-m", "local") (self.author / "version.txt").write_text("remote commit\n", encoding="utf-8") self.commit_and_push() with self.assertRaisesRegex(updater.UpdateError, "已分叉"): updater.update_package() self.assertEqual((self.install / "version.txt").read_text(encoding="utf-8"), "local commit\n") def test_management_request_requires_local_peer_and_same_origin(self): cases = [ ("local browser", "127.0.0.1", "127.0.0.1:8188", "http://127.0.0.1:8188", True), ("localhost", "::1", "localhost:8188", "http://localhost:8188", True), ("remote peer", "192.168.1.2", "127.0.0.1:8188", "http://127.0.0.1:8188", False), ("cross origin", "127.0.0.1", "127.0.0.1:8188", "http://evil.example", False), ("missing origin", "127.0.0.1", "127.0.0.1:8188", "", False), ("malformed origin", "127.0.0.1", "127.0.0.1:8188", "http://[", False), ("rebinding host", "127.0.0.1", "evil.example:8188", "http://evil.example:8188", False), ] for name, peer, host, origin, expected in cases: with self.subTest(name=name): request = Mock() request.transport.get_extra_info.return_value = (peer, 12345) request.headers = {"Host": host, "Origin": origin} request.scheme = "http" self.assertEqual(updater.is_local_management_request(request), expected) if __name__ == "__main__": unittest.main()