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)
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.
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.
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.
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.
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).
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).
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.
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).
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.
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.
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.
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.
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.
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.
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.