backend.tests.unit.components.camera_verification.faceid.test_faceid_service

  1from backend.components.camera_verification.faceid import faceidService
  2from backend.components.workers import workerService
  3import pytest
  4import os
  5import numpy as np
  6import face_recognition
  7from unittest.mock import patch, MagicMock
  8from datetime import datetime
  9
 10from backend.components.camera_verification.faceid.faceidService import (
 11    verify_worker_face,
 12    FaceIDError,
 13    MultipleWorkersError,
 14    NoFacesFoundError,
 15    FaceNotMatchingError
 16)
 17from backend.database.models import Worker
 18
 19
 20# ============================================================================
 21# Fixtures & Setup
 22# ============================================================================
 23
 24@pytest.fixture
 25def mock_worker(app):
 26    """Creates a simple mock of a Worker object."""
 27    with app.app_context():
 28        worker = MagicMock(spec=Worker)
 29        worker.id = 1
 30        worker.name = "Test Worker Mock"
 31        return worker
 32
 33@pytest.fixture
 34def fake_image():
 35    """Creates an empty numpy array imitating an image."""
 36    return np.zeros((100, 100, 3), dtype=np.uint8)
 37
 38
 39@pytest.fixture
 40def test_image_path():
 41    current_dir = os.path.dirname(os.path.abspath(__file__))
 42    path = os.path.normpath(os.path.join(current_dir, "../../../../../tests/assets/testimg.jpg"))
 43    if not os.path.exists(path):
 44        pytest.skip(f"Test image not found at {path}")
 45    return path
 46
 47
 48@pytest.fixture
 49def test_worker(app, db_session, test_image_path):  # <--- Dodano db_session
 50    """Creates a real worker with embedding from the testimg.jpg file."""
 51    with app.app_context():
 52        # Load and create embedding from test image
 53        test_image = face_recognition.load_image_file(test_image_path)
 54
 55        worker = workerService.create_worker(
 56            name="Test Worker Real",
 57            face_image=test_image,
 58            expiration_date=datetime(2030, 12, 31)
 59        )
 60        yield worker
 61
 62# --------------------------------------------------------------------------
 63# Testy verify_worker_face
 64# --------------------------------------------------------------------------
 65
 66@patch('backend.components.camera_verification.faceid.faceidService.face_recognition.compare_faces')
 67@patch('backend.components.camera_verification.faceid.faceidService.face_recognition.face_encodings')
 68@patch('backend.components.camera_verification.faceid.faceidService.get_worker_embedding')
 69def test_verify_worker_face_success_mocked(mock_get_embedding, mock_encodings, mock_compare, mock_worker, fake_image):
 70    """Success Test Using Mocks."""
 71    fake_worker_embedding = [np.random.rand(128)]
 72    fake_scanned_embedding = [np.random.rand(128)]
 73
 74    mock_get_embedding.return_value = fake_worker_embedding
 75    mock_encodings.return_value = fake_scanned_embedding
 76    mock_compare.return_value = [True]
 77
 78    result = verify_worker_face(mock_worker, fake_image)
 79
 80    assert result == [True]
 81
 82
 83@patch('backend.components.camera_verification.faceid.faceidService.face_recognition.face_encodings')
 84@patch('backend.components.camera_verification.faceid.faceidService.get_worker_embedding')
 85def test_verify_worker_face_multiple_faces_mocked(mock_get_embedding, mock_encodings, mock_worker, fake_image):
 86    """Multiple Face Error Test (Mock)."""
 87    mock_get_embedding.return_value = [np.random.rand(128)]
 88    mock_encodings.return_value = [np.random.rand(128), np.random.rand(128)]
 89
 90    with pytest.raises(MultipleWorkersError, match="Wykryto więcej niż jednego pracownika"):
 91        verify_worker_face(mock_worker, fake_image)
 92
 93@patch('backend.components.camera_verification.faceid.faceidService.face_recognition.compare_faces')
 94@patch('backend.components.camera_verification.faceid.faceidService.face_recognition.face_encodings')
 95@patch('backend.components.camera_verification.faceid.faceidService.get_worker_embedding')
 96def test_verify_worker_face_not_matching_mocked(mock_get_embedding, mock_encodings, mock_compare, mock_worker, fake_image):
 97    """Face mismatch test (mock)."""
 98    mock_get_embedding.return_value = [np.random.rand(128)]
 99    mock_encodings.return_value = [np.random.rand(128)]
