SSHTransport.connect()/run() hatten keinen Timeout - im Gegensatz zu RestTransport (timeoutInterval=5). Jeder erste Schreibvorgang einer Session loest ueber ensureSessionBackup() eine dedizierte SSH- Verbindung aus; haengt die, blieb isApplying unbegrenzt aktiv (kein Fehler, kein Recovery, nur Force-Quit). Live vom Nutzer bestaetigt (Experte-Tab, dauerhaft haengend) bevor der Fix geschrieben wurde. Neuer genererischer SSHTransport.withTimeout(_:operation:) (Task- Group-Race gegen eine Deadline), angewendet auf connect() (10s) und run() (30s). 3 neue Regressionstests fuer die Race-Logik isoliert. Nebenbefund: xcodegen generate ueberschreibt Info.plist komplett aus project.yml (kein Merge) - ein Regenerieren fuer die neue Testdatei setzte die Version stillschweigend von 1.1.0 auf 1.0 zurueck. Version jetzt explizit in project.yml verankert. Build + alle 106 Unit-Tests gruen. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
46 lines
1.8 KiB
Swift
46 lines
1.8 KiB
Swift
import XCTest
|
|
@testable import RouterOSAssistant
|
|
|
|
/// Regression tests for bugs.md #11 (2026-09-17): creating an `/ip pool` entry in the Experte tab
|
|
/// hung the app permanently, traced to `SSHTransport.connect()`/`run(_:)` having no timeout at
|
|
/// all — a stalled connection attempt or command execution had no way to ever resolve. These
|
|
/// tests exercise the generic race mechanism (`SSHTransport.withTimeout`) directly, independent
|
|
/// of Citadel/real network I/O, since that's the actual bug: the race logic itself, not anything
|
|
/// SSH-specific.
|
|
final class SSHTransportTimeoutTests: XCTestCase {
|
|
func testFastOperationReturnsItsResultBeforeTheDeadline() async throws {
|
|
let result = try await SSHTransport.withTimeout(.seconds(1)) {
|
|
"done"
|
|
}
|
|
XCTAssertEqual(result, "done")
|
|
}
|
|
|
|
func testHangingOperationThrowsAfterTheDeadlineInsteadOfBlockingForever() async {
|
|
let start = ContinuousClock.now
|
|
do {
|
|
_ = try await SSHTransport.withTimeout(.milliseconds(200)) {
|
|
try await Task.sleep(for: .seconds(60))
|
|
return "never reached"
|
|
}
|
|
XCTFail("Expected a timeout error")
|
|
} catch {
|
|
let elapsed = ContinuousClock.now - start
|
|
XCTAssertLessThan(elapsed, .seconds(5), "Timeout should fire close to the deadline, not wait for the full 60s operation")
|
|
}
|
|
}
|
|
|
|
func testOperationsOwnThrownErrorPropagatesUnchangedWhenItFinishesFirst() async {
|
|
struct SampleError: Error, Equatable {}
|
|
do {
|
|
_ = try await SSHTransport.withTimeout(.seconds(1)) {
|
|
throw SampleError()
|
|
}
|
|
XCTFail("Expected SampleError to propagate")
|
|
} catch is SampleError {
|
|
// expected
|
|
} catch {
|
|
XCTFail("Expected SampleError, got \(error)")
|
|
}
|
|
}
|
|
}
|