Files
Termind/TFMTests/DesktopAuthenticationSubmissionTests.swift
T

141 lines
6.1 KiB
Swift

import Foundation
import TermindApplication
import TermindDomain
import Testing
import struct TermindDomain.Host
@testable import Termind
@Suite @MainActor
struct DesktopAuthenticationSubmissionTests {
@Test(arguments: [ConnectionHistoryEntry.Kind.ssh, .sftp], [false, true])
func unsavedInputPreservesSelectionAndPassphraseContinuation(kind: ConnectionHistoryEntry.Kind, selected: Bool)
throws
{
let key = protectedKey()
let identity = Identity(name: "Deploy", username: " saved-user \n", keyID: key.id)
let host = Host(name: "Server", host: "server.invalid")
let store = HostLibraryModel(hosts: [host], keys: [key], identities: [identity])
let session = DesktopAuthenticationSession(kind: kind, host: host)
defer { session.disconnect() }
session.submit(
identityID: selected ? identity.id : nil, username: " manual-user \n", password: "one-use",
keyID: selected ? UUID() : key.id, save: false, store: store)
let prompt = try #require(session.prompt)
#expect(prompt.requiresPassphrase)
#expect(prompt.username == (selected ? "saved-user" : "manual-user"))
#expect(prompt.selectedKeyID == key.id)
#expect(prompt.selectedIdentityID == (selected ? identity.id : nil))
#expect(store.host(host.id) == host)
#expect(store.identity(identity.id) == identity)
session.submit(
identityID: nil, username: "ignored", password: "ignored", passphrase: "",
keyID: UUID(), save: true, store: store)
#expect(session.prompt?.id == prompt.id)
#expect(store.host(host.id) == host)
#expect(store.identities == [identity])
}
@Test(arguments: [ConnectionHistoryEntry.Kind.ssh, .sftp])
func blankUsernameLeavesTheOriginalPromptAndStoreUntouched(kind: ConnectionHistoryEntry.Kind) throws {
let host = Host(name: "Server", host: "server.invalid")
let store = HostLibraryModel(hosts: [host])
let session = DesktopAuthenticationSession(kind: kind, host: host)
defer { session.disconnect() }
let prompt = try #require(session.prompt)
session.submit(username: " \n\t ", password: "unused", save: true, store: store)
#expect(session.prompt?.id == prompt.id)
#expect(store.host(host.id) == host)
#expect(store.identities.isEmpty)
}
@Test(arguments: [ConnectionHistoryEntry.Kind.ssh, .sftp])
func clearingSavedPasswordDeletesItAndContinuesWithTheKey(kind: ConnectionHistoryEntry.Kind) throws {
let key = protectedKey()
let secrets = InMemorySecretStore()
let identity = Identity(name: "Deploy", username: "deploy", credentialRef: "password", keyID: key.id)
let host = Host(name: "Server", host: "server.invalid")
let store = HostLibraryModel(hosts: [host], keys: [key], identities: [identity], secrets: secrets)
try secrets.setString("old", for: identity.credentialRef, storage: identity.passwordStorage)
let session = DesktopAuthenticationSession(kind: kind, host: host)
defer { session.disconnect() }
session.submit(identityID: identity.id, username: "ignored", password: "", save: true, store: store)
#expect(store.host(host.id)?.identityID == identity.id)
let updatedIdentity = try #require(store.identity(identity.id))
#expect(try store.passwordCredential(for: updatedIdentity) == nil)
#expect(session.prompt?.requiresPassphrase == true)
#expect(session.prompt?.failureMessage == nil)
}
@Test(arguments: [false, true])
func temporarySSHHostCanContinueWithOrWithoutSaving(_ save: Bool) throws {
let key = protectedKey()
let host = Host(name: "Temporary", host: "temporary.invalid")
let store = HostLibraryModel(keys: [key])
let session = DesktopAuthenticationSession(kind: .ssh, host: host)
defer { session.disconnect() }
session.submit(username: " deploy ", password: "one-use", keyID: key.id, save: save, store: store)
let prompt = try #require(session.prompt)
#expect(prompt.host.id == host.id)
#expect(prompt.username == "deploy")
#expect(prompt.requiresPassphrase)
#expect((store.host(host.id) != nil) == save)
#expect(store.identities.count == (save ? 1 : 0))
if save {
let saved = try #require(store.host(host.id))
#expect(prompt.host.identityID == saved.identityID)
#expect(store.identity(for: saved)?.username == "deploy")
#expect(store.identity(for: saved)?.isHostCustom == true)
#expect(store.reusableIdentities.isEmpty)
}
}
private func protectedKey() -> SSHKeyRef {
SSHKeyRef(label: "Protected", publicKey: "ssh-ed25519 fixture", hasPassphrase: true)
}
}
/// A pending protected-key prompt keeps these entry-point tests entirely local:
/// continuing without its passphrase cannot open a transport.
@MainActor
private final class DesktopAuthenticationSession {
let ssh: SSHSessionModel?
let sftp: SFTPChannelModel?
var prompt: SSHAuthenticationPrompt? { ssh?.pendingAuthentication ?? sftp?.pendingAuthentication }
init(kind: ConnectionHistoryEntry.Kind, host: Host) {
ssh = kind == .ssh ? SSHSessionModel(host: host) : nil
sftp = kind == .sftp ? SFTPChannelModel(hostID: host.id) : nil
let prompt = SSHAuthenticationPrompt(host: host, username: "")
ssh?.pendingAuthentication = prompt
sftp?.pendingAuthentication = prompt
}
func submit(
identityID: UUID? = nil, username: String, password: String?, passphrase: String? = nil,
keyID: UUID? = nil, save: Bool, store: HostLibraryModel
) {
ssh?.continueAuthentication(
identityID: identityID, username: username, password: password, passphrase: passphrase,
keyID: keyID, save: save, using: store)
sftp?.continueAuthentication(
identityID: identityID, username: username, password: password, passphrase: passphrase,
keyID: keyID, save: save, using: store)
}
func disconnect() {
ssh?.disconnect()
sftp?.disconnect()
}
}