Files
Termind/TFMTests/RemoteSessionConnectionControllerTests.swift

112 lines
5.0 KiB
Swift

// SPDX-FileCopyrightText: 2026 Kaede Akino
// SPDX-License-Identifier: GPL-3.0-only
import Foundation
import TermindApplication
import TermindDomain
import Testing
import struct TermindDomain.Host
@testable import Termind
@Suite @MainActor
struct RemoteSessionConnectionControllerTests {
@Test(arguments: [ConnectionPurpose.sshSession, .sftpChannel])
func reconnectTearsDownBeforeJourneyAndRetriesWithTheStore(_ purpose: ConnectionPurpose) async {
let jump = Host(name: "Jump", host: "jump.invalid")
let host = Host(name: "Server", host: "server.invalid")
let store = HostLibraryModel(hosts: [host])
let controller = RemoteSessionConnectionController(
reconnect: SessionReconnectController(sleep: { _ in try await Task.sleep(for: .seconds(3_600)) }))
let route = SSHConnectionRoute(hosts: [jump, host], usernames: [host.id: "route-user"])
controller.connectionRoute = route
controller.markConnected()
var didTeardown = false
var didRetry = false
controller.scheduleReconnect(
after: "offline", hostID: host.id, using: store, purpose: purpose,
route: purpose == .sshSession ? route : nil
) {
#expect(controller.journey.phase == .hidden)
#expect(controller.reconnectState == nil)
didTeardown = true
} retry: { retryStore in
#expect(didTeardown)
#expect(retryStore === store)
#expect(controller.journey.mode == .reconnection)
didRetry = true
}
#expect(didTeardown)
#expect(controller.reconnectState?.lastError == "offline")
#expect(controller.journey.purpose == purpose)
#expect(
controller.journey.endpoints.compactMap(\.hostID)
== (purpose == .sshSession ? [jump.id, host.id] : [host.id]))
controller.reconnect.retryNow()
for _ in 0..<100 where !didRetry { await Task.yield() }
#expect(didRetry)
controller.disconnect()
}
@Test func disconnectCancelsTheScheduledReconnect() async {
let host = Host(name: "Server", host: "server.invalid")
let store = HostLibraryModel(hosts: [host])
let controller = RemoteSessionConnectionController(
reconnect: SessionReconnectController(sleep: { _ in try await Task.sleep(for: .seconds(3_600)) }))
controller.markConnected()
var didRetry = false
controller.scheduleReconnect(
after: "offline", hostID: host.id, using: store, purpose: .sftpChannel,
teardown: {}, retry: { _ in didRetry = true })
controller.disconnect()
controller.reconnect.retryNow()
for _ in 0..<100 { await Task.yield() }
#expect(!didRetry)
#expect(controller.reconnectState == nil)
#expect(controller.journey.phase == .hidden)
}
@Test func hostResolutionPrefersRouteThenLibraryThenTemporaryDestination() {
let destination = Host(name: "Library", host: "library.invalid")
var routed = destination
routed.host = "route.invalid"
let temporary = Host(name: "Temporary", host: "temporary.invalid")
let store = HostLibraryModel(hosts: [destination])
let controller = RemoteSessionConnectionController()
controller.transientHost = temporary
controller.connectionRoute = SSHConnectionRoute(hosts: [routed])
#expect(controller.connectionHost(destination.id, destinationID: temporary.id, using: store) == routed)
controller.connectionRoute = nil
#expect(controller.connectionHost(destination.id, destinationID: temporary.id, using: store) == destination)
#expect(controller.connectionHost(temporary.id, destinationID: temporary.id, using: store) == temporary)
#expect(controller.connectionHost(UUID(), destinationID: temporary.id, using: store) == nil)
}
@Test func progressIgnoresReplacedAndClearedAttempts() async {
let host = Host(name: "Server", host: "server.invalid")
let controller = RemoteSessionConnectionController()
controller.journey.begin(host: host, route: [host], purpose: .sshSession, username: "deploy")
let first = UUID()
controller.connectionAttemptID = first
let oldProgress = controller.progressHandler(attemptID: first)
let current = UUID()
controller.connectionAttemptID = current
let progress = controller.progressHandler(attemptID: current)
let initialCount = controller.journey.logs.count
oldProgress(.initializing(host.id, "stale"))
progress(.initializing(host.id, "current"))
for _ in 0..<100 where controller.journey.logs.count == initialCount { await Task.yield() }
#expect(controller.journey.logs.last?.message == "current")
#expect(controller.journey.logs.count == initialCount + 1)
controller.connectionAttemptID = nil
progress(.initializing(host.id, "cleared"))
for _ in 0..<100 { await Task.yield() }
#expect(controller.journey.logs.count == initialCount + 1)
}
}