Initial commit: add AutoDeploy project

This commit is contained in:
wangpeng
2026-07-14 16:30:36 +08:00
commit 2d7b6216ba
23 changed files with 3615 additions and 0 deletions

View File

@@ -0,0 +1,163 @@
import unittest
import queue
from unittest.mock import patch
from deploy_tool.gui import DeployWorkbench, database_name_from_sql_files
import deploy_tool.gui as gui_module
class FakeWidget:
def __init__(self):
self.focused = False
self.seen = False
def focus_set(self):
self.focused = True
def see(self, index):
self.seen = index
class FakeButton:
def __init__(self):
self.state = None
def configure(self, **options):
self.state = options.get("state")
class GuiNavigationTests(unittest.TestCase):
def test_database_name_comes_from_first_sql_filename(self):
from pathlib import Path
self.assertEqual(database_name_from_sql_files([Path("db/movie.sql")]), "movie")
self.assertEqual(database_name_from_sql_files([]), "")
def test_deployment_running_state_updates_deploy_button(self):
app = DeployWorkbench.__new__(DeployWorkbench)
app.deployment_running = False
app.deploy_buttons = [FakeButton()]
set_running = getattr(DeployWorkbench, "_set_deployment_running", lambda self, running: None)
set_running(app, True)
self.assertTrue(app.deployment_running)
self.assertEqual([button.state for button in app.deploy_buttons], ["disabled"])
set_running(app, False)
self.assertFalse(app.deployment_running)
self.assertEqual([button.state for button in app.deploy_buttons], ["normal"])
def test_running_deployment_ignores_another_deploy_request(self):
app = DeployWorkbench.__new__(DeployWorkbench)
app.deployment_running = True
plan_calls = []
logs = []
app._current_plan = lambda: plan_calls.append("planned") or []
app._append_log = logs.append
DeployWorkbench.run_deploy_plan_async(app)
self.assertEqual(plan_calls, [])
self.assertTrue(any("正在执行" in line for line in logs))
def test_confirmed_deployment_locks_buttons_before_worker_starts(self):
app = DeployWorkbench.__new__(DeployWorkbench)
app.deployment_running = False
app.deploy_buttons = [FakeButton()]
app._current_plan = lambda: [object()]
app.ui_queue = object()
app._thread_log = lambda value: None
with patch.object(gui_module.messagebox, "askyesno", return_value=True), patch.object(gui_module.threading, "Thread") as thread_class:
DeployWorkbench.run_deploy_plan_async(app)
self.assertTrue(app.deployment_running)
self.assertEqual([button.state for button in app.deploy_buttons], ["disabled"])
thread_class.return_value.start.assert_called_once_with()
def test_thread_start_failure_restores_deploy_buttons(self):
app = DeployWorkbench.__new__(DeployWorkbench)
app.deployment_running = False
app.deploy_buttons = [FakeButton()]
app._current_plan = lambda: [object()]
logs = []
app._append_log = logs.append
with patch.object(gui_module.messagebox, "askyesno", return_value=True), patch.object(gui_module.threading, "Thread") as thread_class:
thread_class.return_value.start.side_effect = RuntimeError("thread unavailable")
try:
DeployWorkbench.run_deploy_plan_async(app)
except RuntimeError:
pass
self.assertFalse(app.deployment_running)
self.assertEqual([button.state for button in app.deploy_buttons], ["normal"])
self.assertTrue(any("thread unavailable" in line for line in logs))
def test_worker_exception_always_queues_deploy_done(self):
app = DeployWorkbench.__new__(DeployWorkbench)
app.ui_queue = queue.Queue()
run_worker = getattr(DeployWorkbench, "_run_deployment_worker", lambda self, plan: None)
with patch.object(gui_module, "run_steps", side_effect=RuntimeError("unexpected failure")):
run_worker(app, [object()])
events = []
while not app.ui_queue.empty():
events.append(app.ui_queue.get_nowait())
self.assertIn(("deploy_done", 1), events)
self.assertTrue(any(event == "log" and "unexpected failure" in str(payload) for event, payload in events))
def test_deploy_done_event_restores_deploy_buttons(self):
app = DeployWorkbench.__new__(DeployWorkbench)
app.deployment_running = True
app.deploy_buttons = [FakeButton()]
app.ui_queue = queue.Queue()
app.ui_queue.put(("deploy_done", 1))
logs = []
app._append_log = logs.append
app.after = lambda *args, **kwargs: None
DeployWorkbench._drain_queue(app)
self.assertFalse(app.deployment_running)
self.assertEqual([button.state for button in app.deploy_buttons], ["normal"])
self.assertTrue(any("退出码: 1" in line for line in logs))
def test_top_action_only_exposes_execution(self):
self.assertNotIn("plan", gui_module.TEXT)
self.assertEqual(gui_module.TEXT["deploy"], "执行部署")
def test_sidebar_navigation_focuses_logs(self):
app = DeployWorkbench.__new__(DeployWorkbench)
app.log_text = FakeWidget()
app.result_text = FakeWidget()
app.activate_sidebar_item("运行日志")
self.assertTrue(app.log_text.focused)
self.assertEqual(app.log_text.seen, "end")
def test_sidebar_navigation_focuses_database_config(self):
app = DeployWorkbench.__new__(DeployWorkbench)
app.database_widget = FakeWidget()
app.activate_sidebar_item("数据库")
self.assertTrue(app.database_widget.focused)
def test_sidebar_navigation_can_trigger_environment_check(self):
app = DeployWorkbench.__new__(DeployWorkbench)
calls = []
app.check_environment_async = lambda: calls.append("checked")
app.activate_sidebar_item("环境检测")
self.assertEqual(calls, ["checked"])
if __name__ == "__main__":
unittest.main()