mirror of
https://github.com/ipnet-mesh/meshcore-hub.git
synced 2026-07-28 20:42:36 +02:00
fix(routes): clearing observers in the edit modal now persists
The route edit modal builds its PUT body with a ternary that collapses
an empty observer list to null:
observer_public_keys: observerPublicKeys.length > 0 ? observerPublicKeys : null,
Pydantic parses null as None, and the PUT handler's guard
if body.observer_public_keys is not None:
_sync_observers(session, route, observer_nodes)
skips the sync entirely when the field is None. So removing all
observers in the modal sent null -> no DB change. Adding observers
worked because a non-empty array passed the guard and _sync_observers
deleted + recreated.
The None-means-skip semantic is correct for true partial updates, so
the fix is on the frontend: the edit modal is a full-form PUT, so it
must always send the array. When empty, it sends [], which Pydantic
parses as [] (not None), the guard passes, and _sync_observers deletes
every existing RouteObserver row.
Tests: add test_update_clear_observers_with_empty_list next to the
existing test_update_observers as a regression guard. It seeds one
observer via PUT, then PUTs observer_public_keys: [] and asserts both
the response and a fresh GET come back with an empty list.
This commit is contained in:
@@ -809,7 +809,7 @@ export async function render(container, params, router) {
|
||||
enabled: enabledEl.checked,
|
||||
reversible: reversibleEl.checked,
|
||||
node_public_keys: nodePublicKeys,
|
||||
observer_public_keys: observerPublicKeys.length > 0 ? observerPublicKeys : null,
|
||||
observer_public_keys: observerPublicKeys,
|
||||
};
|
||||
|
||||
const clearVal = clearEl.value.trim();
|
||||
|
||||
@@ -995,6 +995,42 @@ class TestUpdateRouteFields:
|
||||
assert len(data["route_observers"]) == 1
|
||||
assert data["route_observers"][0]["public_key"] == obs.public_key
|
||||
|
||||
def test_update_clear_observers_with_empty_list(
|
||||
self, client_no_auth, api_db_session
|
||||
):
|
||||
"""Sending ``observer_public_keys: []`` must clear existing observers.
|
||||
|
||||
Regression guard for a frontend bug where the edit modal sent
|
||||
``null`` instead of ``[]`` when the user removed all observers,
|
||||
causing the backend's ``is not None`` guard to skip the sync.
|
||||
"""
|
||||
route = self._make_route(api_db_session)
|
||||
obs = _make_node(api_db_session, "c" * 64, "Observer")
|
||||
api_db_session.commit()
|
||||
|
||||
# Seed one observer.
|
||||
client_no_auth.put(
|
||||
f"/api/v1/routes/{route.id}",
|
||||
json={"observer_public_keys": [obs.public_key]},
|
||||
headers={"X-User-Roles": "admin"},
|
||||
)
|
||||
|
||||
# Clear with an explicit empty list.
|
||||
resp = client_no_auth.put(
|
||||
f"/api/v1/routes/{route.id}",
|
||||
json={"observer_public_keys": []},
|
||||
headers={"X-User-Roles": "admin"},
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()["route_observers"] == []
|
||||
|
||||
# Confirm persistence via a fresh GET.
|
||||
detail = client_no_auth.get(
|
||||
f"/api/v1/routes/{route.id}",
|
||||
headers={"X-User-Roles": "admin"},
|
||||
).json()
|
||||
assert detail["route_observers"] == []
|
||||
|
||||
def test_update_unresolved_path_nodes_400(self, client_no_auth, api_db_session):
|
||||
route = self._make_route(api_db_session)
|
||||
api_db_session.commit()
|
||||
|
||||
Reference in New Issue
Block a user