Files
RouterOS/RouterOSAssistantTests/RouterOSCliParserTests.swift
KayandClaude Sonnet 5 31607b7270 M17-M19: Bekannte Router, Live-Traffic-Anzeige, Übersicht-Animation+Drag
M17: "Bekannte Router" im Verbinden-Tab (SavedRouter/SavedRoutersStore),
Standort-Freitextfeld, Scroll-Cap ab 4 Einträgen. Bugfix: Umbenennen-
TextField steckte in einem sich selbst deaktivierenden Button.

M18: Live-Traffic-Punkt an Interfaces (InterfaceTrafficMonitor, eigene
SSH-Verbindung, monitor-traffic-Polling). Dabei zwei reale CLI-Parser-Bugs
gefunden und gefixt: running/disabled-Flags werden als Buchstaben vor dem
ersten Feld codiert, nicht als key=value; monitor-traffic liefert
"50.7kbps" statt einer reinen Zahl.

M19: Übersicht-Tab — animierte Flussrichtung auf allen Verbindungslinien
(TimelineView+dashPhase), frei verschiebbare Knoten mit Live-folgenden
Linien, Zurücksetzen-Button.

Zusätzlich (noch nicht live getestet, nur Build+Unit-Tests grün):
LAN-Port-Konflikt-Prüfung im Einrichten-Assistenten mit doppelter
Sicherheitsbestätigung, "Fertig"-Button nach erfolgreichem Anwenden.

82 Tests grün. HANDOFF.md/README.md/Manual.md/CHATLOG.md aktualisiert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YDmUd93KxsYGr2kLTotWnG
2026-09-15 21:40:46 +02:00

89 lines
4.9 KiB
Swift

import XCTest
@testable import RouterOSAssistant
final class RouterOSCliParserTests: XCTestCase {
func testParseDeviceInfo() {
let raw = """
uptime: 1w2d3h4m5s
version: 7.24.2 (stable)
architecture-name: arm64
board-name: RB750Gr3
"""
let info = RouterOSCliParser.parseDeviceInfo(raw)
XCTAssertEqual(info.boardName, "RB750Gr3")
XCTAssertEqual(info.routerOSVersion, "7.24.2 (stable)")
XCTAssertEqual(info.architecture, "arm64")
XCTAssertEqual(info.uptime, "1w2d3h4m5s")
}
/// "running"/"disabled" are never present as `key=value` pairs in real `/interface print
/// terse` output — confirmed live (2026-09-15, hEX/RouterOS 7.x) against a router with a mix
/// of running, bridge-slave, and (synthetically added here) disabled ports:
/// 0 R name=ether1 ... (running)
/// 2 S name=ether3 ... (not running, bridge slave)
/// RouterOS encodes them as single-letter flags in a fixed-width column before the first
/// key=value pair instead ("X" = disabled, "R" = running, "S" = slave). An earlier version of
/// this parser (and this test) assumed invented `running=`/`disabled=` keys that never
/// actually appear — every interface's `running` silently read as `false` as a result.
func testParseInterfaces() {
let raw = """
0 R name=ether1 type=ether mac-address=AA:BB:CC:DD:EE:01
1 S name=ether2 type=ether mac-address=AA:BB:CC:DD:EE:02
2 X name=ether3 type=ether mac-address=AA:BB:CC:DD:EE:03
"""
let interfaces = RouterOSCliParser.parseInterfaces(raw)
XCTAssertEqual(interfaces.count, 3)
XCTAssertEqual(interfaces[0].name, "ether1")
XCTAssertTrue(interfaces[0].running)
XCTAssertFalse(interfaces[0].disabled)
XCTAssertEqual(interfaces[1].name, "ether2")
XCTAssertFalse(interfaces[1].running)
XCTAssertFalse(interfaces[1].disabled)
XCTAssertEqual(interfaces[2].name, "ether3")
XCTAssertFalse(interfaces[2].running)
XCTAssertTrue(interfaces[2].disabled)
}
/// The exact live output pasted by the user (2026-09-15, hEX) that surfaced the flag-column
/// bug — a mix of running/slave/plain ports plus the router's own bridge and loopback.
func testParseInterfacesMatchesLiveHexOutput() {
let raw = """
0 R name=ether1 default-name=ether1 type=ether mtu=1500 actual-mtu=1500 l2mtu=1596 max-l2mtu=2026 vrf=main mac-address=F4:1E:57:1B:37:D5 last-link-up-time=2026-09-15 20:19:19 link-downs=0
1 RS name=ether2 default-name=ether2 type=ether mtu=1500 actual-mtu=1500 l2mtu=1596 max-l2mtu=2026 vrf=main mac-address=F4:1E:57:1B:37:D6 last-link-up-time=2026-09-15 20:19:19 link-downs=0
2 S name=ether3 default-name=ether3 type=ether mtu=1500 actual-mtu=1500 l2mtu=1596 max-l2mtu=2026 vrf=main mac-address=F4:1E:57:1B:37:D7 link-downs=0
3 R name=ether4 default-name=ether4 type=ether mtu=1500 actual-mtu=1500 l2mtu=1596 max-l2mtu=2026 vrf=main mac-address=F4:1E:57:1B:37:D8 last-link-up-time=2026-09-15 20:19:19 link-downs=0
4 S name=ether5 default-name=ether5 type=ether mtu=1500 actual-mtu=1500 l2mtu=1596 max-l2mtu=2026 vrf=main mac-address=F4:1E:57:1B:37:D9 link-downs=0
5 R comment=defconf name=bridge type=bridge mtu=auto actual-mtu=1500 l2mtu=1596 vrf=main mac-address=F4:1E:57:1B:37:D6 last-link-up-time=2026-09-15 20:19:27 link-downs=0
6 R name=lo type=loopback mtu=65536 actual-mtu=65536 vrf=main mac-address=00:00:00:00:00:00 last-link-up-time=2026-09-15 20:19:15 link-downs=0
"""
let interfaces = RouterOSCliParser.parseInterfaces(raw)
XCTAssertEqual(interfaces.map(\.name), ["ether1", "ether2", "ether3", "ether4", "ether5", "bridge", "lo"])
XCTAssertEqual(interfaces.map(\.running), [true, true, false, true, false, true, true])
XCTAssertTrue(interfaces.allSatisfy { !$0.disabled })
}
/// Regression test for a real bug found on a physical hEX device: its SSH output didn't
/// split on bare "\n", collapsing every interface into one "line" whose repeated keys
/// (name=, type=, ...) overwrote each other, leaving only the last interface ("lo").
func testParseInterfacesHandlesCarriageReturnLineEndings() {
let raw = [
"0 R name=ether1 default-name=ether1 type=ether mtu=1500",
"1 RS name=ether2 default-name=ether2 type=ether mtu=1500",
"5 R comment=defconf name=bridge type=bridge mtu=auto",
"6 R name=lo type=loopback mtu=65536"
].joined(separator: "\r\n")
let interfaces = RouterOSCliParser.parseInterfaces(raw)
XCTAssertEqual(interfaces.count, 4)
XCTAssertEqual(interfaces.map(\.name), ["ether1", "ether2", "bridge", "lo"])
XCTAssertEqual(interfaces.map(\.type), ["ether", "ether", "bridge", "loopback"])
}
}