feat(mutator,web): add_decky op — create-and-attach in one mutation
apply_attach_decky requires an existing decky, so the MazeNET editor had no way to grow a live topology: creating a new decky on active topologies 409'd on the direct-CRUD createDecky call. - Backend: new apply_add_decky that creates the decky row + its home-LAN edge atomically, auto-allocating an IP if none pinned. Post-apply validation still runs. Added to DISPATCH + _MUTATION_OPS Literal + CLI help text. - Tests: 3 new ops tests (happy path, duplicate-name rejection, missing-LAN rejection) plus dispatch coverage update. - Frontend: useTopologyEditor gains addDeckyToLan() composite. Pending routes through createDecky + attachEdge as before; active routes through a single add_decky enqueue. MazeNET.tsx drag-archetype, duplicate, DMZ-gateway, and ctx-menu add-decky paths all use the composite so active topologies stop 409'ing on new-decky drops.
This commit is contained in:
@@ -9,7 +9,12 @@ import pytest
|
||||
from decnet.bus import topics as _topics
|
||||
from decnet.bus.fake import FakeBus
|
||||
from decnet.mutator import engine as _engine
|
||||
from decnet.mutator.ops import MutationError, apply_add_lan, apply_update_decky
|
||||
from decnet.mutator.ops import (
|
||||
MutationError,
|
||||
apply_add_decky,
|
||||
apply_add_lan,
|
||||
apply_update_decky,
|
||||
)
|
||||
from decnet.topology.config import TopologyConfig
|
||||
from decnet.topology.generator import generate
|
||||
from decnet.topology.persistence import persist, transition_status
|
||||
@@ -158,6 +163,55 @@ async def test_apply_add_lan_persists(repo):
|
||||
assert "LAN-MUT" in names
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_apply_add_decky_creates_and_attaches(repo):
|
||||
"""add_decky creates a new decky row + home-LAN edge in one op."""
|
||||
tid = await _make_active(repo)
|
||||
lans = await repo.list_lans_for_topology(tid)
|
||||
home_lan = lans[0]
|
||||
|
||||
await apply_add_decky(
|
||||
repo, tid,
|
||||
{
|
||||
"name": "new-decky-mut",
|
||||
"lan": home_lan["name"],
|
||||
"services": ["ssh"],
|
||||
"archetype": "deaddeck",
|
||||
},
|
||||
)
|
||||
|
||||
deckies = await repo.list_topology_deckies(tid)
|
||||
new = next((d for d in deckies if d["decky_config"]["name"] == "new-decky-mut"), None)
|
||||
assert new is not None
|
||||
assert new["services"] == ["ssh"]
|
||||
assert new["decky_config"]["archetype"] == "deaddeck"
|
||||
assert home_lan["name"] in new["decky_config"]["ips_by_lan"]
|
||||
|
||||
edges = await repo.list_topology_edges(tid)
|
||||
assert any(e["decky_uuid"] == new["uuid"] and e["lan_id"] == home_lan["id"] for e in edges)
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_apply_add_decky_rejects_duplicate_name(repo):
|
||||
tid = await _make_active(repo)
|
||||
lans = await repo.list_lans_for_topology(tid)
|
||||
existing = (await repo.list_topology_deckies(tid))[0]
|
||||
with pytest.raises(MutationError, match="already exists"):
|
||||
await apply_add_decky(
|
||||
repo, tid,
|
||||
{"name": existing["decky_config"]["name"], "lan": lans[0]["name"]},
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_apply_add_decky_rejects_missing_lan(repo):
|
||||
tid = await _make_active(repo)
|
||||
with pytest.raises(MutationError, match="not found"):
|
||||
await apply_add_decky(
|
||||
repo, tid, {"name": "orphan-decky", "lan": "nonexistent-lan"},
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_apply_update_decky_replaces_services(repo):
|
||||
"""Top-level ``services`` payload key replaces the decky's services list."""
|
||||
@@ -287,8 +341,9 @@ def test_ops_payload_shape_docstring_present():
|
||||
from decnet.mutator.ops import DISPATCH
|
||||
|
||||
assert set(DISPATCH) == {
|
||||
"add_lan", "remove_lan", "attach_decky", "detach_decky",
|
||||
"remove_decky", "update_decky", "update_lan",
|
||||
"add_lan", "remove_lan",
|
||||
"add_decky", "attach_decky", "detach_decky", "remove_decky",
|
||||
"update_decky", "update_lan",
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user