Übersicht: Interface-Klick hebt transitiv alle zusammenhängenden Linien hervor

OverviewGraph.highlightedNodeIDs(startingAt:) macht für Interface-Knoten
eine BFS über alle Kanten statt nur 1-Hop-Matching, damit z.B. "DHCP-Server
-> Pool" oder "IP-Adresse -> DHCP-Netzwerk" mit sichtbar werden. Andere
Knotentypen bleiben unverändert bei 1-Hop. Logik isoliert unit-getestet
(GUI selbst nicht automatisiert klickbar). 61 Tests grün.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YDmUd93KxsYGr2kLTotWnG
This commit is contained in:
Kay
2026-09-15 19:58:05 +02:00
co-authored by Claude Sonnet 5
parent ba6603ba64
commit 9b26677dc2
5 changed files with 123 additions and 6 deletions
@@ -173,4 +173,40 @@ final class OverviewGraphTests: XCTestCase {
let route = graph.nodes.first { $0.id == "route:0" }
XCTAssertNil(route?.editTarget)
}
/// Clicking an Interface should highlight every line that traces back to it, not just the
/// ones directly touching it e.g. a DHCP server's own pool, or an IP address's DHCP-network
/// options, sit a second hop away from the interface itself. Confirmed scope (2026-09-15):
/// only interfaces expand this way every other node kind stays one-hop-only.
func testHighlightedNodeIDsExpandsTransitivelyOnlyForInterfaces() {
let graph = OverviewViewModel.buildGraph(
interfaces: [item(["name": "ether1", "type": "ether"])],
bridgePorts: [], vlans: [], wireguardPeers: [],
addresses: [item(["address": "192.168.88.1/24", "interface": "ether1", "network": "192.168.88.0"])],
pools: [item(["name": "pool1", "ranges": "192.168.88.10-192.168.88.254"])],
dhcpServers: [item(["name": "dhcp1", "interface": "ether1", "address-pool": "pool1"])],
dhcpNetworks: [item(["address": "192.168.88.0/24", "gateway": "192.168.88.1"])],
dhcpClients: [], routes: [], filterRules: [], natRules: [], addressLists: []
)
let interfaceID = "Interfaces:ether1"
let addressID = "IP-Adressen:192.168.88.1/24"
let dhcpServerID = "Pools & DHCP:dhcp:dhcp1"
let poolID = "Pools & DHCP:pool:pool1"
let dhcpNetworkID = "Pools & DHCP:dhcpnet:192.168.88.0/24"
// Sanity: the pool and the DHCP-network options really are two hops away from the
// interface (no direct edge to either) otherwise this test wouldn't distinguish
// transitive-closure behavior from plain one-hop matching.
XCTAssertFalse(graph.edges.contains { $0.from == interfaceID && $0.to == poolID })
XCTAssertFalse(graph.edges.contains { $0.from == interfaceID && $0.to == dhcpNetworkID })
let fromInterface = graph.highlightedNodeIDs(startingAt: interfaceID)
XCTAssertEqual(fromInterface, [interfaceID, addressID, dhcpServerID, poolID, dhcpNetworkID])
// A non-interface node (e.g. the pool) stays one-hop-only: just itself, regardless of
// what it's connected to.
XCTAssertEqual(graph.highlightedNodeIDs(startingAt: poolID), [poolID])
XCTAssertEqual(graph.highlightedNodeIDs(startingAt: addressID), [addressID])
}
}