File size: 5,866 Bytes
c628352
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import importlib
import sys
from unittest.mock import Mock

import numpy as np
import pytest
from PIL import Image

from faceverification.core import image_processor as image_processor_module
from faceverification.core import vectordb as vectordb_module
from faceverification.core.image_processor import FaceNotDetectedError


class FakeEmbedding:
    def __init__(self, value):
        self.value = value

    def cpu(self):
        return self

    def numpy(self):
        return self.value


@pytest.fixture
def service_module(monkeypatch):
    fake_image_processor = Mock()
    fake_vector_db = Mock()

    monkeypatch.setattr(
        image_processor_module,
        "ImageProcessor",
        lambda: fake_image_processor,
    )
    monkeypatch.setattr(vectordb_module, "VectorDB", lambda: fake_vector_db)

    sys.modules.pop("faceverification.services.face_verification", None)
    module = importlib.import_module("faceverification.services.face_verification")

    return module, fake_image_processor, fake_vector_db


def test_add_person_stores_embedding_and_returns_annotated_image(service_module):
    service, image_processor, vector_db = service_module
    original_image = Image.new("RGB", (10, 10), "white")
    annotated_image = Image.new("RGB", (10, 10), "black")
    embedding = np.array([0.1, 0.2, 0.3])
    image_processor.detect_faces.return_value = (annotated_image, True)
    image_processor.get_embedding.return_value = FakeEmbedding(embedding)

    result = service.add_person(original_image, "Ada")

    assert result is annotated_image
    image_processor.detect_faces.assert_called_once_with(original_image)
    image_processor.get_embedding.assert_called_once_with(annotated_image)
    vector_db.add_embedding.assert_called_once()
    args = vector_db.add_embedding.call_args.args
    np.testing.assert_array_equal(args[0], embedding)
    assert args[1] == {"name": "Ada"}


def test_add_person_raises_when_no_face_is_detected(service_module):
    service, image_processor, vector_db = service_module
    image_processor.detect_faces.return_value = (Image.new("RGB", (10, 10)), False)

    with pytest.raises(FaceNotDetectedError, match="No faces were detected"):
        service.add_person(Image.new("RGB", (10, 10)), "Ada")

    image_processor.get_embedding.assert_not_called()
    vector_db.add_embedding.assert_not_called()


def test_add_person_raises_type_error_when_embedding_is_none(service_module):
    service, image_processor, vector_db = service_module
    image = Image.new("RGB", (10, 10))
    image_processor.detect_faces.return_value = (image, True)
    image_processor.get_embedding.return_value = None

    with pytest.raises(TypeError, match="extracted face embedding"):
        service.add_person(image, "Ada")

    vector_db.add_embedding.assert_not_called()


def test_verify_person_returns_matched_name_and_annotated_image(service_module):
    service, image_processor, vector_db = service_module
    original_image = Image.new("RGB", (10, 10), "white")
    detected_faces = Image.new("RGB", (10, 10), "black")
    embedding = np.array([0.4, 0.5, 0.6])
    image_processor.detect_faces.return_value = (detected_faces, True)
    image_processor.get_embedding.return_value = FakeEmbedding(embedding)
    vector_db.query_embedding.return_value = ({"name": "Grace"}, 0.2)

    name, image = service.verify_person(original_image)

    assert name == "Grace"
    assert image is detected_faces
    detected_input = image_processor.detect_faces.call_args.args[0]
    assert detected_input is not original_image
    image_processor.get_embedding.assert_called_once_with(original_image)
    vector_db.query_embedding.assert_called_once()
    np.testing.assert_array_equal(vector_db.query_embedding.call_args.args[0], embedding)


def test_verify_person_returns_unregistered_when_no_metadata_matches(service_module):
    service, image_processor, vector_db = service_module
    detected_faces = Image.new("RGB", (10, 10))
    image_processor.detect_faces.return_value = (detected_faces, True)
    image_processor.get_embedding.return_value = FakeEmbedding(np.array([0.1, 0.2]))
    vector_db.query_embedding.return_value = (None, 1.5)

    name, image = service.verify_person(Image.new("RGB", (10, 10)))

    assert name == "Unregistered Person"
    assert image is detected_faces


def test_verify_person_raises_when_vector_db_has_no_records(service_module):
    service, image_processor, vector_db = service_module
    detected_faces = Image.new("RGB", (10, 10))
    image_processor.detect_faces.return_value = (detected_faces, True)
    image_processor.get_embedding.return_value = FakeEmbedding(np.array([0.1, 0.2]))
    vector_db.query_embedding.side_effect = ValueError(
        "No record found in the vector database. Add a person before verifying faces."
    )

    with pytest.raises(ValueError, match="Add a person before verifying faces"):
        service.verify_person(Image.new("RGB", (10, 10)))


def test_verify_person_raises_when_no_face_is_detected(service_module):
    service, image_processor, vector_db = service_module
    image_processor.detect_faces.return_value = (Image.new("RGB", (10, 10)), False)

    with pytest.raises(FaceNotDetectedError, match="No faces were detected"):
        service.verify_person(Image.new("RGB", (10, 10)))

    image_processor.get_embedding.assert_not_called()
    vector_db.query_embedding.assert_not_called()


def test_verify_person_raises_when_embedding_is_none(service_module):
    service, image_processor, vector_db = service_module
    image_processor.detect_faces.return_value = (Image.new("RGB", (10, 10)), True)
    image_processor.get_embedding.return_value = None

    with pytest.raises(FaceNotDetectedError, match="No faces were detected"):
        service.verify_person(Image.new("RGB", (10, 10)))

    vector_db.query_embedding.assert_not_called()