mirror of
https://github.com/hansjone/netx.git
synced 2026-10-19 06:53:19 +08:00
MCP: add bizMonitor server, align ops/topology tools, harden CLI exec
- New packages/netx-bizmonitor-mcp: 8 read-only cutover/biz-state tools mirroring the netxops bizMonitor group. - Align netx-mcp (15) and netx-topology-mcp (16) with backend HTTP contracts; surface partial batch failures as tool errors. - CLI hard gate: single-line show/display only; exec job IDs, path containment, owner and ne:exec checks. - Cisco IOSv collection: stable prompt/echo handling over CR. - Remove legacy netx_api/mcp and mcp_server.py; add mcp.all.json; sync skills and docs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
d8da7acb7d
commit
9085cac742
63 changed files with 3251 additions and 1092 deletions
|
|
@ -16,6 +16,60 @@ from netx_topology_mcp.layout_jobs import (
|
|||
)
|
||||
|
||||
|
||||
def test_terminal_job_survives_cache_reset_and_reports_failure(tmp_path) -> None:
|
||||
from netx_topology_mcp import layout_jobs as lj
|
||||
|
||||
lj._write_job_disk({"job_id": "persisted", "status": "running", "started_at": time.time()})
|
||||
lj.finish_job("persisted", result={"ok": False, "error": "backend_write_failed", "applied": False})
|
||||
lj._JOBS.clear()
|
||||
out = _layout_topology_view({"action": "job_status", "params": {"job_id": "persisted"}})
|
||||
assert out["ok"] is False
|
||||
assert out["status"] == "error"
|
||||
assert out["error"] == "backend_write_failed"
|
||||
assert out["result"]["applied"] is False
|
||||
|
||||
|
||||
def test_spawn_failure_is_persisted_as_terminal_error() -> None:
|
||||
from netx_topology_mcp import layout_jobs as lj
|
||||
|
||||
with patch.object(lj.subprocess, "Popen", side_effect=OSError("spawn denied")):
|
||||
jid = start_job(action="layout", view_id="v", tool_args={"view_id": "v"})
|
||||
lj._JOBS.clear()
|
||||
out = _layout_topology_view({"action": "job_status", "params": {"job_id": jid}})
|
||||
assert out["ok"] is False
|
||||
assert out["status"] == "error"
|
||||
assert out["error"].startswith("worker_start_failed:")
|
||||
assert not lj._args_path(jid).exists()
|
||||
|
||||
|
||||
def test_fast_worker_terminal_progress_is_not_overwritten() -> None:
|
||||
from types import SimpleNamespace
|
||||
from netx_topology_mcp import layout_jobs as lj
|
||||
|
||||
def spawn(argv, **kwargs):
|
||||
jid = argv[-1]
|
||||
lj.finish_job(jid, result={"ok": True, "applied": True})
|
||||
return SimpleNamespace(pid=123)
|
||||
|
||||
with patch.object(lj.subprocess, "Popen", side_effect=spawn):
|
||||
jid = start_job(action="layout", view_id="v", tool_args={"view_id": "v"})
|
||||
out = lj.job_public(jid)
|
||||
assert out["status"] == "done"
|
||||
assert out["progress"]["phase"] == "done"
|
||||
assert out["progress"]["pct"] == 100.0
|
||||
|
||||
|
||||
def test_pruning_preserves_active_jobs() -> None:
|
||||
from netx_topology_mcp import layout_jobs as lj
|
||||
|
||||
lj._write_job_disk({"job_id": "active", "status": "running"})
|
||||
lj._write_job_disk({"job_id": "completed", "status": "done"})
|
||||
with patch.object(lj, "_MAX_JOBS", 1):
|
||||
lj._prune_disk()
|
||||
assert lj._read_job_disk("active") is not None
|
||||
assert lj._read_job_disk("completed") is None
|
||||
|
||||
|
||||
def test_start_job_and_poll() -> None:
|
||||
def runner() -> dict:
|
||||
report_progress("mid", pct=50.0, message="halfway", step=1, total_steps=2)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue