diff --git a/src/functions/getFirstExistingParentPath.ts b/src/functions/getFirstExistingParentPath.ts index 56ef1e2..d669dcb 100644 --- a/src/functions/getFirstExistingParentPath.ts +++ b/src/functions/getFirstExistingParentPath.ts @@ -8,15 +8,31 @@ import Dependencies from '@/src/types/dependencies' * @param dependencies - Dependencies container */ async function getFirstExistingParentPath(directoryPath: string, dependencies: Dependencies): Promise { - let parentDirectoryPath = directoryPath + let parentDirectoryPath = dependencies.pathNormalize(directoryPath) let parentDirectoryFound = await isDirectoryExisting(parentDirectoryPath, dependencies) - while (!parentDirectoryFound) { - parentDirectoryPath = dependencies.pathNormalize(parentDirectoryPath + '/..') + const FAILED_TO_FIND_EXISTING_DIRECTORY_VALUE = '' + + /** + * Linux max file path length is 4096 characters. + * With / separators and 1 letter folder names, this gives us a max of ~2048 folders to traverse. + * This is much less error prone than a while loop. + */ + const maxNumberOfFolders = 2048 + for (let i = 0; i < maxNumberOfFolders && !parentDirectoryFound; ++i) { + const newParentDirectoryPath = dependencies.pathNormalize(parentDirectoryPath + '/..') + if (parentDirectoryPath === newParentDirectoryPath || parentDirectoryPath === '.') { + return FAILED_TO_FIND_EXISTING_DIRECTORY_VALUE + } + parentDirectoryPath = newParentDirectoryPath parentDirectoryFound = await isDirectoryExisting(parentDirectoryPath, dependencies) } - return parentDirectoryPath + if (parentDirectoryPath !== '.') { + return parentDirectoryPath + } + + return FAILED_TO_FIND_EXISTING_DIRECTORY_VALUE } export default getFirstExistingParentPath diff --git a/test/functions/getFirstExistingParentPath.spec.ts b/test/functions/getFirstExistingParentPath.spec.ts index 7ffea08..dd21c9a 100644 --- a/test/functions/getFirstExistingParentPath.spec.ts +++ b/test/functions/getFirstExistingParentPath.spec.ts @@ -4,21 +4,47 @@ import { PathLike } from 'fs' import getFirstExistingParentPath from '@/src/functions/getFirstExistingParentPath' import mockDependencies from '@/test/__helpers__/mockDependencies' +const getDependencies = (parentPath: string) => mockDependencies({ + fsAccess: async (directoryPath: PathLike) => directoryPath === parentPath ? Promise.resolve() : Promise.reject(new Error('File does not exists')), +}) test('unix: get first existing parent path', async t => { const parentPath = '/home/Alex' - const dependencies = mockDependencies({ - fsAccess: async (directoryPath: PathLike) => directoryPath === parentPath ? Promise.resolve() : Promise.reject(new Error('File does not exists')), - }) + const dependencies = getDependencies(parentPath) t.is(await getFirstExistingParentPath('/home/Alex/games/Some/Game', dependencies), parentPath) }) test('unix: get first parent can be the path itself', async t => { const parentPath = '/home/Alex' - const dependencies = mockDependencies({ - fsAccess: async (directoryPath: PathLike) => directoryPath === parentPath ? Promise.resolve() : Promise.reject(new Error('File does not exists')), - }) + const dependencies = getDependencies(parentPath) + + t.is(await getFirstExistingParentPath(parentPath, dependencies), parentPath) +}) + +test('unix: get first parent of root is root', async t => { + const parentPath = '/' + const dependencies = getDependencies(parentPath) + + t.is(await getFirstExistingParentPath(parentPath, dependencies), parentPath) +}) + +test('win32: Gets parent to C:\\Alex', async t => { + // note that 'C:/' will fail on UNIX os's as normalize(C:/Alex/..) = C: not C:/ + const parentPath = 'C:' + const dependencies = getDependencies(parentPath) + t.is(await getFirstExistingParentPath('C:/Alex', dependencies), parentPath) +}) +test('win32: Returns root folder when called on root folder', async t => { + const parentPath = 'C:/' + const dependencies = getDependencies(parentPath) t.is(await getFirstExistingParentPath(parentPath, dependencies), parentPath) }) + +test('win32: returns empty string when drive does not exist', async t => { + const drivePathThatExists = 'C:/' + const dependencies = getDependencies(drivePathThatExists) + const drivePathThatDoesNotExist = 'Z:/' + t.is(await getFirstExistingParentPath(drivePathThatDoesNotExist, dependencies), '') +})