feat: add ops center and node onboarding flow
This commit is contained in:
295
domain-api/tests/test_ops_service_contract_registry.py
Normal file
295
domain-api/tests/test_ops_service_contract_registry.py
Normal file
@@ -0,0 +1,295 @@
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
from app.services.ops_service import (
|
||||
get_ops_blueprint,
|
||||
get_ops_capabilities,
|
||||
get_ops_contract_detail,
|
||||
get_ops_contract_registry,
|
||||
get_ops_runbook,
|
||||
)
|
||||
|
||||
|
||||
class OpsServiceContractRegistryTests(unittest.TestCase):
|
||||
def test_contract_registry_lists_all_frozen_ops_contracts(self) -> None:
|
||||
payload = get_ops_contract_registry()
|
||||
|
||||
self.assertEqual("2026-04-18", payload["registry_version"])
|
||||
self.assertEqual("v1", payload["schema_version"])
|
||||
self.assertEqual(11, payload["contracts_total"])
|
||||
self.assertEqual(
|
||||
[
|
||||
"ops_job_contract",
|
||||
"ops_agent_protocol",
|
||||
"release_hub_contract",
|
||||
"ops_driver_contract",
|
||||
"ops_playbook_contract",
|
||||
"ops_observability_contract",
|
||||
"ops_stack_diagnosis_contract",
|
||||
"ops_doctor_decision_contract",
|
||||
"ops_go_live_signoff_contract",
|
||||
"ops_go_live_bundle_contract",
|
||||
"ops_go_live_review_contract",
|
||||
],
|
||||
[item["key"] for item in payload["contracts"]],
|
||||
)
|
||||
self.assertIn("/api/v1/ops/contracts/{contract_key}", payload["discovery_entrypoints"])
|
||||
self.assertEqual(
|
||||
"docs/schemas/ops_job_contract.md",
|
||||
payload["contracts_by_key"]["ops_job_contract"]["schema_doc_path"],
|
||||
)
|
||||
self.assertIn(
|
||||
"/api/v1/ops/jobs/{job_id}/dispatch",
|
||||
payload["contracts_by_key"]["ops_job_contract"]["discovery_endpoints"],
|
||||
)
|
||||
self.assertEqual(
|
||||
"docs/schemas/ops_agent_protocol.md",
|
||||
payload["contracts_by_key"]["ops_agent_protocol"]["schema_doc_path"],
|
||||
)
|
||||
self.assertIn(
|
||||
"/api/v1/ops/agent/bootstrap-plan",
|
||||
payload["contracts_by_key"]["ops_agent_protocol"]["discovery_endpoints"],
|
||||
)
|
||||
self.assertIn(
|
||||
"/api/v1/ops/nodes/{node_code}/delivery-queue",
|
||||
payload["contracts_by_key"]["ops_agent_protocol"]["discovery_endpoints"],
|
||||
)
|
||||
self.assertIn(
|
||||
"/api/v1/ops/nodes/{node_code}/delivery-queue/records",
|
||||
payload["contracts_by_key"]["ops_agent_protocol"]["discovery_endpoints"],
|
||||
)
|
||||
self.assertIn(
|
||||
"/api/v1/ops/nodes/{node_code}/delivery-queue/flush",
|
||||
payload["contracts_by_key"]["ops_agent_protocol"]["discovery_endpoints"],
|
||||
)
|
||||
self.assertIn(
|
||||
"/api/v1/ops/nodes/{node_code}/delivery-queue/replay",
|
||||
payload["contracts_by_key"]["ops_agent_protocol"]["discovery_endpoints"],
|
||||
)
|
||||
self.assertIn(
|
||||
"/api/v1/ops/nodes/{node_code}/delivery-queue/records/{record_id}/replay",
|
||||
payload["contracts_by_key"]["ops_agent_protocol"]["discovery_endpoints"],
|
||||
)
|
||||
self.assertIn(
|
||||
"/api/v1/ops/nodes/{node_code}/delivery-queue/records/{record_id}/discard",
|
||||
payload["contracts_by_key"]["ops_agent_protocol"]["discovery_endpoints"],
|
||||
)
|
||||
self.assertEqual(
|
||||
"/api/v1/ops/releases/launchpad",
|
||||
payload["contracts_by_key"]["release_hub_contract"]["primary_endpoint"],
|
||||
)
|
||||
self.assertIn(
|
||||
"/api/v1/ops/codex-brief",
|
||||
payload["contracts_by_key"]["ops_driver_contract"]["discovery_endpoints"],
|
||||
)
|
||||
self.assertIn(
|
||||
"/api/v1/ops/driver-actions/preview",
|
||||
payload["contracts_by_key"]["ops_driver_contract"]["discovery_endpoints"],
|
||||
)
|
||||
self.assertIn(
|
||||
"/api/v1/ops/driver-actions/resolve",
|
||||
payload["contracts_by_key"]["ops_driver_contract"]["discovery_endpoints"],
|
||||
)
|
||||
self.assertIn(
|
||||
"/api/v1/ops/driver-actions/execute-resolved",
|
||||
payload["contracts_by_key"]["ops_driver_contract"]["discovery_endpoints"],
|
||||
)
|
||||
self.assertIn(
|
||||
"/api/v1/ops/codex-actions/resolve",
|
||||
payload["contracts_by_key"]["ops_driver_contract"]["discovery_endpoints"],
|
||||
)
|
||||
self.assertIn(
|
||||
"/api/v1/ops/codex-actions/execute",
|
||||
payload["contracts_by_key"]["ops_driver_contract"]["discovery_endpoints"],
|
||||
)
|
||||
self.assertEqual(
|
||||
"docs/schemas/ops_playbook_contract.md",
|
||||
payload["contracts_by_key"]["ops_playbook_contract"]["schema_doc_path"],
|
||||
)
|
||||
self.assertIn(
|
||||
"/api/v1/ops/playbooks/execute",
|
||||
payload["contracts_by_key"]["ops_playbook_contract"]["discovery_endpoints"],
|
||||
)
|
||||
self.assertIn(
|
||||
"/api/v1/ops/playbook-runs/{run_code}/events",
|
||||
payload["contracts_by_key"]["ops_playbook_contract"]["discovery_endpoints"],
|
||||
)
|
||||
self.assertEqual(
|
||||
"docs/schemas/ops_observability_contract.md",
|
||||
payload["contracts_by_key"]["ops_observability_contract"]["schema_doc_path"],
|
||||
)
|
||||
self.assertIn(
|
||||
"/api/v1/ops/inspection-overview",
|
||||
payload["contracts_by_key"]["ops_observability_contract"]["discovery_endpoints"],
|
||||
)
|
||||
self.assertIn(
|
||||
"/api/v1/ops/nodes/{node_code}/scene-log",
|
||||
payload["contracts_by_key"]["ops_observability_contract"]["discovery_endpoints"],
|
||||
)
|
||||
self.assertEqual(
|
||||
"docs/schemas/ops_stack_diagnosis_contract.md",
|
||||
payload["contracts_by_key"]["ops_stack_diagnosis_contract"]["schema_doc_path"],
|
||||
)
|
||||
self.assertIn(
|
||||
"/api/v1/ops/stack-diagnosis",
|
||||
payload["contracts_by_key"]["ops_stack_diagnosis_contract"]["discovery_endpoints"],
|
||||
)
|
||||
self.assertIn(
|
||||
"ops_driver_contract",
|
||||
payload["contracts_by_key"]["ops_stack_diagnosis_contract"]["related_contract_keys"],
|
||||
)
|
||||
self.assertEqual(
|
||||
"docs/schemas/ops_doctor_decision_contract.md",
|
||||
payload["contracts_by_key"]["ops_doctor_decision_contract"]["schema_doc_path"],
|
||||
)
|
||||
self.assertIn(
|
||||
"/api/v1/ops/doctor-decision",
|
||||
payload["contracts_by_key"]["ops_doctor_decision_contract"]["discovery_endpoints"],
|
||||
)
|
||||
self.assertIn(
|
||||
"ops_stack_diagnosis_contract",
|
||||
payload["contracts_by_key"]["ops_doctor_decision_contract"]["related_contract_keys"],
|
||||
)
|
||||
self.assertEqual(
|
||||
"docs/schemas/ops_go_live_signoff_contract.md",
|
||||
payload["contracts_by_key"]["ops_go_live_signoff_contract"]["schema_doc_path"],
|
||||
)
|
||||
self.assertIn(
|
||||
"/api/v1/ops/go-live-signoff",
|
||||
payload["contracts_by_key"]["ops_go_live_signoff_contract"]["discovery_endpoints"],
|
||||
)
|
||||
self.assertIn(
|
||||
"ops_stack_diagnosis_contract",
|
||||
payload["contracts_by_key"]["ops_go_live_signoff_contract"]["related_contract_keys"],
|
||||
)
|
||||
self.assertEqual(
|
||||
"docs/schemas/ops_go_live_bundle_contract.md",
|
||||
payload["contracts_by_key"]["ops_go_live_bundle_contract"]["schema_doc_path"],
|
||||
)
|
||||
self.assertIn(
|
||||
"/api/v1/ops/go-live-bundle",
|
||||
payload["contracts_by_key"]["ops_go_live_bundle_contract"]["discovery_endpoints"],
|
||||
)
|
||||
self.assertIn(
|
||||
"ops_go_live_signoff_contract",
|
||||
payload["contracts_by_key"]["ops_go_live_bundle_contract"]["related_contract_keys"],
|
||||
)
|
||||
self.assertEqual(
|
||||
"docs/schemas/ops_go_live_review_contract.md",
|
||||
payload["contracts_by_key"]["ops_go_live_review_contract"]["schema_doc_path"],
|
||||
)
|
||||
self.assertIn(
|
||||
"/api/v1/ops/go-live-review",
|
||||
payload["contracts_by_key"]["ops_go_live_review_contract"]["discovery_endpoints"],
|
||||
)
|
||||
self.assertIn(
|
||||
"ops_go_live_bundle_contract",
|
||||
payload["contracts_by_key"]["ops_go_live_review_contract"]["related_contract_keys"],
|
||||
)
|
||||
self.assertIn(
|
||||
"ops_doctor_decision_contract",
|
||||
payload["contracts_by_key"]["ops_go_live_review_contract"]["related_contract_keys"],
|
||||
)
|
||||
|
||||
def test_contract_detail_exposes_related_contracts(self) -> None:
|
||||
payload = get_ops_contract_detail("ops_stack_diagnosis_contract")
|
||||
|
||||
self.assertEqual("2026-04-18", payload["registry_version"])
|
||||
self.assertEqual("/api/v1/ops/contracts/ops_stack_diagnosis_contract", payload["detail_endpoint"])
|
||||
self.assertEqual("ops_stack_diagnosis_contract", payload["contract"]["key"])
|
||||
self.assertIn("ops_driver_contract", payload["related_contract_keys"])
|
||||
self.assertIn("release_hub_contract", payload["related_contract_keys"])
|
||||
self.assertEqual(
|
||||
"docs/schemas/ops_driver_contract.md",
|
||||
next(
|
||||
item["schema_doc_path"]
|
||||
for item in payload["related_contracts"]
|
||||
if item["key"] == "ops_driver_contract"
|
||||
),
|
||||
)
|
||||
self.assertEqual({}, get_ops_contract_detail("missing-contract"))
|
||||
|
||||
def test_capabilities_blueprint_and_runbook_expose_contract_registry(self) -> None:
|
||||
capabilities = get_ops_capabilities()
|
||||
contract_registry = capabilities["contract_registry"]
|
||||
|
||||
self.assertEqual("/api/v1/ops/contracts", contract_registry["endpoint"])
|
||||
self.assertEqual("/api/v1/ops/contracts/{contract_key}", contract_registry["detail_endpoint_pattern"])
|
||||
self.assertEqual(11, contract_registry["contracts_total"])
|
||||
self.assertIn("ops_job_contract", contract_registry["contract_keys"])
|
||||
self.assertIn("ops_driver_contract", contract_registry["contract_keys"])
|
||||
self.assertIn("ops_playbook_contract", contract_registry["contract_keys"])
|
||||
self.assertIn("ops_observability_contract", contract_registry["contract_keys"])
|
||||
self.assertIn("ops_stack_diagnosis_contract", contract_registry["contract_keys"])
|
||||
self.assertIn("ops_doctor_decision_contract", contract_registry["contract_keys"])
|
||||
self.assertIn("ops_go_live_signoff_contract", contract_registry["contract_keys"])
|
||||
self.assertIn("ops_go_live_bundle_contract", contract_registry["contract_keys"])
|
||||
self.assertIn("ops_go_live_review_contract", contract_registry["contract_keys"])
|
||||
remote_log_capability = next(
|
||||
item for item in capabilities["capabilities"] if item["key"] == "remote_log_sync"
|
||||
)
|
||||
self.assertEqual("ready", remote_log_capability["current_state"])
|
||||
self.assertEqual(
|
||||
"docs/schemas/release_hub_contract.md",
|
||||
contract_registry["schema_docs"]["release_hub_contract"],
|
||||
)
|
||||
self.assertEqual(
|
||||
"docs/schemas/ops_playbook_contract.md",
|
||||
contract_registry["schema_docs"]["ops_playbook_contract"],
|
||||
)
|
||||
self.assertEqual(
|
||||
"docs/schemas/ops_job_contract.md",
|
||||
contract_registry["schema_docs"]["ops_job_contract"],
|
||||
)
|
||||
self.assertEqual(
|
||||
"docs/schemas/ops_observability_contract.md",
|
||||
contract_registry["schema_docs"]["ops_observability_contract"],
|
||||
)
|
||||
self.assertEqual(
|
||||
"docs/schemas/ops_stack_diagnosis_contract.md",
|
||||
contract_registry["schema_docs"]["ops_stack_diagnosis_contract"],
|
||||
)
|
||||
self.assertEqual(
|
||||
"docs/schemas/ops_doctor_decision_contract.md",
|
||||
contract_registry["schema_docs"]["ops_doctor_decision_contract"],
|
||||
)
|
||||
self.assertEqual(
|
||||
"docs/schemas/ops_go_live_signoff_contract.md",
|
||||
contract_registry["schema_docs"]["ops_go_live_signoff_contract"],
|
||||
)
|
||||
self.assertEqual(
|
||||
"docs/schemas/ops_go_live_bundle_contract.md",
|
||||
contract_registry["schema_docs"]["ops_go_live_bundle_contract"],
|
||||
)
|
||||
self.assertEqual(
|
||||
"docs/schemas/ops_go_live_review_contract.md",
|
||||
contract_registry["schema_docs"]["ops_go_live_review_contract"],
|
||||
)
|
||||
|
||||
blueprint = get_ops_blueprint()
|
||||
self.assertEqual("2026-04-18", blueprint["contract_registry"]["registry_version"])
|
||||
self.assertEqual(11, blueprint["contract_registry"]["contracts_total"])
|
||||
|
||||
with patch("app.services.ops_service.get_runtime_status") as mock_runtime_status, patch(
|
||||
"app.services.ops_service.list_managed_nodes_with_agent_state"
|
||||
) as mock_list_managed_nodes_with_agent_state, patch(
|
||||
"app.services.ops_service.get_release_launchpad"
|
||||
) as mock_get_release_launchpad, patch(
|
||||
"app.services.ops_service._attach_ops_runbook_sequence_resolutions"
|
||||
) as mock_attach_sequences:
|
||||
mock_runtime_status.return_value = {
|
||||
"readiness": {"status": "ready", "summary": "ok"},
|
||||
"worker": {"running": True},
|
||||
"sync_agent": {"running": True},
|
||||
"detect": {},
|
||||
}
|
||||
mock_list_managed_nodes_with_agent_state.return_value = {"nodes": [], "summary": {}}
|
||||
mock_get_release_launchpad.return_value = {}
|
||||
mock_attach_sequences.return_value = []
|
||||
runbook = get_ops_runbook()
|
||||
|
||||
self.assertIn("GET /api/v1/ops/contracts", runbook["fast_checks"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user