2026-07-16 09:05:21 -05:00
|
|
|
"""Tests for the shared runtime config swap helper."""
|
|
|
|
|
|
|
|
|
|
import unittest
|
|
|
|
|
from unittest.mock import MagicMock
|
|
|
|
|
|
|
|
|
|
from frigate.api.config_util import swap_runtime_config
|
2026-07-29 09:39:01 -05:00
|
|
|
from frigate.config.holder import ConfigHolder
|
2026-07-16 09:05:21 -05:00
|
|
|
|
|
|
|
|
|
|
|
|
|
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()]
|
2026-07-29 09:39:01 -05:00
|
|
|
app.config_holder = ConfigHolder(MagicMock(name="boot_config"))
|
2026-07-16 09:05:21 -05:00
|
|
|
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
|
2026-07-16 13:30:41 -05:00
|
|
|
app.dispatcher.reapply_runtime_state_to_config.assert_called_once_with()
|
2026-07-16 09:05:21 -05:00
|
|
|
|
2026-07-29 09:39:01 -05:00
|
|
|
def test_updates_the_config_holder(self) -> None:
|
|
|
|
|
app = self._make_app()
|
|
|
|
|
holder = app.config_holder
|
|
|
|
|
config = MagicMock(name="new_config")
|
|
|
|
|
|
|
|
|
|
swap_runtime_config(app, config)
|
|
|
|
|
|
|
|
|
|
self.assertIs(holder.config, config)
|
|
|
|
|
|
|
|
|
|
def test_deferred_factory_builds_from_the_swapped_config(self) -> None:
|
|
|
|
|
"""A watchdog-style factory must not rebuild from the boot config.
|
|
|
|
|
|
|
|
|
|
The factories in FrigateApp are lambdas evaluated when a process is
|
|
|
|
|
restarted, long after a user may have saved. Reading through the
|
|
|
|
|
holder is what keeps a rebuilt process from reverting every change
|
|
|
|
|
made since Frigate started.
|
|
|
|
|
"""
|
|
|
|
|
app = self._make_app()
|
|
|
|
|
holder = app.config_holder
|
|
|
|
|
boot_config = holder.config
|
|
|
|
|
factory = lambda: holder.config # noqa: E731
|
|
|
|
|
self.assertIs(factory(), boot_config)
|
|
|
|
|
|
|
|
|
|
config = MagicMock(name="new_config")
|
|
|
|
|
swap_runtime_config(app, config)
|
|
|
|
|
|
|
|
|
|
self.assertIs(factory(), config)
|
|
|
|
|
|
2026-07-16 09:05:21 -05:00
|
|
|
def test_tolerates_missing_optional_collaborators(self) -> None:
|
|
|
|
|
app = MagicMock()
|
|
|
|
|
app.profile_manager = None
|
|
|
|
|
app.stats_emitter = None
|
|
|
|
|
app.dispatcher = None
|
2026-07-29 09:39:01 -05:00
|
|
|
app.config_holder = None
|
2026-07-16 09:05:21 -05:00
|
|
|
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()
|