M26+M27: Seriennummer bei Bekannte Router, Schlüsselbund-Trennung, terse-Fallback verallgemeinert

M26: SavedRouter bekommt ein optionales serialNumber-Feld. Zwei
physisch verschiedene Router mit identischem Host+Benutzername
(Werks-Adresse) blieben bisher ein gemeinsamer "Bekannte Router"-
Eintrag; recordSuccessfulConnection matcht jetzt zusätzlich nach
Seriennummer, mit sauberer Migration bestehender Einträge ohne
Seriennummer. Drei neue Tests.

M27, beim Live-Test von M26 gefunden:
- Passwort-Anzeige-Button (Augen-Symbol) im Verbinden-Tab
- Bug 35: KeychainService speicherte Passwörter nur nach
  "username@host" — beide Router teilten sich denselben
  Schlüsselbund-Eintrag trotz getrennter SavedRouter-Einträge. Fix:
  optionaler serialNumber-Parameter qualifiziert den Account-Key,
  mit Fallback auf den alten Key für bereits gespeicherte Passwörter.
- Bug 36: neuer Router zeigte keine Routerboard-Infos/Seriennummer —
  derselbe Root Cause wie der zuvor gemeldete "Jetzt prüfen"-Fehler:
  /system routerboard und /system package update scheitern auf diesem
  Router mit "expected end of command" statt dem bisher einzig
  abgefangenen "bad parameter terse". SSHTransport.fetchMenuItems
  erkennt jetzt beide Formulierungen.

Alles live bestätigt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Kay
2026-09-16 14:02:27 +02:00
co-authored by Claude Sonnet 5
parent 8a5df3338a
commit 9de9311bee
11 changed files with 294 additions and 45 deletions
@@ -85,6 +85,37 @@ final class SavedRoutersStoreTests: XCTestCase {
XCTAssertTrue(store.load().isEmpty)
}
/// Live-confirmed (2026-09-16): a second, same-model router left at MikroTik's factory
/// default (192.168.88.1/admin) previously just renamed the *existing* entry from the first
/// router in place, with no way to tell the two physical devices apart afterwards.
func testDifferentSerialAtSameHostUsernameCreatesSeparateEntry() {
let store = makeStore()
store.recordSuccessfulConnection(host: "192.168.88.1", username: "admin", defaultName: "hEX", serialNumber: "AAA111")
let routers = store.recordSuccessfulConnection(host: "192.168.88.1", username: "admin", defaultName: "hEX", serialNumber: "BBB222")
XCTAssertEqual(routers.count, 2)
}
/// A router saved before `serialNumber` existed (or from a device with no routerboard) has
/// no recorded serial yet the next connection that DOES report one should fill it in on
/// the same entry rather than creating a duplicate.
func testLegacyEntryWithoutSerialGetsUpgradedInPlaceNotDuplicated() {
let store = makeStore()
store.recordSuccessfulConnection(host: "192.168.88.1", username: "admin", defaultName: "hEX", serialNumber: nil)
let routers = store.recordSuccessfulConnection(host: "192.168.88.1", username: "admin", defaultName: "hEX", serialNumber: "AAA111")
XCTAssertEqual(routers.count, 1)
XCTAssertEqual(routers[0].serialNumber, "AAA111")
}
func testSameSerialReconnectUpdatesRecencyNotDuplicated() {
let store = makeStore()
store.recordSuccessfulConnection(host: "192.168.88.1", username: "admin", defaultName: "hEX", serialNumber: "AAA111")
let routers = store.recordSuccessfulConnection(host: "192.168.88.1", username: "admin", defaultName: "hEX", serialNumber: "AAA111")
XCTAssertEqual(routers.count, 1)
}
func testLoadOrdersMostRecentlyConnectedFirst() {
let store = makeStore()
store.recordSuccessfulConnection(host: "10.0.0.1", username: "admin", defaultName: "Router A")