Grotti 3.6.0: Lauf-Identität nach Filament-Pause, robustere Queue, Projekte/Aufträge list-first und API-Polish.

This commit is contained in:
Dominic Gundel committed 2026-10-25 08:24:24 +01:00
1 parent d1eb87ba25
commit 2b1e36634a
53 files changed
+4254 -636

No files matched your search

+107
View File
@@ -0,0 +1,107 @@
"""Leere History-Hülle, Resume-Beweis und Chargen-Tag."""
import unittest
from datetime import date, datetime, timedelta, timezone
from unittest.mock import MagicMock
from grotti.jobs import (
history_in_order,
history_shell_is_empty,
is_filament_resume,
)
from grotti.store import chargen_day, history_job_retired
class HistoryShellTests(unittest.TestCase):
def test_pause_shell_is_empty(self):
self.assertTrue(history_shell_is_empty({"duration_s": 0, "ended_at": None}))
def test_recorded_cancel_is_not_a_shell(self):
self.assertFalse(history_shell_is_empty({
"duration_s": 653,
"ended_at": datetime(2026, 10, 18, 13, 18, tzinfo=timezone.utc),
}))
def test_missing_row_is_not_a_shell(self):
self.assertFalse(history_shell_is_empty(None))
class FilamentResumeProofTests(unittest.TestCase):
def test_duration_continued_with_tiny_gap(self):
# 2026-10-25 Kobra 3 A1: 000040 → 000041, 0,04 s Lücke, gleiche print_duration
end = datetime(2026, 10, 25, 5, 48, 27, 569483, tzinfo=timezone.utc)
start = end + timedelta(milliseconds=40)
self.assertTrue(is_filament_resume(
end, start, prev_duration_s=11711, new_duration_s=11711
))
def test_fresh_restart_after_cancel_is_not_resume(self):
# Lauf 27941: 8 s abgebrochen, 45 s später Neustart mit Dauer ~0
end = datetime(2026, 10, 24, 11, 46, 8, tzinfo=timezone.utc)
start = end + timedelta(seconds=45)
self.assertFalse(is_filament_resume(
end, start, prev_duration_s=8, new_duration_s=5
))
def test_two_arg_call_without_proof_is_false(self):
end = datetime(2026, 10, 21, 19, 32, 20, tzinfo=timezone.utc)
start = end + timedelta(milliseconds=40)
self.assertFalse(is_filament_resume(end, start))
def test_watcher_signal_allows_longer_gap(self):
end = datetime(2026, 10, 25, 1, 0, 0, tzinfo=timezone.utc)
start = end + timedelta(hours=4)
self.assertTrue(is_filament_resume(
end, start, prev_duration_s=1000, new_duration_s=1000, watcher_resume=True
))
def test_duration_reset_is_not_resume(self):
end = datetime(2026, 10, 25, 5, 48, 27, tzinfo=timezone.utc)
start = end + timedelta(milliseconds=40)
self.assertFalse(is_filament_resume(
end, start, prev_duration_s=11711, new_duration_s=12
))
class HistoryOrderTests(unittest.TestCase):
def test_cancelled_part_before_resume(self):
items = [
{"job_id": "000041", "start_time": 1792907307.607},
{"job_id": "000040", "start_time": 1792879062.392},
]
self.assertEqual([j["job_id"] for j in history_in_order(items)], ["000040", "000041"])
def test_missing_start_first(self):
items = [{"job_id": "b", "start_time": 5}, {"job_id": "a"}]
self.assertEqual([j["job_id"] for j in history_in_order(items)], ["a", "b"])
class HistoryJobRetiredTests(unittest.TestCase):
def _conn(self, row):
conn = MagicMock()
conn.execute.return_value.fetchone.return_value = row
return conn
def test_folded_predecessor_is_retired(self):
conn = self._conn({"moonraker_job_id": "000041"})
self.assertTrue(history_job_retired(conn, "h", "000040"))
def test_own_job_is_not_retired(self):
conn = self._conn({"moonraker_job_id": "000041"})
self.assertFalse(history_job_retired(conn, "h", "000041"))
def test_unknown_job_is_not_retired(self):
self.assertFalse(history_job_retired(self._conn(None), "h", "000042"))
class ChargenDayTests(unittest.TestCase):
def test_old_print_keeps_its_day(self):
started = datetime(2026, 10, 18, 13, 4, 16, tzinfo=timezone.utc)
self.assertEqual(chargen_day(started, today=date(2026, 10, 24)), date(2026, 10, 18))
def test_without_start_uses_today(self):
self.assertEqual(chargen_day(None, today=date(2026, 10, 24)), date(2026, 10, 24))
if __name__ == "__main__":
unittest.main()