// SPDX-FileCopyrightText: 2026 Kaede Akino // SPDX-License-Identifier: GPL-3.0-only import AgentKit import Foundation import TermindApplication import TermindDomain import TermindInfrastructure import TermindPresentation import TermindRemoteInfrastructure import Testing import struct TermindDomain.Host @testable import Termind @Suite @MainActor struct QuickOpenTests { @Test func resumableConnectActionsUseWithPrefix() { let locale = Locale(identifier: "en") #expect(AppLocalization.resolve(HostConnectAction.mosh.title, locale: locale) == "with Mosh") #expect( AppLocalization.resolve(HostConnectAction.eternalTerminal.title, locale: locale) == "with Eternal Terminal") } @Test func nativeHostMenuCanDuplicateWithoutChangingTheOriginalHost() async throws { let host = Host(name: "Production", host: "server.invalid", port: 2222) let app = AppModel(hosts: HostLibraryModel(hosts: [host])) defer { app.shutdownForTermination() } let entries = HostMenuEntries.entries( for: host, app: app, window: WindowModel(), includesDuplicate: true) let duplicate = try #require(entries.first { $0.symbol == MenuSymbol.duplicate }) #expect(duplicate.isEnabled) duplicate.action() for _ in 0..<200 where app.hostLibrary.hosts.count == 1 { try await Task.sleep(for: .milliseconds(5)) } #expect(app.hostLibrary.hosts.count == 2) let copy = try #require(app.hostLibrary.hosts.first { $0.id != host.id }) #expect(copy.name == String(localized: AppStrings.Vault.duplicatedHostName(host.name))) #expect(copy.host == host.host) #expect(copy.port == host.port) #expect(app.hostLibrary.host(host.id)?.name == host.name) } @Test func telnetUsesItsOwnSymbolInQuickOpenAndNativeHostMenus() throws { let host = Host( name: "Legacy Console", host: "console.example.invalid", telnetEnabled: true) let store = HostLibraryModel(hosts: [host]) let app = AppModel(hosts: store) let window = WindowModel() let commands = app.commands.commands(app: app, window: window) let ssh = try #require(commands.first { $0.target == .connectHost(host.id, .sshSession) }) let telnet = try #require(commands.first { $0.target == .connectHost(host.id, .telnetSession) }) #expect(ssh.symbol == MenuSymbol.ssh) #expect(telnet.symbol == MenuSymbol.telnet) #expect(ssh.symbol != telnet.symbol) let entries = HostMenuEntries.entries(for: host, app: app, window: window) let connect = try #require(entries.first { $0.title == String(localized: "Connect") }) let telnetEntry = try #require( connect.children.first { $0.title == String(localized: "with Telnet") }) #expect(telnetEntry.symbol == MenuSymbol.telnet) #expect( entries.map(\.title) == [ String(localized: "Quick Connect"), String(localized: "Connect"), String(localized: "Copy SSH Command"), "", String(localized: "Edit"), ]) let historyEntries = HostMenuEntries.entries( for: host, app: app, window: window, includesCopySSHCommand: false) #expect(!historyEntries.contains { $0.title == String(localized: "Copy SSH Command") }) } @Test(arguments: [false, true], [false, true]) func nativeWakeSharesTheSectionBeforeTailscale( isTailscaleManaged: Bool, includesCopySSHCommand: Bool ) throws { var host = Host(name: "Remote", host: "remote.example.invalid") host.wakeOnLAN.macAddress = "02:11:22:33:44:55" if isTailscaleManaged { host.tailscale = .init(groupID: UUID(), stableNodeID: "wake-test") } let app = AppModel(hosts: HostLibraryModel(hosts: [host])) defer { app.shutdownForTermination() } let entries = HostMenuEntries.entries( for: host, app: app, window: WindowModel(), includesCopySSHCommand: includesCopySSHCommand) let sections = entries.split(whereSeparator: \.isSeparator) #expect(sections.count == 3) let networkSection = try #require(sections.dropFirst().first) #expect( networkSection.map(\.title) == [ String(localized: AppStrings.LocalNetwork.wake) ] + (isTailscaleManaged ? [String(localized: AppStrings.Tailscale.title)] : [])) #expect(networkSection.first?.symbol == "power") #expect(sections.last?.first?.symbol == MenuSymbol.edit) } @Test(arguments: ["", " ", "invalid", "FF:FF:FF:FF:FF:FF"], [false, true]) func nativeWakeIsHiddenWithoutAValidMAC(macAddress: String, isTailscaleManaged: Bool) { var host = Host(name: "Remote", host: "remote.example.invalid") host.wakeOnLAN.macAddress = macAddress if isTailscaleManaged { host.tailscale = .init(groupID: UUID(), stableNodeID: "wake-test") } let app = AppModel(hosts: HostLibraryModel(hosts: [host])) defer { app.shutdownForTermination() } let entries = HostMenuEntries.entries(for: host, app: app, window: WindowModel()) #expect(!entries.contains { $0.title == String(localized: AppStrings.LocalNetwork.wake) }) #expect(entries.filter(\.isSeparator).count == (isTailscaleManaged ? 2 : 1)) #expect( entries.contains { $0.title == String(localized: AppStrings.Tailscale.title) } == isTailscaleManaged) } @Test func hostConnectMenusUseTheProtocolUnionAndFilterEachActionTargets() throws { let ssh = Host( name: "SSH", host: "ssh.example.invalid", sshEnabled: true, telnetEnabled: false) let telnet = Host( name: "Telnet", host: "telnet.example.invalid", sshEnabled: false, telnetEnabled: true) let both = Host( name: "Both", host: "both.example.invalid", sshEnabled: true, telnetEnabled: true) let neither = Host( name: "Neither", host: "neither.example.invalid", sshEnabled: false, telnetEnabled: false) #expect(HostConnectAction.available(for: [ssh]) == [.ssh, .sftp]) #expect(HostConnectAction.available(for: [telnet]) == [.telnet]) #expect(HostConnectAction.available(for: [both]) == [.ssh, .telnet, .sftp]) #expect(HostConnectAction.available(for: [neither]).isEmpty) #expect(HostConnectAction.available(for: [ssh, telnet]) == [.ssh, .telnet, .sftp]) #expect(HostConnectAction.ssh.matchingHosts(in: [ssh, telnet, both]).map(\.id) == [ssh.id, both.id]) #expect(HostConnectAction.telnet.matchingHosts(in: [ssh, telnet, both]).map(\.id) == [telnet.id, both.id]) #expect(HostConnectAction.sftp.matchingHosts(in: [ssh, telnet, both]).map(\.id) == [ssh.id, both.id]) let app = AppModel(hosts: HostLibraryModel(hosts: [ssh, telnet, both, neither])) let window = WindowModel() let sshEntries = HostMenuEntries.entries(for: ssh, app: app, window: window) let sshConnect = try #require(sshEntries.first { $0.title == String(localized: "Connect") }) #expect(sshConnect.children.map(\.title) == [String(localized: "with SSH"), String(localized: "with SFTP")]) #expect(!sshConnect.children.contains { !$0.isEnabled }) let telnetEntries = HostMenuEntries.entries(for: telnet, app: app, window: window) let telnetConnect = try #require(telnetEntries.first { $0.title == String(localized: "Connect") }) #expect(telnetConnect.children.map(\.title) == [String(localized: "with Telnet")]) #expect(!telnetConnect.children.contains { !$0.isEnabled }) #expect( !HostMenuEntries.entries(for: neither, app: app, window: window) .contains { $0.title == String(localized: "Connect") } ) } @Test func multiHostQuickConnectUsesOneWorkspace() throws { let ssh = Host(name: "SSH", host: "ssh.example.invalid") let telnet = Host( name: "Telnet", host: "telnet.example.invalid", sshEnabled: false, telnetEnabled: true) let app = AppModel(hosts: HostLibraryModel(hosts: [ssh, telnet])) let window = WindowModel() let layout = HostMenuPolicy.quickConnectLayout(for: [ssh, telnet]) #expect(layout == .oneWorkspace) #expect(HostMenuPolicy.quickConnectLayout(for: [ssh]) == .separateWorkspaces) app.open([ssh, telnet], as: .preferredTerminal, layout: layout, in: window) #expect(window.workspaces.count == 1) #expect(window.workspaces[0].panes.map(\.hostID) == [ssh.id, telnet.id]) } @Test(arguments: [BatchLayout.separateWorkspaces, .oneWorkspace]) func defaultOpeningUsesEachHostsTransportAndSkipsUnavailableHosts(layout: BatchLayout) throws { let transports = HostTerminalProtocol.allCases let hosts = transports.map { transport in var host = Host(name: transport.rawValue, host: "server.invalid", telnetEnabled: true) host.sshEnabled = transport != .telnet host.terminalTransports.moshEnabled = true host.terminalTransports.eternalTerminalEnabled = true if transport != .telnet { host.terminalTransports.defaultTransport = transport } return host } let disabled = Host(name: "Disabled", host: "disabled.invalid", sshEnabled: false) let app = AppModel(hosts: HostLibraryModel(hosts: hosts + [disabled])) let window = WindowModel() defer { window.closeAll() app.shutdownForTermination() } for (host, transport) in zip(hosts, transports) { let workspace = try #require(app.open(host, as: .preferredTerminal, in: window)) if transport == .telnet { #expect(workspace.focusedPane.telnetSession != nil) } else { #expect(workspace.focusedPane.sshSession?.transport == transport) } } window.closeAll() #expect(app.open(disabled, as: .preferredTerminal, in: window) == nil) app.open([disabled] + hosts, as: .preferredTerminal, layout: layout, in: window) let panes = window.workspaces.flatMap(\.panes) #expect(panes.map(\.hostID) == hosts.map(\.id)) #expect(panes.compactMap { $0.sshSession?.transport } == [.ssh, .mosh, .eternalTerminal]) #expect(panes.last?.telnetSession != nil) #expect(window.workspaces.count == (layout == .oneWorkspace ? 1 : hosts.count)) #expect(window.selectedWorkspaceID == window.workspaces.first?.id) #expect(app.hostLibrary.status(of: disabled.id) == .disconnected) } @Test(arguments: [HostTerminalProtocol.ssh, .mosh, .eternalTerminal]) func quickOpenPrioritizesAndOpensTheDefaultTransport(transport: HostTerminalProtocol) throws { var host = Host(name: "Production", host: "server.invalid") host.terminalTransports.moshEnabled = true host.terminalTransports.eternalTerminalEnabled = true host.terminalTransports.defaultTransport = transport let app = AppModel(hosts: HostLibraryModel(hosts: [host])) defer { app.shutdownForTermination() } let window = WindowModel() let results = QuickOpenResults(commands: app.commands.commands(app: app, window: window), query: host.name) let first = try #require(results.commands.first) #expect(first.symbol == transport.symbol) app.commands.perform(first.target, app: app, window: window) let session = try #require(window.selectedWorkspace?.focusedPane.sshSession) #expect(session.hostID == host.id) #expect(session.transport == transport) for (target, expected) in [ (HostOpenTarget.sshSession, HostTerminalProtocol.ssh), (.moshSession, .mosh), (.eternalTerminalSession, .eternalTerminal), ] { let command = try #require(results.commands.first { $0.target == .connectHost(host.id, target) }) app.commands.perform(command.target, app: app, window: window) #expect(window.selectedWorkspace?.focusedPane.sshSession?.transport == expected) } } @Test(arguments: [false, true], [false, true]) func quickOpenListsOnlyEnabledResumableTransports(moshEnabled: Bool, eternalTerminalEnabled: Bool) throws { var host = Host(name: "Production", host: "server.invalid", port: 2222, tags: ["critical"]) host.terminalTransports.moshEnabled = moshEnabled host.terminalTransports.eternalTerminalEnabled = eternalTerminalEnabled host.terminalTransports.eternalTerminalPort = 3022 let app = AppModel(hosts: HostLibraryModel(hosts: [host])) defer { app.shutdownForTermination() } let commands = app.commands.commands(app: app, window: WindowModel()).filter { $0.section == .hosts } #expect(commands.contains { $0.target == .connectHost(host.id, .moshSession) } == moshEnabled) #expect( commands.contains { $0.target == .connectHost(host.id, .eternalTerminalSession) } == eternalTerminalEnabled) #expect(commands.contains { $0.target == .connectHost(host.id, .sftpChannel) }) #expect(Set(commands.map(\.id)).count == commands.count) #expect(commands.allSatisfy { $0.matches("critical") }) if moshEnabled { let mosh = try #require(commands.first { $0.target == .connectHost(host.id, .moshSession) }) #expect(mosh.symbol == MenuSymbol.mosh) #expect(mosh.matches("Mosh")) #expect(mosh.subtitle == String(localized: AppStrings.Connections.connectWithMosh("server.invalid:2222"))) } if eternalTerminalEnabled { let et = try #require(commands.first { $0.target == .connectHost(host.id, .eternalTerminalSession) }) #expect(et.symbol == MenuSymbol.eternalTerminal) #expect(et.matches("Eternal Terminal")) #expect( et.subtitle == String(localized: AppStrings.Connections.connectWithEternalTerminal("server.invalid:3022"))) } } @Test func quickOpenFallsBackWhenTheDefaultTransportIsUnavailable() { func targets(for host: Host) -> [AppCommandTarget] { let app = AppModel(hosts: HostLibraryModel(hosts: [host])) defer { app.shutdownForTermination() } return app.commands.commands(app: app, window: WindowModel()) .filter { $0.section == .hosts }.map(\.target) } var host = Host(name: "Production", host: "server.invalid") host.terminalTransports.defaultTransport = .mosh #expect(targets(for: host) == [.connectHost(host.id, .sshSession), .connectHost(host.id, .sftpChannel)]) host.terminalTransports.moshEnabled = true host.terminalTransports.eternalTerminalEnabled = true host.tailscale = .init(groupID: UUID(), stableNodeID: "quick-open-test") #expect( targets(for: host) == [ .connectHost(host.id, .sshSession), .connectHost(host.id, .eternalTerminalSession), .connectHost(host.id, .sftpChannel), ]) host.sshEnabled = false host.telnetEnabled = true #expect(targets(for: host) == [.connectHost(host.id, .telnetSession)]) host.telnetEnabled = false #expect(targets(for: host).isEmpty) } @Test func registryCombinesManagementWorkspacesHostsAndActions() { let host = Host(name: "Production", host: "prod.example", tags: ["critical"]) let template = WorkspaceTemplate( name: "Production Layout", layout: .pane(.ssh(hostID: host.id)) ) let store = HostLibraryModel(hosts: [host], workspaceTemplates: [template]) let app = AppModel(hosts: store) let workspace = Workspace.sshSession(host: host) let window = WindowModel(workspaces: [workspace]) let commands = app.commands.commands(app: app, window: window) #expect(commands.contains { $0.target == .vaultPage(.hosts) }) #expect(commands.contains { $0.target == .vault }) #expect(commands.contains { $0.target == .assistant }) #expect(commands.contains { $0.target == .settings }) #expect(commands.contains { $0.target == .workspace(workspace.id) }) #expect(commands.contains { $0.target == .workspaceTemplate(template.id) }) #expect(commands.contains { $0.target == .connectHost(host.id, .sshSession) }) #expect(commands.contains { $0.target == .newChat }) #expect(commands.contains { $0.target == .showAssistant }) #expect(commands.filter { $0.matches("critical") }.count == 2) #expect( commands.filter { $0.section == .navigation }.allSatisfy { switch $0.target { case .vaultPage, .vault, .assistant, .settings: true default: false } }) #expect( commands.filter { $0.section == .workspaces }.allSatisfy { switch $0.target { case .workspace, .workspaceTemplate: true default: false } }) #expect(commands.filter { $0.section == .conversations }.isEmpty) #expect( commands.filter { $0.section == .hosts }.allSatisfy { if case .connectHost = $0.target { true } else { false } }) #expect( commands.filter { $0.section == .actions }.allSatisfy { switch $0.target { case .newChat, .newHost, .showAssistant, .newWindow, .moveWorkspaceToNewWindow: true default: false } }) } @Test func registryListsSearchesAndOpensWorkspaceTemplates() throws { let production = Host(name: "Production API", host: "prod.example") let staging = Host(name: "Staging Database", host: "staging.example") let fleetID = UUID(uuidString: "00000000-0000-0000-0000-000000000002")! let adminID = UUID(uuidString: "00000000-0000-0000-0000-000000000001")! let fleet = WorkspaceTemplate( id: fleetID, name: "Fleet", layout: .split( axis: .horizontal, fraction: 0.5, first: .pane(.ssh(hostID: production.id)), second: .pane(.sftp(hostID: staging.id)) ) ) let admin = WorkspaceTemplate( id: adminID, name: "Admin", layout: .pane(.empty) ) let store = HostLibraryModel( hosts: [production, staging], workspaceTemplates: [fleet, admin] ) let app = AppModel(hosts: store) let window = WindowModel() window.hostStore = store let templateCommands = app.commands.commands(app: app, window: window).filter { if case .workspaceTemplate = $0.target { true } else { false } } #expect(templateCommands.map(\.title) == ["Admin", "Fleet"]) #expect( templateCommands.map(\.id) == [ "workspace-template.\(adminID.uuidString)", "workspace-template.\(fleetID.uuidString)", ]) #expect(Set(templateCommands.map(\.id)).count == templateCommands.count) #expect(templateCommands.allSatisfy { $0.subtitle == "Workspace Template" }) #expect(templateCommands.allSatisfy { $0.symbol == "rectangle.split.2x1" }) #expect( QuickOpenResults(commands: templateCommands, query: "staging database") .commands.map(\.target) == [.workspaceTemplate(fleet.id)] ) app.commands.perform(.workspaceTemplate(fleet.id), app: app, window: window) let opened = try #require(window.selectedWorkspace) #expect(opened.sourceTemplateID == fleet.id) #expect(opened.panes.map(\.kind) == [.sshSession, .sftpChannel]) } @Test func registryIncludesActiveConversationsInLibraryOrderAndSearchesVisibleContent() throws { let pinned = ConversationModel( messages: [ ChatMessage(role: .assistant, text: "The nginx upstream is healthy.") ], sortIndex: 20) pinned.customTitle = "Pinned deployment" pinned.isPinned = true let firstChat = ConversationModel( messages: [ ChatMessage(role: .user, text: "Inspect the database vacuum schedule") ], sortIndex: 1) firstChat.customTitle = "Database maintenance" let secondChat = ConversationModel( messages: [ ChatMessage( role: .assistant, text: "The proxy is ready.", reasoning: [AgentReasoningBlock(text: "hidden-token")] ) ], sortIndex: 2) secondChat.customTitle = "NGINX rollout" let archived = ConversationModel( messages: [ ChatMessage(role: .user, text: "Archived nginx notes") ], sortIndex: 0) archived.customTitle = "Archived nginx chat" archived.archivedAt = .now let library = AssistantLibraryModel( store: AssistantConversationStore( conversations: [secondChat, archived, pinned, firstChat] )) let app = AppModel(hosts: HostLibraryModel()) let window = WindowModel(assistantLibrary: library) let conversationCommands = app.commands.commands(app: app, window: window).filter { $0.section == .conversations } #expect( conversationCommands.map(\.title) == [ "Pinned deployment", "Database maintenance", "NGINX rollout", ]) #expect(Set(conversationCommands.map(\.id)).count == conversationCommands.count) #expect( conversationCommands.map(\.target) == [ .assistantConversation(pinned.id), .assistantConversation(firstChat.id), .assistantConversation(secondChat.id), ]) #expect( !conversationCommands.contains { command in command.target == .assistantConversation(archived.id) }) let nginx = QuickOpenResults(commands: conversationCommands, query: "nginx") #expect( nginx.commands.map(\.target) == [ .assistantConversation(pinned.id), .assistantConversation(secondChat.id), ]) #expect(QuickOpenResults(commands: conversationCommands, query: "NGINX").commands == nginx.commands) #expect(QuickOpenResults(commands: conversationCommands, query: "hidden-token").commands.isEmpty) } @Test func resultsGroupMatchesAndKeyboardSelectionSkipsHeaders() throws { let host = Host(name: "Production", host: "prod.example", tags: ["critical"]) let store = HostLibraryModel(hosts: [host]) let app = AppModel(hosts: store) let workspace = Workspace.sshSession(host: host) let window = WindowModel(workspaces: [workspace]) let commands = app.commands.commands(app: app, window: window) let results = QuickOpenResults(commands: commands, query: "") #expect(results.sections == [.navigation, .workspaces, .hosts, .actions]) #expect(sectionItems(in: results.listItems(selectedID: nil)) == results.sections) let first = try #require(results.commands.first) let second = try #require(results.commands.dropFirst().first) let last = try #require(results.commands.last) #expect(results.selection(movingFrom: nil, by: 1) == first.id) #expect(results.selection(movingFrom: first.id, by: -1) == first.id) #expect(results.selection(movingFrom: first.id, by: 1) == second.id) #expect(results.selection(movingFrom: last.id, by: 1) == last.id) let filtered = QuickOpenResults(commands: commands, query: "critical") #expect(filtered.sections == [.hosts]) #expect(filtered.commands.count == 2) #expect( sectionItems(in: filtered.listItems(selectedID: filtered.commands.first?.id)) == [ .hosts ]) #expect(filtered.listItems(selectedID: filtered.commands.first?.id).filter(\.isSelected).count == 1) } @Test func registryRoutesWithoutDuplicatingNavigationLogic() { let store = HostLibraryModel() let app = AppModel(hosts: store) let window = WindowModel() #expect( app.commands.commands(app: app, window: window).contains { $0.target == .showAssistant } ) app.commands.perform(.settings, app: app, window: window) #expect(window.selectedDestination == .settings) #expect(window.settingsRoute == .index) #expect(window.workspaces.isEmpty) app.commands.perform(.showAssistant, app: app, window: window) #expect(window.selectedDestination == .assistant) app.commands.perform(.newChat, app: app, window: window) #expect(window.selectedDestination == .assistant) #expect(window.workspaces.isEmpty) #expect(window.assistantLibrary.conversations.isEmpty) #expect(window.assistantLibrary.selectedConversationID == nil) } @Test func registryOpensConversationInMainAssistantAndLeavesArchive() { let conversation = ConversationModel(messages: [ ChatMessage(role: .user, text: "Review deployment") ]) let library = AssistantLibraryModel( store: AssistantConversationStore( conversations: [conversation] )) let app = AppModel(hosts: HostLibraryModel()) let window = WindowModel(assistantLibrary: library) window.showAssistantArchive() app.commands.perform( .assistantConversation(conversation.id), app: app, window: window ) #expect(window.selectedDestination == .assistant) #expect(window.assistantLibrary.selectedConversationID == conversation.id) #expect(!window.isAssistantArchivePresented) } private func sectionItems(in items: [QuickOpenListItem]) -> [AppCommandSection] { items.compactMap { item in guard case .section(let section) = item else { return nil } return section } } }