Files
opsxshell/test/patrol/contracts.patrol.test.ts
Logan.LiandCursor 08696896f9 feat: harden local shell, SFTP, and desktop session reliability
Finder-launched terminals now inherit a login PATH so tools like ollama resolve, destructive SFTP/editor actions require confirmation, and terminal search plus window dragging work again. SSH forwards and RDP sessions keep listeners and survive reconnect without FastPath crashes.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-16 16:08:11 +08:00

127 lines
4.0 KiB
TypeScript

/**
* Platform / protocol contract patrol — catches missing commands or broken defaults.
*/
import { describe, expect, it } from 'vitest'
import { readFileSync } from 'node:fs'
import { resolve } from 'node:path'
import { mockDesktopApi } from '../setup'
import {
buildOpenTabPayload,
defaultPortForProtocol,
isSshSession,
sessionProtocol,
sessionToRdpConfig,
sessionToVncConfig,
tabTypeForProtocol,
} from '../../src/utils/sessionProtocol'
import { RDP_SESSION, VNC_SESSION, SSH_SESSION } from '../helpers/patrol'
import * as rdp from '../../src/platform/rdp'
import * as vnc from '../../src/platform/vnc'
import * as sftpPlatform from '../../src/platform/sftp'
const REQUIRED_SSH_KEYS = [
'sshConnect',
'sshDisconnect',
'sshCreateShell',
'sshShellWrite',
'sshShellResize',
'sshCloseShell',
'sshForwardLocal',
'sshForwardRemote',
'sshForwardDynamic',
'sshForwardStop',
] as const
const REQUIRED_SFTP_KEYS = [
'sftpConnect',
'sftpDisconnect',
'sftpListDirectory',
'sftpUpload',
'sftpUploadDir',
'sftpDownload',
'sftpDownloadDir',
'sftpMkdir',
'sftpRename',
'sftpDelete',
'sftpStat',
'sftpChmod',
] as const
describe('patrol / contracts', () => {
it('leaves HTML5 drag-and-drop available to the SFTP panes', () => {
const config = JSON.parse(
readFileSync(resolve(process.cwd(), 'src-tauri/tauri.conf.json'), 'utf8')
) as { app?: { windows?: Array<{ dragDropEnabled?: boolean }> } }
expect(config.app?.windows?.[0]?.dragDropEnabled).toBe(false)
})
it('exposes SSH + SFTP native commands on the desktop mock contract', () => {
for (const key of REQUIRED_SSH_KEYS) {
expect(typeof mockDesktopApi[key], key).toBe('function')
}
for (const key of REQUIRED_SFTP_KEYS) {
expect(typeof mockDesktopApi[key], key).toBe('function')
}
})
it('keeps protocol defaults for SSH / RDP / VNC', () => {
expect(defaultPortForProtocol('ssh')).toBe(22)
expect(defaultPortForProtocol('rdp')).toBe(3389)
expect(defaultPortForProtocol('vnc')).toBe(5900)
expect(tabTypeForProtocol('ssh')).toBe('terminal')
expect(tabTypeForProtocol('rdp')).toBe('rdp')
expect(tabTypeForProtocol('vnc')).toBe('vnc')
expect(isSshSession(SSH_SESSION)).toBe(true)
expect(isSshSession(RDP_SESSION)).toBe(false)
expect(sessionProtocol(VNC_SESSION)).toBe('vnc')
})
it('builds open-tab payloads with desktop configs for RDP/VNC', () => {
const rdpTab = buildOpenTabPayload(RDP_SESSION)
expect(rdpTab.type).toBe('rdp')
expect(rdpTab.rdpConfig).toEqual(
expect.objectContaining({
host: 'win.example.com',
port: 3389,
username: 'Administrator',
})
)
const vncTab = buildOpenTabPayload(VNC_SESSION)
expect(vncTab.type).toBe('vnc')
expect(vncTab.vncConfig).toEqual(
expect.objectContaining({
host: '192.168.1.50',
port: 5900,
})
)
const sshTab = buildOpenTabPayload(SSH_SESSION)
expect(sshTab.type).toBe('terminal')
expect(sshTab.rdpConfig).toBeUndefined()
expect(sshTab.vncConfig).toBeUndefined()
})
it('maps session credentials into RDP/VNC engine configs', () => {
expect(sessionToRdpConfig(RDP_SESSION).password).toBe('secret')
expect(sessionToVncConfig(VNC_SESSION).password).toBe('vnc-pass')
})
it('exports RDP / VNC / SFTP platform adapters', () => {
expect(typeof rdp.rdpConnect).toBe('function')
expect(typeof rdp.rdpDisconnect).toBe('function')
expect(typeof rdp.rdpResize).toBe('function')
expect(typeof vnc.vncConnect).toBe('function')
expect(typeof vnc.vncDisconnect).toBe('function')
expect(typeof sftpPlatform.uploadRemoteFile).toBe('function')
expect(typeof sftpPlatform.downloadRemoteFile).toBe('function')
})
it('refuses RDP/VNC connect outside Tauri runtime', async () => {
await expect(rdp.rdpConnect(sessionToRdpConfig(RDP_SESSION))).rejects.toThrow(/原生|native|Tauri|RDP/i)
await expect(vnc.vncConnect(sessionToVncConfig(VNC_SESSION))).rejects.toThrow(/原生|native|Tauri|VNC/i)
})
})