Tests added

This commit is contained in:
2025-12-23 10:41:53 +01:00
parent 1b3181b559
commit 9f107a2950
16 changed files with 1607 additions and 56 deletions

3
.gitignore vendored
View File

@@ -1,4 +1,5 @@
.venv
__pycache__
.pytest_cache
build
build
.claude

View File

@@ -1,2 +0,0 @@
install required modules to enviroment:
pip install -r requirements.txt

View File

@@ -1,12 +0,0 @@
{
"ignore_patterns": [
"*.png",
"*.jpg",
"*.mp3",
"*/M/*",
"*/L/*",
"*/Ostatní/*",
"*.hidden*"
],
"last_folder": "/media/veracrypt3"
}

197
poetry.lock generated Normal file
View File

@@ -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"

22
pyproject.toml Normal file
View File

@@ -0,0 +1,22 @@
[tool.poetry]
name = "tagger"
version = "1.0.3"
description = "Universal file tagging utility"
authors = ["Jan Doubravský <jan.doubravsky@gmail.com>"]
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"

View File

@@ -1 +0,0 @@
pillow

1
tests/__init__.py Normal file
View File

@@ -0,0 +1 @@
# Tests package

28
tests/conftest.py Normal file
View File

@@ -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

252
tests/test_config.py Normal file
View File

@@ -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

265
tests/test_file.py Normal file
View File

@@ -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"

283
tests/test_file_manager.py Normal file
View File

@@ -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

View File

@@ -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)

75
tests/test_media_utils.py Normal file
View File

@@ -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)

106
tests/test_tag.py Normal file
View File

@@ -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"

198
tests/test_tag_manager.py Normal file
View File

@@ -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

178
tests/test_utils.py Normal file
View File

@@ -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)