新增 entitlement/license IPC 与 Waffo 一次性结账服务骨架,挂载 AI/转发/代理/串口/广播/批量导入导出门禁;批量执行改为结果矩阵并复用连接池,SFTP 工具栏图标化并去掉已连接条,同时完善跨平台本地终端与窗口配置。 Co-authored-by: Cursor <cursoragent@cursor.com>
225 lines
8.6 KiB
TypeScript
225 lines
8.6 KiB
TypeScript
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(<SessionDialog isOpen onClose={onClose} editSession={null} />)
|
|
|
|
await user.type(screen.getByPlaceholderText('自动用 user@host'), 'OpsBase 验收')
|
|
await user.clear(screen.getByPlaceholderText('root@192.168.1.10'))
|
|
await user.type(screen.getByPlaceholderText('root@192.168.1.10'), 'root@opsbase.cn:55555')
|
|
expect(screen.getByLabelText('端口')).toHaveValue(55555)
|
|
await user.click(screen.getByRole('button', { name: /高级选项/ }))
|
|
await user.clear(screen.getByPlaceholderText('default'))
|
|
await user.type(screen.getByPlaceholderText('default'), '验收环境')
|
|
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(<SessionDialog isOpen onClose={vi.fn()} editSession={null} />)
|
|
|
|
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(<SessionDialog isOpen onClose={vi.fn()} editSession={null} />)
|
|
|
|
await user.clear(screen.getByLabelText('目标'))
|
|
await user.type(screen.getByLabelText('目标'), 'root@opsbase.cn')
|
|
await user.click(screen.getByRole('button', { name: '密钥' }))
|
|
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 a SOCKS5 connection proxy with optional credentials', async () => {
|
|
const user = userEvent.setup()
|
|
render(<SessionDialog isOpen onClose={vi.fn()} editSession={null} />)
|
|
|
|
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(<SessionDialog isOpen onClose={vi.fn()} editSession={null} />)
|
|
|
|
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(<QuickConnectDialog isOpen onClose={onClose} />)
|
|
|
|
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(<QuickConnectDialog isOpen onClose={vi.fn()} />)
|
|
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(<SessionDialog isOpen onClose={vi.fn()} editSession={null} />)
|
|
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(<QuickConnectDialog isOpen onClose={vi.fn()} />)
|
|
|
|
await user.type(screen.getByPlaceholderText('root@192.168.1.10:22'), 'root@example.com')
|
|
await user.type(screen.getByPlaceholderText('••••••••'), 'temporary-secret')
|
|
|
|
rerender(<QuickConnectDialog isOpen={false} onClose={vi.fn()} />)
|
|
rerender(<QuickConnectDialog isOpen onClose={vi.fn()} />)
|
|
|
|
expect(screen.getByPlaceholderText('root@192.168.1.10:22')).toHaveValue('')
|
|
expect(screen.getByPlaceholderText('••••••••')).toHaveValue('')
|
|
})
|
|
})
|