100    mock_compare.return_value = [False]
101
102    with pytest.raises(FaceNotMatchingError, match="Niezgodność zeskanowanej twarzy"):
103        verify_worker_face(mock_worker, fake_image)
104
105
106# ============================================================================
107# Test verify_worker_face - Success Cases
108# ============================================================================
109
110
111def test_verify_worker_face_matching_real(app, test_worker, test_image_path):
112    """Test successful face verification when faces match."""
113    with app.app_context():
114        checked_image = face_recognition.load_image_file(test_image_path)
115        result = faceidService.verify_worker_face(test_worker, checked_image)
116
117        assert result is not None
118        assert len(result) > 0
119        assert result[0]
120
121
122def test_verify_worker_face_with_different_image_real(app, test_worker):
123    """Test face verification with a different person (should fail)."""
124    dummy_image = np.zeros((600, 800, 3), dtype=np.uint8)
125
126    with app.app_context():
127        with pytest.raises(faceidService.NoFacesFoundError):
128            faceidService.verify_worker_face(test_worker, dummy_image)
129
130
131
132
133# ============================================================================
134# Test verify_worker_face - Error Cases
135# ============================================================================
136
137def test_verify_worker_face_no_faces_detected(app, test_worker):
138    """Test verification fails when no face is detected in checked image."""
139    # Create an image with no faces (random noise)
140    blank_image = np.zeros((600, 800, 3), dtype=np.uint8)
141
142    with app.app_context():
143        with pytest.raises(faceidService.NoFacesFoundError) as exc_info:
144            faceidService.verify_worker_face(test_worker, blank_image)
145
146        assert "Nie znaleziono twarzy" in str(exc_info.value)
147
148
149@patch('backend.components.camera_verification.faceid.faceidService.face_recognition.compare_faces')
150@patch('backend.components.camera_verification.faceid.faceidService.face_recognition.face_encodings')
151@patch('backend.components.camera_verification.faceid.faceidService.get_worker_embedding')
152def test_verify_worker_face_not_matching(mock_get_embedding, mock_encodings, mock_compare, mock_worker, fake_image):
153    """
154    Checks whether a FaceNotMatchingError exception is thrown when a face does not match the worker.
155    """
156    mock_get_embedding.return_value = [np.random.rand(128)]
157    mock_encodings.return_value = [np.random.rand(128)]
158    # Porównanie zwraca False
159    mock_compare.return_value = [False]
160
161    with pytest.raises(FaceNotMatchingError, match="Niezgodność zeskanowanej twarzy"):
162        verify_worker_face(mock_worker, fake_image)
163
164
165@patch('backend.components.camera_verification.faceid.faceidService.face_recognition.face_encodings')
166@patch('backend.components.camera_verification.faceid.faceidService.get_worker_embedding')
167def test_verify_worker_face_library_error(mock_get_embedding, mock_encodings, mock_worker, fake_image):
168    """
169    Checks whether generic face_recognition library errors are caught and thrown as FaceIDError.
170    """
171    mock_get_embedding.return_value = [np.random.rand(128)]
172    # Symulujemy błąd biblioteki (np. problem z pamięcią, zły format danych)
173    mock_encodings.side_effect = Exception("Critical Library Fail")
174
175    with pytest.raises(FaceIDError, match="Critical Library Fail"):
176        verify_worker_face(mock_worker, fake_image)
177
178
179def test_verify_worker_face_multiple_faces_detected_real(app, test_worker, test_image_path):
180    """Test verification fails when multiple faces are detected."""
181    with app.app_context():
182        test_image = face_recognition.load_image_file(test_image_path)
183
184        with patch('face_recognition.face_encodings') as mock_encodings:
185            mock_encodings.return_value = [np.random.randn(128), np.random.randn(128)]
186
187            with pytest.raises(faceidService.MultipleWorkersError) as exc_info:
188                faceidService.verify_worker_face(test_worker, test_image)
189
190            assert "więcej niż jednego" in str(exc_info.value).lower()
191
192
193def test_verify_worker_face_not_matching_real(app, test_worker, test_image_path):
194    """Test verification fails when face does not match worker."""
195    with app.app_context():
196        test_image = face_recognition.load_image_file(test_image_path)
197
198        # Mock face_recognition to return non-matching faces
199        with patch('face_recognition.compare_faces') as mock_compare:
200            mock_compare.return_value = [False]
201
202            with pytest.raises(faceidService.FaceNotMatchingError) as exc_info:
203                faceidService.verify_worker_face(test_worker, test_image)
204
205            assert "Niezgodność" in str(exc_info.value)
206
207
208def test_verify_worker_face_encoding_error(app, test_worker):
209    """Test handling of face encoding errors."""
210    dummy_image = np.zeros((600, 800, 3), dtype=np.uint8)
211
212    with app.app_context():
213        with patch('face_recognition.face_encodings') as mock_encodings:
214            mock_encodings.side_effect = Exception("Face recognition library error")
215
216            with pytest.raises(faceidService.FaceIDError):
217                faceidService.verify_worker_face(test_worker, dummy_image)
218
219
220def test_verify_worker_face_comparison_error(app, test_worker):
221    """Test handling of face comparison errors."""
222    current_dir = os.path.dirname(os.path.abspath(__file__))
223    image_path = os.path.normpath(os.path.join(current_dir, "../../../../assets/testimg.jpg"))
224
225    if not os.path.exists(image_path):
226        pytest.skip(f"Test image not found at {image_path}")
227
228    with app.app_context():
229        test_image = face_recognition.load_image_file(image_path)
230
231        with patch('face_recognition.compare_faces') as mock_compare:
232            mock_compare.side_effect = Exception("Comparison error")
233
234            with pytest.raises(faceidService.FaceIDError):
235                faceidService.verify_worker_face(test_worker, test_image)
@pytest.fixture
def mock_worker(app):
25@pytest.fixture
26def mock_worker(app):
27    """Creates a simple mock of a Worker object."""
28    with app.app_context():
29        worker = MagicMock(spec=Worker)
30        worker.id = 1
31        worker.name = "Test Worker Mock"
32        return worker

