Files
grotti/tests/test_watcher_offline.py
T

206 lines
8.8 KiB
Python

"""Kontaktverlust beendet einen Druck nicht."""
import unittest
from unittest import mock
from grotti.watcher import StateWatcher, contact_gap_keeps_run
HOST = "10.10.1.88"
FILE = "boden_PETG_0.2_2h15m.gcode"
class ContactGapTests(unittest.TestCase):
def test_same_file_stays(self):
self.assertTrue(contact_gap_keeps_run(FILE, FILE, "printing"))
self.assertTrue(contact_gap_keeps_run(FILE, FILE, "paused"))
def test_other_file_does_not_stay(self):
self.assertFalse(contact_gap_keeps_run(FILE, "andere.gcode", "printing"))
def test_terminal_state_does_not_stay(self):
self.assertFalse(contact_gap_keeps_run(FILE, FILE, "standby"))
self.assertFalse(contact_gap_keeps_run(FILE, FILE, "complete"))
self.assertFalse(contact_gap_keeps_run(FILE, FILE, "error"))
def test_missing_name_is_not_a_change(self):
self.assertTrue(contact_gap_keeps_run("", FILE, "printing"))
self.assertTrue(contact_gap_keeps_run(FILE, "", "paused"))
class WatcherOfflineTests(unittest.TestCase):
def setUp(self):
self.watcher = StateWatcher("postgresql://x", {})
self.watcher._prev[HOST] = {
"state": "printing",
"offline": False,
"filename": FILE,
"run_id": 42,
}
def test_two_misses_keep_the_run(self):
live = {
"host": HOST,
"name": "Kobra S1",
"state": "offline",
"ok": False,
"filename": "",
}
with mock.patch.object(self.watcher, "_emit") as emit, \
mock.patch("grotti.watcher.store.complete_open_run") as close, \
mock.patch("grotti.watcher.jobs.apply_deduction") as deduct:
self.watcher._one(mock.Mock(), live)
self.watcher._one(mock.Mock(), live)
close.assert_not_called()
deduct.assert_not_called()
remembered = self.watcher._prev[HOST]
self.assertEqual(remembered["state"], "printing")
self.assertTrue(remembered["offline"])
self.assertEqual(remembered["run_id"], 42)
self.assertEqual(remembered["filename"], FILE)
self.assertEqual([call.args[3] for call in emit.call_args_list], ["offline"])
def test_same_print_after_gap_is_the_same_run(self):
self.watcher._prev[HOST]["offline"] = True
live = {
"host": HOST,
"name": "Kobra S1",
"state": "printing",
"ok": True,
"filename": FILE,
"weight": 0,
}
run = {"id": 42, "filename": FILE}
with mock.patch.object(self.watcher, "_emit") as emit, \
mock.patch.object(self.watcher, "_track_print", return_value=run) as track, \
mock.patch.object(self.watcher, "_check_spool", return_value=False), \
mock.patch("grotti.watcher.store.find_live_run", return_value=run), \
mock.patch("grotti.watcher.store.abandon_live_runs") as abandon, \
mock.patch("grotti.watcher.store.complete_open_run") as close, \
mock.patch("grotti.watcher.jobs.sync_history") as sync, \
mock.patch("grotti.watcher.jobs.apply_deduction") as deduct:
self.watcher._one(mock.Mock(), live)
close.assert_not_called()
deduct.assert_not_called()
abandon.assert_not_called()
sync.assert_not_called()
track.assert_called_once()
self.assertEqual([call.args[3] for call in emit.call_args_list], ["offline"])
self.assertIn("wieder erreichbar", emit.call_args_list[0].args[4])
self.assertEqual(self.watcher._prev[HOST]["state"], "printing")
self.assertFalse(self.watcher._prev[HOST]["offline"])
def test_standby_after_gap_closes(self):
self.watcher._prev[HOST]["offline"] = True
live = {
"host": HOST,
"name": "Kobra S1",
"state": "standby",
"ok": True,
"filename": "",
}
run = {"id": 42, "filename": FILE}
with mock.patch.object(self.watcher, "_emit") as emit, \
mock.patch.object(self.watcher, "_track_print", return_value=None), \
mock.patch.object(self.watcher, "_check_spool", return_value=False), \
mock.patch("grotti.watcher.store.find_live_run", return_value=run), \
mock.patch("grotti.watcher.store.running_print_on_host", return_value=None), \
mock.patch("grotti.watcher.store.abandon_live_runs") as abandon, \
mock.patch("grotti.watcher.store.set_bed_needs_clear"), \
mock.patch("grotti.watcher.jobs.sync_history") as sync:
self.watcher._one(mock.Mock(), live)
abandon.assert_called_once()
sync.assert_called_once()
self.assertIn("abbruch", [call.args[3] for call in emit.call_args_list])
def test_other_file_after_gap_releases_the_old_run(self):
self.watcher._prev[HOST]["offline"] = True
live = {
"host": HOST,
"name": "Kobra S1",
"state": "printing",
"ok": True,
"filename": "andere.gcode",
"weight": 0,
}
old = {"id": 42, "filename": FILE}
new = {"id": 99, "filename": "andere.gcode"}
with mock.patch.object(self.watcher, "_emit") as emit, \
mock.patch.object(self.watcher, "_track_print", return_value=new), \
mock.patch.object(self.watcher, "_ensure_start_segment"), \
mock.patch.object(self.watcher, "_check_spool", return_value=False), \
mock.patch("grotti.watcher.store.find_live_run", side_effect=[old, old, None]), \
mock.patch("grotti.watcher.store.abandon_live_runs") as abandon, \
mock.patch("grotti.watcher.jobs.sync_history") as sync:
self.watcher._one(mock.Mock(), live)
sync.assert_called_once()
self.assertGreaterEqual(abandon.call_count, 1)
kinds = [call.args[3] for call in emit.call_args_list]
self.assertEqual(kinds, ["abbruch", "offline", "start"])
def test_other_file_without_offline_releases_the_old_run(self):
live = {
"host": HOST,
"name": "Kobra S1",
"state": "printing",
"ok": True,
"filename": "andere.gcode",
"weight": 0,
}
old = {"id": 42, "filename": FILE}
new = {"id": 99, "filename": "andere.gcode"}
seen = {"n": 0}
def live_run(_conn, _host, filename=""):
seen["n"] += 1
if seen["n"] <= 3:
return old
return None
with mock.patch.object(self.watcher, "_emit") as emit, \
mock.patch.object(self.watcher, "_track_print", return_value=new), \
mock.patch.object(self.watcher, "_ensure_start_segment"), \
mock.patch.object(self.watcher, "_check_spool", return_value=False), \
mock.patch("grotti.watcher.store.find_live_run", side_effect=live_run), \
mock.patch("grotti.watcher.store.abandon_live_runs") as abandon, \
mock.patch("grotti.watcher.jobs.sync_history") as sync:
self.watcher._one(mock.Mock(), live)
sync.assert_called_once()
abandon.assert_called()
self.assertIn("abbruch", [call.args[3] for call in emit.call_args_list])
self.assertIn("start", [call.args[3] for call in emit.call_args_list])
def test_cancel_event_survives_deleted_live_run(self):
self.watcher._cancel_hits[HOST] = 2
live = {
"host": HOST,
"name": "Kobra S1",
"state": "cancelled",
"ok": True,
"filename": FILE,
}
dangling = {"id": 42, "filename": FILE, "state": "in_progress"}
def row(_conn, run_id):
if run_id == 77:
return {"id": 77, "filename": FILE}
return None
with mock.patch("grotti.watcher.store.find_live_run", side_effect=[dangling, None]), \
mock.patch("grotti.watcher.store.find_open_history_run", return_value=None), \
mock.patch("grotti.watcher.store.get_run_row", side_effect=row), \
mock.patch("grotti.watcher.store.latest_run_id", return_value=77), \
mock.patch("grotti.watcher.store.abandon_live_runs"), \
mock.patch("grotti.watcher.store.set_bed_needs_clear"), \
mock.patch("grotti.watcher.store.set_printer_active_run"), \
mock.patch("grotti.watcher.jobs.sync_history") as sync, \
mock.patch("grotti.watcher.notify.record") as record:
self.watcher._one(mock.Mock(), live)
sync.assert_called_once()
record.assert_called()
self.assertEqual(record.call_args.kwargs["run_id"], 77)
self.assertEqual(record.call_args.args[4], "abbruch")
if __name__ == "__main__":
unittest.main()