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:
1 parent
d1eb87ba25
commit
2b1e36634a
53 files changed
+4254
-636
No files matched your search
@@ -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()
|
||||
Reference in new issue
Block a user