mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-05-09 15:05:26 +03:00
i18n and tests
This commit is contained in:
parent
317ca7bb1c
commit
b407334f56
@ -504,7 +504,14 @@ class TestHttpExport(BaseTestHttp):
|
||||
assert row.in_progress is False
|
||||
assert os.path.exists(done_video)
|
||||
|
||||
def test_batch_export_requires_case_target(self):
|
||||
def test_batch_export_without_case_goes_to_uncategorized(self):
|
||||
"""Exports without a case target go to uncategorized."""
|
||||
self._insert_recording("rec-front", "front_door", 100, 400)
|
||||
|
||||
with patch(
|
||||
"frigate.api.export.start_export_job",
|
||||
side_effect=lambda _config, job: job.id,
|
||||
):
|
||||
with AuthTestClient(self.app) as client:
|
||||
response = client.post(
|
||||
"/exports/batch",
|
||||
@ -519,11 +526,10 @@ class TestHttpExport(BaseTestHttp):
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 422
|
||||
assert (
|
||||
response.json()["detail"][0]["msg"]
|
||||
== "Value error, Either export_case_id or new_case_name must be provided"
|
||||
)
|
||||
assert response.status_code == 202
|
||||
response_json = response.json()
|
||||
assert response_json["export_case_id"] is None
|
||||
assert ExportCase.select().count() == 0
|
||||
|
||||
# --- /exports/batch (item-shaped multi-export) ---------------------------
|
||||
|
||||
@ -651,10 +657,18 @@ class TestHttpExport(BaseTestHttp):
|
||||
== "Value error, end_time must be after start_time"
|
||||
)
|
||||
|
||||
def test_batch_export_missing_case_target_rejected(self):
|
||||
def test_batch_export_non_admin_without_case_goes_to_uncategorized(self):
|
||||
"""Non-admin batch exports go to uncategorized."""
|
||||
self._insert_recording("rec-front", "front_door", 100, 400)
|
||||
|
||||
with patch(
|
||||
"frigate.api.export.start_export_job",
|
||||
side_effect=lambda _config, job: job.id,
|
||||
):
|
||||
with AuthTestClient(self.app) as client:
|
||||
response = client.post(
|
||||
"/exports/batch",
|
||||
headers={"remote-user": "viewer", "remote-role": "viewer"},
|
||||
json={
|
||||
"items": [
|
||||
{
|
||||
@ -666,11 +680,10 @@ class TestHttpExport(BaseTestHttp):
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 422
|
||||
assert (
|
||||
response.json()["detail"][0]["msg"]
|
||||
== "Value error, Either export_case_id or new_case_name must be provided"
|
||||
)
|
||||
assert response.status_code == 202
|
||||
response_json = response.json()
|
||||
assert response_json["export_case_id"] is None
|
||||
assert ExportCase.select().count() == 0
|
||||
|
||||
def test_batch_export_camera_access_denied_fails_closed(self):
|
||||
from fastapi import Request
|
||||
@ -1108,3 +1121,313 @@ class TestHttpExport(BaseTestHttp):
|
||||
|
||||
assert response.status_code == 202
|
||||
assert response.json()["success"] is True
|
||||
|
||||
# ── Bulk delete exports ────────────────────────────────────────
|
||||
|
||||
def test_bulk_delete_exports_success(self):
|
||||
"""All IDs exist, none in-progress → 200, all deleted."""
|
||||
Export.create(
|
||||
id="exp1",
|
||||
camera="front_door",
|
||||
name="export_1",
|
||||
date=100,
|
||||
video_path="/tmp/exp1.mp4",
|
||||
thumb_path="/tmp/exp1.jpg",
|
||||
in_progress=False,
|
||||
)
|
||||
Export.create(
|
||||
id="exp2",
|
||||
camera="front_door",
|
||||
name="export_2",
|
||||
date=200,
|
||||
video_path="/tmp/exp2.mp4",
|
||||
thumb_path="/tmp/exp2.jpg",
|
||||
in_progress=False,
|
||||
)
|
||||
|
||||
with AuthTestClient(self.app) as client:
|
||||
response = client.post(
|
||||
"/exports/delete",
|
||||
json={"ids": ["exp1", "exp2"]},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json()["success"] is True
|
||||
assert Export.select().count() == 0
|
||||
|
||||
def test_bulk_delete_exports_single_item(self):
|
||||
"""Regression: single-item delete via batch endpoint."""
|
||||
Export.create(
|
||||
id="exp1",
|
||||
camera="front_door",
|
||||
name="export_1",
|
||||
date=100,
|
||||
video_path="/tmp/exp1.mp4",
|
||||
thumb_path="/tmp/exp1.jpg",
|
||||
in_progress=False,
|
||||
)
|
||||
|
||||
with AuthTestClient(self.app) as client:
|
||||
response = client.post(
|
||||
"/exports/delete",
|
||||
json={"ids": ["exp1"]},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert Export.select().count() == 0
|
||||
|
||||
def test_bulk_delete_exports_some_missing(self):
|
||||
"""Some IDs don't exist → 404, nothing deleted."""
|
||||
Export.create(
|
||||
id="exp1",
|
||||
camera="front_door",
|
||||
name="export_1",
|
||||
date=100,
|
||||
video_path="/tmp/exp1.mp4",
|
||||
thumb_path="/tmp/exp1.jpg",
|
||||
in_progress=False,
|
||||
)
|
||||
|
||||
with AuthTestClient(self.app) as client:
|
||||
response = client.post(
|
||||
"/exports/delete",
|
||||
json={"ids": ["exp1", "nonexistent"]},
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
||||
# Nothing deleted
|
||||
assert Export.select().count() == 1
|
||||
|
||||
def test_bulk_delete_exports_all_missing(self):
|
||||
"""All IDs don't exist → 404."""
|
||||
with AuthTestClient(self.app) as client:
|
||||
response = client.post(
|
||||
"/exports/delete",
|
||||
json={"ids": ["nope1", "nope2"]},
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_bulk_delete_exports_in_progress(self):
|
||||
"""Some exports in-progress → 400, nothing deleted."""
|
||||
Export.create(
|
||||
id="exp1",
|
||||
camera="front_door",
|
||||
name="export_1",
|
||||
date=100,
|
||||
video_path=f"{os.environ.get('EXPORT_DIR', '/media/frigate/exports')}/exp1.mp4",
|
||||
thumb_path="/tmp/exp1.jpg",
|
||||
in_progress=True,
|
||||
)
|
||||
|
||||
with patch(
|
||||
"frigate.api.export._get_files_in_use",
|
||||
return_value={"exp1.mp4"},
|
||||
):
|
||||
with AuthTestClient(self.app) as client:
|
||||
response = client.post(
|
||||
"/exports/delete",
|
||||
json={"ids": ["exp1"]},
|
||||
)
|
||||
|
||||
assert response.status_code == 400
|
||||
assert Export.select().count() == 1
|
||||
|
||||
def test_bulk_delete_exports_non_admin_rejected(self):
|
||||
"""Non-admin users cannot bulk delete."""
|
||||
Export.create(
|
||||
id="exp1",
|
||||
camera="front_door",
|
||||
name="export_1",
|
||||
date=100,
|
||||
video_path="/tmp/exp1.mp4",
|
||||
thumb_path="/tmp/exp1.jpg",
|
||||
in_progress=False,
|
||||
)
|
||||
|
||||
with AuthTestClient(self.app) as client:
|
||||
response = client.post(
|
||||
"/exports/delete",
|
||||
headers={"remote-user": "viewer", "remote-role": "viewer"},
|
||||
json={"ids": ["exp1"]},
|
||||
)
|
||||
|
||||
assert response.status_code == 403
|
||||
assert Export.select().count() == 1
|
||||
|
||||
# ── Bulk reassign exports ──────────────────────────────────────
|
||||
|
||||
def test_bulk_reassign_exports_to_case(self):
|
||||
"""All IDs exist, case exists → 200, all reassigned."""
|
||||
ExportCase.create(
|
||||
id="case1",
|
||||
name="Test Case",
|
||||
description="",
|
||||
created_at=10,
|
||||
updated_at=10,
|
||||
)
|
||||
Export.create(
|
||||
id="exp1",
|
||||
camera="front_door",
|
||||
name="export_1",
|
||||
date=100,
|
||||
video_path="/tmp/exp1.mp4",
|
||||
thumb_path="/tmp/exp1.jpg",
|
||||
in_progress=False,
|
||||
)
|
||||
Export.create(
|
||||
id="exp2",
|
||||
camera="front_door",
|
||||
name="export_2",
|
||||
date=200,
|
||||
video_path="/tmp/exp2.mp4",
|
||||
thumb_path="/tmp/exp2.jpg",
|
||||
in_progress=False,
|
||||
)
|
||||
|
||||
with AuthTestClient(self.app) as client:
|
||||
response = client.post(
|
||||
"/exports/reassign",
|
||||
json={"ids": ["exp1", "exp2"], "export_case_id": "case1"},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json()["success"] is True
|
||||
for exp_id in ["exp1", "exp2"]:
|
||||
exp = Export.get(Export.id == exp_id)
|
||||
assert exp.export_case_id == "case1"
|
||||
|
||||
def test_bulk_reassign_exports_to_null(self):
|
||||
"""Reassign to null (uncategorize) → 200."""
|
||||
ExportCase.create(
|
||||
id="case1",
|
||||
name="Test Case",
|
||||
description="",
|
||||
created_at=10,
|
||||
updated_at=10,
|
||||
)
|
||||
Export.create(
|
||||
id="exp1",
|
||||
camera="front_door",
|
||||
name="export_1",
|
||||
date=100,
|
||||
video_path="/tmp/exp1.mp4",
|
||||
thumb_path="/tmp/exp1.jpg",
|
||||
in_progress=False,
|
||||
export_case="case1",
|
||||
)
|
||||
|
||||
with AuthTestClient(self.app) as client:
|
||||
response = client.post(
|
||||
"/exports/reassign",
|
||||
json={"ids": ["exp1"], "export_case_id": None},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
exp = Export.get(Export.id == "exp1")
|
||||
assert exp.export_case_id is None
|
||||
|
||||
def test_bulk_reassign_exports_single_item(self):
|
||||
"""Regression: single-item reassign via batch endpoint."""
|
||||
ExportCase.create(
|
||||
id="case1",
|
||||
name="Test Case",
|
||||
description="",
|
||||
created_at=10,
|
||||
updated_at=10,
|
||||
)
|
||||
Export.create(
|
||||
id="exp1",
|
||||
camera="front_door",
|
||||
name="export_1",
|
||||
date=100,
|
||||
video_path="/tmp/exp1.mp4",
|
||||
thumb_path="/tmp/exp1.jpg",
|
||||
in_progress=False,
|
||||
)
|
||||
|
||||
with AuthTestClient(self.app) as client:
|
||||
response = client.post(
|
||||
"/exports/reassign",
|
||||
json={"ids": ["exp1"], "export_case_id": "case1"},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
exp = Export.get(Export.id == "exp1")
|
||||
assert exp.export_case_id == "case1"
|
||||
|
||||
def test_bulk_reassign_exports_some_missing(self):
|
||||
"""Some IDs don't exist → 404, nothing reassigned."""
|
||||
ExportCase.create(
|
||||
id="case1",
|
||||
name="Test Case",
|
||||
description="",
|
||||
created_at=10,
|
||||
updated_at=10,
|
||||
)
|
||||
Export.create(
|
||||
id="exp1",
|
||||
camera="front_door",
|
||||
name="export_1",
|
||||
date=100,
|
||||
video_path="/tmp/exp1.mp4",
|
||||
thumb_path="/tmp/exp1.jpg",
|
||||
in_progress=False,
|
||||
)
|
||||
|
||||
with AuthTestClient(self.app) as client:
|
||||
response = client.post(
|
||||
"/exports/reassign",
|
||||
json={
|
||||
"ids": ["exp1", "nonexistent"],
|
||||
"export_case_id": "case1",
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
||||
# Nothing reassigned
|
||||
exp = Export.get(Export.id == "exp1")
|
||||
assert exp.export_case_id is None
|
||||
|
||||
def test_bulk_reassign_exports_case_not_found(self):
|
||||
"""Target case doesn't exist → 404."""
|
||||
Export.create(
|
||||
id="exp1",
|
||||
camera="front_door",
|
||||
name="export_1",
|
||||
date=100,
|
||||
video_path="/tmp/exp1.mp4",
|
||||
thumb_path="/tmp/exp1.jpg",
|
||||
in_progress=False,
|
||||
)
|
||||
|
||||
with AuthTestClient(self.app) as client:
|
||||
response = client.post(
|
||||
"/exports/reassign",
|
||||
json={"ids": ["exp1"], "export_case_id": "nonexistent"},
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
||||
exp = Export.get(Export.id == "exp1")
|
||||
assert exp.export_case_id is None
|
||||
|
||||
def test_bulk_reassign_exports_non_admin_rejected(self):
|
||||
"""Non-admin users cannot bulk reassign."""
|
||||
Export.create(
|
||||
id="exp1",
|
||||
camera="front_door",
|
||||
name="export_1",
|
||||
date=100,
|
||||
video_path="/tmp/exp1.mp4",
|
||||
thumb_path="/tmp/exp1.jpg",
|
||||
in_progress=False,
|
||||
)
|
||||
|
||||
with AuthTestClient(self.app) as client:
|
||||
response = client.post(
|
||||
"/exports/reassign",
|
||||
headers={"remote-user": "viewer", "remote-role": "viewer"},
|
||||
json={"ids": ["exp1"], "export_case_id": None},
|
||||
)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
@ -84,6 +84,7 @@
|
||||
"title_one": "Export 1 review",
|
||||
"title_other": "Export {{count}} reviews",
|
||||
"description": "Export each selected review. All exports will be grouped under a single case.",
|
||||
"descriptionNoCase": "Export each selected review.",
|
||||
"caseNamePlaceholder": "Review export - {{date}}",
|
||||
"exportButton_one": "Export 1 review",
|
||||
"exportButton_other": "Export {{count}} reviews",
|
||||
@ -91,6 +92,8 @@
|
||||
"toast": {
|
||||
"started_one": "Started 1 export. Opening the case now.",
|
||||
"started_other": "Started {{count}} exports. Opening the case now.",
|
||||
"startedNoCase_one": "Started 1 export.",
|
||||
"startedNoCase_other": "Started {{count}} exports.",
|
||||
"partial": "Started {{successful}} of {{total}} exports. Failed: {{failedItems}}",
|
||||
"failed": "Failed to start {{total}} exports. Failed: {{failedItems}}"
|
||||
}
|
||||
|
||||
@ -86,5 +86,38 @@
|
||||
"addButton_one": "Add 1 Export",
|
||||
"addButton_other": "Add {{count}} Exports",
|
||||
"adding": "Adding..."
|
||||
},
|
||||
"selected_one": "{{count}} selected",
|
||||
"selected_other": "{{count}} selected",
|
||||
"bulkActions": {
|
||||
"addToCase": "Add to Case",
|
||||
"moveToCase": "Move to Case",
|
||||
"removeFromCase": "Remove from Case",
|
||||
"delete": "Delete",
|
||||
"deleteNow": "Delete Now"
|
||||
},
|
||||
"bulkDelete": {
|
||||
"title": "Delete Exports",
|
||||
"desc_one": "Are you sure you want to delete {{count}} export?",
|
||||
"desc_other": "Are you sure you want to delete {{count}} exports?"
|
||||
},
|
||||
"bulkRemoveFromCase": {
|
||||
"title": "Remove from Case",
|
||||
"desc_one": "Remove {{count}} export from this case?",
|
||||
"desc_other": "Remove {{count}} exports from this case?",
|
||||
"descKeepExports": "Exports will be moved to uncategorized.",
|
||||
"descDeleteExports": "Exports will be permanently deleted.",
|
||||
"deleteExports": "Delete exports instead"
|
||||
},
|
||||
"bulkToast": {
|
||||
"success": {
|
||||
"delete": "Successfully deleted exports",
|
||||
"reassign": "Successfully updated case assignment",
|
||||
"remove": "Successfully removed exports from case"
|
||||
},
|
||||
"error": {
|
||||
"deleteFailed": "Failed to delete exports: {{errorMessage}}",
|
||||
"reassignFailed": "Failed to update case assignment: {{errorMessage}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user