test_slicer_filament_resolver.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. """Tests for ``resolve_slicer_filament`` (#1815).
  2. The defensive filter at the end of the resolver clears ``tray_info_idx``
  3. when its value isn't slicer-acceptable (literal material names + PFUS /
  4. PFCN cloud-preset prefixes that the printer's calibration table can't
  5. key on). Pre-#1815 it cleared ``setting_id`` alongside, which dropped
  6. the slicer's only handle on the user's actual custom preset and forced
  7. the caller into the generic-material fallback — Bambu Studio then
  8. displayed "Generic <Material>" for spools whose Bambu Cloud detail
  9. lookup didn't resolve a ``filament_id`` (cloud unauth on the on_ams_change
  10. replay path, transient cloud failure, or custom presets whose detail
  11. JSON omits ``filament_id``).
  12. Post-#1815 the filter preserves a setting_id that's still a valid
  13. slicer reference (PFUS / PFCN cloud user/shared preset, or GFS Bambu
  14. official preset) even when ``tray_info_idx`` is cleared.
  15. """
  16. from __future__ import annotations
  17. from unittest.mock import AsyncMock, MagicMock, patch
  18. import pytest
  19. from backend.app.services.slicer_filament_resolver import resolve_slicer_filament
  20. @pytest.mark.asyncio
  21. async def test_pfus_cloud_unavailable_preserves_setting_id():
  22. """Reporter scenario: PFUS cloud user preset, cloud lookup fails to
  23. return a filament_id. setting_id must survive so the slicer can
  24. still find the user's actual custom preset."""
  25. db = MagicMock()
  26. with patch(
  27. "backend.app.api.routes.cloud.build_authenticated_cloud",
  28. AsyncMock(return_value=None),
  29. ):
  30. tray_info_idx, setting_id, sub_brand = await resolve_slicer_filament(
  31. db=db,
  32. current_user=None,
  33. slicer_filament="PFUS990b6e19965353",
  34. slicer_filament_name="Jayo PETG HF",
  35. material="PETG",
  36. )
  37. assert tray_info_idx == ""
  38. assert setting_id == "PFUS990b6e19965353"
  39. assert sub_brand is None
  40. @pytest.mark.asyncio
  41. async def test_pfcn_cloud_unavailable_preserves_setting_id():
  42. """PFCN partner/shared cloud preset (e.g. Polymaker H2D variants,
  43. #1648) shares the same shape problem as PFUS."""
  44. db = MagicMock()
  45. with patch(
  46. "backend.app.api.routes.cloud.build_authenticated_cloud",
  47. AsyncMock(return_value=None),
  48. ):
  49. tray_info_idx, setting_id, sub_brand = await resolve_slicer_filament(
  50. db=db,
  51. current_user=None,
  52. slicer_filament="PFCN1234567890",
  53. slicer_filament_name="Polymaker PolyTerra PLA",
  54. material="PLA",
  55. )
  56. assert tray_info_idx == ""
  57. assert setting_id == "PFCN1234567890"
  58. assert sub_brand is None
  59. @pytest.mark.asyncio
  60. async def test_pfus_cloud_resolves_filament_id_regression_guard():
  61. """When cloud auth works and returns a filament_id, the resolver
  62. keeps its existing behaviour: tray_info_idx = real filament_id,
  63. setting_id = original PFUS reference."""
  64. db = MagicMock()
  65. cloud_mock = MagicMock()
  66. cloud_mock.is_authenticated = True
  67. cloud_mock.get_setting_detail = AsyncMock(return_value={"filament_id": "P285e239", "name": "Jayo PETG HF @P1S"})
  68. cloud_mock.close = AsyncMock()
  69. with patch(
  70. "backend.app.api.routes.cloud.build_authenticated_cloud",
  71. AsyncMock(return_value=cloud_mock),
  72. ):
  73. tray_info_idx, setting_id, sub_brand = await resolve_slicer_filament(
  74. db=db,
  75. current_user=MagicMock(),
  76. slicer_filament="PFUS990b6e19965353",
  77. slicer_filament_name="Jayo PETG HF",
  78. material="PETG",
  79. )
  80. assert tray_info_idx == "P285e239"
  81. assert setting_id == "PFUS990b6e19965353"
  82. assert sub_brand == "Jayo PETG HF"
  83. @pytest.mark.asyncio
  84. async def test_gfs_cloud_unavailable_resolves_via_normalize():
  85. """GFS Bambu official preset + cloud unavailable: normalize strips
  86. the 'S' to give a real filament_id ('GFG02'), so tray_info_idx is
  87. valid and the defensive filter doesn't trigger. setting_id stays as
  88. the original GFS reference. Regression guard for the cloud-down
  89. Bambu-official path."""
  90. db = MagicMock()
  91. with patch(
  92. "backend.app.api.routes.cloud.build_authenticated_cloud",
  93. AsyncMock(return_value=None),
  94. ):
  95. tray_info_idx, setting_id, sub_brand = await resolve_slicer_filament(
  96. db=db,
  97. current_user=None,
  98. slicer_filament="GFSG02",
  99. slicer_filament_name=None,
  100. material="PETG",
  101. )
  102. assert tray_info_idx == "GFG02"
  103. assert setting_id == "GFSG02"
  104. assert sub_brand is None
  105. @pytest.mark.asyncio
  106. async def test_literal_material_name_clears_both():
  107. """slicer_filament='PETG' (free-text material leak from legacy
  108. spools): both tray_info_idx and setting_id must be cleared so the
  109. caller's generic-material fallback rescues the slot. Regression
  110. guard that the PFUS preservation doesn't accidentally preserve
  111. literal material names."""
  112. db = MagicMock()
  113. with patch(
  114. "backend.app.api.routes.cloud.build_authenticated_cloud",
  115. AsyncMock(return_value=None),
  116. ):
  117. tray_info_idx, setting_id, sub_brand = await resolve_slicer_filament(
  118. db=db,
  119. current_user=None,
  120. slicer_filament="PETG",
  121. slicer_filament_name=None,
  122. material="PETG",
  123. )
  124. assert tray_info_idx == ""
  125. assert setting_id == ""
  126. assert sub_brand is None