Ctrl K
ComunidadGuías…

tests/test_deploy_windows_tray.py

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
from __future__ import annotations

import importlib.util
import os
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path
from unittest import mock

from peach import __version__


ROOT = Path(__file__).resolve().parents[1]
SCRIPT = ROOT / "scripts" / "deploy_windows_tray.py"
SPEC = importlib.util.spec_from_file_location("deploy_windows_tray", SCRIPT)
deploy_windows_tray = importlib.util.module_from_spec(SPEC)
assert SPEC.loader is not None
sys.modules[SPEC.name] = deploy_windows_tray
SPEC.loader.exec_module(deploy_windows_tray)


def git_runner(porcelain: str = "", commit: str = "abc12345def", branch: str = "master"):
    answers = {
        "status --porcelain": porcelain,
        "rev-parse HEAD": commit,
        "rev-parse --abbrev-ref HEAD": branch,
    }

    def run(command, **_kwargs):
        return subprocess.CompletedProcess(
            command, 0, stdout=answers[" ".join(command[1:])], stderr="",
        )

    return run


class DeployWindowsTrayTests(unittest.TestCase):
    def setUp(self):
        self.temp = tempfile.TemporaryDirectory()
        # 部署入口先 resolve 检出根和目标,断言也得拿 resolve 过的路径比。
        self.root = Path(self.temp.name).resolve()
        self.target = self.root / "dist" / "Peach" / "Peach.exe"
        self.target.parent.mkdir(parents=True)
        self.target.write_bytes(b"old")
        self.staged = self.root / "staging" / "Peach.exe"
        self.staged.parent.mkdir()
        self.staged.write_bytes(b"new")
        self.installer = mock.Mock()
        self.installer.log_path = self.root / "sync.log"
        self.installer.build_staged_tray.return_value = self.staged
        self.installer.packaged_migrations_pass.return_value = True
        self.restarted = mock.Mock(return_value=mock.Mock(
            ok=True, message="托盘已正常重启并重新拥有 HTTP/HTTPS 子服务",
            old_tray_pid=10, new_tray_pid=30, service_pids=(51, 52),
            backup=str(self.target.with_name("Peach.pre-source-sync-20260906-120000.exe")),
        ))

    def tearDown(self):
        self.temp.cleanup()

    def run_deploy(self, **overrides):
        arguments = dict(
            target=self.target, installer=self.installer, restart=self.restarted,
            run=git_runner(),
            identity=lambda **_kwargs: {"version": __version__, "build_commit": "abc12345def"},
        )
        arguments.update(overrides)
        return deploy_windows_tray.deploy(self.root, **arguments)

    def test_a_dirty_checkout_stops_before_anything_is_built_or_swapped(self):
        outcome = self.run_deploy(run=git_runner(porcelain=" M src/peach/tray.py\n"))
        self.assertFalse(outcome["ok"])
        self.assertEqual(outcome["step"], "checkout")
        self.installer.build_staged_tray.assert_not_called()
        self.restarted.assert_not_called()

    def test_a_package_that_fails_the_migration_check_never_reaches_production(self):
        self.installer.packaged_migrations_pass.return_value = False
        outcome = self.run_deploy()
        self.assertFalse(outcome["ok"])
        self.assertEqual(outcome["step"], "validate")
        self.restarted.assert_not_called()
        self.assertEqual(self.target.read_bytes(), b"old")

    def test_a_given_package_is_reused_instead_of_building_again(self):
        outcome = self.run_deploy(staged=self.staged)
        self.assertTrue(outcome["ok"], outcome["message"])
        self.installer.build_staged_tray.assert_not_called()
        self.assertEqual(self.restarted.call_args.kwargs["swap_from"], self.staged)
        self.assertEqual(outcome["commit"], "abc12345def")
        self.assertEqual(outcome["version"], __version__)

    def test_a_tray_built_from_another_commit_is_reported_as_a_failure(self):
        """认的是构建提交:同一个版本号下有很多个构建,只比版本号证明不了二进制换了。"""
        outcome = self.run_deploy(
            identity=lambda **_kwargs: {"version": __version__, "build_commit": "0ldc0mm1t"})
        self.assertFalse(outcome["ok"])
        self.assertEqual(outcome["step"], "verify")
        self.assertEqual(outcome["served_commit"], "0ldc0mm1t")
        self.assertEqual(outcome["expected_version"], __version__)
        self.assertEqual(outcome["backup"], self.restarted.return_value.backup)

    def swapping_restart(self):
        """照真实那步的样子换文件:`os.replace` 是搬走,暂存包换完就不在了。

        指纹只能在换之前取,这个替身把「换完再去读暂存包」直接变成 FileNotFoundError。
        """
        def restart(target, *, swap_from, **_kwargs):
            os.replace(swap_from, target)
            return self.restarted.return_value
        return restart

    def test_a_venv_owned_deployment_is_verified_by_the_binary_that_landed(self):
        """回话的是源码进程时,认落地那个文件的指纹。

        托盘按 `_peach_executable()` 的设计把两个子服务交回项目 venv,那个进程没有第二个
        版本,`/healthz` 的 `build_commit` 结构上恒为 None。拿它当判据的话,二进制换得
        再对也必然判失败——这条链路上唯一动了生产入口的那一步,反而永远报不出成功。
        """
        outcome = self.run_deploy(
            staged=self.staged, restart=self.swapping_restart(),
            identity=lambda **_kwargs: {"version": __version__, "build_commit": None})
        self.assertTrue(outcome["ok"], outcome["message"])
        self.assertEqual(outcome["step"], "verify")
        self.assertEqual(outcome["built_digest"], outcome["target_digest"])
        self.assertEqual(self.target.read_bytes(), b"new")

    def test_a_binary_that_never_landed_fails_even_though_the_tray_answers(self):
        """托盘回话不等于跑的是这一份:指纹对不上就是没换上。"""
        outcome = self.run_deploy(
            staged=self.staged,
            identity=lambda **_kwargs: {"version": __version__, "build_commit": None})
        self.assertFalse(outcome["ok"])
        self.assertEqual(outcome["step"], "verify")
        self.assertNotEqual(outcome["built_digest"], outcome["target_digest"])
        self.assertIn("指纹", outcome["message"])
        self.assertEqual(outcome["backup"], self.restarted.return_value.backup)

    def test_a_silent_health_endpoint_is_reported_as_a_failure(self):
        outcome = self.run_deploy(identity=lambda **_kwargs: None)
        self.assertFalse(outcome["ok"])
        self.assertEqual(outcome["step"], "verify")
        self.assertIn("/healthz", outcome["message"])

    def test_the_last_health_error_is_reported_verbatim(self):
        error = "https://192.0.2.10/healthz ConnectError: [SSL: CERTIFICATE_VERIFY_FAILED]"
        outcome = self.run_deploy(identity=lambda **_kwargs: {"error": error})
        self.assertFalse(outcome["ok"])
        self.assertEqual(outcome["step"], "verify")
        self.assertIn(error, outcome["message"])