Creates a simple mock of a Worker object.

@pytest.fixture
def fake_image():
34@pytest.fixture
35def fake_image():
36    """Creates an empty numpy array imitating an image."""
37    return np.zeros((100, 100, 3), dtype=np.uint8)

Creates an empty numpy array imitating an image.

@pytest.fixture
def test_image_path():
40@pytest.fixture
41def test_image_path():
42    current_dir = os.path.dirname(os.path.abspath(__file__))
43    path = os.path.normpath(os.path.join(current_dir, "../../../../../tests/assets/testimg.jpg"))
44    if not os.path.exists(path):
45        pytest.skip(f"Test image not found at {path}")
46    return path
@pytest.fixture
def test_worker(app, db_session, test_image_path):
49@pytest.fixture
50def test_worker(app, db_session, test_image_path):  # <--- Dodano db_session
51    """Creates a real worker with embedding from the testimg.jpg file."""
52    with app.app_context():
53        # Load and create embedding from test image
54        test_image = face_recognition.load_image_file(test_image_path)
55
56        worker = workerService.create_worker(
57            name="Test Worker Real",
58            face_image=test_image,
59            expiration_date=datetime(2030, 12, 31)
60        )
61        yield worker

Creates a real worker with embedding from the testimg.jpg file.

@patch('backend.components.camera_verification.faceid.faceidService.face_recognition.compare_faces')
@patch('backend.components.camera_verification.faceid.faceidService.face_recognition.face_encodings')
@patch('backend.components.camera_verification.faceid.faceidService.get_worker_embedding')
def test_verify_worker_face_success_mocked( mock_get_embedding, mock_encodings, mock_compare, mock_worker, fake_image):
67@patch('backend.components.camera_verification.faceid.faceidService.face_recognition.compare_faces')
68@patch('backend.components.camera_verification.faceid.faceidService.face_recognition.face_encodings')
69@patch('backend.components.camera_verification.faceid.faceidService.get_worker_embedding')
70def test_verify_worker_face_success_mocked(mock_get_embedding, mock_encodings, mock_compare, mock_worker, fake_image):
71    """Success Test Using Mocks."""
72    fake_worker_embedding = [np.random.rand(128)]
73    fake_scanned_embedding = [np.random.rand(128)]
74
75    mock_get_embedding.return_value = fake_worker_embedding
76    mock_encodings.return_value = fake_scanned_embedding
77    mock_compare.return_value = [True]
78
79    result = verify_worker_face(mock_worker, fake_image)
80
81    assert result == [True]

Success Test Using Mocks.

