M24: LAN-Scanner Aktionen-Button, Traffic-Monitor+Sparkline, ARP-Fix
- Rechtsklick-Kontextmenü ersetzt durch sichtbaren "Aktionen"-Button je Geräte-Zeile (bisher nicht diskoverbar) - Live-Traffic pro Port (↓/↑, wiederverwendet InterfaceTrafficMonitor) plus kleines Sparkline-Liniendiagramm der letzten 10 Sekunden (Swift Charts, neuer TrafficSample-Typ) - Bug 34: ARP-Tabelle kann mehrere Zeilen für dieselbe MAC halten (reachable + stale/failed) — Auflösung bevorzugte bisher blind die zuletzt gesehene Zeile statt die erreichbare. Zwei Regressionstests. - Nebenbefund: dedizierte SSH-Dienste (Backup/NetworkTools/Traffic/ Update/FactoryReset) haben keinen eigenen Bestätigungspfad für einen neuen SSH-Host-Key, scheitern still solange REST verbindet — als offener Punkt dokumentiert. Alles live bestätigt. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -54,6 +54,54 @@ final class DevicesViewModelTests: XCTestCase {
|
||||
XCTAssertTrue(devices[0].isExactPort)
|
||||
}
|
||||
|
||||
/// Live-confirmed (2026-09-16): RouterOS' `/ip arp` table held two rows for the same MAC at
|
||||
/// once — a "reachable" one on the device's real standalone port, and a stale "failed" one
|
||||
/// left over from an earlier network on "bridge". The old code built `arpInterfaceByMAC` by
|
||||
/// blindly overwriting with whatever row came last, so which one "won" depended on array
|
||||
/// order, not correctness — this device fell into "Unbekannter Port" despite the working
|
||||
/// row existing. Reproduces that exact shape (stale bridge row listed AFTER the reachable
|
||||
/// direct-port row, matching the live capture) and asserts the reachable, more specific
|
||||
/// port wins regardless of order.
|
||||
func testReachableArpRowWinsOverStaleFailedRowForSameMAC() {
|
||||
let devices = DevicesViewModel.buildDevices(
|
||||
leases: [item([
|
||||
"address": "192.168.4.254", "mac-address": "AC:91:A1:04:BB:08",
|
||||
"host-name": "Laptop", "server": "dhcp_ether4"
|
||||
])],
|
||||
arpEntries: [
|
||||
item(["address": "192.168.4.254", "mac-address": "AC:91:A1:04:BB:08", "interface": "ether4", "status": "reachable"]),
|
||||
item(["address": "192.168.88.252", "mac-address": "AC:91:A1:04:BB:08", "interface": "bridge", "status": "failed"])
|
||||
],
|
||||
bridgeHosts: [],
|
||||
interfaces: [item(["name": "bridge", "type": "bridge"]), item(["name": "ether4", "type": "ether"])],
|
||||
dhcpServers: [item(["name": "dhcp_ether4", "interface": "ether4"])]
|
||||
)
|
||||
|
||||
XCTAssertEqual(devices[0].resolvedPort, "ether4")
|
||||
XCTAssertTrue(devices[0].isExactPort)
|
||||
XCTAssertNotEqual(devices[0].displayPort, "unbekannt")
|
||||
}
|
||||
|
||||
/// Same shape, rows in the opposite order (reachable row listed second) — the fix must not
|
||||
/// just happen to work for the one order seen live.
|
||||
func testReachableArpRowWinsOverStaleFailedRowRegardlessOfOrder() {
|
||||
let devices = DevicesViewModel.buildDevices(
|
||||
leases: [item([
|
||||
"address": "192.168.4.254", "mac-address": "AC:91:A1:04:BB:08",
|
||||
"host-name": "Laptop", "server": "dhcp_ether4"
|
||||
])],
|
||||
arpEntries: [
|
||||
item(["address": "192.168.88.252", "mac-address": "AC:91:A1:04:BB:08", "interface": "bridge", "status": "failed"]),
|
||||
item(["address": "192.168.4.254", "mac-address": "AC:91:A1:04:BB:08", "interface": "ether4", "status": "reachable"])
|
||||
],
|
||||
bridgeHosts: [],
|
||||
interfaces: [item(["name": "bridge", "type": "bridge"]), item(["name": "ether4", "type": "ether"])],
|
||||
dhcpServers: [item(["name": "dhcp_ether4", "interface": "ether4"])]
|
||||
)
|
||||
|
||||
XCTAssertEqual(devices[0].resolvedPort, "ether4")
|
||||
}
|
||||
|
||||
func testLeaseNotInStaticMacSetIsDynamic() {
|
||||
// Confirmed live on a real hEX: "/ip dhcp-server lease print terse" never emits a
|
||||
// "dynamic" key at all, in either state — reading fields can't distinguish static from
|
||||
|
||||
Reference in New Issue
Block a user