test_camera_profiles.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. """Unit tests for camera profile registry.
  2. The registry decouples per-model camera tuning (probesize, analyzeduration,
  3. reconnect cadence) from the hard-coded constants that lived in
  4. ``camera.py`` until #1395 follow-up. Adding a new model's quirk should
  5. be a config edit, not a code change.
  6. """
  7. from dataclasses import FrozenInstanceError
  8. import pytest
  9. from backend.app.services.camera_profiles import (
  10. DEFAULT_PROFILE,
  11. CameraProfile,
  12. get_camera_profile,
  13. )
  14. class TestGetCameraProfile:
  15. def test_unknown_model_returns_default(self):
  16. """Models with no override fall through to DEFAULT_PROFILE so the
  17. camera path is never blocked on a missing entry."""
  18. assert get_camera_profile("UNKNOWN_MODEL") is DEFAULT_PROFILE
  19. assert get_camera_profile("Future_Bambu_Model_X42") is DEFAULT_PROFILE
  20. def test_none_model_returns_default(self):
  21. """`None` / empty model (very early in connect handshake) must not
  22. crash; the default profile is safe for any RTSP-capable printer."""
  23. assert get_camera_profile(None) is DEFAULT_PROFILE
  24. assert get_camera_profile("") is DEFAULT_PROFILE
  25. def test_default_profile_preserves_historical_fast_startup(self):
  26. """X1/H2 fast-startup tuning is the historical baseline. The first
  27. refactor must not regress it for the printers that already worked.
  28. """
  29. assert DEFAULT_PROFILE.probesize == 32
  30. assert DEFAULT_PROFILE.analyzeduration == 0
  31. assert DEFAULT_PROFILE.rtsp_reconnect_max == 30
  32. assert DEFAULT_PROFILE.rtsp_reconnect_delay == 0.2
  33. def test_p2s_has_relaxed_probe(self):
  34. """P2S firmware 01.02.00.00 needs more probe room — ffmpeg's own
  35. diagnostic says so. This is the first per-model override and the
  36. regression to guard."""
  37. profile = get_camera_profile("P2S")
  38. assert profile is not DEFAULT_PROFILE
  39. # Order of magnitude up from the default — enough to lock onto a
  40. # slow-keyframe stream without adding multi-second startup.
  41. assert profile.probesize >= 1_000_000
  42. assert profile.analyzeduration >= 500_000
  43. def test_p2s_internal_code_resolves_to_p2s_profile(self):
  44. """SSDP internal codes (e.g. `N7` for P2S) must resolve to the
  45. same profile as their display name. Otherwise printers freshly
  46. connected (before display-name lookup completes) would use the
  47. default profile and hit the same #1395 bug."""
  48. assert get_camera_profile("N7") is get_camera_profile("P2S")
  49. def test_lookup_is_case_insensitive(self):
  50. """Display-name capitalisation should not matter — callers may
  51. carry lowercase or mixed-case values straight from MQTT."""
  52. assert get_camera_profile("p2s") is get_camera_profile("P2S")
  53. assert get_camera_profile("P2s") is get_camera_profile("P2S")
  54. def test_known_rtsp_models_keep_default_unchanged(self):
  55. """X1, X1C, X1E, H2D, H2S, X2D — every other RTSP-capable model
  56. must use the default profile until proven otherwise. Anything
  57. else means we silently changed behaviour for a model the user
  58. hasn't reported a problem on."""
  59. for model in ("X1", "X1C", "X1E", "X2D", "H2C", "H2D", "H2D PRO", "H2S"):
  60. assert get_camera_profile(model) is DEFAULT_PROFILE, (
  61. f"{model} unexpectedly has a non-default profile — review "
  62. "whether the change is intentional before shipping."
  63. )
  64. class TestCameraProfileShape:
  65. def test_profile_is_frozen(self):
  66. """Profiles are immutable; mutating them at runtime would
  67. introduce action-at-a-distance for the camera generator."""
  68. with pytest.raises(FrozenInstanceError):
  69. DEFAULT_PROFILE.probesize = 999 # type: ignore[misc]
  70. def test_extra_ffmpeg_input_args_defaults_to_empty_tuple(self):
  71. """Profiles can declare extra `-flag value` pairs to splice into
  72. the ffmpeg input args without changing the dataclass shape.
  73. Default is empty so the historical command is unchanged."""
  74. p = CameraProfile()
  75. assert p.extra_ffmpeg_input_args == ()