diff options
author | M.Eng. Thomas Feldmann <mail@tfeldmann.de> | 2022-08-17 18:25:07 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-08-17 18:25:07 +0200 |
commit | 084e6200b6e7e6206ca6ea6051fe1769ea3ea1d2 (patch) | |
tree | aa515a901c53f99ae3683d640db1224bd21d950b /tests | |
parent | c1ead1a3325da4d40c2d40b737f7a78c8a90d4a1 (diff) |
Improve testing and support fs_urls as working dir (#234)
* wip
* update comment
* support working dir in `core.run()`
* add copy tests
* add tests
* improve duplicate and regex
* refactor to testfs
* add test_delete
* add tests
* add coverage to dev deps
* add unicode tests
* add filecontent tests
* refactor working_dir cli option
* add extension test
* add python filter tests
* add cli test
* support fs_url in cli
* add test_rename
* update cli
* normalize paths in filters
* normalize paths in action pipeline
* add windows path handler
* use fs.path.join instead of os
Diffstat (limited to 'tests')
40 files changed, 1246 insertions, 1183 deletions
diff --git a/tests/actions/test_copy.py b/tests/actions/test_copy.py index ea87e90..a497d17 100644 --- a/tests/actions/test_copy.py +++ b/tests/actions/test_copy.py @@ -1,69 +1,152 @@ -from copy import deepcopy - -import fs +from fs.base import FS +import pytest from conftest import make_files, read_files from organize import core files = { - "files": { + "test.txt": "", + "file.txt": "Hello world\nAnother line", + "another.txt": "", + "folder": { + "x.txt": "", + }, +} + + +@pytest.fixture +def testfiles(testfs) -> FS: + make_files(testfs, files) + yield testfs + + +def test_copy_on_itself(testfiles): + config = """ + rules: + - locations: "/" + actions: + - copy: "/" + """ + core.run(config, simulate=False, working_dir=testfiles) + result = read_files(testfiles) + assert result == files + + +def test_copy_into_dir(testfiles): + config = """ + rules: + - locations: "/" + actions: + - copy: "a brand/new/folder/" + """ + core.run(config, simulate=False, working_dir=testfiles) + result = read_files(testfiles) + assert result == { "test.txt": "", "file.txt": "Hello world\nAnother line", "another.txt": "", "folder": { "x.txt": "", }, + "a brand": { + "new": { + "folder": { + "test.txt": "", + "file.txt": "Hello world\nAnother line", + "another.txt": "", + } + } + }, } -} -def test_copy_on_itself(): - with fs.open_fs("mem://") as mem: - config = { - "rules": [ - { - "locations": [ - {"path": "files", "filesystem": mem}, - ], - "actions": [ - {"copy": {"dest": "files/", "filesystem": mem}}, - ], - }, - ] - } - make_files(mem, files) - core.run(config, simulate=False) - result = read_files(mem) - assert result == files - - -def test_does_not_create_folder_in_simulation(): - with fs.open_fs("mem://") as mem: - config = { - "rules": [ - { - "locations": [ - {"path": "files", "filesystem": mem}, - ], - "actions": [ - {"copy": {"dest": "files/new-subfolder/", "filesystem": mem}}, - {"copy": {"dest": "files/copyhere/", "filesystem": mem}}, - ], - }, - ] - } - make_files(mem, files) - core.run(config, simulate=True) - result = read_files(mem) - assert result == files - - core.run(config, simulate=False, validate=False) - result = read_files(mem) - - expected = deepcopy(files) - expected["files"]["new-subfolder"] = deepcopy(files["files"]) - expected["files"]["new-subfolder"].pop("folder") - expected["files"]["copyhere"] = deepcopy(files["files"]) - expected["files"]["copyhere"].pop("folder") - - assert result == expected +def test_copy_into_dir_subfolders(testfiles): + config = """ + rules: + - locations: "/" + subfolders: true + actions: + - copy: "a brand/new/folder/" + """ + core.run(config, simulate=False, working_dir=testfiles) + result = read_files(testfiles) + assert result == { + "test.txt": "", + "file.txt": "Hello world\nAnother line", + "another.txt": "", + "folder": { + "x.txt": "", + }, + "a brand": { + "new": { + "folder": { + "test.txt": "", + "file.txt": "Hello world\nAnother line", + "another.txt": "", + "x.txt": "", + } + } + }, + } + + +@pytest.mark.parametrize( + "mode,files,test_txt_content", + [ + ("skip", ["file.txt", "test.txt"], "old"), + ("overwrite", ["file.txt", "test.txt"], "new"), + ("rename_new", ["file.txt", "test.txt", "test 1.txt"], "old"), + ("rename_existing", ["file.txt", "test.txt", "test 1.txt"], "new"), + ], +) +def test_copy_conflict(testfs: FS, mode, files, test_txt_content): + config = """ + rules: + - locations: "/" + filters: + - name: file + actions: + - copy: + dest: "test.txt" + on_conflict: {} + """.format( + mode + ) + testfs.writetext("file.txt", "new") + testfs.writetext("test.txt", "old") + core.run(config, simulate=False, working_dir=testfs) + assert set(testfs.listdir("/")) == set(files) + assert testfs.readtext("file.txt") == "new" + assert testfs.readtext("test.txt") == test_txt_content + + +# def test_does_not_create_folder_in_simulation(): +# with fs.open_fs("mem://") as mem: +# config = { +# "rules": [ +# { +# "locations": [ +# {"path": "files", "filesystem": mem}, +# ], +# "actions": [ +# {"copy": {"dest": "files/new-subfolder/", "filesystem": mem}}, +# {"copy": {"dest": "files/copyhere/", "filesystem": mem}}, +# ], +# }, +# ] +# } +# make_files(mem, files) +# core.run(config, simulate=True) +# result = read_files(mem) +# assert result == files + +# core.run(config, simulate=False, validate=False) +# result = read_files(mem) + +# expected = deepcopy(files) +# expected["files"]["new-subfolder"] = deepcopy(files["files"]) +# expected["files"]["new-subfolder"].pop("folder") +# expected["files"]["copyhere"] = deepcopy(files["files"]) +# expected["files"]["copyhere"].pop("folder") + +# assert result == expected diff --git a/tests/actions/test_delete.py b/tests/actions/test_delete.py new file mode 100644 index 0000000..1b0d0f5 --- /dev/null +++ b/tests/actions/test_delete.py @@ -0,0 +1,97 @@ +from conftest import make_files, read_files + +from organize import core + +FILES = { + "test.txt": "", + "file.txt": "Hello world\nAnother line", + "another.txt": "", + "folder": { + "x.txt": "", + "empty_sub": {}, + }, + "empty_folder": {}, +} + + +def test_delete_empty_files(testfs): + config = """ + rules: + - name: "Recursively delete all empty files" + locations: + - path: "/" + subfolders: true + filters: + - empty + actions: + - delete + """ + make_files(testfs, FILES) + core.run(config, simulate=False, working_dir=testfs) + result = read_files(testfs) + assert result == { + "file.txt": "Hello world\nAnother line", + "folder": { + "empty_sub": {}, + }, + "empty_folder": {}, + } + + +def test_delete_empty_dirs(testfs): + config = """ + rules: + - name: "Recursively delete all empty directories" + locations: "/" + subfolders: true + targets: dirs + filters: + - empty + actions: + - delete + """ + make_files(testfs, FILES) + core.run(config, simulate=False, working_dir=testfs) + result = read_files(testfs) + assert result == { + "test.txt": "", + "file.txt": "Hello world\nAnother line", + "another.txt": "", + "folder": { + "x.txt": "", + }, + } + + +def test_delete_deep(testfs): + files = { + "files": { + "folder": { + "subfolder": { + "test.txt": "", + "other.pdf": "binary", + }, + "file.txt": "Hello world\nAnother line", + }, + } + } + make_files(testfs, files) + config = """ + rules: + - locations: "." + actions: + - delete + - locations: "." + targets: dirs + actions: + - delete + """ + # sim + core.run(config, simulate=True, working_dir=testfs) + result = read_files(testfs) + assert result == files + + # run + core.run(config, simulate=False, working_dir=testfs) + result = read_files(testfs) + assert result == {} diff --git a/tests/actions/test_move.py b/tests/actions/test_move.py index e28dfc9..042430d 100644 --- a/tests/actions/test_move.py +++ b/tests/actions/test_move.py @@ -1,44 +1,61 @@ -import fs +import pytest from conftest import make_files, read_files +from fs.base import FS from organize import core +files = { + "test.txt": "", + "file.txt": "Hello world\nAnother line", + "another.txt": "", + "folder": { + "x.txt": "", + }, +} -def test_move_on_itself(): - files = { - "files": { - "test.txt": "", - "file.txt": "Hello world\nAnother line", - "another.txt": "", - "folder": { - "x.txt": "", - }, - } - } - with fs.open_fs("mem://") as mem: - config = { - "rules": [ - { - "locations": [ - {"path": "files", "filesystem": mem}, - ], - "actions": [ - {"copy": {"dest": "files/", "filesystem": mem}}, - ], - }, - ] - } - make_files(mem, files) - core.run(config, simulate=False) - result = read_files(mem) - - assert result == { - "files": { - "test.txt": "", - "file.txt": "Hello world\nAnother line", - "another.txt": "", - "folder": { - "x.txt": "", - }, - } - } + +@pytest.fixture +def testfiles(testfs) -> FS: + make_files(testfs, files) + yield testfs + + +def test_copy_on_itself(testfiles): + config = """ + rules: + - locations: "/" + actions: + - move: "/" + """ + core.run(config, simulate=True, working_dir=testfiles) + result = read_files(testfiles) + assert result == files + + +@pytest.mark.parametrize( + "mode,files,test_txt_content", + [ + ("skip", ["file.txt", "test.txt"], "old"), + ("overwrite", ["test.txt"], "new"), + ("rename_new", ["test.txt", "test 1.txt"], "old"), + ("rename_existing", ["test.txt", "test 1.txt"], "new"), + ], +) +def test_move_conflict(testfs: FS, mode, files, test_txt_content): + config = """ + rules: + - locations: "/" + filters: + - name: file + actions: + - move: + dest: "test.txt" + on_conflict: {} + """.format( + mode + ) + testfs.writetext("file.txt", "new") + testfs.writetext("test.txt", "old") + core.run(config, simulate=False, working_dir=testfs) + assert set(testfs.listdir("/")) == set(files) + assert testfs.readtext("test.txt") == test_txt_content diff --git a/tests/actions/test_rename.py b/tests/actions/test_rename.py new file mode 100644 index 0000000..220142a --- /dev/null +++ b/tests/actions/test_rename.py @@ -0,0 +1,116 @@ +import fs +from conftest import make_files, read_files + +from organize.core import run + + +def test_rename_issue51(testfs): + # test for issue https://github.com/tfeldmann/organize/issues/51 + files = { + "19asd_WF_test2.PDF": "", + "other.pdf": "", + "18asd_WFX_test2.pdf": "", + } + make_files(testfs, files) + + CONFIG = """ + rules: + - locations: "./" + filters: + - extension + - name: + startswith: "19" + contains: + - "_WF_" + actions: + - rename: "{name}_unread.{extension.lower()}" + - copy: + dest: "copy/" + """ + run(CONFIG, simulate=False, working_dir=testfs) + result = read_files(testfs) + assert result == { + "copy": { + "19asd_WF_test2_unread.pdf": "", + }, + "19asd_WF_test2_unread.pdf": "", + "other.pdf": "", + "18asd_WFX_test2.pdf": "", + } + + +def test_rename_folders(testfs): + config = """ + rules: + - name: "Renaming DVD folders" + locations: "/" + targets: dirs + filters: + - name: + contains: DVD + actions: + - rename: + name: "{name.replace('[DVD] ','').replace(' [1080p]','').replace(' ', '_')}" + """ + files = { + "[DVD] Best Of Video 1080 [1080p]": { + "[DVD] Best Of Video 1080 [1080p]": "", + "Metadata": "", + }, + "[DVD] This Is A Title [1080p]": { + "[DVD] This Is A Title [1080p]": "", + "Metadata": "", + }, + } + make_files(testfs, files) + run(rules=config, simulate=False, working_dir=testfs) + assert read_files(testfs) == { + "Best_Of_Video_1080": { + "[DVD] Best Of Video 1080 [1080p]": "", + "Metadata": "", + }, + "This_Is_A_Title": { + "[DVD] This Is A Title [1080p]": "", + "Metadata": "", + }, + } + + +def test_rename_in_subfolders(testfs): + config = """ + rules: + - locations: "/" + subfolders: true + filters: + - name: + contains: RENAME + - extension + actions: + - rename: "DONE.{extension}" + """ + files = { + "folder": { + "FIRST-RENAME.pdf": "", + "Other": "", + }, + "Another folder": { + "Subfolder": { + "RENAME-ME_TOO.txt": "", + }, + "Metadata": "", + }, + } + make_files(testfs, files) + run(rules=config, simulate=False, working_dir=testfs) + assert read_files(testfs) == { + "folder": { + "DONE.pdf": "", + "Other": "", + }, + "Another folder": { + "Subfolder": { + "DONE.txt": "", + }, + "Metadata": "", + }, + } diff --git a/tests/actions/test_trash.py b/tests/actions/test_trash.py index 258a102..a593296 100644 --- a/tests/actions/test_trash.py +++ b/tests/actions/test_trash.py @@ -1,10 +1,47 @@ +import fs from unittest.mock import patch from organize.actions import Trash +from organize.core import run -def test_trash(): +def test_trash_mocked(): with patch("send2trash.send2trash") as mck: trash = Trash() trash.trash(path="~/Desktop/Test.zip", simulate=False) mck.assert_called_with("~/Desktop/Test.zip") + + +def test_trash_file(): + config = """ + rules: + - locations: "." + filters: + - name: "test-trash-209318123" + actions: + - trash + """ + with fs.open_fs("temp://") as tmp: + tmp.writetext("test-trash-209318123.txt", "Content") + assert tmp.exists("test-trash-209318123.txt") + run(config, simulate=False, working_dir=tmp.getsyspath("/")) + assert not tmp.exists("test-trash-209318123.txt") + + +def test_trash_folder(): + config = """ + rules: + - locations: "." + targets: dirs + filters: + - name: "test-trash-209318123" + actions: + - trash + """ + with fs.open_fs("temp://") as tmp: + tmp_dir = tmp.makedir("test-trash-209318123") + tmp_dir.touch("file.txt") + assert tmp.exists("test-trash-209318123/file.txt") + run(config, simulate=False, working_dir=tmp.getsyspath("/")) + assert not tmp.exists("test-trash-209318123/file.txt") + assert not tmp.exists("test-trash-209318123") diff --git a/tests/conftest.py b/tests/conftest.py index f23b433..8dcb763 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,10 +1,33 @@ +from typing import Union from unittest.mock import patch +import fs import pytest from fs.base import FS from fs.path import basename, join -from organize import config + +@pytest.fixture +def tempfs(): + with fs.open_fs("temp://") as tmp: + yield tmp + + +@pytest.fixture +def memfs(): + with fs.open_fs("mem://") as mem: + yield mem + + +@pytest.fixture( + params=[ + "mem://", + # pytest.param("temp://", marks=pytest.mark.skip), + ], +) +def testfs(request) -> FS: + with fs.open_fs(request.param) as tmp: + yield tmp @pytest.fixture @@ -13,7 +36,7 @@ def mock_echo(): yield mck -def make_files(fs: FS, layout: dict, path="/"): +def make_files(fs: FS, layout: Union[dict, list], path="/"): """ layout = { "folder": { @@ -26,6 +49,10 @@ def make_files(fs: FS, layout: dict, path="/"): } """ fs.makedirs(path, recreate=True) + if isinstance(layout, list): + for f in layout: + fs.touch(f) + return for k, v in layout.items(): respath = join(path, k) @@ -51,31 +78,3 @@ def read_files(fs: FS, path="/"): for x in fs.walk.dirs(path, max_depth=0): result[basename(x)] = read_files(fs, path=join(path, x)) return result - - -def rules_shortcut(fs: FS, filters, actions, location="files", max_depth=0): - if isinstance(filters, str): - filters = config.load_from_string(filters) - if isinstance(actions, str): - actions = config.load_from_string(actions) - - # for action in actions: - # for opts in action.values(): - # if "filesystem" in opts and opts["filesystem"] == "mem": - # opts["filesystem"] = fs - - return { - "rules": [ - { - "locations": [ - { - "path": location, - "filesystem": fs, - "max_depth": max_depth, - } - ], - "actions": actions, - "filters": filters, - } - ] - } diff --git a/tests/core/test_config.py b/tests/core/test_config.py index 68e1d9a..c0cdd71 100644 --- a/tests/core/test_config.py +++ b/tests/core/test_config.py @@ -5,11 +5,10 @@ from schema import SchemaError from organize import config, core -def validate_and_convert(string: str): +def validate_config(string: str): conf = config.load_from_string(string) conf = config.cleanup(conf) config.validate(conf) - core.replace_with_instances(conf) return conf @@ -33,7 +32,7 @@ def test_basic(): - shell: cmd: 'say {path.stem}' """ - validate_and_convert(STR) + validate_config(STR) def test_yaml_ref(): @@ -64,7 +63,7 @@ def test_yaml_ref(): actions: - trash """ - validate_and_convert(STR) + validate_config(STR) def test_error_filter_dict(): @@ -77,7 +76,7 @@ def test_error_filter_dict(): - trash """ with pytest.raises(SchemaError): - validate_and_convert(STR) + validate_config(STR) # def test_error_action_dict(): diff --git a/tests/core/test_unicode.py b/tests/core/test_unicode.py new file mode 100644 index 0000000..2cdcd4d --- /dev/null +++ b/tests/core/test_unicode.py @@ -0,0 +1,80 @@ +import pytest + +from conftest import make_files, read_files +from organize import core + + +def test_startswith_issue74(testfs): + # test for issue https://github.com/tfeldmann/organize/issues/74 + make_files( + testfs, + { + "Cálculo_1.pdf": "", + "Cálculo_2.pdf": "", + "Calculo.pdf": "", + }, + ) + config = r""" + # Cálculo PDF + rules: + - locations: "." + filters: + - extension: + - pdf + - name: + startswith: Cálculo + actions: + - move: "Cálculo Integral/Periodo #6/PDF's/" + """ + core.run(config, simulate=False, working_dir=testfs) + assert read_files(testfs) == { + "Cálculo Integral": { + "Periodo #6": { + "PDF's": { + "Cálculo_1.pdf": "", + "Cálculo_2.pdf": "", + } + } + }, + "Calculo.pdf": "", + } + + +@pytest.mark.skip(reason="TODO") +def test_normalization_regex(testfs): + make_files( + testfs, + {b"Ertra\xcc\x88gnisaufstellung.txt".decode("utf-8"): ""}, + ) + config = ( + b""" + rules: + - locations: "." + filters: + - regex: 'Ertra\xc3\xa4gnisaufstellung.txt$' + actions: + - rename: "found-regex.txt" + """ + ).decode("utf-8") + core.run(config, simulate=False, working_dir=testfs) + assert read_files(testfs) == {"found-regex.txt"} + + +@pytest.mark.skip(reason="TODO") +def test_normalization_filename(testfs): + make_files( + testfs, + {b"Ertr\xcc\x88gnisaufstellung.txt".decode("utf-8"): ""}, + ) + config = ( + b""" + rules: + - locations: "." + filters: + - filename: "Ertr\xc3\xa4gnisaufstellung" + actions: + - rename: "found-regex.txt" + """ + ).decode("utf-8") + core.run(config, simulate=False, working_dir=testfs) + assert read_files(testfs) == {"found-regex.txt"} diff --git a/tests/docs/test_docs.py b/tests/docs/test_docs.py index 63a8035..b57607d 100644 --- a/tests/docs/test_docs.py +++ b/tests/docs/test_docs.py @@ -39,11 +39,15 @@ def test_all_filters_documented(): docdir = fs.open_fs("docs") filter_docs = docdir.readtext("filters.md") for name in FILTERS.keys(): - assert "## {}".format(name) in filter_docs, f"{name} filter is not documented!" + assert ( + "## {}".format(name) in filter_docs + ), f"The {name} filter is not documented!" def test_all_actions_documented(): docdir = fs.open_fs("docs") action_docs = docdir.readtext("actions.md") for name in ACTIONS.keys(): - assert "## {}".format(name) in action_docs, f"{name} action is not documented!" + assert ( + "## {}".format(name) in action_docs + ), f"The {name} action is not documented!" diff --git a/tests/filters/test_duplicate.py b/tests/filters/test_duplicate.py new file mode 100644 index 0000000..b04fcc4 --- /dev/null +++ b/tests/filters/test_duplicate.py @@ -0,0 +1,75 @@ +from conftest import make_files, read_files + +from organize import core + +CONTENT_SMALL = "COPY CONTENT" +CONTENT_LARGE = "XYZ" * 300000 + +DEEP_DUP_DELETE = """ +rules: + - locations: "." + subfolders: true + filters: + - duplicate + actions: + - delete +""" + + +def test_duplicate_smallfiles(testfs): + files = { + "unique.txt": "I'm unique.", + "unique_too.txt": "I'm unique: too.", + "a.txt": CONTENT_SMALL, + "copy2.txt": CONTENT_SMALL, + "other": { + "copy.txt": CONTENT_SMALL, + "copy.jpg": CONTENT_SMALL, + "large.txt": CONTENT_LARGE, + }, + "large_unique.txt": CONTENT_LARGE, + } + + make_files(testfs, files) + core.run(DEEP_DUP_DELETE, simulate=False, working_dir=testfs) + result = read_files(testfs) + testfs.tree() + assert result == { + "unique.txt": "I'm unique.", + "unique_too.txt": "I'm unique: too.", + "a.txt": CONTENT_SMALL, + "other": { + "large.txt": CONTENT_LARGE, + }, + } + + +def test_duplicate_largefiles(testfs): + files = { + "unique.txt": CONTENT_LARGE + "1", + "unique_too.txt": CONTENT_LARGE + "2", + "a.txt": CONTENT_LARGE, + "copy2.txt": CONTENT_LARGE, + "other": { + "copy.txt": CONTENT_LARGE, + "copy.jpg": CONTENT_LARGE, + "large.txt": CONTENT_LARGE, + }, + } + + make_files(testfs, files) + core.run(DEEP_DUP_DELETE, simulate=False, working_dir=testfs) + result = read_files(testfs) + testfs.tree() + assert result == { + "unique.txt": CONTENT_LARGE + "1", + "unique_too.txt": CONTENT_LARGE + "2", + "a.txt": CONTENT_LARGE, + "other": {}, + } + + +# TODO detect_original_by: name +# TODO detect_original_by: first_seen +# TODO detect_original_by: created +# TODO detect_original_by: lastmodified diff --git a/tests/filters/test_exif.py b/tests/filters/test_exif.py new file mode 100644 index 0000000..01fff18 --- /dev/null +++ b/tests/filters/test_exif.py @@ -0,0 +1,89 @@ +from fs import copy +import pytest + +from organize.core import run + + +@pytest.fixture +def images_folder(testfs): + copy.copy_dir(".", "tests/resources/images-with-exif", testfs, "/") + yield testfs + + +def test_exif_read_camera(images_folder): + config = """ + rules: + - locations: "/" + filters: + - name + - exif + actions: + - write_text: + text: '{name}: {exif.image.model}' + textfile: "out.txt" + mode: append + """ + run(config, simulate=False, working_dir=images_folder) + out = images_folder.readtext("out.txt") + assert "1: DMC-GX80" in out + assert "2: NIKON D3200" in out + assert "3: iPhone 6s" in out + assert "4: iPhone 6s" in out + assert "5: iPhone 5s" in out + + +def test_exif_filter_by_cam(images_folder): + config = """ + rules: + - locations: "/" + filters: + - name + - exif: + image.model: Nikon D3200 + actions: + - write_text: + text: '{name}: {exif.image.model}' + textfile: "out.txt" + mode: append + """ + run(config, simulate=False, working_dir=images_folder) + out = images_folder.readtext("out.txt") + assert out.strip() == "2: NIKON D3200" + + +def test_exif_filter_tag_exists(images_folder): + config = """ + rules: + - locations: "/" + filters: + - name + - exif: + gps.gpsdate + actions: + - write_text: + text: '{name}: {exif.gps.gpsdate}' + textfile: "out.txt" + """ + run(config, simulate=False, working_dir=images_folder) + out = images_folder.readtext("out.txt") + assert len(out.splitlines()) == 3 + assert "4: 2018:02:22" in out + assert "5: 2015:07:08" in out + assert "3: 2017:08:12" in out + + +def test_exif_filter_by_multiple_keys(images_folder): + config = """ + rules: + - locations: "/" + filters: + - name + - exif: + image.make: Apple + exif.lensmodel: "iPhone 6s back camera 4.15mm f/2.2" + actions: + - move: "chosen/" + """ + run(config, simulate=False, working_dir=images_folder) + chosen = set(images_folder.listdir("chosen")) + assert chosen == set(["3.jpg", "4.jpg"]) diff --git a/tests/filters/test_extension.py b/tests/filters/test_extension.py index 4318f41..1e48e1a 100644 --- a/tests/filters/test_extension.py +++ b/tests/filters/test_extension.py @@ -1,6 +1,10 @@ +from unittest.mock import call + +from conftest import make_files from fs import open_fs from fs.path import dirname +from organize import core from organize.filters import Extension @@ -46,3 +50,33 @@ def test_extension_result(): assert str(result) == "TxT" assert result.lower() == "txt" assert result.upper() == "TXT" + + +def test_filename_move(testfs, mock_echo): + files = { + "test.jpg": "", + "asd.JPG": "", + "nomatch.jpg.zip": "", + "camel.jPeG": "", + } + make_files(testfs, files) + config = """ + rules: + - locations: "." + filters: + - name + - extension: + - .jpg + - jpeg + actions: + - echo: 'Found JPG file: {name}' + """ + core.run(config, simulate=False, working_dir=testfs) + mock_echo.assert_has_calls( + ( + call("Found JPG file: test"), + call("Found JPG file: asd"), + call("Found JPG file: camel"), + ), + any_order=True, + ) diff --git a/tests/filters/test_filecontent.py b/tests/filters/test_filecontent.py new file mode 100644 index 0000000..43bae6f --- /dev/null +++ b/tests/filters/test_filecontent.py @@ -0,0 +1,32 @@ +from conftest import make_files, read_files + +from organize import core + + +def test_filecontent(tempfs): + # inspired by https://github.com/tfeldmann/organize/issues/43 + files = { + "Test1.txt": "Lorem MegaCorp Ltd. ipsum\nInvoice 12345\nMore text\nID: 98765", + "Test2.txt": "Tests", + "Test3.txt": "My Homework ...", + } + make_files(tempfs, files) + config = r""" + rules: + - locations: "." + filters: + - filecontent: 'MegaCorp Ltd.+^Invoice (?P<number>\w+)$.+^ID: 98765$' + actions: + - rename: "MegaCorp_Invoice_{filecontent.number}.txt" + - locations: "." + filters: + - filecontent: '.*Homework.*' + actions: + - rename: "Homework.txt" + """ + core.run(config, simulate=False, working_dir=tempfs) + assert read_files(tempfs) == { + "Homework.txt": "My Homework ...", + "MegaCorp_Invoice_12345.txt": "Lorem MegaCorp Ltd. ipsum\nInvoice 12345\nMore text\nID: 98765", + "Test2.txt": "Tests", + } diff --git a/tests/filters/test_python.py b/tests/filters/test_python.py index dfe68b3..25cc3e8 100644 --- a/tests/filters/test_python.py +++ b/tests/filters/test_python.py @@ -1,3 +1,8 @@ +from unittest.mock import call + +from conftest import make_files, read_files + +from organize import core from organize.filters import Python @@ -10,3 +15,202 @@ def test_basic(): result = p.run() assert result.matches assert result.updates == {"python": "some-string"} + + +def test_python(testfs, mock_echo): + make_files( + testfs, + ["student-01.jpg", "student-01.txt", "student-02.txt", "student-03.txt"], + ) + config = """ + rules: + - locations: "." + filters: + - name + - extension: txt + - python: | + return int(name.split('.')[0][-2:]) * 100 + actions: + - echo: '{python}' + """ + core.run(config, simulate=False, working_dir=testfs) + mock_echo.assert_has_calls( + ( + call("100"), + call("200"), + call("300"), + ), + any_order=True, + ) + + +def test_odd_detector(testfs, mock_echo): + make_files( + testfs, + ["student-01.txt", "student-02.txt", "student-03.txt", "student-04.txt"], + ) + config = """ + rules: + - locations: "." + filters: + - name + - python: | + return int(name.split('-')[1]) % 2 == 1 + actions: + - echo: 'Odd student numbers: {name}' + """ + core.run(config, simulate=False, working_dir=testfs) + mock_echo.assert_has_calls( + ( + call("Odd student numbers: student-01"), + call("Odd student numbers: student-03"), + ), + any_order=True, + ) + + +def test_python_dict(testfs, mock_echo): + make_files( + testfs, + ["foo-01.jpg", "foo-01.txt", "bar-02.txt", "baz-03.txt"], + ) + config = """ + rules: + - locations: "." + filters: + - extension: txt + - name + - python: | + return { + "name": name[:3], + "code": int(name.split('.')[0][-2:]) * 100, + } + actions: + - echo: '{python.code} {python.name}' + """ + core.run(config, simulate=False, working_dir=testfs) + mock_echo.assert_has_calls( + ( + call("100 foo"), + call("200 bar"), + call("300 baz"), + ), + any_order=True, + ) + + +def test_name_reverser(testfs): + make_files(testfs, ["desrever.jpg", "emanelif.txt"]) + config = """ + rules: + - locations: "." + filters: + - extension + - name + - python: | + return { + "reversed_name": name[::-1], + } + actions: + - rename: '{python.reversed_name}.{extension}' + """ + core.run(config, simulate=False, working_dir=testfs) + assert read_files(testfs) == { + "reversed.jpg": "", + "filename.txt": "", + } + + +def test_folder_instructions(testfs): + """ + I would like to include path/folder-instructions into the filename because I have a + lot of different files (and there are always new categories added) I don't want + create rules for. For example my filename is + + '2019_Jobs_CategoryA_TagB_A-Media_content-name_V01_draft_eng.docx' + + which means: Move the file to the folder '2019/Jobs/CategoryA/TagB/Media/drafts/eng' + whereby 'A-' is an additional instruction and should be removed from the filename + afterwards ('2019_Jobs_CategoryA_TagB_content-name_V01_draft_eng.docx'). + + I have a rough idea to figure it out with python but I'm new to it (see below a + sketch). Is there a possibility to use such variables, conditions etc. with + organizer natively? If no, is it possible to do it with Python in Organizer at all? + + - Transform file-string into array + - Search for 'A-...', 'V...' and 'content-name' and get index of values + - remove value 'A-... and 'content-name' of array + - build new filename string + - remove value 'V...' and 'A-' of array + - build folder-path string (convert _ to /) etc. + + """ + # inspired by: https://github.com/tfeldmann/organize/issues/52 + make_files( + testfs, + [ + "2019_Jobs_CategoryA_TagB_A-Media_content-name_V01_draft_eng.docx", + "2019_Work_CategoryC_V-Test_A-Audio_V14_final.pdf", + "other.pdf", + ], + ) + config = r""" + rules: + - locations: . + filters: + - extension: + - pdf + - docx + - name: + contains: "_" + - python: | + import fs + parts = [] + instructions = dict() + for part in name.split("_"): + if part.startswith("A-"): + instructions["A"] = part[2:] + elif part.startswith("V-"): + instructions["V"] = part[2:] + elif part.startswith("content-name"): + instructions["content"] = part[12:] + else: + parts.append(part) + return { + "new_path": fs.path.join(*parts), + "instructions": instructions, + } + actions: + - echo: "New path: {python.new_path}" + - echo: "Instructions: {python.instructions}" + - echo: "Value of A: {python.instructions.A}" + - move: "{python.new_path}/{name}.{extension}" + """ + core.run(config, simulate=False, working_dir=testfs) + assert read_files(testfs) == { + "other.pdf": "", + "2019": { + "Jobs": { + "CategoryA": { + "TagB": { + "V01": { + "draft": { + "eng": { + "2019_Jobs_CategoryA_TagB_A-Media_content-name_V01_draft_eng.docx": "", + } + } + } + } + } + }, + "Work": { + "CategoryC": { + "V14": { + "final": { + "2019_Work_CategoryC_V-Test_A-Audio_V14_final.pdf": "", + } + } + } + }, + }, + } diff --git a/tests/filters/test_regex.py b/tests/filters/test_regex.py index af7a091..8cf4251 100644 --- a/tests/filters/test_regex.py +++ b/tests/filters/test_regex.py @@ -1,5 +1,6 @@ from pathlib import Path +from organize import core from organize.filters import Regex TESTDATA = [ @@ -39,3 +40,24 @@ def test_regex_umlaut(): result = regex.run(fs_path=doc) assert result.updates == {"regex": {"year": "1998"}} assert result.matches + + +def test_multiple_regex_placeholders(testfs): + config = """ + rules: + - locations: "." + filters: + - regex: (?P<word>\w+)-(?P<number>\d+).* + - regex: (?P<all>.+?)\.\w{3} + - extension + actions: + - write_text: + text: '{regex.word} {regex.number} {regex.all} {extension}' + textfile: out.txt + """ + testfs.touch("test-123.jpg") + testfs.touch("other-456.pdf") + core.run(config, simulate=False, working_dir=testfs) + out = testfs.readtext("out.txt") + assert "test 123 test-123 jpg" in out + assert "other 456 other-456 pdf" in out diff --git a/tests/filters/test_size.py b/tests/filters/test_size.py index 1bbd41e..8dda501 100644 --- a/tests/filters/test_size.py +++ b/tests/filters/test_size.py @@ -1,3 +1,7 @@ +import pytest +from conftest import make_files, read_files + +from organize import core from organize.filters import Size @@ -26,3 +30,79 @@ def test_other(): assert Size("<100 Mb").matches(20) assert Size("<100 Mb, <10 mb, <1 mb, > 0").matches(20) assert Size(["<100 Mb", ">= 0 Tb"]).matches(20) + + +def test_size_zero(testfs): + files = {"1": "", "2": "", "3": ""} + make_files(testfs, files) + config = """ + rules: + - locations: "." + filters: + - size: 0 + actions: + - echo: '{path.name}' + - delete + """ + core.run(config, simulate=False, working_dir=testfs) + assert read_files(testfs) == {} + + +def test_basic(testfs): + files = { + "empty": "", + "full": "0" * 2000, + "halffull": "0" * 1010, + "two_thirds.txt": "0" * 666, + } + make_files(testfs, files) + config = """ + rules: + - locations: "." + filters: + - size: '> 1kb, <= 1.0 KiB' + actions: + - echo: '{path.name} {size.bytes}' + - locations: "." + filters: + - not size: + - '> 0.5 kb' + - '<1.0 KiB' + actions: + - delete + """ + core.run(config, simulate=False, working_dir=testfs) + assert read_files(testfs) == { + "halffull": "0" * 1010, + "two_thirds.txt": "0" * 666, + } + + +# @pytest.mark.skip(reason="TODO - template vars in filters not supported") +# def test_python_args(tmp_path, mock_echo): +# create_filesystem( +# tmp_path, +# files=[ +# "empty", +# ("full", "0" * 2000), +# ("halffull", "0" * 1010), +# ("two_thirds.txt", "0" * 666), +# ], +# config=""" +# rules: +# - folders: files +# filters: +# - python: | +# return 2000 +# - filesize: '= {python}b' +# actions: +# - echo: '{path.name} {filesize.bytes}' +# """, +# ) +# main(["run", "--config-file=%s" % (tmp_path / "config.yaml")]) +# mock_echo.assert_has_calls( +# [ +# call("full 2000"), +# ], +# any_order=True, +# ) diff --git a/tests/integration/test_cli.py b/tests/integration/test_cli.py new file mode 100644 index 0000000..4549a77 --- /dev/null +++ b/tests/integration/test_cli.py @@ -0,0 +1,80 @@ +import os + +import fs +from click.testing import CliRunner +from conftest import make_files +from fs.base import FS + +from organize import cli + +runner = CliRunner() + + +def test_config_file(tempfs: FS): + files = ["my-test-file-name.txt", "my-test-file-name.jpg", "other-file.txt"] + make_files(tempfs, files) + config = """ + rules: + - locations: %s + filters: + - name: my-test-file-name + actions: + - delete + """ + with fs.open_fs("temp://") as configfs: + configfs.writetext("config.yaml", config % tempfs.getsyspath("/")) + args = [configfs.getsyspath("config.yaml")] + result = runner.invoke(cli.sim, args) + print(result.output) + assert set(tempfs.listdir("/")) == set(files) + result = runner.invoke(cli.run, args) + print(result.output) + assert set(tempfs.listdir("/")) == set(["other-file.txt"]) + + +def test_working_dir(tempfs: FS): + files = ["my-test-file-name.txt", "my-test-file-name.jpg", "other-file.txt"] + make_files(tempfs, files) + config = """ + rules: + - locations: "." + filters: + - name: my-test-file-name + actions: + - delete + """ + with fs.open_fs("temp://") as configfs: + configfs.writetext("config.yaml", config) + args = [ + configfs.getsyspath("config.yaml"), + "--working-dir=%s" % tempfs.getsyspath("/"), + ] + print(args) + runner.invoke(cli.sim, args) + assert set(tempfs.listdir("/")) == set(files) + runner.invoke(cli.run, args) + assert set(tempfs.listdir("/")) == set(["other-file.txt"]) + + +def test_with_os_chdir(tempfs: FS): + files = ["my-test-file-name.txt", "my-test-file-name.jpg", "other-file.txt"] + make_files(tempfs, files) + config = """ + rules: + - locations: "." + filters: + - name: my-test-file-name + actions: + - delete + """ + with fs.open_fs("temp://") as configfs: + configfs.writetext("config.yaml", config) + args = [ + configfs.getsyspath("config.yaml"), + ] + print(args) + os.chdir(tempfs.getsyspath("/")) + runner.invoke(cli.sim, args) + assert set(tempfs.listdir("/")) == set(files) + runner.invoke(cli.run, args) + assert set(tempfs.listdir("/")) == set(["other-file.txt"]) diff --git a/tests/integration/test_delete.py b/tests/integration/test_delete.py deleted file mode 100644 index 7261e8e..0000000 --- a/tests/integration/test_delete.py +++ /dev/null @@ -1,45 +0,0 @@ -import fs -from conftest import make_files, read_files - -from organize import core - - -def test_delete(): - files = { - "files": { - "folder": { - "subfolder": { - "test.txt": "", - "other.pdf": "binary", - }, - "file.txt": "Hello world\nAnother line", - }, - } - } - with fs.open_fs("mem://") as mem: - config = { - "rules": [ - { - "locations": [{"path": "files", "filesystem": mem}], - "actions": ["delete"], - }, - { - "locations": [{"path": "files", "filesystem": mem}], - "targets": "dirs", - "actions": ["delete"], - }, - ] - } - make_files(mem, files) - - # simulate - core.run(config, simulate=True) - result = read_files(mem) - assert result == files - - # run - core.run(config, simulate=False, validate=False) - result = read_files(mem) - assert result == { - "files": {}, - } diff --git a/tests/integration/test_dependent_rules.py b/tests/integration/test_dependent_rules.py new file mode 100644 index 0000000..6334b2e --- /dev/null +++ b/tests/integration/test_dependent_rules.py @@ -0,0 +1,34 @@ +from conftest import make_files, read_files + +from organize import core + + +def test_dependent_rules(testfs): + files = { + "asd.txt": "", + "newname 2.pdf": "", + "newname.pdf": "", + "test.pdf": "", + } + make_files(testfs, files) + config = """ + rules: + - locations: "." + filters: + - name: test + actions: + - copy: newfolder/test.pdf + - locations: "newfolder" + filters: + - name: test + actions: + - rename: test-found.pdf + """ + core.run(config, simulate=False, working_dir=testfs) + assert read_files(testfs) == { + "newname.pdf": "", + "newname 2.pdf": "", + "test.pdf": "", + "asd.txt": "", + "newfolder": {"test-found.pdf": ""}, + } diff --git a/tests/integration/test_dict_merge.py b/tests/integration/test_dict_merge.py deleted file mode 100644 index 521466c..0000000 --- a/tests/integration/test_dict_merge.py +++ /dev/null @@ -1,33 +0,0 @@ -from unittest.mock import call - -import fs -from conftest import make_files, rules_shortcut - -from organize import core - - -def test_multiple_regex_placeholders(mock_echo): - files = { - "files": {"test-123.jpg": "", "other-456.pdf": ""}, - } - with fs.open_fs("mem://") as mem: - rules = rules_shortcut( - fs=mem, - filters=r""" - - regex: (?P<word>\w+)-(?P<number>\d+).* - - regex: (?P<all>.+?)\.\w{3} - - extension - """, - actions=""" - - echo: '{regex.word} {regex.number} {regex.all} {extension}' - """, - ) - make_files(mem, files) - core.run(rules, simulate=False, validate=False) - mock_echo.assert_has_calls( - ( - call("test 123 test-123 jpg"), - call("other 456 other-456 pdf"), - ), - any_order=True, - ) diff --git a/tests/integration/test_duplicate.py b/tests/integration/test_duplicate.py deleted file mode 100644 index d728b32..0000000 --- a/tests/integration/test_duplicate.py +++ /dev/null @@ -1,76 +0,0 @@ -import fs -from conftest import make_files, read_files, rules_shortcut - -from organize import core - -CONTENT_SMALL = "COPY CONTENT" -CONTENT_LARGE = "XYZ" * 3000 - - -def test_duplicate_smallfiles(): - files = { - "files": { - "unique.txt": "I'm unique.", - "unique_too.txt": "I'm unique: too.", - "a.txt": CONTENT_SMALL, - "copy2.txt": CONTENT_SMALL, - "other": { - "copy.txt": CONTENT_SMALL, - "copy.jpg": CONTENT_SMALL, - "large.txt": CONTENT_LARGE, - }, - "large_unique.txt": CONTENT_LARGE, - }, - } - - with fs.open_fs("mem://") as mem: - make_files(mem, files) - rules = rules_shortcut( - mem, - filters="- duplicate", - actions="- echo: '{fs_path} is duplicate of {duplicate}'\n- delete", - max_depth=None, - ) - core.run(rules, simulate=False, validate=False) - result = read_files(mem) - mem.tree() - assert result == { - "files": { - "unique.txt": "I'm unique.", - "unique_too.txt": "I'm unique: too.", - "a.txt": CONTENT_SMALL, - "other": { - "large.txt": CONTENT_LARGE, - }, - }, - } - - -# main(["run", "--config-file=%s" % (tmp_path / "config.yaml")]) -# assertdir(tmp_path,) - - -# def test_duplicate_largefiles(tmp_path): -# create_filesystem( -# tmp_path, -# files=[ -# ("unique.txt", CONTENT_LARGE + "1"), -# ("unique_too.txt", CONTENT_LARGE + "2"), -# ("a.txt", CONTENT_LARGE), -# ("copy2.txt", CONTENT_LARGE), -# ("other/copy.txt", CONTENT_LARGE), -# ("other/copy.jpg", CONTENT_LARGE), -# ("other/large.txt", CONTENT_LARGE), -# ], -# config=""" -# rules: -# - folders: files -# subfolders: true -# filters: -# - duplicate -# actions: -# - trash -# """, -# ) -# main(["run", "--config-file=%s" % (tmp_path / "config.yaml")]) -# assertdir(tmp_path, "unique.txt", "unique_too.txt", "a.txt") diff --git a/tests/integration/test_rename.py b/tests/integration/test_rename.py deleted file mode 100644 index 84fb0e4..0000000 --- a/tests/integration/test_rename.py +++ /dev/null @@ -1,45 +0,0 @@ -import fs -from conftest import make_files, read_files, rules_shortcut - -from organize import core - - -def test_rename_issue52(): - # test for issue https://github.com/tfeldmann/organize/issues/51 - files = { - "files": { - "19asd_WF_test2.PDF": "", - "other.pdf": "", - "18asd_WFX_test2.pdf": "", - } - } - with fs.open_fs("temp://") as mem: - make_files(mem, files) - config = rules_shortcut( - mem, - filters=""" - - extension - - name: - startswith: "19" - contains: - - "_WF_" - """, - actions=[ - {"rename": "{path.stem}_unread.{extension.lower()}"}, - {"copy": {"dest": "files/copy/", "filesystem": mem}}, - ], - ) - core.run(config, simulate=False) - mem.tree() - result = read_files(mem) - - assert result == { - "files": { - "copy": { - "19asd_WF_test2_unread.pdf": "", - }, - "19asd_WF_test2_unread.pdf": "", - "other.pdf": "", - "18asd_WFX_test2.pdf": "", - } - } diff --git a/tests/integration/test_rename_regex.py b/tests/integration/test_rename_regex.py new file mode 100644 index 0000000..889e39d --- /dev/null +++ b/tests/integration/test_rename_regex.py @@ -0,0 +1,32 @@ +import pytest +from conftest import make_files, read_files + +from organize import core + + +@pytest.mark.parametrize("loc", (".", "/")) +def test_rename_files_date(testfs, loc): + # inspired by https://github.com/tfeldmann/organize/issues/43 + files = { + "File_abc_dat20190812_xyz.pdf": "", + "File_xyz_bar19990101_a.pdf": "", + "File_123456_foo20000101_xyz20190101.pdf": "", + } + make_files(testfs, files) + config = ( + r""" + rules: + - locations: "%s" + filters: + - regex: 'File_.*?(?P<y>\d{4})(?P<m>\d{2})(?P<d>\d{2}).*?.pdf' + actions: + - rename: "File_{regex.d}{regex.m}{regex.y}.pdf" + """ + % loc + ) + core.run(config, simulate=False, working_dir=testfs) + assert read_files(testfs) == { + "File_12082019.pdf": "", + "File_01011999.pdf": "", + "File_01012000.pdf": "", + } diff --git a/tests/resources/1.jpg b/tests/resources/images-with-exif/1.jpg Binary files differindex a034a3c..a034a3c 100644 --- a/tests/resources/1.jpg +++ b/tests/resources/images-with-exif/1.jpg diff --git a/tests/resources/2.jpg b/tests/resources/images-with-exif/2.jpg Binary files differindex 9d124b9..9d124b9 100755 --- a/tests/resources/2.jpg +++ b/tests/resources/images-with-exif/2.jpg diff --git a/tests/resources/3.jpg b/tests/resources/images-with-exif/3.jpg Binary files differindex e8723a7..e8723a7 100644 --- a/tests/resources/3.jpg +++ b/tests/resources/images-with-exif/3.jpg diff --git a/tests/resources/4.jpg b/tests/resources/images-with-exif/4.jpg Binary files differindex ded1738..ded1738 100644 --- a/tests/resources/4.jpg +++ b/tests/resources/images-with-exif/4.jpg diff --git a/tests/resources/5.jpg b/tests/resources/images-with-exif/5.jpg Binary files differindex 4da9b86..4da9b86 100644 --- a/tests/resources/5.jpg +++ b/tests/resources/images-with-exif/5.jpg diff --git a/tests/todo/integration/test_exif.py b/tests/todo/integration/test_exif.py deleted file mode 100644 index c3826d5..0000000 --- a/tests/todo/integration/test_exif.py +++ /dev/null @@ -1,128 +0,0 @@ -import os -import shutil - -from conftest import TESTS_FOLDER, assertdir, create_filesystem - -from organize.cli import main - - -def copy_resources(tmp_path): - src = os.path.join(TESTS_FOLDER, "resources") - dst = os.path.join(str(tmp_path), "files") - shutil.copytree(src=src, dst=dst) - - -def test_exif(tmp_path): - """Sort photos by camera""" - copy_resources(tmp_path) - create_filesystem( - tmp_path, - files=["nothing.jpg"], - config=""" - rules: - - folders: files - filters: - - extension: jpg - - exif - actions: - - move: 'files/{exif.image.model}/' - - echo: "{exif}" - """, - ) - main(["run", "--config-file=%s" % (tmp_path / "config.yaml")]) - assertdir( - tmp_path, - "nothing.jpg", - "DMC-GX80/1.jpg", - "NIKON D3200/2.jpg", - "iPhone 6s/3.jpg", - "iPhone 6s/4.jpg", - "iPhone 5s/5.jpg", - ) - - -def test_exif_filter_single(tmp_path): - """Filter by camera""" - copy_resources(tmp_path) - create_filesystem( - tmp_path, - files=["nothing.jpg"], - config=""" - rules: - - folders: files - filters: - - exif: - image.model: Nikon D3200 - actions: - - move: 'files/{exif.image.model}/' - """, - ) - main(["run", "--config-file=%s" % (tmp_path / "config.yaml")]) - assertdir( - tmp_path, - "nothing.jpg", - "1.jpg", - "NIKON D3200/2.jpg", - "3.jpg", - "4.jpg", - "5.jpg", - ) - - -def test_exif_filter_tag_exists(tmp_path): - """Filter by GPS""" - copy_resources(tmp_path) - create_filesystem( - tmp_path, - files=["nothing.jpg"], - config=""" - rules: - - folders: files - filters: - - exif: - gps.gpsdate - actions: - - echo: "{exif.gps.gpsdate}" - - move: 'files/has_gps/' - """, - ) - main(["run", "--config-file=%s" % (tmp_path / "config.yaml")]) - assertdir( - tmp_path, - "nothing.jpg", - "1.jpg", - "2.jpg", - "has_gps/3.jpg", - "has_gps/4.jpg", - "has_gps/5.jpg", - ) - - -def test_exif_filter_multiple(tmp_path): - """Filter by camera""" - copy_resources(tmp_path) - create_filesystem( - tmp_path, - files=["nothing.jpg"], - config=""" - rules: - - folders: files - filters: - - exif: - image.make: Apple - exif.lensmodel: "iPhone 6s back camera 4.15mm f/2.2" - actions: - - echo: "{exif.image}" - - move: 'files/chosen/' - """, - ) - main(["run", "--config-file=%s" % (tmp_path / "config.yaml")]) - assertdir( - tmp_path, - "nothing.jpg", - "1.jpg", - "2.jpg", - "chosen/3.jpg", - "chosen/4.jpg", - "5.jpg", - ) diff --git a/tests/todo/integration/test_extension.py b/tests/todo/integration/test_extension.py deleted file mode 100644 index 66c88ff..0000000 --- a/tests/todo/integration/test_extension.py +++ /dev/null @@ -1,31 +0,0 @@ -from unittest.mock import call - -from conftest import create_filesystem - -from organize.cli import main - - -def test_filename_move(tmp_path, mock_echo): - create_filesystem( - tmp_path, - files=["test.jpg", "asd.JPG", "nomatch.jpg.zip", "camel.jPeG"], - config=""" - rules: - - folders: files - filters: - - extension: - - .jpg - - jpeg - actions: - - echo: 'Found JPG file: {path.name}' - """, - ) - main(["run", "--config-file=%s" % (tmp_path / "config.yaml")]) - mock_echo.assert_has_calls( - ( - call("Found JPG file: test.jpg"), - call("Found JPG file: asd.JPG"), - call("Found JPG file: camel.jPeG"), - ), - any_order=True, - ) diff --git a/tests/todo/integration/test_file_content.py b/tests/todo/integration/test_file_content.py deleted file mode 100644 index b290b9a..0000000 --- a/tests/todo/integration/test_file_content.py +++ /dev/null @@ -1,35 +0,0 @@ -import os - -from conftest import assertdir, create_filesystem - -from organize.cli import main - - -def test_file_content(tmp_path): - # inspired by https://github.com/tfeldmann/organize/issues/43 - create_filesystem( - tmp_path, - files=[ - ( - "Test1.txt", - "Lorem MegaCorp Ltd. ipsum\nInvoice 12345\nMore text\nID: 98765", - ), - ("Test2.txt", "Tests"), - ("Test3.txt", "My Homework ..."), - ], - config=r""" - rules: - - folders: files - filters: - - filecontent: 'MegaCorp Ltd.+^Invoice (?P<number>\w+)$.+^ID: 98765$' - actions: - - rename: "MegaCorp_Invoice_{filecontent.number}.txt" - - folders: files - filters: - - filecontent: '.*Homework.*' - actions: - - rename: "Homework.txt" - """, - ) - main(["run", "--config-file=%s" % (tmp_path / "config.yaml")]) - assertdir(tmp_path, "Homework.txt", "MegaCorp_Invoice_12345.txt", "Test2.txt") diff --git a/tests/todo/integration/test_filesize.py b/tests/todo/integration/test_filesize.py deleted file mode 100644 index 11aea4b..0000000 --- a/tests/todo/integration/test_filesize.py +++ /dev/null @@ -1,88 +0,0 @@ -from unittest.mock import call - -import pytest -from conftest import create_filesystem - -from organize.cli import main - - -def test_size_zero(tmp_path, mock_echo): - create_filesystem( - tmp_path, - files=["1", "2", "3"], - config=""" - rules: - - folders: files - filters: - - filesize: 0 - actions: - - echo: '{path.name}' - """, - ) - main(["run", "--config-file=%s" % (tmp_path / "config.yaml")]) - mock_echo.assert_has_calls((call("1"), call("2"), call("3")), any_order=True) - - -def test_basic(tmp_path, mock_echo): - create_filesystem( - tmp_path, - files=[ - "empty", - ("full", "0" * 2000), - ("halffull", "0" * 1010), - ("two_thirds.txt", "0" * 666), - ], - config=""" - rules: - - folders: files - filters: - - filesize: '> 1kb, <= 1.0 KiB' - actions: - - echo: '{path.name} {filesize.bytes}' - - folders: files - filters: - - filesize: - - '> 0.5 kb' - - '<1.0 KiB' - actions: - - echo: '2/3 {filesize.bytes}' - """, - ) - main(["run", "--config-file=%s" % (tmp_path / "config.yaml")]) - mock_echo.assert_has_calls( - [ - call("halffull 1010"), - call("2/3 666"), - ], - any_order=True, - ) - - -@pytest.mark.skip(reason="TODO - template vars in filters not supported") -def test_python_args(tmp_path, mock_echo): - create_filesystem( - tmp_path, - files=[ - "empty", - ("full", "0" * 2000), - ("halffull", "0" * 1010), - ("two_thirds.txt", "0" * 666), - ], - config=""" - rules: - - folders: files - filters: - - python: | - return 2000 - - filesize: '= {python}b' - actions: - - echo: '{path.name} {filesize.bytes}' - """, - ) - main(["run", "--config-file=%s" % (tmp_path / "config.yaml")]) - mock_echo.assert_has_calls( - [ - call("full 2000"), - ], - any_order=True, - ) diff --git a/tests/todo/integration/test_globstrings.py b/tests/todo/integration/test_globstrings.py deleted file mode 100644 index d8d9a6c..0000000 --- a/tests/todo/integration/test_globstrings.py +++ /dev/null @@ -1,154 +0,0 @@ -from conftest import assertdir, create_filesystem - -from organize.cli import main - - -def test_globstr(tmp_path): - # inspired by https://github.com/tfeldmann/organize/issues/39 - create_filesystem( - tmp_path, - files=[ - "Test.pdf", - "Invoice.pdf", - "Other.pdf", - "Start.txt", - "journal.md", - "sub/test.pdf", - "sub/other/marks.pdf", - ], - config=""" - rules: - - folders: files/*.pdf - actions: - - shell: "rm {path}" - """, - ) - main(["run", "--config-file=%s" % (tmp_path / "config.yaml")]) - assertdir( - tmp_path, "Start.txt", "journal.md", "sub/test.pdf", "sub/other/marks.pdf" - ) - - -def test_globstr_subfolder(tmp_path): - create_filesystem( - tmp_path, - files=[ - "Test.pdf", - "Invoice.pdf", - "Other.pdf", - "Start.txt", - "sub/journal.md", - "sub/test.pdf", - "sub/other/marks.pdf", - ], - config=""" - rules: - - folders: files/**/*.pdf - actions: - - shell: "rm {path}" - """, - ) - main(["run", "--config-file=%s" % (tmp_path / "config.yaml")]) - assertdir(tmp_path, "Start.txt", "sub/journal.md") - - -def test_globstr_subfolder_serious(tmp_path): - create_filesystem( - tmp_path, - files=[ - "Test.pdf", - "Invoice.pdf", - "Other.pdf", - "Start.txt", - "x/foo/test.pdf", - "x/sub/journal.md", - "x/sub/journal.pdf", - "wub/best.pdf", - "sub/other/marks.pdf", - "x/sub/testjournal.pdf", - "sub/test.pdf", - "wub/test.pdf", - "dub/test.pdf", - ], - config=""" - rules: - - folders: - - files/**/*ub/**/test*.pdf - - !files/dub/* - actions: - - shell: "rm {path}" - """, - ) - main(["run", "--config-file=%s" % (tmp_path / "config.yaml")]) - assertdir( - tmp_path, - "Test.pdf", - "Invoice.pdf", - "Other.pdf", - "Start.txt", - "x/foo/test.pdf", - "x/sub/journal.md", - "x/sub/journal.pdf", - "wub/best.pdf", - "sub/other/marks.pdf", - "dub/test.pdf", - ) - - -def test_globstr_exclude(tmp_path): - create_filesystem( - tmp_path, - files=[ - "Test.pdf", - "Invoice.pdf", - "Start.txt", - "other/a.txt", - "other/next/b.txt", - "exclude/test.pdf", - "exclude/sub/journal.md", - "exclude/sub/journal.pdf", - ], - config=""" - rules: - - folders: - - files/**/* - - !files/exclude/* - - '! files/*' - actions: - - shell: "rm {path}" - """, - ) - main(["run", "--config-file=%s" % (tmp_path / "config.yaml")]) - assertdir( - tmp_path, - "Test.pdf", - "Invoice.pdf", - "Start.txt", - "exclude/test.pdf", - ) - - -def test_globstr_subfolder_setting(tmp_path): - create_filesystem( - tmp_path, - files=[ - "Test.pdf", - "Invoice.pdf", - "Other.pdf", - "Start.txt", - "sub/journal.md", - "sub/test.pdf", - "sub/other/marks.pdf", - ], - config=""" - rules: - - folders: files - subfolders: true - filters: - - extension: pdf - actions: - - shell: "rm {path}" - """, - ) - main(["run", "--config-file=%s" % (tmp_path / "config.yaml")]) - assertdir(tmp_path, "Start.txt", "sub/journal.md") diff --git a/tests/todo/integration/test_integration.py b/tests/todo/integration/test_integration.py index a313fb7..44095c4 100644 --- a/tests/todo/integration/test_integration.py +++ b/tests/todo/integration/test_integration.py @@ -38,49 +38,3 @@ def test_basic(tmp_path): assertdir( tmp_path, "newname.pdf", "newname 2.pdf", "newname 3.pdf", "test.pdf", "asd.txt" ) - - -def test_globstr(tmp_path): - create_filesystem( - tmp_path, - files=["asd.txt", "newname 2.pdf", "newname.pdf", "test.pdf"], - config=""" - rules: - - folders: 'file[s]/*.pdf' - actions: - - delete - """, - ) - main(["run", "--config-file=%s" % (tmp_path / "config.yaml")]) - assertdir(tmp_path, "asd.txt") - - -@pytest.mark.skip(reason="Todo") -def test_dependent_rules(tmp_path): - create_filesystem( - tmp_path, - files=["asd.txt", "newname 2.pdf", "newname.pdf", "test.pdf"], - config=""" - rules: - - folders: files - filters: - - filename: test - actions: - - copy: files/newfolder/test.pdf - - - folders: files/newfolder/ - filters: - - filename: test - actions: - - rename: test-found.pdf - """, - ) - main(["run", "--config-file=%s" % (tmp_path / "config.yaml")]) - assertdir( - tmp_path, - "newname.pdf", - "newname 2.pdf", - "test.pdf", - "asd.txt", - "newfolder/test-found.pdf", - ) diff --git a/tests/todo/integration/test_python_filter.py b/tests/todo/integration/test_python_filter.py deleted file mode 100644 index 97cf790..0000000 --- a/tests/todo/integration/test_python_filter.py +++ /dev/null @@ -1,180 +0,0 @@ -from unittest.mock import call - -from conftest import assertdir, create_filesystem - -from organize.cli import main - - -def test_python(tmp_path, mock_echo): - create_filesystem( - tmp_path, - files=["student-01.jpg", "student-01.txt", "student-02.txt", "student-03.txt"], - config=""" - rules: - - folders: files - filters: - - extension: txt - - python: | - return int(path.name.split('.')[0][-2:]) * 100 - actions: - - echo: '{python}' - """, - ) - main(["run", "--config-file=%s" % (tmp_path / "config.yaml")]) - mock_echo.assert_has_calls( - ( - call("100"), - call("200"), - call("300"), - ), - any_order=True, - ) - - -def test_odd_detector(tmp_path, mock_echo): - create_filesystem( - tmp_path, - files=["student-01.txt", "student-02.txt", "student-03.txt", "student-04.txt"], - config=""" - rules: - - folders: files - filters: - - python: | - return int(path.stem.split('-')[1]) % 2 == 1 - actions: - - echo: 'Odd student numbers: {path.name}' - """, - ) - main(["run", "--config-file=%s" % (tmp_path / "config.yaml")]) - mock_echo.assert_has_calls( - ( - call("Odd student numbers: student-01.txt"), - call("Odd student numbers: student-03.txt"), - ), - any_order=True, - ) - - -def test_python_dict(tmp_path, mock_echo): - create_filesystem( - tmp_path, - files=["foo-01.jpg", "foo-01.txt", "bar-02.txt", "baz-03.txt"], - config=""" - rules: - - folders: files - filters: - - extension: txt - - python: | - return { - "name": path.name[:3], - "code": int(path.name.split('.')[0][-2:]) * 100, - } - actions: - - echo: '{python.code} {python.name}' - """, - ) - main(["run", "--config-file=%s" % (tmp_path / "config.yaml")]) - mock_echo.assert_has_calls( - ( - call("100 foo"), - call("200 bar"), - call("300 baz"), - ), - any_order=True, - ) - - -def test_name_reverser(tmp_path): - create_filesystem( - tmp_path, - files=["desrever.jpg", "emanelif.txt"], - config=""" - rules: - - folders: files - filters: - - extension - - python: | - return { - "reversed_name": path.stem[::-1], - } - actions: - - rename: '{python.reversed_name}.{extension}' - """, - ) - main(["run", "--config-file=%s" % (tmp_path / "config.yaml")]) - assertdir(tmp_path, "reversed.jpg", "filename.txt") - - -def test_folder_instructions(tmp_path): - """ - I would like to include path/folder-instructions into the filename because I have a - lot of different files (and there are always new categories added) I don't want - create rules for. For example my filename is - - '2019_Jobs_CategoryA_TagB_A-Media_content-name_V01_draft_eng.docx' - - which means: Move the file to the folder '2019/Jobs/CategoryA/TagB/Media/drafts/eng' - whereby 'A-' is an additional instruction and should be removed from the filename - afterwards ('2019_Jobs_CategoryA_TagB_content-name_V01_draft_eng.docx'). - - I have a rough idea to figure it out with python but I'm new to it (see below a - sketch). Is there a possibility to use such variables, conditions etc. with - organizer natively? If no, is it possible to do it with Python in Organizer at all? - - - Transform file-string into array - - Search for 'A-...', 'V...' and 'content-name' and get index of values - - remove value 'A-... and 'content-name' of array - - build new filename string - - remove value 'V...' and 'A-' of array - - build folder-path string (convert _ to /) etc. - - """ - # inspired by: https://github.com/tfeldmann/organize/issues/52 - create_filesystem( - tmp_path, - files=[ - "2019_Jobs_CategoryA_TagB_A-Media_content-name_V01_draft_eng.docx", - "2019_Work_CategoryC_V-Test_A-Audio_V14_final.pdf", - "other.pdf", - ], - config=r""" - rules: - - folders: files - filters: - - extension: - - pdf - - docx - - filename: - contains: "_" - - python: | - import os - parts = [] - instructions = dict() - for part in path.stem.split("_"): - if part.startswith("A-"): - instructions["A"] = part[2:] - elif part.startswith("V-"): - instructions["V"] = part[2:] - elif part.startswith("content-name"): - instructions["content"] = part[12:] - else: - parts.append(part) - return { - "new_path": os.path.join(*parts), - "instructions": instructions, - } - actions: - - echo: "New path: {python.new_path}" - - echo: "Instructions: {python.instructions}" - - echo: "Value of A: {python.instructions.A}" - - move: "files/{python.new_path}/{path.name}" - """, - ) - main(["run", "--config-file=%s" % (tmp_path / "config.yaml")]) - assertdir( - tmp_path, - "other.pdf", - "2019/Jobs/CategoryA/TagB/V01/draft/eng/2019_Jobs_CategoryA_TagB_A-Media_content-name_V01_draft_eng.docx", - "2019/Work/CategoryC/V14/final/2019_Work_CategoryC_V-Test_A-Audio_V14_final.pdf", - "other.pdf", - ) diff --git a/tests/todo/integration/test_regex.py b/tests/todo/integration/test_regex.py deleted file mode 100644 index f44eb4b..0000000 --- a/tests/todo/integration/test_regex.py +++ /dev/null @@ -1,25 +0,0 @@ -from conftest import assertdir, create_filesystem - -from organize.cli import main - - -def test_rename_files_date(tmp_path): - # inspired by https://github.com/tfeldmann/organize/issues/43 - create_filesystem( - tmp_path, - files=[ - "File_abc_dat20190812_xyz.pdf", - "File_xyz_bar19990101_a.pdf", - "File_123456_foo20000101_xyz20190101.pdf", - ], - config=r""" - rules: - - folders: files - filters: - - regex: 'File_.*?(?P<y>\d{4})(?P<m>\d{2})(?P<d>\d{2}).*?.pdf' - actions: - - rename: "File_{regex.d}{regex.m}{regex.y}.pdf" - """, - ) - main(["run", "--config-file=%s" % (tmp_path / "config.yaml")]) - assertdir(tmp_path, "File_12082019.pdf", "File_01011999.pdf", "File_01012000.pdf") diff --git a/tests/todo/integration/test_rename.py b/tests/todo/integration/test_rename.py deleted file mode 100644 index ccd0d1a..0000000 --- a/tests/todo/integration/test_rename.py +++ /dev/null @@ -1,38 +0,0 @@ -from conftest import assertdir, create_filesystem - -from organize.cli import main - - -def test_rename_issue51(tmp_path): - # test for issue https://github.com/tfeldmann/organize/issues/51 - create_filesystem( - tmp_path, - files=[ - "19asd_WF_test2.pdf", - "other.pdf", - "18asd_WFX_test2.pdf", - ], - config=r""" - rules: - - folders: files - filters: - - filename: - startswith: "19" - contains: - - "_WF_" - actions: - - rename: "{path.stem}_unread{path.suffix}" - - copy: - dest: "files/copy/" - overwrite: false - counter_separator: "_" - """, - ) - main(["run", "--config-file=%s" % (tmp_path / "config.yaml")]) - assertdir( - tmp_path, - "19asd_WF_test2_unread.pdf", - "other.pdf", - "copy/19asd_WF_test2_unread.pdf", - "18asd_WFX_test2.pdf", - ) diff --git a/tests/todo/integration/test_startswith.py b/tests/todo/integration/test_startswith.py deleted file mode 100644 index ec8f452..0000000 --- a/tests/todo/integration/test_startswith.py +++ /dev/null @@ -1,34 +0,0 @@ -from conftest import assertdir, create_filesystem - -from organize.cli import main - - -def test_startswith_issue74(tmp_path): - # test for issue https://github.com/tfeldmann/organize/issues/74 - create_filesystem( - tmp_path, - files=[ - "Cálculo_1.pdf", - "Cálculo_2.pdf", - "Calculo.pdf", - ], - config=r""" - # Cálculo PDF - rules: - - folders: files - filters: - - extension: - - pdf - - filename: - startswith: Cálculo - actions: - - move: "files/Cálculo Integral/Periodo #6/PDF's/" - """, - ) - main(["run", "--config-file=%s" % (tmp_path / "config.yaml")]) - assertdir( - tmp_path, - "Cálculo Integral/Periodo #6/PDF's/Cálculo_1.pdf", - "Cálculo Integral/Periodo #6/PDF's/Cálculo_2.pdf", - "Calculo.pdf", - ) diff --git a/tests/todo/integration/test_unicode.py b/tests/todo/integration/test_unicode.py deleted file mode 100644 index 29b5d57..0000000 --- a/tests/todo/integration/test_unicode.py +++ /dev/null @@ -1,63 +0,0 @@ -import pytest -from conftest import assertdir, create_filesystem - -from organize.cli import main - - -@pytest.mark.skip(reason="Todo") -def test_normalization_regex(tmp_path): - create_filesystem( - tmp_path, - files=[b"Ertra\xcc\x88gnisaufstellung.txt".decode("utf-8")], - config=""" - rules: - - folders: files - filters: - - regex: '^{}$' - actions: - - rename: "found-regex.txt" - """.format( - b"Ertr\xc3\xa4gnisaufstellung\\.txt".decode("utf-8") - ), - ) - main(["run", "--config-file=%s" % (tmp_path / "config.yaml")]) - assertdir(tmp_path, "found-regex.txt") - - -@pytest.mark.skip(reason="Todo") -def test_normalization_glob(tmp_path): - create_filesystem( - tmp_path, - files=[b"Ertra\xcc\x88gnisaufstellung.txt".decode("utf-8")], - config=""" - rules: - - folders: ./**/{}.* - actions: - - rename: "found-glob.txt" - """.format( - b"Ertr\xc3\xa4gnisaufstellung".decode("utf-8") - ), - ) - main(["run", "--config-file=%s" % (tmp_path / "config.yaml")]) - assertdir(tmp_path, "found-glob.txt") - - -@pytest.mark.skip(reason="Todo") -def test_normalization_filename(tmp_path): - create_filesystem( - tmp_path, - files=[b"Ertra\xcc\x88gnisaufstellung.txt".decode("utf-8")], - config=""" - rules: - - folders: files - filters: - - filename: - contains: {} - actions: - - rename: "found-filename.txt" - """.format( - b"Ertr\xc3\xa4gnisaufstellung".decode("utf-8") - ), - ) - main(["run", "--config-file=%s" % (tmp_path / "config.yaml")]) - assertdir(tmp_path, "found-filename.txt") diff --git a/tests/todo/todo_copy.py b/tests/todo/todo_copy.py index 8f794b8..8698381 100644 --- a/tests/todo/todo_copy.py +++ b/tests/todo/todo_copy.py @@ -55,36 +55,6 @@ def test_overwrite(mock_exists, mock_samefile, mock_copy, mock_trash, mock_mkdir ) -def test_already_exists(mock_exists, mock_samefile, mock_copy, mock_trash, mock_mkdir): - mock_exists.side_effect = [True, False] - mock_samefile.return_value = False - copy = Copy(dest="~/folder/", overwrite=False) - copy.run(**DEFAULT_ARGS) - mock_mkdir.assert_called_with(exist_ok=True, parents=True) - mock_exists.assert_called_with() - mock_trash.assert_not_called() - mock_copy.assert_called_with( - src=os.path.join(USER_DIR, "test.py"), - dst=os.path.join(USER_DIR, "folder", "test 2.py"), - ) - - -def test_already_exists_multiple( - mock_exists, mock_samefile, mock_copy, mock_trash, mock_mkdir -): - mock_exists.side_effect = [True, True, True, False] - mock_samefile.return_value = False - copy = Copy(dest="~/folder/", overwrite=False) - copy.run(**DEFAULT_ARGS) - mock_mkdir.assert_called_with(exist_ok=True, parents=True) - mock_exists.assert_called_with() - mock_trash.assert_not_called() - mock_copy.assert_called_with( - src=os.path.join(USER_DIR, "test.py"), - dst=os.path.join(USER_DIR, "folder", "test 4.py"), - ) - - def test_already_exists_multiple_with_separator( mock_exists, mock_samefile, mock_copy, mock_trash, mock_mkdir ): |