forked from kay/RouterOS
Eine dynamische/verbundene Route (automatisch angelegt durch eine IP-Adresse auf einem Interface) scheiterte beim Bearbeiten mit "no such item (4)" — bestätigt per /ip route print detail: D-Flag, distance=0. RouterOS' "dynamic"-Flag steht nicht zuverlässig in print terse (dasselbe Problem schon bei DHCP-Leases dokumentiert), aber distance=0 ist ein verlässliches Signal, da keine echte statische Route das je haben kann. Solche Routen bekommen jetzt kein editTarget mehr — ihre .id ist ohnehin nicht stabil, RouterOS kann sie jederzeit neu anlegen. 59 Unit-Tests grün (neuer Test OverviewGraphTests. testDynamicRouteHasNoEditTarget). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YDmUd93KxsYGr2kLTotWnG
154 lines
8.0 KiB
Swift
154 lines
8.0 KiB
Swift
import XCTest
|
|
@testable import RouterOSAssistant
|
|
|
|
final class OverviewGraphTests: XCTestCase {
|
|
private func item(_ fields: [String: String], id: String = "*1") -> RouterOSMenuItem {
|
|
RouterOSMenuItem(id: id, fields: fields)
|
|
}
|
|
|
|
func testInterfaceToIPAddressEdge() {
|
|
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: [], dhcpServers: [], dhcpNetworks: [], dhcpClients: [], routes: [],
|
|
filterRules: [], natRules: [], addressLists: []
|
|
)
|
|
|
|
XCTAssertTrue(graph.nodes.contains { $0.id == "Interfaces:ether1" })
|
|
XCTAssertTrue(graph.nodes.contains { $0.id == "IP-Adressen:192.168.88.1/24" })
|
|
XCTAssertTrue(graph.edges.contains { $0.from == "Interfaces:ether1" && $0.to == "IP-Adressen:192.168.88.1/24" })
|
|
}
|
|
|
|
func testVlanEdgeToParentInterface() {
|
|
let graph = OverviewViewModel.buildGraph(
|
|
interfaces: [
|
|
item(["name": "ether5", "type": "ether"]),
|
|
item(["name": "vlan20", "type": "vlan"])
|
|
],
|
|
bridgePorts: [],
|
|
vlans: [item(["name": "vlan20", "vlan-id": "20", "interface": "ether5"])],
|
|
wireguardPeers: [], addresses: [], pools: [], dhcpServers: [], dhcpNetworks: [],
|
|
dhcpClients: [], routes: [], filterRules: [], natRules: [], addressLists: []
|
|
)
|
|
|
|
XCTAssertTrue(graph.edges.contains { $0.from == "Interfaces:ether5" && $0.to == "Interfaces:vlan20" })
|
|
let vlanNode = graph.nodes.first { $0.id == "Interfaces:vlan20" }
|
|
XCTAssertEqual(vlanNode?.subtitle, "VLAN 20")
|
|
}
|
|
|
|
func testDhcpServerEdgesToInterfaceAndPool() {
|
|
let graph = OverviewViewModel.buildGraph(
|
|
interfaces: [item(["name": "ether2", "type": "ether"])],
|
|
bridgePorts: [], vlans: [], wireguardPeers: [], addresses: [],
|
|
pools: [item(["name": "pool1", "ranges": "192.168.88.10-192.168.88.254"])],
|
|
dhcpServers: [item(["name": "dhcp1", "interface": "ether2", "address-pool": "pool1"])],
|
|
dhcpNetworks: [], dhcpClients: [], routes: [], filterRules: [], natRules: [], addressLists: []
|
|
)
|
|
|
|
XCTAssertTrue(graph.edges.contains { $0.from == "Interfaces:ether2" && $0.to == "Pools & DHCP:dhcp:dhcp1" })
|
|
XCTAssertTrue(graph.edges.contains { $0.from == "Pools & DHCP:dhcp:dhcp1" && $0.to == "Pools & DHCP:pool:pool1" })
|
|
}
|
|
|
|
func testDhcpNetworkLinksViaAddressNetworkField() {
|
|
let graph = OverviewViewModel.buildGraph(
|
|
interfaces: [item(["name": "ether2", "type": "ether"])],
|
|
bridgePorts: [], vlans: [], wireguardPeers: [],
|
|
addresses: [item(["address": "192.168.88.1/24", "interface": "ether2", "network": "192.168.88.0"])],
|
|
pools: [], dhcpServers: [],
|
|
dhcpNetworks: [item(["address": "192.168.88.0/24", "gateway": "192.168.88.1"])],
|
|
dhcpClients: [], routes: [], filterRules: [], natRules: [], addressLists: []
|
|
)
|
|
|
|
XCTAssertTrue(graph.edges.contains {
|
|
$0.from == "IP-Adressen:192.168.88.1/24" && $0.to == "Pools & DHCP:dhcpnet:192.168.88.0/24"
|
|
})
|
|
}
|
|
|
|
func testRouteLinksToInterfaceOnlyWhenGatewayIsAnInterfaceName() {
|
|
let graph = OverviewViewModel.buildGraph(
|
|
interfaces: [item(["name": "ether1", "type": "ether"])],
|
|
bridgePorts: [], vlans: [], wireguardPeers: [], addresses: [], pools: [],
|
|
dhcpServers: [], dhcpNetworks: [], dhcpClients: [],
|
|
routes: [
|
|
item(["dst-address": "0.0.0.0/0", "gateway": "ether1"]),
|
|
item(["dst-address": "10.0.0.0/24", "gateway": "192.168.88.254"])
|
|
],
|
|
filterRules: [], natRules: [], addressLists: []
|
|
)
|
|
|
|
XCTAssertTrue(graph.edges.contains { $0.from == "Interfaces:ether1" && $0.to == "route:0" })
|
|
XCTAssertFalse(graph.edges.contains { $0.to == "route:1" })
|
|
}
|
|
|
|
func testFirewallFilterEdgesToInterfacesAndAddressList() {
|
|
let graph = OverviewViewModel.buildGraph(
|
|
interfaces: [
|
|
item(["name": "ether1", "type": "ether"]),
|
|
item(["name": "ether2", "type": "ether"])
|
|
],
|
|
bridgePorts: [], vlans: [], wireguardPeers: [], addresses: [], pools: [],
|
|
dhcpServers: [], dhcpNetworks: [], dhcpClients: [], routes: [],
|
|
filterRules: [item([
|
|
"chain": "forward", "action": "drop",
|
|
"in-interface": "ether1", "out-interface": "ether2",
|
|
"src-address-list": "gesperrt"
|
|
])],
|
|
natRules: [],
|
|
addressLists: [item(["list": "gesperrt", "address": "10.0.0.5"])]
|
|
)
|
|
|
|
XCTAssertTrue(graph.edges.contains { $0.from == "Interfaces:ether1" && $0.to == "filter:0" })
|
|
XCTAssertTrue(graph.edges.contains { $0.from == "filter:0" && $0.to == "Interfaces:ether2" })
|
|
XCTAssertTrue(graph.edges.contains { $0.from == "Firewall & NAT:addrlist:gesperrt" && $0.to == "filter:0" })
|
|
}
|
|
|
|
func testEditableNodesCarryTheirRouterOSMenuAndItemID() {
|
|
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"], id: "*1")],
|
|
pools: [item(["name": "pool1", "ranges": "192.168.88.10-192.168.88.254"], id: "*2")],
|
|
dhcpServers: [], dhcpNetworks: [], dhcpClients: [],
|
|
routes: [item(["dst-address": "0.0.0.0/0", "gateway": "192.168.88.254"], id: "*3")],
|
|
filterRules: [item(["chain": "forward", "action": "drop"], id: "*4")],
|
|
natRules: [item(["chain": "srcnat", "action": "masquerade"], id: "*5")],
|
|
addressLists: [item(["list": "gesperrt", "address": "10.0.0.5"], id: "*6")]
|
|
)
|
|
|
|
let address = graph.nodes.first { $0.id == "IP-Adressen:192.168.88.1/24" }
|
|
XCTAssertEqual(address?.editTarget, OverviewNode.EditTarget(menuPath: "/ip address", restPath: "ip/address", itemID: "*1"))
|
|
|
|
let pool = graph.nodes.first { $0.id == "Pools & DHCP:pool:pool1" }
|
|
XCTAssertEqual(pool?.editTarget, OverviewNode.EditTarget(menuPath: "/ip pool", restPath: "ip/pool", itemID: "*2"))
|
|
|
|
let route = graph.nodes.first { $0.id == "route:0" }
|
|
XCTAssertEqual(route?.editTarget, OverviewNode.EditTarget(menuPath: "/ip route", restPath: "ip/route", itemID: "*3"))
|
|
|
|
let filter = graph.nodes.first { $0.id == "filter:0" }
|
|
XCTAssertEqual(filter?.editTarget, OverviewNode.EditTarget(menuPath: "/ip firewall filter", restPath: "ip/firewall/filter", itemID: "*4"))
|
|
|
|
let nat = graph.nodes.first { $0.id == "nat:0" }
|
|
XCTAssertEqual(nat?.editTarget, OverviewNode.EditTarget(menuPath: "/ip firewall nat", restPath: "ip/firewall/nat", itemID: "*5"))
|
|
|
|
// Address-list nodes fold multiple entries together — no single item to write back to.
|
|
let addressList = graph.nodes.first { $0.id == "Firewall & NAT:addrlist:gesperrt" }
|
|
XCTAssertNil(addressList?.editTarget)
|
|
}
|
|
|
|
/// A dynamic/connected route (distance=0, auto-created by an interface address) must not be
|
|
/// offered for editing — confirmed live: RouterOS rejects resending its own distance=0
|
|
/// ("value of distance out of range (1...255)"), and its .id isn't stable either.
|
|
func testDynamicRouteHasNoEditTarget() {
|
|
let graph = OverviewViewModel.buildGraph(
|
|
interfaces: [], bridgePorts: [], vlans: [], wireguardPeers: [], addresses: [],
|
|
pools: [], dhcpServers: [], dhcpNetworks: [], dhcpClients: [],
|
|
routes: [item(["dst-address": "192.168.88.0/24", "gateway": "bridge", "distance": "0"], id: "*201C5060")],
|
|
filterRules: [], natRules: [], addressLists: []
|
|
)
|
|
|
|
let route = graph.nodes.first { $0.id == "route:0" }
|
|
XCTAssertNil(route?.editTarget)
|
|
}
|
|
}
|