diff --git a/.gitignore b/.gitignore index a2565a9..f2bc122 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .venv __pycache__ .pytest_cache -build \ No newline at end of file +build +.claude \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index bf6aff1..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -install required modules to enviroment: - pip install -r requirements.txt \ No newline at end of file diff --git a/config.json b/config.json deleted file mode 100644 index 6cd5b96..0000000 --- a/config.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "ignore_patterns": [ - "*.png", - "*.jpg", - "*.mp3", - "*/M/*", - "*/L/*", - "*/Ostatní/*", - "*.hidden*" - ], - "last_folder": "/media/veracrypt3" -} \ No newline at end of file diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..b9a70b1 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,197 @@ +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.10" +files = [ + {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, + {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, +] + +[[package]] +name = "packaging" +version = "25.0" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, + {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, +] + +[[package]] +name = "pillow" +version = "12.0.0" +description = "Python Imaging Library (fork)" +optional = false +python-versions = ">=3.10" +files = [ + {file = "pillow-12.0.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:3adfb466bbc544b926d50fe8f4a4e6abd8c6bffd28a26177594e6e9b2b76572b"}, + {file = "pillow-12.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1ac11e8ea4f611c3c0147424eae514028b5e9077dd99ab91e1bd7bc33ff145e1"}, + {file = "pillow-12.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d49e2314c373f4c2b39446fb1a45ed333c850e09d0c59ac79b72eb3b95397363"}, + {file = "pillow-12.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c7b2a63fd6d5246349f3d3f37b14430d73ee7e8173154461785e43036ffa96ca"}, + {file = "pillow-12.0.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d64317d2587c70324b79861babb9c09f71fbb780bad212018874b2c013d8600e"}, + {file = "pillow-12.0.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d77153e14b709fd8b8af6f66a3afbb9ed6e9fc5ccf0b6b7e1ced7b036a228782"}, + {file = "pillow-12.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:32ed80ea8a90ee3e6fa08c21e2e091bba6eda8eccc83dbc34c95169507a91f10"}, + {file = "pillow-12.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c828a1ae702fc712978bda0320ba1b9893d99be0badf2647f693cc01cf0f04fa"}, + {file = "pillow-12.0.0-cp310-cp310-win32.whl", hash = "sha256:bd87e140e45399c818fac4247880b9ce719e4783d767e030a883a970be632275"}, + {file = "pillow-12.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:455247ac8a4cfb7b9bc45b7e432d10421aea9fc2e74d285ba4072688a74c2e9d"}, + {file = "pillow-12.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:6ace95230bfb7cd79ef66caa064bbe2f2a1e63d93471c3a2e1f1348d9f22d6b7"}, + {file = "pillow-12.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0fd00cac9c03256c8b2ff58f162ebcd2587ad3e1f2e397eab718c47e24d231cc"}, + {file = "pillow-12.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3475b96f5908b3b16c47533daaa87380c491357d197564e0ba34ae75c0f3257"}, + {file = "pillow-12.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:110486b79f2d112cf6add83b28b627e369219388f64ef2f960fef9ebaf54c642"}, + {file = "pillow-12.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5269cc1caeedb67e6f7269a42014f381f45e2e7cd42d834ede3c703a1d915fe3"}, + {file = "pillow-12.0.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa5129de4e174daccbc59d0a3b6d20eaf24417d59851c07ebb37aeb02947987c"}, + {file = "pillow-12.0.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bee2a6db3a7242ea309aa7ee8e2780726fed67ff4e5b40169f2c940e7eb09227"}, + {file = "pillow-12.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:90387104ee8400a7b4598253b4c406f8958f59fcf983a6cea2b50d59f7d63d0b"}, + {file = "pillow-12.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bc91a56697869546d1b8f0a3ff35224557ae7f881050e99f615e0119bf934b4e"}, + {file = "pillow-12.0.0-cp311-cp311-win32.whl", hash = "sha256:27f95b12453d165099c84f8a8bfdfd46b9e4bda9e0e4b65f0635430027f55739"}, + {file = "pillow-12.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:b583dc9070312190192631373c6c8ed277254aa6e6084b74bdd0a6d3b221608e"}, + {file = "pillow-12.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:759de84a33be3b178a64c8ba28ad5c135900359e85fb662bc6e403ad4407791d"}, + {file = "pillow-12.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:53561a4ddc36facb432fae7a9d8afbfaf94795414f5cdc5fc52f28c1dca90371"}, + {file = "pillow-12.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:71db6b4c1653045dacc1585c1b0d184004f0d7e694c7b34ac165ca70c0838082"}, + {file = "pillow-12.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2fa5f0b6716fc88f11380b88b31fe591a06c6315e955c096c35715788b339e3f"}, + {file = "pillow-12.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:82240051c6ca513c616f7f9da06e871f61bfd7805f566275841af15015b8f98d"}, + {file = "pillow-12.0.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55f818bd74fe2f11d4d7cbc65880a843c4075e0ac7226bc1a23261dbea531953"}, + {file = "pillow-12.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b87843e225e74576437fd5b6a4c2205d422754f84a06942cfaf1dc32243e45a8"}, + {file = "pillow-12.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c607c90ba67533e1b2355b821fef6764d1dd2cbe26b8c1005ae84f7aea25ff79"}, + {file = "pillow-12.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:21f241bdd5080a15bc86d3466a9f6074a9c2c2b314100dd896ac81ee6db2f1ba"}, + {file = "pillow-12.0.0-cp312-cp312-win32.whl", hash = "sha256:dd333073e0cacdc3089525c7df7d39b211bcdf31fc2824e49d01c6b6187b07d0"}, + {file = "pillow-12.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:9fe611163f6303d1619bbcb653540a4d60f9e55e622d60a3108be0d5b441017a"}, + {file = "pillow-12.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:7dfb439562f234f7d57b1ac6bc8fe7f838a4bd49c79230e0f6a1da93e82f1fad"}, + {file = "pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:0869154a2d0546545cde61d1789a6524319fc1897d9ee31218eae7a60ccc5643"}, + {file = "pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a7921c5a6d31b3d756ec980f2f47c0cfdbce0fc48c22a39347a895f41f4a6ea4"}, + {file = "pillow-12.0.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:1ee80a59f6ce048ae13cda1abf7fbd2a34ab9ee7d401c46be3ca685d1999a399"}, + {file = "pillow-12.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c50f36a62a22d350c96e49ad02d0da41dbd17ddc2e29750dbdba4323f85eb4a5"}, + {file = "pillow-12.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5193fde9a5f23c331ea26d0cf171fbf67e3f247585f50c08b3e205c7aeb4589b"}, + {file = "pillow-12.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bde737cff1a975b70652b62d626f7785e0480918dece11e8fef3c0cf057351c3"}, + {file = "pillow-12.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a6597ff2b61d121172f5844b53f21467f7082f5fb385a9a29c01414463f93b07"}, + {file = "pillow-12.0.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b817e7035ea7f6b942c13aa03bb554fc44fea70838ea21f8eb31c638326584e"}, + {file = "pillow-12.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4f1231b7dec408e8670264ce63e9c71409d9583dd21d32c163e25213ee2a344"}, + {file = "pillow-12.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e51b71417049ad6ab14c49608b4a24d8fb3fe605e5dfabfe523b58064dc3d27"}, + {file = "pillow-12.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d120c38a42c234dc9a8c5de7ceaaf899cf33561956acb4941653f8bdc657aa79"}, + {file = "pillow-12.0.0-cp313-cp313-win32.whl", hash = "sha256:4cc6b3b2efff105c6a1656cfe59da4fdde2cda9af1c5e0b58529b24525d0a098"}, + {file = "pillow-12.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:4cf7fed4b4580601c4345ceb5d4cbf5a980d030fd5ad07c4d2ec589f95f09905"}, + {file = "pillow-12.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:9f0b04c6b8584c2c193babcccc908b38ed29524b29dd464bc8801bf10d746a3a"}, + {file = "pillow-12.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7fa22993bac7b77b78cae22bad1e2a987ddf0d9015c63358032f84a53f23cdc3"}, + {file = "pillow-12.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f135c702ac42262573fe9714dfe99c944b4ba307af5eb507abef1667e2cbbced"}, + {file = "pillow-12.0.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c85de1136429c524e55cfa4e033b4a7940ac5c8ee4d9401cc2d1bf48154bbc7b"}, + {file = "pillow-12.0.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:38df9b4bfd3db902c9c2bd369bcacaf9d935b2fff73709429d95cc41554f7b3d"}, + {file = "pillow-12.0.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d87ef5795da03d742bf49439f9ca4d027cde49c82c5371ba52464aee266699a"}, + {file = "pillow-12.0.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aff9e4d82d082ff9513bdd6acd4f5bd359f5b2c870907d2b0a9c5e10d40c88fe"}, + {file = "pillow-12.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8d8ca2b210ada074d57fcee40c30446c9562e542fc46aedc19baf758a93532ee"}, + {file = "pillow-12.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:99a7f72fb6249302aa62245680754862a44179b545ded638cf1fef59befb57ef"}, + {file = "pillow-12.0.0-cp313-cp313t-win32.whl", hash = "sha256:4078242472387600b2ce8d93ade8899c12bf33fa89e55ec89fe126e9d6d5d9e9"}, + {file = "pillow-12.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2c54c1a783d6d60595d3514f0efe9b37c8808746a66920315bfd34a938d7994b"}, + {file = "pillow-12.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:26d9f7d2b604cd23aba3e9faf795787456ac25634d82cd060556998e39c6fa47"}, + {file = "pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:beeae3f27f62308f1ddbcfb0690bf44b10732f2ef43758f169d5e9303165d3f9"}, + {file = "pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:d4827615da15cd59784ce39d3388275ec093ae3ee8d7f0c089b76fa87af756c2"}, + {file = "pillow-12.0.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:3e42edad50b6909089750e65c91aa09aaf1e0a71310d383f11321b27c224ed8a"}, + {file = "pillow-12.0.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e5d8efac84c9afcb40914ab49ba063d94f5dbdf5066db4482c66a992f47a3a3b"}, + {file = "pillow-12.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:266cd5f2b63ff316d5a1bba46268e603c9caf5606d44f38c2873c380950576ad"}, + {file = "pillow-12.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:58eea5ebe51504057dd95c5b77d21700b77615ab0243d8152793dc00eb4faf01"}, + {file = "pillow-12.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f13711b1a5ba512d647a0e4ba79280d3a9a045aaf7e0cc6fbe96b91d4cdf6b0c"}, + {file = "pillow-12.0.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6846bd2d116ff42cba6b646edf5bf61d37e5cbd256425fa089fee4ff5c07a99e"}, + {file = "pillow-12.0.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c98fa880d695de164b4135a52fd2e9cd7b7c90a9d8ac5e9e443a24a95ef9248e"}, + {file = "pillow-12.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa3ed2a29a9e9d2d488b4da81dcb54720ac3104a20bf0bd273f1e4648aff5af9"}, + {file = "pillow-12.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d034140032870024e6b9892c692fe2968493790dd57208b2c37e3fb35f6df3ab"}, + {file = "pillow-12.0.0-cp314-cp314-win32.whl", hash = "sha256:1b1b133e6e16105f524a8dec491e0586d072948ce15c9b914e41cdadd209052b"}, + {file = "pillow-12.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:8dc232e39d409036af549c86f24aed8273a40ffa459981146829a324e0848b4b"}, + {file = "pillow-12.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:d52610d51e265a51518692045e372a4c363056130d922a7351429ac9f27e70b0"}, + {file = "pillow-12.0.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1979f4566bb96c1e50a62d9831e2ea2d1211761e5662afc545fa766f996632f6"}, + {file = "pillow-12.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b2e4b27a6e15b04832fe9bf292b94b5ca156016bbc1ea9c2c20098a0320d6cf6"}, + {file = "pillow-12.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fb3096c30df99fd01c7bf8e544f392103d0795b9f98ba71a8054bcbf56b255f1"}, + {file = "pillow-12.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7438839e9e053ef79f7112c881cef684013855016f928b168b81ed5835f3e75e"}, + {file = "pillow-12.0.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d5c411a8eaa2299322b647cd932586b1427367fd3184ffbb8f7a219ea2041ca"}, + {file = "pillow-12.0.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7e091d464ac59d2c7ad8e7e08105eaf9dafbc3883fd7265ffccc2baad6ac925"}, + {file = "pillow-12.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:792a2c0be4dcc18af9d4a2dfd8a11a17d5e25274a1062b0ec1c2d79c76f3e7f8"}, + {file = "pillow-12.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:afbefa430092f71a9593a99ab6a4e7538bc9eabbf7bf94f91510d3503943edc4"}, + {file = "pillow-12.0.0-cp314-cp314t-win32.whl", hash = "sha256:3830c769decf88f1289680a59d4f4c46c72573446352e2befec9a8512104fa52"}, + {file = "pillow-12.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:905b0365b210c73afb0ebe9101a32572152dfd1c144c7e28968a331b9217b94a"}, + {file = "pillow-12.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:99353a06902c2e43b43e8ff74ee65a7d90307d82370604746738a1e0661ccca7"}, + {file = "pillow-12.0.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b22bd8c974942477156be55a768f7aa37c46904c175be4e158b6a86e3a6b7ca8"}, + {file = "pillow-12.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:805ebf596939e48dbb2e4922a1d3852cfc25c38160751ce02da93058b48d252a"}, + {file = "pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cae81479f77420d217def5f54b5b9d279804d17e982e0f2fa19b1d1e14ab5197"}, + {file = "pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aeaefa96c768fc66818730b952a862235d68825c178f1b3ffd4efd7ad2edcb7c"}, + {file = "pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09f2d0abef9e4e2f349305a4f8cc784a8a6c2f58a8c4892eea13b10a943bd26e"}, + {file = "pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bdee52571a343d721fb2eb3b090a82d959ff37fc631e3f70422e0c2e029f3e76"}, + {file = "pillow-12.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:b290fd8aa38422444d4b50d579de197557f182ef1068b75f5aa8558638b8d0a5"}, + {file = "pillow-12.0.0.tar.gz", hash = "sha256:87d4f8125c9988bfbed67af47dd7a953e2fc7b0cc1e7800ec6d2080d490bb353"}, +] + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=8.2)", "sphinx-autobuild", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] +fpx = ["olefile"] +mic = ["olefile"] +test-arrow = ["arro3-compute", "arro3-core", "nanoarrow", "pyarrow"] +tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma (>=5)", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "trove-classifiers (>=2024.10.12)"] +xmp = ["defusedxml"] + +[[package]] +name = "pluggy" +version = "1.6.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, + {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["coverage", "pytest", "pytest-benchmark"] + +[[package]] +name = "pygments" +version = "2.19.2" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, + {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pytest" +version = "9.0.2" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.10" +files = [ + {file = "pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b"}, + {file = "pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11"}, +] + +[package.dependencies] +colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} +iniconfig = ">=1.0.1" +packaging = ">=22" +pluggy = ">=1.5,<2" +pygments = ">=2.7.2" + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.12" +content-hash = "d9b2c3a8467631e5de03f3a79ad641da445743ec08afb777b0fa7eef1b046045" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..de79d31 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,22 @@ +[tool.poetry] +name = "tagger" +version = "1.0.3" +description = "Universal file tagging utility" +authors = ["Jan Doubravský "] +readme = "README.md" +package-mode = false + + +[tool.poetry.dependencies] +python = "^3.12" +pillow = "^12.0.0" + + +[tool.poetry.group.dev.dependencies] +pytest = "^9.0.2" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + + diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 037103e..0000000 --- a/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -pillow \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..d4839a6 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +# Tests package diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..9d10015 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,28 @@ +""" +Konfigurace pytest - sdílené fixtures a nastavení pro všechny testy +""" +import pytest +import tempfile +import shutil +from pathlib import Path + + +@pytest.fixture(scope="session") +def session_temp_dir(): + """Session-wide dočasný adresář""" + temp_dir = Path(tempfile.mkdtemp()) + yield temp_dir + shutil.rmtree(temp_dir, ignore_errors=True) + + +@pytest.fixture(autouse=True) +def cleanup_config_files(): + """Automaticky vyčistí config.json soubory po každém testu""" + yield + # Cleanup po testu + config_file = Path("config.json") + if config_file.exists(): + try: + config_file.unlink() + except Exception: + pass diff --git a/tests/test_config.py b/tests/test_config.py new file mode 100644 index 0000000..abc933f --- /dev/null +++ b/tests/test_config.py @@ -0,0 +1,252 @@ +import pytest +import json +from pathlib import Path +from src.core.config import load_config, save_config, default_config + + +class TestConfig: + """Testy pro config modul""" + + @pytest.fixture + def temp_config_file(self, tmp_path, monkeypatch): + """Fixture pro dočasný config soubor""" + config_path = tmp_path / "test_config.json" + # Změníme CONFIG_FILE v modulu config + import src.core.config as config_module + monkeypatch.setattr(config_module, 'CONFIG_FILE', config_path) + return config_path + + def test_default_config_structure(self): + """Test struktury defaultní konfigurace""" + assert "ignore_patterns" in default_config + assert "last_folder" in default_config + assert isinstance(default_config["ignore_patterns"], list) + assert default_config["last_folder"] is None + + def test_load_config_nonexistent_file(self, temp_config_file): + """Test načtení konfigurace když soubor neexistuje""" + config = load_config() + + assert config == default_config + assert config["ignore_patterns"] == [] + assert config["last_folder"] is None + + def test_save_config(self, temp_config_file): + """Test uložení konfigurace""" + test_config = { + "ignore_patterns": ["*.tmp", "*.log"], + "last_folder": "/home/user/documents" + } + + save_config(test_config) + + # Kontrola že soubor existuje + assert temp_config_file.exists() + + # Kontrola obsahu + with open(temp_config_file, "r", encoding="utf-8") as f: + saved_data = json.load(f) + + assert saved_data == test_config + + def test_load_config_existing_file(self, temp_config_file): + """Test načtení existující konfigurace""" + test_config = { + "ignore_patterns": ["*.tmp"], + "last_folder": "/test/path" + } + + # Uložení + save_config(test_config) + + # Načtení + loaded_config = load_config() + + assert loaded_config == test_config + assert loaded_config["ignore_patterns"] == ["*.tmp"] + assert loaded_config["last_folder"] == "/test/path" + + def test_save_and_load_config_cycle(self, temp_config_file): + """Test cyklu uložení a načtení""" + original_config = { + "ignore_patterns": ["*.jpg", "*.png", "*.gif"], + "last_folder": "/home/user/pictures" + } + + save_config(original_config) + loaded_config = load_config() + + assert loaded_config == original_config + + def test_config_json_format(self, temp_config_file): + """Test že config je uložen ve správném JSON formátu""" + test_config = { + "ignore_patterns": ["*.tmp"], + "last_folder": "/test" + } + + save_config(test_config) + + # Kontrola formátování + with open(temp_config_file, "r", encoding="utf-8") as f: + content = f.read() + + # Mělo by být naformátováno s indentací + assert " " in content # 2 mezery jako indent + + def test_config_utf8_encoding(self, temp_config_file): + """Test UTF-8 encoding s českými znaky""" + test_config = { + "ignore_patterns": ["*.čeština"], + "last_folder": "/cesta/s/čestnými/znaky" + } + + save_config(test_config) + loaded_config = load_config() + + assert loaded_config == test_config + assert loaded_config["last_folder"] == "/cesta/s/čestnými/znaky" + + def test_config_empty_ignore_patterns(self, temp_config_file): + """Test s prázdným seznamem ignore_patterns""" + test_config = { + "ignore_patterns": [], + "last_folder": "/test" + } + + save_config(test_config) + loaded_config = load_config() + + assert loaded_config["ignore_patterns"] == [] + + def test_config_null_last_folder(self, temp_config_file): + """Test s None hodnotou pro last_folder""" + test_config = { + "ignore_patterns": ["*.tmp"], + "last_folder": None + } + + save_config(test_config) + loaded_config = load_config() + + assert loaded_config["last_folder"] is None + + def test_config_multiple_ignore_patterns(self, temp_config_file): + """Test s více ignore patterny""" + patterns = ["*.tmp", "*.log", "*.cache", "*/node_modules/*", "*.pyc"] + test_config = { + "ignore_patterns": patterns, + "last_folder": "/test" + } + + save_config(test_config) + loaded_config = load_config() + + assert loaded_config["ignore_patterns"] == patterns + assert len(loaded_config["ignore_patterns"]) == 5 + + def test_config_special_characters_in_patterns(self, temp_config_file): + """Test se speciálními znaky v patterns""" + test_config = { + "ignore_patterns": ["*.tmp", "file[0-9].txt", "test?.log"], + "last_folder": "/test" + } + + save_config(test_config) + loaded_config = load_config() + + assert loaded_config["ignore_patterns"] == test_config["ignore_patterns"] + + def test_load_config_corrupted_file(self, temp_config_file): + """Test načtení poškozeného config souboru""" + # Vytvoření poškozeného JSON + with open(temp_config_file, "w") as f: + f.write("{ invalid json }") + + # Mělo by vrátit default config + config = load_config() + assert config == default_config + + def test_load_config_returns_new_dict(self, temp_config_file): + """Test že load_config vrací nový dictionary (ne stejnou referenci)""" + config1 = load_config() + config2 = load_config() + + # Měly by to být různé objekty (ne stejná reference) + assert config1 is not config2 + + # Ale hodnoty by měly být stejné + assert config1 == config2 + + def test_config_overwrite(self, temp_config_file): + """Test přepsání existující konfigurace""" + config1 = { + "ignore_patterns": ["*.tmp"], + "last_folder": "/path1" + } + + config2 = { + "ignore_patterns": ["*.log"], + "last_folder": "/path2" + } + + save_config(config1) + save_config(config2) + + loaded = load_config() + assert loaded == config2 + + def test_config_path_with_spaces(self, temp_config_file): + """Test s cestou obsahující mezery""" + test_config = { + "ignore_patterns": [], + "last_folder": "/path/with spaces/in name" + } + + save_config(test_config) + loaded_config = load_config() + + assert loaded_config["last_folder"] == "/path/with spaces/in name" + + def test_config_long_path(self, temp_config_file): + """Test s dlouhou cestou""" + long_path = "/very/long/path/" + "subdir/" * 50 + "final" + test_config = { + "ignore_patterns": [], + "last_folder": long_path + } + + save_config(test_config) + loaded_config = load_config() + + assert loaded_config["last_folder"] == long_path + + def test_config_many_patterns(self, temp_config_file): + """Test s velkým počtem patterns""" + patterns = [f"*.ext{i}" for i in range(100)] + test_config = { + "ignore_patterns": patterns, + "last_folder": "/test" + } + + save_config(test_config) + loaded_config = load_config() + + assert len(loaded_config["ignore_patterns"]) == 100 + assert loaded_config["ignore_patterns"] == patterns + + def test_config_ensure_ascii_false(self, temp_config_file): + """Test že ensure_ascii=False funguje správně""" + test_config = { + "ignore_patterns": ["čeština", "русский", "中文"], + "last_folder": "/cesta/čeština" + } + + save_config(test_config) + + # Kontrola že znaky nejsou escapovány + with open(temp_config_file, "r", encoding="utf-8") as f: + content = f.read() + + assert "čeština" in content + assert "\\u" not in content # Nemělo by být escapováno diff --git a/tests/test_file.py b/tests/test_file.py new file mode 100644 index 0000000..aa9149d --- /dev/null +++ b/tests/test_file.py @@ -0,0 +1,265 @@ +import pytest +import json +from pathlib import Path +from src.core.file import File +from src.core.tag import Tag +from src.core.tag_manager import TagManager + + +class TestFile: + """Testy pro třídu File""" + + @pytest.fixture + def temp_dir(self, tmp_path): + """Fixture pro dočasný adresář""" + return tmp_path + + @pytest.fixture + def tag_manager(self): + """Fixture pro TagManager""" + return TagManager() + + @pytest.fixture + def test_file(self, temp_dir): + """Fixture pro testovací soubor""" + test_file = temp_dir / "test.txt" + test_file.write_text("test content") + return test_file + + def test_file_creation(self, test_file, tag_manager): + """Test vytvoření File objektu""" + file_obj = File(test_file, tag_manager) + assert file_obj.file_path == test_file + assert file_obj.filename == "test.txt" + assert file_obj.new == True + + def test_file_metadata_filename(self, test_file, tag_manager): + """Test názvu metadata souboru""" + file_obj = File(test_file, tag_manager) + expected = test_file.parent / ".test.txt.!tag" + assert file_obj.metadata_filename == expected + + def test_file_initial_tags(self, test_file, tag_manager): + """Test že nový soubor má tag Stav/Nové""" + file_obj = File(test_file, tag_manager) + assert len(file_obj.tags) == 1 + assert file_obj.tags[0].full_path == "Stav/Nové" + + def test_file_metadata_saved(self, test_file, tag_manager): + """Test že metadata jsou uložena při vytvoření""" + file_obj = File(test_file, tag_manager) + assert file_obj.metadata_filename.exists() + + def test_file_save_metadata(self, test_file, tag_manager): + """Test uložení metadat""" + file_obj = File(test_file, tag_manager) + file_obj.new = False + file_obj.ignored = True + file_obj.save_metadata() + + # Načtení a kontrola + with open(file_obj.metadata_filename, "r", encoding="utf-8") as f: + data = json.load(f) + + assert data["new"] == False + assert data["ignored"] == True + + def test_file_load_metadata(self, test_file, tag_manager): + """Test načtení metadat""" + # Vytvoření a uložení metadat + file_obj = File(test_file, tag_manager) + tag = tag_manager.add_tag("Video", "HD") + file_obj.tags.append(tag) + file_obj.date = "2025-01-15" + file_obj.save_metadata() + + # Vytvoření nového objektu - měl by načíst metadata + file_obj2 = File(test_file, tag_manager) + assert len(file_obj2.tags) == 2 # Stav/Nové + Video/HD + assert file_obj2.date == "2025-01-15" + + # Kontrola že tagy obsahují správné hodnoty + tag_paths = {tag.full_path for tag in file_obj2.tags} + assert "Video/HD" in tag_paths + assert "Stav/Nové" in tag_paths + + def test_file_set_date(self, test_file, tag_manager): + """Test nastavení data""" + file_obj = File(test_file, tag_manager) + file_obj.set_date("2025-12-25") + assert file_obj.date == "2025-12-25" + + # Kontrola že bylo uloženo + with open(file_obj.metadata_filename, "r", encoding="utf-8") as f: + data = json.load(f) + assert data["date"] == "2025-12-25" + + def test_file_set_date_to_none(self, test_file, tag_manager): + """Test smazání data""" + file_obj = File(test_file, tag_manager) + file_obj.set_date("2025-12-25") + file_obj.set_date(None) + assert file_obj.date is None + + def test_file_set_date_empty_string(self, test_file, tag_manager): + """Test nastavení prázdného řetězce jako datum""" + file_obj = File(test_file, tag_manager) + file_obj.set_date("2025-12-25") + file_obj.set_date("") + assert file_obj.date is None + + def test_file_add_tag_object(self, test_file, tag_manager): + """Test přidání Tag objektu""" + file_obj = File(test_file, tag_manager) + tag = Tag("Video", "4K") + file_obj.add_tag(tag) + + assert tag in file_obj.tags + assert len(file_obj.tags) == 2 # Stav/Nové + Video/4K + + def test_file_add_tag_string(self, test_file, tag_manager): + """Test přidání tagu jako string""" + file_obj = File(test_file, tag_manager) + file_obj.add_tag("Audio/MP3") + + tag_paths = {tag.full_path for tag in file_obj.tags} + assert "Audio/MP3" in tag_paths + + def test_file_add_tag_string_without_category(self, test_file, tag_manager): + """Test přidání tagu bez kategorie (použije 'default')""" + file_obj = File(test_file, tag_manager) + file_obj.add_tag("SimpleTag") + + tag_paths = {tag.full_path for tag in file_obj.tags} + assert "default/SimpleTag" in tag_paths + + def test_file_add_duplicate_tag(self, test_file, tag_manager): + """Test že duplicitní tag není přidán""" + file_obj = File(test_file, tag_manager) + tag = Tag("Video", "HD") + file_obj.add_tag(tag) + file_obj.add_tag(tag) + + # Spočítáme kolikrát se tag vyskytuje + count = sum(1 for t in file_obj.tags if t == tag) + assert count == 1 + + def test_file_remove_tag_object(self, test_file, tag_manager): + """Test odstranění Tag objektu""" + file_obj = File(test_file, tag_manager) + tag = Tag("Video", "HD") + file_obj.add_tag(tag) + file_obj.remove_tag(tag) + + assert tag not in file_obj.tags + + def test_file_remove_tag_string(self, test_file, tag_manager): + """Test odstranění tagu jako string""" + file_obj = File(test_file, tag_manager) + file_obj.add_tag("Video/HD") + file_obj.remove_tag("Video/HD") + + tag_paths = {tag.full_path for tag in file_obj.tags} + assert "Video/HD" not in tag_paths + + def test_file_remove_tag_string_without_category(self, test_file, tag_manager): + """Test odstranění tagu bez kategorie""" + file_obj = File(test_file, tag_manager) + file_obj.add_tag("SimpleTag") + file_obj.remove_tag("SimpleTag") + + tag_paths = {tag.full_path for tag in file_obj.tags} + assert "default/SimpleTag" not in tag_paths + + def test_file_remove_nonexistent_tag(self, test_file, tag_manager): + """Test odstranění neexistujícího tagu (nemělo by vyhodit výjimku)""" + file_obj = File(test_file, tag_manager) + initial_count = len(file_obj.tags) + file_obj.remove_tag("Nonexistent/Tag") + assert len(file_obj.tags) == initial_count + + def test_file_without_tagmanager(self, test_file): + """Test File bez TagManager""" + file_obj = File(test_file, tagmanager=None) + assert file_obj.tagmanager is None + assert len(file_obj.tags) == 0 # Bez TagManager se nepřidá Stav/Nové + + def test_file_metadata_persistence(self, test_file, tag_manager): + """Test že metadata přežijí reload""" + # Vytvoření a úprava souboru + file_obj1 = File(test_file, tag_manager) + file_obj1.add_tag("Video/HD") + file_obj1.add_tag("Audio/Stereo") + file_obj1.set_date("2025-01-01") + file_obj1.new = False + file_obj1.ignored = True + file_obj1.save_metadata() + + # Načtení nového objektu + file_obj2 = File(test_file, tag_manager) + + # Kontrola + assert file_obj2.new == False + assert file_obj2.ignored == True + assert file_obj2.date == "2025-01-01" + + tag_paths = {tag.full_path for tag in file_obj2.tags} + assert "Video/HD" in tag_paths + assert "Audio/Stereo" in tag_paths + + def test_file_metadata_json_format(self, test_file, tag_manager): + """Test formátu JSON metadat""" + file_obj = File(test_file, tag_manager) + file_obj.add_tag("Test/Tag") + file_obj.set_date("2025-06-15") + + # Kontrola obsahu JSON + with open(file_obj.metadata_filename, "r", encoding="utf-8") as f: + data = json.load(f) + + assert "new" in data + assert "ignored" in data + assert "tags" in data + assert "date" in data + assert isinstance(data["tags"], list) + + def test_file_unicode_handling(self, temp_dir, tag_manager): + """Test správného zacházení s unicode znaky""" + test_file = temp_dir / "český_soubor.txt" + test_file.write_text("obsah") + + file_obj = File(test_file, tag_manager) + file_obj.add_tag("Kategorie/Český tag") + file_obj.save_metadata() + + # Reload a kontrola + file_obj2 = File(test_file, tag_manager) + tag_paths = {tag.full_path for tag in file_obj2.tags} + assert "Kategorie/Český tag" in tag_paths + + def test_file_complex_scenario(self, test_file, tag_manager): + """Test komplexního scénáře použití""" + file_obj = File(test_file, tag_manager) + + # Přidání více tagů + file_obj.add_tag("Video/HD") + file_obj.add_tag("Video/Stereo") + file_obj.add_tag("Stav/Zkontrolováno") + file_obj.set_date("2025-01-01") + + # Odstranění tagu + file_obj.remove_tag("Stav/Nové") + + # Kontrola stavu + tag_paths = {tag.full_path for tag in file_obj.tags} + assert "Video/HD" in tag_paths + assert "Video/Stereo" in tag_paths + assert "Stav/Zkontrolováno" in tag_paths + assert "Stav/Nové" not in tag_paths + assert file_obj.date == "2025-01-01" + + # Reload a kontrola persistence + file_obj2 = File(test_file, tag_manager) + tag_paths2 = {tag.full_path for tag in file_obj2.tags} + assert tag_paths == tag_paths2 + assert file_obj2.date == "2025-01-01" diff --git a/tests/test_file_manager.py b/tests/test_file_manager.py new file mode 100644 index 0000000..96d3e34 --- /dev/null +++ b/tests/test_file_manager.py @@ -0,0 +1,283 @@ +import pytest +from pathlib import Path +from src.core.file_manager import FileManager +from src.core.tag_manager import TagManager +from src.core.file import File +from src.core.tag import Tag + + +class TestFileManager: + """Testy pro třídu FileManager""" + + @pytest.fixture + def tag_manager(self): + """Fixture pro TagManager""" + return TagManager() + + @pytest.fixture + def file_manager(self, tag_manager): + """Fixture pro FileManager""" + return FileManager(tag_manager) + + @pytest.fixture + def temp_dir(self, tmp_path): + """Fixture pro dočasný adresář s testovacími soubory""" + # Vytvoření struktury souborů + (tmp_path / "file1.txt").write_text("content1") + (tmp_path / "file2.txt").write_text("content2") + (tmp_path / "file3.jpg").write_text("image") + + # Podsložka + subdir = tmp_path / "subdir" + subdir.mkdir() + (subdir / "file4.txt").write_text("content4") + + return tmp_path + + @pytest.fixture + def temp_config_file(self, tmp_path, monkeypatch): + """Fixture pro dočasný config soubor""" + config_path = tmp_path / "test_config.json" + # Změníme CONFIG_FILE v modulu config + import src.core.config as config_module + monkeypatch.setattr(config_module, 'CONFIG_FILE', config_path) + return config_path + + def test_file_manager_creation(self, file_manager, tag_manager): + """Test vytvoření FileManager""" + assert file_manager.filelist == [] + assert file_manager.folders == [] + assert file_manager.tagmanager == tag_manager + + def test_file_manager_append_folder(self, file_manager, temp_dir, temp_config_file): + """Test přidání složky""" + file_manager.append(temp_dir) + + assert temp_dir in file_manager.folders + assert len(file_manager.filelist) > 0 + + def test_file_manager_append_folder_finds_all_files(self, file_manager, temp_dir, temp_config_file): + """Test že append najde všechny soubory včetně podsložek""" + file_manager.append(temp_dir) + + # Měli bychom najít file1.txt, file2.txt, file3.jpg, subdir/file4.txt + # (ne .!tag soubory) + filenames = {f.filename for f in file_manager.filelist} + assert "file1.txt" in filenames + assert "file2.txt" in filenames + assert "file3.jpg" in filenames + assert "file4.txt" in filenames + + def test_file_manager_ignores_tag_files(self, file_manager, temp_dir, temp_config_file): + """Test že .!tag soubory jsou ignorovány""" + # Vytvoření .!tag souboru + (temp_dir / ".file1.txt.!tag").write_text('{"tags": []}') + + file_manager.append(temp_dir) + + filenames = {f.filename for f in file_manager.filelist} + assert ".file1.txt.!tag" not in filenames + + def test_file_manager_ignore_patterns(self, file_manager, temp_dir, temp_config_file): + """Test ignorování souborů podle patternů""" + file_manager.config["ignore_patterns"] = ["*.jpg"] + file_manager.append(temp_dir) + + filenames = {f.filename for f in file_manager.filelist} + assert "file3.jpg" not in filenames + assert "file1.txt" in filenames + + def test_file_manager_ignore_patterns_path(self, file_manager, temp_dir, temp_config_file): + """Test ignorování podle celé cesty""" + file_manager.config["ignore_patterns"] = ["*/subdir/*"] + file_manager.append(temp_dir) + + filenames = {f.filename for f in file_manager.filelist} + assert "file4.txt" not in filenames + assert "file1.txt" in filenames + + def test_file_manager_assign_tag_to_file_objects(self, file_manager, temp_dir, temp_config_file): + """Test přiřazení tagu k souborům""" + file_manager.append(temp_dir) + + # Vybereme první dva soubory + files = file_manager.filelist[:2] + tag = Tag("Video", "HD") + + file_manager.assign_tag_to_file_objects(files, tag) + + for f in files: + assert tag in f.tags + + def test_file_manager_assign_tag_string(self, file_manager, temp_dir, temp_config_file): + """Test přiřazení tagu jako string""" + file_manager.append(temp_dir) + + files = file_manager.filelist[:1] + file_manager.assign_tag_to_file_objects(files, "Video/4K") + + tag_paths = {tag.full_path for tag in files[0].tags} + assert "Video/4K" in tag_paths + + def test_file_manager_assign_tag_without_category(self, file_manager, temp_dir, temp_config_file): + """Test přiřazení tagu bez kategorie""" + file_manager.append(temp_dir) + + files = file_manager.filelist[:1] + file_manager.assign_tag_to_file_objects(files, "SimpleTag") + + tag_paths = {tag.full_path for tag in files[0].tags} + assert "default/SimpleTag" in tag_paths + + def test_file_manager_remove_tag_from_file_objects(self, file_manager, temp_dir, temp_config_file): + """Test odstranění tagu ze souborů""" + file_manager.append(temp_dir) + + files = file_manager.filelist[:2] + tag = Tag("Video", "HD") + + # Přidání a pak odstranění + file_manager.assign_tag_to_file_objects(files, tag) + file_manager.remove_tag_from_file_objects(files, tag) + + for f in files: + assert tag not in f.tags + + def test_file_manager_remove_tag_string(self, file_manager, temp_dir, temp_config_file): + """Test odstranění tagu jako string""" + file_manager.append(temp_dir) + + files = file_manager.filelist[:1] + file_manager.assign_tag_to_file_objects(files, "Video/HD") + file_manager.remove_tag_from_file_objects(files, "Video/HD") + + tag_paths = {tag.full_path for tag in files[0].tags} + assert "Video/HD" not in tag_paths + + def test_file_manager_filter_files_by_tags_empty(self, file_manager, temp_dir, temp_config_file): + """Test filtrace bez tagů vrací všechny soubory""" + file_manager.append(temp_dir) + + filtered = file_manager.filter_files_by_tags([]) + assert len(filtered) == len(file_manager.filelist) + + def test_file_manager_filter_files_by_tags_none(self, file_manager, temp_dir, temp_config_file): + """Test filtrace s None vrací všechny soubory""" + file_manager.append(temp_dir) + + filtered = file_manager.filter_files_by_tags(None) + assert len(filtered) == len(file_manager.filelist) + + def test_file_manager_filter_files_by_single_tag(self, file_manager, temp_dir, temp_config_file): + """Test filtrace podle jednoho tagu""" + file_manager.append(temp_dir) + + # Přiřadíme tag některým souborům + tag = Tag("Video", "HD") + files_to_tag = file_manager.filelist[:2] + file_manager.assign_tag_to_file_objects(files_to_tag, tag) + + # Filtrujeme + filtered = file_manager.filter_files_by_tags([tag]) + assert len(filtered) == 2 + for f in filtered: + assert tag in f.tags + + def test_file_manager_filter_files_by_multiple_tags(self, file_manager, temp_dir, temp_config_file): + """Test filtrace podle více tagů (AND logika)""" + file_manager.append(temp_dir) + + tag1 = Tag("Video", "HD") + tag2 = Tag("Audio", "Stereo") + + # První soubor má oba tagy + file_manager.assign_tag_to_file_objects([file_manager.filelist[0]], tag1) + file_manager.assign_tag_to_file_objects([file_manager.filelist[0]], tag2) + + # Druhý soubor má jen první tag + file_manager.assign_tag_to_file_objects([file_manager.filelist[1]], tag1) + + # Filtrujeme podle obou tagů + filtered = file_manager.filter_files_by_tags([tag1, tag2]) + assert len(filtered) == 1 + assert filtered[0] == file_manager.filelist[0] + + def test_file_manager_filter_files_by_tag_strings(self, file_manager, temp_dir, temp_config_file): + """Test filtrace podle tagů jako stringy""" + file_manager.append(temp_dir) + + file_manager.assign_tag_to_file_objects([file_manager.filelist[0]], "Video/HD") + + filtered = file_manager.filter_files_by_tags(["Video/HD"]) + assert len(filtered) == 1 + + def test_file_manager_on_files_changed_callback(self, file_manager, temp_dir, temp_config_file): + """Test callback při změně souborů""" + callback_called = [] + + def callback(filelist): + callback_called.append(filelist) + + file_manager.on_files_changed = callback + file_manager.append(temp_dir) + + # Přiřazení tagu by mělo zavolat callback + tag = Tag("Video", "HD") + file_manager.assign_tag_to_file_objects([file_manager.filelist[0]], tag) + + assert len(callback_called) == 1 + + def test_file_manager_complex_scenario(self, file_manager, temp_dir, temp_config_file): + """Test komplexního scénáře""" + # Přidání složky + file_manager.append(temp_dir) + initial_count = len(file_manager.filelist) + assert initial_count > 0 + + # Přiřazení různých tagů různým souborům + tag_hd = Tag("Video", "HD") + tag_4k = Tag("Video", "4K") + tag_stereo = Tag("Audio", "Stereo") + + file_manager.assign_tag_to_file_objects([file_manager.filelist[0]], tag_hd) + file_manager.assign_tag_to_file_objects([file_manager.filelist[0]], tag_stereo) + file_manager.assign_tag_to_file_objects([file_manager.filelist[1]], tag_4k) + + # Filtrace podle HD + filtered_hd = file_manager.filter_files_by_tags([tag_hd]) + assert len(filtered_hd) == 1 + + # Filtrace podle HD + Stereo + filtered_both = file_manager.filter_files_by_tags([tag_hd, tag_stereo]) + assert len(filtered_both) == 1 + + # Filtrace podle 4K + filtered_4k = file_manager.filter_files_by_tags([tag_4k]) + assert len(filtered_4k) == 1 + + def test_file_manager_config_last_folder(self, file_manager, temp_dir, temp_config_file): + """Test uložení poslední složky do konfigurace""" + file_manager.append(temp_dir) + + assert file_manager.config["last_folder"] == str(temp_dir) + + def test_file_manager_empty_filelist(self, file_manager): + """Test práce s prázdným filelistem""" + # Test filtrace na prázdném seznamu + filtered = file_manager.filter_files_by_tags([Tag("Video", "HD")]) + assert filtered == [] + + # Test přiřazení tagů na prázdný seznam + file_manager.assign_tag_to_file_objects([], Tag("Video", "HD")) + assert len(file_manager.filelist) == 0 + + def test_file_manager_multiple_ignore_patterns(self, file_manager, temp_dir, temp_config_file): + """Test více ignore patternů najednou""" + file_manager.config["ignore_patterns"] = ["*.jpg", "*.png", "*/subdir/*"] + file_manager.append(temp_dir) + + filenames = {f.filename for f in file_manager.filelist} + assert "file3.jpg" not in filenames + assert "file4.txt" not in filenames + assert "file1.txt" in filenames + assert "file2.txt" in filenames diff --git a/tests/test_image.py b/tests/test_image.py deleted file mode 100644 index 3ce500e..0000000 --- a/tests/test_image.py +++ /dev/null @@ -1,40 +0,0 @@ -import sys, os -import tempfile -from pathlib import Path -import pytest - -# přidáme src do sys.path (pokud nespouštíš pytest s -m nebo PYTHONPATH=src) -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src"))) - -from core.image import load_icon -from PIL import Image, ImageTk -import tkinter as tk - - -@pytest.fixture(scope="module") -def tk_root(): - """Fixture pro inicializaci Tkinteru (nutné pro ImageTk).""" - root = tk.Tk() - yield root - root.destroy() - - -def test_load_icon_returns_photoimage(tk_root): - # vytvoříme dočasný obrázek - with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp: - tmp_path = Path(tmp.name) - try: - # vytvoříme 100x100 červený obrázek - img = Image.new("RGB", (100, 100), color="red") - img.save(tmp_path) - - icon = load_icon(tmp_path) - - # musí být PhotoImage - assert isinstance(icon, ImageTk.PhotoImage) - - # ověříme velikost 16x16 - assert icon.width() == 16 - assert icon.height() == 16 - finally: - tmp_path.unlink(missing_ok=True) \ No newline at end of file diff --git a/tests/test_media_utils.py b/tests/test_media_utils.py new file mode 100644 index 0000000..e626d7e --- /dev/null +++ b/tests/test_media_utils.py @@ -0,0 +1,75 @@ +import tempfile +from pathlib import Path +import pytest + +from src.core.media_utils import load_icon +from PIL import Image, ImageTk +import tkinter as tk + + +@pytest.fixture(scope="module") +def tk_root(): + """Fixture pro inicializaci Tkinteru (nutné pro ImageTk).""" + root = tk.Tk() + yield root + root.destroy() + + +def test_load_icon_returns_photoimage(tk_root): + """Test že load_icon vrací PhotoImage""" + # vytvoříme dočasný obrázek + with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp: + tmp_path = Path(tmp.name) + try: + # vytvoříme 100x100 červený obrázek + img = Image.new("RGB", (100, 100), color="red") + img.save(tmp_path) + + icon = load_icon(tmp_path) + + # musí být PhotoImage + assert isinstance(icon, ImageTk.PhotoImage) + + # ověříme velikost 16x16 + assert icon.width() == 16 + assert icon.height() == 16 + finally: + tmp_path.unlink(missing_ok=True) + + +def test_load_icon_resizes_image(tk_root): + """Test že load_icon správně změní velikost obrázku""" + with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp: + tmp_path = Path(tmp.name) + try: + # vytvoříme velký obrázek 500x500 + img = Image.new("RGB", (500, 500), color="blue") + img.save(tmp_path) + + icon = load_icon(tmp_path) + + # i velký obrázek by měl být zmenšen na 16x16 + assert icon.width() == 16 + assert icon.height() == 16 + finally: + tmp_path.unlink(missing_ok=True) + + +def test_load_icon_different_formats(tk_root): + """Test načítání různých formátů obrázků""" + formats = [".png", ".jpg", ".bmp"] + + for fmt in formats: + with tempfile.NamedTemporaryFile(suffix=fmt, delete=False) as tmp: + tmp_path = Path(tmp.name) + try: + img = Image.new("RGB", (32, 32), color="green") + img.save(tmp_path) + + icon = load_icon(tmp_path) + + assert isinstance(icon, ImageTk.PhotoImage) + assert icon.width() == 16 + assert icon.height() == 16 + finally: + tmp_path.unlink(missing_ok=True) diff --git a/tests/test_tag.py b/tests/test_tag.py new file mode 100644 index 0000000..37ccd7b --- /dev/null +++ b/tests/test_tag.py @@ -0,0 +1,106 @@ +import pytest +from src.core.tag import Tag + + +class TestTag: + """Testy pro třídu Tag""" + + def test_tag_creation(self): + """Test vytvoření tagu""" + tag = Tag("Kategorie", "Název") + assert tag.category == "Kategorie" + assert tag.name == "Název" + + def test_tag_full_path(self): + """Test full_path property""" + tag = Tag("Video", "HD") + assert tag.full_path == "Video/HD" + + def test_tag_str_representation(self): + """Test string reprezentace""" + tag = Tag("Foto", "Dovolená") + assert str(tag) == "Foto/Dovolená" + + def test_tag_repr(self): + """Test repr reprezentace""" + tag = Tag("Audio", "Hudba") + assert repr(tag) == "Tag(Audio/Hudba)" + + def test_tag_equality_same_tags(self): + """Test rovnosti stejných tagů""" + tag1 = Tag("Kategorie", "Název") + tag2 = Tag("Kategorie", "Název") + assert tag1 == tag2 + + def test_tag_equality_different_tags(self): + """Test nerovnosti různých tagů""" + tag1 = Tag("Kategorie1", "Název") + tag2 = Tag("Kategorie2", "Název") + assert tag1 != tag2 + + tag3 = Tag("Kategorie", "Název1") + tag4 = Tag("Kategorie", "Název2") + assert tag3 != tag4 + + def test_tag_equality_with_non_tag(self): + """Test porovnání s ne-Tag objektem""" + tag = Tag("Kategorie", "Název") + assert tag != "Kategorie/Název" + assert tag != 123 + assert tag != None + + def test_tag_hash(self): + """Test hashování - důležité pro použití v set/dict""" + tag1 = Tag("Kategorie", "Název") + tag2 = Tag("Kategorie", "Název") + tag3 = Tag("Jiná", "Název") + + # Stejné tagy mají stejný hash + assert hash(tag1) == hash(tag2) + # Různé tagy mají různý hash (většinou) + assert hash(tag1) != hash(tag3) + + def test_tag_in_set(self): + """Test použití tagů v set""" + tag1 = Tag("Kategorie", "Název") + tag2 = Tag("Kategorie", "Název") + tag3 = Tag("Jiná", "Název") + + tag_set = {tag1, tag2, tag3} + # tag1 a tag2 jsou stejné, takže set obsahuje pouze 2 prvky + assert len(tag_set) == 2 + assert tag1 in tag_set + assert tag3 in tag_set + + def test_tag_in_dict(self): + """Test použití tagů jako klíčů v dict""" + tag1 = Tag("Kategorie", "Název") + tag2 = Tag("Kategorie", "Název") + + tag_dict = {tag1: "hodnota1"} + tag_dict[tag2] = "hodnota2" + + # tag1 a tag2 jsou stejné, takže dict má 1 klíč + assert len(tag_dict) == 1 + assert tag_dict[tag1] == "hodnota2" + + def test_tag_with_special_characters(self): + """Test tagů se speciálními znaky""" + tag = Tag("Kategorie/Složitá", "Název s mezerami") + assert tag.category == "Kategorie/Složitá" + assert tag.name == "Název s mezerami" + assert tag.full_path == "Kategorie/Složitá/Název s mezerami" + + def test_tag_with_empty_strings(self): + """Test tagů s prázdnými řetězci""" + tag = Tag("", "") + assert tag.category == "" + assert tag.name == "" + assert tag.full_path == "/" + + def test_tag_unicode(self): + """Test tagů s unicode znaky""" + tag = Tag("Kategorie", "Čeština") + assert tag.category == "Kategorie" + assert tag.name == "Čeština" + assert tag.full_path == "Kategorie/Čeština" diff --git a/tests/test_tag_manager.py b/tests/test_tag_manager.py new file mode 100644 index 0000000..1bbbdd1 --- /dev/null +++ b/tests/test_tag_manager.py @@ -0,0 +1,198 @@ +import pytest +from src.core.tag_manager import TagManager +from src.core.tag import Tag + + +class TestTagManager: + """Testy pro třídu TagManager""" + + @pytest.fixture + def tag_manager(self): + """Fixture pro vytvoření TagManager instance""" + return TagManager() + + def test_tag_manager_creation(self, tag_manager): + """Test vytvoření TagManager""" + assert tag_manager.tags_by_category == {} + + def test_add_category(self, tag_manager): + """Test přidání kategorie""" + tag_manager.add_category("Video") + assert "Video" in tag_manager.tags_by_category + assert tag_manager.tags_by_category["Video"] == set() + + def test_add_category_duplicate(self, tag_manager): + """Test přidání duplicitní kategorie""" + tag_manager.add_category("Video") + tag_manager.add_category("Video") + assert len(tag_manager.tags_by_category) == 1 + + def test_remove_category(self, tag_manager): + """Test odstranění kategorie""" + tag_manager.add_category("Video") + tag_manager.remove_category("Video") + assert "Video" not in tag_manager.tags_by_category + + def test_remove_nonexistent_category(self, tag_manager): + """Test odstranění neexistující kategorie""" + # Nemělo by vyhodit výjimku + tag_manager.remove_category("Neexistující") + assert "Neexistující" not in tag_manager.tags_by_category + + def test_add_tag(self, tag_manager): + """Test přidání tagu""" + tag = tag_manager.add_tag("Video", "HD") + assert isinstance(tag, Tag) + assert tag.category == "Video" + assert tag.name == "HD" + assert "Video" in tag_manager.tags_by_category + assert tag in tag_manager.tags_by_category["Video"] + + def test_add_tag_creates_category(self, tag_manager): + """Test že add_tag vytvoří kategorii pokud neexistuje""" + tag = tag_manager.add_tag("NovaKategorie", "Tag") + assert "NovaKategorie" in tag_manager.tags_by_category + + def test_add_multiple_tags_same_category(self, tag_manager): + """Test přidání více tagů do stejné kategorie""" + tag1 = tag_manager.add_tag("Video", "HD") + tag2 = tag_manager.add_tag("Video", "4K") + tag3 = tag_manager.add_tag("Video", "SD") + + assert len(tag_manager.tags_by_category["Video"]) == 3 + assert tag1 in tag_manager.tags_by_category["Video"] + assert tag2 in tag_manager.tags_by_category["Video"] + assert tag3 in tag_manager.tags_by_category["Video"] + + def test_add_duplicate_tag(self, tag_manager): + """Test přidání duplicitního tagu (set zabrání duplicitám)""" + tag1 = tag_manager.add_tag("Video", "HD") + tag2 = tag_manager.add_tag("Video", "HD") + + assert len(tag_manager.tags_by_category["Video"]) == 1 + assert tag1 == tag2 + + def test_remove_tag(self, tag_manager): + """Test odstranění tagu - když je poslední, kategorie se smaže""" + tag_manager.add_tag("Video", "HD") + tag_manager.remove_tag("Video", "HD") + + # Kategorie by měla být smazána (podle implementace v tag_manager.py) + assert "Video" not in tag_manager.tags_by_category + + def test_remove_tag_removes_empty_category(self, tag_manager): + """Test že odstranění posledního tagu odstraní i kategorii""" + tag_manager.add_tag("Video", "HD") + tag_manager.remove_tag("Video", "HD") + + assert "Video" not in tag_manager.tags_by_category + + def test_remove_tag_keeps_category_with_other_tags(self, tag_manager): + """Test že odstranění tagu neodstraní kategorii s dalšími tagy""" + tag_manager.add_tag("Video", "HD") + tag_manager.add_tag("Video", "4K") + tag_manager.remove_tag("Video", "HD") + + assert "Video" in tag_manager.tags_by_category + assert len(tag_manager.tags_by_category["Video"]) == 1 + + def test_remove_nonexistent_tag(self, tag_manager): + """Test odstranění neexistujícího tagu""" + tag_manager.add_category("Video") + # Nemělo by vyhodit výjimku + tag_manager.remove_tag("Video", "Neexistující") + + def test_remove_tag_from_nonexistent_category(self, tag_manager): + """Test odstranění tagu z neexistující kategorie""" + # Nemělo by vyhodit výjimku + tag_manager.remove_tag("Neexistující", "Tag") + + def test_get_all_tags_empty(self, tag_manager): + """Test získání všech tagů (prázdný manager)""" + tags = tag_manager.get_all_tags() + assert tags == [] + + def test_get_all_tags(self, tag_manager): + """Test získání všech tagů""" + tag_manager.add_tag("Video", "HD") + tag_manager.add_tag("Video", "4K") + tag_manager.add_tag("Audio", "MP3") + + tags = tag_manager.get_all_tags() + assert len(tags) == 3 + assert "Video/HD" in tags + assert "Video/4K" in tags + assert "Audio/MP3" in tags + + def test_get_categories_empty(self, tag_manager): + """Test získání kategorií (prázdný manager)""" + categories = tag_manager.get_categories() + assert categories == [] + + def test_get_categories(self, tag_manager): + """Test získání kategorií""" + tag_manager.add_tag("Video", "HD") + tag_manager.add_tag("Audio", "MP3") + tag_manager.add_tag("Foto", "RAW") + + categories = tag_manager.get_categories() + assert len(categories) == 3 + assert "Video" in categories + assert "Audio" in categories + assert "Foto" in categories + + def test_get_tags_in_category_empty(self, tag_manager): + """Test získání tagů z prázdné kategorie""" + tag_manager.add_category("Video") + tags = tag_manager.get_tags_in_category("Video") + assert tags == [] + + def test_get_tags_in_category(self, tag_manager): + """Test získání tagů z kategorie""" + tag_manager.add_tag("Video", "HD") + tag_manager.add_tag("Video", "4K") + tag_manager.add_tag("Audio", "MP3") + + video_tags = tag_manager.get_tags_in_category("Video") + assert len(video_tags) == 2 + + # Kontrola že obsahují správné tagy (pořadí není garantováno) + tag_names = {tag.name for tag in video_tags} + assert "HD" in tag_names + assert "4K" in tag_names + + def test_get_tags_in_nonexistent_category(self, tag_manager): + """Test získání tagů z neexistující kategorie""" + tags = tag_manager.get_tags_in_category("Neexistující") + assert tags == [] + + def test_complex_scenario(self, tag_manager): + """Test komplexního scénáře použití""" + # Přidání několika kategorií a tagů + tag_manager.add_tag("Video", "HD") + tag_manager.add_tag("Video", "4K") + tag_manager.add_tag("Audio", "MP3") + tag_manager.add_tag("Audio", "FLAC") + tag_manager.add_tag("Foto", "RAW") + + # Kontrola stavu + assert len(tag_manager.get_categories()) == 3 + assert len(tag_manager.get_all_tags()) == 5 + + # Odstranění některých tagů + tag_manager.remove_tag("Video", "HD") + assert len(tag_manager.get_tags_in_category("Video")) == 1 + + # Odstranění celé kategorie + tag_manager.remove_category("Foto") + assert "Foto" not in tag_manager.get_categories() + assert len(tag_manager.get_all_tags()) == 3 + + def test_tag_uniqueness_in_set(self, tag_manager): + """Test že tagy jsou správně ukládány jako set (bez duplicit)""" + tag_manager.add_tag("Video", "HD") + tag_manager.add_tag("Video", "HD") + tag_manager.add_tag("Video", "HD") + + # I když přidáme 3x, v setu je jen 1 + assert len(tag_manager.tags_by_category["Video"]) == 1 diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..55d42d2 --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,178 @@ +import pytest +from pathlib import Path +from src.core.utils import list_files + + +class TestUtils: + """Testy pro utils funkce""" + + @pytest.fixture + def temp_dir(self, tmp_path): + """Fixture pro dočasný adresář s testovací strukturou""" + # Vytvoření souborů v root + (tmp_path / "file1.txt").write_text("content1") + (tmp_path / "file2.jpg").write_text("image") + + # Podsložka + subdir1 = tmp_path / "subdir1" + subdir1.mkdir() + (subdir1 / "file3.txt").write_text("content3") + (subdir1 / "file4.png").write_text("image2") + + # Vnořená podsložka + subdir2 = subdir1 / "subdir2" + subdir2.mkdir() + (subdir2 / "file5.txt").write_text("content5") + + # Prázdná složka + empty_dir = tmp_path / "empty" + empty_dir.mkdir() + + return tmp_path + + def test_list_files_basic(self, temp_dir): + """Test základního listování souborů""" + files = list_files(temp_dir) + assert isinstance(files, list) + assert len(files) > 0 + assert all(isinstance(f, Path) for f in files) + + def test_list_files_finds_all_files(self, temp_dir): + """Test že najde všechny soubory včetně vnořených""" + files = list_files(temp_dir) + filenames = {f.name for f in files} + + assert "file1.txt" in filenames + assert "file2.jpg" in filenames + assert "file3.txt" in filenames + assert "file4.png" in filenames + assert "file5.txt" in filenames + assert len(filenames) == 5 + + def test_list_files_recursive(self, temp_dir): + """Test rekurzivního procházení složek""" + files = list_files(temp_dir) + + # Kontrola cest - měly by obsahovat subdir1 a subdir2 + file_paths = [str(f) for f in files] + assert any("subdir1" in path for path in file_paths) + assert any("subdir2" in path for path in file_paths) + + def test_list_files_only_files_no_directories(self, temp_dir): + """Test že vrací pouze soubory, ne složky""" + files = list_files(temp_dir) + + # Všechny výsledky by měly být soubory + assert all(f.is_file() for f in files) + + # Složky by neměly být ve výsledcích + filenames = {f.name for f in files} + assert "subdir1" not in filenames + assert "subdir2" not in filenames + assert "empty" not in filenames + + def test_list_files_with_string_path(self, temp_dir): + """Test s cestou jako string""" + files = list_files(str(temp_dir)) + assert len(files) == 5 + + def test_list_files_with_path_object(self, temp_dir): + """Test s cestou jako Path objekt""" + files = list_files(temp_dir) + assert len(files) == 5 + + def test_list_files_empty_directory(self, temp_dir): + """Test prázdné složky""" + empty_dir = temp_dir / "empty" + files = list_files(empty_dir) + assert files == [] + + def test_list_files_nonexistent_directory(self): + """Test neexistující složky""" + with pytest.raises(NotADirectoryError) as exc_info: + list_files("/nonexistent/path") + assert "není platná složka" in str(exc_info.value) + + def test_list_files_file_not_directory(self, temp_dir): + """Test když je zadán soubor místo složky""" + file_path = temp_dir / "file1.txt" + with pytest.raises(NotADirectoryError) as exc_info: + list_files(file_path) + assert "není platná složka" in str(exc_info.value) + + def test_list_files_returns_absolute_paths(self, temp_dir): + """Test že vrací absolutní cesty""" + files = list_files(temp_dir) + assert all(f.is_absolute() for f in files) + + def test_list_files_different_extensions(self, temp_dir): + """Test s různými příponami""" + files = list_files(temp_dir) + extensions = {f.suffix for f in files} + + assert ".txt" in extensions + assert ".jpg" in extensions + assert ".png" in extensions + + def test_list_files_hidden_files(self, temp_dir): + """Test se skrytými soubory (začínající tečkou)""" + # Vytvoření skrytého souboru + (temp_dir / ".hidden").write_text("hidden content") + + files = list_files(temp_dir) + filenames = {f.name for f in files} + + # Skryté soubory by měly být také nalezeny + assert ".hidden" in filenames + + def test_list_files_special_characters_in_names(self, temp_dir): + """Test se speciálními znaky v názvech""" + # Vytvoření souborů se spec. znaky + (temp_dir / "soubor s mezerami.txt").write_text("content") + (temp_dir / "český_název.txt").write_text("content") + + files = list_files(temp_dir) + filenames = {f.name for f in files} + + assert "soubor s mezerami.txt" in filenames + assert "český_název.txt" in filenames + + def test_list_files_symlinks(self, temp_dir): + """Test se symbolickými linky (pokud OS podporuje)""" + try: + # Vytvoření symlinku + target = temp_dir / "file1.txt" + link = temp_dir / "link_to_file1.txt" + link.symlink_to(target) + + files = list_files(temp_dir) + # Symlink by měl být také nalezen a považován za soubor + filenames = {f.name for f in files} + assert "link_to_file1.txt" in filenames or "file1.txt" in filenames + except OSError: + # Pokud OS nepodporuje symlinky, přeskočíme + pytest.skip("OS does not support symlinks") + + def test_list_files_large_directory_structure(self, tmp_path): + """Test s větší strukturou složek""" + # Vytvoření více vnořených úrovní + for i in range(3): + level_dir = tmp_path / f"level{i}" + level_dir.mkdir() + for j in range(5): + (level_dir / f"file_{i}_{j}.txt").write_text(f"content {i} {j}") + + files = list_files(tmp_path) + # Měli bychom najít 3 * 5 = 15 souborů + assert len(files) == 15 + + def test_list_files_preserves_path_structure(self, temp_dir): + """Test že zachovává strukturu cest""" + files = list_files(temp_dir) + + # Najdeme soubor v subdir2 + file5 = [f for f in files if f.name == "file5.txt"][0] + + # Cesta by měla obsahovat obě složky + assert "subdir1" in str(file5) + assert "subdir2" in str(file5)