926 lines
37 KiB
Python
926 lines
37 KiB
Python
from __future__ import annotations
|
|
|
|
import unittest
|
|
from unittest.mock import patch
|
|
|
|
from app.services.ops_service import execute_codex_action, resolve_codex_action
|
|
|
|
|
|
def _build_codex_brief_entry(
|
|
*,
|
|
key: str,
|
|
executor_kind: str,
|
|
primary_action_code: str,
|
|
recommendation: str,
|
|
automation_level: str,
|
|
target_api: str,
|
|
target_method: str = "POST",
|
|
sequence_key: str = "",
|
|
primary_action_payload: dict | None = None,
|
|
secondary_action_code: str = "",
|
|
secondary_action_payload: dict | None = None,
|
|
focus_ref: dict | None = None,
|
|
primary_focus_ref: dict | None = None,
|
|
secondary_focus_ref: dict | None = None,
|
|
) -> dict:
|
|
primary_action_payload = dict(primary_action_payload or {})
|
|
secondary_action_payload = dict(secondary_action_payload or {})
|
|
normalized_focus_ref = dict(focus_ref or {})
|
|
normalized_primary_focus_ref = dict(primary_focus_ref or {})
|
|
normalized_secondary_focus_ref = dict(secondary_focus_ref or {})
|
|
if normalized_primary_focus_ref and not dict(primary_action_payload.get("focus_ref") or {}):
|
|
primary_action_payload["focus_ref"] = dict(normalized_primary_focus_ref)
|
|
if normalized_secondary_focus_ref and not dict(secondary_action_payload.get("focus_ref") or {}):
|
|
secondary_action_payload["focus_ref"] = dict(normalized_secondary_focus_ref)
|
|
request_payload_preview = (
|
|
{
|
|
"sequence_key": sequence_key,
|
|
"secondary": False,
|
|
"action_payload": primary_action_payload,
|
|
}
|
|
if executor_kind == "runbook_sequence"
|
|
else {
|
|
"action_code": primary_action_code,
|
|
"node_codes": ["mainland-controller-01"],
|
|
"action_payload": primary_action_payload,
|
|
}
|
|
)
|
|
return {
|
|
"key": key,
|
|
"title": "测试动作",
|
|
"summary": "测试摘要",
|
|
"reason": "测试原因",
|
|
"meta_text": "meta",
|
|
"executor_kind": executor_kind,
|
|
"sequence_key": sequence_key,
|
|
"focus_ref": normalized_focus_ref,
|
|
"primary_focus_ref": normalized_primary_focus_ref,
|
|
"secondary_focus_ref": normalized_secondary_focus_ref,
|
|
"primary_label": "主动作",
|
|
"secondary_label": "次动作" if secondary_action_code else "",
|
|
"primary_action_code": primary_action_code,
|
|
"secondary_action_code": secondary_action_code,
|
|
"autopilot": {
|
|
"status_text": "测试状态",
|
|
"automation_level": automation_level,
|
|
"recommendation": recommendation,
|
|
"executor_mode_hint": "test-mode",
|
|
"confirm_required": recommendation == "confirm_then_execute",
|
|
"ui_only": recommendation == "open_ui",
|
|
"blocked": recommendation == "blocked",
|
|
"backend_handled": True,
|
|
"risk_level": "low" if recommendation == "auto_execute" else "high",
|
|
"reason": "autopilot reason",
|
|
"target_method": target_method,
|
|
"target_api": target_api,
|
|
},
|
|
"primary_execution": {
|
|
"label": "主动作",
|
|
"node_codes": ["mainland-controller-01"],
|
|
"action_payload": primary_action_payload,
|
|
"request_payload_preview": request_payload_preview,
|
|
},
|
|
"secondary_execution": (
|
|
{
|
|
"label": "次动作",
|
|
"node_codes": [],
|
|
"action_payload": secondary_action_payload,
|
|
}
|
|
if secondary_action_code
|
|
else {}
|
|
),
|
|
}
|
|
|
|
|
|
def _build_preview_data(
|
|
*,
|
|
executor_kind: str,
|
|
action_code: str,
|
|
recommendation: str,
|
|
automation_level: str,
|
|
target_api: str,
|
|
request_payload: dict,
|
|
sequence_key: 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": action_code,
|
|
"target_api": target_api,
|
|
"target_method": "POST",
|
|
"request_payload": dict(request_payload or {}),
|
|
"focus_ref": dict(primary_focus_ref or {}),
|
|
"execution_profile": {
|
|
"action_code": 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": {},
|
|
}
|
|
|
|
|
|
def _build_codex_activity_focus_entry(
|
|
*,
|
|
key: str,
|
|
activity_key: str,
|
|
activity_kind: str,
|
|
ui_intent_kind: str,
|
|
focus_ref: dict | None = None,
|
|
source_focus_ref: dict | None = None,
|
|
target_node_codes: list[str] | None = None,
|
|
) -> dict:
|
|
normalized_focus_ref = dict(focus_ref or {})
|
|
return {
|
|
"key": key,
|
|
"activity_key": activity_key,
|
|
"activity_kind": activity_kind,
|
|
"title": "观察焦点",
|
|
"subtitle": "日志回传待确认",
|
|
"summary": "该焦点属于观察项,应进入对应工作区查看上下文。",
|
|
"status": "attention",
|
|
"status_label": "待关注",
|
|
"meta_text": "activity-meta",
|
|
"occurred_at": "2026-04-18T06:00:00",
|
|
"target_node_codes": list(target_node_codes or []),
|
|
"focus_ref": normalized_focus_ref,
|
|
"source_focus_ref": dict(source_focus_ref or {}),
|
|
"ui_intent": {
|
|
"kind": ui_intent_kind,
|
|
"activity_key": activity_key,
|
|
},
|
|
"ui_intent_kind": ui_intent_kind,
|
|
"focus_action_code": "focus_activity_item",
|
|
"focus_action_payload": {
|
|
"activity_key": activity_key,
|
|
"kind": activity_kind,
|
|
"status": "attention",
|
|
"ui_intent": {
|
|
"kind": ui_intent_kind,
|
|
"activity_key": activity_key,
|
|
},
|
|
"focus_ref": normalized_focus_ref,
|
|
},
|
|
"observation_only": True,
|
|
}
|
|
|
|
|
|
class OpsServiceCodexActionTests(unittest.TestCase):
|
|
@patch("app.services.ops_service.preview_driver_action")
|
|
@patch("app.services.ops_service.get_ops_codex_brief")
|
|
def test_resolve_codex_action_returns_gate_for_safe_auto(self, mock_get_ops_codex_brief, mock_preview_driver_action) -> None:
|
|
mock_get_ops_codex_brief.return_value = {
|
|
"focus": {"entry_key": "recommendation:enable-log-sync"},
|
|
"summary": {"total": 1},
|
|
"entries": [
|
|
_build_codex_brief_entry(
|
|
key="recommendation:enable-log-sync",
|
|
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",
|
|
focus_ref={
|
|
"kind": "execution_scene",
|
|
"scene_key": "scene.logs",
|
|
},
|
|
primary_focus_ref={
|
|
"kind": "execution_scene",
|
|
"scene_key": "scene.logs",
|
|
},
|
|
)
|
|
],
|
|
}
|
|
mock_preview_driver_action.return_value = (
|
|
True,
|
|
"ok",
|
|
_build_preview_data(
|
|
executor_kind="driver_action",
|
|
action_code="enable_log_sync_key",
|
|
recommendation="auto_execute",
|
|
automation_level="safe_auto",
|
|
target_api="/api/v1/ops/driver-actions/execute",
|
|
request_payload={
|
|
"action_code": "enable_log_sync_key",
|
|
"node_codes": ["mainland-controller-01"],
|
|
"action_payload": {
|
|
"focus_ref": {
|
|
"kind": "execution_scene",
|
|
"scene_key": "scene.logs",
|
|
}
|
|
},
|
|
"requested_by": "test/codex-action",
|
|
},
|
|
focus_ref={
|
|
"kind": "execution_scene",
|
|
"scene_key": "scene.logs",
|
|
},
|
|
primary_focus_ref={
|
|
"kind": "execution_scene",
|
|
"scene_key": "scene.logs",
|
|
},
|
|
),
|
|
)
|
|
|
|
ok, message, data = resolve_codex_action({"requested_by": "test/codex-action"})
|
|
|
|
self.assertTrue(ok)
|
|
self.assertEqual("Codex 驾驶动作解析完成", message)
|
|
self.assertEqual("recommendation:enable-log-sync", data["entry_key"])
|
|
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")
|
|
@patch("app.services.ops_service.get_ops_codex_brief")
|
|
def test_resolve_codex_action_requires_confirmation_for_guarded_runbook(
|
|
self,
|
|
mock_get_ops_codex_brief,
|
|
mock_preview_driver_action,
|
|
) -> None:
|
|
mock_get_ops_codex_brief.return_value = {
|
|
"focus": {"entry_key": "runbook:release_progression"},
|
|
"summary": {"total": 1},
|
|
"entries": [
|
|
_build_codex_brief_entry(
|
|
key="runbook:release_progression",
|
|
executor_kind="runbook_sequence",
|
|
sequence_key="release_progression",
|
|
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_action_payload={"release_id": 7},
|
|
secondary_action_code="focus_release_hub",
|
|
secondary_action_payload={"release_id": 7},
|
|
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",
|
|
},
|
|
)
|
|
],
|
|
}
|
|
mock_preview_driver_action.return_value = (
|
|
True,
|
|
"ok",
|
|
_build_preview_data(
|
|
executor_kind="runbook_sequence",
|
|
action_code="create_release_rollout_worker",
|
|
recommendation="confirm_then_execute",
|
|
automation_level="guarded_auto",
|
|
target_api="/api/v1/ops/runbook/sequences/release_progression/execute",
|
|
request_payload={
|
|
"secondary": False,
|
|
"requested_by": "test/codex-action",
|
|
"action_payload": {
|
|
"release_id": 7,
|
|
"focus_ref": {
|
|
"kind": "release_hub",
|
|
"release_id": 7,
|
|
"section": "release_launchpad",
|
|
"mode": "worker",
|
|
},
|
|
},
|
|
},
|
|
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",
|
|
},
|
|
),
|
|
)
|
|
|
|
ok, _, data = resolve_codex_action(
|
|
{
|
|
"entry_key": "runbook:release_progression",
|
|
"requested_by": "test/codex-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")
|
|
@patch("app.services.ops_service.get_ops_codex_brief")
|
|
def test_resolve_codex_action_can_select_secondary_section(
|
|
self,
|
|
mock_get_ops_codex_brief,
|
|
mock_preview_driver_action,
|
|
) -> None:
|
|
mock_get_ops_codex_brief.return_value = {
|
|
"focus": {"entry_key": "runbook:release_progression"},
|
|
"summary": {"total": 1},
|
|
"entries": [
|
|
_build_codex_brief_entry(
|
|
key="runbook:release_progression",
|
|
executor_kind="runbook_sequence",
|
|
sequence_key="release_progression",
|
|
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_action_payload={"release_id": 7},
|
|
secondary_action_code="focus_release_hub",
|
|
secondary_action_payload={"release_id": 7},
|
|
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",
|
|
},
|
|
)
|
|
],
|
|
}
|
|
preview_data = _build_preview_data(
|
|
executor_kind="runbook_sequence",
|
|
action_code="create_release_rollout_worker",
|
|
recommendation="confirm_then_execute",
|
|
automation_level="guarded_auto",
|
|
target_api="/api/v1/ops/runbook/sequences/release_progression/execute",
|
|
request_payload={
|
|
"secondary": False,
|
|
"requested_by": "test/codex-action",
|
|
"action_payload": {
|
|
"release_id": 7,
|
|
"focus_ref": {
|
|
"kind": "release_hub",
|
|
"release_id": 7,
|
|
"section": "release_launchpad",
|
|
"mode": "worker",
|
|
},
|
|
},
|
|
},
|
|
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",
|
|
},
|
|
)
|
|
preview_data["secondary"] = {
|
|
"label": "查看版本区",
|
|
"action_code": "focus_release_hub",
|
|
"target_api": "/api/v1/ops/runbook/sequences/release_progression/execute",
|
|
"target_method": "POST",
|
|
"request_payload": {
|
|
"secondary": True,
|
|
"requested_by": "test/codex-action",
|
|
"action_payload": {
|
|
"release_id": 7,
|
|
"focus_ref": {
|
|
"kind": "release_hub",
|
|
"release_id": 7,
|
|
"section": "release_detail",
|
|
},
|
|
},
|
|
},
|
|
"focus_ref": {
|
|
"kind": "release_hub",
|
|
"release_id": 7,
|
|
"section": "release_detail",
|
|
},
|
|
"execution_profile": {
|
|
"action_code": "focus_release_hub",
|
|
"backend_handled": False,
|
|
"automation_level": "safe_auto",
|
|
"recommendation": "auto_execute",
|
|
"executor_mode_hint": "ui-intent",
|
|
"risk_level": "low",
|
|
"confirm_required": False,
|
|
"ui_only": False,
|
|
"blocked": False,
|
|
"requires_fields": [],
|
|
"missing_fields": [],
|
|
"reason": "secondary preview reason",
|
|
},
|
|
}
|
|
mock_preview_driver_action.return_value = (True, "ok", preview_data)
|
|
|
|
ok, _, data = resolve_codex_action(
|
|
{
|
|
"entry_key": "runbook:release_progression",
|
|
"requested_by": "test/codex-action",
|
|
"secondary": True,
|
|
}
|
|
)
|
|
|
|
self.assertTrue(ok)
|
|
self.assertEqual("secondary", data["selected_section"])
|
|
self.assertTrue(data["gate"]["secondary"])
|
|
self.assertEqual("focus_release_hub", data["gate"]["action_code"])
|
|
self.assertEqual("auto_execute", data["gate"]["recommendation"])
|
|
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")
|
|
@patch("app.services.ops_service.get_ops_codex_brief")
|
|
def test_resolve_codex_action_can_fall_back_to_activity_focus_entry(
|
|
self,
|
|
mock_get_ops_codex_brief,
|
|
mock_preview_driver_action,
|
|
) -> None:
|
|
mock_get_ops_codex_brief.return_value = {
|
|
"focus": {"entry_key": "activity:ops-job-42", "recommendation": "open_ui"},
|
|
"summary": {"total": 0, "activity_focus_total": 1},
|
|
"entries": [],
|
|
"activity_focus": [
|
|
_build_codex_activity_focus_entry(
|
|
key="activity:ops-job-42",
|
|
activity_key="ops-job-42",
|
|
activity_kind="ops_job",
|
|
ui_intent_kind="job_events",
|
|
focus_ref={
|
|
"kind": "ops_job",
|
|
"job_key": "job-42",
|
|
},
|
|
source_focus_ref={
|
|
"kind": "ops_job_event",
|
|
"job_key": "job-42",
|
|
"event_key": "event-1",
|
|
},
|
|
target_node_codes=["mainland-controller-01"],
|
|
)
|
|
],
|
|
}
|
|
mock_preview_driver_action.return_value = (
|
|
True,
|
|
"ok",
|
|
_build_preview_data(
|
|
executor_kind="driver_action",
|
|
action_code="focus_activity_item",
|
|
recommendation="auto_execute",
|
|
automation_level="safe_auto",
|
|
target_api="/api/v1/ops/driver-actions/execute",
|
|
request_payload={
|
|
"action_code": "focus_activity_item",
|
|
"node_codes": ["mainland-controller-01"],
|
|
"action_payload": {
|
|
"activity_key": "ops-job-42",
|
|
"kind": "ops_job",
|
|
"status": "attention",
|
|
"ui_intent": {
|
|
"kind": "job_events",
|
|
"activity_key": "ops-job-42",
|
|
},
|
|
"focus_ref": {
|
|
"kind": "ops_job",
|
|
"job_key": "job-42",
|
|
},
|
|
},
|
|
"requested_by": "test/codex-action",
|
|
},
|
|
focus_ref={
|
|
"kind": "ops_job",
|
|
"job_key": "job-42",
|
|
},
|
|
primary_focus_ref={
|
|
"kind": "ops_job",
|
|
"job_key": "job-42",
|
|
},
|
|
),
|
|
)
|
|
|
|
ok, _, data = resolve_codex_action({"requested_by": "test/codex-action"})
|
|
|
|
self.assertTrue(ok)
|
|
self.assertEqual("activity:ops-job-42", data["entry_key"])
|
|
self.assertTrue(data["selected_entry"]["observation_only"])
|
|
self.assertEqual("ops_job", data["selected_entry"]["activity_kind"])
|
|
self.assertEqual("focus_activity_item", data["preview_request"]["primary_action_code"])
|
|
self.assertEqual("execute_now", data["gate"]["decision"])
|
|
self.assertTrue(data["gate"]["will_execute"])
|
|
self.assertEqual("job-42", data["focus_ref"]["job_key"])
|
|
|
|
@patch("app.services.ops_service.execute_driver_action")
|
|
@patch("app.services.ops_service.preview_driver_action")
|
|
@patch("app.services.ops_service.get_ops_codex_brief")
|
|
def test_execute_codex_action_runs_safe_auto_driver_action(
|
|
self,
|
|
mock_get_ops_codex_brief,
|
|
mock_preview_driver_action,
|
|
mock_execute_driver_action,
|
|
) -> None:
|
|
mock_get_ops_codex_brief.return_value = {
|
|
"focus": {"entry_key": "recommendation:enable-log-sync"},
|
|
"summary": {"total": 1},
|
|
"entries": [
|
|
_build_codex_brief_entry(
|
|
key="recommendation:enable-log-sync",
|
|
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",
|
|
)
|
|
],
|
|
}
|
|
request_payload = {
|
|
"action_code": "enable_log_sync_key",
|
|
"node_codes": ["mainland-controller-01"],
|
|
"action_payload": {},
|
|
"requested_by": "test/codex-action",
|
|
}
|
|
mock_preview_driver_action.return_value = (
|
|
True,
|
|
"ok",
|
|
_build_preview_data(
|
|
executor_kind="driver_action",
|
|
action_code="enable_log_sync_key",
|
|
recommendation="auto_execute",
|
|
automation_level="safe_auto",
|
|
target_api="/api/v1/ops/driver-actions/execute",
|
|
request_payload=request_payload,
|
|
),
|
|
)
|
|
mock_execute_driver_action.return_value = (
|
|
True,
|
|
"执行成功",
|
|
{"handled": True, "action_code": "enable_log_sync_key"},
|
|
)
|
|
|
|
ok, message, data = execute_codex_action({"requested_by": "test/codex-action"})
|
|
|
|
self.assertTrue(ok)
|
|
self.assertEqual("执行成功", message)
|
|
self.assertTrue(data["executed"])
|
|
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")
|
|
@patch("app.services.ops_service.get_ops_codex_brief")
|
|
def test_execute_codex_action_blocks_guarded_runbook_without_confirm(
|
|
self,
|
|
mock_get_ops_codex_brief,
|
|
mock_preview_driver_action,
|
|
mock_execute_ops_runbook_sequence,
|
|
) -> None:
|
|
mock_get_ops_codex_brief.return_value = {
|
|
"focus": {"entry_key": "runbook:release_progression"},
|
|
"summary": {"total": 1},
|
|
"entries": [
|
|
_build_codex_brief_entry(
|
|
key="runbook:release_progression",
|
|
executor_kind="runbook_sequence",
|
|
sequence_key="release_progression",
|
|
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_action_payload={"release_id": 7},
|
|
)
|
|
],
|
|
}
|
|
mock_preview_driver_action.return_value = (
|
|
True,
|
|
"ok",
|
|
_build_preview_data(
|
|
executor_kind="runbook_sequence",
|
|
action_code="create_release_rollout_worker",
|
|
recommendation="confirm_then_execute",
|
|
automation_level="guarded_auto",
|
|
target_api="/api/v1/ops/runbook/sequences/release_progression/execute",
|
|
request_payload={
|
|
"secondary": False,
|
|
"requested_by": "test/codex-action",
|
|
"action_payload": {"release_id": 7},
|
|
},
|
|
sequence_key="release_progression",
|
|
),
|
|
)
|
|
|
|
ok, message, data = execute_codex_action(
|
|
{
|
|
"entry_key": "runbook:release_progression",
|
|
"requested_by": "test/codex-action",
|
|
}
|
|
)
|
|
|
|
self.assertFalse(ok)
|
|
self.assertIn("确认", message)
|
|
self.assertFalse(data["executed"])
|
|
mock_execute_ops_runbook_sequence.assert_not_called()
|
|
|
|
@patch("app.services.ops_service.execute_driver_action")
|
|
@patch("app.services.ops_service.preview_driver_action")
|
|
@patch("app.services.ops_service.get_ops_codex_brief")
|
|
def test_execute_codex_action_runs_activity_focus_as_safe_auto(
|
|
self,
|
|
mock_get_ops_codex_brief,
|
|
mock_preview_driver_action,
|
|
mock_execute_driver_action,
|
|
) -> None:
|
|
mock_get_ops_codex_brief.return_value = {
|
|
"focus": {"entry_key": "activity:ops-job-42", "recommendation": "open_ui"},
|
|
"summary": {"total": 0, "activity_focus_total": 1},
|
|
"entries": [],
|
|
"activity_focus": [
|
|
_build_codex_activity_focus_entry(
|
|
key="activity:ops-job-42",
|
|
activity_key="ops-job-42",
|
|
activity_kind="ops_job",
|
|
ui_intent_kind="job_events",
|
|
focus_ref={
|
|
"kind": "ops_job",
|
|
"job_key": "job-42",
|
|
},
|
|
)
|
|
],
|
|
}
|
|
mock_preview_driver_action.return_value = (
|
|
True,
|
|
"ok",
|
|
_build_preview_data(
|
|
executor_kind="driver_action",
|
|
action_code="focus_activity_item",
|
|
recommendation="auto_execute",
|
|
automation_level="safe_auto",
|
|
target_api="/api/v1/ops/driver-actions/execute",
|
|
request_payload={
|
|
"action_code": "focus_activity_item",
|
|
"node_codes": [],
|
|
"action_payload": {
|
|
"activity_key": "ops-job-42",
|
|
"kind": "ops_job",
|
|
"status": "attention",
|
|
"ui_intent": {
|
|
"kind": "job_events",
|
|
"activity_key": "ops-job-42",
|
|
},
|
|
"focus_ref": {
|
|
"kind": "ops_job",
|
|
"job_key": "job-42",
|
|
},
|
|
},
|
|
"requested_by": "test/codex-action",
|
|
},
|
|
focus_ref={
|
|
"kind": "ops_job",
|
|
"job_key": "job-42",
|
|
},
|
|
primary_focus_ref={
|
|
"kind": "ops_job",
|
|
"job_key": "job-42",
|
|
},
|
|
),
|
|
)
|
|
mock_execute_driver_action.return_value = (
|
|
True,
|
|
"执行成功",
|
|
{"handled": True, "action_code": "focus_activity_item"},
|
|
)
|
|
|
|
ok, message, data = execute_codex_action({"requested_by": "test/codex-action"})
|
|
|
|
self.assertTrue(ok)
|
|
self.assertEqual("执行成功", message)
|
|
self.assertTrue(data["executed"])
|
|
mock_execute_driver_action.assert_called_once()
|
|
|
|
@patch("app.services.ops_service.preview_driver_action")
|
|
@patch("app.services.ops_service.get_ops_codex_brief")
|
|
def test_resolve_codex_action_supports_focus_latest_job_events_without_ui_intent(
|
|
self,
|
|
mock_get_ops_codex_brief,
|
|
mock_preview_driver_action,
|
|
) -> None:
|
|
mock_get_ops_codex_brief.return_value = {
|
|
"focus": {"entry_key": "activity:ops-job-81", "recommendation": "open_ui"},
|
|
"summary": {"total": 0, "activity_focus_total": 1},
|
|
"entries": [],
|
|
"activity_focus": [
|
|
{
|
|
"key": "activity:ops-job-81",
|
|
"activity_key": "ops-job-81",
|
|
"activity_kind": "ops_job",
|
|
"title": "开始回执异常",
|
|
"subtitle": "ops-agent-81",
|
|
"summary": "节点已接单执行,但开始回执未成功送达控制面。",
|
|
"status": "attention",
|
|
"status_label": "待处理",
|
|
"meta_text": "ops-agent-81",
|
|
"occurred_at": "2026-04-18T06:00:00",
|
|
"target_node_codes": ["mainland-worker-01"],
|
|
"focus_ref": {
|
|
"kind": "ops_job",
|
|
"job_id": 81,
|
|
"job_code": "ops-agent-81",
|
|
"target_node_code": "mainland-worker-01",
|
|
},
|
|
"source_focus_ref": {
|
|
"kind": "ops_job_event",
|
|
"job_id": 81,
|
|
"job_code": "ops-agent-81",
|
|
"event_key": "job-event:81",
|
|
"target_node_code": "mainland-worker-01",
|
|
},
|
|
"ui_intent": {},
|
|
"ui_intent_kind": "",
|
|
"focus_action_code": "focus_latest_job_events",
|
|
"focus_action_payload": {
|
|
"focus_ref": {
|
|
"kind": "ops_job",
|
|
"job_id": 81,
|
|
"job_code": "ops-agent-81",
|
|
"target_node_code": "mainland-worker-01",
|
|
},
|
|
"source_focus_ref": {
|
|
"kind": "ops_job_event",
|
|
"job_id": 81,
|
|
"job_code": "ops-agent-81",
|
|
"event_key": "job-event:81",
|
|
"target_node_code": "mainland-worker-01",
|
|
},
|
|
},
|
|
"observation_only": True,
|
|
}
|
|
],
|
|
}
|
|
mock_preview_driver_action.return_value = (
|
|
True,
|
|
"ok",
|
|
_build_preview_data(
|
|
executor_kind="driver_action",
|
|
action_code="focus_latest_job_events",
|
|
recommendation="auto_execute",
|
|
automation_level="safe_auto",
|
|
target_api="/api/v1/ops/driver-actions/execute",
|
|
request_payload={
|
|
"action_code": "focus_latest_job_events",
|
|
"node_codes": ["mainland-worker-01"],
|
|
"action_payload": {
|
|
"focus_ref": {
|
|
"kind": "ops_job",
|
|
"job_id": 81,
|
|
"job_code": "ops-agent-81",
|
|
"target_node_code": "mainland-worker-01",
|
|
},
|
|
"source_focus_ref": {
|
|
"kind": "ops_job_event",
|
|
"job_id": 81,
|
|
"job_code": "ops-agent-81",
|
|
"event_key": "job-event:81",
|
|
"target_node_code": "mainland-worker-01",
|
|
},
|
|
},
|
|
"requested_by": "test/codex-action",
|
|
},
|
|
focus_ref={
|
|
"kind": "ops_job",
|
|
"job_id": 81,
|
|
"job_code": "ops-agent-81",
|
|
},
|
|
primary_focus_ref={
|
|
"kind": "ops_job",
|
|
"job_id": 81,
|
|
"job_code": "ops-agent-81",
|
|
},
|
|
),
|
|
)
|
|
|
|
ok, _, data = resolve_codex_action({"requested_by": "test/codex-action"})
|
|
|
|
self.assertTrue(ok)
|
|
self.assertEqual("activity:ops-job-81", data["entry_key"])
|
|
self.assertEqual("focus_latest_job_events", data["preview_request"]["primary_action_code"])
|
|
self.assertEqual("execute_now", data["gate"]["decision"])
|
|
self.assertTrue(data["gate"]["will_execute"])
|
|
|
|
@patch("app.services.ops_service.execute_ops_runbook_sequence")
|
|
@patch("app.services.ops_service.preview_driver_action")
|
|
@patch("app.services.ops_service.get_ops_codex_brief")
|
|
def test_execute_codex_action_runs_guarded_runbook_after_confirm(
|
|
self,
|
|
mock_get_ops_codex_brief,
|
|
mock_preview_driver_action,
|
|
mock_execute_ops_runbook_sequence,
|
|
) -> None:
|
|
mock_get_ops_codex_brief.return_value = {
|
|
"focus": {"entry_key": "runbook:release_progression"},
|
|
"summary": {"total": 1},
|
|
"entries": [
|
|
_build_codex_brief_entry(
|
|
key="runbook:release_progression",
|
|
executor_kind="runbook_sequence",
|
|
sequence_key="release_progression",
|
|
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_action_payload={"release_id": 7},
|
|
)
|
|
],
|
|
}
|
|
request_payload = {
|
|
"secondary": False,
|
|
"requested_by": "test/codex-action",
|
|
"action_payload": {"release_id": 7},
|
|
}
|
|
mock_preview_driver_action.return_value = (
|
|
True,
|
|
"ok",
|
|
_build_preview_data(
|
|
executor_kind="runbook_sequence",
|
|
action_code="create_release_rollout_worker",
|
|
recommendation="confirm_then_execute",
|
|
automation_level="guarded_auto",
|
|
target_api="/api/v1/ops/runbook/sequences/release_progression/execute",
|
|
request_payload=request_payload,
|
|
sequence_key="release_progression",
|
|
),
|
|
)
|
|
mock_execute_ops_runbook_sequence.return_value = (
|
|
True,
|
|
"runbook 执行成功",
|
|
{"sequence_key": "release_progression", "driver_action_code": "create_release_rollout_worker"},
|
|
)
|
|
|
|
ok, message, data = execute_codex_action(
|
|
{
|
|
"entry_key": "runbook:release_progression",
|
|
"requested_by": "test/codex-action",
|
|
"confirm": True,
|
|
}
|
|
)
|
|
|
|
self.assertTrue(ok)
|
|
self.assertEqual("runbook 执行成功", message)
|
|
self.assertTrue(data["executed"])
|
|
mock_execute_ops_runbook_sequence.assert_called_once_with("release_progression", request_payload)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|