"""Tests für agent-04-dashboard""" import pytest import pytest_asyncio from datetime import date, timedelta from httpx import AsyncClient # ── Fixtures ────────────────────────────────────────────────────────────────── @pytest_asyncio.fixture(scope="session", loop_scope="session") async def report_headers(registered_user): """Admin-Headers aus dem bestehenden registered_user Fixture.""" return {"Authorization": f"Bearer {registered_user['tokens']['access_token']}"} @pytest_asyncio.fixture(scope="session", loop_scope="session") async def report_vacation_type(client: AsyncClient, report_headers): resp = await client.get("/api/v1/absence-types/", headers=report_headers) types = resp.json() vacation = next((t for t in types if t["name"] == "Urlaub"), types[0]) return vacation["id"] # ── Employee Dashboard ───────────────────────────────────────────────────────── @pytest.mark.asyncio async def test_employee_dashboard(client: AsyncClient, report_headers): resp = await client.get("/api/v1/dashboard/me", headers=report_headers) assert resp.status_code == 200 data = resp.json() assert "today_open" in data assert "week_hours_worked" in data assert "week_hours_expected" in data assert "week_overtime" in data assert "vacation_entitled_days" in data assert "pending_absences" in data @pytest.mark.asyncio async def test_employee_dashboard_unauthenticated(client: AsyncClient): resp = await client.get("/api/v1/dashboard/me") assert resp.status_code == 401 # ── Team Dashboard ───────────────────────────────────────────────────────────── @pytest.mark.asyncio async def test_team_dashboard(client: AsyncClient, report_headers): """COMPANY_ADMIN kann Team-Dashboard abrufen.""" resp = await client.get("/api/v1/dashboard/team", headers=report_headers) assert resp.status_code == 200 data = resp.json() assert "present_count" in data assert "on_leave_count" in data assert "absent_count" in data assert "pending_time_approvals" in data assert "pending_absence_approvals" in data assert "members" in data assert isinstance(data["members"], list) # Mindestens der Admin selbst assert len(data["members"]) >= 1 @pytest.mark.asyncio async def test_team_dashboard_member_fields(client: AsyncClient, report_headers): resp = await client.get("/api/v1/dashboard/team", headers=report_headers) data = resp.json() member = data["members"][0] assert "user_id" in member assert "user_name" in member assert "status" in member assert member["status"] in ("present", "on_leave", "absent") # ── Company Dashboard ────────────────────────────────────────────────────────── @pytest.mark.asyncio async def test_company_dashboard(client: AsyncClient, report_headers): """COMPANY_ADMIN kann Unternehmens-Dashboard abrufen.""" resp = await client.get("/api/v1/dashboard/company", headers=report_headers) assert resp.status_code == 200 data = resp.json() assert data["total_employees"] >= 1 assert "active_today" in data assert "attendance_rate" in data assert "month_hours_worked" in data assert "month_hours_expected" in data assert "pending_time_approvals" in data assert "pending_absence_approvals" in data assert "upcoming_absences" in data # ── Time Report ──────────────────────────────────────────────────────────────── @pytest.mark.asyncio async def test_time_report(client: AsyncClient, report_headers): today = date.today() month_start = today.replace(day=1) resp = await client.get( "/api/v1/reports/time", params={"date_from": str(month_start), "date_to": str(today)}, headers=report_headers, ) assert resp.status_code == 200 data = resp.json() assert "total_rows" in data assert "total_hours" in data assert "rows" in data assert isinstance(data["rows"], list) @pytest.mark.asyncio async def test_time_report_row_structure(client: AsyncClient, report_headers): """Falls Einträge vorhanden, Struktur prüfen.""" today = date.today() month_start = today.replace(day=1) resp = await client.get( "/api/v1/reports/time", params={"date_from": str(month_start), "date_to": str(today)}, headers=report_headers, ) data = resp.json() if data["rows"]: row = data["rows"][0] assert "date" in row assert "user_name" in row assert "start_time" in row assert "status" in row assert "worked_hours" in row @pytest.mark.asyncio async def test_time_report_default_params(client: AsyncClient, report_headers): """Report ohne date_from/date_to → default = aktueller Monat.""" resp = await client.get("/api/v1/reports/time", headers=report_headers) assert resp.status_code == 200 # ── Absence Report ───────────────────────────────────────────────────────────── @pytest.mark.asyncio async def test_absence_report(client: AsyncClient, report_headers): today = date.today() resp = await client.get( "/api/v1/reports/absences", params={"date_from": str(today.replace(day=1)), "date_to": str(today)}, headers=report_headers, ) assert resp.status_code == 200 data = resp.json() assert "total_rows" in data assert "total_days" in data assert "rows" in data @pytest.mark.asyncio async def test_absence_report_with_data(client: AsyncClient, report_headers, report_vacation_type): """Urlaub beantragen und im Report prüfen.""" next_monday = date.today() + timedelta(days=(7 - date.today().weekday()) + 56) await client.post( "/api/v1/absences/", json={ "type_id": str(report_vacation_type), "start_date": str(next_monday), "end_date": str(next_monday + timedelta(days=4)), }, headers=report_headers, ) resp = await client.get( "/api/v1/reports/absences", params={"date_from": str(next_monday), "date_to": str(next_monday + timedelta(days=4))}, headers=report_headers, ) assert resp.status_code == 200 data = resp.json() assert data["total_rows"] >= 1 assert data["total_days"] > 0 # ── Overtime Report ──────────────────────────────────────────────────────────── @pytest.mark.asyncio async def test_overtime_report(client: AsyncClient, report_headers): today = date.today() resp = await client.get( "/api/v1/reports/overtime", params={"date_from": str(today.replace(day=1)), "date_to": str(today)}, headers=report_headers, ) assert resp.status_code == 200 data = resp.json() assert "total_employees" in data assert "total_overtime" in data assert "rows" in data assert data["total_employees"] >= 1 @pytest.mark.asyncio async def test_overtime_report_row_fields(client: AsyncClient, report_headers): today = date.today() resp = await client.get( "/api/v1/reports/overtime", params={"date_from": str(today.replace(day=1)), "date_to": str(today)}, headers=report_headers, ) data = resp.json() row = data["rows"][0] assert "user_name" in row assert "hours_worked" in row assert "hours_expected" in row assert "overtime_hours" in row # ── Export ───────────────────────────────────────────────────────────────────── @pytest.mark.asyncio async def test_export_time_csv(client: AsyncClient, report_headers): today = date.today() resp = await client.get( "/api/v1/reports/time/export", params={"date_from": str(today.replace(day=1)), "date_to": str(today), "format": "csv"}, headers=report_headers, ) assert resp.status_code == 200 assert "text/csv" in resp.headers["content-type"] assert "attachment" in resp.headers.get("content-disposition", "") @pytest.mark.asyncio async def test_export_time_xlsx(client: AsyncClient, report_headers): today = date.today() resp = await client.get( "/api/v1/reports/time/export", params={"date_from": str(today.replace(day=1)), "date_to": str(today), "format": "xlsx"}, headers=report_headers, ) assert resp.status_code == 200 assert "spreadsheetml" in resp.headers["content-type"] assert len(resp.content) > 0 @pytest.mark.asyncio async def test_export_absence_csv(client: AsyncClient, report_headers): today = date.today() resp = await client.get( "/api/v1/reports/absences/export", params={"date_from": str(today.replace(day=1)), "date_to": str(today), "format": "csv"}, headers=report_headers, ) assert resp.status_code == 200 assert "text/csv" in resp.headers["content-type"] @pytest.mark.asyncio async def test_export_overtime_xlsx(client: AsyncClient, report_headers): today = date.today() resp = await client.get( "/api/v1/reports/overtime/export", params={"date_from": str(today.replace(day=1)), "date_to": str(today), "format": "xlsx"}, headers=report_headers, ) assert resp.status_code == 200 assert "spreadsheetml" in resp.headers["content-type"] @pytest.mark.asyncio async def test_export_invalid_format(client: AsyncClient, report_headers): today = date.today() resp = await client.get( "/api/v1/reports/time/export", params={"date_from": str(today.replace(day=1)), "date_to": str(today), "format": "xml"}, headers=report_headers, ) assert resp.status_code == 422 # ── Unit Tests: Export-Helpers ───────────────────────────────────────────────── def test_to_csv_empty(): from app.services.report_service import ReportService svc = ReportService() assert svc.to_csv([]) == "" def test_to_csv_with_data(): from app.services.report_service import ReportService svc = ReportService() rows = [{"Name": "Max", "Stunden": 8.0}, {"Name": "Anna", "Stunden": 7.5}] csv_str = svc.to_csv(rows) assert "Name" in csv_str assert "Max" in csv_str assert "Anna" in csv_str def test_to_xlsx_empty(): from app.services.report_service import ReportService svc = ReportService() result = svc.to_xlsx([]) assert isinstance(result, bytes) assert len(result) > 0 # Leere XLSX-Datei hat noch Header def test_to_xlsx_with_data(): from app.services.report_service import ReportService svc = ReportService() rows = [{"Mitarbeiter": "Max", "Stunden": 8.0}] result = svc.to_xlsx(rows, sheet_name="Test") assert isinstance(result, bytes) assert len(result) > 1000 # XLSX ist ZIP-basiert # ── PDF Export ───────────────────────────────────────────────────────────────── @pytest.mark.asyncio async def test_export_time_pdf(client: AsyncClient, report_headers): today = date.today() resp = await client.get( "/api/v1/reports/time/export", params={"date_from": str(today.replace(day=1)), "date_to": str(today), "format": "pdf"}, headers=report_headers, ) assert resp.status_code == 200 assert "pdf" in resp.headers["content-type"] assert "attachment" in resp.headers.get("content-disposition", "") assert len(resp.content) > 1000 @pytest.mark.asyncio async def test_export_absence_pdf(client: AsyncClient, report_headers): today = date.today() resp = await client.get( "/api/v1/reports/absences/export", params={"date_from": str(today.replace(day=1)), "date_to": str(today), "format": "pdf"}, headers=report_headers, ) assert resp.status_code == 200 assert "pdf" in resp.headers["content-type"] assert "attachment" in resp.headers.get("content-disposition", "") assert len(resp.content) > 1000 @pytest.mark.asyncio async def test_export_overtime_pdf(client: AsyncClient, report_headers): today = date.today() resp = await client.get( "/api/v1/reports/overtime/export", params={"date_from": str(today.replace(day=1)), "date_to": str(today), "format": "pdf"}, headers=report_headers, ) assert resp.status_code == 200 assert "pdf" in resp.headers["content-type"] assert "attachment" in resp.headers.get("content-disposition", "") assert len(resp.content) > 1000