mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-07-25 21:29:01 +03:00
/api/config/set and camera deletion re-parse yaml into a fresh FrigateConfig and swap it in, then re-layered the persisted runtime toggle overrides so a camera the user turned off wouldn't come back on. That re-layer ran apply_runtime_state, which replays each override through the command handlers, so every save re-published a ZMQ config update, a retained MQTT state message, and a runtime-state disk write for every camera with a stored toggle. All of it was redundant: the worker processes were never swapped and still hold the live toggle values, so only the in-process config object the API and dispatcher read was out of date. The extra traffic churned the retained MQTT topics, amplified disk writes, and co-drained enabled updates with other topics on the config socket. Add Dispatcher.reapply_runtime_state_to_config, which corrects only the swapped-in config object, mirroring the field mutations and gates of the _on_*_command handlers with no ZMQ, MQTT, or disk writes. swap_runtime_config now calls it instead of apply_runtime_state; apply_runtime_state is unchanged and still used at startup, where the workers genuinely must be told.
56 lines
1.8 KiB
Python
56 lines
1.8 KiB
Python
"""Tests for the shared runtime config swap helper."""
|
|
|
|
import unittest
|
|
from unittest.mock import MagicMock
|
|
|
|
from frigate.api.config_util import swap_runtime_config
|
|
|
|
|
|
class TestSwapRuntimeConfig(unittest.TestCase):
|
|
"""swap_runtime_config rebinds every collaborator to the new config."""
|
|
|
|
def _make_app(self) -> MagicMock:
|
|
app = MagicMock()
|
|
app.dispatcher.comms = [MagicMock(), MagicMock()]
|
|
return app
|
|
|
|
def test_rebinds_all_references(self) -> None:
|
|
app = self._make_app()
|
|
config = MagicMock(name="new_config")
|
|
|
|
swap_runtime_config(app, config)
|
|
|
|
self.assertIs(app.frigate_config, config)
|
|
app.genai_manager.update_config.assert_called_once_with(config)
|
|
app.profile_manager.update_config.assert_called_once_with(config)
|
|
self.assertIs(app.stats_emitter.config, config)
|
|
self.assertIs(app.dispatcher.config, config)
|
|
for comm in app.dispatcher.comms:
|
|
self.assertIs(comm.config, config)
|
|
|
|
def test_reapplies_runtime_state_after_swap(self) -> None:
|
|
app = self._make_app()
|
|
config = MagicMock(name="new_config")
|
|
|
|
swap_runtime_config(app, config)
|
|
|
|
# the swap rebuilds cameras from yaml, so overrides must be re-layered
|
|
app.dispatcher.reapply_runtime_state_to_config.assert_called_once_with()
|
|
|
|
def test_tolerates_missing_optional_collaborators(self) -> None:
|
|
app = MagicMock()
|
|
app.profile_manager = None
|
|
app.stats_emitter = None
|
|
app.dispatcher = None
|
|
config = MagicMock(name="new_config")
|
|
|
|
# must not raise when the optional collaborators are absent
|
|
swap_runtime_config(app, config)
|
|
|
|
self.assertIs(app.frigate_config, config)
|
|
app.genai_manager.update_config.assert_called_once_with(config)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|