diff --git a/.cspell/frigate-dictionary.txt b/.cspell/frigate-dictionary.txt index b019f8492..64fd7ca72 100644 --- a/.cspell/frigate-dictionary.txt +++ b/.cspell/frigate-dictionary.txt @@ -12,6 +12,7 @@ argmax argmin argpartition ascontiguousarray +astype authelia authentik autodetected @@ -195,6 +196,7 @@ poweroff preexec probesize protobuf +pstate psutil pubkey putenv @@ -278,6 +280,7 @@ uvicorn vaapi vainfo variations +vbios vconcat vitb vstream diff --git a/frigate/test/http_api/__init__.py b/frigate/test/http_api/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/frigate/test/http_api/base_http_test.py b/frigate/test/http_api/base_http_test.py new file mode 100644 index 000000000..013785692 --- /dev/null +++ b/frigate/test/http_api/base_http_test.py @@ -0,0 +1,162 @@ +import datetime +import logging +import os +import unittest + +from peewee_migrate import Router +from playhouse.sqlite_ext import SqliteExtDatabase +from playhouse.sqliteq import SqliteQueueDatabase + +from frigate.api.fastapi_app import create_fastapi_app +from frigate.config import FrigateConfig +from frigate.models import Event, ReviewSegment +from frigate.review.maintainer import SeverityEnum +from frigate.test.const import TEST_DB, TEST_DB_CLEANUPS + + +class BaseTestHttp(unittest.TestCase): + def setUp(self, models): + # setup clean database for each test run + migrate_db = SqliteExtDatabase("test.db") + del logging.getLogger("peewee_migrate").handlers[:] + router = Router(migrate_db) + router.run() + migrate_db.close() + self.db = SqliteQueueDatabase(TEST_DB) + self.db.bind(models) + + self.minimal_config = { + "mqtt": {"host": "mqtt"}, + "cameras": { + "front_door": { + "ffmpeg": { + "inputs": [ + {"path": "rtsp://10.0.0.1:554/video", "roles": ["detect"]} + ] + }, + "detect": { + "height": 1080, + "width": 1920, + "fps": 5, + }, + } + }, + } + self.test_stats = { + "detection_fps": 13.7, + "detectors": { + "cpu1": { + "detection_start": 0.0, + "inference_speed": 91.43, + "pid": 42, + }, + "cpu2": { + "detection_start": 0.0, + "inference_speed": 84.99, + "pid": 44, + }, + }, + "front_door": { + "camera_fps": 0.0, + "capture_pid": 53, + "detection_fps": 0.0, + "pid": 52, + "process_fps": 0.0, + "skipped_fps": 0.0, + }, + "service": { + "storage": { + "/dev/shm": { + "free": 50.5, + "mount_type": "tmpfs", + "total": 67.1, + "used": 16.6, + }, + "/media/frigate/clips": { + "free": 42429.9, + "mount_type": "ext4", + "total": 244529.7, + "used": 189607.0, + }, + "/media/frigate/recordings": { + "free": 0.2, + "mount_type": "ext4", + "total": 8.0, + "used": 7.8, + }, + "/tmp/cache": { + "free": 976.8, + "mount_type": "tmpfs", + "total": 1000.0, + "used": 23.2, + }, + }, + "uptime": 101113, + "version": "0.10.1", + "latest_version": "0.11", + }, + } + + def tearDown(self): + if not self.db.is_closed(): + self.db.close() + + try: + for file in TEST_DB_CLEANUPS: + os.remove(file) + except OSError: + pass + + def create_app(self, stats=None): + return create_fastapi_app( + FrigateConfig(**self.minimal_config), + self.db, + None, + None, + None, + None, + None, + stats, + None, + ) + + def insert_mock_event( + self, + id: str, + start_time: datetime.datetime = datetime.datetime.now().timestamp(), + ) -> Event: + """Inserts a basic event model with a given id.""" + return Event.insert( + id=id, + label="Mock", + camera="front_door", + start_time=start_time, + end_time=start_time + 20, + top_score=100, + false_positive=False, + zones=list(), + thumbnail="", + region=[], + box=[], + area=0, + has_clip=True, + has_snapshot=True, + ).execute() + + def insert_mock_review_segment( + self, + id: str, + start_time: datetime.datetime = datetime.datetime.now().timestamp(), + end_time: datetime.datetime = datetime.datetime.now().timestamp() + 20, + ) -> Event: + """Inserts a basic event model with a given id.""" + return ReviewSegment.insert( + id=id, + camera="front_door", + start_time=start_time, + end_time=end_time, + has_been_reviewed=False, + severity=SeverityEnum.alert, + thumb_path=False, + data={}, + ).execute() diff --git a/frigate/test/http_api/test_http_review.py b/frigate/test/http_api/test_http_review.py new file mode 100644 index 000000000..19e1f26f8 --- /dev/null +++ b/frigate/test/http_api/test_http_review.py @@ -0,0 +1,110 @@ +import datetime + +from fastapi.testclient import TestClient + +from frigate.models import Event, ReviewSegment +from frigate.test.http_api.base_http_test import BaseTestHttp + + +class TestHttpReview(BaseTestHttp): + def setUp(self): + super().setUp([Event, ReviewSegment]) + + # Does not return any data point since the end time (before parameter) is not passed and the review segment end_time is 2 seconds from now + def test_get_review_no_filters_no_matches(self): + app = super().create_app() + now = datetime.datetime.now().timestamp() + + with TestClient(app) as client: + super().insert_mock_review_segment("123456.random", now, now + 2) + reviews_response = client.get("/review") + assert reviews_response.status_code == 200 + reviews_in_response = reviews_response.json() + assert len(reviews_in_response) == 0 + + def test_get_review_no_filters(self): + app = super().create_app() + now = datetime.datetime.now().timestamp() + + with TestClient(app) as client: + super().insert_mock_review_segment("123456.random", now - 2, now - 1) + reviews_response = client.get("/review") + assert reviews_response.status_code == 200 + reviews_in_response = reviews_response.json() + assert len(reviews_in_response) == 1 + + def test_get_review_with_time_filter_no_matches(self): + app = super().create_app() + now = datetime.datetime.now().timestamp() + + with TestClient(app) as client: + id = "123456.random" + super().insert_mock_review_segment(id, now, now + 2) + params = { + "after": now, + "before": now + 3, + } + reviews_response = client.get("/review", params=params) + assert reviews_response.status_code == 200 + reviews_in_response = reviews_response.json() + assert len(reviews_in_response) == 0 + + def test_get_review_with_time_filter(self): + app = super().create_app() + now = datetime.datetime.now().timestamp() + + with TestClient(app) as client: + id = "123456.random" + super().insert_mock_review_segment(id, now, now + 2) + params = { + "after": now - 1, + "before": now + 3, + } + reviews_response = client.get("/review", params=params) + assert reviews_response.status_code == 200 + reviews_in_response = reviews_response.json() + assert len(reviews_in_response) == 1 + assert reviews_in_response[0]["id"] == id + + def test_get_review_with_limit_filter(self): + app = super().create_app() + now = datetime.datetime.now().timestamp() + + with TestClient(app) as client: + id = "123456.random" + id2 = "654321.random" + super().insert_mock_review_segment(id, now, now + 2) + super().insert_mock_review_segment(id2, now + 1, now + 2) + params = { + "limit": 1, + "after": now, + "before": now + 3, + } + reviews_response = client.get("/review", params=params) + assert reviews_response.status_code == 200 + reviews_in_response = reviews_response.json() + assert len(reviews_in_response) == 1 + assert reviews_in_response[0]["id"] == id2 + + def test_get_review_with_all_filters(self): + app = super().create_app() + now = datetime.datetime.now().timestamp() + + with TestClient(app) as client: + id = "123456.random" + super().insert_mock_review_segment(id, now, now + 2) + params = { + "cameras": "front_door", + "labels": "all", + "zones": "all", + "reviewed": 0, + "limit": 1, + "severity": "alert", + "after": now - 1, + "before": now + 3, + } + reviews_response = client.get("/review", params=params) + assert reviews_response.status_code == 200 + reviews_in_response = reviews_response.json() + assert len(reviews_in_response) == 1 + assert reviews_in_response[0]["id"] == id