test_slice_request_schema.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. """Tests for `SliceRequest` validator — covers both the legacy bare-int
  2. shape and the new source-aware shape, plus the backwards-compat
  3. normalisation that lets the route handler ignore the difference.
  4. """
  5. import pytest
  6. from pydantic import ValidationError
  7. from backend.app.schemas.slicer import PresetRef, SliceRequest
  8. class TestLegacyBareIntegerShape:
  9. """Existing clients (and stale browser tabs after upgrade) keep
  10. sending bare integer ids. They must continue working unchanged."""
  11. def test_bare_int_ids_normalise_to_local_preset_ref(self):
  12. req = SliceRequest(printer_preset_id=1, process_preset_id=2, filament_preset_id=3)
  13. assert req.printer_preset == PresetRef(source="local", id="1")
  14. assert req.process_preset == PresetRef(source="local", id="2")
  15. assert req.filament_preset == PresetRef(source="local", id="3")
  16. def test_legacy_ids_unchanged_in_payload(self):
  17. """The legacy fields stay populated — no behaviour change for
  18. clients that read them back from the model."""
  19. req = SliceRequest(printer_preset_id=10, process_preset_id=20, filament_preset_id=30)
  20. assert req.printer_preset_id == 10
  21. assert req.process_preset_id == 20
  22. assert req.filament_preset_id == 30
  23. class TestNewSourceAwareShape:
  24. """The new modal sends source-aware refs explicitly."""
  25. def test_cloud_refs_pass_through(self):
  26. req = SliceRequest(
  27. printer_preset=PresetRef(source="cloud", id="PFUprinter"),
  28. process_preset=PresetRef(source="cloud", id="PFUprocess"),
  29. filament_preset=PresetRef(source="cloud", id="PFUfilament"),
  30. )
  31. assert req.printer_preset.source == "cloud"
  32. assert req.printer_preset.id == "PFUprinter"
  33. def test_mixed_sources_per_slot(self):
  34. """A user may pick cloud for printer, local for process, standard
  35. for filament — the modal is per-slot."""
  36. req = SliceRequest(
  37. printer_preset=PresetRef(source="cloud", id="PFU123"),
  38. process_preset=PresetRef(source="local", id="42"),
  39. filament_preset=PresetRef(source="standard", id="Bambu PLA Basic"),
  40. )
  41. assert req.printer_preset.source == "cloud"
  42. assert req.process_preset.source == "local"
  43. assert req.filament_preset.source == "standard"
  44. class TestValidationErrors:
  45. def test_missing_printer_slot_raises(self):
  46. with pytest.raises(ValidationError) as exc:
  47. SliceRequest(process_preset_id=2, filament_preset_id=3)
  48. assert "printer" in str(exc.value)
  49. def test_invalid_source_rejected(self):
  50. with pytest.raises(ValidationError):
  51. SliceRequest(
  52. printer_preset={"source": "made_up", "id": "x"},
  53. process_preset_id=2,
  54. filament_preset_id=3,
  55. )
  56. class TestPriorityWhenBothSet:
  57. """If a client sends BOTH the legacy id AND the new ref for the same
  58. slot (unlikely in practice, but ambiguous), the new ref wins. Tests
  59. pin the resolution order so a future schema change can't silently
  60. flip it."""
  61. def test_explicit_ref_wins_over_legacy_id(self):
  62. req = SliceRequest(
  63. printer_preset_id=999, # would resolve to local:999
  64. printer_preset=PresetRef(source="cloud", id="PFU"),
  65. process_preset_id=2,
  66. filament_preset_id=3,
  67. )
  68. # Validator only fills the ref when it's None — the explicit cloud
  69. # ref stays untouched.
  70. assert req.printer_preset == PresetRef(source="cloud", id="PFU")
  71. class TestFilamentPresetsList:
  72. """Multi-color: the new array shape carries one filament profile per
  73. plate slot in plate order. Backwards-compat: legacy clients still
  74. submit a singular `filament_preset` and the validator promotes it into
  75. a one-element list so the route handler only deals with one shape."""
  76. def test_explicit_list_passes_through(self):
  77. refs = [
  78. PresetRef(source="cloud", id="A"),
  79. PresetRef(source="local", id="2"),
  80. PresetRef(source="standard", id="Bambu PLA Basic"),
  81. ]
  82. req = SliceRequest(
  83. printer_preset_id=1,
  84. process_preset_id=2,
  85. filament_preset_id=99, # explicit legacy id — should be ignored
  86. filament_presets=refs,
  87. )
  88. assert req.filament_presets == refs
  89. # Precedence pin: when caller sends both shapes, the array wins and
  90. # the singular gets backfilled from the array's first entry — NOT
  91. # from the legacy id 99. Documents the migration ordering for a
  92. # future change that might quietly mix them.
  93. assert req.filament_preset == refs[0]
  94. def test_empty_list_is_backfilled_from_singular(self):
  95. req = SliceRequest(printer_preset_id=1, process_preset_id=2, filament_preset_id=3)
  96. # Legacy single-color path: validator promotes the singular into a
  97. # one-element list so route handlers can iterate uniformly.
  98. assert req.filament_presets == [PresetRef(source="local", id="3")]
  99. def test_explicit_empty_list_with_singular_set_uses_singular(self):
  100. # User of the new schema can leave `filament_presets` as the empty
  101. # default and rely on the legacy `filament_preset_id` — same path
  102. # as `test_empty_list_is_backfilled_from_singular`.
  103. req = SliceRequest(
  104. printer_preset_id=1,
  105. process_preset_id=2,
  106. filament_preset=PresetRef(source="cloud", id="PFU"),
  107. filament_presets=[],
  108. )
  109. assert req.filament_presets == [PresetRef(source="cloud", id="PFU")]
  110. def test_list_preserves_order(self):
  111. refs = [
  112. PresetRef(source="cloud", id="slot1"),
  113. PresetRef(source="cloud", id="slot2"),
  114. PresetRef(source="cloud", id="slot3"),
  115. ]
  116. req = SliceRequest(
  117. printer_preset_id=1,
  118. process_preset_id=2,
  119. filament_preset_id=3,
  120. filament_presets=refs,
  121. )
  122. assert [r.id for r in req.filament_presets] == ["slot1", "slot2", "slot3"]