Files
RouterOS/RouterOSAssistantTests/ConnectionServiceTests.swift
T
KayandClaude Sonnet 5 e2b303963a M12: Geräte-Tab — LAN-Scanner mit Static-IP-Zuweisung
Neuer Tab: eine Tabelle pro physischem Ethernet/WLAN-Port mit den dort
gefundenen Geräten (Name, IP, MAC, Fest/Dynamisch/Kein-DHCP), gebaut aus
DHCP-Leases + ARP + Bridge-Host-Tabelle. Rechtsklick auf ein dynamisches
Gerät -> "Feste IP zuweisen" (RouterOS' "Make Static", per
/ip dhcp-server lease make-static), mit Bestätigungsdialog und
Session-Backup vor dem ersten Schreibvorgang (geteilter Mechanismus mit
dem Experte-Tab).

Vier reale Bugs live gefunden und gefixt (siehe HANDOFF.md Bug 14-17):

- "print terse" gibt das "dynamic"-Feld von /ip dhcp-server lease nie
  aus, in keinem Zustand -> Status kommt jetzt über RouterOS' find/get
  gegen die interne Eigenschaft, nicht aus gelesenen Feldern.
- fetchMenuItems' .id-Positionsüberlagerung ordnete für dieses Menü die
  falsche .id der falschen Zeile zu -> Erkennung und make-static-Ziel
  laufen jetzt über die MAC-Adresse statt .id.
- Ein SwiftUI-.confirmationDialog löschte sein eigenes Ziel-Objekt vor
  der Ausführung der bestätigten Aktion (Setter feuert bei jedem
  Knopfdruck, nicht nur Abbrechen) -> Dialog-Sichtbarkeit und
  Nutzlast entkoppelt, wie in BackupListView.
- Die eigene Verifikations-Abfrage (get [find ...] feld als ein
  kombinierter Befehl) war selbst eine nie verifizierte Annahme und
  lieferte falsche Negative -> ersetzt durch :foreach aus zwei einzeln
  bestätigten Bausteinen (find, get <id> feld).

RouterOSCommand bekommt einen neuen .action-Operationstyp für
RouterOS-"Menü-spezifische Befehle" jenseits von add/set/remove (aktuell
nur make-static). HANDOFF.md/CHATLOG.md mit allen vier Bugs, neuen
Milestones M11/M12 und offenen Punkten aktualisiert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CTgRxJTzaQwaRkngbaE1GJ
2026-09-14 13:55:46 +02:00

83 lines
3.1 KiB
Swift

import XCTest
@testable import RouterOSAssistant
private final class MockTransport: RouterOSTransport {
let kind: RouterOSTransportKind
var connectError: Error?
var deviceInfo = RouterDeviceInfo(boardName: "Mock", routerOSVersion: "7.0", architecture: "arm64", uptime: "1h")
init(kind: RouterOSTransportKind, connectError: Error? = nil) {
self.kind = kind
self.connectError = connectError
}
func connect() async throws {
if let connectError { throw connectError }
}
func fetchDeviceInfo() async throws -> RouterDeviceInfo { deviceInfo }
func fetchInterfaces() async throws -> [NetworkInterface] { [] }
func fetchFirewallRuleCounts() async throws -> FirewallRuleCounts { FirewallRuleCounts(filterRuleCount: 0, natRuleCount: 0) }
func fetchMenuItems(menuPath: String, restPath: String) async throws -> [RouterOSMenuItem] { [] }
func fetchFieldValues(menuPath: String, restPath: String, whereField: String, whereValue: String, returnField: String) async throws -> Set<String> { [] }
func apply(_ command: RouterOSCommand) async throws {}
func disconnect() async {}
}
@MainActor
final class ConnectionServiceTests: XCTestCase {
private let credentials = RouterOSCredentials(host: "192.168.88.1", username: "admin", password: "")
func testFallsBackToSSHWhenRestFails() async {
let service = ConnectionService()
let sshTransport = MockTransport(kind: .ssh)
await service.connect(
with: credentials,
makeRestTransport: { MockTransport(kind: .rest, connectError: RouterOSError.transportUnavailable("kein REST")) },
makeSSHTransport: { sshTransport }
)
XCTAssertEqual(service.state, .connected(kind: .ssh))
}
func testUsesRestWhenAvailable() async {
let service = ConnectionService()
await service.connect(
with: credentials,
makeRestTransport: { MockTransport(kind: .rest) },
makeSSHTransport: { MockTransport(kind: .ssh) }
)
XCTAssertEqual(service.state, .connected(kind: .rest))
}
func testUntrustedCertificateAsksForConfirmationInsteadOfFallingBackToSSH() async {
let service = ConnectionService()
await service.connect(
with: credentials,
makeRestTransport: { MockTransport(kind: .rest, connectError: RouterOSError.untrustedCertificate(fingerprint: "AA:BB")) },
makeSSHTransport: { MockTransport(kind: .ssh) }
)
XCTAssertEqual(service.state, .needsCertificateConfirmation(fingerprint: "AA:BB"))
}
func testFailsWhenBothTransportsFail() async {
let service = ConnectionService()
await service.connect(
with: credentials,
makeRestTransport: { MockTransport(kind: .rest, connectError: RouterOSError.transportUnavailable("kein REST")) },
makeSSHTransport: { MockTransport(kind: .ssh, connectError: RouterOSError.transportUnavailable("kein SSH")) }
)
guard case .failed = service.state else {
XCTFail("Erwarteter Zustand .failed, war \(service.state)")
return
}
}
}