2024-12-04 12:52:08 +00:00
|
|
|
|
from datetime import datetime, timedelta
|
2024-11-19 23:35:10 +00:00
|
|
|
|
|
2025-11-26 15:07:28 -06:00
|
|
|
|
from fastapi import Request
|
2025-03-13 15:20:09 -05:00
|
|
|
|
from peewee import DoesNotExist
|
2024-11-19 23:35:10 +00:00
|
|
|
|
|
2025-09-12 06:19:29 -05:00
|
|
|
|
from frigate.api.auth import get_allowed_cameras_for_filter, get_current_user
|
2025-03-13 15:20:09 -05:00
|
|
|
|
from frigate.models import Event, Recordings, ReviewSegment, UserReviewStatus
|
2024-12-09 09:25:45 -06:00
|
|
|
|
from frigate.review.types import SeverityEnum
|
2025-11-26 15:07:28 -06:00
|
|
|
|
from frigate.test.http_api.base_http_test import AuthTestClient, BaseTestHttp
|
2024-11-19 23:35:10 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestHttpReview(BaseTestHttp):
|
|
|
|
|
|
def setUp(self):
|
2025-03-13 15:20:09 -05:00
|
|
|
|
super().setUp([Event, Recordings, ReviewSegment, UserReviewStatus])
|
2024-12-04 12:52:08 +00:00
|
|
|
|
self.app = super().create_app()
|
2025-03-13 15:20:09 -05:00
|
|
|
|
self.user_id = "admin"
|
|
|
|
|
|
|
|
|
|
|
|
# Mock get_current_user for all tests
|
2025-11-26 15:07:28 -06:00
|
|
|
|
# This mock uses headers set by AuthTestClient
|
|
|
|
|
|
async def mock_get_current_user(request: Request):
|
|
|
|
|
|
username = request.headers.get("remote-user")
|
|
|
|
|
|
role = request.headers.get("remote-role")
|
|
|
|
|
|
if not username or not role:
|
|
|
|
|
|
from fastapi.responses import JSONResponse
|
|
|
|
|
|
|
|
|
|
|
|
return JSONResponse(
|
|
|
|
|
|
content={"message": "No authorization headers."}, status_code=401
|
|
|
|
|
|
)
|
|
|
|
|
|
return {"username": username, "role": role}
|
2025-03-13 15:20:09 -05:00
|
|
|
|
|
|
|
|
|
|
self.app.dependency_overrides[get_current_user] = mock_get_current_user
|
|
|
|
|
|
|
2025-11-26 15:07:28 -06:00
|
|
|
|
async def mock_get_allowed_cameras_for_filter(request: Request):
|
|
|
|
|
|
return ["front_door"]
|
|
|
|
|
|
|
|
|
|
|
|
self.app.dependency_overrides[get_allowed_cameras_for_filter] = (
|
|
|
|
|
|
mock_get_allowed_cameras_for_filter
|
|
|
|
|
|
)
|
2025-09-12 06:19:29 -05:00
|
|
|
|
|
2025-03-13 15:20:09 -05:00
|
|
|
|
def tearDown(self):
|
|
|
|
|
|
self.app.dependency_overrides.clear()
|
|
|
|
|
|
super().tearDown()
|
2024-12-04 12:52:08 +00:00
|
|
|
|
|
|
|
|
|
|
def _get_reviews(self, ids: list[str]):
|
|
|
|
|
|
return list(
|
|
|
|
|
|
ReviewSegment.select(ReviewSegment.id)
|
|
|
|
|
|
.where(ReviewSegment.id.in_(ids))
|
|
|
|
|
|
.execute()
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def _get_recordings(self, ids: list[str]):
|
|
|
|
|
|
return list(
|
|
|
|
|
|
Recordings.select(Recordings.id).where(Recordings.id.in_(ids)).execute()
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-03-13 15:20:09 -05:00
|
|
|
|
def _insert_user_review_status(self, review_id: str, reviewed: bool = True):
|
|
|
|
|
|
UserReviewStatus.create(
|
|
|
|
|
|
user_id=self.user_id,
|
|
|
|
|
|
review_segment=ReviewSegment.get(ReviewSegment.id == review_id),
|
|
|
|
|
|
has_been_reviewed=reviewed,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2024-12-04 12:52:08 +00:00
|
|
|
|
####################################################################################################################
|
|
|
|
|
|
################################### GET /review Endpoint ########################################################
|
|
|
|
|
|
####################################################################################################################
|
2024-11-19 23:35:10 +00:00
|
|
|
|
|
2025-06-26 13:01:09 -06:00
|
|
|
|
def test_get_review_that_overlaps_default_period(self):
|
|
|
|
|
|
"""Test that a review item that starts during the default period
|
|
|
|
|
|
but ends after is included in the results."""
|
2024-12-04 12:52:08 +00:00
|
|
|
|
now = datetime.now().timestamp()
|
2024-11-19 23:35:10 +00:00
|
|
|
|
|
2025-11-26 15:07:28 -06:00
|
|
|
|
with AuthTestClient(self.app) as client:
|
2024-11-19 23:35:10 +00:00
|
|
|
|
super().insert_mock_review_segment("123456.random", now, now + 2)
|
2024-12-04 12:52:08 +00:00
|
|
|
|
response = client.get("/review")
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
response_json = response.json()
|
2025-06-26 13:01:09 -06:00
|
|
|
|
assert len(response_json) == 1
|
2024-11-19 23:35:10 +00:00
|
|
|
|
|
|
|
|
|
|
def test_get_review_no_filters(self):
|
2024-12-04 12:52:08 +00:00
|
|
|
|
now = datetime.now().timestamp()
|
2024-11-19 23:35:10 +00:00
|
|
|
|
|
2025-11-26 15:07:28 -06:00
|
|
|
|
with AuthTestClient(self.app) as client:
|
2025-03-13 15:20:09 -05:00
|
|
|
|
id = "123456.random"
|
|
|
|
|
|
super().insert_mock_review_segment(id, now - 2, now - 1)
|
2024-12-04 12:52:08 +00:00
|
|
|
|
response = client.get("/review")
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
response_json = response.json()
|
|
|
|
|
|
assert len(response_json) == 1
|
2025-03-13 15:20:09 -05:00
|
|
|
|
assert response_json[0]["id"] == id
|
|
|
|
|
|
assert response_json[0]["has_been_reviewed"] == False
|
2024-11-19 23:35:10 +00:00
|
|
|
|
|
|
|
|
|
|
def test_get_review_with_time_filter_no_matches(self):
|
2025-06-26 13:01:09 -06:00
|
|
|
|
"""Test that review items outside the range are not returned."""
|
2024-12-04 12:52:08 +00:00
|
|
|
|
now = datetime.now().timestamp()
|
2024-11-19 23:35:10 +00:00
|
|
|
|
|
2025-11-26 15:07:28 -06:00
|
|
|
|
with AuthTestClient(self.app) as client:
|
2024-11-19 23:35:10 +00:00
|
|
|
|
id = "123456.random"
|
2025-06-26 13:01:09 -06:00
|
|
|
|
super().insert_mock_review_segment(id, now - 2, now - 1)
|
|
|
|
|
|
super().insert_mock_review_segment(f"{id}2", now + 4, now + 5)
|
2024-11-19 23:35:10 +00:00
|
|
|
|
params = {
|
|
|
|
|
|
"after": now,
|
|
|
|
|
|
"before": now + 3,
|
|
|
|
|
|
}
|
2024-12-04 12:52:08 +00:00
|
|
|
|
response = client.get("/review", params=params)
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
response_json = response.json()
|
|
|
|
|
|
assert len(response_json) == 0
|
2024-11-19 23:35:10 +00:00
|
|
|
|
|
|
|
|
|
|
def test_get_review_with_time_filter(self):
|
2024-12-04 12:52:08 +00:00
|
|
|
|
now = datetime.now().timestamp()
|
2024-11-19 23:35:10 +00:00
|
|
|
|
|
2025-11-26 15:07:28 -06:00
|
|
|
|
with AuthTestClient(self.app) as client:
|
2024-11-19 23:35:10 +00:00
|
|
|
|
id = "123456.random"
|
|
|
|
|
|
super().insert_mock_review_segment(id, now, now + 2)
|
|
|
|
|
|
params = {
|
|
|
|
|
|
"after": now - 1,
|
|
|
|
|
|
"before": now + 3,
|
|
|
|
|
|
}
|
2024-12-04 12:52:08 +00:00
|
|
|
|
response = client.get("/review", params=params)
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
response_json = response.json()
|
|
|
|
|
|
assert len(response_json) == 1
|
|
|
|
|
|
assert response_json[0]["id"] == id
|
2024-11-19 23:35:10 +00:00
|
|
|
|
|
|
|
|
|
|
def test_get_review_with_limit_filter(self):
|
2024-12-04 12:52:08 +00:00
|
|
|
|
now = datetime.now().timestamp()
|
2024-11-19 23:35:10 +00:00
|
|
|
|
|
2025-11-26 15:07:28 -06:00
|
|
|
|
with AuthTestClient(self.app) as client:
|
2024-11-19 23:35:10 +00:00
|
|
|
|
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,
|
|
|
|
|
|
}
|
2024-12-04 12:52:08 +00:00
|
|
|
|
response = client.get("/review", params=params)
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
response_json = response.json()
|
|
|
|
|
|
assert len(response_json) == 1
|
|
|
|
|
|
assert response_json[0]["id"] == id2
|
|
|
|
|
|
|
|
|
|
|
|
def test_get_review_with_severity_filters_no_matches(self):
|
|
|
|
|
|
now = datetime.now().timestamp()
|
|
|
|
|
|
|
2025-11-26 15:07:28 -06:00
|
|
|
|
with AuthTestClient(self.app) as client:
|
2024-12-04 12:52:08 +00:00
|
|
|
|
id = "123456.random"
|
|
|
|
|
|
super().insert_mock_review_segment(id, now, now + 2, SeverityEnum.detection)
|
|
|
|
|
|
params = {
|
|
|
|
|
|
"severity": "detection",
|
|
|
|
|
|
"after": now - 1,
|
|
|
|
|
|
"before": now + 3,
|
|
|
|
|
|
}
|
|
|
|
|
|
response = client.get("/review", params=params)
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
response_json = response.json()
|
|
|
|
|
|
assert len(response_json) == 1
|
|
|
|
|
|
assert response_json[0]["id"] == id
|
|
|
|
|
|
|
|
|
|
|
|
def test_get_review_with_severity_filters(self):
|
|
|
|
|
|
now = datetime.now().timestamp()
|
|
|
|
|
|
|
2025-11-26 15:07:28 -06:00
|
|
|
|
with AuthTestClient(self.app) as client:
|
2024-12-04 12:52:08 +00:00
|
|
|
|
id = "123456.random"
|
|
|
|
|
|
super().insert_mock_review_segment(id, now, now + 2, SeverityEnum.detection)
|
|
|
|
|
|
params = {
|
|
|
|
|
|
"severity": "alert",
|
|
|
|
|
|
"after": now - 1,
|
|
|
|
|
|
"before": now + 3,
|
|
|
|
|
|
}
|
|
|
|
|
|
response = client.get("/review", params=params)
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
response_json = response.json()
|
|
|
|
|
|
assert len(response_json) == 0
|
2024-11-19 23:35:10 +00:00
|
|
|
|
|
|
|
|
|
|
def test_get_review_with_all_filters(self):
|
2024-12-04 12:52:08 +00:00
|
|
|
|
now = datetime.now().timestamp()
|
2024-11-19 23:35:10 +00:00
|
|
|
|
|
2025-11-26 15:07:28 -06:00
|
|
|
|
with AuthTestClient(self.app) as client:
|
2024-11-19 23:35:10 +00:00
|
|
|
|
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,
|
|
|
|
|
|
}
|
2024-12-04 12:52:08 +00:00
|
|
|
|
response = client.get("/review", params=params)
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
response_json = response.json()
|
|
|
|
|
|
assert len(response_json) == 1
|
|
|
|
|
|
assert response_json[0]["id"] == id
|
|
|
|
|
|
|
2026-01-11 05:42:33 -08:00
|
|
|
|
def test_get_review_with_reviewed_filter_unreviewed(self):
|
|
|
|
|
|
"""Test that reviewed=0 returns only unreviewed items."""
|
|
|
|
|
|
now = datetime.now().timestamp()
|
|
|
|
|
|
|
|
|
|
|
|
with AuthTestClient(self.app) as client:
|
|
|
|
|
|
id_unreviewed = "123456.unreviewed"
|
|
|
|
|
|
id_reviewed = "123456.reviewed"
|
|
|
|
|
|
super().insert_mock_review_segment(id_unreviewed, now, now + 2)
|
|
|
|
|
|
super().insert_mock_review_segment(id_reviewed, now, now + 2)
|
|
|
|
|
|
self._insert_user_review_status(id_reviewed, reviewed=True)
|
|
|
|
|
|
|
|
|
|
|
|
params = {
|
|
|
|
|
|
"reviewed": 0,
|
|
|
|
|
|
"after": now - 1,
|
|
|
|
|
|
"before": now + 3,
|
|
|
|
|
|
}
|
|
|
|
|
|
response = client.get("/review", params=params)
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
response_json = response.json()
|
|
|
|
|
|
assert len(response_json) == 1
|
|
|
|
|
|
assert response_json[0]["id"] == id_unreviewed
|
|
|
|
|
|
|
|
|
|
|
|
def test_get_review_with_reviewed_filter_reviewed(self):
|
|
|
|
|
|
"""Test that reviewed=1 returns only reviewed items."""
|
|
|
|
|
|
now = datetime.now().timestamp()
|
|
|
|
|
|
|
|
|
|
|
|
with AuthTestClient(self.app) as client:
|
|
|
|
|
|
id_unreviewed = "123456.unreviewed"
|
|
|
|
|
|
id_reviewed = "123456.reviewed"
|
|
|
|
|
|
super().insert_mock_review_segment(id_unreviewed, now, now + 2)
|
|
|
|
|
|
super().insert_mock_review_segment(id_reviewed, now, now + 2)
|
|
|
|
|
|
self._insert_user_review_status(id_reviewed, reviewed=True)
|
|
|
|
|
|
|
|
|
|
|
|
params = {
|
|
|
|
|
|
"reviewed": 1,
|
|
|
|
|
|
"after": now - 1,
|
|
|
|
|
|
"before": now + 3,
|
|
|
|
|
|
}
|
|
|
|
|
|
response = client.get("/review", params=params)
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
response_json = response.json()
|
|
|
|
|
|
assert len(response_json) == 1
|
|
|
|
|
|
assert response_json[0]["id"] == id_reviewed
|
|
|
|
|
|
|
2026-09-17 10:54:54 -06:00
|
|
|
|
def test_get_review_with_label_filter_matches_verified(self):
|
|
|
|
|
|
"""Test that a label filter also matches the `-verified` variant."""
|
|
|
|
|
|
now = datetime.now().timestamp()
|
|
|
|
|
|
|
|
|
|
|
|
with AuthTestClient(self.app) as client:
|
|
|
|
|
|
super().insert_mock_review_segment(
|
|
|
|
|
|
"123456.person", now, now + 2, data={"objects": ["person"]}
|
|
|
|
|
|
)
|
|
|
|
|
|
super().insert_mock_review_segment(
|
|
|
|
|
|
"123456.verified", now, now + 2, data={"objects": ["person-verified"]}
|
|
|
|
|
|
)
|
|
|
|
|
|
super().insert_mock_review_segment(
|
|
|
|
|
|
"123456.car", now, now + 2, data={"objects": ["car"]}
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
params = {
|
|
|
|
|
|
"labels": "person",
|
|
|
|
|
|
"after": now - 1,
|
|
|
|
|
|
"before": now + 3,
|
|
|
|
|
|
}
|
|
|
|
|
|
response = client.get("/review", params=params)
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
response_json = response.json()
|
|
|
|
|
|
assert {r["id"] for r in response_json} == {
|
|
|
|
|
|
"123456.person",
|
|
|
|
|
|
"123456.verified",
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
def test_get_review_with_label_filter_does_not_match_prefix(self):
|
|
|
|
|
|
"""Test that a label filter does not match labels that only share a prefix."""
|
|
|
|
|
|
now = datetime.now().timestamp()
|
|
|
|
|
|
|
|
|
|
|
|
with AuthTestClient(self.app) as client:
|
|
|
|
|
|
super().insert_mock_review_segment(
|
|
|
|
|
|
"123456.carrot", now, now + 2, data={"objects": ["carrot"]}
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
params = {
|
|
|
|
|
|
"labels": "car",
|
|
|
|
|
|
"after": now - 1,
|
|
|
|
|
|
"before": now + 3,
|
|
|
|
|
|
}
|
|
|
|
|
|
response = client.get("/review", params=params)
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
assert len(response.json()) == 0
|
|
|
|
|
|
|
|
|
|
|
|
def test_get_review_with_audio_label_filter(self):
|
|
|
|
|
|
"""Test that a label filter still matches audio labels."""
|
|
|
|
|
|
now = datetime.now().timestamp()
|
|
|
|
|
|
|
|
|
|
|
|
with AuthTestClient(self.app) as client:
|
|
|
|
|
|
super().insert_mock_review_segment(
|
|
|
|
|
|
"123456.audio", now, now + 2, data={"audio": ["speech"]}
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
params = {
|
|
|
|
|
|
"labels": "speech",
|
|
|
|
|
|
"after": now - 1,
|
|
|
|
|
|
"before": now + 3,
|
|
|
|
|
|
}
|
|
|
|
|
|
response = client.get("/review", params=params)
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
response_json = response.json()
|
|
|
|
|
|
assert len(response_json) == 1
|
|
|
|
|
|
assert response_json[0]["id"] == "123456.audio"
|
|
|
|
|
|
|
2024-12-04 12:52:08 +00:00
|
|
|
|
####################################################################################################################
|
|
|
|
|
|
################################### GET /review/summary Endpoint #################################################
|
|
|
|
|
|
####################################################################################################################
|
2026-09-17 10:54:54 -06:00
|
|
|
|
def test_get_review_summary_label_filter_matches_verified(self):
|
|
|
|
|
|
"""Test that the summary label filter also matches the `-verified` variant."""
|
|
|
|
|
|
with AuthTestClient(self.app) as client:
|
|
|
|
|
|
super().insert_mock_review_segment(
|
|
|
|
|
|
"123456.verified", data={"objects": ["person-verified"]}
|
|
|
|
|
|
)
|
|
|
|
|
|
super().insert_mock_review_segment(
|
|
|
|
|
|
"123456.car", data={"objects": ["car"]}, severity=SeverityEnum.detection
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
params = {
|
|
|
|
|
|
"cameras": "front_door",
|
|
|
|
|
|
"labels": "person",
|
|
|
|
|
|
"zones": "all",
|
|
|
|
|
|
"timezone": "utc",
|
|
|
|
|
|
}
|
|
|
|
|
|
response = client.get("/review/summary", params=params)
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
response_json = response.json()
|
|
|
|
|
|
assert response_json["last24Hours"]["total_alert"] == 1
|
|
|
|
|
|
assert response_json["last24Hours"]["total_detection"] == 0
|
|
|
|
|
|
|
|
|
|
|
|
today_formatted = datetime.today().strftime("%Y-%m-%d")
|
|
|
|
|
|
assert response_json[today_formatted]["total_alert"] == 1
|
|
|
|
|
|
assert response_json[today_formatted]["total_detection"] == 0
|
|
|
|
|
|
|
2024-12-04 12:52:08 +00:00
|
|
|
|
def test_get_review_summary_all_filters(self):
|
2025-11-26 15:07:28 -06:00
|
|
|
|
with AuthTestClient(self.app) as client:
|
2024-12-04 12:52:08 +00:00
|
|
|
|
super().insert_mock_review_segment("123456.random")
|
|
|
|
|
|
params = {
|
|
|
|
|
|
"cameras": "front_door",
|
|
|
|
|
|
"labels": "all",
|
|
|
|
|
|
"zones": "all",
|
|
|
|
|
|
"timezone": "utc",
|
|
|
|
|
|
}
|
|
|
|
|
|
response = client.get("/review/summary", params=params)
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
response_json = response.json()
|
|
|
|
|
|
# e.g. '2024-11-24'
|
|
|
|
|
|
today_formatted = datetime.today().strftime("%Y-%m-%d")
|
|
|
|
|
|
expected_response = {
|
|
|
|
|
|
"last24Hours": {
|
|
|
|
|
|
"reviewed_alert": 0,
|
|
|
|
|
|
"reviewed_detection": 0,
|
|
|
|
|
|
"total_alert": 1,
|
|
|
|
|
|
"total_detection": 0,
|
|
|
|
|
|
},
|
|
|
|
|
|
today_formatted: {
|
|
|
|
|
|
"day": today_formatted,
|
|
|
|
|
|
"reviewed_alert": 0,
|
|
|
|
|
|
"reviewed_detection": 0,
|
|
|
|
|
|
"total_alert": 1,
|
|
|
|
|
|
"total_detection": 0,
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
self.assertEqual(response_json, expected_response)
|
|
|
|
|
|
|
|
|
|
|
|
def test_get_review_summary_no_filters(self):
|
2025-11-26 15:07:28 -06:00
|
|
|
|
with AuthTestClient(self.app) as client:
|
2024-12-04 12:52:08 +00:00
|
|
|
|
super().insert_mock_review_segment("123456.random")
|
|
|
|
|
|
response = client.get("/review/summary")
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
response_json = response.json()
|
|
|
|
|
|
# e.g. '2024-11-24'
|
|
|
|
|
|
today_formatted = datetime.today().strftime("%Y-%m-%d")
|
|
|
|
|
|
expected_response = {
|
|
|
|
|
|
"last24Hours": {
|
|
|
|
|
|
"reviewed_alert": 0,
|
|
|
|
|
|
"reviewed_detection": 0,
|
|
|
|
|
|
"total_alert": 1,
|
|
|
|
|
|
"total_detection": 0,
|
|
|
|
|
|
},
|
|
|
|
|
|
today_formatted: {
|
|
|
|
|
|
"day": today_formatted,
|
|
|
|
|
|
"reviewed_alert": 0,
|
|
|
|
|
|
"reviewed_detection": 0,
|
|
|
|
|
|
"total_alert": 1,
|
|
|
|
|
|
"total_detection": 0,
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
self.assertEqual(response_json, expected_response)
|
|
|
|
|
|
|
|
|
|
|
|
def test_get_review_summary_multiple_days(self):
|
|
|
|
|
|
now = datetime.now()
|
|
|
|
|
|
five_days_ago = datetime.today() - timedelta(days=5)
|
|
|
|
|
|
|
2025-11-26 15:07:28 -06:00
|
|
|
|
with AuthTestClient(self.app) as client:
|
2024-12-04 12:52:08 +00:00
|
|
|
|
super().insert_mock_review_segment(
|
|
|
|
|
|
"123456.random", now.timestamp() - 2, now.timestamp() - 1
|
|
|
|
|
|
)
|
|
|
|
|
|
super().insert_mock_review_segment(
|
|
|
|
|
|
"654321.random",
|
|
|
|
|
|
five_days_ago.timestamp(),
|
|
|
|
|
|
five_days_ago.timestamp() + 1,
|
|
|
|
|
|
)
|
|
|
|
|
|
response = client.get("/review/summary")
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
response_json = response.json()
|
|
|
|
|
|
# e.g. '2024-11-24'
|
|
|
|
|
|
today_formatted = now.strftime("%Y-%m-%d")
|
|
|
|
|
|
# e.g. '2024-11-19'
|
|
|
|
|
|
five_days_ago_formatted = five_days_ago.strftime("%Y-%m-%d")
|
|
|
|
|
|
expected_response = {
|
|
|
|
|
|
"last24Hours": {
|
|
|
|
|
|
"reviewed_alert": 0,
|
|
|
|
|
|
"reviewed_detection": 0,
|
|
|
|
|
|
"total_alert": 1,
|
|
|
|
|
|
"total_detection": 0,
|
|
|
|
|
|
},
|
|
|
|
|
|
today_formatted: {
|
|
|
|
|
|
"day": today_formatted,
|
|
|
|
|
|
"reviewed_alert": 0,
|
|
|
|
|
|
"reviewed_detection": 0,
|
|
|
|
|
|
"total_alert": 1,
|
|
|
|
|
|
"total_detection": 0,
|
|
|
|
|
|
},
|
|
|
|
|
|
five_days_ago_formatted: {
|
|
|
|
|
|
"day": five_days_ago_formatted,
|
|
|
|
|
|
"reviewed_alert": 0,
|
|
|
|
|
|
"reviewed_detection": 0,
|
|
|
|
|
|
"total_alert": 1,
|
|
|
|
|
|
"total_detection": 0,
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
self.assertEqual(response_json, expected_response)
|
|
|
|
|
|
|
|
|
|
|
|
def test_get_review_summary_multiple_in_same_day(self):
|
|
|
|
|
|
now = datetime.now()
|
|
|
|
|
|
five_days_ago = datetime.today() - timedelta(days=5)
|
|
|
|
|
|
|
2025-11-26 15:07:28 -06:00
|
|
|
|
with AuthTestClient(self.app) as client:
|
2024-12-04 12:52:08 +00:00
|
|
|
|
super().insert_mock_review_segment("123456.random", now.timestamp())
|
|
|
|
|
|
five_days_ago_ts = five_days_ago.timestamp()
|
|
|
|
|
|
for i in range(20):
|
|
|
|
|
|
super().insert_mock_review_segment(
|
|
|
|
|
|
f"123456_{i}.random_alert",
|
|
|
|
|
|
five_days_ago_ts,
|
|
|
|
|
|
five_days_ago_ts,
|
|
|
|
|
|
SeverityEnum.alert,
|
|
|
|
|
|
)
|
|
|
|
|
|
for i in range(15):
|
|
|
|
|
|
super().insert_mock_review_segment(
|
|
|
|
|
|
f"123456_{i}.random_detection",
|
|
|
|
|
|
five_days_ago_ts,
|
|
|
|
|
|
five_days_ago_ts,
|
|
|
|
|
|
SeverityEnum.detection,
|
|
|
|
|
|
)
|
|
|
|
|
|
response = client.get("/review/summary")
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
response_json = response.json()
|
|
|
|
|
|
# e.g. '2024-11-24'
|
|
|
|
|
|
today_formatted = now.strftime("%Y-%m-%d")
|
|
|
|
|
|
# e.g. '2024-11-19'
|
|
|
|
|
|
five_days_ago_formatted = five_days_ago.strftime("%Y-%m-%d")
|
|
|
|
|
|
expected_response = {
|
|
|
|
|
|
"last24Hours": {
|
|
|
|
|
|
"reviewed_alert": 0,
|
|
|
|
|
|
"reviewed_detection": 0,
|
|
|
|
|
|
"total_alert": 1,
|
|
|
|
|
|
"total_detection": 0,
|
|
|
|
|
|
},
|
|
|
|
|
|
today_formatted: {
|
|
|
|
|
|
"day": today_formatted,
|
|
|
|
|
|
"reviewed_alert": 0,
|
|
|
|
|
|
"reviewed_detection": 0,
|
|
|
|
|
|
"total_alert": 1,
|
|
|
|
|
|
"total_detection": 0,
|
|
|
|
|
|
},
|
|
|
|
|
|
five_days_ago_formatted: {
|
|
|
|
|
|
"day": five_days_ago_formatted,
|
|
|
|
|
|
"reviewed_alert": 0,
|
|
|
|
|
|
"reviewed_detection": 0,
|
|
|
|
|
|
"total_alert": 20,
|
|
|
|
|
|
"total_detection": 15,
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
self.assertEqual(response_json, expected_response)
|
|
|
|
|
|
|
|
|
|
|
|
def test_get_review_summary_multiple_in_same_day_with_reviewed(self):
|
|
|
|
|
|
five_days_ago = datetime.today() - timedelta(days=5)
|
|
|
|
|
|
|
2025-11-26 15:07:28 -06:00
|
|
|
|
with AuthTestClient(self.app) as client:
|
2024-12-04 12:52:08 +00:00
|
|
|
|
five_days_ago_ts = five_days_ago.timestamp()
|
|
|
|
|
|
for i in range(10):
|
2025-03-13 15:20:09 -05:00
|
|
|
|
id = f"123456_{i}.random_alert_not_reviewed"
|
2024-12-04 12:52:08 +00:00
|
|
|
|
super().insert_mock_review_segment(
|
2025-03-13 15:20:09 -05:00
|
|
|
|
id, five_days_ago_ts, five_days_ago_ts, SeverityEnum.alert
|
2024-12-04 12:52:08 +00:00
|
|
|
|
)
|
|
|
|
|
|
for i in range(10):
|
2025-03-13 15:20:09 -05:00
|
|
|
|
id = f"123456_{i}.random_alert_reviewed"
|
2024-12-04 12:52:08 +00:00
|
|
|
|
super().insert_mock_review_segment(
|
2025-03-13 15:20:09 -05:00
|
|
|
|
id, five_days_ago_ts, five_days_ago_ts, SeverityEnum.alert
|
2024-12-04 12:52:08 +00:00
|
|
|
|
)
|
2025-03-13 15:20:09 -05:00
|
|
|
|
self._insert_user_review_status(id, reviewed=True)
|
2024-12-04 12:52:08 +00:00
|
|
|
|
for i in range(10):
|
2025-03-13 15:20:09 -05:00
|
|
|
|
id = f"123456_{i}.random_detection_not_reviewed"
|
2024-12-04 12:52:08 +00:00
|
|
|
|
super().insert_mock_review_segment(
|
2025-03-13 15:20:09 -05:00
|
|
|
|
id, five_days_ago_ts, five_days_ago_ts, SeverityEnum.detection
|
2024-12-04 12:52:08 +00:00
|
|
|
|
)
|
|
|
|
|
|
for i in range(5):
|
2025-03-13 15:20:09 -05:00
|
|
|
|
id = f"123456_{i}.random_detection_reviewed"
|
2024-12-04 12:52:08 +00:00
|
|
|
|
super().insert_mock_review_segment(
|
2025-03-13 15:20:09 -05:00
|
|
|
|
id, five_days_ago_ts, five_days_ago_ts, SeverityEnum.detection
|
2024-12-04 12:52:08 +00:00
|
|
|
|
)
|
2025-03-13 15:20:09 -05:00
|
|
|
|
self._insert_user_review_status(id, reviewed=True)
|
2024-12-04 12:52:08 +00:00
|
|
|
|
response = client.get("/review/summary")
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
response_json = response.json()
|
|
|
|
|
|
# e.g. '2024-11-19'
|
|
|
|
|
|
five_days_ago_formatted = five_days_ago.strftime("%Y-%m-%d")
|
|
|
|
|
|
expected_response = {
|
|
|
|
|
|
"last24Hours": {
|
|
|
|
|
|
"reviewed_alert": None,
|
|
|
|
|
|
"reviewed_detection": None,
|
|
|
|
|
|
"total_alert": None,
|
|
|
|
|
|
"total_detection": None,
|
|
|
|
|
|
},
|
|
|
|
|
|
five_days_ago_formatted: {
|
|
|
|
|
|
"day": five_days_ago_formatted,
|
|
|
|
|
|
"reviewed_alert": 10,
|
|
|
|
|
|
"reviewed_detection": 5,
|
|
|
|
|
|
"total_alert": 20,
|
|
|
|
|
|
"total_detection": 15,
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
self.assertEqual(response_json, expected_response)
|
|
|
|
|
|
|
|
|
|
|
|
####################################################################################################################
|
|
|
|
|
|
################################### POST reviews/viewed Endpoint ################################################
|
|
|
|
|
|
####################################################################################################################
|
2025-03-13 15:20:09 -05:00
|
|
|
|
|
2024-12-04 12:52:08 +00:00
|
|
|
|
def test_post_reviews_viewed_no_body(self):
|
2025-11-26 15:07:28 -06:00
|
|
|
|
with AuthTestClient(self.app) as client:
|
2024-12-04 12:52:08 +00:00
|
|
|
|
super().insert_mock_review_segment("123456.random")
|
|
|
|
|
|
response = client.post("/reviews/viewed")
|
|
|
|
|
|
# Missing ids
|
|
|
|
|
|
assert response.status_code == 422
|
|
|
|
|
|
|
|
|
|
|
|
def test_post_reviews_viewed_no_body_ids(self):
|
2025-11-26 15:07:28 -06:00
|
|
|
|
with AuthTestClient(self.app) as client:
|
2024-12-04 12:52:08 +00:00
|
|
|
|
super().insert_mock_review_segment("123456.random")
|
|
|
|
|
|
body = {"ids": [""]}
|
|
|
|
|
|
response = client.post("/reviews/viewed", json=body)
|
|
|
|
|
|
# Missing ids
|
|
|
|
|
|
assert response.status_code == 422
|
|
|
|
|
|
|
|
|
|
|
|
def test_post_reviews_viewed_non_existent_id(self):
|
2025-11-26 15:07:28 -06:00
|
|
|
|
with AuthTestClient(self.app) as client:
|
2024-12-04 12:52:08 +00:00
|
|
|
|
id = "123456.random"
|
|
|
|
|
|
super().insert_mock_review_segment(id)
|
|
|
|
|
|
body = {"ids": ["1"]}
|
|
|
|
|
|
response = client.post("/reviews/viewed", json=body)
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
response = response.json()
|
|
|
|
|
|
assert response["success"] == True
|
2025-10-12 08:10:56 -05:00
|
|
|
|
assert response["message"] == "Marked multiple items as reviewed"
|
2024-12-04 12:52:08 +00:00
|
|
|
|
# Verify that in DB the review segment was not changed
|
2025-03-13 15:20:09 -05:00
|
|
|
|
with self.assertRaises(DoesNotExist):
|
|
|
|
|
|
UserReviewStatus.get(
|
|
|
|
|
|
UserReviewStatus.user_id == self.user_id,
|
|
|
|
|
|
UserReviewStatus.review_segment == "1",
|
|
|
|
|
|
)
|
2024-12-04 12:52:08 +00:00
|
|
|
|
|
|
|
|
|
|
def test_post_reviews_viewed(self):
|
2025-11-26 15:07:28 -06:00
|
|
|
|
with AuthTestClient(self.app) as client:
|
2024-12-04 12:52:08 +00:00
|
|
|
|
id = "123456.random"
|
|
|
|
|
|
super().insert_mock_review_segment(id)
|
|
|
|
|
|
body = {"ids": [id]}
|
|
|
|
|
|
response = client.post("/reviews/viewed", json=body)
|
|
|
|
|
|
assert response.status_code == 200
|
2025-03-13 15:20:09 -05:00
|
|
|
|
response_json = response.json()
|
|
|
|
|
|
assert response_json["success"] == True
|
2025-10-12 08:10:56 -05:00
|
|
|
|
assert response_json["message"] == "Marked multiple items as reviewed"
|
2025-03-13 15:20:09 -05:00
|
|
|
|
# Verify UserReviewStatus was created
|
|
|
|
|
|
user_review = UserReviewStatus.get(
|
|
|
|
|
|
UserReviewStatus.user_id == self.user_id,
|
|
|
|
|
|
UserReviewStatus.review_segment == id,
|
2024-12-04 12:52:08 +00:00
|
|
|
|
)
|
2025-03-13 15:20:09 -05:00
|
|
|
|
assert user_review.has_been_reviewed == True
|
2024-12-04 12:52:08 +00:00
|
|
|
|
|
2026-09-03 14:53:59 -07:00
|
|
|
|
def test_reviews_concurrent_insert_peewee_ignore(self):
|
|
|
|
|
|
"""Validates that on_conflict_ignore() silently skips a duplicate insert
|
|
|
|
|
|
|
|
|
|
|
|
Two requests can both SELECT and find no existing status, then both try
|
|
|
|
|
|
to INSERT, hitting the unique (user_id, review_segment) constraint.
|
|
|
|
|
|
on_conflict_ignore() must silently skip the duplicate instead of raising
|
|
|
|
|
|
an IntegrityError (which was previously caught with try/except).
|
|
|
|
|
|
"""
|
|
|
|
|
|
id = "123456.random"
|
|
|
|
|
|
with AuthTestClient(self.app):
|
|
|
|
|
|
super().insert_mock_review_segment(id)
|
|
|
|
|
|
|
|
|
|
|
|
# Simulate the first request having already committed its insert.
|
|
|
|
|
|
self._insert_user_review_status(id, reviewed=True)
|
|
|
|
|
|
|
|
|
|
|
|
# Simulate the second concurrent request attempting the same insert.
|
|
|
|
|
|
UserReviewStatus.insert_many(
|
|
|
|
|
|
[
|
|
|
|
|
|
{
|
|
|
|
|
|
"user_id": self.user_id,
|
|
|
|
|
|
"review_segment_id": id,
|
|
|
|
|
|
"has_been_reviewed": True,
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
).on_conflict_ignore().execute()
|
|
|
|
|
|
|
|
|
|
|
|
# Exactly one row should exist; no exception should have been raised.
|
|
|
|
|
|
count = (
|
|
|
|
|
|
UserReviewStatus.select()
|
|
|
|
|
|
.where(
|
|
|
|
|
|
(UserReviewStatus.user_id == self.user_id)
|
|
|
|
|
|
& (UserReviewStatus.review_segment == id)
|
|
|
|
|
|
)
|
|
|
|
|
|
.count()
|
|
|
|
|
|
)
|
|
|
|
|
|
assert count == 1
|
|
|
|
|
|
|
2024-12-04 12:52:08 +00:00
|
|
|
|
####################################################################################################################
|
|
|
|
|
|
################################### POST reviews/delete Endpoint ################################################
|
|
|
|
|
|
####################################################################################################################
|
|
|
|
|
|
def test_post_reviews_delete_no_body(self):
|
2025-11-26 15:07:28 -06:00
|
|
|
|
with AuthTestClient(self.app) as client:
|
2024-12-04 12:52:08 +00:00
|
|
|
|
super().insert_mock_review_segment("123456.random")
|
2025-03-08 10:01:08 -06:00
|
|
|
|
response = client.post("/reviews/delete", headers={"remote-role": "admin"})
|
2024-12-04 12:52:08 +00:00
|
|
|
|
# Missing ids
|
|
|
|
|
|
assert response.status_code == 422
|
|
|
|
|
|
|
|
|
|
|
|
def test_post_reviews_delete_no_body_ids(self):
|
2025-11-26 15:07:28 -06:00
|
|
|
|
with AuthTestClient(self.app) as client:
|
2024-12-04 12:52:08 +00:00
|
|
|
|
super().insert_mock_review_segment("123456.random")
|
|
|
|
|
|
body = {"ids": [""]}
|
2025-03-08 10:01:08 -06:00
|
|
|
|
response = client.post(
|
|
|
|
|
|
"/reviews/delete", json=body, headers={"remote-role": "admin"}
|
|
|
|
|
|
)
|
2024-12-04 12:52:08 +00:00
|
|
|
|
# Missing ids
|
|
|
|
|
|
assert response.status_code == 422
|
|
|
|
|
|
|
|
|
|
|
|
def test_post_reviews_delete_non_existent_id(self):
|
2025-11-26 15:07:28 -06:00
|
|
|
|
with AuthTestClient(self.app) as client:
|
2024-12-04 12:52:08 +00:00
|
|
|
|
id = "123456.random"
|
|
|
|
|
|
super().insert_mock_review_segment(id)
|
|
|
|
|
|
body = {"ids": ["1"]}
|
2025-03-08 10:01:08 -06:00
|
|
|
|
response = client.post(
|
|
|
|
|
|
"/reviews/delete", json=body, headers={"remote-role": "admin"}
|
|
|
|
|
|
)
|
2024-12-04 12:52:08 +00:00
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
response_json = response.json()
|
|
|
|
|
|
assert response_json["success"] == True
|
2024-12-06 08:04:02 -06:00
|
|
|
|
assert response_json["message"] == "Deleted review items."
|
2024-12-04 12:52:08 +00:00
|
|
|
|
# Verify that in DB the review segment was not deleted
|
|
|
|
|
|
review_ids_in_db_after = self._get_reviews([id])
|
|
|
|
|
|
assert len(review_ids_in_db_after) == 1
|
|
|
|
|
|
assert review_ids_in_db_after[0].id == id
|
|
|
|
|
|
|
|
|
|
|
|
def test_post_reviews_delete(self):
|
2025-11-26 15:07:28 -06:00
|
|
|
|
with AuthTestClient(self.app) as client:
|
2024-12-04 12:52:08 +00:00
|
|
|
|
id = "123456.random"
|
|
|
|
|
|
super().insert_mock_review_segment(id)
|
|
|
|
|
|
body = {"ids": [id]}
|
2025-03-08 10:01:08 -06:00
|
|
|
|
response = client.post(
|
|
|
|
|
|
"/reviews/delete", json=body, headers={"remote-role": "admin"}
|
|
|
|
|
|
)
|
2024-12-04 12:52:08 +00:00
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
response_json = response.json()
|
|
|
|
|
|
assert response_json["success"] == True
|
2024-12-06 08:04:02 -06:00
|
|
|
|
assert response_json["message"] == "Deleted review items."
|
2024-12-04 12:52:08 +00:00
|
|
|
|
# Verify that in DB the review segment was deleted
|
|
|
|
|
|
review_ids_in_db_after = self._get_reviews([id])
|
|
|
|
|
|
assert len(review_ids_in_db_after) == 0
|
|
|
|
|
|
|
|
|
|
|
|
def test_post_reviews_delete_many(self):
|
2025-11-26 15:07:28 -06:00
|
|
|
|
with AuthTestClient(self.app) as client:
|
2024-12-04 12:52:08 +00:00
|
|
|
|
ids = ["123456.random", "654321.random"]
|
|
|
|
|
|
for id in ids:
|
|
|
|
|
|
super().insert_mock_review_segment(id)
|
|
|
|
|
|
super().insert_mock_recording(id)
|
|
|
|
|
|
|
|
|
|
|
|
review_ids_in_db_before = self._get_reviews(ids)
|
|
|
|
|
|
recordings_ids_in_db_before = self._get_recordings(ids)
|
|
|
|
|
|
assert len(review_ids_in_db_before) == 2
|
|
|
|
|
|
assert len(recordings_ids_in_db_before) == 2
|
|
|
|
|
|
|
|
|
|
|
|
body = {"ids": ids}
|
2025-03-08 10:01:08 -06:00
|
|
|
|
response = client.post(
|
|
|
|
|
|
"/reviews/delete", json=body, headers={"remote-role": "admin"}
|
|
|
|
|
|
)
|
2024-12-04 12:52:08 +00:00
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
response_json = response.json()
|
|
|
|
|
|
assert response_json["success"] == True
|
2024-12-06 08:04:02 -06:00
|
|
|
|
assert response_json["message"] == "Deleted review items."
|
2024-12-04 12:52:08 +00:00
|
|
|
|
|
|
|
|
|
|
# Verify that in DB all review segments and recordings that were passed were deleted
|
|
|
|
|
|
review_ids_in_db_after = self._get_reviews(ids)
|
|
|
|
|
|
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
|
2025-02-04 13:28:14 +00:00
|
|
|
|
|
|
|
|
|
|
####################################################################################################################
|
|
|
|
|
|
################################### GET /review/activity/motion Endpoint ########################################
|
|
|
|
|
|
####################################################################################################################
|
|
|
|
|
|
def test_review_activity_motion_no_data_for_time_range(self):
|
|
|
|
|
|
now = datetime.now().timestamp()
|
|
|
|
|
|
|
2025-11-26 15:07:28 -06:00
|
|
|
|
with AuthTestClient(self.app) as client:
|
2025-02-04 13:28:14 +00:00
|
|
|
|
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())
|
|
|
|
|
|
|
2025-11-26 15:07:28 -06:00
|
|
|
|
with AuthTestClient(self.app) as client:
|
2025-02-04 13:28:14 +00:00
|
|
|
|
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()
|
2026-06-01 14:55:52 -05:00
|
|
|
|
# Only buckets with an actual recording are returned. Empty
|
|
|
|
|
|
# gap-fill buckets between the two recordings are dropped.
|
|
|
|
|
|
assert len(response_json) == 2
|
2025-02-04 13:28:14 +00:00
|
|
|
|
self.assertDictEqual(
|
|
|
|
|
|
{"motion": 50.5, "camera": "front_door", "start_time": now + 1},
|
|
|
|
|
|
response_json[0],
|
|
|
|
|
|
)
|
|
|
|
|
|
self.assertDictEqual(
|
|
|
|
|
|
{"motion": 100.0, "camera": "front_door", "start_time": one_m + 1},
|
2026-06-01 14:55:52 -05:00
|
|
|
|
response_json[1],
|
2025-02-04 13:28:14 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
####################################################################################################################
|
|
|
|
|
|
################################### GET /review/event/{event_id} Endpoint #######################################
|
|
|
|
|
|
####################################################################################################################
|
|
|
|
|
|
def test_review_event_not_found(self):
|
2025-11-26 15:07:28 -06:00
|
|
|
|
with AuthTestClient(self.app) as client:
|
2025-02-04 13:28:14 +00:00
|
|
|
|
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()
|
|
|
|
|
|
|
2025-11-26 15:07:28 -06:00
|
|
|
|
with AuthTestClient(self.app) as client:
|
2025-02-04 13:28:14 +00:00
|
|
|
|
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()
|
|
|
|
|
|
|
2025-11-26 15:07:28 -06:00
|
|
|
|
with AuthTestClient(self.app) as client:
|
2025-02-04 13:28:14 +00:00
|
|
|
|
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,
|
2025-03-13 15:20:09 -05:00
|
|
|
|
"severity": "alert",
|
2025-02-04 13:28:14 +00:00
|
|
|
|
"thumb_path": "False",
|
|
|
|
|
|
"data": {"detections": {"event_id": event_id}},
|
|
|
|
|
|
},
|
|
|
|
|
|
response_json,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
####################################################################################################################
|
|
|
|
|
|
################################### GET /review/{review_id} Endpoint #######################################
|
|
|
|
|
|
####################################################################################################################
|
|
|
|
|
|
def test_review_not_found(self):
|
2025-11-26 15:07:28 -06:00
|
|
|
|
with AuthTestClient(self.app) as client:
|
2025-02-04 13:28:14 +00:00
|
|
|
|
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()
|
|
|
|
|
|
|
2025-11-26 15:07:28 -06:00
|
|
|
|
with AuthTestClient(self.app) as client:
|
2025-02-04 13:28:14 +00:00
|
|
|
|
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,
|
2025-03-13 15:20:09 -05:00
|
|
|
|
"severity": "alert",
|
2025-02-04 13:28:14 +00:00
|
|
|
|
"thumb_path": "False",
|
|
|
|
|
|
"data": {},
|
|
|
|
|
|
},
|
|
|
|
|
|
response_json,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
####################################################################################################################
|
|
|
|
|
|
################################### DELETE /review/{review_id}/viewed Endpoint ##################################
|
|
|
|
|
|
####################################################################################################################
|
2025-03-13 15:20:09 -05:00
|
|
|
|
|
2025-02-04 13:28:14 +00:00
|
|
|
|
def test_delete_review_viewed_review_not_found(self):
|
2025-11-26 15:07:28 -06:00
|
|
|
|
with AuthTestClient(self.app) as client:
|
2025-02-04 13:28:14 +00:00
|
|
|
|
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()
|
|
|
|
|
|
|
2025-11-26 15:07:28 -06:00
|
|
|
|
with AuthTestClient(self.app) as client:
|
2025-02-04 13:28:14 +00:00
|
|
|
|
review_id = "123456.review.random"
|
2025-03-13 15:20:09 -05:00
|
|
|
|
super().insert_mock_review_segment(review_id, now + 1, now + 2)
|
|
|
|
|
|
self._insert_user_review_status(review_id, reviewed=True)
|
|
|
|
|
|
# Verify it’s reviewed before
|
|
|
|
|
|
response = client.get(f"/review/{review_id}")
|
2025-02-04 13:28:14 +00:00
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-03-13 15:20:09 -05:00
|
|
|
|
# Verify it’s unreviewed after
|
|
|
|
|
|
with self.assertRaises(DoesNotExist):
|
|
|
|
|
|
UserReviewStatus.get(
|
|
|
|
|
|
UserReviewStatus.user_id == self.user_id,
|
|
|
|
|
|
UserReviewStatus.review_segment == review_id,
|
|
|
|
|
|
)
|