Face-Expression-Analyzer / backend /tests /api /test_logs_write.py
Aynkhn's picture
Deploy FaceVision to Hugging Face Spaces
67af2df verified
Raw History Blame Contribute Delete
4.47 kB
"""Write-path route tests."""
from __future__ import annotations
import pytest
from app.schemas.emotion_log import EMOTION_NAMES
class TestCreate:
@pytest.mark.parametrize("emotion", EMOTION_NAMES)
def test_accepts_every_valid_emotion(self, client, ingest_headers, emotion):
response = client.post(
"/api/logs",
json={"dominant_emotion": emotion, "duration_seconds": 10},
headers=ingest_headers,
)
assert response.status_code == 201
assert response.json()["dominant_emotion"] == emotion
def test_rejects_unknown_emotion_with_422(self, client, ingest_headers):
"""Previously a hand-rolled validator; now a Literal, so the error is typed."""
response = client.post(
"/api/logs",
json={"dominant_emotion": "Ecstatic", "duration_seconds": 10},
headers=ingest_headers,
)
assert response.status_code == 422
@pytest.mark.parametrize("duration", [0.5, 0, -1, 3601])
def test_rejects_out_of_range_duration(self, client, ingest_headers, duration):
response = client.post(
"/api/logs",
json={"dominant_emotion": "Happy", "duration_seconds": duration},
headers=ingest_headers,
)
assert response.status_code == 422
def test_response_includes_server_assigned_fields(self, client, ingest_headers):
body = client.post(
"/api/logs",
json={"dominant_emotion": "Happy", "duration_seconds": 10},
headers=ingest_headers,
).json()
assert isinstance(body["id"], int)
assert body["timestamp"]
def test_timestamp_carries_an_explicit_utc_offset(self, client, ingest_headers):
"""Without an offset, browsers parse the value as local time.
SQLite returns a naive datetime even for a column declared timezone-aware,
so the serialiser has to reattach UTC. A bare "2026-08-13T22:01:45" would
shift every displayed record by the viewer's offset.
"""
body = client.post(
"/api/logs",
json={"dominant_emotion": "Happy", "duration_seconds": 10},
headers=ingest_headers,
).json()
assert body["timestamp"].endswith(("Z", "+00:00"))
def test_listed_timestamps_carry_an_offset(self, client, ingest_headers, session_headers):
client.post(
"/api/logs",
json={"dominant_emotion": "Sad", "duration_seconds": 10},
headers=ingest_headers,
)
items = client.get("/api/logs", headers=session_headers).json()["items"]
assert items
assert all(item["timestamp"].endswith(("Z", "+00:00")) for item in items)
class TestBulkCreate:
def test_creates_all_items(self, client, ingest_headers):
items = [{"dominant_emotion": "Happy", "duration_seconds": 10} for _ in range(5)]
response = client.post("/api/logs/bulk", json={"items": items}, headers=ingest_headers)
assert response.status_code == 201
body = response.json()
assert body["created"] == 5
assert body["rejected"] == 0
def test_reports_per_item_indexes(self, client, ingest_headers):
items = [{"dominant_emotion": "Sad", "duration_seconds": 10} for _ in range(3)]
body = client.post("/api/logs/bulk", json={"items": items}, headers=ingest_headers).json()
assert [entry["index"] for entry in body["results"]] == [0, 1, 2]
def test_rejects_empty_batch(self, client, ingest_headers):
response = client.post("/api/logs/bulk", json={"items": []}, headers=ingest_headers)
assert response.status_code == 422
def test_rejects_oversized_batch(self, client, ingest_headers):
items = [{"dominant_emotion": "Happy", "duration_seconds": 10}] * 501
response = client.post("/api/logs/bulk", json={"items": items}, headers=ingest_headers)
assert response.status_code == 422
def test_invalid_item_fails_the_batch_validation(self, client, ingest_headers):
items = [
{"dominant_emotion": "Happy", "duration_seconds": 10},
{"dominant_emotion": "Nonsense", "duration_seconds": 10},
]
response = client.post("/api/logs/bulk", json={"items": items}, headers=ingest_headers)
assert response.status_code == 422