From d0e8620baa0534fec7d7018c2e8235c24e1c8816 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Thu, 23 Apr 2026 12:59:05 -0500 Subject: [PATCH] tests --- frigate/test/http_api/test_http_app.py | 20 +++++++ frigate/test/http_api/test_http_event.py | 19 ++++++ .../test/test_chat_find_similar_objects.py | 7 ++- frigate/test/test_output_ws_auth.py | 59 +++++++++++++++++++ .../test/test_webpush_camera_monitoring.py | 29 +++++++++ 5 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 frigate/test/test_output_ws_auth.py create mode 100644 frigate/test/test_webpush_camera_monitoring.py diff --git a/frigate/test/http_api/test_http_app.py b/frigate/test/http_api/test_http_app.py index bf8e9c72a9..2be0e65da8 100644 --- a/frigate/test/http_api/test_http_app.py +++ b/frigate/test/http_api/test_http_app.py @@ -23,6 +23,26 @@ class TestHttpApp(BaseTestHttp): response_json = response.json() assert response_json == self.test_stats + def test_recordings_storage_requires_admin(self): + stats = Mock(spec=StatsEmitter) + stats.get_latest_stats.return_value = self.test_stats + app = super().create_app(stats) + app.storage_maintainer = Mock() + app.storage_maintainer.calculate_camera_usages.return_value = { + "front_door": {"usage": 2.0}, + } + + with AuthTestClient(app) as client: + response = client.get( + "/recordings/storage", + headers={"remote-user": "viewer", "remote-role": "viewer"}, + ) + assert response.status_code == 403 + + response = client.get("/recordings/storage") + assert response.status_code == 200 + assert response.json()["front_door"]["usage_percent"] == 25.0 + def test_config_set_in_memory_replaces_objects_track_list(self): self.minimal_config["cameras"]["front_door"]["objects"] = { "track": ["person", "car"], diff --git a/frigate/test/http_api/test_http_event.py b/frigate/test/http_api/test_http_event.py index bc7f388e15..8aca6577d9 100644 --- a/frigate/test/http_api/test_http_event.py +++ b/frigate/test/http_api/test_http_event.py @@ -219,6 +219,25 @@ class TestHttpApp(BaseTestHttp): assert len(events) == 1 assert events[0]["id"] == event_id + def test_similarity_search_hides_unauthorized_anchor_event(self): + mock_embeddings = Mock() + self.app.frigate_config.semantic_search.enabled = True + self.app.embeddings = mock_embeddings + + with AuthTestClient(self.app) as client: + super().insert_mock_event("hidden.anchor", camera="back_door") + response = client.get( + "/events/search", + params={ + "search_type": "similarity", + "event_id": "hidden.anchor", + }, + ) + + assert response.status_code == 404 + assert response.json()["message"] == "Event not found" + mock_embeddings.search_thumbnail.assert_not_called() + def test_get_good_event(self): id = "123456.random" diff --git a/frigate/test/test_chat_find_similar_objects.py b/frigate/test/test_chat_find_similar_objects.py index 38055658e1..73fd3b27db 100644 --- a/frigate/test/test_chat_find_similar_objects.py +++ b/frigate/test/test_chat_find_similar_objects.py @@ -145,9 +145,12 @@ class TestExecuteFindSimilarObjects(unittest.TestCase): embeddings=embeddings, frigate_config=SimpleNamespace( semantic_search=SimpleNamespace(enabled=semantic_enabled), + cameras={"driveway": object()}, + auth=SimpleNamespace(roles={"admin": [], "viewer": ["driveway"]}), + proxy=SimpleNamespace(separator=","), ), ) - return SimpleNamespace(app=app) + return SimpleNamespace(app=app, headers={}) def test_semantic_search_disabled_returns_error(self): req = self._make_request(semantic_enabled=False) @@ -180,7 +183,7 @@ class TestExecuteFindSimilarObjects(unittest.TestCase): _execute_find_similar_objects( req, {"event_id": "anchor", "cameras": ["nonexistent_cam"]}, - allowed_cameras=["nonexistent_cam"], + allowed_cameras=["driveway"], ) ) self.assertEqual(result["results"], []) diff --git a/frigate/test/test_output_ws_auth.py b/frigate/test/test_output_ws_auth.py new file mode 100644 index 0000000000..e5d1d90e20 --- /dev/null +++ b/frigate/test/test_output_ws_auth.py @@ -0,0 +1,59 @@ +"""Tests for JSMPEG websocket authorization.""" + +import unittest +from types import SimpleNamespace + +from frigate.config import FrigateConfig +from frigate.output.ws_auth import ws_has_camera_access + + +class TestWsHasCameraAccess(unittest.TestCase): + def setUp(self): + self.config = FrigateConfig( + mqtt={"host": "mqtt"}, + auth={"roles": {"limited_user": ["front_door"]}}, + cameras={ + "front_door": { + "ffmpeg": { + "inputs": [ + {"path": "rtsp://10.0.0.1:554/video", "roles": ["detect"]} + ] + }, + "detect": {"height": 1080, "width": 1920, "fps": 5}, + }, + "back_door": { + "ffmpeg": { + "inputs": [ + {"path": "rtsp://10.0.0.2:554/video", "roles": ["detect"]} + ] + }, + "detect": {"height": 1080, "width": 1920, "fps": 5}, + }, + }, + ) + + def _make_ws(self, role: str): + return SimpleNamespace(environ={"HTTP_REMOTE_ROLE": role}) + + def test_restricted_role_only_gets_allowed_camera(self): + ws = self._make_ws("limited_user") + self.assertTrue(ws_has_camera_access(ws, "front_door", self.config)) + self.assertFalse(ws_has_camera_access(ws, "back_door", self.config)) + + def test_unrestricted_role_can_access_any_camera(self): + ws = self._make_ws("viewer") + self.assertTrue(ws_has_camera_access(ws, "front_door", self.config)) + self.assertTrue(ws_has_camera_access(ws, "back_door", self.config)) + + def test_birdseye_requires_unrestricted_access(self): + self.assertTrue( + ws_has_camera_access(self._make_ws("admin"), "birdseye", self.config) + ) + self.assertTrue( + ws_has_camera_access(self._make_ws("viewer"), "birdseye", self.config) + ) + self.assertFalse( + ws_has_camera_access( + self._make_ws("limited_user"), "birdseye", self.config + ) + ) \ No newline at end of file diff --git a/frigate/test/test_webpush_camera_monitoring.py b/frigate/test/test_webpush_camera_monitoring.py new file mode 100644 index 0000000000..0eac5114d3 --- /dev/null +++ b/frigate/test/test_webpush_camera_monitoring.py @@ -0,0 +1,29 @@ +"""Tests for camera monitoring notification authorization.""" + +import unittest +from types import SimpleNamespace +from unittest.mock import MagicMock + +from frigate.comms.webpush import WebPushClient + + +class TestCameraMonitoringNotifications(unittest.TestCase): + def test_send_camera_monitoring_filters_by_camera_access(self): + client = WebPushClient.__new__(WebPushClient) + client.config = SimpleNamespace( + cameras={"front_door": SimpleNamespace(friendly_name=None)} + ) + client.web_pushers = {"allowed": [], "denied": []} + client.user_cameras = {"allowed": {"front_door"}, "denied": set()} + client.check_registrations = MagicMock() + client.cleanup_registrations = MagicMock() + client.send_push_notification = MagicMock() + + client.send_camera_monitoring( + {"camera": "front_door", "message": "Monitoring condition met"} + ) + + self.assertEqual(client.send_push_notification.call_count, 1) + self.assertEqual( + client.send_push_notification.call_args.kwargs["user"], "allowed" + ) \ No newline at end of file