| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- """Tests for the filament override feature in the print scheduler."""
- from unittest.mock import MagicMock, patch
- import pytest
- from backend.app.services.print_scheduler import PrintScheduler
- class TestCountOverrideColorMatches:
- """Test the _count_override_color_matches method."""
- @pytest.fixture
- def scheduler(self):
- return PrintScheduler()
- @patch("backend.app.services.print_scheduler.printer_manager")
- def test_no_status_returns_zero(self, mock_pm, scheduler):
- """When printer_manager.get_status() returns None, should return 0."""
- mock_pm.get_status.return_value = None
- result = scheduler._count_override_color_matches(1, [{"type": "PLA", "color": "#FF0000"}])
- assert result == 0
- @patch("backend.app.services.print_scheduler.printer_manager")
- def test_exact_match(self, mock_pm, scheduler):
- """Override with matching type+color on printer returns 1."""
- mock_pm.get_status.return_value = MagicMock(
- raw_data={
- "ams": [{"id": 0, "tray": [{"id": 0, "tray_type": "PLA", "tray_color": "FF0000FF"}]}],
- }
- )
- result = scheduler._count_override_color_matches(1, [{"type": "PLA", "color": "#FF0000"}])
- assert result == 1
- @patch("backend.app.services.print_scheduler.printer_manager")
- def test_no_match(self, mock_pm, scheduler):
- """Override with type+color not on printer returns 0."""
- mock_pm.get_status.return_value = MagicMock(
- raw_data={
- "ams": [{"id": 0, "tray": [{"id": 0, "tray_type": "PLA", "tray_color": "FF0000FF"}]}],
- }
- )
- result = scheduler._count_override_color_matches(1, [{"type": "PETG", "color": "#00FF00"}])
- assert result == 0
- @patch("backend.app.services.print_scheduler.printer_manager")
- def test_multiple_overrides_partial_match(self, mock_pm, scheduler):
- """2 overrides, only 1 matching = returns 1."""
- mock_pm.get_status.return_value = MagicMock(
- raw_data={
- "ams": [{"id": 0, "tray": [{"id": 0, "tray_type": "PLA", "tray_color": "FF0000FF"}]}],
- }
- )
- overrides = [
- {"type": "PLA", "color": "#FF0000"}, # Matches
- {"type": "PETG", "color": "#00FF00"}, # Does not match
- ]
- result = scheduler._count_override_color_matches(1, overrides)
- assert result == 1
- @patch("backend.app.services.print_scheduler.printer_manager")
- def test_color_normalization(self, mock_pm, scheduler):
- """Override color '#FF0000' matches printer tray_color 'FF0000FF' (with alpha)."""
- mock_pm.get_status.return_value = MagicMock(
- raw_data={
- "ams": [{"id": 0, "tray": [{"id": 0, "tray_type": "PLA", "tray_color": "FF0000FF"}]}],
- }
- )
- # Override uses #-prefixed color; printer uses 8-char RGBA without hash
- result = scheduler._count_override_color_matches(1, [{"type": "PLA", "color": "#FF0000"}])
- assert result == 1
- @patch("backend.app.services.print_scheduler.printer_manager")
- def test_external_spool_match(self, mock_pm, scheduler):
- """Override matches filament in vt_tray."""
- mock_pm.get_status.return_value = MagicMock(
- raw_data={
- "ams": [],
- "vt_tray": [{"tray_type": "TPU", "tray_color": "0000FFFF"}],
- }
- )
- result = scheduler._count_override_color_matches(1, [{"type": "TPU", "color": "#0000FF"}])
- assert result == 1
- class TestFilamentOverrideInMatching:
- """Test that when overrides are applied to filament requirements, the matching uses overridden values."""
- @pytest.fixture
- def scheduler(self):
- return PrintScheduler()
- def _apply_overrides(self, filament_reqs, overrides):
- """Simulate override application as done in _compute_ams_mapping_for_printer."""
- override_map = {o["slot_id"]: o for o in overrides}
- for req in filament_reqs:
- if req["slot_id"] in override_map:
- override = override_map[req["slot_id"]]
- req["type"] = override["type"]
- req["color"] = override["color"]
- req["tray_info_idx"] = "" # Clear for override
- return filament_reqs
- def test_override_changes_color_match(self, scheduler):
- """Original req has color A, loaded has color B. Override to color B gives exact match."""
- filament_reqs = [{"slot_id": 1, "type": "PLA", "color": "#000000", "tray_info_idx": ""}]
- loaded = [
- {"type": "PLA", "color": "#FF0000", "global_tray_id": 0},
- ]
- # Without override: type-only match (colors differ)
- result_without = scheduler._match_filaments_to_slots(filament_reqs, loaded)
- assert result_without == [0] # Matches by type only
- # Now apply override changing color to match loaded
- overrides = [{"slot_id": 1, "type": "PLA", "color": "#FF0000"}]
- filament_reqs_overridden = [{"slot_id": 1, "type": "PLA", "color": "#000000", "tray_info_idx": ""}]
- self._apply_overrides(filament_reqs_overridden, overrides)
- result_with = scheduler._match_filaments_to_slots(filament_reqs_overridden, loaded)
- assert result_with == [0] # Exact color match now
- # Verify the override actually changed the color in the requirement
- assert filament_reqs_overridden[0]["color"] == "#FF0000"
- def test_override_clears_tray_info_idx(self, scheduler):
- """When tray_info_idx is cleared, matching falls to color-based instead of tray_info_idx-based."""
- loaded = [
- {"type": "PLA", "color": "#FF0000", "global_tray_id": 0, "tray_info_idx": "GFA00"},
- {"type": "PLA", "color": "#00FF00", "global_tray_id": 1, "tray_info_idx": "GFB00"},
- ]
- # Without override: tray_info_idx "GFA00" matches tray 0 (red)
- filament_reqs_original = [{"slot_id": 1, "type": "PLA", "color": "#FF0000", "tray_info_idx": "GFA00"}]
- result_original = scheduler._match_filaments_to_slots(filament_reqs_original, loaded)
- assert result_original == [0] # Matched by tray_info_idx
- # With override: tray_info_idx is cleared, color changed to green -> matches tray 1
- filament_reqs_overridden = [{"slot_id": 1, "type": "PLA", "color": "#FF0000", "tray_info_idx": "GFA00"}]
- overrides = [{"slot_id": 1, "type": "PLA", "color": "#00FF00"}]
- self._apply_overrides(filament_reqs_overridden, overrides)
- assert filament_reqs_overridden[0]["tray_info_idx"] == "" # Cleared
- result_overridden = scheduler._match_filaments_to_slots(filament_reqs_overridden, loaded)
- assert result_overridden == [1] # Now matches tray 1 by color
- def test_override_type_change(self, scheduler):
- """Override changes type from PLA to PETG, loaded has PETG -> matches."""
- loaded = [
- {"type": "PETG", "color": "#FF0000", "global_tray_id": 0},
- ]
- # Without override: PLA requirement, PETG loaded -> no match
- filament_reqs_original = [{"slot_id": 1, "type": "PLA", "color": "#FF0000", "tray_info_idx": ""}]
- result_original = scheduler._match_filaments_to_slots(filament_reqs_original, loaded)
- assert result_original == [-1] # Type mismatch
- # With override: type changed to PETG -> matches
- filament_reqs_overridden = [{"slot_id": 1, "type": "PLA", "color": "#FF0000", "tray_info_idx": ""}]
- overrides = [{"slot_id": 1, "type": "PETG", "color": "#FF0000"}]
- self._apply_overrides(filament_reqs_overridden, overrides)
- result_overridden = scheduler._match_filaments_to_slots(filament_reqs_overridden, loaded)
- assert result_overridden == [0] # Exact match now
- def test_partial_override(self, scheduler):
- """2 slots, only slot 1 overridden. Slot 1 uses override, slot 2 uses original."""
- loaded = [
- {"type": "PLA", "color": "#FF0000", "global_tray_id": 0},
- {"type": "PETG", "color": "#00FF00", "global_tray_id": 1},
- ]
- filament_reqs = [
- {"slot_id": 1, "type": "PLA", "color": "#000000", "tray_info_idx": "GFA00"},
- {"slot_id": 2, "type": "PETG", "color": "#00FF00", "tray_info_idx": "GFG02"},
- ]
- # Override only slot 1: change color to red
- overrides = [{"slot_id": 1, "type": "PLA", "color": "#FF0000"}]
- self._apply_overrides(filament_reqs, overrides)
- # Slot 1: overridden to PLA/#FF0000, tray_info_idx cleared -> matches tray 0 by exact color
- assert filament_reqs[0]["color"] == "#FF0000"
- assert filament_reqs[0]["tray_info_idx"] == ""
- # Slot 2: NOT overridden, retains original tray_info_idx
- assert filament_reqs[1]["color"] == "#00FF00"
- assert filament_reqs[1]["tray_info_idx"] == "GFG02"
- result = scheduler._match_filaments_to_slots(filament_reqs, loaded)
- assert result == [0, 1] # Slot 1 -> tray 0 (red PLA), slot 2 -> tray 1 (green PETG)
- def test_nozzle_filtering_with_override(self, scheduler):
- """Override to a type only available on the wrong nozzle returns -1."""
- loaded = [
- # PETG on RIGHT nozzle (extruder 0) only
- {"type": "PETG", "color": "#FF0000", "global_tray_id": 0, "extruder_id": 0},
- # PLA on LEFT nozzle (extruder 1) only
- {"type": "PLA", "color": "#00FF00", "global_tray_id": 4, "extruder_id": 1},
- ]
- # Override to PETG on LEFT nozzle — but PETG is only on RIGHT
- filament_reqs = [{"slot_id": 1, "type": "PLA", "color": "#000000", "tray_info_idx": "GFA00", "nozzle_id": 1}]
- overrides = [{"slot_id": 1, "type": "PETG", "color": "#FF0000"}]
- self._apply_overrides(filament_reqs, overrides)
- result = scheduler._match_filaments_to_slots(filament_reqs, loaded)
- # Nozzle filter limits to extruder 1 (LEFT) which only has PLA.
- # Override changed type to PETG, so no type match on LEFT nozzle -> -1
- assert result == [-1]
|