test_spoolman_tray_preset_3433.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. """The AMS sync keeps the tray's filament preset on the Spoolman spool (#3433).
  2. An internal RFID spool stores the tray's ``tray_info_idx`` as its
  3. ``slicer_filament``. A Spoolman spool the sync created from the same tray got
  4. only its tag, so assigning it to the external slot later -- no reader there --
  5. wrote the generic preset for its material: a Bambu PLA Basic became Generic PLA.
  6. """
  7. import json
  8. from unittest.mock import AsyncMock, patch
  9. import pytest
  10. from backend.app.services.spoolman import AMSTray, SpoolmanClient, SpoolmanUnavailableError
  11. UUID = "A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4"
  12. def _tray(*, preset="GFA00", uuid=UUID, tag="", sub_brands="PLA Basic"):
  13. return AMSTray(
  14. ams_id=0,
  15. tray_id=0,
  16. tray_type="PLA",
  17. tray_sub_brands=sub_brands,
  18. tray_color="000000FF",
  19. remain=100,
  20. tag_uid=tag,
  21. tray_uuid=uuid,
  22. tray_info_idx=preset,
  23. tray_weight=1000,
  24. )
  25. @pytest.fixture
  26. def client():
  27. return SpoolmanClient("http://localhost:7912")
  28. async def _create(client, tray):
  29. """Run the sync's auto-create for an unknown tag; returns (create_spool mock, profile-default fill mock)."""
  30. with (
  31. patch.object(client, "find_spool_by_tag", AsyncMock(return_value=None)),
  32. patch.object(client, "_find_or_create_filament", AsyncMock(return_value={"id": 5})),
  33. patch.object(client, "find_or_create_filament", AsyncMock(return_value=5)),
  34. patch.object(client, "create_spool", AsyncMock(return_value={"id": 9, "extra": {}})) as create,
  35. patch.object(client, "add_native_tags", AsyncMock()),
  36. patch("backend.app.services.spoolman.resolve_bambu_color", AsyncMock(return_value=None)),
  37. patch("backend.app.services.filament_profile_default.fill_auto_added_spoolman_spool", AsyncMock()) as fill,
  38. ):
  39. await client.sync_ams_tray(tray, "X2D", None, auto_add_unknown_rfid=True)
  40. return create, fill
  41. class TestAutoCreate:
  42. @pytest.mark.asyncio
  43. async def test_a_bambu_tray_s_preset_is_stored_with_its_name(self, client):
  44. create, fill = await _create(client, _tray())
  45. extra = create.call_args.kwargs["extra"]
  46. assert extra["bambu_slicer_filament"] == json.dumps("GFA00")
  47. assert extra["bambu_slicer_filament_name"] == json.dumps("Bambu PLA Basic")
  48. # The tag's preset is the spool's, so a profile default's is not applied.
  49. assert fill.call_args.kwargs["include_slicer"] is False
  50. @pytest.mark.asyncio
  51. async def test_an_unlisted_preset_is_named_by_the_sub_brand(self, client):
  52. create, _ = await _create(client, _tray(preset="GFZ77", sub_brands="PLA Something"))
  53. extra = create.call_args.kwargs["extra"]
  54. assert extra["bambu_slicer_filament"] == json.dumps("GFZ77")
  55. assert extra["bambu_slicer_filament_name"] == json.dumps("PLA Something")
  56. @pytest.mark.asyncio
  57. async def test_a_third_party_tag_gets_no_preset(self, client):
  58. create, fill = await _create(client, _tray(preset="", uuid="0" * 32, tag="0A1B2C3D4E5F6071"))
  59. extra = create.call_args.kwargs["extra"]
  60. assert "bambu_slicer_filament" not in extra
  61. assert fill.call_args.kwargs["include_slicer"] is True
  62. class TestExistingSpool:
  63. async def _sync(self, client, spool, *, merge=None):
  64. merge = merge or AsyncMock(return_value={})
  65. with (
  66. patch.object(client, "find_spool_by_tag", AsyncMock(return_value=spool)),
  67. patch.object(client, "add_native_tags", AsyncMock()),
  68. patch.object(client, "update_spool", AsyncMock(return_value={"id": spool["id"]})) as update,
  69. patch.object(client, "merge_spool_extra", merge),
  70. ):
  71. result = await client.sync_ams_tray(_tray(), "X2D", None)
  72. return merge, update, result
  73. @pytest.mark.asyncio
  74. async def test_a_spool_without_a_preset_gets_the_tray_s_once(self, client):
  75. merge, _, _ = await self._sync(client, {"id": 6, "extra": {"tag": json.dumps(UUID)}})
  76. merge.assert_awaited_once_with(
  77. 6,
  78. {"bambu_slicer_filament": json.dumps("GFA00"), "bambu_slicer_filament_name": json.dumps("Bambu PLA Basic")},
  79. )
  80. @pytest.mark.asyncio
  81. async def test_an_empty_preset_counts_as_none(self, client):
  82. merge, _, _ = await self._sync(client, {"id": 6, "extra": {"bambu_slicer_filament": json.dumps("")}})
  83. merge.assert_awaited_once()
  84. @pytest.mark.asyncio
  85. async def test_a_preset_set_by_hand_stays(self, client):
  86. merge, _, _ = await self._sync(client, {"id": 6, "extra": {"bambu_slicer_filament": json.dumps("GFA01")}})
  87. merge.assert_not_awaited()
  88. @pytest.mark.asyncio
  89. async def test_spoolman_refusing_the_write_does_not_stop_the_sync(self, client):
  90. merge = AsyncMock(side_effect=SpoolmanUnavailableError("down"))
  91. _, update, result = await self._sync(client, {"id": 6, "extra": {}}, merge=merge)
  92. update.assert_awaited_once()
  93. assert result == {"id": 6}