Compare commits

..

5 Commits

Author SHA1 Message Date
Josh Hawkins
f7271e0a5b remove 2026-03-16 07:38:49 -05:00
Josh Hawkins
67604eb61d fix test pollution
test_maintainer was injecting MagicMock() into sys.modules["frigate.config.camera.updater"] at module load time and never restoring it. When the profile tests later imported CameraConfigUpdateEnum and CameraConfigUpdateTopic from that module, they got mock objects instead of the real dataclass/enum, so equality comparisons always failed
2026-03-16 07:31:36 -05:00
Josh Hawkins
0835aa7ea5 fix typing 2026-03-16 06:54:35 -05:00
Josh Hawkins
3c3cf11da4 formatting 2026-03-16 06:36:25 -05:00
Josh Hawkins
b657f04d0e formatting 2026-03-16 06:36:06 -05:00
4 changed files with 37 additions and 38 deletions

View File

@ -119,6 +119,7 @@ class FrigateApp:
self.ptz_metrics: dict[str, PTZMetrics] = {}
self.processes: dict[str, int] = {}
self.embeddings: Optional[EmbeddingsContext] = None
self.profile_manager: Optional[ProfileManager] = None
self.config = config
def ensure_dirs(self) -> None:
@ -351,9 +352,7 @@ class FrigateApp:
)
def init_profile_manager(self) -> None:
self.profile_manager = ProfileManager(
self.config, self.inter_config_updater
)
self.profile_manager = ProfileManager(self.config, self.inter_config_updater)
self.dispatcher.profile_manager = self.profile_manager
persisted = ProfileManager.load_persisted_profile()

View File

@ -16,6 +16,7 @@ from frigate.config.camera.updater import (
CameraConfigUpdateTopic,
)
from frigate.config.config import RuntimeFilterConfig, RuntimeMotionConfig
from frigate.config.profile_manager import ProfileManager
from frigate.const import (
CLEAR_ONGOING_REVIEW_SEGMENTS,
EXPIRE_AUDIO_ACTIVITY,
@ -93,7 +94,7 @@ class Dispatcher:
"notifications": self._on_global_notification_command,
"profile": self._on_profile_command,
}
self.profile_manager = None
self.profile_manager: Optional[ProfileManager] = None
for comm in self.comms:
comm.subscribe(self._receive)
@ -569,7 +570,9 @@ class Dispatcher:
logger.error("Profile manager not initialized")
return
profile_name = payload.strip() if payload.strip() not in ("", "none", "None") else None
profile_name = (
payload.strip() if payload.strip() not in ("", "none", "None") else None
)
err = self.profile_manager.activate_profile(profile_name)
if err:
logger.error("Failed to activate profile: %s", err)

View File

@ -2,16 +2,29 @@ import sys
import unittest
from unittest.mock import MagicMock, patch
# Mock complex imports before importing maintainer
sys.modules["frigate.comms.inter_process"] = MagicMock()
sys.modules["frigate.comms.detections_updater"] = MagicMock()
sys.modules["frigate.comms.recordings_updater"] = MagicMock()
sys.modules["frigate.config.camera.updater"] = MagicMock()
# Mock complex imports before importing maintainer, saving originals so we can
# restore them after import and avoid polluting sys.modules for other tests.
_MOCKED_MODULES = [
"frigate.comms.inter_process",
"frigate.comms.detections_updater",
"frigate.comms.recordings_updater",
"frigate.config.camera.updater",
]
_originals = {name: sys.modules.get(name) for name in _MOCKED_MODULES}
for name in _MOCKED_MODULES:
sys.modules[name] = MagicMock()
# Now import the class under test
from frigate.config import FrigateConfig # noqa: E402
from frigate.record.maintainer import RecordingMaintainer # noqa: E402
# Restore original modules (or remove mock if there was no original)
for name, orig in _originals.items():
if orig is None:
sys.modules.pop(name, None)
else:
sys.modules[name] = orig
class TestMaintainer(unittest.IsolatedAsyncioTestCase):
async def test_move_files_survives_bad_filename(self):

View File

