backend.tests.api.test_verification_api
1import pytest 2import io 3from unittest.mock import patch, MagicMock 4from backend.components.camera_verification.qrcode.qrcodeService import NoCodeFoundError 5from backend.components.camera_verification.faceid.faceidService import FaceNotMatchingError 6 7# ============================================================================ 8# Test Helpers & Fixtures 9# ============================================================================ 10 11def mock_response_obj(code, message, logged=True): 12 r = MagicMock() 13 r.code = code 14 r.message = message 15 r.logged = logged 16 r.asdict.return_value = {"code": code, "message": message, "logged": logged} 17 return r 18 19 20@pytest.fixture 21def mock_image_file(): 22 """Creates a fake image file to upload in the form.""" 23 return (io.BytesIO(b"fake_image_bytes"), "test_image.jpg") 24 25# ============================================================================ 26# Test Basic Validation (Input) 27# ============================================================================ 28 29def test_post_scan_no_file(client): 30 """ 31 Checks the endpoint's behavior when a file has not been transferred. 32 Expected status: 400. 33 """ 34 response = client.post('/api/skan') 35 assert response.status_code == 400 36 assert "Brak pliku" in response.get_json()['error'] 37 38 39# ============================================================================ 40# Test Happy Path (Success) 41# ============================================================================ 42 43@patch('backend.components.camera_verification.verificationController.parse_image') 44@patch('backend.components.camera_verification.verificationController.get_worker_from_qr_code') 45@patch('backend.components.camera_verification.verificationController.verify_worker_face') 46@patch('backend.components.camera_verification.verificationController.verification_response_handler') 47@patch('backend.components.camera_verification.verificationController.log_worker_entry') 48def test_post_scan_success(mock_log, mock_handler, mock_verify, mock_get_worker, mock_parse, client, mock_image_file): 49 """ 50 Positive scenario: valid QR code and matching face. 51 Expected status: 200. 52 """ 53 # 1. Konfiguracja mocków dla sukcesu 54 mock_get_worker.return_value = MagicMock(id=1, name="Jan Testowy") 55 mock_verify.return_value = True # Twarz zweryfikowana 56 57 # Handler zwraca kod 0 (sukces) 58 mock_handler.return_value = mock_response_obj(code=0, message="Weryfikacja udana") 59 60 # 2. Wykonanie żądania 61 data = {'file': mock_image_file} 62 response = client.post('/api/skan', data=data, content_type='multipart/form-data') 63 64 # 3. Asercje 65 assert response.status_code == 200 66 json_data = response.get_json() 67 assert json_data['code'] == 0 68 assert json_data['message'] == "Weryfikacja udana" 69 70 # Sprawdzenie czy wywołano logowanie 71 mock_log.assert_called_once() 72 73# ============================================================================ 74# Test Error Mapping Logic (Exceptions -> HTTP Codes) 75# ============================================================================ 76 77@patch('backend.components.camera_verification.verificationController.parse_image') 78@patch('backend.components.camera_verification.verificationController.get_worker_from_qr_code') 79@patch('backend.components.camera_verification.verificationController.verification_response_handler') 80@patch('backend.components.camera_verification.verificationController.log_worker_entry') 81def test_post_scan_qr_error_bad_request(mock_log, mock_handler, mock_get_worker, mock_parse, client, mock_image_file): 82 """ 83 Client error scenario (400): e.g., missing QR code or corrupted code. 84 The controller maps codes < 10 to HTTP 400. 85 """ 86 # Symulujemy rzucenie wyjątku przez serwis QR 87 mock_get_worker.side_effect = NoCodeFoundError("Nie znaleziono kodu") 88 89 # Symulujemy, że handler mapuje ten błąd na kod wewnętrzny 2 (z zakresu < 10) 90 mock_handler.return_value = mock_response_obj(code=2, message="Nie znaleziono kodu QR") 91 92 data = {'file': mock_image_file} 93 response = client.post('/api/skan', data=data, content_type='multipart/form-data') 94 95 assert response.status_code == 400 96 assert response.get_json()['code'] == 2 97 # Nawet przy błędzie logowanie powinno zostać wywołane (jeśli handler ustawił logged=True) 98 mock_log.assert_called_once() 99 100 101@patch('backend.components.camera_verification.verificationController.parse_image') 102@patch('backend.components.camera_verification.verificationController.get_worker_from_qr_code') 103@patch('backend.components.camera_verification.verificationController.verify_worker_face') 104@patch('backend.components.camera_verification.verificationController.verification_response_handler') 105@patch('backend.components.camera_verification.verificationController.log_worker_entry') 106def test_post_scan_face_mismatch_forbidden(mock_log, mock_handler, mock_verify, mock_get_worker, mock_parse, client, 107 mock_image_file): 108 """ 109 Permission error scenario (403): Face does not match pattern. 110 The controller maps codes >= 10 (that are not a multiple of 10) to HTTP 403. 111 """ 112 mock_get_worker.return_value = MagicMock(id=1) 113 114 # Symulacja błędu weryfikacji twarzy 115 mock_verify.side_effect = FaceNotMatchingError("Twarz nie pasuje") 116 117 # Symulujemy, że handler mapuje to na kod 12 118 mock_handler.return_value = mock_response_obj(code=12, message="Twarz nie pasuje do wzorca") 119 120 data = {'file': mock_image_file} 121 response = client.post('/api/skan', data=data, content_type='multipart/form-data') 122 123 assert response.status_code == 403 124 assert response.get_json()['code'] == 12 125 126 # Upewniamy się, że próba wejścia została zalogowana 127 mock_log.assert_called_once() 128 129 130@patch('backend.components.camera_verification.verificationController.parse_image') 131@patch('backend.components.camera_verification.verificationController.get_worker_from_qr_code') 132@patch('backend.components.camera_verification.verificationController.verification_response_handler') 133@patch('backend.components.camera_verification.verificationController.log_worker_entry') 134def test_post_scan_internal_server_error(mock_log, mock_handler, mock_get_worker, mock_parse, client, mock_image_file): 135 """ 136 Server error scenario (500). 137 The controller maps codes that are multiples of 10 (e.g., 20, 30) or -1 to HTTP 500. 138 """ 139 mock_get_worker.side_effect = Exception("Nieoczekiwany błąd bazy danych") 140 141 # Symulujemy kod błędu krytycznego (np. 20) 142 mock_handler.return_value = mock_response_obj(code=20, message="Błąd wewnętrzny") 143 144 data = {'file': mock_image_file} 145 response = client.post('/api/skan', data=data, content_type='multipart/form-data') 146 147 assert response.status_code == 500 148 assert response.get_json()['code'] == 20 149 150 mock_log.assert_called_once() 151 152# ============================================================================ 153# Test Audit Logging 154# ============================================================================ 155 156@patch('backend.components.camera_verification.verificationController.parse_image') 157@patch('backend.components.camera_verification.verificationController.get_worker_from_qr_code') 158@patch('backend.components.camera_verification.verificationController.verify_worker_face') 159@patch('backend.components.camera_verification.verificationController.verification_response_handler') 160@patch('backend.components.camera_verification.verificationController.log_worker_entry') 161def test_scan_logs_even_on_failure(mock_log, mock_handler, mock_verify, mock_get_worker, mock_parse, client, 162 mock_image_file): 163 """ 164 Verifies that the log_worker_entry function is called even if it fails if the handler returns 165 the logged=True flag. 166 """ 167 mock_verify.side_effect = FaceNotMatchingError("Błąd") 168 169 # logged=True oznacza, że próba wejścia ma zostać odnotowana w bazie 170 mock_handler.return_value = mock_response_obj(code=12, message="Fail", logged=True) 171 172 client.post('/api/skan', data={'file': mock_image_file}, content_type='multipart/form-data') 173 174 mock_log.assert_called_once() 175 # Sprawdzamy czy przekazano odpowiednie argumenty do logowania 176 args, _ = mock_log.call_args 177 assert args[0] == 12 # code 178 assert args[1] == "Fail" # message
def
mock_response_obj(code, message, logged=True):
@pytest.fixture
def
mock_image_file():
21@pytest.fixture 22def mock_image_file(): 23 """Creates a fake image file to upload in the form.""" 24 return (io.BytesIO(b"fake_image_bytes"), "test_image.jpg")
Creates a fake image file to upload in the form.
def
test_post_scan_no_file(client):
30def test_post_scan_no_file(client): 31 """ 32 Checks the endpoint's behavior when a file has not been transferred. 33 Expected status: 400. 34 """ 35 response = client.post('/api/skan') 36 assert response.status_code == 400 37 assert "Brak pliku" in response.get_json()['error']
Checks the endpoint's behavior when a file has not been transferred. Expected status: 400.
@patch('backend.components.camera_verification.verificationController.parse_image')
@patch('backend.components.camera_verification.verificationController.get_worker_from_qr_code')
@patch('backend.components.camera_verification.verificationController.verify_worker_face')
@patch('backend.components.camera_verification.verificationController.verification_response_handler')
@patch('backend.components.camera_verification.verificationController.log_worker_entry')
def
test_post_scan_success( mock_log, mock_handler, mock_verify, mock_get_worker, mock_parse, client, mock_image_file):
44@patch('backend.components.camera_verification.verificationController.parse_image') 45@patch('backend.components.camera_verification.verificationController.get_worker_from_qr_code') 46@patch('backend.components.camera_verification.verificationController.verify_worker_face') 47@patch('backend.components.camera_verification.verificationController.verification_response_handler') 48@patch('backend.components.camera_verification.verificationController.log_worker_entry') 49def test_post_scan_success(mock_log, mock_handler, mock_verify, mock_get_worker, mock_parse, client, mock_image_file): 50 """ 51 Positive scenario: valid QR code and matching face. 52 Expected status: 200. 53 """ 54 # 1. Konfiguracja mocków dla sukcesu 55 mock_get_worker.return_value = MagicMock(id=1, name="Jan Testowy") 56 mock_verify.return_value = True # Twarz zweryfikowana 57 58 # Handler zwraca kod 0 (sukces) 59 mock_handler.return_value = mock_response_obj(code=0, message="Weryfikacja udana") 60 61 # 2. Wykonanie żądania 62 data = {'file': mock_image_file} 63 response = client.post('/api/skan', data=data, content_type='multipart/form-data') 64 65 # 3. Asercje 66 assert response.status_code == 200 67 json_data = response.get_json() 68 assert json_data['code'] == 0 69 assert json_data['message'] == "Weryfikacja udana" 70 71 # Sprawdzenie czy wywołano logowanie 72 mock_log.assert_called_once()
Positive scenario: valid QR code and matching face. Expected status: 200.
@patch('backend.components.camera_verification.verificationController.parse_image')
@patch('backend.components.camera_verification.verificationController.get_worker_from_qr_code')
@patch('backend.components.camera_verification.verificationController.verification_response_handler')
@patch('backend.components.camera_verification.verificationController.log_worker_entry')
def
test_post_scan_qr_error_bad_request( mock_log, mock_handler, mock_get_worker, mock_parse, client, mock_image_file):
78@patch('backend.components.camera_verification.verificationController.parse_image') 79@patch('backend.components.camera_verification.verificationController.get_worker_from_qr_code') 80@patch('backend.components.camera_verification.verificationController.verification_response_handler') 81@patch('backend.components.camera_verification.verificationController.log_worker_entry') 82def test_post_scan_qr_error_bad_request(mock_log, mock_handler, mock_get_worker, mock_parse, client, mock_image_file): 83 """ 84 Client error scenario (400): e.g., missing QR code or corrupted code. 85 The controller maps codes < 10 to HTTP 400. 86 """ 87 # Symulujemy rzucenie wyjątku przez serwis QR 88 mock_get_worker.side_effect = NoCodeFoundError("Nie znaleziono kodu") 89 90 # Symulujemy, że handler mapuje ten błąd na kod wewnętrzny 2 (z zakresu < 10) 91 mock_handler.return_value = mock_response_obj(code=2, message="Nie znaleziono kodu QR") 92 93 data = {'file': mock_image_file} 94 response = client.post('/api/skan', data=data, content_type='multipart/form-data') 95 96 assert response.status_code == 400 97 assert response.get_json()['code'] == 2 98 # Nawet przy błędzie logowanie powinno zostać wywołane (jeśli handler ustawił logged=True) 99 mock_log.assert_called_once()
Client error scenario (400): e.g., missing QR code or corrupted code. The controller maps codes < 10 to HTTP 400.
@patch('backend.components.camera_verification.verificationController.parse_image')
@patch('backend.components.camera_verification.verificationController.get_worker_from_qr_code')
@patch('backend.components.camera_verification.verificationController.verify_worker_face')
@patch('backend.components.camera_verification.verificationController.verification_response_handler')
@patch('backend.components.camera_verification.verificationController.log_worker_entry')
def
test_post_scan_face_mismatch_forbidden( mock_log, mock_handler, mock_verify, mock_get_worker, mock_parse, client, mock_image_file):
102@patch('backend.components.camera_verification.verificationController.parse_image') 103@patch('backend.components.camera_verification.verificationController.get_worker_from_qr_code') 104@patch('backend.components.camera_verification.verificationController.verify_worker_face') 105@patch('backend.components.camera_verification.verificationController.verification_response_handler') 106@patch('backend.components.camera_verification.verificationController.log_worker_entry') 107def test_post_scan_face_mismatch_forbidden(mock_log, mock_handler, mock_verify, mock_get_worker, mock_parse, client, 108 mock_image_file): 109 """ 110 Permission error scenario (403): Face does not match pattern. 111 The controller maps codes >= 10 (that are not a multiple of 10) to HTTP 403. 112 """ 113 mock_get_worker.return_value = MagicMock(id=1) 114 115 # Symulacja błędu weryfikacji twarzy 116 mock_verify.side_effect = FaceNotMatchingError("Twarz nie pasuje") 117 118 # Symulujemy, że handler mapuje to na kod 12 119 mock_handler.return_value = mock_response_obj(code=12, message="Twarz nie pasuje do wzorca") 120 121 data = {'file': mock_image_file} 122 response = client.post('/api/skan', data=data, content_type='multipart/form-data') 123 124 assert response.status_code == 403 125 assert response.get_json()['code'] == 12 126 127 # Upewniamy się, że próba wejścia została zalogowana 128 mock_log.assert_called_once()
Permission error scenario (403): Face does not match pattern. The controller maps codes >= 10 (that are not a multiple of 10) to HTTP 403.
@patch('backend.components.camera_verification.verificationController.parse_image')
@patch('backend.components.camera_verification.verificationController.get_worker_from_qr_code')
@patch('backend.components.camera_verification.verificationController.verification_response_handler')
@patch('backend.components.camera_verification.verificationController.log_worker_entry')
def
test_post_scan_internal_server_error( mock_log, mock_handler, mock_get_worker, mock_parse, client, mock_image_file):
131@patch('backend.components.camera_verification.verificationController.parse_image') 132@patch('backend.components.camera_verification.verificationController.get_worker_from_qr_code') 133@patch('backend.components.camera_verification.verificationController.verification_response_handler') 134@patch('backend.components.camera_verification.verificationController.log_worker_entry') 135def test_post_scan_internal_server_error(mock_log, mock_handler, mock_get_worker, mock_parse, client, mock_image_file): 136 """ 137 Server error scenario (500). 138 The controller maps codes that are multiples of 10 (e.g., 20, 30) or -1 to HTTP 500. 139 """ 140 mock_get_worker.side_effect = Exception("Nieoczekiwany błąd bazy danych") 141 142 # Symulujemy kod błędu krytycznego (np. 20) 143 mock_handler.return_value = mock_response_obj(code=20, message="Błąd wewnętrzny") 144 145 data = {'file': mock_image_file} 146 response = client.post('/api/skan', data=data, content_type='multipart/form-data') 147 148 assert response.status_code == 500 149 assert response.get_json()['code'] == 20 150 151 mock_log.assert_called_once()
Server error scenario (500). The controller maps codes that are multiples of 10 (e.g., 20, 30) or -1 to HTTP 500.
@patch('backend.components.camera_verification.verificationController.parse_image')
@patch('backend.components.camera_verification.verificationController.get_worker_from_qr_code')
@patch('backend.components.camera_verification.verificationController.verify_worker_face')
@patch('backend.components.camera_verification.verificationController.verification_response_handler')
@patch('backend.components.camera_verification.verificationController.log_worker_entry')
def
test_scan_logs_even_on_failure( mock_log, mock_handler, mock_verify, mock_get_worker, mock_parse, client, mock_image_file):
157@patch('backend.components.camera_verification.verificationController.parse_image') 158@patch('backend.components.camera_verification.verificationController.get_worker_from_qr_code') 159@patch('backend.components.camera_verification.verificationController.verify_worker_face') 160@patch('backend.components.camera_verification.verificationController.verification_response_handler') 161@patch('backend.components.camera_verification.verificationController.log_worker_entry') 162def test_scan_logs_even_on_failure(mock_log, mock_handler, mock_verify, mock_get_worker, mock_parse, client, 163 mock_image_file): 164 """ 165 Verifies that the log_worker_entry function is called even if it fails if the handler returns 166 the logged=True flag. 167 """ 168 mock_verify.side_effect = FaceNotMatchingError("Błąd") 169 170 # logged=True oznacza, że próba wejścia ma zostać odnotowana w bazie 171 mock_handler.return_value = mock_response_obj(code=12, message="Fail", logged=True) 172 173 client.post('/api/skan', data={'file': mock_image_file}, content_type='multipart/form-data') 174 175 mock_log.assert_called_once() 176 # Sprawdzamy czy przekazano odpowiednie argumenty do logowania 177 args, _ = mock_log.call_args 178 assert args[0] == 12 # code 179 assert args[1] == "Fail" # message
Verifies that the log_worker_entry function is called even if it fails if the handler returns the logged=True flag.