Files
Netcatty/components/sftp/sftpSidePanelAutoConnect.test.ts
T
陈大猫 6fad3f0b76 fix(sftp): reconnect terminal side panel after SSH session loss (#1608) (#1621)
* fix(sftp): reconnect terminal side panel after SSH session loss

When SSH disconnects while the terminal SFTP side panel is browsing a stale
path, auto-connect reused a dead tab and left the pane stuck on loading.
Rebind SFTP when the terminal session changes and skip reuse unless the
backend session is healthy; also stop follow-terminal-cwd retry loops to
missing directories.

Fixes #1608

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(sftp): address review findings for side panel reconnect

Harden auto-connect after bugbot review: skip tab reuse on SSH session
change, move follow-cwd backoff ref into the interactive body, and make
navigateTo return reached/failed/aborted so follow mode only backs off on
real path failures.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-24 14:31:23 +08:00

99 lines
3.0 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
import type { SftpPane } from "../../application/state/sftp/types";
import {
findReusableSftpSidePanelTab,
isRemoteSftpTabHealthy,
shouldResetSftpSidePanelSourceSession,
shouldSkipSftpSidePanelAutoConnect,
} from "./sftpSidePanelAutoConnect";
const remoteConnectedTab = (overrides: Partial<SftpPane> = {}): SftpPane => ({
id: "tab-1",
connection: {
id: "conn-1",
hostId: "host-1",
hostLabel: "server",
isLocal: false,
status: "connected",
currentPath: "/var/www",
},
files: [],
loading: false,
reconnecting: false,
error: null,
connectionLogs: [],
selectedFiles: new Set(),
filter: "",
filenameEncoding: "auto",
showHiddenFiles: false,
transferMutationToken: 0,
...overrides,
});
test("isRemoteSftpTabHealthy rejects loading tabs", () => {
const tab = remoteConnectedTab({ loading: true });
assert.equal(isRemoteSftpTabHealthy(tab, true), false);
});
test("isRemoteSftpTabHealthy rejects tabs without a backend SFTP session", () => {
const tab = remoteConnectedTab();
assert.equal(isRemoteSftpTabHealthy(tab, false), false);
});
test("isRemoteSftpTabHealthy rejects connecting tabs", () => {
const tab = remoteConnectedTab({
connection: {
...remoteConnectedTab().connection!,
status: "connecting",
},
});
assert.equal(isRemoteSftpTabHealthy(tab, true), false);
});
test("shouldSkipSftpSidePanelAutoConnect returns false for stale connected keys", () => {
const tab = remoteConnectedTab({ loading: true });
assert.equal(
shouldSkipSftpSidePanelAutoConnect("host-key", "host-key", tab, true),
false,
);
});
test("findReusableSftpSidePanelTab ignores tabs stuck in loading after SSH disconnect", () => {
const tab = remoteConnectedTab({ loading: true });
const map = new Map([[tab.id, "host-key"]]);
assert.equal(
findReusableSftpSidePanelTab([tab], "host-1", "host-key", map, () => true),
null,
);
});
test("findReusableSftpSidePanelTab returns healthy tabs", () => {
const tab = remoteConnectedTab();
const map = new Map([[tab.id, "host-key"]]);
assert.equal(
findReusableSftpSidePanelTab([tab], "host-1", "host-key", map, () => true),
tab,
);
});
test("shouldResetSftpSidePanelSourceSession detects terminal session changes", () => {
assert.equal(shouldResetSftpSidePanelSourceSession("sess-a", "sess-b"), true);
assert.equal(shouldResetSftpSidePanelSourceSession("sess-a", "sess-a"), false);
assert.equal(shouldResetSftpSidePanelSourceSession(null, "sess-a"), false);
assert.equal(shouldResetSftpSidePanelSourceSession("sess-a", null), false);
});
test("shouldSkipSftpSidePanelAutoConnect returns false after terminal session changes", () => {
const tab = remoteConnectedTab();
assert.equal(
shouldSkipSftpSidePanelAutoConnect("host-key", "host-key", tab, true),
true,
);
// Caller gates on sessionChanged before invoking skip — stale reuse must not win.
assert.equal(
shouldResetSftpSidePanelSourceSession("sess-a", "sess-b"),
true,
);
});