netx/packages/netx-topology-mcp/tests/test_pin_beam_soft_petals.py
oliver 79f929de0c Converge netx-topology MCP to dual-unit drain + orbit/polish mainline.
Trim public tools to 14, expose only kept layout actions/recipes, merge Fabric queries, drop createTopologyView/listTopologyViews, and align skill/docs with the live catalog.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-09 23:18:53 +08:00

84 lines
3.4 KiB
Python

"""Tests for stage-2 pin_beam + soft_petals actions."""
from __future__ import annotations
from netx_topology_mcp.layout_ops import LayoutParams, build_state_from_nodes_edges
from netx_topology_mcp.layout_ops.pin_beam import pin_beam_rigid
from netx_topology_mcp.layout_ops.soft_petals import soft_petals_greedy
from netx_topology_mcp.layout_tool import ACTIONS, run_layout_on_graph
def _starish():
"""Two cores + two AN hubs each with a few stubs (positions intentionally messy)."""
nodes = [
{"fabric_node_id": "c0", "name": "X-CN0-Y", "x": 100, "y": 500},
{"fabric_node_id": "c1", "name": "X-CN1-Y", "x": 900, "y": 100},
{"fabric_node_id": "a0", "name": "X-AN0-Y", "x": 200, "y": 800},
{"fabric_node_id": "a1", "name": "X-AN1-Y", "x": 800, "y": 50},
{"fabric_node_id": "e0", "name": "X-EN0-Y", "x": 50, "y": 900},
{"fabric_node_id": "e1", "name": "X-EN1-Y", "x": 350, "y": 950},
{"fabric_node_id": "e2", "name": "X-EN2-Y", "x": 750, "y": 20},
{"fabric_node_id": "e3", "name": "X-EN3-Y", "x": 950, "y": 40},
{"fabric_node_id": "e4", "name": "X-EN4-Y", "x": 250, "y": 700},
{"fabric_node_id": "e5", "name": "X-EN5-Y", "x": 850, "y": 200},
]
edges = [
{"a_node_id": "c0", "b_node_id": "c1"},
{"a_node_id": "c0", "b_node_id": "a0"},
{"a_node_id": "c1", "b_node_id": "a1"},
{"a_node_id": "a0", "b_node_id": "e0"},
{"a_node_id": "a0", "b_node_id": "e1"},
{"a_node_id": "a0", "b_node_id": "e4"},
{"a_node_id": "a1", "b_node_id": "e2"},
{"a_node_id": "a1", "b_node_id": "e3"},
{"a_node_id": "a1", "b_node_id": "e5"},
{"a_node_id": "e0", "b_node_id": "e4"},
{"a_node_id": "e2", "b_node_id": "e5"},
]
return nodes, edges
def test_actions_include_pin_and_petals() -> None:
assert "pin_beam" not in ACTIONS
assert "soft_petals" not in ACTIONS
def test_pin_beam_aligns_cores() -> None:
nodes, edges = _starish()
st = build_state_from_nodes_edges(nodes, edges)
st.positions = {n["fabric_node_id"]: (float(n["x"]), float(n["y"])) for n in nodes}
op = pin_beam_rigid(st, LayoutParams())
assert op.op == "pin_beam"
ys = [op.state.positions["c0"][1], op.state.positions["c1"][1]]
# Either accepted with near-collinear cores, or refused (no_improvement) keeping input
if op.params.get("accepted"):
assert abs(ys[0] - ys[1]) < 5.0
else:
assert op.note in {"no_improvement", "need_ge_2_cores"}
def test_soft_petals_runs() -> None:
nodes, edges = _starish()
st = build_state_from_nodes_edges(nodes, edges)
st.positions = {n["fabric_node_id"]: (float(n["x"]), float(n["y"])) for n in nodes}
op = soft_petals_greedy(st, LayoutParams())
assert op.op == "soft_petals"
assert "accepted_n" in op.params
def test_run_layout_on_graph_pin_beam_unpublished() -> None:
nodes, edges = _starish()
try:
run_layout_on_graph(nodes, edges, action="pin_beam")
raise AssertionError("pin_beam should be unpublished")
except ValueError as e:
assert "unknown_action" in str(e)
def test_run_layout_on_graph_soft_petals_unpublished() -> None:
nodes, edges = _starish()
try:
run_layout_on_graph(nodes, edges, action="soft_petals")
raise AssertionError("soft_petals should be unpublished")
except ValueError as e:
assert "unknown_action" in str(e)