import { render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { beforeEach, describe, expect, it, vi } from 'vitest' import SessionDialog from '../../src/components/sessions/SessionDialog' import QuickConnectDialog from '../../src/components/sessions/QuickConnectDialog' import { useSessionStore } from '../../src/stores/sessionStore' import { useTabStore } from '../../src/stores/tabStore' import { mockDesktopApi } from '../setup' import { unlockProForTests } from '../helpers/entitlement' describe('Session workflow acceptance', () => { beforeEach(() => { vi.clearAllMocks() unlockProForTests() window.opsxApi = mockDesktopApi useSessionStore.setState({ sessions: [] }) useTabStore.setState({ tabs: [{ id: 'welcome', title: '欢迎', type: 'welcome', isActive: true }], activeTabId: 'welcome', }) }) it('creates and saves a session in the selected group', async () => { const user = userEvent.setup() const onClose = vi.fn() render() await user.type(screen.getByPlaceholderText('自动用 user@host'), 'OpsBase 验收') await user.clear(screen.getByPlaceholderText('192.168.1.10')) await user.type(screen.getByPlaceholderText('192.168.1.10'), 'root@opsbase.cn:55555') expect(screen.getByLabelText('端口')).toHaveValue(55555) await user.selectOptions(screen.getByLabelText('分组'), '__new__') await user.type(screen.getByPlaceholderText('如:生产环境'), '验收环境') await user.click(screen.getByRole('button', { name: '创建' })) await user.click(screen.getByRole('button', { name: '创建并保存' })) await waitFor(() => { expect(mockDesktopApi.sessionCreate).toHaveBeenCalledWith( expect.objectContaining({ host: 'opsbase.cn', port: 55555, username: 'root', group: '验收环境', }) ) }) expect(useSessionStore.getState().sessions).toEqual([ expect.objectContaining({ name: 'OpsBase 验收', host: 'opsbase.cn', port: 55555, group: '验收环境', }), ]) expect(onClose).toHaveBeenCalledOnce() }) it('defaults new sessions to port 22 and password authentication', async () => { const user = userEvent.setup() render() expect(screen.getByLabelText('端口')).toHaveValue(22) expect(screen.getByRole('button', { name: '密码' })).toBeVisible() expect(screen.getByRole('button', { name: '密钥' })).toBeVisible() expect(screen.queryByRole('button', { name: 'Agent' })).not.toBeInTheDocument() await user.clear(screen.getByLabelText('主机')) await user.type(screen.getByLabelText('主机'), 'admin@example.com') await user.type(screen.getByLabelText('密码'), 'session-secret') await user.click(screen.getByRole('button', { name: '创建并保存' })) await waitFor(() => { expect(mockDesktopApi.sessionCreate).toHaveBeenCalledWith( expect.objectContaining({ host: 'example.com', port: 22, username: 'admin', authType: 'password', password: 'session-secret', privateKey: undefined, passphrase: undefined, }) ) }) }) it('saves key authentication without retaining a password', async () => { const user = userEvent.setup() render() await user.clear(screen.getByLabelText('主机')) await user.type(screen.getByLabelText('主机'), 'root@opsbase.cn') await user.click(screen.getByRole('button', { name: '密钥' })) expect(screen.getByRole('button', { name: '文件' })).toBeVisible() expect(screen.getByRole('button', { name: '文本' })).toBeVisible() await user.clear(screen.getByLabelText('私钥路径')) await user.type(screen.getByLabelText('私钥路径'), '~/.ssh/id_ed25519') await user.type(screen.getByLabelText('密钥口令(可选)'), 'key-secret') await user.click(screen.getByRole('button', { name: '创建并保存' })) await waitFor(() => { expect(mockDesktopApi.sessionCreate).toHaveBeenCalledWith( expect.objectContaining({ port: 22, authType: 'key', password: undefined, privateKey: '~/.ssh/id_ed25519', passphrase: 'key-secret', }) ) }) }) it('saves pasted PEM private key as text content', async () => { const user = userEvent.setup() const pem = [ '-----BEGIN OPENSSH PRIVATE KEY-----', 'b3BlbnNzaC1rZXktdjEAAAAABG5vbmU=', '-----END OPENSSH PRIVATE KEY-----', ].join('\n') render() await user.clear(screen.getByLabelText('主机')) await user.type(screen.getByLabelText('主机'), 'root@opsbase.cn') await user.click(screen.getByRole('button', { name: '密钥' })) await user.click(screen.getByRole('button', { name: '文本' })) await user.click(screen.getByLabelText('私钥内容')) await user.paste(pem) await user.click(screen.getByRole('button', { name: '创建并保存' })) await waitFor(() => { expect(mockDesktopApi.sessionCreate).toHaveBeenCalledWith( expect.objectContaining({ authType: 'key', privateKey: pem, password: undefined, }) ) }) }) it('saves a SOCKS5 connection proxy with optional credentials', async () => { const user = userEvent.setup() render() await user.clear(screen.getByLabelText('主机')) await user.type(screen.getByLabelText('主机'), 'admin@internal.example.com') await user.click(screen.getByRole('button', { name: /高级选项/ })) await user.click(screen.getByRole('button', { name: 'SOCKS5' })) await user.type(screen.getByPlaceholderText('代理地址'), '127.0.0.1') await user.type(screen.getByPlaceholderText('用户名(可选)'), 'proxy-user') await user.type(screen.getByPlaceholderText('密码(可选)'), 'proxy-secret') await user.click(screen.getByRole('button', { name: '创建并保存' })) await waitFor(() => { expect(mockDesktopApi.sessionCreate).toHaveBeenCalledWith( expect.objectContaining({ proxy: { type: 'socks5', host: '127.0.0.1', port: 1080, username: 'proxy-user', password: 'proxy-secret', }, }) ) }) }) it('saves one existing session as a ProxyJump host', async () => { const user = userEvent.setup() useSessionStore.setState({ sessions: [ { id: 'jump-1', name: 'Bastion', host: 'gateway.example.com', port: 22, username: 'ops', authType: 'agent', }, ], }) render() await user.clear(screen.getByLabelText('主机')) await user.type(screen.getByLabelText('主机'), 'root@internal.example.com') await user.click(screen.getByRole('button', { name: /高级选项/ })) await user.selectOptions(screen.getByLabelText('跳板机(可选)'), 'jump-1') await user.click(screen.getByRole('button', { name: '创建并保存' })) await waitFor(() => { expect(mockDesktopApi.sessionCreate).toHaveBeenCalledWith( expect.objectContaining({ jumpSessionId: 'jump-1', jumpSessionIds: ['jump-1'], proxy: undefined, }) ) }) }) it('creates a quick-connect terminal tab with the parsed target', async () => { const user = userEvent.setup() const onClose = vi.fn() render() await user.type(screen.getByPlaceholderText('root@192.168.1.10:22'), 'root@opsbase.cn:55555') await user.click(screen.getByRole('button', { name: '连接' })) const terminalTab = useTabStore .getState() .tabs.find((tab) => tab.type === 'terminal' && tab.title === 'root@opsbase.cn') expect(terminalTab?.connectionConfig).toEqual({ host: 'opsbase.cn', port: 55555, username: 'root', password: undefined, }) expect(onClose).toHaveBeenCalledOnce() }) it('accepts pasted targets in quick connect and new session inputs', async () => { const user = userEvent.setup() const quick = render() const quickTarget = screen.getByPlaceholderText('root@192.168.1.10:22') await user.click(quickTarget) await user.paste('root@opsbase.cn:55555') expect(quickTarget).toHaveValue('root@opsbase.cn:55555') quick.unmount() render() const sessionTarget = screen.getByLabelText('主机') await user.clear(sessionTarget) await user.paste('admin@example.com:2200') expect(sessionTarget).toHaveValue('admin@example.com:2200') expect(screen.getByLabelText('端口')).toHaveValue(2200) }) it('clears quick-connect credentials whenever the dialog is reopened', async () => { const user = userEvent.setup() const { rerender } = render() await user.type(screen.getByPlaceholderText('root@192.168.1.10:22'), 'root@example.com') await user.type(screen.getByPlaceholderText('••••••••'), 'temporary-secret') rerender() rerender() expect(screen.getByPlaceholderText('root@192.168.1.10:22')).toHaveValue('') expect(screen.getByPlaceholderText('••••••••')).toHaveValue('') }) })