"""Per-model FTP profile registry (#1401). Mirrors ``test_camera_profiles.py`` in shape — the FTP profile module follows the same pattern. """ import ssl from backend.app.services.ftp_profiles import ( DEFAULT_PROFILE, FTPProfile, get_ftp_profile, ) def test_default_profile_does_not_cap_tls(): """Default profile keeps the historical Python-default TLS negotiation (typically TLS 1.3 on Python 3.13). Capping would be a silent regression for users who work fine today.""" assert DEFAULT_PROFILE.cap_tls_v1_2 is False def test_unknown_model_returns_default(): """Unknown / missing models fall back to DEFAULT_PROFILE so the FTP path is never blocked on a missing entry.""" assert get_ftp_profile(None) is DEFAULT_PROFILE assert get_ftp_profile("") is DEFAULT_PROFILE assert get_ftp_profile("Unknown Future Model") is DEFAULT_PROFILE def test_p2s_caps_tls_v1_2(): """P2S firmware 01.02.00.00 trips a vsFTPd + TLS 1.3 session-reuse bug on the data channel; the profile must cap to TLS 1.2 so session resumption is synchronous (#1401, reporter @iitazz).""" profile = get_ftp_profile("P2S") assert profile.cap_tls_v1_2 is True def test_p2s_internal_ssdp_code_resolves_to_p2s(): """SSDP internal code N7 → P2S profile. Camera profiles do the same thing — keeps callers free of the code↔display-name mapping.""" profile = get_ftp_profile("N7") assert profile.cap_tls_v1_2 is True def test_lookup_is_case_insensitive(): """Printer.model may carry mixed case; the lookup normalises.""" assert get_ftp_profile("p2s").cap_tls_v1_2 is True assert get_ftp_profile("P2s").cap_tls_v1_2 is True def test_non_capped_models_still_default(): """Spot-check: the models the user dogfoods today (X1C, H2D) stay on the default profile. Adding the P2S override must not accidentally flip these.""" assert get_ftp_profile("X1C").cap_tls_v1_2 is False assert get_ftp_profile("H2D").cap_tls_v1_2 is False assert get_ftp_profile("P1S").cap_tls_v1_2 is False assert get_ftp_profile("A1").cap_tls_v1_2 is False def test_profile_is_frozen(): """FTPProfile is a frozen dataclass — runtime mutation should raise. Same guarantee CameraProfile has.""" try: DEFAULT_PROFILE.cap_tls_v1_2 = True # type: ignore[misc] except Exception as e: assert "frozen" in str(e).lower() or "FrozenInstanceError" in type(e).__name__ return raise AssertionError("FTPProfile should be frozen but assignment succeeded") def test_cap_tls_v1_2_actually_applied_to_ssl_context(): """Pins the integration: when ``cap_tls_v1_2=True`` is passed to the FTPS subclass, the SSL context's ``maximum_version`` is set to TLSv1.2. Guards against a future refactor that drops the wiring between profile and context (the registry would still look correct, but the cap would silently stop applying).""" from backend.app.services.bambu_ftp import ImplicitFTP_TLS capped = ImplicitFTP_TLS(cap_tls_v1_2=True) assert capped.ssl_context.maximum_version == ssl.TLSVersion.TLSv1_2 uncapped = ImplicitFTP_TLS(cap_tls_v1_2=False) # MAXIMUM_SUPPORTED is the "no cap applied" sentinel for SSLContext. assert uncapped.ssl_context.maximum_version == ssl.TLSVersion.MAXIMUM_SUPPORTED def test_ftp_profile_dataclass_default_constructible(): """Sanity: FTPProfile() with no args yields the default profile (every field has a default).""" fresh = FTPProfile() assert fresh == DEFAULT_PROFILE