mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-30 07:05:24 +00:00
feat(file-manager): default to the remote login directory (#1512)
This commit is contained in:
1 parent
e69029fbf7
commit
fdb2f74259
6 files changed
+195
-9
No files matched your search
@@ -0,0 +1,99 @@
|
||||
import { EventEmitter } from "node:events";
|
||||
import type { Express, Request, Response } from "express";
|
||||
import type { PluginContext } from "@termix/plugin-sdk/backend";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { registerFileListingRoutes } from "../../src/backend/list-routes.js";
|
||||
import {
|
||||
execChannel,
|
||||
getSessionSftp,
|
||||
type SSHSession,
|
||||
} from "../../src/backend/session.js";
|
||||
import { resolveHomeDirectory } from "../../src/backend/home-directory.js";
|
||||
|
||||
vi.mock("../../src/backend/session.js", () => ({
|
||||
getSessionSftp: vi.fn(),
|
||||
execChannel: vi.fn(),
|
||||
}));
|
||||
beforeEach(() => vi.resetAllMocks());
|
||||
|
||||
function setup(home: string, owned = true) {
|
||||
const sftp = {
|
||||
realpath: vi.fn((_path, done) => done(null, home)),
|
||||
readdir: vi.fn((_path, done) => done(null, [])),
|
||||
};
|
||||
vi.mocked(getSessionSftp).mockResolvedValue(sftp as never);
|
||||
let handler!: (req: Request, res: Response) => Promise<unknown>;
|
||||
registerFileListingRoutes(
|
||||
{
|
||||
get: (_path, cb) => {
|
||||
handler = cb;
|
||||
},
|
||||
} as Express,
|
||||
{
|
||||
ctx: {
|
||||
currentActor: () => "user",
|
||||
log: { info: vi.fn(), warn: vi.fn(), error: vi.fn() },
|
||||
} as unknown as PluginContext,
|
||||
sshSessions: {
|
||||
s: { isConnected: true, activeOperations: 0 } as SSHSession,
|
||||
},
|
||||
activeListRequests: {},
|
||||
verifySessionOwnership: () => owned,
|
||||
},
|
||||
);
|
||||
const res = { on: vi.fn(), status: vi.fn().mockReturnThis(), json: vi.fn() };
|
||||
return {
|
||||
sftp,
|
||||
res,
|
||||
list: (path?: string) =>
|
||||
handler(
|
||||
{ query: { sessionId: "s", path } } as unknown as Request,
|
||||
res as unknown as Response,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
describe("remote login directory", () => {
|
||||
it.each(["/root", "/srv/users/alice", "/"])(
|
||||
"lists the actual SFTP directory %s",
|
||||
async (home) => {
|
||||
const { sftp, res, list } = setup(home);
|
||||
await list(".");
|
||||
await vi.waitFor(() =>
|
||||
expect(res.json).toHaveBeenCalledWith({ files: [], path: home }),
|
||||
);
|
||||
expect(sftp.realpath).toHaveBeenCalledWith(".", expect.any(Function));
|
||||
expect(sftp.readdir).toHaveBeenCalledWith(home, expect.any(Function));
|
||||
expect(execChannel).not.toHaveBeenCalled();
|
||||
},
|
||||
);
|
||||
it("preserves an explicit root instead of replacing it with home", async () => {
|
||||
const { sftp, res, list } = setup("/home/alice");
|
||||
await list("/");
|
||||
await vi.waitFor(() =>
|
||||
expect(res.json).toHaveBeenCalledWith({ files: [], path: "/" }),
|
||||
);
|
||||
expect(sftp.realpath).not.toHaveBeenCalled();
|
||||
});
|
||||
it("checks ownership before discovering the home directory", async () => {
|
||||
const { res, list } = setup("/home/alice", false);
|
||||
await list(".");
|
||||
expect(res.status).toHaveBeenCalledWith(403);
|
||||
expect(getSessionSftp).not.toHaveBeenCalled();
|
||||
});
|
||||
it("falls back to the shell login directory when SFTP is unavailable", async () => {
|
||||
vi.mocked(getSessionSftp).mockRejectedValue(new Error("SFTP unavailable"));
|
||||
vi.mocked(execChannel).mockImplementation((_session, command, done) => {
|
||||
expect(command).toBe("pwd -P");
|
||||
const stream = new EventEmitter();
|
||||
done(undefined as never, stream as never);
|
||||
queueMicrotask(() => {
|
||||
stream.emit("data", Buffer.from("/srv/alice\n"));
|
||||
stream.emit("close", 0);
|
||||
});
|
||||
});
|
||||
await expect(resolveHomeDirectory({} as SSHSession)).resolves.toBe(
|
||||
"/srv/alice",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -124,6 +124,35 @@ async function selectHosts() {
|
||||
}
|
||||
|
||||
describe("SftpTransferTab", () => {
|
||||
it("opens the server login directory when no default path is configured", async () => {
|
||||
const hosts = await api.getSSHHosts();
|
||||
api.getSSHHosts.mockResolvedValue(
|
||||
hosts.map((host: object) => ({ ...host, pluginSettings: {} })),
|
||||
);
|
||||
api.browseSSHDirectory.mockImplementation(
|
||||
async (sessionId: string, path: string) => ({
|
||||
status: "ok",
|
||||
path: path === "." ? `/srv/home-${sessionId}` : path,
|
||||
files: [
|
||||
{
|
||||
name: `remote-${sessionId}.txt`,
|
||||
type: "file",
|
||||
path: `/srv/home-${sessionId}/remote-${sessionId}.txt`,
|
||||
size: 1,
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
render(<SftpTransferTab />);
|
||||
await selectHosts();
|
||||
await waitFor(() => {
|
||||
expect(api.browseSSHDirectory).toHaveBeenCalledWith("1", ".");
|
||||
expect(api.browseSSHDirectory).toHaveBeenCalledWith("2", ".");
|
||||
});
|
||||
expect(await screen.findByDisplayValue("/srv/home-1")).toBeTruthy();
|
||||
expect(await screen.findByDisplayValue("/srv/home-2")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("loads file manager-enabled hosts into both host pickers", async () => {
|
||||
render(<SftpTransferTab />);
|
||||
const selects = await screen.findAllByRole("combobox");
|
||||
|
||||
Reference in new issue
Block a user