class ServingIdentityTests(unittest.TestCase):
    def test_the_verdict_comes_from_the_https_port_checked_against_the_project_ca(self):
        specs = (
            mock.Mock(name="http", health_url="http://127.0.0.1/healthz", verify=True),
            mock.Mock(health_url="https://192.0.2.10/healthz", verify="/ca/peach.crt"),
        )
        specs[0].name = "http"
        specs[1].name = "https"
        response = mock.Mock(status_code=200)
        response.json.return_value = {"ok": True, "version": "9.9.9", "build_commit": "c0ffee"}
        with (
            mock.patch.object(deploy_windows_tray, "build_service_specs",
                              return_value=specs),
            mock.patch.object(deploy_windows_tray.httpx, "get",
                              return_value=response) as get,
        ):
            self.assertEqual(deploy_windows_tray.serving_identity(),
                             {"version": "9.9.9", "build_commit": "c0ffee"})
        self.assertEqual(get.call_args.args[0], "https://192.0.2.10/healthz")
        self.assertEqual(get.call_args.kwargs["verify"], "/ca/peach.crt")
        self.assertFalse(get.call_args.kwargs["trust_env"],
                         "系统代理会替生产口回话,探测必须直连")

    def test_a_timeout_carries_the_last_raw_error(self):
        spec = mock.Mock(health_url="https://192.0.2.10/healthz", verify="/ca/peach.crt")
        spec.name = "https"
        refused = deploy_windows_tray.httpx.ConnectError("[SSL: CERTIFICATE_VERIFY_FAILED]")
        unready = mock.Mock(status_code=503)
        for answer, expected in ((refused, "ConnectError: [SSL: CERTIFICATE_VERIFY_FAILED]"),
                                 (unready, "HTTP 503")):
            with self.subTest(expected=expected), \
                    mock.patch.object(deploy_windows_tray, "build_service_specs", return_value=(spec,)), \
                    mock.patch.object(deploy_windows_tray.httpx, "get",
                                      **({"side_effect": answer} if isinstance(answer, Exception)
                                         else {"return_value": answer})):
                served = deploy_windows_tray.serving_identity(timeout=0, sleep=mock.Mock())
            self.assertEqual(served, {"error": f"https://192.0.2.10/healthz {expected}"})


if __name__ == "__main__":
    unittest.main()