backend.tests.api.test_reports_api
1import pytest 2from datetime import datetime, timedelta 3from backend.database.models import Entry, Worker 4 5# ============================================================================ 6# Test Fixtures & Data Setup 7# ============================================================================ 8 9@pytest.fixture 10def populate_entries(db_session, created_worker): 11 """ 12 A helper fixture that adds a series of entries to the database to test filtering and statistics. 13 It creates entries for 'created_worker' (from conftest) and an additional worker. 14 """ 15 # Dodajemy drugiego pracownika dla różnorodności 16 worker_2 = Worker( 17 name="Test Worker 2", 18 face_embedding=b'dummy_embedding', 19 expiration_date=datetime.now() + timedelta(days=30), 20 secret="secret_2" 21 ) 22 db_session.session.add(worker_2) 23 db_session.session.commit() 24 25 base_time = datetime.now() 26 27 entries = [ 28 # Wpis 1: Dzisiaj, Poprawny, Worker 1 29 Entry( 30 worker_id=created_worker.id, 31 code=0, 32 message="Wstęp przyznany", 33 date=base_time, 34 face_image=b'img1' 35 ), 36 # Wpis 2: Dzisiaj, Niepoprawny (błąd weryfikacji), Worker 1 37 Entry( 38 worker_id=created_worker.id, 39 code=1, 40 message="Błąd weryfikacji twarzy", 41 date=base_time - timedelta(minutes=10), 42 face_image=b'img2' 43 ), 44 # Wpis 3: Wczoraj, Poprawny, Worker 2 45 Entry( 46 worker_id=worker_2.id, 47 code=0, 48 message="Wstęp przyznany", 49 date=base_time - timedelta(days=1), 50 face_image=b'img3' 51 ), 52 # Wpis 4: Przedwczoraj, Niepoprawny (nieznany pracownik - brak worker_id) 53 Entry( 54 worker_id=None, 55 code=2, 56 message="Nie rozpoznano pracownika", 57 date=base_time - timedelta(days=2), 58 face_image=b'img4' 59 ) 60 ] 61 62 db_session.session.add_all(entries) 63 db_session.session.commit() 64 65 return { 66 "worker_1": created_worker, 67 "worker_2": worker_2, 68 "entries": entries 69 } 70 71 72# ============================================================================ 73# Test GET /api/raport - Basic Retrieval 74# ============================================================================ 75 76 77def test_get_report_empty(client, db_session): 78 """ 79 Tests report download when the database is empty. 80 """ 81 response = client.get('/api/raport') 82 83 assert response.status_code == 200 84 data = response.get_json() 85 assert data['count'] == 0 86 assert data['data'] == [] 87 # Sprawdzenie czy statystyki są wyzerowane 88 assert data['statistics']['total_entries'] == 0 89 90 91def test_get_report_all_data(client, populate_entries): 92 """ 93 Tests downloading all data without filters (default sorting). 94 """ 95 response = client.get('/api/raport') 96 97 assert response.status_code == 200 98 data = response.get_json() 99 100 # Mamy 4 wpisy w fixturze 101 assert data['count'] == 4 102 assert len(data['data']) == 4 103 104 # Sprawdzenie poprawności statystyk 105 stats = data['statistics'] 106 assert stats['total_entries'] == 4 107 assert stats['valid_entries'] == 2 # Wpis 1 i 3 108 assert stats['invalid_entries'] == 2 # Wpis 2 i 4 109 assert stats['success_rate_percent'] == 50.0 110 111 112# ============================================================================ 113# Test GET /api/raport - Filtering Logic 114# ============================================================================ 115 116def test_filter_by_worker_id(client, populate_entries): 117 """ 118 Testing filtering by employee ID. 119 """ 120 worker_1 = populate_entries['worker_1'] 121 122 # Filtrujemy tylko dla worker_1 (powinien mieć 2 wpisy: 1 valid, 1 invalid) 123 response = client.get(f'/api/raport?pracownik_id={worker_1.id}') 124 125 assert response.status_code == 200 126 data = response.get_json() 127 128 assert data['count'] == 2 129 for item in data['data']: 130 assert item['worker_id'] == worker_1.id 131 132 133def test_filter_by_date_range(client, populate_entries): 134 """ 135 Tests filtering by date (date_from, date_to). 136 """ 137 # Chcemy pobrać wpisy tylko z "dzisiaj" 138 today_str = datetime.now().strftime('%Y-%m-%d') 139 140 # date_from = dzisiaj, date_to = dzisiaj (obejmuje cały dzień do 23:59:59 wg logiki w kontrolerze) 141 response = client.get(f'/api/raport?date_from={today_str}&date_to={today_str}') 142 143 assert response.status_code == 200 144 data = response.get_json() 145 146 # Powinny być 2 wpisy z dzisiaj (dla worker_1) 147 assert data['count'] == 2 148 for item in data['data']: 149 assert item['date'].startswith(today_str) 150 151 152def test_filter_valid_only(client, populate_entries): 153 """ 154 Tests the 'wejscia_poprawne' flag - it should return only entries with code 0. 155 """ 156 # Przekazujemy parametr 'wejscia_poprawne' (wystarczy jego obecność) 157 response = client.get('/api/raport?wejscia_poprawne=true') 158 159 assert response.status_code == 200 160 data = response.get_json() 161 162 assert data['count'] == 2 163 for item in data['data']: 164 assert item['code'] == 0 165 166 167def test_filter_invalid_only(client, populate_entries): 168 """ 169 Tests the 'wejscia_niepoprawne' flag - it should return entries with the code != 0. 170 """ 171 response = client.get('/api/raport?wejscia_niepoprawne=true') 172 173 assert response.status_code == 200 174 data = response.get_json() 175 176 assert data['count'] == 2 177 for item in data['data']: 178 assert item['code'] != 0 179 180 181def test_invalid_date_format(client): 182 """ 183 Tests error handling for bad date format (400 Bad Request). 184 """ 185 response = client.get('/api/raport?date_from=bad-format') 186 assert response.status_code == 400 187 assert 'error' in response.get_json() 188 189 190# ============================================================================ 191# Test GET /api/raport/pdf - PDF Generation 192# ============================================================================ 193 194 195def test_pdf_generation(client, populate_entries): 196 """ 197 Tests the PDF generation endpoint (/api/report/pdf). 198 Checks whether the correct MIME type is returned and whether there is no 500 error. 199 """ 200 response = client.get('/api/raport/pdf') 201 202 assert response.status_code == 200 203 assert response.headers['Content-Type'] == 'application/pdf' 204 # Sprawdzamy, czy plik nie jest pusty 205 assert len(response.data) > 0 206 # Sprawdzamy nagłówek Content-Disposition (czy jest jako attachment) 207 assert 'attachment' in response.headers['Content-Disposition'] 208 assert '.pdf' in response.headers['Content-Disposition'] 209 210 211def test_pdf_generation_empty(client, db_session): 212 """ 213 Tests PDF generation with no data (should generate an empty report without error). 214 """ 215 response = client.get('/api/raport/pdf') 216 assert response.status_code == 200 217 assert response.headers['Content-Type'] == 'application/pdf' 218 219# ============================================================================ 220# Test Statistics Logic 221# ============================================================================ 222 223 224def test_statistics_calculation(client, populate_entries): 225 """ 226 Detailed test of the correctness of calculating statistics in JSON. 227 """ 228 worker_1 = populate_entries['worker_1'] 229 230 response = client.get('/api/raport') 231 stats = response.get_json()['statistics'] 232 233 # Sprawdzenie "most_valid_entries_worker" (powinien być worker_1 lub worker_2, obaj mają po 1) 234 # W implementacji licznika (Counter) kolejność przy remisie zależy od kolejności dodawania 235 assert stats['most_valid_entries_worker']['count'] == 1 236 237 # Sprawdzenie "most_invalid_attempts_worker" 238 # Worker 1 ma 1 nieudane, Worker "None" ma 1 nieudane. 239 # Sprawdzamy czy w ogóle zwróciło obiekt 240 assert stats['most_invalid_attempts_worker'] is not None 241 assert stats['most_invalid_attempts_worker']['count'] == 1
10@pytest.fixture 11def populate_entries(db_session, created_worker): 12 """ 13 A helper fixture that adds a series of entries to the database to test filtering and statistics. 14 It creates entries for 'created_worker' (from conftest) and an additional worker. 15 """ 16 # Dodajemy drugiego pracownika dla różnorodności 17 worker_2 = Worker( 18 name="Test Worker 2", 19 face_embedding=b'dummy_embedding', 20 expiration_date=datetime.now() + timedelta(days=30), 21 secret="secret_2" 22 ) 23 db_session.session.add(worker_2) 24 db_session.session.commit() 25 26 base_time = datetime.now() 27 28 entries = [ 29 # Wpis 1: Dzisiaj, Poprawny, Worker 1 30 Entry( 31 worker_id=created_worker.id, 32 code=0, 33 message="Wstęp przyznany", 34 date=base_time, 35 face_image=b'img1' 36 ), 37 # Wpis 2: Dzisiaj, Niepoprawny (błąd weryfikacji), Worker 1 38 Entry( 39 worker_id=created_worker.id, 40 code=1, 41 message="Błąd weryfikacji twarzy", 42 date=base_time - timedelta(minutes=10), 43 face_image=b'img2' 44 ), 45 # Wpis 3: Wczoraj, Poprawny, Worker 2 46 Entry( 47 worker_id=worker_2.id, 48 code=0, 49 message="Wstęp przyznany", 50 date=base_time - timedelta(days=1), 51 face_image=b'img3' 52 ), 53 # Wpis 4: Przedwczoraj, Niepoprawny (nieznany pracownik - brak worker_id) 54 Entry( 55 worker_id=None, 56 code=2, 57 message="Nie rozpoznano pracownika", 58 date=base_time - timedelta(days=2), 59 face_image=b'img4' 60 ) 61 ] 62 63 db_session.session.add_all(entries) 64 db_session.session.commit() 65 66 return { 67 "worker_1": created_worker, 68 "worker_2": worker_2, 69 "entries": entries 70 }
A helper fixture that adds a series of entries to the database to test filtering and statistics. It creates entries for 'created_worker' (from conftest) and an additional worker.
78def test_get_report_empty(client, db_session): 79 """ 80 Tests report download when the database is empty. 81 """ 82 response = client.get('/api/raport') 83 84 assert response.status_code == 200 85 data = response.get_json() 86 assert data['count'] == 0 87 assert data['data'] == [] 88 # Sprawdzenie czy statystyki są wyzerowane 89 assert data['statistics']['total_entries'] == 0
Tests report download when the database is empty.
92def test_get_report_all_data(client, populate_entries): 93 """ 94 Tests downloading all data without filters (default sorting). 95 """ 96 response = client.get('/api/raport') 97 98 assert response.status_code == 200 99 data = response.get_json() 100 101 # Mamy 4 wpisy w fixturze 102 assert data['count'] == 4 103 assert len(data['data']) == 4 104 105 # Sprawdzenie poprawności statystyk 106 stats = data['statistics'] 107 assert stats['total_entries'] == 4 108 assert stats['valid_entries'] == 2 # Wpis 1 i 3 109 assert stats['invalid_entries'] == 2 # Wpis 2 i 4 110 assert stats['success_rate_percent'] == 50.0
Tests downloading all data without filters (default sorting).
117def test_filter_by_worker_id(client, populate_entries): 118 """ 119 Testing filtering by employee ID. 120 """ 121 worker_1 = populate_entries['worker_1'] 122 123 # Filtrujemy tylko dla worker_1 (powinien mieć 2 wpisy: 1 valid, 1 invalid) 124 response = client.get(f'/api/raport?pracownik_id={worker_1.id}') 125 126 assert response.status_code == 200 127 data = response.get_json() 128 129 assert data['count'] == 2 130 for item in data['data']: 131 assert item['worker_id'] == worker_1.id
Testing filtering by employee ID.
134def test_filter_by_date_range(client, populate_entries): 135 """ 136 Tests filtering by date (date_from, date_to). 137 """ 138 # Chcemy pobrać wpisy tylko z "dzisiaj" 139 today_str = datetime.now().strftime('%Y-%m-%d') 140 141 # date_from = dzisiaj, date_to = dzisiaj (obejmuje cały dzień do 23:59:59 wg logiki w kontrolerze) 142 response = client.get(f'/api/raport?date_from={today_str}&date_to={today_str}') 143 144 assert response.status_code == 200 145 data = response.get_json() 146 147 # Powinny być 2 wpisy z dzisiaj (dla worker_1) 148 assert data['count'] == 2 149 for item in data['data']: 150 assert item['date'].startswith(today_str)
Tests filtering by date (date_from, date_to).
153def test_filter_valid_only(client, populate_entries): 154 """ 155 Tests the 'wejscia_poprawne' flag - it should return only entries with code 0. 156 """ 157 # Przekazujemy parametr 'wejscia_poprawne' (wystarczy jego obecność) 158 response = client.get('/api/raport?wejscia_poprawne=true') 159 160 assert response.status_code == 200 161 data = response.get_json() 162 163 assert data['count'] == 2 164 for item in data['data']: 165 assert item['code'] == 0
Tests the 'wejscia_poprawne' flag - it should return only entries with code 0.
168def test_filter_invalid_only(client, populate_entries): 169 """ 170 Tests the 'wejscia_niepoprawne' flag - it should return entries with the code != 0. 171 """ 172 response = client.get('/api/raport?wejscia_niepoprawne=true') 173 174 assert response.status_code == 200 175 data = response.get_json() 176 177 assert data['count'] == 2 178 for item in data['data']: 179 assert item['code'] != 0
Tests the 'wejscia_niepoprawne' flag - it should return entries with the code != 0.
182def test_invalid_date_format(client): 183 """ 184 Tests error handling for bad date format (400 Bad Request). 185 """ 186 response = client.get('/api/raport?date_from=bad-format') 187 assert response.status_code == 400 188 assert 'error' in response.get_json()
Tests error handling for bad date format (400 Bad Request).
196def test_pdf_generation(client, populate_entries): 197 """ 198 Tests the PDF generation endpoint (/api/report/pdf). 199 Checks whether the correct MIME type is returned and whether there is no 500 error. 200 """ 201 response = client.get('/api/raport/pdf') 202 203 assert response.status_code == 200 204 assert response.headers['Content-Type'] == 'application/pdf' 205 # Sprawdzamy, czy plik nie jest pusty 206 assert len(response.data) > 0 207 # Sprawdzamy nagłówek Content-Disposition (czy jest jako attachment) 208 assert 'attachment' in response.headers['Content-Disposition'] 209 assert '.pdf' in response.headers['Content-Disposition']
Tests the PDF generation endpoint (/api/report/pdf). Checks whether the correct MIME type is returned and whether there is no 500 error.
212def test_pdf_generation_empty(client, db_session): 213 """ 214 Tests PDF generation with no data (should generate an empty report without error). 215 """ 216 response = client.get('/api/raport/pdf') 217 assert response.status_code == 200 218 assert response.headers['Content-Type'] == 'application/pdf'
Tests PDF generation with no data (should generate an empty report without error).
225def test_statistics_calculation(client, populate_entries): 226 """ 227 Detailed test of the correctness of calculating statistics in JSON. 228 """ 229 worker_1 = populate_entries['worker_1'] 230 231 response = client.get('/api/raport') 232 stats = response.get_json()['statistics'] 233 234 # Sprawdzenie "most_valid_entries_worker" (powinien być worker_1 lub worker_2, obaj mają po 1) 235 # W implementacji licznika (Counter) kolejność przy remisie zależy od kolejności dodawania 236 assert stats['most_valid_entries_worker']['count'] == 1 237 238 # Sprawdzenie "most_invalid_attempts_worker" 239 # Worker 1 ma 1 nieudane, Worker "None" ma 1 nieudane. 240 # Sprawdzamy czy w ogóle zwróciło obiekt 241 assert stats['most_invalid_attempts_worker'] is not None 242 assert stats['most_invalid_attempts_worker']['count'] == 1
Detailed test of the correctness of calculating statistics in JSON.