Files
Termind/TFMTests/RemoteSessionAuthenticationContinuationTests.swift

115 lines
5.9 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
@Suite @MainActor
struct RemoteSessionAuthenticationContinuationTests {
@Test(arguments: [false, true], [false, true])
func identityPolicyRunsAfterStagingAndConnectsOnlyAfterSuccessfulSave(save: Bool, failsSave: Bool) {
let identity = Identity(name: "Deploy", username: " saved-user ")
let host = Host(name: "Server", host: "server.invalid")
let store = HostLibraryModel(hosts: [host], identities: [identity])
let controller = RemoteSessionConnectionController()
controller.pendingAuthentication = SSHAuthenticationPrompt(host: host, username: "")
controller.journey.begin(host: host, route: [host], purpose: .sshSession, username: nil)
controller.journey.waitForLocalIdentity(at: host.id)
var events: [String] = []
controller.continueAuthentication(
identityID: identity.id, username: "ignored", password: "one-use", passphrase: nil,
keyID: nil, save: save, destinationID: host.id, using: store
) { stagedHost, submission in
#expect(stagedHost.id == host.id)
#expect(submission.username == "saved-user")
#expect(controller.suppliedAuthentications[host.id]?.password == "one-use")
#expect(controller.pendingAuthentication != nil)
events.append("stage")
} saveIdentity: { _, _, selected in
#expect(selected?.id == identity.id)
#expect(controller.pendingAuthentication == nil)
#expect(controller.journey.phase == .connecting)
events.append("save")
if failsSave { throw TermindSSHError.missingCredential }
} onFailure: { failure in
guard case .identity(let failedHost, let username, _, let saving, _) = failure else {
Issue.record("Expected an identity save failure")
return
}
#expect(failedHost.id == host.id)
#expect(username == "saved-user")
#expect(saving)
events.append("failure")
} connect: {
#expect(controller.pendingAuthentication == nil)
events.append("connect")
}
#expect(events == (save ? ["stage", "save", failsSave ? "failure" : "connect"] : ["stage", "connect"]))
controller.disconnect()
#expect(controller.suppliedAuthentications.isEmpty)
}
@Test(arguments: [false, true])
func proxyUsesEnteredUsernameAndSavesBeforeContinuing(_ save: Bool) throws {
let identity = Identity(name: "Proxy", username: "identity-user")
let host = Host(name: "Server", host: "server.invalid", proxy: HostProxy(hostname: "proxy.invalid"))
let store = HostLibraryModel(hosts: [host], identities: [identity], secrets: InMemorySecretStore())
let controller = RemoteSessionConnectionController()
controller.pendingAuthentication = SSHAuthenticationPrompt(
host: host, username: "", target: .proxy(ownerHostID: host.id))
var connected = false
controller.continueAuthentication(
identityID: identity.id, username: " proxy-user ", password: "proxy-password", passphrase: nil,
keyID: nil, save: save, destinationID: host.id, using: store,
stageIdentity: { _, _ in Issue.record("Proxy must not stage SSH credentials") },
saveIdentity: { _, _, _ in Issue.record("Proxy must not use the SSH save policy") },
onFailure: { _ in Issue.record("Unexpected proxy failure") },
connect: {
connected = true
#expect(controller.pendingAuthentication == nil)
#expect(controller.suppliedAuthentications.isEmpty)
#expect(controller.suppliedProxyAuthentications[host.id]?.username == "proxy-user")
#expect(store.host(host.id)?.proxy?.identityID == (save ? identity.id : nil))
})
#expect(connected)
let updatedIdentity = try #require(store.identity(identity.id))
#expect(try store.passwordCredential(for: updatedIdentity) == (save ? "proxy-password" : nil))
controller.disconnect()
#expect(controller.suppliedProxyAuthentications.isEmpty)
}
@Test func proxySaveFailureDoesNotStageOrConnect() {
let identity = Identity(name: "Missing", username: "identity-user", credentialRef: "missing")
let host = Host(name: "Server", host: "server.invalid", proxy: HostProxy(hostname: "proxy.invalid"))
let store = HostLibraryModel(hosts: [host], identities: [identity], secrets: InMemorySecretStore())
let controller = RemoteSessionConnectionController()
let prompt = SSHAuthenticationPrompt(host: host, username: "", target: .proxy(ownerHostID: host.id))
controller.pendingAuthentication = prompt
var failed = false
controller.continueAuthentication(
identityID: identity.id, username: "proxy-user", password: "replacement", passphrase: nil,
keyID: nil, save: true, destinationID: host.id, using: store,
stageIdentity: { _, _ in Issue.record("Unexpected SSH staging") },
saveIdentity: { _, _, _ in Issue.record("Unexpected SSH save") },
onFailure: { failure in
guard case .proxy(let failedHost, _) = failure else {
Issue.record("Expected a proxy failure")
return
}
#expect(failedHost.id == host.id)
failed = true
},
connect: { Issue.record("A failed save must not connect") })
#expect(failed)
#expect(controller.pendingAuthentication?.id == prompt.id)
#expect(controller.suppliedProxyAuthentications.isEmpty)
#expect(store.host(host.id) == host)
}
}