universal-file-client
Version:
Universal file transfer client with unified interface for FTP, SFTP, and HTTP protocols
246 lines • 11.1 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
const universal_ftp_client_1 = require("../universal-ftp-client");
const protocol_detector_1 = require("../utils/protocol-detector");
const file_matcher_1 = require("../utils/file-matcher");
describe('UniversalFileClient', () => {
let client;
beforeEach(() => {
client = new universal_ftp_client_1.UniversalFileClient();
});
afterEach(async () => {
if (client.isConnected()) {
await client.disconnect();
}
});
describe('Connection Management', () => {
it('should initialize with no connection', () => {
expect(client.isConnected()).toBe(false);
expect(client.getProtocol()).toBeNull();
expect(client.getConnectionConfig()).toBeNull();
});
it('should throw error when calling methods without connection', async () => {
await expect(client.list()).rejects.toThrow('Not connected to any server');
await expect(client.download('/test')).rejects.toThrow('Not connected to any server');
await expect(client.upload('/local', '/remote')).rejects.toThrow('Not connected to any server');
await expect(client.stat('/test')).rejects.toThrow('Not connected to any server');
await expect(client.exists('/test')).rejects.toThrow('Not connected to any server');
});
it('should handle unsupported protocol', async () => {
await expect(client.connect({ host: 'unknown://example.com' })).rejects.toThrow('Unsupported protocol');
});
});
describe('Protocol Detection', () => {
it('should detect FTP protocol', () => {
expect(protocol_detector_1.ProtocolDetector.detect('ftp://example.com')).toBe('ftp');
expect(protocol_detector_1.ProtocolDetector.detect('ftp:example.com')).toBe('ftp');
});
it('should detect FTPS protocol', () => {
expect(protocol_detector_1.ProtocolDetector.detect('ftps://example.com')).toBe('ftps');
expect(protocol_detector_1.ProtocolDetector.detect('ftps:example.com')).toBe('ftps');
});
it('should detect SFTP protocol', () => {
expect(protocol_detector_1.ProtocolDetector.detect('sftp://example.com')).toBe('sftp');
expect(protocol_detector_1.ProtocolDetector.detect('sftp:example.com')).toBe('sftp');
});
it('should detect HTTP protocol', () => {
expect(protocol_detector_1.ProtocolDetector.detect('http://example.com')).toBe('http');
});
it('should detect HTTPS protocol', () => {
expect(protocol_detector_1.ProtocolDetector.detect('https://example.com')).toBe('https');
});
it('should normalize host correctly', () => {
expect(protocol_detector_1.ProtocolDetector.normalizeHost('ftp://example.com')).toBe('example.com');
expect(protocol_detector_1.ProtocolDetector.normalizeHost('sftp://example.com:22')).toBe('example.com:22');
expect(protocol_detector_1.ProtocolDetector.normalizeHost('https://api.example.com/path')).toBe('api.example.com/path');
});
it('should validate URLs', () => {
expect(protocol_detector_1.ProtocolDetector.isValidUrl('https://example.com')).toBe(true);
expect(protocol_detector_1.ProtocolDetector.isValidUrl('invalid-url')).toBe(false);
});
});
describe('File Matching', () => {
const mockFiles = [
{
name: 'test.txt',
size: 100,
date: new Date('2023-01-01'),
type: 'file',
isDirectory: false,
modifyTime: new Date('2023-01-01').getTime(),
},
{
name: 'test_20230101.txt',
size: 200,
date: new Date('2023-01-02'),
type: 'file',
isDirectory: false,
modifyTime: new Date('2023-01-02').getTime(),
},
{
name: 'other.csv',
size: 300,
date: new Date('2023-01-03'),
type: 'file',
isDirectory: false,
modifyTime: new Date('2023-01-03').getTime(),
},
];
const matchOptions = {
basename: 'test',
filepath: 'test.txt',
extname: '.txt',
};
it('should find exact matches', () => {
const result = file_matcher_1.FileMatcher.findBestMatch(mockFiles, matchOptions, 'exact');
expect(result?.name).toBe('test.txt');
});
it('should find prefix matches', () => {
const result = file_matcher_1.FileMatcher.findBestMatch(mockFiles, matchOptions, 'prefix');
expect(result?.name).toBe('test_20230101.txt'); // Newest match
});
it('should find smart matches', () => {
const result = file_matcher_1.FileMatcher.findBestMatch(mockFiles, matchOptions, 'smart');
expect(result?.name).toBe('test_20230101.txt'); // Newest match
});
it('should handle regex matches', () => {
const regexOptions = {
basename: '^test.*',
filepath: 'test',
extname: '.txt',
};
const result = file_matcher_1.FileMatcher.findBestMatch(mockFiles, regexOptions, 'regex');
expect(result?.name).toBe('test_20230101.txt'); // Newest matching
});
it('should return null when no matches found', () => {
const noMatchOptions = {
basename: 'nonexistent',
filepath: 'nonexistent.txt',
extname: '.txt',
};
const result = file_matcher_1.FileMatcher.findBestMatch(mockFiles, noMatchOptions, 'exact');
expect(result).toBeNull();
});
});
describe('Download with Retry Logic', () => {
beforeEach(() => {
client = new universal_ftp_client_1.UniversalFileClient();
});
it('should apply default retry options', async () => {
// Mock a client that fails twice then succeeds
const mockAdapter = {
download: jest.fn()
.mockRejectedValueOnce(new Error('Network error'))
.mockRejectedValueOnce(new Error('Network error'))
.mockResolvedValueOnce(Buffer.from('success')),
disconnect: jest.fn().mockResolvedValue(undefined),
};
// @ts-ignore - Access private property for testing
client['adapter'] = mockAdapter;
const result = await client.download('/test/file.txt');
expect(result.toString()).toBe('success');
expect(mockAdapter.download).toHaveBeenCalledTimes(3);
});
it('should fail after max retries', async () => {
const mockAdapter = {
download: jest.fn().mockRejectedValue(new Error('Persistent error')),
disconnect: jest.fn().mockResolvedValue(undefined),
};
// @ts-ignore - Access private property for testing
client['adapter'] = mockAdapter;
await expect(client.download('/test/file.txt', { retries: 2 })).rejects.toThrow('Persistent error');
expect(mockAdapter.download).toHaveBeenCalledTimes(3); // Initial + 2 retries
});
});
describe('List with Options', () => {
const mockFiles = [
{
name: 'file1.txt',
size: 100,
date: new Date(),
type: 'file',
isDirectory: false,
},
{
name: 'dir1',
size: 0,
date: new Date(),
type: 'directory',
isDirectory: true,
},
];
beforeEach(() => {
client = new universal_ftp_client_1.UniversalFileClient();
});
it('should filter out directories by default', async () => {
const mockAdapter = {
list: jest.fn().mockResolvedValue(mockFiles),
disconnect: jest.fn().mockResolvedValue(undefined),
};
// @ts-ignore - Access private property for testing
client['adapter'] = mockAdapter;
const result = await client.list('/test');
expect(result).toHaveLength(1);
expect(result[0].type).toBe('file');
});
it('should include directories when requested', async () => {
const mockAdapter = {
list: jest.fn().mockResolvedValue(mockFiles),
disconnect: jest.fn().mockResolvedValue(undefined),
};
// @ts-ignore - Access private property for testing
client['adapter'] = mockAdapter;
const result = await client.list('/test', { includeDirectories: true });
expect(result).toHaveLength(2);
});
});
describe('Configuration', () => {
it('should store connection config after connecting', async () => {
const config = {
host: 'http://example.com',
username: 'test',
password: 'pass',
};
// Mock HTTP adapter for successful connection
const mockAdapter = {
connect: jest.fn().mockResolvedValue(undefined),
disconnect: jest.fn().mockResolvedValue(undefined),
};
// @ts-ignore - Access private property for testing
client['adapter'] = mockAdapter;
await client.connect(config);
expect(client.getConnectionConfig()).toEqual(config);
expect(client.getProtocol()).toBe('http');
expect(client.isConnected()).toBe(true);
});
});
});
// Additional integration-style tests for HTTP adapter specifically
describe('HTTP Adapter Integration', () => {
let client;
beforeEach(() => {
client = new universal_ftp_client_1.UniversalFileClient();
});
afterEach(async () => {
if (client.isConnected()) {
await client.disconnect();
}
});
it('should handle HTTP connection', async () => {
await client.connect({
host: 'https://httpbin.org',
timeout: 10000,
});
expect(client.isConnected()).toBe(true);
expect(client.getProtocol()).toBe('https');
});
it('should throw error for HTTP list operation', async () => {
await client.connect({ host: 'https://httpbin.org' });
await expect(client.list()).rejects.toThrow('HTTP adapter does not support directory listing');
});
it('should throw error for HTTP upload operation', async () => {
await client.connect({ host: 'https://httpbin.org' });
await expect(client.upload('/local', '/remote')).rejects.toThrow('HTTP adapter does not support file upload');
});
});
//# sourceMappingURL=universal-ftp-client.test.js.map
;