mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-09-30 11:12:35 +02:00
Every field that takes a chamber target stopped at 60: the per-filament chamber map and per-print override in Preheat & Heat Soak, the chamber quick-select presets, and the printer-card chamber control. 60 is the X1E's ceiling and the X1E was the only heated-chamber model when that limit was written; the H2 series and X2D heat to 65, so the top of their range was unreachable. The ceiling now lives in one constant per side (MAX_CHAMBER_TEMP_C in backend/app/utils/printer_models.py and frontend/src/utils/printer.ts) rather than as a literal at each call site. X1E firmware clamps a higher request to its own maximum, so a shared ceiling is safe. Also fixes a live bug at PrintersPage.tsx:7985: parsePresetTriple was bounded to 60 there, and it rejects the whole triple on any out-of-range entry, so a saved 65 preset would have silently reverted the printer card to the defaults while Settings still showed 65.
100 lines
3.8 KiB
Python
100 lines
3.8 KiB
Python
"""Validator tests for the user-configurable temperature / fan presets."""
|
|
|
|
import pytest
|
|
from pydantic import ValidationError
|
|
|
|
from backend.app.schemas.settings import AppSettingsUpdate
|
|
|
|
# (field_name, valid_payload, range_lo, range_hi)
|
|
PRESET_FIELDS = [
|
|
("nozzle_temp_presets", "[120, 220, 260]", 0, 320),
|
|
("bed_temp_presets", "[55, 75, 90]", 0, 140),
|
|
("chamber_temp_presets", "[35, 45, 60]", 0, 65),
|
|
("fan_speed_presets", "[50, 75, 100]", 0, 100),
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize("field,valid,lo,hi", PRESET_FIELDS)
|
|
def test_valid_triple_round_trips(field, valid, lo, hi):
|
|
update = AppSettingsUpdate(**{field: valid})
|
|
assert getattr(update, field) == valid
|
|
|
|
|
|
@pytest.mark.parametrize("field,_valid,_lo,_hi", PRESET_FIELDS)
|
|
def test_empty_string_means_use_defaults(field, _valid, _lo, _hi):
|
|
update = AppSettingsUpdate(**{field: ""})
|
|
assert getattr(update, field) == ""
|
|
|
|
|
|
@pytest.mark.parametrize("field,_valid,_lo,_hi", PRESET_FIELDS)
|
|
def test_missing_field_is_optional(field, _valid, _lo, _hi):
|
|
# Updates are PATCH-style; omitting the field shouldn't trigger the validator.
|
|
update = AppSettingsUpdate()
|
|
assert getattr(update, field) is None
|
|
|
|
|
|
@pytest.mark.parametrize("field,_valid,_lo,_hi", PRESET_FIELDS)
|
|
def test_malformed_json_rejected(field, _valid, _lo, _hi):
|
|
with pytest.raises(ValidationError) as exc:
|
|
AppSettingsUpdate(**{field: "not json"})
|
|
assert field in str(exc.value)
|
|
|
|
|
|
@pytest.mark.parametrize("field,_valid,_lo,_hi", PRESET_FIELDS)
|
|
def test_non_array_rejected(field, _valid, _lo, _hi):
|
|
with pytest.raises(ValidationError) as exc:
|
|
AppSettingsUpdate(**{field: '{"a": 1}'})
|
|
assert "array of exactly 3 integers" in str(exc.value)
|
|
|
|
|
|
@pytest.mark.parametrize("field,_valid,_lo,_hi", PRESET_FIELDS)
|
|
@pytest.mark.parametrize("bad_payload", ["[]", "[100]", "[100, 200]", "[100, 200, 300, 400]"])
|
|
def test_wrong_length_rejected(field, _valid, _lo, _hi, bad_payload):
|
|
with pytest.raises(ValidationError) as exc:
|
|
AppSettingsUpdate(**{field: bad_payload})
|
|
assert "array of exactly 3 integers" in str(exc.value)
|
|
|
|
|
|
@pytest.mark.parametrize("field,_valid,_lo,_hi", PRESET_FIELDS)
|
|
def test_float_entries_rejected(field, _valid, _lo, _hi):
|
|
with pytest.raises(ValidationError) as exc:
|
|
AppSettingsUpdate(**{field: "[1.5, 2.0, 3.0]"})
|
|
assert "must all be integers" in str(exc.value)
|
|
|
|
|
|
@pytest.mark.parametrize("field,_valid,_lo,_hi", PRESET_FIELDS)
|
|
def test_string_entries_rejected(field, _valid, _lo, _hi):
|
|
with pytest.raises(ValidationError) as exc:
|
|
AppSettingsUpdate(**{field: '["120", "220", "260"]'})
|
|
assert "must all be integers" in str(exc.value)
|
|
|
|
|
|
@pytest.mark.parametrize("field,_valid,lo,hi", PRESET_FIELDS)
|
|
def test_below_range_rejected(field, _valid, lo, hi):
|
|
bad = f"[{lo - 1}, {lo}, {lo}]"
|
|
with pytest.raises(ValidationError) as exc:
|
|
AppSettingsUpdate(**{field: bad})
|
|
assert f"[{lo}, {hi}]" in str(exc.value)
|
|
|
|
|
|
@pytest.mark.parametrize("field,_valid,lo,hi", PRESET_FIELDS)
|
|
def test_above_range_rejected(field, _valid, lo, hi):
|
|
bad = f"[{hi}, {hi}, {hi + 1}]"
|
|
with pytest.raises(ValidationError) as exc:
|
|
AppSettingsUpdate(**{field: bad})
|
|
assert f"[{lo}, {hi}]" in str(exc.value)
|
|
|
|
|
|
@pytest.mark.parametrize("field,_valid,lo,hi", PRESET_FIELDS)
|
|
def test_range_bounds_inclusive(field, _valid, lo, hi):
|
|
"""Both endpoints lo and hi must be accepted (inclusive bounds)."""
|
|
update = AppSettingsUpdate(**{field: f"[{lo}, {hi}, {hi}]"})
|
|
assert getattr(update, field) == f"[{lo}, {hi}, {hi}]"
|
|
|
|
|
|
def test_booleans_rejected_even_though_python_treats_them_as_ints():
|
|
"""`isinstance(True, int)` is True in Python — explicit guard required."""
|
|
with pytest.raises(ValidationError) as exc:
|
|
AppSettingsUpdate(nozzle_temp_presets="[true, 220, 260]")
|
|
assert "must all be integers" in str(exc.value)
|