faceverification / test /test_fastapi_app.py
leandrodevai's picture
Sync from GitHub via hub-sync
88cf4ae verified
Raw
History Blame Contribute Delete
7.21 kB
from io import BytesIO
from fastapi.testclient import TestClient
from PIL import Image
from faceverification.config import settings
from faceverification.core.image_processor import FaceNotDetectedError
from faceverification.interfaces.fastapi_app import app, get_face_service
def _image_bytes() -> bytes:
buffer = BytesIO()
Image.new("RGB", (12, 12), "white").save(buffer, format="PNG")
return buffer.getvalue()
class FakeService:
def __init__(self):
self.add_person_calls = []
self.verify_person_calls = []
def add_person(self, image, name):
self.add_person_calls.append((image, name))
return Image.new("RGB", image.size, "black")
def verify_person(self, image):
self.verify_person_calls.append(image)
return "Ada", Image.new("RGB", image.size, "black")
def _auth_headers(client: TestClient) -> dict[str, str]:
response = client.post(
"/auth/login",
data={"username": "demo", "password": "demo123"},
)
token = response.json()["access_token"]
return {"Authorization": f"Bearer {token}"}
def test_health_returns_ok():
client = TestClient(app)
response = client.get("/health")
assert response.status_code == 200
assert response.json() == {"status": "ok"}
def test_login_returns_access_token():
client = TestClient(app)
response = client.post(
"/auth/login",
data={"username": "demo", "password": "demo123"},
)
body = response.json()
assert response.status_code == 200
assert body["token_type"] == "bearer"
assert body["access_token"]
def test_login_rejects_invalid_credentials():
client = TestClient(app)
response = client.post(
"/auth/login",
data={"username": "demo", "password": "wrong"},
)
assert response.status_code == 401
assert response.json() == {"detail": "Incorrect username or password."}
def test_verify_identity_requires_token():
client = TestClient(app)
response = client.post(
"/verify",
files={"image": ("face.png", _image_bytes(), "image/png")},
)
assert response.status_code == 401
assert response.json() == {"detail": "Not authenticated"}
def test_enroll_person_calls_service_and_returns_annotated_image():
fake_service = FakeService()
app.dependency_overrides[get_face_service] = lambda: fake_service
try:
client = TestClient(app)
response = client.post(
"/persons",
headers=_auth_headers(client),
data={"name": " Ada "},
files={"image": ("face.png", _image_bytes(), "image/png")},
)
finally:
app.dependency_overrides.clear()
body = response.json()
assert response.status_code == 201
assert body["name"] == "Ada"
assert body["message"] == "Person added to the embeddings database."
assert body["annotated_image"].startswith("data:image/png;base64,")
assert fake_service.add_person_calls[0][1] == "Ada"
def test_verify_identity_returns_match_result():
fake_service = FakeService()
app.dependency_overrides[get_face_service] = lambda: fake_service
try:
client = TestClient(app)
response = client.post(
"/verify",
headers=_auth_headers(client),
files={"image": ("face.png", _image_bytes(), "image/png")},
)
finally:
app.dependency_overrides.clear()
body = response.json()
assert response.status_code == 200
assert body["name"] == "Ada"
assert body["matched"] is True
assert body["annotated_image"].startswith("data:image/png;base64,")
def test_verify_identity_can_skip_annotated_image():
fake_service = FakeService()
app.dependency_overrides[get_face_service] = lambda: fake_service
try:
client = TestClient(app)
response = client.post(
"/verify?include_image=false",
headers=_auth_headers(client),
files={"image": ("face.png", _image_bytes(), "image/png")},
)
finally:
app.dependency_overrides.clear()
body = response.json()
assert response.status_code == 200
assert body == {"name": "Ada", "matched": True}
def test_verify_identity_returns_unprocessable_when_no_face_is_detected():
class NoFaceService(FakeService):
def verify_person(self, image):
raise FaceNotDetectedError("No faces were detected in the image.")
app.dependency_overrides[get_face_service] = lambda: NoFaceService()
try:
client = TestClient(app)
response = client.post(
"/verify",
headers=_auth_headers(client),
files={"image": ("face.png", _image_bytes(), "image/png")},
)
finally:
app.dependency_overrides.clear()
assert response.status_code == 422
assert response.json() == {"detail": "No faces were detected in the image."}
def test_verify_identity_returns_internal_error_for_unexpected_service_failure():
class BrokenService(FakeService):
def verify_person(self, image):
raise RuntimeError("model failed")
app.dependency_overrides[get_face_service] = lambda: BrokenService()
try:
client = TestClient(app)
response = client.post(
"/verify",
headers=_auth_headers(client),
files={"image": ("face.png", _image_bytes(), "image/png")},
)
finally:
app.dependency_overrides.clear()
assert response.status_code == 500
assert response.json() == {"detail": "Face verification failed."}
def test_enroll_person_rejects_blank_name():
app.dependency_overrides[get_face_service] = lambda: FakeService()
try:
client = TestClient(app)
response = client.post(
"/persons",
headers=_auth_headers(client),
data={"name": " "},
files={"image": ("face.png", _image_bytes(), "image/png")},
)
finally:
app.dependency_overrides.clear()
assert response.status_code == 422
assert response.json() == {"detail": "Person name is required."}
def test_upload_rejects_non_image_content_type():
app.dependency_overrides[get_face_service] = lambda: FakeService()
try:
client = TestClient(app)
response = client.post(
"/verify",
headers=_auth_headers(client),
files={"image": ("face.txt", b"hello", "text/plain")},
)
finally:
app.dependency_overrides.clear()
assert response.status_code == 415
assert response.json() == {"detail": "Uploaded file must be an image."}
def test_upload_rejects_large_image(monkeypatch):
monkeypatch.setattr(settings, "max_upload_bytes", 1)
app.dependency_overrides[get_face_service] = lambda: FakeService()
try:
client = TestClient(app)
response = client.post(
"/verify",
headers=_auth_headers(client),
files={"image": ("face.png", _image_bytes(), "image/png")},
)
finally:
app.dependency_overrides.clear()
assert response.status_code == 413
assert response.json() == {"detail": "Uploaded image is too large."}