@ -48,9 +48,7 @@ class TestCameraProfileConfig(unittest.TestCase):
def test_partial_review(self):
"""Profile with nested review.alerts.labels."""
profile = CameraProfileConfig(
review={"alerts": {"labels": ["person", "car"]}}
)
profile = CameraProfileConfig(review={"alerts": {"labels": ["person", "car"]}})
assert profile.review is not None
assert profile.review.alerts.labels == ["person", "car"]
@ -116,9 +114,7 @@ class TestCameraProfileConfig(unittest.TestCase):
from pydantic import ValidationError
with self.assertRaises(ValidationError):
CameraProfileConfig(
review={"alerts": {"labels": "not_a_list"}}
)
CameraProfileConfig(review={"alerts": {"labels": "not_a_list"}})
def test_invalid_profile_in_camera_config(self):
"""Invalid profile section in full config is caught at parse time."""
@ -410,14 +406,14 @@ class TestProfileManager(unittest.TestCase):
self.manager.activate_profile("disarmed")
# Back camera has no "disarmed" profile, should be unchanged
assert self.config.cameras["back"].notifications.enabled == back_base_notifications
assert (
self.config.cameras["back"].notifications.enabled == back_base_notifications
)
@patch.object(ProfileManager, "_persist_active_profile")
def test_activate_profile_disables_camera(self, mock_persist):
"""Profile with enabled=false disables the camera."""
self.config.profiles["away"] = ProfileDefinitionConfig(
friendly_name="Away"
)
self.config.profiles["away"] = ProfileDefinitionConfig(friendly_name="Away")
self.config.cameras["front"].profiles["away"] = CameraProfileConfig(
enabled=False
)
@ -431,9 +427,7 @@ class TestProfileManager(unittest.TestCase):
@patch.object(ProfileManager, "_persist_active_profile")
def test_deactivate_restores_enabled(self, mock_persist):
"""Deactivating a profile restores the camera's base enabled state."""
self.config.profiles["away"] = ProfileDefinitionConfig(
friendly_name="Away"
)
self.config.profiles["away"] = ProfileDefinitionConfig(friendly_name="Away")
self.config.cameras["front"].profiles["away"] = CameraProfileConfig(
enabled=False
)
@ -450,9 +444,7 @@ class TestProfileManager(unittest.TestCase):
"""Profile with zones adds/overrides zones on camera."""
from frigate.config.camera.zone import ZoneConfig
self.config.profiles["away"] = ProfileDefinitionConfig(
friendly_name="Away"
)
self.config.profiles["away"] = ProfileDefinitionConfig(friendly_name="Away")
self.config.cameras["front"].profiles["away"] = CameraProfileConfig(
zones={
"driveway": ZoneConfig(
@ -474,9 +466,7 @@ class TestProfileManager(unittest.TestCase):
"""Deactivating a profile restores base zones."""
from frigate.config.camera.zone import ZoneConfig
self.config.profiles["away"] = ProfileDefinitionConfig(
friendly_name="Away"
)
self.config.profiles["away"] = ProfileDefinitionConfig(friendly_name="Away")
self.config.cameras["front"].profiles["away"] = CameraProfileConfig(
zones={
"driveway": ZoneConfig(
@ -502,9 +492,7 @@ class TestProfileManager(unittest.TestCase):
)
from frigate.config.camera.zone import ZoneConfig
self.config.profiles["away"] = ProfileDefinitionConfig(
friendly_name="Away"
)
self.config.profiles["away"] = ProfileDefinitionConfig(friendly_name="Away")
self.config.cameras["front"].profiles["away"] = CameraProfileConfig(
zones={
"driveway": ZoneConfig(
@ -534,9 +522,7 @@ class TestProfileManager(unittest.TestCase):
CameraConfigUpdateTopic,
)
self.config.profiles["away"] = ProfileDefinitionConfig(
friendly_name="Away"
)
self.config.profiles["away"] = ProfileDefinitionConfig(friendly_name="Away")
self.config.cameras["front"].profiles["away"] = CameraProfileConfig(
enabled=False
)
@ -598,9 +584,7 @@ class TestProfilePersistence(unittest.TestCase):
try:
with patch.object(type(PERSISTENCE_FILE), "exists", return_value=True):
with patch.object(
type(PERSISTENCE_FILE), "read_text", return_value=""
):
with patch.object(type(PERSISTENCE_FILE), "read_text", return_value=""):
result = ProfileManager.load_persisted_profile()
assert result is None
finally: