test_ftp_profiles.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. """Per-model FTP profile registry (#1401).
  2. Mirrors ``test_camera_profiles.py`` in shape — the FTP profile module
  3. follows the same pattern.
  4. """
  5. import ssl
  6. from backend.app.services.ftp_profiles import (
  7. DEFAULT_PROFILE,
  8. FTPProfile,
  9. get_ftp_profile,
  10. )
  11. def test_default_profile_does_not_cap_tls():
  12. """Default profile keeps the historical Python-default TLS negotiation
  13. (typically TLS 1.3 on Python 3.13). Capping would be a silent
  14. regression for users who work fine today."""
  15. assert DEFAULT_PROFILE.cap_tls_v1_2 is False
  16. def test_unknown_model_returns_default():
  17. """Unknown / missing models fall back to DEFAULT_PROFILE so the FTP
  18. path is never blocked on a missing entry."""
  19. assert get_ftp_profile(None) is DEFAULT_PROFILE
  20. assert get_ftp_profile("") is DEFAULT_PROFILE
  21. assert get_ftp_profile("Unknown Future Model") is DEFAULT_PROFILE
  22. def test_p2s_caps_tls_v1_2():
  23. """P2S firmware 01.02.00.00 trips a vsFTPd + TLS 1.3 session-reuse
  24. bug on the data channel; the profile must cap to TLS 1.2 so session
  25. resumption is synchronous (#1401, reporter @iitazz)."""
  26. profile = get_ftp_profile("P2S")
  27. assert profile.cap_tls_v1_2 is True
  28. def test_p2s_internal_ssdp_code_resolves_to_p2s():
  29. """SSDP internal code N7 → P2S profile. Camera profiles do the same
  30. thing — keeps callers free of the code↔display-name mapping."""
  31. profile = get_ftp_profile("N7")
  32. assert profile.cap_tls_v1_2 is True
  33. def test_x2d_caps_tls_v1_2():
  34. """X2D firmware 01.01.00.00 fails implicit-FTPS handshake on port
  35. 990 with WRONG_VERSION_NUMBER against Python 3.13's TLS-1.3 default
  36. (#1638, reporter @vasmarfas). The profile caps to TLS 1.2 by
  37. analogy with P2S."""
  38. profile = get_ftp_profile("X2D")
  39. assert profile.cap_tls_v1_2 is True
  40. def test_x2d_internal_ssdp_code_resolves_to_x2d():
  41. """SSDP internal code N6 → X2D profile."""
  42. profile = get_ftp_profile("N6")
  43. assert profile.cap_tls_v1_2 is True
  44. def test_h2c_caps_tls_v1_2():
  45. """H2C firmware 01.02.00.00 — same H2 generation / firmware line as
  46. P2S — intermittently fails the FTPS 3MF download and falls through to
  47. the no-3MF fallback archive, so nothing gets deducted (#2582, reporter
  48. @gyrene2083). The profile caps to TLS 1.2 by analogy with P2S."""
  49. profile = get_ftp_profile("H2C")
  50. assert profile.cap_tls_v1_2 is True
  51. def test_h2c_internal_ssdp_codes_resolve_to_h2c():
  52. """SSDP internal codes O1C and O1C2 (dual-nozzle variant) → H2C
  53. profile, so the cap applies however the model string arrives."""
  54. assert get_ftp_profile("O1C").cap_tls_v1_2 is True
  55. assert get_ftp_profile("O1C2").cap_tls_v1_2 is True
  56. def test_lookup_is_case_insensitive():
  57. """Printer.model may carry mixed case; the lookup normalises."""
  58. assert get_ftp_profile("p2s").cap_tls_v1_2 is True
  59. assert get_ftp_profile("P2s").cap_tls_v1_2 is True
  60. assert get_ftp_profile("x2d").cap_tls_v1_2 is True
  61. def test_non_capped_models_still_default():
  62. """Spot-check: the models the user dogfoods today (X1C, H2D) stay on
  63. the default profile. Adding the P2S override must not accidentally
  64. flip these."""
  65. assert get_ftp_profile("X1C").cap_tls_v1_2 is False
  66. assert get_ftp_profile("H2D").cap_tls_v1_2 is False
  67. assert get_ftp_profile("P1S").cap_tls_v1_2 is False
  68. assert get_ftp_profile("A1").cap_tls_v1_2 is False
  69. def test_profile_is_frozen():
  70. """FTPProfile is a frozen dataclass — runtime mutation should raise.
  71. Same guarantee CameraProfile has."""
  72. try:
  73. DEFAULT_PROFILE.cap_tls_v1_2 = True # type: ignore[misc]
  74. except Exception as e:
  75. assert "frozen" in str(e).lower() or "FrozenInstanceError" in type(e).__name__
  76. return
  77. raise AssertionError("FTPProfile should be frozen but assignment succeeded")
  78. def test_cap_tls_v1_2_actually_applied_to_ssl_context():
  79. """Pins the integration: when ``cap_tls_v1_2=True`` is passed to the
  80. FTPS subclass, the SSL context's ``maximum_version`` is set to
  81. TLSv1.2. Guards against a future refactor that drops the wiring
  82. between profile and context (the registry would still look
  83. correct, but the cap would silently stop applying)."""
  84. from backend.app.services.bambu_ftp import ImplicitFTP_TLS
  85. capped = ImplicitFTP_TLS(cap_tls_v1_2=True)
  86. assert capped.ssl_context.maximum_version == ssl.TLSVersion.TLSv1_2
  87. uncapped = ImplicitFTP_TLS(cap_tls_v1_2=False)
  88. # MAXIMUM_SUPPORTED is the "no cap applied" sentinel for SSLContext.
  89. assert uncapped.ssl_context.maximum_version == ssl.TLSVersion.MAXIMUM_SUPPORTED
  90. def test_ftp_profile_dataclass_default_constructible():
  91. """Sanity: FTPProfile() with no args yields the default profile
  92. (every field has a default)."""
  93. fresh = FTPProfile()
  94. assert fresh == DEFAULT_PROFILE