@patch('backend.components.camera_verification.faceid.faceidService.face_recognition.face_encodings')
@patch('backend.components.camera_verification.faceid.faceidService.get_worker_embedding')
def test_verify_worker_face_multiple_faces_mocked(mock_get_embedding, mock_encodings, mock_worker, fake_image):
84@patch('backend.components.camera_verification.faceid.faceidService.face_recognition.face_encodings')
85@patch('backend.components.camera_verification.faceid.faceidService.get_worker_embedding')
86def test_verify_worker_face_multiple_faces_mocked(mock_get_embedding, mock_encodings, mock_worker, fake_image):
87    """Multiple Face Error Test (Mock)."""
88    mock_get_embedding.return_value = [np.random.rand(128)]
89    mock_encodings.return_value = [np.random.rand(128), np.random.rand(128)]
90
91    with pytest.raises(MultipleWorkersError, match="Wykryto więcej niż jednego pracownika"):
92        verify_worker_face(mock_worker, fake_image)

Multiple Face Error Test (Mock).

@patch('backend.components.camera_verification.faceid.faceidService.face_recognition.compare_faces')
@patch('backend.components.camera_verification.faceid.faceidService.face_recognition.face_encodings')
@patch('backend.components.camera_verification.faceid.faceidService.get_worker_embedding')
def test_verify_worker_face_not_matching_mocked( mock_get_embedding, mock_encodings, mock_compare, mock_worker, fake_image):
 94@patch('backend.components.camera_verification.faceid.faceidService.face_recognition.compare_faces')
 95@patch('backend.components.camera_verification.faceid.faceidService.face_recognition.face_encodings')
 96@patch('backend.components.camera_verification.faceid.faceidService.get_worker_embedding')
 97def test_verify_worker_face_not_matching_mocked(mock_get_embedding, mock_encodings, mock_compare, mock_worker, fake_image):
 98    """Face mismatch test (mock)."""
 99    mock_get_embedding.return_value = [np.random.rand(128)]
100    mock_encodings.return_value = [np.random.rand(128)]
101    mock_compare.return_value = [False]
102
103    with pytest.raises(FaceNotMatchingError, match="Niezgodność zeskanowanej twarzy"):
104        verify_worker_face(mock_worker, fake_image)

Face mismatch test (mock).

def test_verify_worker_face_matching_real(app, test_worker, test_image_path):
112def test_verify_worker_face_matching_real(app, test_worker, test_image_path):
113    """Test successful face verification when faces match."""
114    with app.app_context():
115        checked_image = face_recognition.load_image_file(test_image_path)
116        result = faceidService.verify_worker_face(test_worker, checked_image)
117
118        assert result is not None
119        assert len(result) > 0
120        assert result[0]

Test successful face verification when faces match.

def test_verify_worker_face_with_different_image_real(app, test_worker):
123def test_verify_worker_face_with_different_image_real(app, test_worker):
124    """Test face verification with a different person (should fail)."""
125    dummy_image = np.zeros((600, 800, 3), dtype=np.uint8)
126
127    with app.app_context():
128        with pytest.raises(faceidService.NoFacesFoundError):
129            faceidService.verify_worker_face(test_worker, dummy_image)

Test face verification with a different person (should fail).

def test_verify_worker_face_no_faces_detected(app, test_worker):
138def test_verify_worker_face_no_faces_detected(app, test_worker):
139    """Test verification fails when no face is detected in checked image."""
140    # Create an image with no faces (random noise)
141    blank_image = np.zeros((600, 800, 3), dtype=np.uint8)
142
143    with app.app_context():
144        with pytest.raises(faceidService.NoFacesFoundError) as exc_info:
145            faceidService.verify_worker_face(test_worker, blank_image)
146
147        assert "Nie znaleziono twarzy" in str(exc_info.value)

Test verification fails when no face is detected in checked image.

@patch('backend.components.camera_verification.faceid.faceidService.face_recognition.compare_faces')
@patch('backend.components.camera_verification.faceid.faceidService.face_recognition.face_encodings')
@patch('backend.components.camera_verification.faceid.faceidService.get_worker_embedding')
def test_verify_worker_face_not_matching( mock_get_embedding, mock_encodings, mock_compare, mock_worker, fake_image):
150@patch('backend.components.camera_verification.faceid.faceidService.face_recognition.compare_faces')
151@patch('backend.components.camera_verification.faceid.faceidService.face_recognition.face_encodings')
152@patch('backend.components.camera_verification.faceid.faceidService.get_worker_embedding')
153def test_verify_worker_face_not_matching(mock_get_embedding, mock_encodings, mock_compare, mock_worker, fake_image):
154    """
155    Checks whether a FaceNotMatchingError exception is thrown when a face does not match the worker.
156    """
157    mock_get_embedding.return_value = [np.random.rand(128)]
158    mock_encodings.return_value = [np.random.rand(128)]
159    # Porównanie zwraca False
160    mock_compare.return_value = [False]
161
162    with pytest.raises(FaceNotMatchingError, match="Niezgodność zeskanowanej twarzy"):
163        verify_worker_face(mock_worker, fake_image)

