Files
grotti/tests/test_jobs_spool.py
T
Dominic Gundel db6a85a703 Grotti 3.7.0: Treiber für Kobra 4 und Marlin, smartere Dateien-Auswahl und einstellbare Etiketten.
Senden und Abbruch laufen über den Treiber, Orca-Doppelzeilen und tote Ideen-API fallen weg, Drucker folgen der Datei, Etikettenformat liegt in den Einstellungen.
2026-11-02 14:03:57 +01:00

132 lines
5.2 KiB
Python

"""Unit-Tests für Spulenwahl und Steuer-Parameter-Aliase."""
import unittest
from datetime import datetime, timedelta, timezone
from unittest.mock import MagicMock, patch
from grotti import jobs
from grotti import moonraker
class ChooseSpoolTests(unittest.TestCase):
def test_rejects_spool_created_after_print_end(self):
end = datetime(2026, 10, 1, 12, 0, tzinfo=timezone.utc)
old = {
"id": 1,
"material_type": "PLA",
"created_at": end - timedelta(days=2),
}
new = {
"id": 2,
"material_type": "PLA",
"created_at": end + timedelta(hours=1),
}
conn = MagicMock()
with patch("grotti.jobs.store.spools_for_printer", return_value=[new]):
with patch("grotti.jobs.store.get_spool_row", return_value=new):
self.assertIsNone(jobs.choose_spool(conn, "h", "PLA", 2, ended_at=end))
with patch("grotti.jobs.store.spools_for_printer", return_value=[old]):
self.assertEqual(jobs.choose_spool(conn, "h", "PLA", None, ended_at=end)["id"], 1)
def test_prefers_host_spool_over_stale_hint(self):
"""Rollenwechsel: Hint im Regal, neue Rolle am Drucker → Abzug auf die neue."""
end = datetime(2026, 10, 18, 14, 58, tzinfo=timezone.utc)
hinted = {
"id": 4,
"material_type": "PLA",
"printer_host": "",
"created_at": end - timedelta(days=5),
}
current = {
"id": 14,
"material_type": "PLA",
"printer_host": "h",
"created_at": end - timedelta(minutes=10),
}
conn = MagicMock()
with patch("grotti.jobs.store.spools_for_printer", return_value=[current]):
with patch("grotti.jobs.store.get_spool_row", return_value=hinted):
self.assertEqual(jobs.choose_spool(conn, "h", "PLA", 4, ended_at=end)["id"], 14)
def test_keeps_hint_when_no_host_spool(self):
end = datetime(2026, 10, 18, 14, 58, tzinfo=timezone.utc)
hinted = {
"id": 4,
"material_type": "PLA",
"printer_host": "",
"created_at": end - timedelta(days=5),
}
conn = MagicMock()
with patch("grotti.jobs.store.spools_for_printer", return_value=[]):
with patch("grotti.jobs.store.get_spool_row", return_value=hinted):
self.assertEqual(jobs.choose_spool(conn, "h", "PLA", 4, ended_at=end)["id"], 4)
def test_resolve_tracking_spool_prefers_host(self):
conn = MagicMock()
with patch(
"grotti.jobs.resolve_spools_at_host",
return_value=[{"id": 14, "printer_host": "h"}],
):
self.assertEqual(jobs.resolve_tracking_spool(conn, "h", 4), 14)
def test_ace_target_skips_spool_created_after_end(self):
end = datetime(2026, 10, 15, 22, 0, tzinfo=timezone.utc)
future = {"id": 18, "created_at": end + timedelta(days=6)}
conn = MagicMock()
with patch("grotti.jobs.store.spool_at_slot", return_value=future):
self.assertEqual(
jobs._targets_from_ace(conn, "h", [{"ams_index": 3}], ended_at=end),
[],
)
def test_spool_valid_for_end_naive(self):
end = datetime(2026, 10, 1, 12, 0, tzinfo=timezone.utc)
spool = {"created_at": datetime(2026, 9, 1, 12, 0)}
self.assertTrue(jobs._spool_valid_for_end(spool, end))
class UndoDeductionTests(unittest.TestCase):
def test_reopens_last_segment(self):
conn = MagicMock()
conn.execute.return_value.fetchall.return_value = [
{"spool_id": 7, "weight_g": 10},
]
run = {"id": 42, "deducted": True}
self.assertTrue(jobs.undo_deduction(conn, run))
sqls = [str(call.args[0]) for call in conn.execute.call_args_list]
self.assertTrue(any("UPDATE run_segments" in sql and "mm_end = NULL" in sql for sql in sqls))
class SteuernAliasTests(unittest.TestCase):
@patch("grotti.moonraker.gcode_script", return_value={"ok": True})
def test_value_alias_for_temp(self, gcode):
moonraker.steuern("10.0.0.1", "nozzle", {"value": 210})
script = gcode.call_args[0][1]
self.assertIn("TARGET=210.0", script)
@patch("grotti.moonraker.gcode_script", return_value={"ok": True})
def test_percent_alias_for_speed(self, gcode):
moonraker.steuern("10.0.0.1", "speed", {"value": 120})
script = gcode.call_args[0][1]
self.assertIn("M220 S120", script)
class HostValidationTests(unittest.TestCase):
def test_validate_printer_host(self):
from web.app import validate_printer_host
from grotti.errors import GrottiError
self.assertEqual(validate_printer_host("10.10.1.60"), "10.10.1.60")
self.assertEqual(validate_printer_host("kobra.local:80"), "kobra.local:80")
self.assertEqual(validate_printer_host("COM5@250000"), "COM5@250000")
self.assertEqual(validate_printer_host("/dev/ttyUSB0"), "/dev/ttyUSB0")
with self.assertRaises(GrottiError):
validate_printer_host("http://evil")
with self.assertRaises(GrottiError):
validate_printer_host("../etc/passwd")
if __name__ == "__main__":
unittest.main()