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>
This commit is contained in:
1 parent
188f3556ed
commit
08696896f9
223 files changed
+13493
-3423
No files matched your search
@@ -0,0 +1,135 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import {
|
||||
clearForwardIntent,
|
||||
forgetForwardIntent,
|
||||
forwardIntentKey,
|
||||
listForwardIntent,
|
||||
rememberForwardIntent,
|
||||
resetForwardIntentForTests,
|
||||
restoreForwardIntent,
|
||||
tunnelCoversRule,
|
||||
} from '../../../src/components/connection/forwardIntent'
|
||||
import type { ForwardRule } from '../../../src/components/connection/forwardRules'
|
||||
import type { SSHForwardStatus } from '../../../shared/types/ipc.types'
|
||||
|
||||
const localRule: ForwardRule = {
|
||||
id: 'pf-local',
|
||||
type: 'local',
|
||||
name: 'db',
|
||||
localPort: 3306,
|
||||
remoteHost: '127.0.0.1',
|
||||
remotePort: 3306,
|
||||
}
|
||||
|
||||
function tunnel(overrides: Partial<SSHForwardStatus> = {}): SSHForwardStatus {
|
||||
return {
|
||||
forwardId: 'pf-local',
|
||||
connectionId: 'conn-1',
|
||||
kind: 'local',
|
||||
bindHost: '127.0.0.1',
|
||||
boundPort: 3306,
|
||||
startedAt: 1,
|
||||
running: true,
|
||||
activeConnections: 0,
|
||||
bytesUp: 0,
|
||||
bytesDown: 0,
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
resetForwardIntentForTests()
|
||||
})
|
||||
|
||||
describe('forward reconnect intent', () => {
|
||||
it('keys by session so a new connection id does not drop remembered rules', () => {
|
||||
expect(forwardIntentKey('sess-1', 'tab-1')).toBe('session:sess-1')
|
||||
expect(forwardIntentKey(undefined, 'tab-1')).toBe('tab:tab-1')
|
||||
})
|
||||
|
||||
it('remembers start intent and forgets explicit stop', () => {
|
||||
const key = forwardIntentKey('sess-1', 'tab-1')
|
||||
rememberForwardIntent(key, localRule)
|
||||
expect(listForwardIntent(key)).toEqual([localRule])
|
||||
forgetForwardIntent(key, localRule.id)
|
||||
expect(listForwardIntent(key)).toEqual([])
|
||||
})
|
||||
|
||||
it('matches a running tunnel by rule id or listen port after reconnect', () => {
|
||||
expect(tunnelCoversRule([tunnel()], localRule)).toBe(true)
|
||||
expect(
|
||||
tunnelCoversRule([tunnel({ forwardId: 'legacy', boundPort: 3306 })], localRule)
|
||||
).toBe(true)
|
||||
expect(
|
||||
tunnelCoversRule([tunnel({ running: false })], localRule)
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
it('matches remote forwards by remote listen port, not local target port', () => {
|
||||
const remoteRule: ForwardRule = {
|
||||
id: 'pf-remote',
|
||||
type: 'remote',
|
||||
name: 'expose',
|
||||
localPort: 8080,
|
||||
remoteHost: '127.0.0.1',
|
||||
remotePort: 18080,
|
||||
}
|
||||
expect(
|
||||
tunnelCoversRule(
|
||||
[tunnel({ forwardId: 'x', kind: 'remote', boundPort: 18080 })],
|
||||
remoteRule
|
||||
)
|
||||
).toBe(true)
|
||||
expect(
|
||||
tunnelCoversRule(
|
||||
[tunnel({ forwardId: 'x', kind: 'remote', boundPort: 8080 })],
|
||||
remoteRule
|
||||
)
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
it('clears remembered intent when a tab is disposed', () => {
|
||||
const key = forwardIntentKey('sess-1', 'tab-1')
|
||||
rememberForwardIntent(key, localRule)
|
||||
clearForwardIntent(key)
|
||||
expect(listForwardIntent(key)).toEqual([])
|
||||
})
|
||||
|
||||
it('restores only rules that are not already running', async () => {
|
||||
const key = forwardIntentKey('sess-1', 'tab-1')
|
||||
rememberForwardIntent(key, localRule)
|
||||
rememberForwardIntent(key, { ...localRule, id: 'pf-other', localPort: 8080 })
|
||||
const start = vi.fn().mockResolvedValue(true)
|
||||
|
||||
const restored = await restoreForwardIntent(
|
||||
key,
|
||||
(rule) => rule.id === 'pf-local',
|
||||
start
|
||||
)
|
||||
|
||||
expect(restored).toBe(1)
|
||||
expect(start).toHaveBeenCalledTimes(1)
|
||||
expect(start).toHaveBeenCalledWith(expect.objectContaining({ id: 'pf-other' }))
|
||||
})
|
||||
|
||||
it('shares one restore pass when StatusBar and PortForwardView race', async () => {
|
||||
const key = forwardIntentKey('sess-1', 'tab-1')
|
||||
rememberForwardIntent(key, localRule)
|
||||
let release!: (value: boolean) => void
|
||||
const start = vi.fn(
|
||||
() =>
|
||||
new Promise<boolean>((resolve) => {
|
||||
release = resolve
|
||||
})
|
||||
)
|
||||
|
||||
const first = restoreForwardIntent(key, () => false, start)
|
||||
const second = restoreForwardIntent(key, () => false, start)
|
||||
release(true)
|
||||
|
||||
await expect(first).resolves.toBe(1)
|
||||
await expect(second).resolves.toBe(1)
|
||||
expect(start).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
@@ -2,9 +2,13 @@ import { describe, expect, it } from 'vitest'
|
||||
|
||||
import {
|
||||
duplicateForwardRule,
|
||||
formatHostPort,
|
||||
formatTransferBytes,
|
||||
parseForwardRules,
|
||||
parseHostPort,
|
||||
portForwardStorageKey,
|
||||
resolveForwardTargetHost,
|
||||
resolveRemoteBindHost,
|
||||
runtimeMap,
|
||||
} from '../../../src/components/connection/forwardRules'
|
||||
|
||||
@@ -59,4 +63,35 @@ describe('forward rule persistence', () => {
|
||||
expect(second.name).toBe('SOCKS5 副本 2')
|
||||
expect(second).not.toHaveProperty('isActive')
|
||||
})
|
||||
|
||||
it('parses and formats host:port listen/target addresses', () => {
|
||||
expect(parseHostPort('127.0.0.1:18080')).toEqual({ host: '127.0.0.1', port: 18080 })
|
||||
expect(parseHostPort('0.0.0.0:8080')).toEqual({ host: '0.0.0.0', port: 8080 })
|
||||
expect(parseHostPort('bad')).toBeNull()
|
||||
expect(formatHostPort('0.0.0.0', 8080)).toBe('0.0.0.0:8080')
|
||||
expect(resolveForwardTargetHost('0.0.0.0')).toBe('127.0.0.1')
|
||||
})
|
||||
|
||||
it('keeps remote listen bind including 0.0.0.0 for GatewayPorts', () => {
|
||||
const rules = parseForwardRules([
|
||||
{
|
||||
id: 'r1',
|
||||
type: 'remote',
|
||||
name: 'expose',
|
||||
localPort: 8080,
|
||||
remoteBindHost: '0.0.0.0',
|
||||
remoteHost: '192.168.1.10',
|
||||
remotePort: 18080,
|
||||
},
|
||||
])
|
||||
expect(rules[0]).toMatchObject({
|
||||
type: 'remote',
|
||||
remoteBindHost: '0.0.0.0',
|
||||
remoteHost: '192.168.1.10',
|
||||
remotePort: 18080,
|
||||
localPort: 8080,
|
||||
})
|
||||
expect(resolveRemoteBindHost('localhost')).toBe('127.0.0.1')
|
||||
expect(resolveRemoteBindHost('0.0.0.0')).toBe('0.0.0.0')
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user