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