M30: Experte-Tab Mode-Taste-Menü + SSH-Trust-/REST-Fixes

Neues Schema für /system routerboard mode-button (enabled/on-event/
hold-time), live gegen echte Hardware verifiziert.

Dabei drei echte Bugs gefunden und gefixt:
- SSH-Trust-Dead-End beim ersten Experte-Tab-Schreiben (Backup-vor-
  Schreiben-Pfad hatte keinen Weg, den Trust-Dialog auszulösen) —
  ConnectionService.noteUntrustedSSHHostKey
- RouterOSFieldSchema.clearable: optionale Felder senden nie mehr
  einen expliziten Leer-Wert, wenn das RouterOS ablehnt
- RouterOSMenuSchema.writesRequireSSH + ConnectionService.applyViaSSH:
  für Menüs ohne REST-Anbindung (bewiesen per direktem SSH-Test) wird
  zwingend eine dedizierte SSH-Verbindung genutzt statt REST

Live Ende-zu-Ende bestätigt: Skript anlegen, Mode-Taste zuweisen,
Tastendruck löst Skript korrekt aus.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Kay
2026-09-17 15:14:31 +02:00
co-authored by Claude Sonnet 5
parent 221b914b05
commit 6f29bbcb2b
13 changed files with 272 additions and 14 deletions
@@ -14,6 +14,29 @@ final class ExpertViewModelTests: XCTestCase {
)
}
func testNonClearableFieldNeverSendsExplicitEmptyValue() {
let schema = RouterOSMenuSchema(
menuPath: "/system routerboard mode-button", restPath: "system/routerboard/mode-button", category: .system,
displayName: "Test", summary: "", explanation: "",
fields: [
RouterOSFieldSchema(key: "enabled", label: "Aktiviert", kind: .bool, help: "", defaultValue: "no"),
RouterOSFieldSchema(key: "hold-time", label: "Haltedauer", kind: .text, help: "", clearable: false)
],
isSingleton: true
)
let viewModel = ExpertViewModel(connectionService: ConnectionService())
viewModel.selectedSchema = schema
// A previously non-empty "hold-time" that the user's form now shows blank (whatever the
// cause) must still never be sent as an explicit clear only this field's `clearable:
// false` should suppress it; "enabled" (clearable, unaffected) still sends normally.
viewModel.startEditing(RouterOSMenuItem(id: "singleton", fields: ["enabled": "no", "hold-time": "3s..5s"]))
viewModel.formValues["enabled"] = "yes"
viewModel.formValues["hold-time"] = ""
XCTAssertNil(viewModel.pendingCommand?.arguments["hold-time"])
XCTAssertEqual(viewModel.pendingCommand?.arguments["enabled"], "yes")
}
func testClearingACuratedFieldSendsExplicitEmptyValue() {
let viewModel = ExpertViewModel(connectionService: ConnectionService())
viewModel.selectedSchema = makeSchema()