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):
12def mock_response_obj(code, message, logged=True):
13    r = MagicMock()
14    r.code = code
15    r.message = message
16    r.logged = logged
17    r.asdict.return_value = {"code": code, "message": message, "logged": logged}
18    return r
@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.