import { fireEvent, render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { beforeEach, describe, expect, it, vi } from 'vitest' import SFTPView from '../../src/components/sftp/SFTPView' import { usePoolStore } from '../../src/stores/connection/poolStore' import { useSessionStore } from '../../src/stores/sessionStore' import { useTabStore } from '../../src/stores/tabStore' import { mockDesktopApi } from '../setup' const session = { id: 'opsbase', name: 'OpsBase', host: 'opsbase.cn', port: 55555, username: 'root', authType: 'agent' as const, } const remoteFile = { filename: 'remote.txt', longname: '-rw-r--r-- 1 root root 12 Jul 30 12:00 remote.txt', attrs: { size: 12, mtime: 1_700_000_000, mode: 0o100644 }, } function createDataTransfer(): DataTransfer { const values = new Map() const transfer = { effectAllowed: 'all', dropEffect: 'none', files: [] as unknown as FileList, types: [] as string[], setData(type: string, value: string) { values.set(type, value) if (!this.types.includes(type)) this.types.push(type) }, getData(type: string) { return values.get(type) || '' }, } return transfer as unknown as DataTransfer } describe('SFTP basic transfer acceptance', () => { beforeEach(() => { vi.clearAllMocks() window.opsxApi = mockDesktopApi usePoolStore.setState({ connections: new Map() }) useSessionStore.setState({ sessions: [session] }) useTabStore.setState({ tabs: [{ id: 'sftp-tab', title: 'SFTP: OpsBase', type: 'sftp', sessionId: session.id, isActive: true }], activeTabId: 'sftp-tab', }) vi.mocked(mockDesktopApi.sshConnect).mockResolvedValue({ success: true, connectionId: 'conn-sftp' }) vi.mocked(mockDesktopApi.sessionGetById).mockResolvedValue({ success: true, session }) vi.mocked(mockDesktopApi.sftpConnect).mockResolvedValue({ success: true, sftpId: 'sftp-native' }) vi.mocked(mockDesktopApi.sftpListDirectory).mockResolvedValue({ success: true, files: [remoteFile] }) vi.mocked(mockDesktopApi.getHomePath).mockResolvedValue({ success: true, path: '/Users/test' }) vi.mocked(mockDesktopApi.localReadDir).mockResolvedValue({ success: true, files: [] }) }) it('uploads the absolute path returned by the native file picker', async () => { const user = userEvent.setup() vi.mocked(mockDesktopApi.showOpenDialog).mockResolvedValueOnce({ canceled: false, filePaths: ['/tmp/actual-selected.txt'], }) vi.mocked(mockDesktopApi.localGetStats).mockResolvedValueOnce({ success: true, stats: { name: 'actual-selected.txt', path: '/tmp/actual-selected.txt', type: 'file', size: 17 }, }) render() await screen.findByText('已连接') await user.click(screen.getByRole('button', { name: '上传' })) await waitFor(() => { expect(mockDesktopApi.sftpUpload).toHaveBeenCalledWith( 'sftp-native', '/tmp/actual-selected.txt', '/root/actual-selected.txt' ) }) }) it('downloads a selected remote file into the current local directory', async () => { const user = userEvent.setup() render() await screen.findByText('已连接') await user.click(await screen.findByText('remote.txt')) await user.click(screen.getByRole('button', { name: '下载' })) await waitFor(() => { expect(mockDesktopApi.sftpDownload).toHaveBeenCalledWith( 'sftp-native', '/root/remote.txt', '/Users/test/remote.txt' ) }) }) it('shows the native upload failure instead of reporting success', async () => { const user = userEvent.setup() vi.mocked(mockDesktopApi.showOpenDialog).mockResolvedValueOnce({ canceled: false, filePaths: ['/tmp/denied.txt'], }) vi.mocked(mockDesktopApi.localGetStats).mockResolvedValueOnce({ success: true, stats: { name: 'denied.txt', path: '/tmp/denied.txt', type: 'file', size: 4 }, }) vi.mocked(mockDesktopApi.sftpUpload).mockResolvedValueOnce({ success: false, error: 'Permission denied', }) render() await screen.findByText('已连接') await user.click(screen.getByRole('button', { name: '上传' })) expect(await screen.findByText(/Permission denied/)).toBeVisible() }) it('uploads by dragging a local row onto the remote panel', async () => { vi.mocked(mockDesktopApi.localReadDir).mockResolvedValue({ success: true, files: [{ name: 'local.txt', path: '/Users/test/local.txt', type: 'file', size: 8, modified: 1_700_000_000 }], }) render() await screen.findByText('已连接') const transfer = createDataTransfer() fireEvent.dragStart(await screen.findByText('local.txt'), { dataTransfer: transfer }) fireEvent.drop(screen.getByLabelText('远程文件面板'), { dataTransfer: transfer }) await waitFor(() => { expect(mockDesktopApi.sftpUpload).toHaveBeenCalledWith( 'sftp-native', '/Users/test/local.txt', '/root/local.txt' ) }) }) it('downloads by dragging a remote row onto the local panel', async () => { render() await screen.findByText('已连接') const transfer = createDataTransfer() fireEvent.dragStart(await screen.findByText('remote.txt'), { dataTransfer: transfer }) fireEvent.drop(screen.getByLabelText('本地文件面板'), { dataTransfer: transfer }) await waitFor(() => { expect(mockDesktopApi.sftpDownload).toHaveBeenCalledWith( 'sftp-native', '/root/remote.txt', '/Users/test/remote.txt' ) }) }) })