test_bulk_spool_create.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. """Unit tests for bulk spool creation.
  2. Tests:
  3. - SpoolBulkCreate schema validation (quantity bounds)
  4. - Bulk create endpoint creates the requested number of spools
  5. - Bulk create with quantity=1 (single spool)
  6. - Bulk create returns spools with k_profiles loaded
  7. """
  8. from unittest.mock import AsyncMock, MagicMock
  9. import pytest
  10. from pydantic import ValidationError
  11. from backend.app.schemas.spool import SpoolBulkCreate, SpoolCreate
  12. # ── Schema Validation ──────────────────────────────────────────────────────
  13. class TestSpoolBulkCreateSchema:
  14. """Tests for the SpoolBulkCreate Pydantic model."""
  15. def test_default_quantity_is_1(self):
  16. data = SpoolBulkCreate(spool=SpoolCreate(material="PLA"))
  17. assert data.quantity == 1
  18. def test_quantity_within_range(self):
  19. data = SpoolBulkCreate(spool=SpoolCreate(material="PLA"), quantity=50)
  20. assert data.quantity == 50
  21. def test_quantity_max_100(self):
  22. data = SpoolBulkCreate(spool=SpoolCreate(material="PLA"), quantity=100)
  23. assert data.quantity == 100
  24. def test_quantity_zero_rejected(self):
  25. with pytest.raises(ValidationError, match="greater than or equal to 1"):
  26. SpoolBulkCreate(spool=SpoolCreate(material="PLA"), quantity=0)
  27. def test_quantity_negative_rejected(self):
  28. with pytest.raises(ValidationError, match="greater than or equal to 1"):
  29. SpoolBulkCreate(spool=SpoolCreate(material="PLA"), quantity=-1)
  30. def test_quantity_over_100_rejected(self):
  31. with pytest.raises(ValidationError, match="less than or equal to 100"):
  32. SpoolBulkCreate(spool=SpoolCreate(material="PLA"), quantity=101)
  33. def test_spool_fields_preserved(self):
  34. data = SpoolBulkCreate(
  35. spool=SpoolCreate(
  36. material="PETG",
  37. brand="Polymaker",
  38. subtype="Basic",
  39. color_name="Red",
  40. rgba="FF0000FF",
  41. label_weight=750,
  42. note="Test batch",
  43. ),
  44. quantity=5,
  45. )
  46. assert data.spool.material == "PETG"
  47. assert data.spool.brand == "Polymaker"
  48. assert data.spool.label_weight == 750
  49. assert data.spool.note == "Test batch"
  50. assert data.quantity == 5
  51. def test_spool_without_slicer_filament_is_stock(self):
  52. """A spool without slicer_filament is a 'stock' spool (computed, not stored)."""
  53. data = SpoolBulkCreate(
  54. spool=SpoolCreate(material="PLA", label_weight=1000),
  55. quantity=3,
  56. )
  57. assert data.spool.slicer_filament is None
  58. def test_spool_with_slicer_filament_is_configured(self):
  59. data = SpoolBulkCreate(
  60. spool=SpoolCreate(material="PLA", slicer_filament="GFL99"),
  61. quantity=2,
  62. )
  63. assert data.spool.slicer_filament == "GFL99"
  64. def test_material_required(self):
  65. with pytest.raises(ValidationError):
  66. SpoolBulkCreate(spool=SpoolCreate(material=""), quantity=1)
  67. # ── Endpoint Logic ─────────────────────────────────────────────────────────
  68. def _make_mock_spool(spool_id):
  69. """Create a mock Spool ORM object."""
  70. spool = MagicMock()
  71. spool.id = spool_id
  72. spool.material = "PLA"
  73. spool.label_weight = 1000
  74. spool.k_profiles = []
  75. return spool
  76. class TestBulkCreateEndpoint:
  77. """Tests for the bulk_create_spools endpoint logic."""
  78. @pytest.mark.asyncio
  79. async def test_creates_requested_number_of_spools(self):
  80. """Verify N spools are created and added to the session."""
  81. from backend.app.api.routes.inventory import bulk_create_spools
  82. data = SpoolBulkCreate(
  83. spool=SpoolCreate(material="PLA", brand="Test", label_weight=1000),
  84. quantity=3,
  85. )
  86. db = AsyncMock()
  87. added_objects = []
  88. db.add = lambda obj: added_objects.append(obj)
  89. # Mock the re-fetch query
  90. mock_result = MagicMock()
  91. mock_spools = [_make_mock_spool(i + 1) for i in range(3)]
  92. mock_result.scalars.return_value.all.return_value = mock_spools
  93. db.execute = AsyncMock(return_value=mock_result)
  94. result = await bulk_create_spools(data=data, db=db, _=None)
  95. assert len(result) == 3
  96. assert len(added_objects) == 3
  97. db.commit.assert_awaited_once()
  98. @pytest.mark.asyncio
  99. async def test_single_quantity_creates_one_spool(self):
  100. """Bulk create with quantity=1 should create exactly one spool."""
  101. from backend.app.api.routes.inventory import bulk_create_spools
  102. data = SpoolBulkCreate(
  103. spool=SpoolCreate(material="PETG"),
  104. quantity=1,
  105. )
  106. db = AsyncMock()
  107. added_objects = []
  108. db.add = lambda obj: added_objects.append(obj)
  109. mock_result = MagicMock()
  110. mock_spools = [_make_mock_spool(1)]
  111. mock_result.scalars.return_value.all.return_value = mock_spools
  112. db.execute = AsyncMock(return_value=mock_result)
  113. result = await bulk_create_spools(data=data, db=db, _=None)
  114. assert len(result) == 1
  115. assert len(added_objects) == 1
  116. @pytest.mark.asyncio
  117. async def test_all_spools_have_same_fields(self):
  118. """All created spools should have identical field values."""
  119. from backend.app.api.routes.inventory import bulk_create_spools
  120. data = SpoolBulkCreate(
  121. spool=SpoolCreate(
  122. material="ABS",
  123. brand="Bambu Lab",
  124. color_name="Black",
  125. rgba="000000FF",
  126. label_weight=750,
  127. ),
  128. quantity=3,
  129. )
  130. db = AsyncMock()
  131. added_objects = []
  132. db.add = lambda obj: added_objects.append(obj)
  133. mock_result = MagicMock()
  134. mock_spools = [_make_mock_spool(i + 1) for i in range(3)]
  135. mock_result.scalars.return_value.all.return_value = mock_spools
  136. db.execute = AsyncMock(return_value=mock_result)
  137. await bulk_create_spools(data=data, db=db, _=None)
  138. # All added Spool objects should have the same material/brand/color
  139. for spool_obj in added_objects:
  140. assert spool_obj.material == "ABS"
  141. assert spool_obj.brand == "Bambu Lab"
  142. assert spool_obj.color_name == "Black"
  143. assert spool_obj.label_weight == 750