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
@pytest.fixture
def populate_entries(db_session, created_worker):
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.

def test_get_report_empty(client, db_session):
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.

def test_get_report_all_data(client, populate_entries):
 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).

def test_filter_by_worker_id(client, populate_entries):
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.

def test_filter_by_date_range(client, populate_entries):
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).

def test_filter_valid_only(client, populate_entries):
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.

def test_filter_invalid_only(client, populate_entries):
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.

def test_invalid_date_format(client):
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).

def test_pdf_generation(client, populate_entries):
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.

def test_pdf_generation_empty(client, db_session):
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).

def test_statistics_calculation(client, populate_entries):
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.