项目文件夹

文件
wehub-resource-sync 2771cff92b
CI / lint (push) Failing after 1s
CI / typecheck (push) Failing after 2s
CI / test (push) Failing after 1s
CI / build (push) Has been skipped
chore: import upstream snapshot with attribution
2026-07-13 12:22:56 +08:00

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);
});
});
});