test_pathutils.py 3.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. # This file is part of Radicale - CalDAV and CardDAV server
  2. # Copyright © 2025 Tobias Brox <tobias@tobix.eu>
  3. #
  4. # This library is free software: you can redistribute it and/or modify
  5. # it under the terms of the GNU General Public License as published by
  6. # the Free Software Foundation, either version 3 of the License, or
  7. # (at your option) any later version.
  8. #
  9. # This library is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU General Public License
  15. # along with Radicale. If not, see <http://www.gnu.org/licenses/>.
  16. """
  17. Tests for pathutils module.
  18. """
  19. import gc
  20. import os
  21. import tempfile
  22. import pytest
  23. from radicale import pathutils
  24. class TestPathToFilesystem:
  25. """Tests for path_to_filesystem function."""
  26. @pytest.mark.filterwarnings("error::ResourceWarning")
  27. @pytest.mark.filterwarnings("error::pytest.PytestUnraisableExceptionWarning")
  28. def test_scandir_iterator_closed(self) -> None:
  29. """Verify that os.scandir iterator is properly closed.
  30. This test catches ResourceWarning: unclosed scandir iterator
  31. which occurs when os.scandir() is used without a context manager.
  32. See: https://github.com/Kozea/Radicale/issues/1972
  33. The ResourceWarning is emitted during garbage collection when an
  34. unclosed scandir iterator is finalized. We use pytest.mark.filterwarnings
  35. to convert both ResourceWarning and PytestUnraisableExceptionWarning
  36. to errors.
  37. """
  38. with tempfile.TemporaryDirectory() as tmpdir:
  39. # Create a subdirectory so path_to_filesystem has something
  40. # to scan (the scandir check is for case-insensitive filesystems)
  41. subdir = os.path.join(tmpdir, "testdir")
  42. os.makedirs(subdir)
  43. # Call path_to_filesystem - if scandir iterator is not closed,
  44. # a ResourceWarning will be emitted during garbage collection
  45. result = pathutils.path_to_filesystem(tmpdir, "testdir")
  46. assert result == subdir
  47. # Force garbage collection to trigger any ResourceWarning
  48. # from unclosed iterators
  49. gc.collect()
  50. def test_path_to_filesystem_basic(self) -> None:
  51. """Test basic path_to_filesystem functionality."""
  52. with tempfile.TemporaryDirectory() as tmpdir:
  53. # Test empty path
  54. result = pathutils.path_to_filesystem(tmpdir, "")
  55. assert result == tmpdir
  56. # Test single component
  57. subdir = os.path.join(tmpdir, "test")
  58. os.makedirs(subdir)
  59. result = pathutils.path_to_filesystem(tmpdir, "test")
  60. assert result == subdir
  61. # Test nested path
  62. nested = os.path.join(subdir, "nested")
  63. os.makedirs(nested)
  64. result = pathutils.path_to_filesystem(tmpdir, "test/nested")
  65. assert result == nested
  66. def test_unsafe_path_raises(self) -> None:
  67. """Test that unsafe path components raise UnsafePathError."""
  68. with tempfile.TemporaryDirectory() as tmpdir:
  69. # Hidden files (starting with .) are not safe
  70. with pytest.raises(pathutils.UnsafePathError):
  71. pathutils.path_to_filesystem(tmpdir, ".hidden")
  72. # Backup files (ending with ~) are not safe
  73. with pytest.raises(pathutils.UnsafePathError):
  74. pathutils.path_to_filesystem(tmpdir, "backup~")