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('')
})
})