434 lines
18 KiB
Python
434 lines
18 KiB
Python
from __future__ import annotations
|
|
|
|
import unittest
|
|
from unittest.mock import patch
|
|
|
|
from app.services.ops_service import execute_resolved_driver_action, resolve_driver_action_request
|
|
|
|
|
|
def _build_preview_data(
|
|
*,
|
|
executor_kind: str,
|
|
primary_action_code: str,
|
|
recommendation: str,
|
|
automation_level: str,
|
|
target_api: str,
|
|
primary_request_payload: dict,
|
|
sequence_key: str = "",
|
|
secondary_action_code: str = "",
|
|
secondary_request_payload: dict | None = None,
|
|
secondary_recommendation: str = "",
|
|
secondary_automation_level: str = "",
|
|
secondary_target_api: str = "",
|
|
focus_ref: dict | None = None,
|
|
primary_focus_ref: dict | None = None,
|
|
secondary_focus_ref: dict | None = None,
|
|
) -> dict:
|
|
return {
|
|
"executor_kind": executor_kind,
|
|
"sequence_key": sequence_key,
|
|
"execution_chain": "ops-driver -> preview",
|
|
"focus_ref": dict(focus_ref or {}),
|
|
"primary_focus_ref": dict(primary_focus_ref or {}),
|
|
"secondary_focus_ref": dict(secondary_focus_ref or {}),
|
|
"primary": {
|
|
"label": "主动作",
|
|
"action_code": primary_action_code,
|
|
"node_codes": ["mainland-controller-01"],
|
|
"target_api": target_api,
|
|
"target_method": "POST",
|
|
"request_payload": dict(primary_request_payload or {}),
|
|
"focus_ref": dict(primary_focus_ref or {}),
|
|
"execution_profile": {
|
|
"action_code": primary_action_code,
|
|
"backend_handled": True,
|
|
"automation_level": automation_level,
|
|
"recommendation": recommendation,
|
|
"executor_mode_hint": "test-mode",
|
|
"risk_level": "low" if recommendation == "auto_execute" else "high",
|
|
"confirm_required": recommendation == "confirm_then_execute",
|
|
"ui_only": recommendation == "open_ui",
|
|
"blocked": recommendation == "blocked",
|
|
"requires_fields": [],
|
|
"missing_fields": [],
|
|
"reason": "preview reason",
|
|
},
|
|
},
|
|
"secondary": (
|
|
{
|
|
"label": "次动作",
|
|
"action_code": secondary_action_code,
|
|
"node_codes": ["mainland-controller-01"],
|
|
"target_api": secondary_target_api or target_api,
|
|
"target_method": "POST",
|
|
"request_payload": dict(secondary_request_payload or {}),
|
|
"focus_ref": dict(secondary_focus_ref or {}),
|
|
"execution_profile": {
|
|
"action_code": secondary_action_code,
|
|
"backend_handled": True,
|
|
"automation_level": secondary_automation_level or automation_level,
|
|
"recommendation": secondary_recommendation or recommendation,
|
|
"executor_mode_hint": "test-mode",
|
|
"risk_level": "low" if (secondary_recommendation or recommendation) == "auto_execute" else "high",
|
|
"confirm_required": (secondary_recommendation or recommendation) == "confirm_then_execute",
|
|
"ui_only": (secondary_recommendation or recommendation) == "open_ui",
|
|
"blocked": (secondary_recommendation or recommendation) == "blocked",
|
|
"requires_fields": [],
|
|
"missing_fields": [],
|
|
"reason": "preview reason",
|
|
},
|
|
}
|
|
if secondary_action_code
|
|
else {}
|
|
),
|
|
}
|
|
|
|
|
|
class OpsServiceDriverActionResolveTests(unittest.TestCase):
|
|
@patch("app.services.ops_service.preview_driver_action")
|
|
def test_resolve_driver_action_request_returns_gate_for_safe_auto(self, mock_preview_driver_action) -> None:
|
|
request_payload = {
|
|
"action_code": "enable_log_sync_key",
|
|
"node_codes": ["mainland-controller-01"],
|
|
"action_payload": {},
|
|
"requested_by": "test/driver-action",
|
|
}
|
|
mock_preview_driver_action.return_value = (
|
|
True,
|
|
"ok",
|
|
_build_preview_data(
|
|
executor_kind="driver_action",
|
|
primary_action_code="enable_log_sync_key",
|
|
recommendation="auto_execute",
|
|
automation_level="safe_auto",
|
|
target_api="/api/v1/ops/driver-actions/execute",
|
|
primary_request_payload=request_payload,
|
|
focus_ref={
|
|
"kind": "execution_scene",
|
|
"scene_key": "scene.logs",
|
|
},
|
|
primary_focus_ref={
|
|
"kind": "execution_scene",
|
|
"scene_key": "scene.logs",
|
|
},
|
|
),
|
|
)
|
|
|
|
ok, message, data = resolve_driver_action_request(
|
|
{
|
|
"source_label": "driver-feed",
|
|
"title": "开启关键日志回传",
|
|
"focus_ref": {
|
|
"kind": "execution_scene",
|
|
"scene_key": "scene.logs",
|
|
},
|
|
"primary_focus_ref": {
|
|
"kind": "execution_scene",
|
|
"scene_key": "scene.logs",
|
|
},
|
|
"primary_action_code": "enable_log_sync_key",
|
|
"primary_action_payload": {},
|
|
"requested_by": "test/driver-action",
|
|
}
|
|
)
|
|
|
|
self.assertTrue(ok)
|
|
self.assertEqual("驾驶动作解析完成", message)
|
|
self.assertEqual("execute_now", data["gate"]["decision"])
|
|
self.assertTrue(data["gate"]["will_execute"])
|
|
self.assertEqual("/api/v1/ops/driver-actions/execute", data["gate"]["target_api"])
|
|
self.assertEqual("enable_log_sync_key", data["preview_request"]["primary_action_code"])
|
|
self.assertEqual("execution_scene", data["preview_request"]["focus_ref"]["kind"])
|
|
self.assertEqual("scene.logs", data["gate"]["focus_ref"]["scene_key"])
|
|
self.assertEqual("scene.logs", data["focus_ref"]["scene_key"])
|
|
|
|
@patch("app.services.ops_service.preview_driver_action")
|
|
def test_resolve_driver_action_request_requires_confirmation_for_guarded_runbook(self, mock_preview_driver_action) -> None:
|
|
request_payload = {
|
|
"secondary": False,
|
|
"requested_by": "test/driver-action",
|
|
"action_payload": {"release_id": 7},
|
|
}
|
|
mock_preview_driver_action.return_value = (
|
|
True,
|
|
"ok",
|
|
_build_preview_data(
|
|
executor_kind="runbook_sequence",
|
|
primary_action_code="create_release_rollout_worker",
|
|
recommendation="confirm_then_execute",
|
|
automation_level="guarded_auto",
|
|
target_api="/api/v1/ops/runbook/sequences/release_progression/execute",
|
|
primary_request_payload=request_payload,
|
|
sequence_key="release_progression",
|
|
focus_ref={
|
|
"kind": "release_hub",
|
|
"release_id": 7,
|
|
"section": "release_launchpad",
|
|
},
|
|
primary_focus_ref={
|
|
"kind": "release_hub",
|
|
"release_id": 7,
|
|
"section": "release_launchpad",
|
|
"mode": "worker",
|
|
},
|
|
),
|
|
)
|
|
|
|
ok, _, data = resolve_driver_action_request(
|
|
{
|
|
"source_label": "driver-feed",
|
|
"title": "版本发布与放量",
|
|
"executor_kind": "runbook_sequence",
|
|
"sequence_key": "release_progression",
|
|
"focus_ref": {
|
|
"kind": "release_hub",
|
|
"release_id": 7,
|
|
"section": "release_launchpad",
|
|
},
|
|
"primary_focus_ref": {
|
|
"kind": "release_hub",
|
|
"release_id": 7,
|
|
"section": "release_launchpad",
|
|
"mode": "worker",
|
|
},
|
|
"primary_action_code": "create_release_rollout_worker",
|
|
"primary_action_payload": {"release_id": 7},
|
|
"requested_by": "test/driver-action",
|
|
}
|
|
)
|
|
|
|
self.assertTrue(ok)
|
|
self.assertEqual("confirmation_required", data["gate"]["decision"])
|
|
self.assertFalse(data["gate"]["will_execute"])
|
|
self.assertTrue(data["gate"]["confirm_required"])
|
|
self.assertEqual("release_progression", data["gate"]["sequence_key"])
|
|
self.assertEqual("worker", data["preview_request"]["primary_focus_ref"]["mode"])
|
|
self.assertEqual("worker", data["gate"]["focus_ref"]["mode"])
|
|
self.assertEqual("worker", data["focus_ref"]["mode"])
|
|
|
|
@patch("app.services.ops_service.preview_driver_action")
|
|
def test_resolve_driver_action_request_can_select_secondary_open_ui_section(self, mock_preview_driver_action) -> None:
|
|
mock_preview_driver_action.return_value = (
|
|
True,
|
|
"ok",
|
|
_build_preview_data(
|
|
executor_kind="runbook_sequence",
|
|
primary_action_code="create_release_rollout_worker",
|
|
recommendation="confirm_then_execute",
|
|
automation_level="guarded_auto",
|
|
target_api="/api/v1/ops/runbook/sequences/release_progression/execute",
|
|
primary_request_payload={
|
|
"secondary": False,
|
|
"requested_by": "test/driver-action",
|
|
"action_payload": {"release_id": 7},
|
|
},
|
|
sequence_key="release_progression",
|
|
secondary_action_code="focus_release_hub",
|
|
secondary_request_payload={
|
|
"secondary": True,
|
|
"requested_by": "test/driver-action",
|
|
"action_payload": {"release_id": 7},
|
|
},
|
|
secondary_recommendation="auto_execute",
|
|
secondary_automation_level="safe_auto",
|
|
secondary_target_api="/api/v1/ops/runbook/sequences/release_progression/execute",
|
|
focus_ref={
|
|
"kind": "release_hub",
|
|
"release_id": 7,
|
|
"section": "release_launchpad",
|
|
},
|
|
primary_focus_ref={
|
|
"kind": "release_hub",
|
|
"release_id": 7,
|
|
"section": "release_launchpad",
|
|
"mode": "worker",
|
|
},
|
|
secondary_focus_ref={
|
|
"kind": "release_hub",
|
|
"release_id": 7,
|
|
"section": "release_detail",
|
|
},
|
|
),
|
|
)
|
|
|
|
ok, _, data = resolve_driver_action_request(
|
|
{
|
|
"source_label": "driver-feed",
|
|
"title": "版本发布与放量",
|
|
"executor_kind": "runbook_sequence",
|
|
"sequence_key": "release_progression",
|
|
"focus_ref": {
|
|
"kind": "release_hub",
|
|
"release_id": 7,
|
|
"section": "release_launchpad",
|
|
},
|
|
"primary_focus_ref": {
|
|
"kind": "release_hub",
|
|
"release_id": 7,
|
|
"section": "release_launchpad",
|
|
"mode": "worker",
|
|
},
|
|
"secondary_focus_ref": {
|
|
"kind": "release_hub",
|
|
"release_id": 7,
|
|
"section": "release_detail",
|
|
},
|
|
"primary_action_code": "create_release_rollout_worker",
|
|
"secondary_action_code": "focus_release_hub",
|
|
"primary_action_payload": {"release_id": 7},
|
|
"secondary_action_payload": {"release_id": 7},
|
|
"requested_by": "test/driver-action",
|
|
"secondary": True,
|
|
}
|
|
)
|
|
|
|
self.assertTrue(ok)
|
|
self.assertEqual("secondary", data["selected_section"])
|
|
self.assertEqual("execute_now", data["gate"]["decision"])
|
|
self.assertTrue(data["gate"]["will_execute"])
|
|
self.assertEqual("focus_release_hub", data["gate"]["action_code"])
|
|
self.assertEqual("release_detail", data["gate"]["focus_ref"]["section"])
|
|
self.assertEqual("release_detail", data["focus_ref"]["section"])
|
|
|
|
@patch("app.services.ops_service.preview_driver_action")
|
|
def test_execute_resolved_driver_action_blocks_guarded_action_without_confirm(self, mock_preview_driver_action) -> None:
|
|
mock_preview_driver_action.return_value = (
|
|
True,
|
|
"ok",
|
|
_build_preview_data(
|
|
executor_kind="runbook_sequence",
|
|
primary_action_code="create_release_rollout_worker",
|
|
recommendation="confirm_then_execute",
|
|
automation_level="guarded_auto",
|
|
target_api="/api/v1/ops/runbook/sequences/release_progression/execute",
|
|
primary_request_payload={
|
|
"secondary": False,
|
|
"requested_by": "test/driver-action",
|
|
"action_payload": {"release_id": 7},
|
|
},
|
|
sequence_key="release_progression",
|
|
),
|
|
)
|
|
|
|
ok, message, data = execute_resolved_driver_action(
|
|
{
|
|
"source_label": "driver-feed",
|
|
"title": "版本发布与放量",
|
|
"executor_kind": "runbook_sequence",
|
|
"sequence_key": "release_progression",
|
|
"primary_action_code": "create_release_rollout_worker",
|
|
"primary_action_payload": {"release_id": 7},
|
|
"requested_by": "test/driver-action",
|
|
}
|
|
)
|
|
|
|
self.assertFalse(ok)
|
|
self.assertIn("需要显式确认", message)
|
|
self.assertFalse(data["executed"])
|
|
self.assertEqual("confirmation_required", data["gate"]["decision"])
|
|
|
|
@patch("app.services.ops_service.execute_driver_action")
|
|
@patch("app.services.ops_service.preview_driver_action")
|
|
def test_execute_resolved_driver_action_runs_safe_auto_driver_action(
|
|
self,
|
|
mock_preview_driver_action,
|
|
mock_execute_driver_action,
|
|
) -> None:
|
|
request_payload = {
|
|
"action_code": "enable_log_sync_key",
|
|
"node_codes": ["mainland-controller-01"],
|
|
"action_payload": {},
|
|
"requested_by": "test/driver-action",
|
|
}
|
|
mock_preview_driver_action.return_value = (
|
|
True,
|
|
"ok",
|
|
_build_preview_data(
|
|
executor_kind="driver_action",
|
|
primary_action_code="enable_log_sync_key",
|
|
recommendation="auto_execute",
|
|
automation_level="safe_auto",
|
|
target_api="/api/v1/ops/driver-actions/execute",
|
|
primary_request_payload=request_payload,
|
|
),
|
|
)
|
|
mock_execute_driver_action.return_value = (
|
|
True,
|
|
"日志回传已开启",
|
|
{"handled": True, "mode": "ops-job"},
|
|
)
|
|
|
|
ok, message, data = execute_resolved_driver_action(
|
|
{
|
|
"source_label": "driver-feed",
|
|
"title": "开启关键日志回传",
|
|
"primary_action_code": "enable_log_sync_key",
|
|
"primary_action_payload": {},
|
|
"requested_by": "test/driver-action",
|
|
}
|
|
)
|
|
|
|
self.assertTrue(ok)
|
|
self.assertEqual("日志回传已开启", message)
|
|
self.assertTrue(data["executed"])
|
|
self.assertEqual({"handled": True, "mode": "ops-job"}, data["execution_result"])
|
|
mock_execute_driver_action.assert_called_once_with(request_payload)
|
|
|
|
@patch("app.services.ops_service.execute_ops_runbook_sequence")
|
|
@patch("app.services.ops_service.preview_driver_action")
|
|
def test_execute_resolved_driver_action_runs_guarded_runbook_after_confirm(
|
|
self,
|
|
mock_preview_driver_action,
|
|
mock_execute_ops_runbook_sequence,
|
|
) -> None:
|
|
request_payload = {
|
|
"secondary": False,
|
|
"requested_by": "test/driver-action",
|
|
"action_payload": {"release_id": 7},
|
|
}
|
|
mock_preview_driver_action.return_value = (
|
|
True,
|
|
"ok",
|
|
_build_preview_data(
|
|
executor_kind="runbook_sequence",
|
|
primary_action_code="create_release_rollout_worker",
|
|
recommendation="confirm_then_execute",
|
|
automation_level="guarded_auto",
|
|
target_api="/api/v1/ops/runbook/sequences/release_progression/execute",
|
|
primary_request_payload=request_payload,
|
|
sequence_key="release_progression",
|
|
),
|
|
)
|
|
mock_execute_ops_runbook_sequence.return_value = (
|
|
True,
|
|
"Release rollout 已创建",
|
|
{
|
|
"handled": True,
|
|
"mode": "smart-release-rollout",
|
|
"driver_action_code": "create_release_rollout_worker",
|
|
},
|
|
)
|
|
|
|
ok, message, data = execute_resolved_driver_action(
|
|
{
|
|
"source_label": "driver-feed",
|
|
"title": "版本发布与放量",
|
|
"executor_kind": "runbook_sequence",
|
|
"sequence_key": "release_progression",
|
|
"primary_action_code": "create_release_rollout_worker",
|
|
"primary_action_payload": {"release_id": 7},
|
|
"requested_by": "test/driver-action",
|
|
"confirm": True,
|
|
}
|
|
)
|
|
|
|
self.assertTrue(ok)
|
|
self.assertEqual("Release rollout 已创建", message)
|
|
self.assertTrue(data["executed"])
|
|
self.assertEqual("execute_confirmed", data["gate"]["decision"])
|
|
mock_execute_ops_runbook_sequence.assert_called_once_with("release_progression", request_payload)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|