Letzter offener Punkt aus HANDOFF.md: gespeicherte .rsc-Backups lassen sich jetzt wieder einspielen. Weg: Backup-Datei per SFTP auf den Router hochladen (Citadel, die bereits eingebundene SSH-Bibliothek, hat einen SFTP-Client), dann RouterOS' offiziell dokumentierter Restore-Weg in einem Rutsch: /system reset-configuration no-defaults=yes run-after-reset=<datei> (kompletter Wipe + sofortiges Wiederanwenden, sicherer als ein Re-Import auf eine bestehende, andere Config). Vor dem Bestätigungsdialog wird das Routermodell abgeglichen (/system routerboard prints "model"-Feld gegen die "# model = ..."- Kopfzeile der Sicherung) und bei Mismatch komplett blockiert, um ein Brick-Risiko durch falsches Modell zu vermeiden - der Dialog selbst warnt zusaetzlich prominent davor. Ein ernster Bug live gefunden und gefixt: der erste echte Restore-Test sperrte den Router komplett aus (RouterOS exportiert nie Passwoerter, no-defaults=yes loescht zusaetzlich den Werks-Admin-Account), nur per Hardware-Reset behebbar. Fix: das aktuell verwendete App-Login wird jetzt vorne ins Restore-Skript eingefuegt, noch vor dem eigentlichen Sicherungsinhalt, da RouterOS den Import beim ersten Fehler irgendwo im Skript komplett abbricht. Zwei Tests fuer die Escaping-Logik ergaenzt. Nebenbei: BackupListView mit den neuen Restore-Dialogen liess sich nicht mehr kompilieren (SwiftUI-Typpruefung timeoutete bei der langen Modifier-Kette) - Restore-Dialoge in eine eigene @ViewBuilder-Property ausgelagert. HANDOFF.md/CHATLOG.md aktualisiert: M13, Bug 18+19, "Backup- Wiederherstellung fehlt" aus den offenen Punkten entfernt, Hinweis auf den zweiten (ungewollten) Werksreset waehrend der Session. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CTgRxJTzaQwaRkngbaE1GJ
58 lines
2.4 KiB
Swift
58 lines
2.4 KiB
Swift
import XCTest
|
|
@testable import RouterOSAssistant
|
|
|
|
final class BackupServiceTests: XCTestCase {
|
|
override func tearDown() {
|
|
BackupService.customDirectoryURL = nil
|
|
super.tearDown()
|
|
}
|
|
|
|
func testCustomDirectoryDefaultsToNil() {
|
|
BackupService.customDirectoryURL = nil
|
|
XCTAssertNil(BackupService.customDirectoryURL)
|
|
}
|
|
|
|
func testCustomDirectoryRoundTrips() {
|
|
let url = URL(fileURLWithPath: "/tmp/RouterOSAssistantTestBackups", isDirectory: true)
|
|
BackupService.customDirectoryURL = url
|
|
XCTAssertEqual(BackupService.customDirectoryURL?.path, url.path)
|
|
}
|
|
|
|
func testLoginPreservationScriptEscapesQuotesAndBackslashes() {
|
|
let script = BackupService.loginPreservationScript(username: "ad\"min", password: "pa\\ss\"word")
|
|
XCTAssertTrue(script.contains("name=\"ad\\\"min\""))
|
|
XCTAssertTrue(script.contains("password=\"pa\\\\ss\\\"word\""))
|
|
}
|
|
|
|
func testLoginPreservationScriptRecreatesOrRepasswordsCurrentLogin() {
|
|
let script = BackupService.loginPreservationScript(username: "admin", password: "secret")
|
|
XCTAssertTrue(script.contains("[/user find name=\"admin\"]"))
|
|
XCTAssertTrue(script.contains("/user add name=\"admin\" password=\"secret\" group=full"))
|
|
XCTAssertTrue(script.contains("/user set [find name=\"admin\"] password=\"secret\""))
|
|
}
|
|
|
|
func testBackupModelParsesHeaderLine() {
|
|
let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent("test-backup-\(UUID().uuidString).rsc")
|
|
let contents = """
|
|
# 2026-09-14 12:00:00 by RouterOS 7.24.2
|
|
# software id = TEST-1234
|
|
#
|
|
# model = RB750Gr3
|
|
# serial number = ABC123
|
|
/interface bridge add name=bridge
|
|
"""
|
|
try? contents.write(to: tempURL, atomically: true, encoding: .utf8)
|
|
defer { try? FileManager.default.removeItem(at: tempURL) }
|
|
|
|
XCTAssertEqual(BackupService.backupModel(from: tempURL), "RB750Gr3")
|
|
}
|
|
|
|
func testBackupModelReturnsNilWithoutHeader() {
|
|
let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent("test-backup-\(UUID().uuidString).rsc")
|
|
try? "/interface bridge add name=bridge".write(to: tempURL, atomically: true, encoding: .utf8)
|
|
defer { try? FileManager.default.removeItem(at: tempURL) }
|
|
|
|
XCTAssertNil(BackupService.backupModel(from: tempURL))
|
|
}
|
|
}
|