diff --git a/frigate/api/review.py b/frigate/api/review.py index e5692f009..9266e1f8c 100644 --- a/frigate/api/review.py +++ b/frigate/api/review.py @@ -490,8 +490,6 @@ def set_not_reviewed(review_id: str): review.save() return JSONResponse( - content=( - {"success": True, "message": "Set Review " + review_id + " as not viewed"} - ), + content=({"success": True, "message": f"Set Review {review_id} as not viewed"}), status_code=200, ) diff --git a/frigate/test/http_api/base_http_test.py b/frigate/test/http_api/base_http_test.py index e7a1d03e8..c16ab9926 100644 --- a/frigate/test/http_api/base_http_test.py +++ b/frigate/test/http_api/base_http_test.py @@ -6,6 +6,7 @@ import unittest from peewee_migrate import Router from playhouse.sqlite_ext import SqliteExtDatabase from playhouse.sqliteq import SqliteQueueDatabase +from pydantic import Json from frigate.api.fastapi_app import create_fastapi_app from frigate.config import FrigateConfig @@ -123,7 +124,12 @@ class BaseTestHttp(unittest.TestCase): def insert_mock_event( self, id: str, - start_time: datetime.datetime = datetime.datetime.now().timestamp(), + start_time: float = datetime.datetime.now().timestamp(), + end_time: float = datetime.datetime.now().timestamp() + 20, + has_clip: bool = True, + top_score: int = 100, + score: int = 0, + data: Json = {}, ) -> Event: """Inserts a basic event model with a given id.""" return Event.insert( @@ -131,16 +137,18 @@ class BaseTestHttp(unittest.TestCase): label="Mock", camera="front_door", start_time=start_time, - end_time=start_time + 20, - top_score=100, + end_time=end_time, + top_score=top_score, + score=score, false_positive=False, zones=list(), thumbnail="", region=[], box=[], area=0, - has_clip=True, + has_clip=has_clip, has_snapshot=True, + data=data, ).execute() def insert_mock_review_segment( @@ -150,6 +158,7 @@ class BaseTestHttp(unittest.TestCase): end_time: float = datetime.datetime.now().timestamp() + 20, severity: SeverityEnum = SeverityEnum.alert, has_been_reviewed: bool = False, + data: Json = {}, ) -> Event: """Inserts a review segment model with a given id.""" return ReviewSegment.insert( @@ -160,7 +169,7 @@ class BaseTestHttp(unittest.TestCase): has_been_reviewed=has_been_reviewed, severity=severity, thumb_path=False, - data={}, + data=data, ).execute() def insert_mock_recording( @@ -168,6 +177,7 @@ class BaseTestHttp(unittest.TestCase): id: str, start_time: float = datetime.datetime.now().timestamp(), end_time: float = datetime.datetime.now().timestamp() + 20, + motion: int = 0, ) -> Event: """Inserts a recording model with a given id.""" return Recordings.insert( @@ -177,4 +187,5 @@ class BaseTestHttp(unittest.TestCase): start_time=start_time, end_time=end_time, duration=end_time - start_time, + motion=motion, ).execute() diff --git a/frigate/test/http_api/test_http_app.py b/frigate/test/http_api/test_http_app.py new file mode 100644 index 000000000..e7785a9d7 --- /dev/null +++ b/frigate/test/http_api/test_http_app.py @@ -0,0 +1,26 @@ +from unittest.mock import Mock + +from fastapi.testclient import TestClient + +from frigate.models import Event, Recordings, ReviewSegment +from frigate.stats.emitter import StatsEmitter +from frigate.test.http_api.base_http_test import BaseTestHttp + + +class TestHttpApp(BaseTestHttp): + def setUp(self): + super().setUp([Event, Recordings, ReviewSegment]) + self.app = super().create_app() + + #################################################################################################################### + ################################### GET /stats Endpoint ######################################################### + #################################################################################################################### + def test_stats_endpoint(self): + stats = Mock(spec=StatsEmitter) + stats.get_latest_stats.return_value = self.test_stats + app = super().create_app(stats) + + with TestClient(app) as client: + response = client.get("/stats") + response_json = response.json() + assert response_json == self.test_stats diff --git a/frigate/test/http_api/test_http_event.py b/frigate/test/http_api/test_http_event.py new file mode 100644 index 000000000..e3f41fdc3 --- /dev/null +++ b/frigate/test/http_api/test_http_event.py @@ -0,0 +1,137 @@ +from datetime import datetime + +from fastapi.testclient import TestClient + +from frigate.models import Event, Recordings, ReviewSegment +from frigate.test.http_api.base_http_test import BaseTestHttp + + +class TestHttpApp(BaseTestHttp): + def setUp(self): + super().setUp([Event, Recordings, ReviewSegment]) + self.app = super().create_app() + + #################################################################################################################### + ################################### GET /events Endpoint ######################################################### + #################################################################################################################### + def test_get_event_list_no_events(self): + with TestClient(self.app) as client: + events = client.get("/events").json() + assert len(events) == 0 + + def test_get_event_list_no_match_event_id(self): + id = "123456.random" + with TestClient(self.app) as client: + super().insert_mock_event(id) + events = client.get("/events", params={"event_id": "abc"}).json() + assert len(events) == 0 + + def test_get_event_list_match_event_id(self): + id = "123456.random" + with TestClient(self.app) as client: + super().insert_mock_event(id) + events = client.get("/events", params={"event_id": id}).json() + assert len(events) == 1 + assert events[0]["id"] == id + + def test_get_event_list_match_length(self): + now = int(datetime.now().timestamp()) + + id = "123456.random" + with TestClient(self.app) as client: + super().insert_mock_event(id, now, now + 1) + events = client.get( + "/events", params={"max_length": 1, "min_length": 1} + ).json() + assert len(events) == 1 + assert events[0]["id"] == id + + def test_get_event_list_no_match_max_length(self): + now = int(datetime.now().timestamp()) + + with TestClient(self.app) as client: + id = "123456.random" + super().insert_mock_event(id, now, now + 2) + events = client.get("/events", params={"max_length": 1}).json() + assert len(events) == 0 + + def test_get_event_list_no_match_min_length(self): + now = int(datetime.now().timestamp()) + + with TestClient(self.app) as client: + id = "123456.random" + super().insert_mock_event(id, now, now + 2) + events = client.get("/events", params={"min_length": 3}).json() + assert len(events) == 0 + + def test_get_event_list_limit(self): + id = "123456.random" + id2 = "54321.random" + + with TestClient(self.app) as client: + super().insert_mock_event(id) + events = client.get("/events").json() + assert len(events) == 1 + assert events[0]["id"] == id + + super().insert_mock_event(id2) + events = client.get("/events").json() + assert len(events) == 2 + + events = client.get("/events", params={"limit": 1}).json() + assert len(events) == 1 + assert events[0]["id"] == id + + events = client.get("/events", params={"limit": 3}).json() + assert len(events) == 2 + + def test_get_event_list_no_match_has_clip(self): + now = int(datetime.now().timestamp()) + + with TestClient(self.app) as client: + id = "123456.random" + super().insert_mock_event(id, now, now + 2) + events = client.get("/events", params={"has_clip": 0}).json() + assert len(events) == 0 + + def test_get_event_list_has_clip(self): + with TestClient(self.app) as client: + id = "123456.random" + super().insert_mock_event(id, has_clip=True) + events = client.get("/events", params={"has_clip": 1}).json() + assert len(events) == 1 + assert events[0]["id"] == id + + def test_get_event_list_sort_score(self): + with TestClient(self.app) as client: + id = "123456.random" + id2 = "54321.random" + super().insert_mock_event(id, top_score=37, score=37, data={"score": 50}) + super().insert_mock_event(id2, top_score=47, score=47, data={"score": 20}) + events = client.get("/events", params={"sort": "score_asc"}).json() + assert len(events) == 2 + assert events[0]["id"] == id2 + assert events[1]["id"] == id + + events = client.get("/events", params={"sort": "score_des"}).json() + assert len(events) == 2 + assert events[0]["id"] == id + assert events[1]["id"] == id2 + + def test_get_event_list_sort_start_time(self): + now = int(datetime.now().timestamp()) + + with TestClient(self.app) as client: + id = "123456.random" + id2 = "54321.random" + super().insert_mock_event(id, start_time=now + 3) + super().insert_mock_event(id2, start_time=now) + events = client.get("/events", params={"sort": "date_asc"}).json() + assert len(events) == 2 + assert events[0]["id"] == id2 + assert events[1]["id"] == id + + events = client.get("/events", params={"sort": "date_desc"}).json() + assert len(events) == 2 + assert events[0]["id"] == id + assert events[1]["id"] == id2 diff --git a/frigate/test/http_api/test_http_review.py b/frigate/test/http_api/test_http_review.py index 11bd33495..c8f2b1719 100644 --- a/frigate/test/http_api/test_http_review.py +++ b/frigate/test/http_api/test_http_review.py @@ -569,3 +569,177 @@ class TestHttpReview(BaseTestHttp): recording_ids_in_db_after = self._get_recordings(ids) assert len(review_ids_in_db_after) == 0 assert len(recording_ids_in_db_after) == 0 + + #################################################################################################################### + ################################### GET /review/activity/motion Endpoint ######################################## + #################################################################################################################### + def test_review_activity_motion_no_data_for_time_range(self): + now = datetime.now().timestamp() + + with TestClient(self.app) as client: + params = { + "after": now, + "before": now + 3, + } + response = client.get("/review/activity/motion", params=params) + assert response.status_code == 200 + response_json = response.json() + assert len(response_json) == 0 + + def test_review_activity_motion(self): + now = int(datetime.now().timestamp()) + + with TestClient(self.app) as client: + one_m = int((datetime.now() + timedelta(minutes=1)).timestamp()) + id = "123456.random" + id2 = "123451.random" + super().insert_mock_recording(id, now + 1, now + 2, motion=101) + super().insert_mock_recording(id2, one_m + 1, one_m + 2, motion=200) + params = { + "after": now, + "before": one_m + 3, + "scale": 1, + } + response = client.get("/review/activity/motion", params=params) + assert response.status_code == 200 + response_json = response.json() + assert len(response_json) == 61 + self.assertDictEqual( + {"motion": 50.5, "camera": "front_door", "start_time": now + 1}, + response_json[0], + ) + for item in response_json[1:-1]: + self.assertDictEqual( + {"motion": 0.0, "camera": "", "start_time": item["start_time"]}, + item, + ) + self.assertDictEqual( + {"motion": 100.0, "camera": "front_door", "start_time": one_m + 1}, + response_json[len(response_json) - 1], + ) + + #################################################################################################################### + ################################### GET /review/event/{event_id} Endpoint ####################################### + #################################################################################################################### + def test_review_event_not_found(self): + with TestClient(self.app) as client: + response = client.get("/review/event/123456.random") + assert response.status_code == 404 + response_json = response.json() + self.assertDictEqual( + {"success": False, "message": "Review item not found"}, + response_json, + ) + + def test_review_event_not_found_in_data(self): + now = datetime.now().timestamp() + + with TestClient(self.app) as client: + id = "123456.random" + super().insert_mock_review_segment(id, now + 1, now + 2) + response = client.get(f"/review/event/{id}") + assert response.status_code == 404 + response_json = response.json() + self.assertDictEqual( + {"success": False, "message": "Review item not found"}, + response_json, + ) + + def test_review_get_specific_event(self): + now = datetime.now().timestamp() + + with TestClient(self.app) as client: + event_id = "123456.event.random" + super().insert_mock_event(event_id) + review_id = "123456.review.random" + super().insert_mock_review_segment( + review_id, now + 1, now + 2, data={"detections": {"event_id": event_id}} + ) + response = client.get(f"/review/event/{event_id}") + assert response.status_code == 200 + response_json = response.json() + self.assertDictEqual( + { + "id": review_id, + "camera": "front_door", + "start_time": now + 1, + "end_time": now + 2, + "has_been_reviewed": False, + "severity": SeverityEnum.alert, + "thumb_path": "False", + "data": {"detections": {"event_id": event_id}}, + }, + response_json, + ) + + #################################################################################################################### + ################################### GET /review/{review_id} Endpoint ####################################### + #################################################################################################################### + def test_review_not_found(self): + with TestClient(self.app) as client: + response = client.get("/review/123456.random") + assert response.status_code == 404 + response_json = response.json() + self.assertDictEqual( + {"success": False, "message": "Review item not found"}, + response_json, + ) + + def test_get_review(self): + now = datetime.now().timestamp() + + with TestClient(self.app) as client: + review_id = "123456.review.random" + super().insert_mock_review_segment(review_id, now + 1, now + 2) + response = client.get(f"/review/{review_id}") + assert response.status_code == 200 + response_json = response.json() + self.assertDictEqual( + { + "id": review_id, + "camera": "front_door", + "start_time": now + 1, + "end_time": now + 2, + "has_been_reviewed": False, + "severity": SeverityEnum.alert, + "thumb_path": "False", + "data": {}, + }, + response_json, + ) + + #################################################################################################################### + ################################### DELETE /review/{review_id}/viewed Endpoint ################################## + #################################################################################################################### + def test_delete_review_viewed_review_not_found(self): + with TestClient(self.app) as client: + review_id = "123456.random" + response = client.delete(f"/review/{review_id}/viewed") + assert response.status_code == 404 + response_json = response.json() + self.assertDictEqual( + {"success": False, "message": f"Review {review_id} not found"}, + response_json, + ) + + def test_delete_review_viewed(self): + now = datetime.now().timestamp() + + with TestClient(self.app) as client: + review_id = "123456.review.random" + super().insert_mock_review_segment( + review_id, now + 1, now + 2, has_been_reviewed=True + ) + review_before = ReviewSegment.get(ReviewSegment.id == review_id) + assert review_before.has_been_reviewed == True + + response = client.delete(f"/review/{review_id}/viewed") + assert response.status_code == 200 + response_json = response.json() + self.assertDictEqual( + {"success": True, "message": f"Set Review {review_id} as not viewed"}, + response_json, + ) + + review_after = ReviewSegment.get(ReviewSegment.id == review_id) + assert review_after.has_been_reviewed == False diff --git a/frigate/test/test_http.py b/frigate/test/test_http.py index 213794259..8c89e0433 100644 --- a/frigate/test/test_http.py +++ b/frigate/test/test_http.py @@ -2,7 +2,6 @@ import datetime import logging import os import unittest -from unittest.mock import Mock from fastapi.testclient import TestClient from peewee_migrate import Router @@ -13,7 +12,6 @@ from playhouse.sqliteq import SqliteQueueDatabase from frigate.api.fastapi_app import create_fastapi_app from frigate.config import FrigateConfig from frigate.models import Event, Recordings, Timeline -from frigate.stats.emitter import StatsEmitter from frigate.test.const import TEST_DB, TEST_DB_CLEANUPS @@ -111,43 +109,6 @@ class TestHttp(unittest.TestCase): except OSError: pass - def test_get_event_list(self): - app = create_fastapi_app( - FrigateConfig(**self.minimal_config), - self.db, - None, - None, - None, - None, - None, - None, - None, - ) - id = "123456.random" - id2 = "7890.random" - - with TestClient(app) as client: - _insert_mock_event(id) - events = client.get("/events").json() - assert events - assert len(events) == 1 - assert events[0]["id"] == id - _insert_mock_event(id2) - events = client.get("/events").json() - assert events - assert len(events) == 2 - events = client.get( - "/events", - params={"limit": 1}, - ).json() - assert events - assert len(events) == 1 - events = client.get( - "/events", - params={"has_clip": 0}, - ).json() - assert not events - def test_get_good_event(self): app = create_fastapi_app( FrigateConfig(**self.minimal_config), @@ -381,25 +342,6 @@ class TestHttp(unittest.TestCase): assert recording assert recording[0]["id"] == id - def test_stats(self): - stats = Mock(spec=StatsEmitter) - stats.get_latest_stats.return_value = self.test_stats - app = create_fastapi_app( - FrigateConfig(**self.minimal_config), - self.db, - None, - None, - None, - None, - None, - stats, - None, - ) - - with TestClient(app) as client: - full_stats = client.get("/stats").json() - assert full_stats == self.test_stats - def _insert_mock_event( id: str,