test_assign_uses_model_preset_override.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. """The per-model override has to reach the slot, not just the database.
  2. ``apply_spool_to_slot_via_mqtt`` is where a spool becomes an AMS slot
  3. configuration, and the preset it resolves is what the printer (and the slicer
  4. reading the slot back) ends up with. These drive that function and assert on
  5. what it handed to ``resolve_slicer_filament``, which is the last point the
  6. preset is still a stored reference rather than a printer-side id.
  7. The regression they guard is the whole point of the feature: a spool whose
  8. single ``slicer_filament`` is an ``@BBL X1C`` preset, assigned on an H2C,
  9. previously configured that H2C slot with the X1C preset.
  10. """
  11. from __future__ import annotations
  12. from unittest.mock import AsyncMock, MagicMock, patch
  13. import pytest
  14. from sqlalchemy import select
  15. from sqlalchemy.orm import selectinload
  16. from backend.app.models.spool import Spool
  17. from backend.app.models.spool_filament_preset import SpoolFilamentPreset
  18. pytestmark = pytest.mark.asyncio
  19. SPOOL_DEFAULT = "GFSA00"
  20. SPOOL_DEFAULT_NAME = "Bambu PLA Basic @BBL X1C"
  21. class _Nozzle:
  22. def __init__(self, diameter: str):
  23. self.nozzle_diameter = diameter
  24. self.nozzle_type = "HH01"
  25. class _State:
  26. """Just enough live printer state for the assign path."""
  27. def __init__(self, diameter: str = "0.4"):
  28. self.nozzles = [_Nozzle(diameter), _Nozzle(diameter)]
  29. self.ams_extruder_map = {}
  30. self.kprofiles = []
  31. self.raw_data = {}
  32. async def _spool(db_session) -> Spool:
  33. spool = Spool(
  34. brand="Bambu",
  35. material="PLA",
  36. color_name="Charcoal",
  37. slicer_filament=SPOOL_DEFAULT,
  38. slicer_filament_name=SPOOL_DEFAULT_NAME,
  39. )
  40. db_session.add(spool)
  41. await db_session.commit()
  42. # Loaded the way every production caller loads it: k_profiles is a lazy
  43. # relationship the assign path walks, and an async session cannot resolve
  44. # it mid-call.
  45. result = await db_session.execute(select(Spool).options(selectinload(Spool.k_profiles)).where(Spool.id == spool.id))
  46. return result.scalar_one()
  47. async def _assign(db_session, spool, printer, *, model: str, diameter: str = "0.4"):
  48. """Run the assign path far enough to capture the resolved preset.
  49. Returns the kwargs ``resolve_slicer_filament`` was called with. Everything
  50. downstream of it is stubbed: this asserts which preset was chosen, not how
  51. the MQTT payload is built (that is covered elsewhere).
  52. """
  53. from backend.app.api.routes import inventory as inventory_module
  54. resolver = AsyncMock(return_value=("GFL99", "GFSL99", "", ""))
  55. manager = MagicMock()
  56. manager.get_client = MagicMock(return_value=MagicMock())
  57. manager.get_status = MagicMock(return_value=_State(diameter))
  58. manager.get_model = MagicMock(return_value=model)
  59. with (
  60. patch.object(inventory_module, "resolve_slicer_filament", resolver),
  61. patch("backend.app.services.printer_manager.printer_manager", manager),
  62. ):
  63. await inventory_module.apply_spool_to_slot_via_mqtt(
  64. db=db_session,
  65. current_user=None,
  66. spool=spool,
  67. printer_id=printer.id,
  68. ams_id=0,
  69. tray_id=0,
  70. )
  71. assert resolver.await_count == 1, "the assign path did not reach the preset resolver"
  72. return resolver.await_args.kwargs
  73. class TestWithoutAnOverride:
  74. async def test_the_spools_own_preset_is_used(self, db_session, printer_factory):
  75. """Every spool in every existing install. Behaviour must be unchanged."""
  76. printer = await printer_factory(model="H2C")
  77. spool = await _spool(db_session)
  78. kwargs = await _assign(db_session, spool, printer, model="H2C")
  79. assert kwargs["slicer_filament"] == SPOOL_DEFAULT
  80. assert kwargs["slicer_filament_name"] == SPOOL_DEFAULT_NAME
  81. class TestWithAModelOverride:
  82. async def test_the_override_replaces_the_spools_preset(self, db_session, printer_factory):
  83. printer = await printer_factory(model="H2C")
  84. spool = await _spool(db_session)
  85. db_session.add(
  86. SpoolFilamentPreset(
  87. spool_id=spool.id,
  88. printer_model="H2C",
  89. nozzle_diameter="",
  90. slicer_filament="GFSA09",
  91. slicer_filament_name="Bambu PLA Basic @BBL H2C",
  92. )
  93. )
  94. await db_session.commit()
  95. kwargs = await _assign(db_session, spool, printer, model="H2C")
  96. assert kwargs["slicer_filament"] == "GFSA09"
  97. assert kwargs["slicer_filament_name"] == "Bambu PLA Basic @BBL H2C"
  98. async def test_a_different_model_still_gets_the_spools_preset(self, db_session, printer_factory):
  99. """An override for the H2C must not follow the spool onto the X1C."""
  100. printer = await printer_factory(model="X1C")
  101. spool = await _spool(db_session)
  102. db_session.add(
  103. SpoolFilamentPreset(
  104. spool_id=spool.id,
  105. printer_model="H2C",
  106. nozzle_diameter="",
  107. slicer_filament="GFSA09",
  108. slicer_filament_name="Bambu PLA Basic @BBL H2C",
  109. )
  110. )
  111. await db_session.commit()
  112. kwargs = await _assign(db_session, spool, printer, model="X1C")
  113. assert kwargs["slicer_filament"] == SPOOL_DEFAULT
  114. class TestPerDiameterOverride:
  115. async def test_the_slots_nozzle_diameter_selects_the_preset(self, db_session, printer_factory):
  116. """The diameter comes from live printer state, so the same spool on
  117. the same model resolves differently once a 0.2 nozzle is fitted."""
  118. printer = await printer_factory(model="A1 mini")
  119. spool = await _spool(db_session)
  120. db_session.add_all(
  121. [
  122. SpoolFilamentPreset(
  123. spool_id=spool.id,
  124. printer_model="A1 mini",
  125. nozzle_diameter="",
  126. slicer_filament="GFSA20",
  127. slicer_filament_name="Bambu PLA Basic @BBL A1M",
  128. ),
  129. SpoolFilamentPreset(
  130. spool_id=spool.id,
  131. printer_model="A1 mini",
  132. nozzle_diameter="0.2",
  133. slicer_filament="GFSA21",
  134. slicer_filament_name="Bambu PLA Basic @BBL A1M 0.2 nozzle",
  135. ),
  136. ]
  137. )
  138. await db_session.commit()
  139. on_04 = await _assign(db_session, spool, printer, model="A1 mini", diameter="0.4")
  140. on_02 = await _assign(db_session, spool, printer, model="A1 mini", diameter="0.2")
  141. assert on_04["slicer_filament"] == "GFSA20"
  142. assert on_02["slicer_filament"] == "GFSA21"