Checks whether a FaceNotMatchingError exception is thrown when a face does not match the worker.

@patch('backend.components.camera_verification.faceid.faceidService.face_recognition.face_encodings')
@patch('backend.components.camera_verification.faceid.faceidService.get_worker_embedding')
def test_verify_worker_face_library_error(mock_get_embedding, mock_encodings, mock_worker, fake_image):
166@patch('backend.components.camera_verification.faceid.faceidService.face_recognition.face_encodings')
167@patch('backend.components.camera_verification.faceid.faceidService.get_worker_embedding')
168def test_verify_worker_face_library_error(mock_get_embedding, mock_encodings, mock_worker, fake_image):
169    """
170    Checks whether generic face_recognition library errors are caught and thrown as FaceIDError.
171    """
172    mock_get_embedding.return_value = [np.random.rand(128)]
173    # Symulujemy błąd biblioteki (np. problem z pamięcią, zły format danych)
174    mock_encodings.side_effect = Exception("Critical Library Fail")
175
176    with pytest.raises(FaceIDError, match="Critical Library Fail"):
177        verify_worker_face(mock_worker, fake_image)

Checks whether generic face_recognition library errors are caught and thrown as FaceIDError.

def test_verify_worker_face_multiple_faces_detected_real(app, test_worker, test_image_path):
180def test_verify_worker_face_multiple_faces_detected_real(app, test_worker, test_image_path):
181    """Test verification fails when multiple faces are detected."""
182    with app.app_context():
183        test_image = face_recognition.load_image_file(test_image_path)
184
185        with patch('face_recognition.face_encodings') as mock_encodings:
186            mock_encodings.return_value = [np.random.randn(128), np.random.randn(128)]
187
188            with pytest.raises(faceidService.MultipleWorkersError) as exc_info:
189                faceidService.verify_worker_face(test_worker, test_image)
190
191            assert "więcej niż jednego" in str(exc_info.value).lower()

Test verification fails when multiple faces are detected.

def test_verify_worker_face_not_matching_real(app, test_worker, test_image_path):
194def test_verify_worker_face_not_matching_real(app, test_worker, test_image_path):
195    """Test verification fails when face does not match worker."""
196    with app.app_context():
197        test_image = face_recognition.load_image_file(test_image_path)
198
199        # Mock face_recognition to return non-matching faces
200        with patch('face_recognition.compare_faces') as mock_compare:
201            mock_compare.return_value = [False]
202
203            with pytest.raises(faceidService.FaceNotMatchingError) as exc_info:
204                faceidService.verify_worker_face(test_worker, test_image)
205
206            assert "Niezgodność" in str(exc_info.value)

Test verification fails when face does not match worker.

def test_verify_worker_face_encoding_error(app, test_worker):
209def test_verify_worker_face_encoding_error(app, test_worker):
210    """Test handling of face encoding errors."""
211    dummy_image = np.zeros((600, 800, 3), dtype=np.uint8)
212
213    with app.app_context():
214        with patch('face_recognition.face_encodings') as mock_encodings:
215            mock_encodings.side_effect = Exception("Face recognition library error")
216
217            with pytest.raises(faceidService.FaceIDError):
218                faceidService.verify_worker_face(test_worker, dummy_image)

Test handling of face encoding errors.

def test_verify_worker_face_comparison_error(app, test_worker):
221def test_verify_worker_face_comparison_error(app, test_worker):
222    """Test handling of face comparison errors."""
223    current_dir = os.path.dirname(os.path.abspath(__file__))
224    image_path = os.path.normpath(os.path.join(current_dir, "../../../../assets/testimg.jpg"))
225
226    if not os.path.exists(image_path):
227        pytest.skip(f"Test image not found at {image_path}")
228
229    with app.app_context():
230        test_image = face_recognition.load_image_file(image_path)
231
232        with patch('face_recognition.compare_faces') as mock_compare:
233            mock_compare.side_effect = Exception("Comparison error")
234
235            with pytest.raises(faceidService.FaceIDError):
236                faceidService.verify_worker_face(test_worker, test_image)

Test handling of face comparison errors.