mirror of
https://github.com/UNITRONIX/BetterDesk.git
synced 2026-10-26 14:30:19 +00:00
d03129cf8f
Integrate focused, reviewed contributor fixes on dev: bounded transport framing, live peer-ID rename, explicit enrollment precedence, lazy file-transfer compatibility, focused tests, and provenance/attribution documentation. Refs #346 Thanks: INSOLVE (Honorary); Marco Jakobs (@jacotec); MyNameisStitch (@MyNameisStitch); Redspin (@playerumpknow)
203 lines
7.5 KiB
JavaScript
203 lines
7.5 KiB
JavaScript
'use strict';
|
|
|
|
const { RDFileTransfer } = require('../public/js/rdclient/filetransfer.js');
|
|
|
|
describe('RDFileTransfer static helpers', () => {
|
|
it('uses 128 KB block size (hbb_common parity)', () => {
|
|
const ft = new RDFileTransfer({
|
|
proto: {},
|
|
sendMessage: () => {},
|
|
emit: () => {}
|
|
});
|
|
expect(ft.BLOCK_SIZE).toBe(131072);
|
|
});
|
|
|
|
it('computeOffsetBlk derives block index from transferred bytes', () => {
|
|
expect(RDFileTransfer.computeOffsetBlk(0, 131072)).toBe(0);
|
|
expect(RDFileTransfer.computeOffsetBlk(131072, 131072)).toBe(1);
|
|
expect(RDFileTransfer.computeOffsetBlk(262144, 131072)).toBe(2);
|
|
});
|
|
|
|
it('isPreCompressedFileName skips zstd for archives and media', () => {
|
|
expect(RDFileTransfer.isPreCompressedFileName('photo.jpg')).toBe(true);
|
|
expect(RDFileTransfer.isPreCompressedFileName('archive.zip')).toBe(true);
|
|
expect(RDFileTransfer.isPreCompressedFileName('readme.txt')).toBe(false);
|
|
expect(RDFileTransfer.isPreCompressedFileName('data.bin')).toBe(false);
|
|
});
|
|
|
|
it('buildRemoteFilePath joins Windows and Unix paths', () => {
|
|
expect(RDFileTransfer.buildRemoteFilePath('C:\\Users\\admin', 'test.txt'))
|
|
.toBe('C:\\Users\\admin\\test.txt');
|
|
expect(RDFileTransfer.buildRemoteFilePath('/home/user/', 'a.bin'))
|
|
.toBe('/home/user/a.bin');
|
|
});
|
|
|
|
it('buildRemoteRelativePath nests folders with remote separators', () => {
|
|
expect(RDFileTransfer.buildRemoteRelativePath('C:\\Users\\admin', 'docs/a.txt'))
|
|
.toBe('C:\\Users\\admin\\docs\\a.txt');
|
|
expect(RDFileTransfer.buildRemoteRelativePath('/home/user', 'docs/sub/a.bin'))
|
|
.toBe('/home/user/docs/sub/a.bin');
|
|
expect(RDFileTransfer.buildRemoteRelativePath('/tmp', '')).toBe('/tmp');
|
|
});
|
|
|
|
it('joinLocalPath builds nested local paths', () => {
|
|
expect(RDFileTransfer.joinLocalPath('C:\\Downloads', 'proj/readme.txt'))
|
|
.toBe('C:\\Downloads\\proj\\readme.txt');
|
|
expect(RDFileTransfer.joinLocalPath('/home/me/dl', 'proj/readme.txt'))
|
|
.toBe('/home/me/dl/proj/readme.txt');
|
|
expect(RDFileTransfer.joinLocalPath('/tmp/out', '')).toBe('/tmp/out');
|
|
expect(RDFileTransfer.joinLocalPath('', 'proj/readme.txt')).toBe('proj/readme.txt');
|
|
});
|
|
});
|
|
|
|
describe('RDFileTransfer overwrite strategy', () => {
|
|
it('confirmOverwrite resolves pending prompt and sets session strategy', () => {
|
|
const ft = new RDFileTransfer({
|
|
proto: {},
|
|
sendMessage: () => {},
|
|
emit: () => {}
|
|
});
|
|
let skipped = null;
|
|
ft._pendingOverwrite.set(1, {
|
|
resolve: (s) => { skipped = s; }
|
|
});
|
|
ft.confirmOverwrite(1, false, true);
|
|
expect(skipped).toBe(false);
|
|
expect(ft._overwriteStrategy).toBe('overwrite');
|
|
expect(ft._pendingOverwrite.has(1)).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('RDFileTransfer folder job queue', () => {
|
|
it('queues a second folder job while one is active', () => {
|
|
const ft = new RDFileTransfer({
|
|
proto: {
|
|
buildFileDirCreate: () => ({}),
|
|
buildReadDir: () => ({}),
|
|
buildFileReceiveRequest: () => ({}),
|
|
buildFileDigest: () => ({}),
|
|
buildFileCancel: () => ({})
|
|
},
|
|
sendMessage: () => {},
|
|
emit: () => {}
|
|
});
|
|
ft.enable();
|
|
// Prevent createDirAsync from hanging on real connection hooks.
|
|
ft.createDirAsync = async () => {};
|
|
ft.uploadFile = () => {
|
|
const id = ft._nextId++;
|
|
const transfer = {
|
|
id,
|
|
type: 'upload',
|
|
folderJobId: ft._activeFolderJob && ft._activeFolderJob.id,
|
|
_folderWait: null,
|
|
status: 'pending'
|
|
};
|
|
ft._transfers.set(id, transfer);
|
|
// Never resolve — keeps first job active.
|
|
return id;
|
|
};
|
|
|
|
const id1 = ft.uploadFolder({
|
|
rootName: 'a',
|
|
dirs: [],
|
|
files: [{ name: '1.txt', relativePath: '1.txt', size: 1, file: { name: '1.txt', size: 1 } }]
|
|
}, '/remote');
|
|
const id2 = ft.uploadFolder({
|
|
rootName: 'b',
|
|
dirs: [],
|
|
files: [{ name: '2.txt', relativePath: '2.txt', size: 1, file: { name: '2.txt', size: 1 } }]
|
|
}, '/remote');
|
|
|
|
expect(id1).toBeGreaterThan(0);
|
|
expect(id2).toBeGreaterThan(0);
|
|
expect(ft._activeFolderJob).toBeTruthy();
|
|
expect(ft._activeFolderJob.id).toBe(id1);
|
|
expect(ft._folderJobQueue.length).toBe(1);
|
|
expect(ft._folderJobQueue[0].id).toBe(id2);
|
|
|
|
ft.cancelTransfer(id2);
|
|
expect(ft._folderJobQueue.length).toBe(0);
|
|
ft.cancelTransfer(id1);
|
|
expect(ft._activeFolderJob).toBeNull();
|
|
});
|
|
|
|
it('background readDirAsync resolves from FileResponse.dir without clobbering UI path', async () => {
|
|
const ft = new RDFileTransfer({
|
|
proto: {
|
|
buildReadDir: (path) => ({ fileAction: { readDir: { path } } })
|
|
},
|
|
sendMessage: () => {},
|
|
emit: () => {}
|
|
});
|
|
ft.enable();
|
|
ft._currentPath = '/keep';
|
|
const pending = ft.readDirAsync('/walk');
|
|
ft.handleFileResponse({
|
|
dir: {
|
|
path: '/walk',
|
|
entries: [
|
|
{ name: 'f.txt', entryType: 4, size: 3, modifiedTime: 1 }
|
|
]
|
|
}
|
|
});
|
|
const entries = await pending;
|
|
expect(entries).toHaveLength(1);
|
|
expect(entries[0].name).toBe('f.txt');
|
|
expect(ft.currentPath).toBe('/keep');
|
|
});
|
|
});
|
|
|
|
describe('RDFileTransfer dedicated connection', () => {
|
|
it('waits for the file relay before sending a directory request', async () => {
|
|
let resolveConnection;
|
|
const ensureConnected = jest.fn(() => new Promise((resolve) => {
|
|
resolveConnection = resolve;
|
|
}));
|
|
const sendMessage = jest.fn();
|
|
const emit = jest.fn();
|
|
const ft = new RDFileTransfer({
|
|
proto: {
|
|
buildReadDir: (path, showHidden) => ({ readDir: { path, showHidden } })
|
|
},
|
|
sendMessage,
|
|
emit,
|
|
ensureConnected,
|
|
isConnected: () => false
|
|
});
|
|
ft.enable();
|
|
|
|
ft.browseDir('C:\\Users');
|
|
expect(ensureConnected).toHaveBeenCalledTimes(1);
|
|
expect(sendMessage).not.toHaveBeenCalled();
|
|
expect(emit).toHaveBeenCalledWith('file_connecting');
|
|
|
|
resolveConnection();
|
|
await new Promise((resolve) => setImmediate(resolve));
|
|
|
|
expect(sendMessage).toHaveBeenCalledWith({
|
|
readDir: { path: 'C:\\Users', showHidden: false }
|
|
});
|
|
clearTimeout(ft._browseTimeout);
|
|
});
|
|
|
|
it('reports connection errors instead of throwing from the click handler', async () => {
|
|
const emit = jest.fn();
|
|
const ft = new RDFileTransfer({
|
|
proto: { buildReadDir: jest.fn() },
|
|
sendMessage: jest.fn(),
|
|
emit,
|
|
ensureConnected: () => Promise.reject(new Error('relay unavailable')),
|
|
isConnected: () => false
|
|
});
|
|
ft.enable();
|
|
|
|
expect(() => ft.browseDir('')).not.toThrow();
|
|
await new Promise((resolve) => setImmediate(resolve));
|
|
|
|
expect(emit).toHaveBeenCalledWith('file_connect_error', {
|
|
error: 'relay unavailable'
|
|
});
|
|
});
|
|
});
|