filetree-pro
Version:
A powerful file tree generator for VS Code and Cursor. Generate beautiful file trees in multiple formats with smart exclusions and custom configurations.
234 lines (197 loc) • 7.89 kB
text/typescript
/**
* Tests for security utilities
* Validates path validation, pattern validation, and file size checks
*
* @author FileTree Pro Team
* @since 0.2.0
*/
import {
MAX_FILE_SIZE,
MAX_PATH_DEPTH,
MAX_PATTERN_LENGTH,
sanitizeFilename,
validateExclusionPatterns,
validateFileSize,
validatePath,
validatePattern,
} from '../utils/securityUtils';
describe('Security Utils', () => {
describe('validatePath', () => {
test('should accept valid paths', () => {
const result = validatePath('/home/user/project/file.ts');
expect(result.valid).toBe(true);
expect(result.error).toBeUndefined();
});
test('should reject empty paths', () => {
const result = validatePath('');
expect(result.valid).toBe(false);
expect(result.error).toContain('empty');
});
test('should reject paths with null bytes', () => {
const result = validatePath('/home/user\0/file.ts');
expect(result.valid).toBe(false);
expect(result.error).toContain('null bytes');
});
test('should reject path traversal attempts', () => {
const result = validatePath('/home/user/../../etc/passwd');
expect(result.valid).toBe(false);
expect(result.error).toContain('traversal');
});
test('should reject excessive path depth', () => {
const deepPath = '/' + 'a/'.repeat(MAX_PATH_DEPTH + 1);
const result = validatePath(deepPath);
expect(result.valid).toBe(false);
expect(result.error).toContain('depth exceeds');
});
test('should validate path against base path', () => {
const result = validatePath('/home/user/../etc/passwd', '/home/user');
expect(result.valid).toBe(false);
expect(result.error).toContain('traversal');
});
test('should accept path within base path', () => {
const result = validatePath('/home/user/project/file.ts', '/home/user');
expect(result.valid).toBe(true);
});
test('should reject excessively long paths', () => {
const longPath = '/' + 'a'.repeat(MAX_PATH_DEPTH * 300);
const result = validatePath(longPath);
expect(result.valid).toBe(false);
expect(result.error).toContain('exceeds maximum length');
});
});
describe('validatePattern', () => {
test('should accept valid glob patterns', () => {
expect(validatePattern('*.ts').valid).toBe(true);
expect(validatePattern('**/*.js').valid).toBe(true);
expect(validatePattern('node_modules').valid).toBe(true);
});
test('should reject empty patterns', () => {
const result = validatePattern('');
expect(result.valid).toBe(false);
expect(result.error).toContain('empty');
});
test('should reject excessively long patterns', () => {
const longPattern = 'a'.repeat(MAX_PATTERN_LENGTH + 1);
const result = validatePattern(longPattern);
expect(result.valid).toBe(false);
expect(result.error).toContain('exceeds maximum length');
});
test('should reject nested quantifiers (ReDoS risk)', () => {
const result = validatePattern('(a+)+');
expect(result.valid).toBe(false);
expect(result.error).toContain('nested quantifiers');
});
test('should reject overlapping alternatives (ReDoS risk)', () => {
const result = validatePattern('(a|ab)+');
expect(result.valid).toBe(false);
expect(result.error).toContain('overlapping alternatives');
});
test('should reject excessive wildcards', () => {
const result = validatePattern('*'.repeat(15));
expect(result.valid).toBe(false);
expect(result.error).toContain('too many wildcards');
});
test('should accept reasonable number of wildcards', () => {
const result = validatePattern('**/*.{ts,js,tsx,jsx}');
expect(result.valid).toBe(true);
});
});
describe('validateFileSize', () => {
test('should accept files within size limit', () => {
const result = validateFileSize(1024 * 1024); // 1MB
expect(result.valid).toBe(true);
});
test('should reject files exceeding size limit', () => {
const result = validateFileSize(MAX_FILE_SIZE + 1);
expect(result.valid).toBe(false);
expect(result.error).toContain('exceeds maximum');
});
test('should reject negative file sizes', () => {
const result = validateFileSize(-1);
expect(result.valid).toBe(false);
expect(result.error).toContain('cannot be negative');
});
test('should reject NaN file sizes', () => {
const result = validateFileSize(NaN);
expect(result.valid).toBe(false);
expect(result.error).toContain('finite number');
});
test('should reject Infinity file sizes', () => {
const result = validateFileSize(Infinity);
expect(result.valid).toBe(false);
expect(result.error).toContain('finite number');
});
test('should accept custom max size', () => {
const customMax = 1024 * 1024; // 1MB
const result = validateFileSize(customMax + 1, customMax);
expect(result.valid).toBe(false);
});
test('should accept zero-size files', () => {
const result = validateFileSize(0);
expect(result.valid).toBe(true);
});
});
describe('validateExclusionPatterns', () => {
test('should accept valid pattern arrays', () => {
const result = validateExclusionPatterns(['*.log', 'node_modules', 'dist']);
expect(result.valid).toBe(true);
});
test('should reject non-array input', () => {
const result = validateExclusionPatterns('not-an-array' as any);
expect(result.valid).toBe(false);
expect(result.error).toContain('must be an array');
});
test('should reject excessive number of patterns', () => {
const tooMany = Array(1001).fill('*.log');
const result = validateExclusionPatterns(tooMany);
expect(result.valid).toBe(false);
expect(result.error).toContain('Too many');
});
test('should reject non-string patterns', () => {
const result = validateExclusionPatterns([123 as any, '*.log']);
expect(result.valid).toBe(false);
expect(result.error).toContain('not a string');
});
test('should reject invalid patterns in array', () => {
const result = validateExclusionPatterns(['*.log', '(a+)+', 'dist']);
expect(result.valid).toBe(false);
expect(result.error).toContain('nested quantifiers');
});
test('should accept empty array', () => {
const result = validateExclusionPatterns([]);
expect(result.valid).toBe(true);
});
});
describe('sanitizeFilename', () => {
test('should remove dangerous characters', () => {
const result = sanitizeFilename('my<file>name.txt');
expect(result).toBe('my_file_name.txt');
});
test('should remove null bytes', () => {
const result = sanitizeFilename('file\0name.txt');
expect(result).toBe('filename.txt');
});
test('should trim leading/trailing dots', () => {
const result = sanitizeFilename('..filename.txt.');
expect(result).toBe('filename.txt');
});
test('should return untitled for empty result', () => {
const result = sanitizeFilename('<<>>');
expect(result).toBe('untitled');
});
test('should limit filename length', () => {
const longName = 'a'.repeat(300) + '.txt';
const result = sanitizeFilename(longName);
expect(result.length).toBeLessThanOrEqual(255);
expect(result.endsWith('.txt')).toBe(true);
});
test('should preserve valid filenames', () => {
const result = sanitizeFilename('valid-file_name.123.txt');
expect(result).toBe('valid-file_name.123.txt');
});
test('should remove control characters', () => {
const result = sanitizeFilename('file\x01\x02name.txt');
expect(result).toBe('file__name.txt');
});
});
});