yishentu--claudian
337 行
12 KiB
TypeScript
337 行
12 KiB
TypeScript
import * as fs from 'fs';
|
|
|
|
import {
|
|
filterValidPaths,
|
|
findConflictingPath,
|
|
getFolderName,
|
|
isDuplicatePath,
|
|
isValidDirectoryPath,
|
|
normalizePathForComparison,
|
|
validateDirectoryPath,
|
|
} from '@/utils/externalContext';
|
|
|
|
jest.mock('fs');
|
|
|
|
describe('externalContext utilities', () => {
|
|
describe('normalizePathForComparison', () => {
|
|
const originalPlatform = process.platform;
|
|
const expectNormalized = (input: string, expected: string) => {
|
|
const normalized = normalizePathForComparison(input);
|
|
const resolved = process.platform === 'win32' ? expected.toLowerCase() : expected;
|
|
expect(normalized).toBe(resolved);
|
|
};
|
|
|
|
afterEach(() => {
|
|
Object.defineProperty(process, 'platform', { value: originalPlatform });
|
|
});
|
|
|
|
// eslint-disable-next-line jest/expect-expect
|
|
it('should convert backslashes to forward slashes', () => {
|
|
expectNormalized('C:\\Users\\test', 'C:/Users/test');
|
|
expectNormalized('path\\to\\file', 'path/to/file');
|
|
});
|
|
|
|
// eslint-disable-next-line jest/expect-expect
|
|
it('should remove trailing slashes', () => {
|
|
expectNormalized('/path/to/dir/', '/path/to/dir');
|
|
expectNormalized('/path/to/dir///', '/path/to/dir');
|
|
});
|
|
|
|
// eslint-disable-next-line jest/expect-expect
|
|
it('should handle combined cases', () => {
|
|
expectNormalized('C:\\Users\\test\\', 'C:/Users/test');
|
|
expectNormalized('C:\\Users\\test\\subdir\\', 'C:/Users/test/subdir');
|
|
});
|
|
|
|
// eslint-disable-next-line jest/expect-expect
|
|
it('should handle paths without trailing slashes', () => {
|
|
expectNormalized('/path/to/dir', '/path/to/dir');
|
|
expectNormalized('C:/Users/test', 'C:/Users/test');
|
|
});
|
|
|
|
// eslint-disable-next-line jest/expect-expect
|
|
it('should handle Unix-style paths', () => {
|
|
expectNormalized('/home/user/project', '/home/user/project');
|
|
expectNormalized('/home/user/project/', '/home/user/project');
|
|
});
|
|
|
|
it('should normalize Windows case when platform is win32', () => {
|
|
Object.defineProperty(process, 'platform', { value: 'win32' });
|
|
expect(normalizePathForComparison('C:\\Users\\Test\\Docs')).toBe('c:/users/test/docs');
|
|
});
|
|
|
|
it('should translate MSYS paths on Windows', () => {
|
|
Object.defineProperty(process, 'platform', { value: 'win32' });
|
|
expect(normalizePathForComparison('/c/Users/Test')).toBe('c:/users/test');
|
|
});
|
|
});
|
|
|
|
describe('findConflictingPath', () => {
|
|
describe('child path detection (adding child when parent exists)', () => {
|
|
it('should detect when new path is inside existing path', () => {
|
|
const result = findConflictingPath('/parent/child', ['/parent']);
|
|
expect(result).toEqual({ path: '/parent', type: 'parent' });
|
|
});
|
|
|
|
it('should detect deeply nested child paths', () => {
|
|
const result = findConflictingPath('/parent/child/grandchild', ['/parent']);
|
|
expect(result).toEqual({ path: '/parent', type: 'parent' });
|
|
});
|
|
|
|
it('should detect with multiple existing paths', () => {
|
|
const result = findConflictingPath('/workspace/project', ['/other', '/workspace', '/another']);
|
|
expect(result).toEqual({ path: '/workspace', type: 'parent' });
|
|
});
|
|
|
|
it('should detect with Windows paths', () => {
|
|
const result = findConflictingPath('C:\\Users\\test\\project', ['C:\\Users\\test']);
|
|
expect(result).toEqual({ path: 'C:\\Users\\test', type: 'parent' });
|
|
});
|
|
});
|
|
|
|
describe('parent path detection (adding parent when child exists)', () => {
|
|
it('should detect when new path would contain existing path', () => {
|
|
const result = findConflictingPath('/parent', ['/parent/child']);
|
|
expect(result).toEqual({ path: '/parent/child', type: 'child' });
|
|
});
|
|
|
|
it('should detect when new path would contain deeply nested path', () => {
|
|
const result = findConflictingPath('/parent', ['/parent/child/grandchild']);
|
|
expect(result).toEqual({ path: '/parent/child/grandchild', type: 'child' });
|
|
});
|
|
|
|
it('should detect with Windows paths', () => {
|
|
const result = findConflictingPath('C:\\Users\\test', ['C:\\Users\\test\\project']);
|
|
expect(result).toEqual({ path: 'C:\\Users\\test\\project', type: 'child' });
|
|
});
|
|
});
|
|
|
|
describe('unrelated paths (should be allowed)', () => {
|
|
it('should return null for completely unrelated paths', () => {
|
|
const result = findConflictingPath('/project1', ['/project2']);
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('should return null for sibling paths', () => {
|
|
const result = findConflictingPath('/parent/sibling1', ['/parent/sibling2']);
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('should return null when no existing paths', () => {
|
|
const result = findConflictingPath('/new/path', []);
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('should return null for paths with similar prefixes but not nested', () => {
|
|
// /vault vs /vault2 - should NOT be considered nested
|
|
const result = findConflictingPath('/vault2', ['/vault']);
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('should handle Windows paths with similar prefixes', () => {
|
|
const result = findConflictingPath('C:\\Users\\test2', ['C:\\Users\\test']);
|
|
expect(result).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('edge cases', () => {
|
|
it('should handle trailing slashes correctly', () => {
|
|
const result = findConflictingPath('/parent/child/', ['/parent/']);
|
|
expect(result).toEqual({ path: '/parent/', type: 'parent' });
|
|
});
|
|
|
|
it('should handle mixed path separators', () => {
|
|
const result = findConflictingPath('/parent\\child', ['/parent']);
|
|
expect(result).toEqual({ path: '/parent', type: 'parent' });
|
|
});
|
|
|
|
it('should handle exact same path', () => {
|
|
// Exact same paths are not nested (handled by duplicate check elsewhere)
|
|
const result = findConflictingPath('/parent', ['/parent']);
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('should return first conflict when multiple exist', () => {
|
|
const result = findConflictingPath('/a/b', ['/a', '/a/b/c']);
|
|
// Should return /a as it appears first and is a parent
|
|
expect(result).toEqual({ path: '/a', type: 'parent' });
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('getFolderName', () => {
|
|
it('should extract folder name from Unix path', () => {
|
|
expect(getFolderName('/Users/test/workspace')).toBe('workspace');
|
|
});
|
|
|
|
it('should extract folder name from Windows path', () => {
|
|
expect(getFolderName('C:\\Users\\test\\workspace')).toBe('workspace');
|
|
});
|
|
|
|
it('should handle trailing slashes', () => {
|
|
expect(getFolderName('/Users/test/workspace/')).toBe('workspace');
|
|
});
|
|
|
|
it('should handle single segment paths', () => {
|
|
expect(getFolderName('workspace')).toBe('workspace');
|
|
});
|
|
|
|
it('should handle root paths', () => {
|
|
expect(getFolderName('/')).toBe('');
|
|
});
|
|
});
|
|
|
|
describe('validateDirectoryPath', () => {
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
});
|
|
|
|
it('should return valid for existing directory', () => {
|
|
(fs.statSync as jest.Mock).mockReturnValue({ isDirectory: () => true });
|
|
expect(validateDirectoryPath('/existing/dir')).toEqual({ valid: true });
|
|
});
|
|
|
|
it('should return not-a-directory error for file path', () => {
|
|
(fs.statSync as jest.Mock).mockReturnValue({ isDirectory: () => false });
|
|
expect(validateDirectoryPath('/path/to/file.txt')).toEqual({
|
|
valid: false,
|
|
error: 'Path exists but is not a directory',
|
|
});
|
|
});
|
|
|
|
it('should return ENOENT error for non-existent path', () => {
|
|
const err = new Error('ENOENT') as NodeJS.ErrnoException;
|
|
err.code = 'ENOENT';
|
|
(fs.statSync as jest.Mock).mockImplementation(() => { throw err; });
|
|
expect(validateDirectoryPath('/non/existent')).toEqual({
|
|
valid: false,
|
|
error: 'Path does not exist',
|
|
});
|
|
});
|
|
|
|
it('should return permission denied error for EACCES', () => {
|
|
const err = new Error('EACCES') as NodeJS.ErrnoException;
|
|
err.code = 'EACCES';
|
|
(fs.statSync as jest.Mock).mockImplementation(() => { throw err; });
|
|
expect(validateDirectoryPath('/restricted/path')).toEqual({
|
|
valid: false,
|
|
error: 'Permission denied',
|
|
});
|
|
});
|
|
|
|
it('should return generic error for unknown errors', () => {
|
|
const err = new Error('Something went wrong') as NodeJS.ErrnoException;
|
|
err.code = 'EIO';
|
|
(fs.statSync as jest.Mock).mockImplementation(() => { throw err; });
|
|
const result = validateDirectoryPath('/some/path');
|
|
expect(result.valid).toBe(false);
|
|
expect(result.error).toContain('Cannot access path');
|
|
expect(result.error).toContain('Something went wrong');
|
|
});
|
|
});
|
|
|
|
describe('isValidDirectoryPath', () => {
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
});
|
|
|
|
it('should return true for existing directory', () => {
|
|
(fs.statSync as jest.Mock).mockReturnValue({ isDirectory: () => true });
|
|
expect(isValidDirectoryPath('/existing/dir')).toBe(true);
|
|
expect(fs.statSync).toHaveBeenCalledWith('/existing/dir');
|
|
});
|
|
|
|
it('should return false for non-existent path', () => {
|
|
(fs.statSync as jest.Mock).mockImplementation(() => {
|
|
throw new Error('ENOENT');
|
|
});
|
|
expect(isValidDirectoryPath('/non/existent')).toBe(false);
|
|
});
|
|
|
|
it('should return false for file path (not directory)', () => {
|
|
(fs.statSync as jest.Mock).mockReturnValue({ isDirectory: () => false });
|
|
expect(isValidDirectoryPath('/path/to/file.txt')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('filterValidPaths', () => {
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
});
|
|
|
|
it('should filter out non-existent paths', () => {
|
|
(fs.statSync as jest.Mock).mockImplementation((p: string) => {
|
|
if (p === '/valid/path') {
|
|
return { isDirectory: () => true };
|
|
}
|
|
throw new Error('ENOENT');
|
|
});
|
|
|
|
const result = filterValidPaths(['/valid/path', '/invalid/path', '/another/invalid']);
|
|
expect(result).toEqual(['/valid/path']);
|
|
});
|
|
|
|
it('should return empty array when all paths are invalid', () => {
|
|
(fs.statSync as jest.Mock).mockImplementation(() => {
|
|
throw new Error('ENOENT');
|
|
});
|
|
|
|
const result = filterValidPaths(['/invalid1', '/invalid2']);
|
|
expect(result).toEqual([]);
|
|
});
|
|
|
|
it('should return all paths when all are valid', () => {
|
|
(fs.statSync as jest.Mock).mockReturnValue({ isDirectory: () => true });
|
|
|
|
const paths = ['/path1', '/path2', '/path3'];
|
|
const result = filterValidPaths(paths);
|
|
expect(result).toEqual(paths);
|
|
});
|
|
|
|
it('should handle empty array', () => {
|
|
const result = filterValidPaths([]);
|
|
expect(result).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe('isDuplicatePath', () => {
|
|
const originalPlatform = process.platform;
|
|
|
|
afterEach(() => {
|
|
Object.defineProperty(process, 'platform', { value: originalPlatform });
|
|
});
|
|
|
|
it('should detect exact duplicate paths', () => {
|
|
expect(isDuplicatePath('/path/to/dir', ['/path/to/dir'])).toBe(true);
|
|
});
|
|
|
|
it('should return false for non-duplicate paths', () => {
|
|
expect(isDuplicatePath('/path/to/dir', ['/other/path'])).toBe(false);
|
|
});
|
|
|
|
it('should detect duplicates with different trailing slashes', () => {
|
|
expect(isDuplicatePath('/path/to/dir/', ['/path/to/dir'])).toBe(true);
|
|
expect(isDuplicatePath('/path/to/dir', ['/path/to/dir/'])).toBe(true);
|
|
});
|
|
|
|
it('should detect duplicates with mixed path separators', () => {
|
|
expect(isDuplicatePath('/path\\to\\dir', ['/path/to/dir'])).toBe(true);
|
|
});
|
|
|
|
it('should detect case-insensitive duplicates on Windows', () => {
|
|
Object.defineProperty(process, 'platform', { value: 'win32' });
|
|
expect(isDuplicatePath('C:\\Users\\Test', ['c:\\users\\test'])).toBe(true);
|
|
expect(isDuplicatePath('C:/Users/Test', ['c:/users/test'])).toBe(true);
|
|
});
|
|
|
|
it('should handle empty existing paths array', () => {
|
|
expect(isDuplicatePath('/path/to/dir', [])).toBe(false);
|
|
});
|
|
|
|
it('should check against multiple existing paths', () => {
|
|
expect(isDuplicatePath('/path/b', ['/path/a', '/path/b', '/path/c'])).toBe(true);
|
|
expect(isDuplicatePath('/path/d', ['/path/a', '/path/b', '/path/c'])).toBe(false);
|
|
});
|
|
});
|
|
});
|