diff --git a/securefile_handler/errors.py b/securefile_handler/errors.py index c68e42e..571bcd8 100644 --- a/securefile_handler/errors.py +++ b/securefile_handler/errors.py @@ -26,6 +26,10 @@ class CannotReadFileError(Exception): pass +class SameFileError(Exception): + pass + + class SameDirectoryError(Exception): pass diff --git a/securefile_handler/securefile_handler.py b/securefile_handler/securefile_handler.py index 817c3a5..a8f3f0c 100644 --- a/securefile_handler/securefile_handler.py +++ b/securefile_handler/securefile_handler.py @@ -48,7 +48,7 @@ def remove_file(filepath: Union[str, Path], erase_function: callable = erase_hel """ if not _check_params([filepath]): raise errors.WrongFilepathType(f'Wrong filepath type {type(filepath)}') - fpath = Path(filepath) + fpath = filepath if isinstance(filepath, Path) else Path(filepath) if not fpath.is_file(): raise errors.NotAFileError(f'{filepath} is not a file') if not callable(erase_function): @@ -76,7 +76,7 @@ def remove_dirtree(dirpath: Union[str, Path], erase_function: callable = erase_h raise errors.WrongFilepathType(f'Wrong dirpath type {type(dirpath)}') if not callable(erase_function): raise errors.EraseFunctionError('Erase function is not callable') - dir_path = Path(dirpath) + dir_path = dirpath if isinstance(dirpath, Path) else Path(dirpath) if len(os.listdir(dir_path.absolute())) == 0: raise errors.EmptyFolderError(f'Folder {dir_path.absolute()} is empty') erase_helpers.remove_dirtree(dir_path, erase_function) @@ -100,13 +100,14 @@ def move_folder(src_dir: Union[str, Path], dst_dir: Union[str, Path], erase_func raise errors.WrongFilepathType(f'Wrong parameter type') if not callable(erase_function): raise errors.EraseFunctionError('Erase function is not callable') + src_path, dst_path = src_dir if isinstance(src_dir, Path) else Path(src_dir), dst_dir if isinstance(dst_dir, Path) \ + else Path(dst_dir) + if not len(os.listdir(src_path.absolute())): + raise errors.EmptyFolderError(f'Folder {src_path.absolute()} is empty') if _is_subdir(str(src_dir), str(dst_dir)): raise errors.SubDirectoryError(f'Cannot move {dst_dir} subdirectory of {src_dir}') - src_path, dst_path = Path(src_dir), Path(dst_dir) if src_path.stat().st_dev == dst_path.stat().st_dev: raise errors.SameDriveError("Cannot move folder to same drive") - if len(os.listdir(src_path.absolute())) == 0: - raise errors.EmptyFolderError(f'Folder {src_path.absolute()} is empty') shutil.copytree(src_path.resolve(), dst_path.resolve()) erase_helpers.remove_dirtree(src_path, erase_function) @@ -127,14 +128,16 @@ def move_file(src: Union[str, Path], dst: Union[str, Path], erase_function: call :param erase_function: Function for erasing data in files. This optional argument is a callable. """ if not _check_params([src, dst]): - raise errors.WrongFilepathType(f'Wrong parameter type') + raise errors.WrongFilepathType(f'Wrong parameter type {src} {dst}') if not callable(erase_function): raise errors.EraseFunctionError('Erase function is not callable') - src_path, dst_path = Path(src), Path(dst) + src_path, dst_path = src if isinstance(src, Path) else Path(src), dst if isinstance(dst, Path) else Path(dst) if src_path.stat().st_dev == dst_path.stat().st_dev: raise errors.SameDriveError("Cannot move file to same drive") - if not src_path.is_file() or src_path.is_symlink(): - raise errors.NotAFileError(f'{src} is not a regular file') + if src_path.resolve() == dst_path.resolve(): + raise errors.SameFileError(f'{src_path} is same file as {dst_path}') + if not src_path.is_file(): + raise errors.NotAFileError(f'{src} is not a file') shutil.copy2(src_path.absolute(), dst_path.absolute()) erase_function(src_path.resolve()) if src_path.is_symlink(): diff --git a/tests/test_is_subdir.py b/tests/test_is_subdir.py index 79b873b..b71e3af 100644 --- a/tests/test_is_subdir.py +++ b/tests/test_is_subdir.py @@ -5,11 +5,19 @@ def test_is_subdir(): assert _is_subdir('/', '/home') - assert not _is_subdir('/home', '/') - assert not _is_subdir('/a/b/c', '/a/b/') - assert not _is_subdir('/a/b/c', '/a/b/') + assert _is_subdir('/source', '/source/dest') + assert _is_subdir('/tmp/a/b/', '/tmp/a/b/c') + assert _is_subdir('/root', '/root/some_subdir') + + +def test_is_subdir_error(): with pytest.raises(SameDirectoryError): assert _is_subdir('/', '/') assert _is_subdir('~', '~') + + +def test_is_subdir_not(): + assert not _is_subdir('/home', '/') + assert not _is_subdir('/a/b/c', '/a/b/') + assert not _is_subdir('/a/b/c', '/a/b/d') assert not _is_subdir('/tmp', '/tmp2') - assert _is_subdir('/source', '/source/dest') diff --git a/tests/test_move_files.py b/tests/test_move_files.py index e69de29..10587f0 100644 --- a/tests/test_move_files.py +++ b/tests/test_move_files.py @@ -0,0 +1,87 @@ +import pytest +from unittest.mock import MagicMock +from pathlib import Path +import os +import securefile_handler.securefile_handler +from securefile_handler import errors +import preparements + + +class FakePathObject: + + _parts = '' + + def stat(self): + raise NotImplementedError() + + def absolute(self): + raise NotImplementedError() + + def __fspath__(self): + raise NotImplementedError() + + +class FakeStat: + def __init__(self, st_dev): + self.st_dev = st_dev + + +def test_move_file_errors(): + with pytest.raises(errors.WrongFilepathType): + securefile_handler.securefile_handler.move_file((',', ), 'str') + with pytest.raises(errors.EraseFunctionError): + securefile_handler.securefile_handler.move_file('correct/src', 'correct/dst', 'non-callable') + with pytest.raises(errors.SameDriveError): + securefile_handler.securefile_handler.move_file('/', '/',) + with pytest.raises(errors.SameFileError): + file_path = Path(preparements.prepare_empty_file()) + attrs = { + 'stat.return_value': FakeStat(file_path.stat().st_dev + 1), + 'resolve': file_path.resolve + } + fake_file = MagicMock(spec=Path, wraps=FakePathObject, **attrs) + fake_file.__fspath__ = lambda: file_path.absolute() + securefile_handler.securefile_handler.move_file(fake_file, file_path) + preparements.delete_file(file_path) + with pytest.raises(errors.NotAFileError): + tmp_dir = Path(preparements.prepare_tmp_dir()) + attrs = { + 'stat.return_value': FakeStat(tmp_dir.stat().st_dev + 1), + 'resolve': lambda: os.urandom(10) + } + fake_file = MagicMock(spec=Path, wraps=FakePathObject, **attrs) + fake_file.__fspath__ = lambda: file_path.absolute() + securefile_handler.securefile_handler.move_file(tmp_dir, fake_file) + tmp_dir.rmdir() + + +def test_move_folder_errors(): + with pytest.raises(errors.WrongFilepathType): + securefile_handler.securefile_handler.move_folder((',', ), 'str') + with pytest.raises(errors.EraseFunctionError): + securefile_handler.securefile_handler.move_folder('correct/src', 'correct/dst', 'non-callable') + with pytest.raises(errors.EmptyFolderError): + tmp_dir = Path(preparements.prepare_tmp_dir()) + securefile_handler.move_folder(tmp_dir, tmp_dir) + tmp_dir.rmdir() + with pytest.raises(errors.SameDirectoryError): + securefile_handler.securefile_handler.move_folder('/', '/') + with pytest.raises(errors.SubDirectoryError): + tmp_dir = Path(preparements.prepare_tmp_dir()) + dst_dir = Path(preparements.prepare_tmp_dir(tmp_dir.resolve())) + securefile_handler.securefile_handler.move_folder(tmp_dir, dst_dir) + tmp = dst_dir + with pytest.raises(errors.SameDriveError): + dst_dir = Path(preparements.prepare_tmp_dir()) + securefile_handler.securefile_handler.move_folder(tmp_dir, dst_dir) + tmp.rmdir() + dst_dir.rmdir() + tmp_dir.rmdir() + + +def test_move_file(): + ... + + +def test_move_folder(): + ...