build: harden controller v2 runtime
This commit is contained in:
+57
-2
@@ -14,8 +14,9 @@ from controller.config import (
|
||||
StorageConfig,
|
||||
)
|
||||
from controller.downloads import canonical_playlist_path
|
||||
from controller.models import SlotProtectedError
|
||||
from controller.mqtt_adapter import MqttEvent, PublishReceipt
|
||||
from controller.service import ControllerService
|
||||
from controller.service import ControllerService, ControllerUnavailableError
|
||||
from controller.store import ControllerStore
|
||||
|
||||
|
||||
@@ -82,12 +83,20 @@ class FakeMqttAdapter:
|
||||
class FakeDownloadManager:
|
||||
def __init__(self):
|
||||
self.recovered = False
|
||||
self.recovery_calls = 0
|
||||
self.expiry_calls = 0
|
||||
self.stopped = False
|
||||
|
||||
def recover_pending_deletions(self):
|
||||
self.recovered = True
|
||||
|
||||
def schedule_pending_deletion_recovery(self):
|
||||
self.recovered = True
|
||||
self.recovery_calls += 1
|
||||
return True
|
||||
|
||||
def expire_due_slots(self):
|
||||
self.expiry_calls += 1
|
||||
return []
|
||||
|
||||
def shutdown(self, *, wait):
|
||||
@@ -144,7 +153,7 @@ class ControllerServiceTests(unittest.TestCase):
|
||||
wall_clock=self.wall,
|
||||
)
|
||||
self.service.start()
|
||||
self.assertTrue(self.downloads.recovered)
|
||||
self.assertFalse(self.downloads.recovered)
|
||||
return self.service
|
||||
|
||||
def ready_slot(self, slot=1):
|
||||
@@ -414,6 +423,52 @@ class ControllerServiceTests(unittest.TestCase):
|
||||
self.assertTrue(health["coordinator_alive"])
|
||||
self.assertEqual(health["status"], "degraded")
|
||||
self.assertIn("mqtt_disconnected", health["degraded_reasons"])
|
||||
self.assertIn("awaiting_player_evidence", health["degraded_reasons"])
|
||||
|
||||
def test_cleanup_and_downloads_wait_for_post_start_player_evidence(self):
|
||||
self.start_service()
|
||||
self.service._next_expiry = self.monotonic()
|
||||
time.sleep(0.2)
|
||||
|
||||
self.assertEqual(self.downloads.expiry_calls, 0)
|
||||
self.assertFalse(self.downloads.recovered)
|
||||
with self.assertRaisesRegex(
|
||||
ControllerUnavailableError,
|
||||
"deferred until fresh Player evidence",
|
||||
):
|
||||
self.service.enqueue_download(
|
||||
"https://example.test/deferred",
|
||||
None,
|
||||
None,
|
||||
)
|
||||
|
||||
self.send_message(
|
||||
self.config.topics.player_state,
|
||||
"idle",
|
||||
retain=True,
|
||||
)
|
||||
self.wait_for(
|
||||
lambda: self.downloads.recovered,
|
||||
"retained Player idle did not release deferred recovery",
|
||||
)
|
||||
self.wait_for(
|
||||
lambda: self.downloads.expiry_calls > 0,
|
||||
"Player idle did not release deferred expiry",
|
||||
)
|
||||
self.assertNotIn(
|
||||
"awaiting_player_evidence",
|
||||
self.service.health()["degraded_reasons"],
|
||||
)
|
||||
|
||||
def test_manual_delete_requires_explicit_override_while_player_is_unknown(self):
|
||||
self.start_service()
|
||||
self.ready_slot(1)
|
||||
|
||||
with self.assertRaisesRegex(
|
||||
SlotProtectedError,
|
||||
"confirm_player_unknown=true",
|
||||
):
|
||||
self.service.submit_control({"action": "delete_slot", "slot": 1})
|
||||
|
||||
def test_administrator_can_clear_exact_uncertain_playback_review(self):
|
||||
self.start_service()
|
||||
|
||||
Reference in New Issue
Block a user