M25: Einrichten-Wizard-Politur + SSH-Trust-Persistenz endlich verifiziert
- WAN-Schritt: fehlenden "Zurück"-Button ergänzt (beide Modi) - "Abbrechen"/"Jetzt sichern" prominent gemacht (wie "Neu scannen") - LAN-/VLAN-Schritt: Adressfelder starten leer, nur Format-Beispiel im Feld sichtbar statt vorbelegter Werte. VlanStepView bekam dafür eine Validierungssperre auf "Weiter" (fehlte bisher, war ok solange Defaults immer gültig waren). Zwei Tests entsprechend angepasst. - SSH-Host-Key-Trust aus M24 hatte sich entgegen der Live-Bestätigung nie tatsächlich persistiert (defaults read zeigte leeren Schlüssel, Ursache ungeklärt) — betraf BackupServices dedizierte SSH-Verbindung beim ersten Experte-Tab-Schreibversuch pro Sitzung. Erneut über den bestehenden Trust-Dialog bestätigt, diesmal per defaults read verifiziert statt nur der UI-Bestätigung vertraut. Alles live bestätigt. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+68
-9
@@ -1481,6 +1481,42 @@ Zeitpunkt bereits verbunden ist, würden dieselben dedizierten Dienste
|
|||||||
wieder wortlos nichts tun, ohne Hinweis auf die Ursache — siehe
|
wieder wortlos nichts tun, ohne Hinweis auf die Ursache — siehe
|
||||||
"Nächste Schritte".
|
"Nächste Schritte".
|
||||||
|
|
||||||
|
**M25: Einrichten-Wizard-Politur** (2026-09-16, mehrere kleine
|
||||||
|
Nutzerwünsche in einer Runde):
|
||||||
|
|
||||||
|
- **WAN-Schritt: "Zurück"-Button ergänzt** — fehlte seit jeher (nur
|
||||||
|
"Weiter"), in beiden Modi, fiel im Experte-Modus nur eher auf, da
|
||||||
|
dort mehr Schritte durchgeklickt werden. Live bestätigt.
|
||||||
|
- **"Abbrechen" (Einrichten) + "Jetzt sichern" (Sicherungen) prominent
|
||||||
|
gemacht** — dieselbe `.buttonStyle(.borderedProminent)`-Behandlung
|
||||||
|
wie zuvor schon "Neu scannen" (Bug: "leicht zu übersehen"), jetzt
|
||||||
|
konsequent auf alle **einzelnen, gleichrangigen** Toolbar-Aktions-
|
||||||
|
buttons der App angewendet. Übersicht-Tabs Toolbar-Gruppe (Zoom/
|
||||||
|
Zurücksetzen/Aktualisieren, mehrere gleichrangige Buttons
|
||||||
|
nebeneinander) bewusst ausgenommen — dort würde durchgängiges
|
||||||
|
Prominent-Styling überladen wirken, ein anderes UI-Muster als "eine
|
||||||
|
einzelne leicht übersehene Hauptaktion".
|
||||||
|
- **LAN- und VLAN-Schritt: Adressfelder starten leer** (Nutzerwunsch:
|
||||||
|
"die auszufüllenden Felder leer lassen, nur Beispiele zeigen ...
|
||||||
|
so sieht man das Schema dahinter") — `LanDhcpConfig`/`VlanEntry`
|
||||||
|
hatten bisher konkrete Default-Werte (z.B. "192.168.88.1/24" bzw.
|
||||||
|
eine aus der VLAN-ID abgeleitete Adresse); da jedes TextField-Label
|
||||||
|
("Router-Adresse (z.B. 192.168.88.1/24)") ohnehin als Platzhalter
|
||||||
|
dient, sobald das Feld leer ist, versteckte ein vorbelegter Wert
|
||||||
|
dieses Beispiel bisher komplett. Betrifft beide Modi (LAN-Schritt
|
||||||
|
wird von Einfach und Experte geteilt). `interfaceName` (Picker) und
|
||||||
|
`leaseTimeHours` (Stepper) bleiben unverändert vorbelegt — beides
|
||||||
|
Auswahl-UI ohne "Beispiel tippen"-Konzept. Notwendige Begleitänderung:
|
||||||
|
`VlanStepView`s "Weiter" hatte bisher **keine** Validierungssperre
|
||||||
|
(unnötig, solange die Defaults immer gültig waren) — jetzt analog zum
|
||||||
|
längst vorhandenen `LanStepView`-Muster ergänzt, sonst hätte ein leer
|
||||||
|
gelassenes VLAN-Feld einen Befehl mit leeren Argumenten zum Router
|
||||||
|
geschickt. Zwei Tests entsprechend angepasst
|
||||||
|
(`testDefaultAddressesStartEmptyForExamplePlaceholder` ersetzt die
|
||||||
|
alte Ableitungs-Prüfung; `testLanDhcpCommandsCoverAddressPoolServerAndNetwork`
|
||||||
|
setzt die Adressfelder jetzt explizit statt sich auf Modell-Defaults
|
||||||
|
zu verlassen). Live bestätigt ("gut soweit").
|
||||||
|
|
||||||
## Nächste Schritte
|
## Nächste Schritte
|
||||||
|
|
||||||
1. ~~M16: restliche `RouterOSSchemaCatalog.swift`-Sektionen übersetzen~~,
|
1. ~~M16: restliche `RouterOSSchemaCatalog.swift`-Sektionen übersetzen~~,
|
||||||
@@ -1645,15 +1681,38 @@ wieder wortlos nichts tun, ohne Hinweis auf die Ursache — siehe
|
|||||||
`UpdateService`/`FactoryResetService` haben keinen eigenen
|
`UpdateService`/`FactoryResetService` haben keinen eigenen
|
||||||
UI-Bestätigungspfad für einen neuen SSH-Host-Key, nur
|
UI-Bestätigungspfad für einen neuen SSH-Host-Key, nur
|
||||||
`ConnectionService.connect`s SSH-*Fallback* zeigt den Dialog — und
|
`ConnectionService.connect`s SSH-*Fallback* zeigt den Dialog — und
|
||||||
der wird nie erreicht, solange REST erfolgreich verbindet. Ein
|
der wird nie erreicht, solange REST erfolgreich verbindet. Mögliche
|
||||||
künftiger Host-Key-Wechsel (z.B. nach einem weiteren Werksreset)
|
Fixes: diese Dienste bei `untrustedSSHHostKey` einen eigenen
|
||||||
würde also wieder zu wortlos leeren Ergebnissen führen (leere
|
Bestätigungsdialog zeigen lassen, oder `ConnectionService` beim
|
||||||
Traffic-Anzeige, fehlschlagende Backups, etc.), ohne erkennbaren
|
Verbinden zusätzlich (nicht nur im Fallback-Fall) einmal den
|
||||||
Grund in der UI. Mögliche Fixes: diese Dienste bei
|
SSH-Host-Key prüfen/bestätigen lassen, unabhängig davon, ob REST
|
||||||
`untrustedSSHHostKey` einen eigenen Bestätigungsdialog zeigen
|
erfolgreich war. **Tatsächlich so eingetreten (2026-09-16):** beim
|
||||||
lassen, oder `ConnectionService` beim Verbinden zusätzlich (nicht
|
ersten Experte-Tab-Schreibversuch einer neuen Sitzung (`ensureSessionBackup`,
|
||||||
nur im Fallback-Fall) einmal den SSH-Host-Key prüfen/bestätigen
|
läuft einmal pro Verbindung vor der ersten Änderung) schlug
|
||||||
lassen, unabhängig davon, ob REST erfolgreich war.
|
`BackupService`s eigene, dedizierte SSH-Verbindung mit
|
||||||
|
"Unbekannter SSH-Schlüssel" fehl — sichtbar nur als roter
|
||||||
|
`applyError`-Text im Experte-Formular, ohne erkennbaren
|
||||||
|
Zusammenhang zum eigentlichen SSH-Host-Key-Problem. Der erste
|
||||||
|
Trust-Fix-Versuch von vorhin (M24-Nebenbefund) hatte sich entgegen
|
||||||
|
der Live-Bestätigung des Nutzers ("vertraut, verbunden per SSH")
|
||||||
|
**nicht** tatsächlich persistiert — `defaults read
|
||||||
|
com.focus72.RouterOSAssistant
|
||||||
|
RouterOSAssistant.TrustedSSHHostKeyFingerprints` zeigte einen
|
||||||
|
komplett fehlenden Schlüssel, obwohl der strukturell identische,
|
||||||
|
nachweislich funktionierende REST-Zertifikat-Trust
|
||||||
|
(`TrustedCertificateFingerprints`) im selben Preferences-Bereich
|
||||||
|
korrekt vorhanden war. Ursache dafür bleibt ungeklärt — der Code-Pfad
|
||||||
|
(`ConnectViewModel.trustSSHHostKeyAndRetry` →
|
||||||
|
`ConnectionService.trustCurrentSSHHostKeyAndRetry` →
|
||||||
|
`SSHHostKeyTrustStore.trust`) ist exakt symmetrisch zum
|
||||||
|
funktionierenden Zertifikat-Pfad, kein Unterschied im Code
|
||||||
|
gefunden. Fix diesmal per `www-ssl` erneut kurz deaktiviert,
|
||||||
|
Trust-Dialog erneut bestätigt, **und diesmal per `defaults read`
|
||||||
|
direkt verifiziert, dass der Fingerprint wirklich persistiert wurde**
|
||||||
|
(war er) — DHCP-Netzwerk-Anlage danach live erfolgreich. Falls
|
||||||
|
dieses Symptom nochmal auftritt: zuerst per `defaults read` prüfen,
|
||||||
|
ob der Trust wirklich gespeichert wurde, nicht nur der UI-Bestätigung
|
||||||
|
vertrauen.
|
||||||
|
|
||||||
Gitea-Remote `origin` ist eingerichtet und wird laufend gepusht (siehe
|
Gitea-Remote `origin` ist eingerichtet und wird laufend gepusht (siehe
|
||||||
oben) — dieser Hinweis war veraltet, korrigiert am 2026-09-15.
|
oben) — dieser Hinweis war veraltet, korrigiert am 2026-09-15.
|
||||||
|
|||||||
@@ -89,7 +89,12 @@ Geführter Schritt-für-Schritt-Assistent für die Grundkonfiguration:
|
|||||||
getrennte Netzwerke einrichten, jeweils mit eigenem Adressbereich und
|
getrennte Netzwerke einrichten, jeweils mit eigenem Adressbereich und
|
||||||
optionaler **Netzwerk-Isolation** (Geräte in diesem Netz erreichen
|
optionaler **Netzwerk-Isolation** (Geräte in diesem Netz erreichen
|
||||||
keine anderen konfigurierten Netze, Internetzugriff bleibt erhalten).
|
keine anderen konfigurierten Netze, Internetzugriff bleibt erhalten).
|
||||||
|
Adressfelder starten leer — das Feld selbst zeigt ein Beispielformat
|
||||||
|
(z.B. "Router-Adresse (z.B. 192.168.88.1/24)"), das beim Tippen
|
||||||
|
verschwindet.
|
||||||
- **VLAN**: zusätzliche virtuelle Netzwerke auf demselben Kabel.
|
- **VLAN**: zusätzliche virtuelle Netzwerke auf demselben Kabel.
|
||||||
|
Adressfelder starten hier ebenfalls leer, mit Beispielformat im
|
||||||
|
jeweiligen Feld.
|
||||||
- **Firewall-Grundschutz**: Internetfreigabe (NAT) plus Schutz vor
|
- **Firewall-Grundschutz**: Internetfreigabe (NAT) plus Schutz vor
|
||||||
unaufgeforderten Zugriffen aus dem Internet — mit einem Klick
|
unaufgeforderten Zugriffen aus dem Internet — mit einem Klick
|
||||||
aktivierbar, bestehende eigene Regeln bleiben erhalten.
|
aktivierbar, bestehende eigene Regeln bleiben erhalten.
|
||||||
@@ -263,8 +268,13 @@ Guided step-by-step assistant for base configuration:
|
|||||||
Expert mode, multiple physical ports/interfaces can be set up as
|
Expert mode, multiple physical ports/interfaces can be set up as
|
||||||
separate networks, each with its own address range and optional
|
separate networks, each with its own address range and optional
|
||||||
**network isolation** (devices on this network can't reach any other
|
**network isolation** (devices on this network can't reach any other
|
||||||
configured network, while internet access still works).
|
configured network, while internet access still works). Address
|
||||||
- **VLAN**: additional virtual networks on the same cable.
|
fields start empty — the field itself shows a format example (e.g.
|
||||||
|
"Router Address (e.g. 192.168.88.1/24)"), which disappears as you
|
||||||
|
type.
|
||||||
|
- **VLAN**: additional virtual networks on the same cable. Address
|
||||||
|
fields here start empty too, with a format example shown in each
|
||||||
|
field.
|
||||||
- **Basic firewall**: internet sharing (NAT) plus protection against
|
- **Basic firewall**: internet sharing (NAT) plus protection against
|
||||||
unsolicited access from the internet — one click to enable, existing
|
unsolicited access from the internet — one click to enable, existing
|
||||||
custom rules are preserved.
|
custom rules are preserved.
|
||||||
|
|||||||
@@ -157,6 +157,7 @@ nur die zugehörigen Passwörter liegen weiterhin im macOS-Schlüsselbund.
|
|||||||
| M22 | REST-Transport (M7) erstmals live gegen Hardware verifiziert, 4 Bugs gefunden+gefixt | ✅ live verifiziert |
|
| M22 | REST-Transport (M7) erstmals live gegen Hardware verifiziert, 4 Bugs gefunden+gefixt | ✅ live verifiziert |
|
||||||
| M23 | Experte-Tab: Sektionsüberschriften prominenter+eingefärbt, einklappbar (Standard: zugeklappt) | ✅ live verifiziert |
|
| M23 | Experte-Tab: Sektionsüberschriften prominenter+eingefärbt, einklappbar (Standard: zugeklappt) | ✅ live verifiziert |
|
||||||
| M24 | LAN-Scanner: "Aktionen"-Button statt Rechtsklick, Traffic-Monitor+Sparkline pro Port, ARP-Bug gefixt | ✅ live verifiziert |
|
| M24 | LAN-Scanner: "Aktionen"-Button statt Rechtsklick, Traffic-Monitor+Sparkline pro Port, ARP-Bug gefixt | ✅ live verifiziert |
|
||||||
|
| M25 | Einrichten-Wizard-Politur: WAN-Zurück-Button, prominente Aktionsbuttons, leere Platzhalter-Felder (LAN/VLAN) | ✅ live verifiziert |
|
||||||
| — | LAN-Port-Konflikt-Prüfung + "Fertig"-Button (Einrichten) | 🔶 gebaut, Live-Test offen |
|
| — | LAN-Port-Konflikt-Prüfung + "Fertig"-Button (Einrichten) | 🔶 gebaut, Live-Test offen |
|
||||||
|
|
||||||
Ausführlicher Stand inkl. aller gefundenen Bugs, offener Punkte und
|
Ausführlicher Stand inkl. aller gefundenen Bugs, offener Punkte und
|
||||||
|
|||||||
@@ -3,12 +3,18 @@ import Foundation
|
|||||||
struct LanDhcpConfig: Identifiable, Equatable {
|
struct LanDhcpConfig: Identifiable, Equatable {
|
||||||
var id = UUID()
|
var id = UUID()
|
||||||
var interfaceName: String = "bridge"
|
var interfaceName: String = "bridge"
|
||||||
var networkAddress: String = "192.168.88.0/24"
|
/// Empty on purpose (Nutzerwunsch: "die auszufüllenden Felder leer lassen, nur Beispiele
|
||||||
var routerAddress: String = "192.168.88.1/24"
|
/// zeigen ... so sieht man das Schema dahinter") — each TextField's label (e.g.
|
||||||
var poolRangeStart: String = "192.168.88.10"
|
/// "Router-Adresse (z.B. 192.168.88.1/24)") doubles as its placeholder while empty, so a
|
||||||
var poolRangeEnd: String = "192.168.88.254"
|
/// pre-filled value would hide the format example instead of showing it. `interfaceName`
|
||||||
|
/// above and `leaseTimeHours` below stay as real defaults — a Picker selection and a
|
||||||
|
/// Stepper, neither has a "type an example" UI to hide.
|
||||||
|
var networkAddress: String = ""
|
||||||
|
var routerAddress: String = ""
|
||||||
|
var poolRangeStart: String = ""
|
||||||
|
var poolRangeEnd: String = ""
|
||||||
var leaseTimeHours: Int = 24
|
var leaseTimeHours: Int = 24
|
||||||
var dnsServers: String = "192.168.88.1"
|
var dnsServers: String = ""
|
||||||
/// Blocks forward traffic to/from every other configured LAN/VLAN network — see
|
/// Blocks forward traffic to/from every other configured LAN/VLAN network — see
|
||||||
/// `FirewallConfig.NetworkSegment`. Internet access (WAN NAT) is unaffected.
|
/// `FirewallConfig.NetworkSegment`. Internet access (WAN NAT) is unaffected.
|
||||||
var isolated: Bool = false
|
var isolated: Bool = false
|
||||||
|
|||||||
@@ -21,16 +21,21 @@ struct VlanEntry: Identifiable, Equatable {
|
|||||||
/// `FirewallConfig.NetworkSegment`. Internet access (WAN NAT) is unaffected.
|
/// `FirewallConfig.NetworkSegment`. Internet access (WAN NAT) is unaffected.
|
||||||
var isolated: Bool = false
|
var isolated: Bool = false
|
||||||
|
|
||||||
|
/// Fields start empty on purpose (Nutzerwunsch: "die auszufüllenden Felder leer lassen,
|
||||||
|
/// nur Beispiele zeigen ... so sieht man das Schema dahinter") — each TextField's label
|
||||||
|
/// (e.g. "Router-Adresse (z.B. 192.168.20.1/24)") doubles as its placeholder while empty,
|
||||||
|
/// so leaving these blank shows the format example instead of hiding it behind an
|
||||||
|
/// auto-derived value the user might not even notice, let alone check. `VlanStepView`'s
|
||||||
|
/// "Weiter" button is disabled until every VLAN's fields are actually filled in.
|
||||||
init(name: String = "Gäste", vlanID: Int, parentInterface: String) {
|
init(name: String = "Gäste", vlanID: Int, parentInterface: String) {
|
||||||
self.name = name
|
self.name = name
|
||||||
self.vlanID = vlanID
|
self.vlanID = vlanID
|
||||||
self.parentInterface = parentInterface
|
self.parentInterface = parentInterface
|
||||||
let octet = vlanID % 256
|
self.networkAddress = ""
|
||||||
self.networkAddress = "192.168.\(octet).0/24"
|
self.routerAddress = ""
|
||||||
self.routerAddress = "192.168.\(octet).1/24"
|
self.poolRangeStart = ""
|
||||||
self.poolRangeStart = "192.168.\(octet).10"
|
self.poolRangeEnd = ""
|
||||||
self.poolRangeEnd = "192.168.\(octet).254"
|
self.dnsServers = ""
|
||||||
self.dnsServers = "192.168.\(octet).1"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var interfaceName: String { "vlan\(vlanID)" }
|
var interfaceName: String { "vlan\(vlanID)" }
|
||||||
|
|||||||
@@ -223,6 +223,9 @@ struct BackupListView: View {
|
|||||||
Label(L10n.t("Jetzt sichern", appLanguage), systemImage: "square.and.arrow.down")
|
Label(L10n.t("Jetzt sichern", appLanguage), systemImage: "square.and.arrow.down")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Same precedent as LAN-Scanner's "Neu scannen"/Einrichten's "Abbrechen" —
|
||||||
|
// a plain text toolbar button is easy to miss, applied app-wide now.
|
||||||
|
.buttonStyle(.borderedProminent)
|
||||||
.disabled(connectionService.credentials == nil || viewModel.isCreatingBackup)
|
.disabled(connectionService.credentials == nil || viewModel.isCreatingBackup)
|
||||||
.help(connectionService.credentials == nil ? L10n.t("Zuerst im Tab 'Verbinden' mit dem Router verbinden.", appLanguage) : L10n.t("Sicherung jetzt erstellen", appLanguage))
|
.help(connectionService.credentials == nil ? L10n.t("Zuerst im Tab 'Verbinden' mit dem Router verbinden.", appLanguage) : L10n.t("Sicherung jetzt erstellen", appLanguage))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,13 @@ struct SetupView: View {
|
|||||||
Button(L10n.t("Abbrechen", appLanguage)) {
|
Button(L10n.t("Abbrechen", appLanguage)) {
|
||||||
showCancelConfirmation = true
|
showCancelConfirmation = true
|
||||||
}
|
}
|
||||||
|
// Same reasoning/precedent as LAN-Scanner's "Neu scannen" button
|
||||||
|
// (Bug/Nutzerwunsch: "wird übersehen") — a plain text toolbar button
|
||||||
|
// blends into the window chrome; `.borderedProminent` gives it a
|
||||||
|
// filled, colored background so it's actually noticeable. Applied
|
||||||
|
// consistently to every standalone toolbar action button in the app
|
||||||
|
// per explicit request ("für die gesamte App fixen").
|
||||||
|
.buttonStyle(.borderedProminent)
|
||||||
.disabled(viewModel.isApplying)
|
.disabled(viewModel.isApplying)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,10 +62,24 @@ struct VlanStepView: View {
|
|||||||
Button(L10n.t("Zurück", appLanguage)) { viewModel.goBack() }
|
Button(L10n.t("Zurück", appLanguage)) { viewModel.goBack() }
|
||||||
Spacer()
|
Spacer()
|
||||||
Button(L10n.t("Weiter", appLanguage)) { viewModel.goNext() }
|
Button(L10n.t("Weiter", appLanguage)) { viewModel.goNext() }
|
||||||
|
.disabled(!isStepValid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.formStyle(.grouped)
|
.formStyle(.grouped)
|
||||||
.navigationTitle(LocalizedStringKey(L10n.t("Zusätzliche Netzwerke (VLAN)", appLanguage)))
|
.navigationTitle(LocalizedStringKey(L10n.t("Zusätzliche Netzwerke (VLAN)", appLanguage)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Only gates on filled-in fields while the VLAN section is actually enabled — with it off,
|
||||||
|
/// `viewModel.vlans` is empty (see `setVlanSectionEnabled`) and there's nothing to validate.
|
||||||
|
/// Needed as of the fields-start-empty change: previously every VLAN's address fields had a
|
||||||
|
/// valid auto-derived default, so "Weiter" was always safe to press; now an incompletely
|
||||||
|
/// filled-in VLAN would otherwise reach `buildCommands()` with blank arguments.
|
||||||
|
private var isStepValid: Bool {
|
||||||
|
guard viewModel.vlanSectionEnabled else { return true }
|
||||||
|
return viewModel.vlans.allSatisfy { vlan in
|
||||||
|
!vlan.name.isEmpty && !vlan.networkAddress.isEmpty && !vlan.routerAddress.isEmpty
|
||||||
|
&& !vlan.poolRangeStart.isEmpty && !vlan.poolRangeEnd.isEmpty
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ struct WanStepView: View {
|
|||||||
|
|
||||||
Section {
|
Section {
|
||||||
HStack {
|
HStack {
|
||||||
|
Button(L10n.t("Zurück", appLanguage)) { viewModel.goBack() }
|
||||||
Spacer()
|
Spacer()
|
||||||
Button(L10n.t("Weiter", appLanguage)) { viewModel.goNext() }
|
Button(L10n.t("Weiter", appLanguage)) { viewModel.goNext() }
|
||||||
.disabled(!isStepValid)
|
.disabled(!isStepValid)
|
||||||
|
|||||||
@@ -40,7 +40,15 @@ final class RouterOSCommandBuilderTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testLanDhcpCommandsCoverAddressPoolServerAndNetwork() {
|
func testLanDhcpCommandsCoverAddressPoolServerAndNetwork() {
|
||||||
let config = LanDhcpConfig()
|
// Address fields default to "" now (Nutzerwunsch, 2026-09-16: fields start empty,
|
||||||
|
// showing only the format example) — set explicitly here so this test keeps verifying
|
||||||
|
// the command-building logic itself, not incidentally the model's old default values.
|
||||||
|
var config = LanDhcpConfig()
|
||||||
|
config.networkAddress = "192.168.88.0/24"
|
||||||
|
config.routerAddress = "192.168.88.1/24"
|
||||||
|
config.poolRangeStart = "192.168.88.10"
|
||||||
|
config.poolRangeEnd = "192.168.88.254"
|
||||||
|
config.dnsServers = "192.168.88.1"
|
||||||
let commands = config.buildCommands()
|
let commands = config.buildCommands()
|
||||||
|
|
||||||
XCTAssertEqual(commands.count, 6)
|
XCTAssertEqual(commands.count, 6)
|
||||||
|
|||||||
@@ -23,12 +23,16 @@ final class VlanEntryTests: XCTestCase {
|
|||||||
XCTAssertEqual(commands[7].arguments["interface"], "vlan20")
|
XCTAssertEqual(commands[7].arguments["interface"], "vlan20")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testDefaultAddressesAreDerivedFromVlanID() {
|
/// Address fields start empty by design (Nutzerwunsch, 2026-09-16: "die auszufüllenden
|
||||||
|
/// Felder leer lassen, nur Beispiele zeigen ... so sieht man das Schema dahinter") — a
|
||||||
|
/// previous version auto-derived these from the VLAN ID; replaced with this test.
|
||||||
|
func testDefaultAddressesStartEmptyForExamplePlaceholder() {
|
||||||
let vlan = VlanEntry(vlanID: 30, parentInterface: "bridge")
|
let vlan = VlanEntry(vlanID: 30, parentInterface: "bridge")
|
||||||
|
|
||||||
XCTAssertEqual(vlan.routerAddress, "192.168.30.1/24")
|
XCTAssertEqual(vlan.routerAddress, "")
|
||||||
XCTAssertEqual(vlan.networkAddress, "192.168.30.0/24")
|
XCTAssertEqual(vlan.networkAddress, "")
|
||||||
XCTAssertEqual(vlan.poolRangeStart, "192.168.30.10")
|
XCTAssertEqual(vlan.poolRangeStart, "")
|
||||||
XCTAssertEqual(vlan.poolRangeEnd, "192.168.30.254")
|
XCTAssertEqual(vlan.poolRangeEnd, "")
|
||||||
|
XCTAssertEqual(vlan.dnsServers, "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user