feat: add ops center and node onboarding flow
This commit is contained in:
433
domain-api/tests/test_ops_service_driver_action_resolve.py
Normal file
433
domain-api/tests/test_ops_service_driver_action_resolve.py
Normal file
@@ -0,0 +1,433 @@
|
||||
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()
|
||||
Reference in New Issue
Block a user