Files
getDomain/domain-api/tests/test_ops_service_doctor_decision.py
2026-04-18 23:52:51 +08:00

150 lines
7.0 KiB
Python

from __future__ import annotations
import json
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch
from app.services.ops_service import get_ops_doctor_decision
class OpsServiceDoctorDecisionTests(unittest.TestCase):
@patch("app.services.ops_service.get_ops_contract_registry")
@patch("app.services.ops_service._build_live_doctor_payload")
@patch("app.services.ops_service._find_latest_go_live_bundle_manifest")
def test_doctor_decision_falls_back_to_live_payload_when_bundle_missing(
self,
mock_find_latest_go_live_bundle_manifest,
mock_build_live_doctor_payload,
mock_get_ops_contract_registry,
) -> None:
mock_find_latest_go_live_bundle_manifest.return_value = (None, Path("/tmp/go-live-bundles"))
mock_build_live_doctor_payload.return_value = (
{
"status": "attention",
"reason_code": "live_fallback",
"headline": "当前先补 attention 项",
"detail": "需要先顺着 stack next 收口。",
"preferred_surface": "stack-summary",
"next_action_code": "review_stack_next",
"recommended_commands": ["bash primary"],
"evidence": {},
},
{
"ok": False,
"required_failures": [],
"optional_unavailable": [],
"scene_log_reports_total": 0,
"scene_log_reports_ok": 0,
"scene_log_status_counts": {},
"scene_log_reports": [],
"contract_surface_gaps": [],
},
)
mock_get_ops_contract_registry.return_value = {
"contracts_by_key": {
"ops_doctor_decision_contract": {"version": "v1"},
}
}
payload = get_ops_doctor_decision(base_url="http://127.0.0.1:8100")
self.assertEqual("attention", payload["status"])
self.assertEqual("主决策待复核", payload["status_label"])
self.assertEqual("live_fallback", payload["source"])
self.assertEqual("review_stack_next", payload["decision"]["next_action_code"])
self.assertIn("doctor-decision", payload["recommended_commands"]["doctor_decision"])
self.assertEqual("bash primary", payload["recommended_commands"]["primary_recovery"])
self.assertEqual(
"bash domain-api/deploy/multi-region/drive_ops_center.sh stack-diagnosis http://127.0.0.1:8100 summary",
payload["recommended_commands"]["stack_check"],
)
self.assertEqual(
payload["recommended_commands"]["stack_check"],
payload["recommended_commands"]["stack_diagnosis"],
)
@patch("app.services.ops_service.get_ops_contract_registry")
def test_doctor_decision_reads_bundle_doctor_artifact_when_available(self, mock_get_ops_contract_registry) -> None:
mock_get_ops_contract_registry.return_value = {
"contracts_by_key": {
"ops_doctor_decision_contract": {"version": "v1"},
}
}
with tempfile.TemporaryDirectory() as temp_dir:
report_dir = Path(temp_dir) / "go-live-bundle-001"
report_dir.mkdir(parents=True, exist_ok=True)
doctor_artifact_path = report_dir / "07_doctor_decision.json"
doctor_artifact_path.write_text(
json.dumps(
{
"summary": {
"ok": False,
"required_failures": ["06_contracts_registry.txt"],
"optional_unavailable": ["12_activity_preview.txt"],
"scene_log_reports_total": 2,
"scene_log_reports_ok": 1,
"scene_log_status_counts": {"ok": 1, "missing": 1},
"scene_log_reports": [
{"node_code": "mainland-worker-01", "status": "ok", "ok": True},
{"node_code": "mainland-controller-01", "status": "missing", "ok": False},
],
"contract_surface_gaps": ["activity-stream"],
"launchpad_recommended_target_node_code": "mainland-worker-01",
"launchpad_recommended_recovery_label": "先补协议面",
"launchpad_recommended_recovery_summary": "先补 driver / activity 的协议导航。",
},
"decision": {
"status": "blocked",
"reason_code": "contracts_registry_unavailable",
"headline": "contract registry 当前不可用",
"detail": "先恢复 contracts registry 再继续自动驾驶。",
"preferred_surface": "contracts",
"next_action_code": "restore_contract_registry",
"recommended_commands": [
"bash domain-api/deploy/multi-region/drive_ops_center.sh contracts http://127.0.0.1:8100",
"bash domain-api/deploy/multi-region/drive_ops_center.sh runtime-refresh-recover http://127.0.0.1:8100",
],
"evidence": {"surface_http_status": {"contracts_registry": 503}},
},
},
ensure_ascii=False,
indent=2,
),
encoding="utf-8",
)
manifest_path = report_dir / "manifest.json"
manifest_path.write_text(
json.dumps(
{
"report_dir": str(report_dir),
"artifacts": [
{"key": "doctor_decision", "path": str(doctor_artifact_path), "ok": True},
],
},
ensure_ascii=False,
indent=2,
),
encoding="utf-8",
)
with patch(
"app.services.ops_service._find_latest_go_live_bundle_manifest",
return_value=(manifest_path, report_dir),
):
payload = get_ops_doctor_decision(base_url="http://127.0.0.1:8100")
self.assertEqual("blocked", payload["status"])
self.assertEqual("bundle_manifest", payload["source"])
self.assertEqual("contracts", payload["decision"]["preferred_surface"])
self.assertEqual(["06_contracts_registry.txt"], payload["summary"]["required_failures"])
self.assertEqual(["mainland-controller-01"], payload["summary"]["scene_log_problem_node_codes"])
self.assertIn("contracts", payload["recommended_commands"]["primary_recovery"])
self.assertIn(str(manifest_path), payload["recommended_commands"]["doctor_decision"])
if __name__ == "__main__":
unittest.main()