150 lines
7.0 KiB
Python
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()
|