test_filename_validation.py 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566
  1. """Validator tests for FAT32/exFAT-safe print filenames (#1540)."""
  2. import pytest
  3. from backend.app.utils.filename import (
  4. INVALID_FILENAME_CHARS,
  5. InvalidFilenameError,
  6. validate_print_filename,
  7. )
  8. class TestValidatePrintFilename:
  9. @pytest.mark.parametrize(
  10. "name",
  11. [
  12. "model.3mf",
  13. "Bersaglio.gcode.3mf",
  14. "Plate 1.3mf",
  15. "プリント.3mf",
  16. "model_v2-final.3mf",
  17. "a.3mf",
  18. ],
  19. )
  20. def test_valid_names_accepted(self, name: str) -> None:
  21. validate_print_filename(name)
  22. @pytest.mark.parametrize("char", list(INVALID_FILENAME_CHARS))
  23. def test_each_invalid_char_rejected(self, char: str) -> None:
  24. with pytest.raises(InvalidFilenameError) as exc_info:
  25. validate_print_filename(f"L{char}R.3mf")
  26. assert exc_info.value.char == char
  27. def test_pipe_from_issue_1540(self) -> None:
  28. """The exact reproducer from the bug report."""
  29. with pytest.raises(InvalidFilenameError) as exc_info:
  30. validate_print_filename("L|R.3mf")
  31. assert exc_info.value.char == "|"
  32. @pytest.mark.parametrize("name", ["", " ", " "])
  33. def test_empty_rejected(self, name: str) -> None:
  34. with pytest.raises(InvalidFilenameError, match="empty"):
  35. validate_print_filename(name)
  36. @pytest.mark.parametrize("name", [".", ".."])
  37. def test_dot_names_rejected(self, name: str) -> None:
  38. with pytest.raises(InvalidFilenameError):
  39. validate_print_filename(name)
  40. def test_control_char_rejected(self) -> None:
  41. with pytest.raises(InvalidFilenameError, match="control"):
  42. validate_print_filename("file\x01.3mf")
  43. @pytest.mark.parametrize("name", ["file.3mf.", "file.3mf "])
  44. def test_trailing_space_or_dot_rejected(self, name: str) -> None:
  45. with pytest.raises(InvalidFilenameError, match="space or dot"):
  46. validate_print_filename(name)
  47. def test_too_long_rejected(self) -> None:
  48. with pytest.raises(InvalidFilenameError, match="bytes"):
  49. validate_print_filename("a" * 256)
  50. def test_unicode_byte_length_not_codepoint(self) -> None:
  51. """255 multi-byte codepoints exceeds 255 bytes — must reject."""
  52. # 'ä' is 2 bytes in UTF-8
  53. with pytest.raises(InvalidFilenameError, match="bytes"):
  54. validate_print_filename("ä" * 200)