Files
getDomain/domain-api/tests/test_ops_job_service.py
Your Name 7cbde2aa78 d
2026-04-22 14:13:21 +08:00

431 lines
15 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import json
import unittest
from datetime import datetime
from unittest.mock import MagicMock, patch
from app.core.config import settings
from app.core.db import get_db
from app.services.ops_job_service import (
_execute_control_plane_job,
_serialize_job_row,
create_ops_job,
dispatch_ops_job,
list_managed_nodes,
upsert_managed_node,
)
class OpsJobServiceTests(unittest.TestCase):
def test_serialize_job_row_adds_execution_mode_label(self) -> None:
row = (
9,
"ops-2026041801-abcd12",
"logs.collect",
"node",
"mainland-worker-01",
"queued",
"remote-agent",
"api",
json.dumps({"service_name": "domaincheck-worker", "lines": 120}, ensure_ascii=False),
json.dumps({}, ensure_ascii=False),
json.dumps({}, ensure_ascii=False),
"",
datetime(2026, 4, 18, 10, 0, 0),
None,
None,
datetime(2026, 4, 18, 10, 0, 0),
"low",
False,
"approved",
"",
None,
"",
"",
None,
json.dumps({}, ensure_ascii=False),
json.dumps({}, ensure_ascii=False),
None,
)
job = _serialize_job_row(row)
self.assertEqual("remote-agent", job["execution_mode"])
self.assertEqual("远端 Agent", job["execution_mode_label"])
self.assertEqual("排队中", job["status_label"])
self.assertEqual("已审批", job["approval_status_label"])
self.assertEqual(["mainland-worker-01"], job["target_node_codes"])
self.assertEqual(0, job["steps_total"])
self.assertFalse(job["steps_loaded"])
self.assertFalse(job["is_compact"])
self.assertEqual("ops_job", job["focus_ref"]["kind"])
self.assertEqual(9, job["focus_ref"]["job_id"])
self.assertIn("目标节点 mainland-worker-01", job["summary_text"])
def test_serialize_job_row_counts_loaded_steps(self) -> None:
row = (
10,
"ops-2026041801-efgh34",
"diagnostics.collect",
"node",
"mainland-worker-02",
"running",
"ssh",
"web-ui",
json.dumps({}, ensure_ascii=False),
json.dumps({}, ensure_ascii=False),
json.dumps({}, ensure_ascii=False),
"",
datetime(2026, 4, 18, 10, 5, 0),
datetime(2026, 4, 18, 10, 5, 2),
None,
datetime(2026, 4, 18, 10, 5, 3),
"medium",
True,
"approved",
"admin",
datetime(2026, 4, 18, 10, 5, 1),
"",
"",
datetime(2026, 4, 18, 10, 5, 2),
json.dumps({}, ensure_ascii=False),
json.dumps({}, ensure_ascii=False),
None,
)
job = _serialize_job_row(
row,
steps=[
{"id": 1, "status": "success"},
{"id": 2, "status": "running"},
{"id": 3, "status": "failed"},
],
)
self.assertEqual("执行中", job["status_label"])
self.assertEqual("已审批", job["approval_status_label"])
self.assertEqual(3, job["steps_total"])
self.assertEqual(1, job["steps_running"])
self.assertEqual(1, job["steps_success"])
self.assertEqual(1, job["steps_failed"])
self.assertEqual(2, job["steps_terminal"])
self.assertTrue(job["steps_loaded"])
self.assertIn("mainland-worker-02", job["summary_text"])
def test_upsert_managed_node_supports_ssh_entry_and_secret_flags(self) -> None:
node_code = "test-managed-node-secret"
ok, message, data = upsert_managed_node(
{
"node_code": node_code,
"region": "mainland",
"role": "worker",
"ssh_entry": "121.204.244.188 root <demo-password>",
"is_enabled": True,
}
)
self.assertTrue(ok, message)
node = data["node"]
self.assertEqual("121.204.244.188", node["ssh_host"])
self.assertEqual("root", node["ssh_user"])
self.assertEqual(22, node["ssh_port"])
self.assertEqual("password", node["auth_mode"])
self.assertTrue(node["ssh_password_configured"])
managed = next(item for item in list_managed_nodes() if item["node_code"] == node_code)
self.assertTrue(managed["ssh_password_configured"])
with get_db() as conn:
with conn.cursor() as cur:
cur.execute(
"SELECT ssh_password FROM ops_managed_node_secrets WHERE node_code = %s",
(node_code,),
)
row = cur.fetchone()
self.assertIsNotNone(row)
self.assertEqual("demo-password", str(row[0] or ""))
@patch("app.services.ops_job_service.list_managed_nodes")
@patch("app.services.ops_agent_service.build_node_agent_bootstrap_plan")
def test_execute_control_plane_bootstrap_uses_managed_node_defaults(
self,
mock_build_node_agent_bootstrap_plan,
mock_list_managed_nodes,
) -> None:
mock_list_managed_nodes.return_value = [
{
"node_code": "mainland-worker-02",
"region": "mainland",
"role": "worker",
}
]
mock_build_node_agent_bootstrap_plan.return_value = (
True,
"节点 Agent 接入方案已生成",
{
"token_preview": "agent-...1234",
"bootstrap_plan": {
"bootstrap_script_name": "bootstrap-node-agent-mainland-worker-02.sh",
},
},
)
ok, message, data = _execute_control_plane_job(
"node.bootstrap",
target_node_code="mainland-worker-02",
payload={
"root_dir": "/opt/domaincheck",
"expires_in_hours": 12,
},
requested_by="unit-test",
metadata={"source": "test"},
)
self.assertTrue(ok)
self.assertEqual("节点 Agent 接入方案已生成", message)
self.assertEqual("control-plane", data["execution_mode"])
self.assertEqual("mainland", data["node_region"])
self.assertEqual("worker", data["node_role"])
mock_build_node_agent_bootstrap_plan.assert_called_once_with(
node_code="mainland-worker-02",
node_region="mainland",
node_role="worker",
issued_by="unit-test",
expires_in_hours=12,
control_plane_base_url="",
root_dir="/opt/domaincheck",
metadata={
"source": "test",
"issued_from": "ops-job/control-plane",
"target_node_code": "mainland-worker-02",
"node_region": "mainland",
"node_role": "worker",
},
)
@patch("app.services.ops_job_service.get_db")
def test_upsert_managed_node_preserves_remote_fields_when_sync_payload_omits_them(self, mock_get_db) -> None:
existing_metadata = {"operator_note": "keep-me"}
merged_metadata = {"operator_note": "keep-me", "cluster_status": "online"}
existing_row = (
"mainland-worker-01",
"mainland",
"worker",
"Mainland Worker 01",
"121.204.244.248",
22,
"root",
"key",
"stable",
False,
json.dumps(existing_metadata, ensure_ascii=False),
datetime(2026, 4, 18, 1, 0, 0),
None,
datetime(2026, 4, 18, 1, 5, 0),
)
returned_row = (
"mainland-worker-01",
"mainland",
"worker",
"S244-248",
"121.204.244.248",
22,
"root",
"key",
"stable",
False,
json.dumps(merged_metadata, ensure_ascii=False),
datetime(2026, 4, 18, 1, 0, 0),
None,
datetime(2026, 4, 18, 1, 6, 0),
)
conn = MagicMock()
cursor = MagicMock()
db_ctx = MagicMock()
cursor_ctx = MagicMock()
db_ctx.__enter__.return_value = conn
db_ctx.__exit__.return_value = False
cursor_ctx.__enter__.return_value = cursor
cursor_ctx.__exit__.return_value = False
conn.cursor.return_value = cursor_ctx
mock_get_db.return_value = db_ctx
cursor.fetchone.side_effect = [existing_row, returned_row]
ok, message, data = upsert_managed_node(
{
"node_code": "mainland-worker-01",
"region": "mainland",
"role": "worker",
"title": "S244-248",
"metadata": {
"cluster_status": "online",
},
}
)
self.assertTrue(ok)
self.assertEqual("托管节点已保存", message)
node = data["node"]
self.assertEqual("121.204.244.248", node["ssh_host"])
self.assertEqual("root", node["ssh_user"])
self.assertFalse(node["is_enabled"])
self.assertEqual("keep-me", node["metadata"]["operator_note"])
self.assertEqual("online", node["metadata"]["cluster_status"])
insert_call = next(
call
for call in cursor.execute.call_args_list
if "INSERT INTO ops_managed_nodes" in str(call.args[0])
)
insert_params = insert_call.args[1]
self.assertEqual("121.204.244.248", insert_params[4])
self.assertEqual(22, insert_params[5])
self.assertEqual("root", insert_params[6])
self.assertFalse(insert_params[9])
self.assertEqual(merged_metadata, json.loads(insert_params[10]))
@patch("app.services.ops_agent_service.append_ops_job_event")
@patch("app.services.ops_job_service._execute_ssh_job_record")
@patch("app.services.ops_job_service.get_db")
def test_dispatch_ops_job_ssh_executes_via_ssh_record(
self,
mock_get_db,
mock_execute_ssh_job_record,
mock_append_ops_job_event,
) -> None:
conn = MagicMock()
cursor = MagicMock()
db_ctx = MagicMock()
cursor_ctx = MagicMock()
db_ctx.__enter__.return_value = conn
db_ctx.__exit__.return_value = False
cursor_ctx.__enter__.return_value = cursor
cursor_ctx.__exit__.return_value = False
conn.cursor.return_value = cursor_ctx
mock_get_db.return_value = db_ctx
cursor.fetchone.return_value = (
9,
"ops-2026041801-abcd12",
"logs.collect",
"node",
"mainland-worker-01",
"queued",
"ssh",
"api",
json.dumps({"service_name": "domaincheck-worker", "lines": 120}, ensure_ascii=False),
json.dumps({}, ensure_ascii=False),
json.dumps({}, ensure_ascii=False),
"",
datetime(2026, 4, 18, 10, 0, 0),
None,
None,
datetime(2026, 4, 18, 10, 0, 0),
"low",
False,
"approved",
"",
None,
"",
"",
None,
json.dumps({}, ensure_ascii=False),
json.dumps({}, ensure_ascii=False),
None,
)
mock_execute_ssh_job_record.return_value = (
True,
"worker logs collected",
{"job": {"id": 9, "status": "success"}},
)
ok, message, data = dispatch_ops_job(9)
self.assertTrue(ok)
self.assertEqual("已按 SSH 执行派发worker logs collected", message)
self.assertEqual("success", data["job"]["status"])
mock_execute_ssh_job_record.assert_called_once_with(9)
mock_append_ops_job_event.assert_called_once()
event_kwargs = mock_append_ops_job_event.call_args.kwargs
self.assertEqual("job_dispatch_requested", event_kwargs["event_type"])
self.assertEqual("mainland-worker-01", event_kwargs["node_code"])
self.assertEqual("ssh", event_kwargs["payload"]["execution_mode"])
@patch("app.services.ops_agent_service.append_ops_job_event")
@patch("app.services.ops_job_service.get_ops_job")
@patch("app.services.ops_job_service._execute_local_job")
@patch("app.services.ops_policy_service.preview_ops_job_policy")
@patch("app.services.ops_job_service.get_db")
def test_create_ops_job_auto_uses_local_runtime_for_local_structured_action(
self,
mock_get_db,
mock_preview_ops_job_policy,
mock_execute_local_job,
mock_get_ops_job,
mock_append_ops_job_event,
) -> None:
conn = MagicMock()
cursor = MagicMock()
db_ctx = MagicMock()
cursor_ctx = MagicMock()
db_ctx.__enter__.return_value = conn
db_ctx.__exit__.return_value = False
cursor_ctx.__enter__.return_value = cursor
cursor_ctx.__exit__.return_value = False
conn.cursor.return_value = cursor_ctx
mock_get_db.return_value = db_ctx
cursor.fetchone.return_value = (42,)
mock_preview_ops_job_policy.return_value = {
"risk_level": "low",
"approval_required": False,
"blocked": False,
"blocking_reasons": [],
}
mock_execute_local_job.return_value = (
True,
"worker logs collected",
{"executor": "local-runtime"},
)
mock_get_ops_job.return_value = {
"id": 42,
"status": "success",
"execution_mode": "local-runtime",
}
ok, message, data = create_ops_job(
{
"action": "logs.collect",
"target_node_code": settings.node_code,
"payload": {
"service_name": "domaincheck-worker",
"lines": 120,
},
"requested_by": "unit-test",
}
)
self.assertTrue(ok)
self.assertEqual("worker logs collected", message)
self.assertTrue(data["executed_immediately"])
self.assertEqual("local-runtime", data["job"]["execution_mode"])
mock_execute_local_job.assert_called_once_with(
"logs.collect",
{
"service_name": "domaincheck-worker",
"lines": 120,
},
)
insert_call = next(
call
for call in cursor.execute.call_args_list
if "INSERT INTO ops_jobs" in str(call.args[0])
)
insert_params = insert_call.args[1]
self.assertEqual("local-runtime", insert_params[5])
mock_append_ops_job_event.assert_called_once()
if __name__ == "__main__":
unittest.main()