test_http_utils.py 2.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071
  1. """Unit tests for backend.app.utils.http."""
  2. from urllib.parse import unquote
  3. import pytest
  4. from backend.app.utils.http import build_content_disposition
  5. @pytest.mark.parametrize(
  6. ("filename", "expected_ascii_fallback"),
  7. [
  8. ("hello.gcode.3mf", "hello.gcode.3mf"),
  9. ("龙泡泡石墩子_p2s_ok.gcode.3mf", "p2s_ok.gcode.3mf"),
  10. ("こんにちは.gcode.3mf", "gcode.3mf"),
  11. ("résumé.gcode.3mf", "rsum.gcode.3mf"),
  12. ("مرحبا.gcode.3mf", "gcode.3mf"),
  13. ("文件.3mf", "3mf"),
  14. ("模型.gcode.3mf", "gcode.3mf"),
  15. ("project_2026-05-08.zip", "project_2026-05-08.zip"),
  16. ("___.zip", "zip"),
  17. ("", "download"),
  18. ],
  19. )
  20. def test_ascii_fallback_strips_non_ascii(filename: str, expected_ascii_fallback: str) -> None:
  21. header = build_content_disposition(filename)
  22. assert f'filename="{expected_ascii_fallback}"' in header
  23. @pytest.mark.parametrize(
  24. "filename",
  25. [
  26. "龙泡泡石墩子_p2s_ok.gcode.3mf",
  27. "こんにちは.gcode.3mf",
  28. "résumé.gcode.3mf",
  29. "مرحبا.gcode.3mf",
  30. "文件.3mf",
  31. "hello world (final).pdf",
  32. "你好/世界.pdf",
  33. ],
  34. )
  35. def test_filename_star_round_trips_to_original(filename: str) -> None:
  36. header = build_content_disposition(filename)
  37. assert "filename*=UTF-8''" in header
  38. encoded = header.split("filename*=UTF-8''", 1)[1]
  39. assert unquote(encoded) == filename
  40. def test_header_is_latin1_encodable() -> None:
  41. """Starlette/uvicorn encodes response headers as latin-1 — the helper's
  42. output MUST round-trip through latin-1 without raising."""
  43. for filename in [
  44. "龙泡泡石墩子_p2s_ok.gcode.3mf",
  45. "こんにちは.gcode.3mf",
  46. "résumé.gcode.3mf",
  47. "مرحبا.gcode.3mf",
  48. '"quoted"name.zip',
  49. "back\\slash.zip",
  50. ]:
  51. header = build_content_disposition(filename)
  52. header.encode("latin-1")
  53. def test_disposition_param_is_respected() -> None:
  54. assert build_content_disposition("foo.pdf", disposition="inline").startswith("inline; ")
  55. assert build_content_disposition("foo.pdf").startswith("attachment; ")
  56. def test_quotes_and_backslashes_stripped_from_ascii_fallback() -> None:
  57. header = build_content_disposition('a"b\\c.pdf')
  58. assert 'filename="abc.pdf"' in header