test_library_add_to_queue_plate_filament.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. """A multi-plate file queued without a plate is matched on the plate that prints (PL-C6).
  2. Queued without a plate, the file prints its default plate. The scheduler was
  3. nevertheless told every plate's filament, so an X1C with only plate 1's PLA
  4. loaded never qualified because plate 2 needs PETG-CF; and with the type list
  5. narrowed alone, the AMS mapping and the unmatched-filament check still read
  6. every plate. The plate that prints is now stored on the job.
  7. """
  8. import json
  9. import zipfile
  10. import pytest
  11. from sqlalchemy import select
  12. from backend.app.models.library import LibraryFile
  13. from backend.app.models.print_queue import PrintQueueItem
  14. pytestmark = [pytest.mark.asyncio, pytest.mark.integration]
  15. def _two_plate_3mf(path):
  16. plate = (
  17. "<plate><metadata key='index' value='{n}'/><filament id='{n}' type='{t}' color='#FFFFFF' used_g='5'/></plate>"
  18. )
  19. with zipfile.ZipFile(path, "w") as zf:
  20. zf.writestr(
  21. "Metadata/slice_info.config",
  22. "<?xml version='1.0'?><config>" + plate.format(n=1, t="PLA") + plate.format(n=2, t="PETG-CF") + "</config>",
  23. )
  24. zf.writestr("Metadata/plate_1.gcode", "; plate 1")
  25. zf.writestr("Metadata/plate_2.gcode", "; plate 2")
  26. async def _library_file(db_session, tmp_path, monkeypatch) -> LibraryFile:
  27. from backend.app.core.config import settings as app_settings
  28. monkeypatch.setattr(app_settings, "base_dir", tmp_path)
  29. _two_plate_3mf(tmp_path / "two.gcode.3mf")
  30. lib_file = LibraryFile(
  31. filename="two.gcode.3mf",
  32. file_path="two.gcode.3mf",
  33. file_type="3mf",
  34. file_size=1,
  35. file_hash="two-plates",
  36. source_type="uploaded",
  37. file_metadata={"sliced_for_model": "X1C"},
  38. )
  39. db_session.add(lib_file)
  40. await db_session.commit()
  41. return lib_file
  42. async def _requirements(db_session, item) -> list[str]:
  43. from backend.app.services.print_scheduler import PrintScheduler
  44. return [r["type"] for r in await PrintScheduler()._get_filament_requirements(db_session, item)]
  45. @pytest.mark.parametrize("model", ["X1C", "x1c"])
  46. async def test_a_bulk_add_from_the_library(async_client, db_session, printer_factory, tmp_path, monkeypatch, model):
  47. """In lower case too, as the scheduler matches a model (Q-U8)."""
  48. await printer_factory(model="X1C")
  49. lib_file = await _library_file(db_session, tmp_path, monkeypatch)
  50. response = await async_client.post(
  51. "/api/v1/library/files/add-to-queue", json={"file_ids": [lib_file.id], "target_model": model}
  52. )
  53. assert response.status_code == 200, response.text
  54. item = await db_session.scalar(select(PrintQueueItem).where(PrintQueueItem.library_file_id == lib_file.id))
  55. assert (item.plate_id, item.target_model, json.loads(item.required_filament_types)) == (1, "X1C", ["PLA"])
  56. assert await _requirements(db_session, item) == ["PLA"]
  57. async def test_post_queue(async_client, db_session, printer_factory, tmp_path, monkeypatch):
  58. await printer_factory(model="X1C")
  59. lib_file = await _library_file(db_session, tmp_path, monkeypatch)
  60. response = await async_client.post("/api/v1/queue/", json={"library_file_id": lib_file.id, "target_model": "X1C"})
  61. assert response.status_code == 200, response.text
  62. item = await db_session.get(PrintQueueItem, response.json()["id"])
  63. assert (item.plate_id, json.loads(item.required_filament_types)) == (1, ["PLA"])
  64. assert await _requirements(db_session, item) == ["PLA"]
  65. async def test_a_plate_named_by_the_caller_stands(async_client, db_session, printer_factory, tmp_path, monkeypatch):
  66. await printer_factory(model="X1C")
  67. lib_file = await _library_file(db_session, tmp_path, monkeypatch)
  68. response = await async_client.post(
  69. "/api/v1/queue/", json={"library_file_id": lib_file.id, "target_model": "X1C", "plate_id": 2}
  70. )
  71. item = await db_session.get(PrintQueueItem, response.json()["id"])
  72. assert (item.plate_id, json.loads(item.required_filament_types)) == (2, ["PETG-CF"])
  73. async def test_a_single_plate_file_keeps_no_plate(async_client, db_session, printer_factory, tmp_path, monkeypatch):
  74. """Nothing to choose between, so the job stays as it always was."""
  75. await printer_factory(model="X1C")
  76. lib_file = await _library_file(db_session, tmp_path, monkeypatch)
  77. with zipfile.ZipFile(tmp_path / "two.gcode.3mf", "w") as zf:
  78. zf.writestr(
  79. "Metadata/slice_info.config",
  80. "<?xml version='1.0'?><config><plate><metadata key='index' value='1'/>"
  81. "<filament id='1' type='PLA' color='#FFFFFF' used_g='5'/></plate></config>",
  82. )
  83. zf.writestr("Metadata/plate_1.gcode", "; plate 1")
  84. response = await async_client.post("/api/v1/queue/", json={"library_file_id": lib_file.id, "target_model": "X1C"})
  85. item = await db_session.get(PrintQueueItem, response.json()["id"])
  86. assert item.plate_id is None