Spaces:
Running
Running
File size: 7,208 Bytes
bf2c053 88cf4ae bf2c053 88cf4ae bf2c053 88cf4ae bf2c053 88cf4ae | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 | 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."}
|