213 lines
11 KiB
Python
213 lines
11 KiB
Python
import unittest
|
|
|
|
from app.services.ops_release_service import (
|
|
_build_release_launchpad_status,
|
|
_compact_latest_smart_rollout_preview,
|
|
)
|
|
|
|
|
|
class OpsReleaseServiceLaunchpadTests(unittest.TestCase):
|
|
def test_launchpad_status_blocks_when_package_missing(self) -> None:
|
|
status = _build_release_launchpad_status(
|
|
package={"available": False},
|
|
latest_package_preview={},
|
|
worker_preview={},
|
|
control_preview={},
|
|
)
|
|
|
|
self.assertEqual("blocked", status["status"])
|
|
self.assertEqual("release_package", status["recommended_action_code"])
|
|
self.assertEqual(status["summary"], status["summary_text"])
|
|
self.assertEqual("release_hub", status["focus_ref"]["kind"])
|
|
self.assertEqual("release_launchpad", status["focus_ref"]["section"])
|
|
|
|
def test_launchpad_status_blocks_when_rollout_preview_has_blockers(self) -> None:
|
|
status = _build_release_launchpad_status(
|
|
package={"available": True},
|
|
latest_package_preview={},
|
|
worker_preview={"policy_preview": {"blocked": True}},
|
|
control_preview={"policy_preview": {"blocked": False}},
|
|
)
|
|
|
|
self.assertEqual("blocked", status["status"])
|
|
self.assertEqual("fix_rollout_blockers", status["recommended_action_code"])
|
|
self.assertEqual(status["summary"], status["summary_text"])
|
|
|
|
def test_launchpad_status_blocks_when_final_signoff_missing(self) -> None:
|
|
status = _build_release_launchpad_status(
|
|
package={
|
|
"available": True,
|
|
"final_release_ok": False,
|
|
"final_release_report_available": True,
|
|
"final_release_report_ok": True,
|
|
"final_release_report_matches_latest": False,
|
|
"final_release_report_stale_reason": "final_release_report does not match latest_release: package_name",
|
|
"final_release_gate_decision": "ready",
|
|
"final_release_gate_blocked_reasons": [],
|
|
},
|
|
latest_package_preview={},
|
|
worker_preview={"smart_rollout": {"available": True}, "policy_preview": {}},
|
|
control_preview={"smart_rollout": {"available": True}, "policy_preview": {}},
|
|
)
|
|
|
|
self.assertEqual("blocked", status["status"])
|
|
self.assertEqual("release_prepare", status["recommended_action_code"])
|
|
self.assertIn("final_release_report does not match latest_release: package_name", status["summary"])
|
|
self.assertIn("release-prepare", status["recommended_command"])
|
|
self.assertEqual(status["summary"], status["summary_text"])
|
|
|
|
def test_launchpad_status_prefers_runtime_refresh_when_runtime_schema_is_stale(self) -> None:
|
|
status = _build_release_launchpad_status(
|
|
package={"available": True, "final_release_ok": True},
|
|
latest_package_preview={},
|
|
worker_preview={"smart_rollout": {"available": True}, "policy_preview": {}},
|
|
control_preview={"smart_rollout": {"available": True}, "policy_preview": {}},
|
|
runtime_build_info={
|
|
"runtime_schema_stale": True,
|
|
"route_surface_complete": True,
|
|
"route_surface_missing_keys": ["ops_node_handover_bootstrap_plan"],
|
|
},
|
|
)
|
|
|
|
self.assertEqual("attention", status["status"])
|
|
self.assertEqual("api-restart", status["recommended_action_code"])
|
|
self.assertEqual("待刷新运行时", status["status_label"])
|
|
self.assertTrue(status["runtime_refresh_required"])
|
|
self.assertTrue(status["runtime_schema_stale"])
|
|
self.assertIn("runtime-refresh-recover", status["recommended_command"])
|
|
self.assertEqual(status["summary"], status["summary_text"])
|
|
|
|
def test_launchpad_status_marks_attention_when_confirmation_required(self) -> None:
|
|
status = _build_release_launchpad_status(
|
|
package={"available": True},
|
|
latest_package_preview={},
|
|
worker_preview={"requires_confirmation": True, "policy_preview": {}},
|
|
control_preview={"policy_preview": {}},
|
|
)
|
|
|
|
self.assertEqual("attention", status["status"])
|
|
self.assertEqual("review_smart_rollout_preview", status["recommended_action_code"])
|
|
self.assertEqual("release_launchpad", status["focus_ref"]["section"])
|
|
|
|
def test_launchpad_status_ready_when_worker_rollout_available(self) -> None:
|
|
status = _build_release_launchpad_status(
|
|
package={"available": True},
|
|
latest_package_preview={},
|
|
worker_preview={"smart_rollout": {"available": True, "execution_mode": "ssh", "execution_mode_label": "SSH"}, "policy_preview": {}},
|
|
control_preview={"smart_rollout": {"available": False}, "policy_preview": {}},
|
|
)
|
|
|
|
self.assertEqual("ready", status["status"])
|
|
self.assertEqual("publish_latest_worker", status["recommended_action_code"])
|
|
self.assertEqual("ssh", status["recommended_execution_mode"])
|
|
self.assertEqual("SSH", status["recommended_execution_mode_label"])
|
|
self.assertEqual(status["summary"], status["summary_text"])
|
|
|
|
def test_launchpad_status_requires_managed_nodes_when_no_targets_available(self) -> None:
|
|
status = _build_release_launchpad_status(
|
|
package={"available": True},
|
|
latest_package_preview={},
|
|
worker_preview={"smart_rollout": {"available": False}, "policy_preview": {}},
|
|
control_preview={"smart_rollout": {"available": False}, "policy_preview": {}},
|
|
)
|
|
|
|
self.assertEqual("attention", status["status"])
|
|
self.assertEqual("fix_managed_nodes", status["recommended_action_code"])
|
|
self.assertEqual("release_hub", status["focus_ref"]["kind"])
|
|
|
|
def test_launchpad_status_prefers_bootstrap_recovery_when_gap_row_has_recovery_action(self) -> None:
|
|
status = _build_release_launchpad_status(
|
|
package={"available": True},
|
|
latest_package_preview={},
|
|
worker_preview={
|
|
"smart_rollout": {"available": False},
|
|
"policy_preview": {
|
|
"blocked": True,
|
|
"release_gate": {
|
|
"operational_readiness": {
|
|
"rows": [
|
|
{
|
|
"node_code": "mainland-worker-02",
|
|
"recovery_action": "bootstrap_run",
|
|
"recovery_label": "签发接入工单",
|
|
"recovery_summary": "当前节点仍处于接入阶段,建议先签发 onboarding.bootstrap。",
|
|
}
|
|
]
|
|
}
|
|
},
|
|
},
|
|
},
|
|
control_preview={"smart_rollout": {"available": False}, "policy_preview": {}},
|
|
)
|
|
|
|
self.assertEqual("blocked", status["status"])
|
|
self.assertEqual("bootstrap_run", status["recommended_action_code"])
|
|
self.assertEqual("mainland-worker-02", status["recommended_target_node_code"])
|
|
self.assertEqual("签发接入工单", status["recommended_recovery_label"])
|
|
self.assertEqual("当前节点仍处于接入阶段,建议先签发 onboarding.bootstrap。", status["recommended_recovery_summary"])
|
|
self.assertEqual(1, status["onboarding_bootstrap_pending_nodes"])
|
|
self.assertEqual(0, status["onboarding_acceptance_ready_nodes"])
|
|
self.assertEqual(1, status["onboarding_gap_nodes_total"])
|
|
self.assertIn("node-bootstrap-run", status["recommended_command"])
|
|
|
|
def test_compact_latest_smart_rollout_preview_adds_rollout_preview_alias(self) -> None:
|
|
preview = _compact_latest_smart_rollout_preview(
|
|
"智能 Rollout 预检通过",
|
|
{
|
|
"release": {
|
|
"id": 18,
|
|
"release_version": "2026.04.18+bd6bcb2",
|
|
"channel": "stable",
|
|
"status": "ready",
|
|
"artifact_url": "https://example.com/domaincheck.tar.gz",
|
|
},
|
|
"package": {
|
|
"available": True,
|
|
"package_name": "domaincheck-2026.04.18+bd6bcb2",
|
|
"package_type": "tar.gz",
|
|
"sha256": "sha256:test",
|
|
"metadata": {
|
|
"manifest": {
|
|
"included_services": ["domaincheck-api", "domaincheck-worker"],
|
|
}
|
|
},
|
|
},
|
|
"smart_rollout": {
|
|
"available": True,
|
|
"execution_mode": "remote-agent",
|
|
"execution_mode_label": "远端 Agent",
|
|
"summary": "已按 Worker 预选 2 台节点。",
|
|
"target_selector": {"node_codes": ["mainland-worker-01", "mainland-worker-02"]},
|
|
"target_nodes": [
|
|
{
|
|
"node_code": "mainland-worker-01",
|
|
"region": "mainland",
|
|
"role": "worker",
|
|
"delivery_queue_state": "healthy",
|
|
},
|
|
{
|
|
"node_code": "mainland-worker-02",
|
|
"region": "mainland",
|
|
"role": "worker",
|
|
"delivery_queue_state": "retrying",
|
|
},
|
|
],
|
|
},
|
|
"preview": {
|
|
"target_summary": {"nodes_total": 2},
|
|
"batch_plan": {"batches_total": 2, "first_batch_size": 1},
|
|
"release_gate": {"status": "ready", "summary": "当前可推进。"},
|
|
},
|
|
},
|
|
)
|
|
|
|
self.assertEqual("remote-agent", preview["rollout_preview"]["execution_mode"])
|
|
self.assertEqual(2, preview["rollout_preview"]["target_nodes_total"])
|
|
self.assertEqual(2, preview["rollout_preview"]["expected_batches_total"])
|
|
self.assertEqual(2, preview["rollout_preview"]["expected_jobs_total"])
|
|
self.assertEqual("release_hub", preview["rollout_preview"]["focus_ref"]["kind"])
|
|
self.assertEqual("healthy", preview["smart_rollout"]["target_nodes"][0]["delivery_queue_state"])
|
|
self.assertEqual("retrying", preview["smart_rollout"]["target_nodes"][1]["delivery_queue_state"])
|
|
self.assertEqual("rollout_preview", preview["rollout_preview"]["focus_ref"]["section"])
|
|
self.assertEqual(["domaincheck-api", "domaincheck-worker"], preview["package"]["manifest"]["included_services"])
|