from fastapi.testclient import TestClient from datetime import date, timedelta import holidays import json import re from sqlalchemy import select from sqlalchemy.orm import Session from app.database import get_engine from app.models import TimeEntry, User def test_vacation_reduces_weekly_target_and_month_report(app): with TestClient(app) as client: register = client.post( "/auth/register", json={"email": "vac@example.com", "password": "strongpasswordVac1"}, ) assert register.status_code == 200 csrf = register.json()["csrf_token"] add_vacation = client.post( "/settings/vacations/add", data={ "start_date": "2026-03-03", "end_date": "2026-03-04", "csrf_token": csrf, }, follow_redirects=False, ) assert add_vacation.status_code == 303 week_report = client.get("/reports/week", params={"date": "2026-03-03"}) assert week_report.status_code == 200 data = week_report.json() assert data["vacation_days"] == 2 assert data["weekly_soll_minutes"] == 900 month_report = client.get("/reports/month", params={"month": "2026-03"}) assert month_report.status_code == 200 weeks = month_report.json()["weeks"] assert any(item.get("vacation_days", 0) >= 2 for item in weeks) def test_month_report_counts_partial_weeks_only_within_month(app): with TestClient(app) as client: register = client.post( "/auth/register", json={"email": "month-partial@example.com", "password": "strongpasswordMonth1"}, ) assert register.status_code == 200 csrf = register.json()["csrf_token"] # Entries in the previous month must not influence March partial week totals. for day in ["2026-02-23", "2026-02-24", "2026-02-25", "2026-02-26", "2026-02-27"]: create = client.post( "/time-entries", headers={"x-csrf-token": csrf}, json={ "date": day, "start_time": "08:00", "end_time": "14:00", "break_minutes": 0, }, ) assert create.status_code == 200 month_report = client.get("/reports/month", params={"month": "2026-03"}) assert month_report.status_code == 200 data = month_report.json() first_week = next(item for item in data["weeks"] if item["week_start"] == "2026-02-23") assert first_week["ist_minutes"] == 0 assert first_week["soll_minutes"] == 0 assert first_week["delta_minutes"] == 0 # March 2026 has 22 workdays; default target is 25h/week -> 5h/day (300 min). assert data["month_soll_minutes"] == 22 * 300 assert data["month_delta_minutes"] == -(22 * 300) def test_custom_working_days_affect_soll_and_month_partial_weeks(app): with TestClient(app) as client: register = client.post( "/auth/register", json={"email": "workdays@example.com", "password": "strongpasswordWork1"}, ) assert register.status_code == 200 csrf = register.json()["csrf_token"] set_target = client.post( "/weekly-target", data={ "week_start": "2026-03-02", "weekly_target_hours": "30", "scope": "all_weeks", "csrf_token": csrf, }, follow_redirects=False, ) assert set_target.status_code == 303 update_workdays = client.post( "/settings/workdays", data={ "working_days": ["0", "1", "2", "3"], # Mo-Do "csrf_token": csrf, }, follow_redirects=False, ) assert update_workdays.status_code == 303 add_vacation = client.post( "/settings/vacations/add", data={ "start_date": "2026-03-03", "end_date": "2026-03-04", "csrf_token": csrf, }, follow_redirects=False, ) assert add_vacation.status_code == 303 week_report = client.get("/reports/week", params={"date": "2026-03-02"}) assert week_report.status_code == 200 week_data = week_report.json() assert week_data["weekly_soll_minutes"] == 900 # 2 verbleibende Arbeitstage * 7.5h assert week_data["vacation_days"] == 2 month_report = client.get("/reports/month", params={"month": "2026-03"}) assert month_report.status_code == 200 month_data = month_report.json() first_week = next(item for item in month_data["weeks"] if item["week_start"] == "2026-02-23") assert first_week["soll_minutes"] == 0 def test_quick_vacation_toggle_for_day_and_week(app): with TestClient(app) as client: register = client.post( "/auth/register", json={"email": "quickvac@example.com", "password": "strongpasswordQuick1"}, ) assert register.status_code == 200 csrf = register.json()["csrf_token"] day_on = client.post( "/vacation/day/toggle", data={"date": "2026-03-03", "return_to": "/dashboard?date=2026-03-03", "csrf_token": csrf}, follow_redirects=False, ) assert day_on.status_code == 303 week_after_day_on = client.get("/reports/week", params={"date": "2026-03-03"}) assert week_after_day_on.status_code == 200 assert week_after_day_on.json()["vacation_days"] == 1 day_off = client.post( "/vacation/day/toggle", data={"date": "2026-03-03", "return_to": "/dashboard?date=2026-03-03", "csrf_token": csrf}, follow_redirects=False, ) assert day_off.status_code == 303 week_after_day_off = client.get("/reports/week", params={"date": "2026-03-03"}) assert week_after_day_off.status_code == 200 assert week_after_day_off.json()["vacation_days"] == 0 week_on = client.post( "/vacation/week/toggle", data={ "week_start": "2026-03-02", "week_end": "2026-03-08", "return_to": "/month?month=2026-03&view=flat", "csrf_token": csrf, }, follow_redirects=False, ) assert week_on.status_code == 303 week_after_week_on = client.get("/reports/week", params={"date": "2026-03-03"}) assert week_after_week_on.status_code == 200 assert week_after_week_on.json()["vacation_days"] == 5 week_off = client.post( "/vacation/week/toggle", data={ "week_start": "2026-03-02", "week_end": "2026-03-08", "return_to": "/month?month=2026-03&view=flat", "csrf_token": csrf, }, follow_redirects=False, ) assert week_off.status_code == 303 week_after_week_off = client.get("/reports/week", params={"date": "2026-03-03"}) assert week_after_week_off.status_code == 200 assert week_after_week_off.json()["vacation_days"] == 0 def test_week_vacation_toggle_uses_configured_workdays(app): with TestClient(app) as client: register = client.post( "/auth/register", json={"email": "weekworkdays@example.com", "password": "strongpasswordWeekWork1"}, ) assert register.status_code == 200 csrf = register.json()["csrf_token"] set_target = client.post( "/weekly-target", data={ "week_start": "2026-03-02", "weekly_target_hours": "30", "scope": "all_weeks", "csrf_token": csrf, }, follow_redirects=False, ) assert set_target.status_code == 303 update_workdays = client.post( "/settings/workdays", data={ "working_days": ["0", "1", "2", "3"], # Mo-Do "csrf_token": csrf, }, follow_redirects=False, ) assert update_workdays.status_code == 303 week_on = client.post( "/vacation/week/toggle", data={ "week_start": "2026-03-02", "week_end": "2026-03-08", "return_to": "/dashboard?date=2026-03-02", "csrf_token": csrf, }, follow_redirects=False, ) assert week_on.status_code == 303 week_after_week_on = client.get("/reports/week", params={"date": "2026-03-03"}) assert week_after_week_on.status_code == 200 payload = week_after_week_on.json() assert payload["vacation_days"] == 4 assert payload["weekly_soll_minutes"] == 0 def test_settings_vacation_ranges_follow_configured_workdays(app): with TestClient(app) as client: register = client.post( "/auth/register", json={"email": "vac-ranges@example.com", "password": "strongpasswordVacRanges1"}, ) assert register.status_code == 200 csrf = register.json()["csrf_token"] update_allowance = client.post( "/settings/vacation-allowance", data={"vacation_days_total": "22", "csrf_token": csrf}, follow_redirects=False, ) assert update_allowance.status_code == 303 update_workdays = client.post( "/settings/workdays", data={ "working_days": ["0", "1", "2", "3"], # Mo-Do "csrf_token": csrf, }, follow_redirects=False, ) assert update_workdays.status_code == 303 add_vacation = client.post( "/settings/vacations/add", data={ "start_date": "2026-03-02", "end_date": "2026-03-15", "csrf_token": csrf, }, follow_redirects=False, ) assert add_vacation.status_code == 303 week_1 = client.get("/reports/week", params={"date": "2026-03-03"}) assert week_1.status_code == 200 assert week_1.json()["vacation_days"] == 4 week_2 = client.get("/reports/week", params={"date": "2026-03-10"}) assert week_2.status_code == 200 assert week_2.json()["vacation_days"] == 4 dashboard = client.get("/dashboard", params={"date": "2026-03-10"}) assert dashboard.status_code == 200 # Resturlaub / Gesamturlaub: 22 - 8 = 14 assert "14/22" in dashboard.text settings_page = client.get("/settings") assert settings_page.status_code == 200 assert "02.03.2026 - 05.03.2026" in settings_page.text assert "09.03.2026 - 12.03.2026" in settings_page.text assert "07.03.2026 - 12.03.2026" not in settings_page.text assert "14.03.2026 - 15.03.2026" not in settings_page.text delete_second_range = client.post( "/settings/vacations/delete-range", data={ "start_date": "2026-03-09", "end_date": "2026-03-12", "csrf_token": csrf, }, follow_redirects=False, ) assert delete_second_range.status_code == 303 week_2_after_delete = client.get("/reports/week", params={"date": "2026-03-10"}) assert week_2_after_delete.status_code == 200 assert week_2_after_delete.json()["vacation_days"] == 0 def test_vacation_allowance_is_saved_and_shows_remaining_days_in_header(app): with TestClient(app) as client: register = client.post( "/auth/register", json={"email": "allowance@example.com", "password": "strongpasswordAllow1"}, ) assert register.status_code == 200 csrf = register.json()["csrf_token"] update_allowance = client.post( "/settings/vacation-allowance", data={"vacation_days_total": "22", "csrf_token": csrf}, follow_redirects=False, ) assert update_allowance.status_code == 303 me = client.get("/me") assert me.status_code == 200 assert me.json()["vacation_days_total"] == 22 current_year = date.today().year target_day = date(current_year, 1, 1) while target_day.weekday() > 4: target_day += timedelta(days=1) add_vacation = client.post( "/settings/vacations/add", data={ "start_date": target_day.isoformat(), "end_date": target_day.isoformat(), "csrf_token": csrf, }, follow_redirects=False, ) assert add_vacation.status_code == 303 dashboard = client.get("/dashboard") assert dashboard.status_code == 200 assert "21/22" in dashboard.text def test_federal_state_auto_holidays_skip_days_with_work_entries(app): with TestClient(app) as client: password = "strongpasswordState1" register = client.post( "/auth/register", json={"email": "state-holidays@example.com", "password": password}, ) assert register.status_code == 200 csrf = register.json()["csrf_token"] holiday_map = holidays.country_holidays("DE", subdiv="NW", years=[date.today().year, date.today().year + 1]) weekday_holidays = sorted([day for day in holiday_map.keys() if day.weekday() <= 4 and day >= date.today()]) assert len(weekday_holidays) >= 2 worked_holiday = weekday_holidays[0] untouched_holiday = weekday_holidays[1] create_entry = client.post( "/time-entries", headers={"x-csrf-token": csrf}, json={ "date": worked_holiday.isoformat(), "start_time": "08:00", "end_time": "12:00", "break_minutes": 0, }, ) assert create_entry.status_code == 200 update_profile = client.post( "/settings/profile", data={ "email": "state-holidays@example.com", "federal_state": "NW", "current_password": password, "csrf_token": csrf, }, follow_redirects=False, ) assert update_profile.status_code == 303 worked_month = client.get("/reports/month", params={"month": worked_holiday.strftime("%Y-%m")}) assert worked_month.status_code == 200 worked_days = {item["date"]: item for item in worked_month.json()["days"]} assert worked_days[worked_holiday.isoformat()]["special_status"] is None untouched_month = client.get("/reports/month", params={"month": untouched_holiday.strftime("%Y-%m")}) assert untouched_month.status_code == 200 untouched_days = {item["date"]: item for item in untouched_month.json()["days"]} assert untouched_days[untouched_holiday.isoformat()]["special_status"] == "holiday" def test_federal_state_holidays_also_mark_non_configured_workdays(app): with TestClient(app) as client: password = "strongpasswordState2" register = client.post( "/auth/register", json={"email": "state-holidays-2@example.com", "password": password}, ) assert register.status_code == 200 csrf = register.json()["csrf_token"] # Restrict workdays to Mo-Do (Friday excluded). update_workdays = client.post( "/settings/workdays", data={ "working_days": ["0", "1", "2", "3"], "csrf_token": csrf, }, follow_redirects=False, ) assert update_workdays.status_code == 303 update_profile = client.post( "/settings/profile", data={ "email": "state-holidays-2@example.com", "federal_state": "HH", "current_password": password, "csrf_token": csrf, }, follow_redirects=False, ) assert update_profile.status_code == 303 # 01.05.2026 is Friday and still should be marked as holiday. may_report = client.get("/reports/month", params={"month": "2026-05"}) assert may_report.status_code == 200 days = {item["date"]: item for item in may_report.json()["days"]} assert days["2026-05-01"]["special_status"] == "holiday" def test_special_status_reduces_soll_without_counting_as_vacation(app): with TestClient(app) as client: register = client.post( "/auth/register", json={"email": "specialstatus@example.com", "password": "strongpasswordSpecial1"}, ) assert register.status_code == 200 csrf = register.json()["csrf_token"] add_holiday = client.post( "/special-day/toggle", data={ "date": "2026-03-03", "status": "holiday", "return_to": "/dashboard?date=2026-03-03", "csrf_token": csrf, }, follow_redirects=False, ) assert add_holiday.status_code == 303 week_data = client.get("/reports/week", params={"date": "2026-03-03"}) assert week_data.status_code == 200 payload = week_data.json() assert payload["vacation_days"] == 0 assert payload["weekly_soll_minutes"] == 1200 switch_to_sick = client.post( "/special-day/toggle", data={ "date": "2026-03-03", "status": "sick", "return_to": "/dashboard?date=2026-03-03", "csrf_token": csrf, }, follow_redirects=False, ) assert switch_to_sick.status_code == 303 week_after_switch = client.get("/reports/week", params={"date": "2026-03-03"}) assert week_after_switch.status_code == 200 assert week_after_switch.json()["weekly_soll_minutes"] == 1200 remove_sick = client.post( "/special-day/toggle", data={ "date": "2026-03-03", "status": "sick", "return_to": "/dashboard?date=2026-03-03", "csrf_token": csrf, }, follow_redirects=False, ) assert remove_sick.status_code == 303 week_without_special = client.get("/reports/week", params={"date": "2026-03-03"}) assert week_without_special.status_code == 200 assert week_without_special.json()["weekly_soll_minutes"] == 1500 def test_workhours_counter_settings_and_value(app): with TestClient(app) as client: register = client.post( "/auth/register", json={"email": "workcounter@example.com", "password": "strongpasswordCounter1"}, ) assert register.status_code == 200 csrf = register.json()["csrf_token"] entry_1 = client.post( "/time-entries", headers={"x-csrf-token": csrf}, json={ "date": "2026-03-02", "start_time": "08:00", "end_time": "13:00", "break_minutes": 0, }, ) assert entry_1.status_code == 200 entry_2 = client.post( "/time-entries", headers={"x-csrf-token": csrf}, json={ "date": "2026-03-03", "start_time": "08:00", "end_time": "13:00", "break_minutes": 0, }, ) assert entry_2.status_code == 200 weekend_entry = client.post( "/time-entries", headers={"x-csrf-token": csrf}, json={ "date": "2026-03-07", "start_time": "08:00", "end_time": "13:00", "break_minutes": 0, }, ) assert weekend_entry.status_code == 200 add_vacation = client.post( "/settings/vacations/add", data={ "start_date": "2026-03-04", "end_date": "2026-03-04", "csrf_token": csrf, }, follow_redirects=False, ) assert add_vacation.status_code == 303 add_holiday = client.post( "/special-day/toggle", data={ "date": "2026-03-05", "status": "holiday", "return_to": "/dashboard?date=2026-03-05", "csrf_token": csrf, }, follow_redirects=False, ) assert add_holiday.status_code == 303 enable_counter = client.post( "/settings/workhours-counter", data={ "workhours_counter_enabled": "on", "workhours_counter_start_date": "2026-03-01", "workhours_counter_end_date": "2026-03-31", "workhours_counter_manual_offset_hours": "2.5", "csrf_token": csrf, }, follow_redirects=False, ) assert enable_counter.status_code == 303 settings_page = client.get("/settings") assert settings_page.status_code == 200 assert "Aktueller Stand im gewählten Zeitraum:" in settings_page.text def test_workhours_counter_counts_flagged_non_working_days_as_regular_workdays(app): with TestClient(app) as client: register = client.post( "/auth/register", json={"email": "workcounter-flags@example.com", "password": "strongpasswordCounter2"}, ) assert register.status_code == 200 csrf = register.json()["csrf_token"] entry_1 = client.post( "/time-entries", headers={"x-csrf-token": csrf}, json={"date": "2026-03-02", "start_time": "08:00", "end_time": "13:00", "break_minutes": 0}, ) assert entry_1.status_code == 200 entry_2 = client.post( "/time-entries", headers={"x-csrf-token": csrf}, json={"date": "2026-03-03", "start_time": "08:00", "end_time": "13:00", "break_minutes": 0}, ) assert entry_2.status_code == 200 update_workdays = client.post( "/settings/workdays", data={ "working_days": ["0", "1", "2", "3", "4"], "count_vacation_as_worktime": "on", "count_holiday_as_worktime": "on", "count_sick_as_worktime": "on", "csrf_token": csrf, }, follow_redirects=False, ) assert update_workdays.status_code == 303 add_vacation = client.post( "/settings/vacations/add", data={"start_date": "2026-03-04", "end_date": "2026-03-04", "csrf_token": csrf}, follow_redirects=False, ) assert add_vacation.status_code == 303 add_holiday = client.post( "/special-day/toggle", data={"date": "2026-03-05", "status": "holiday", "return_to": "/dashboard?date=2026-03-05", "csrf_token": csrf}, follow_redirects=False, ) assert add_holiday.status_code == 303 add_sick = client.post( "/special-day/toggle", data={"date": "2026-03-06", "status": "sick", "return_to": "/dashboard?date=2026-03-06", "csrf_token": csrf}, follow_redirects=False, ) assert add_sick.status_code == 303 enable_counter = client.post( "/settings/workhours-counter", data={ "workhours_counter_enabled": "on", "workhours_counter_start_date": "2026-03-01", "workhours_counter_end_date": "2026-03-31", "workhours_counter_manual_offset_hours": "2.5", "csrf_token": csrf, }, follow_redirects=False, ) assert enable_counter.status_code == 303 settings_page = client.get("/settings") assert settings_page.status_code == 200 assert "Aktueller Stand im gewählten Zeitraum:" in settings_page.text def test_automatic_break_rules_can_be_enabled_in_settings(app): with TestClient(app) as client: register = client.post( "/auth/register", json={"email": "auto-break-settings@example.com", "password": "strongpasswordBreak1"}, ) assert register.status_code == 200 csrf = register.json()["csrf_token"] update_break_settings = client.post( "/settings/weekly-target", data={ "weekly_target_hours": "25", "automatic_break_rules_enabled": "on", "default_break_minutes": "20", "csrf_token": csrf, }, follow_redirects=False, ) assert update_break_settings.status_code == 303 me = client.get("/me") assert me.status_code == 200 assert me.json()["automatic_break_rules_enabled"] is True assert me.json()["default_break_minutes"] == 20 def test_new_entry_uses_automatic_break_rules_for_new_entries(app): with TestClient(app) as client: register = client.post( "/auth/register", json={"email": "auto-break-new@example.com", "password": "strongpasswordBreak2"}, ) assert register.status_code == 200 csrf = register.json()["csrf_token"] update_break_settings = client.post( "/settings/weekly-target", data={ "weekly_target_hours": "25", "automatic_break_rules_enabled": "on", "default_break_minutes": "20", "csrf_token": csrf, }, follow_redirects=False, ) assert update_break_settings.status_code == 303 create_entry = client.post( "/entry/new", data={ "date": "2026-03-03", "start_time": "08:00", "end_time": "14:01", "break_minutes": "0", "break_mode": "auto", "notes": "", "return_to": "/dashboard?date=2026-03-03", "csrf_token": csrf, }, follow_redirects=False, ) assert create_entry.status_code == 303 entries = client.get("/time-entries", params={"from": "2026-03-03", "to": "2026-03-03"}) assert entries.status_code == 200 payload = entries.json()["items"] assert len(payload) == 1 assert payload[0]["break_minutes"] == 30 assert payload[0]["break_mode"] == "auto" assert payload[0]["net_minutes"] == 331 def test_edit_entry_can_override_automatic_break_rules_manually(app): with TestClient(app) as client: register = client.post( "/auth/register", json={"email": "auto-break-edit@example.com", "password": "strongpasswordBreak3"}, ) assert register.status_code == 200 csrf = register.json()["csrf_token"] update_break_settings = client.post( "/settings/weekly-target", data={ "weekly_target_hours": "25", "automatic_break_rules_enabled": "on", "default_break_minutes": "20", "csrf_token": csrf, }, follow_redirects=False, ) assert update_break_settings.status_code == 303 create_entry = client.post( "/time-entries", headers={"x-csrf-token": csrf}, json={ "date": "2026-03-03", "start_time": "08:00", "end_time": "14:30", "break_mode": "auto", }, ) assert create_entry.status_code == 200 entry_id = create_entry.json()["id"] assert create_entry.json()["break_minutes"] == 30 assert create_entry.json()["break_mode"] == "auto" edit_entry = client.post( f"/entry/{entry_id}/edit", data={ "date": "2026-03-03", "start_time": "08:00", "end_time": "15:30", "break_minutes": "15", "break_mode": "manual", "notes": "", "return_to": "/dashboard?date=2026-03-03", "csrf_token": csrf, }, follow_redirects=False, ) assert edit_entry.status_code == 303 updated = client.get("/time-entries", params={"from": "2026-03-03", "to": "2026-03-03"}) assert updated.status_code == 200 payload = updated.json()["items"] assert len(payload) == 1 assert payload[0]["break_minutes"] == 15 assert payload[0]["break_mode"] == "manual" def test_edit_entry_recalculates_auto_break_when_times_change(app): with TestClient(app) as client: register = client.post( "/auth/register", json={"email": "auto-break-recalc@example.com", "password": "strongpasswordBreak4"}, ) assert register.status_code == 200 csrf = register.json()["csrf_token"] update_break_settings = client.post( "/settings/weekly-target", data={ "weekly_target_hours": "25", "automatic_break_rules_enabled": "on", "default_break_minutes": "20", "csrf_token": csrf, }, follow_redirects=False, ) assert update_break_settings.status_code == 303 create_entry = client.post( "/time-entries", headers={"x-csrf-token": csrf}, json={ "date": "2026-03-03", "start_time": "08:00", "end_time": "17:00", "break_mode": "auto", }, ) assert create_entry.status_code == 200 entry_id = create_entry.json()["id"] assert create_entry.json()["break_minutes"] == 30 update_entry = client.patch( f"/time-entries/{entry_id}", headers={"x-csrf-token": csrf}, json={ "end_time": "17:30", "break_mode": "auto", }, ) assert update_entry.status_code == 200 assert update_entry.json()["break_minutes"] == 45 assert update_entry.json()["break_mode"] == "auto" def test_new_entry_uses_configured_default_break_when_auto_break_is_disabled(app): with TestClient(app) as client: register = client.post( "/auth/register", json={"email": "manual-break-default@example.com", "password": "strongpasswordBreak5"}, ) assert register.status_code == 200 csrf = register.json()["csrf_token"] update_break_settings = client.post( "/settings/weekly-target", data={ "weekly_target_hours": "25", "default_break_minutes": "25", "csrf_token": csrf, }, follow_redirects=False, ) assert update_break_settings.status_code == 303 create_entry = client.post( "/time-entries", headers={"x-csrf-token": csrf}, json={ "date": "2026-03-03", "start_time": "08:00", "end_time": "14:00", }, ) assert create_entry.status_code == 200 assert create_entry.json()["break_minutes"] == 25 assert create_entry.json()["break_mode"] == "manual" def test_entry_form_renders_full_day_button(app): with TestClient(app) as client: register = client.post( "/auth/register", json={"email": "entry-form-fullday@example.com", "password": "strongpasswordBreak7"}, ) assert register.status_code == 200 entry_form = client.get("/entry/new?date=2026-03-03") assert entry_form.status_code == 200 assert 'name="date"' in entry_form.text assert 'value="2026-03-03"' in entry_form.text assert 'data-action="entry-apply-full-day"' in entry_form.text assert 'data-full-day-net-minutes="' in entry_form.text def test_auto_break_setting_keeps_manual_default_break_value(app): with TestClient(app) as client: register = client.post( "/auth/register", json={"email": "manual-break-preserve@example.com", "password": "strongpasswordBreak6"}, ) assert register.status_code == 200 csrf = register.json()["csrf_token"] save_manual_break = client.post( "/settings/weekly-target", data={ "weekly_target_hours": "25", "default_break_minutes": "35", "csrf_token": csrf, }, follow_redirects=False, ) assert save_manual_break.status_code == 303 enable_auto_break = client.post( "/settings/weekly-target", data={ "weekly_target_hours": "25", "automatic_break_rules_enabled": "on", "csrf_token": csrf, }, follow_redirects=False, ) assert enable_auto_break.status_code == 303 me = client.get("/me") assert me.status_code == 200 assert me.json()["automatic_break_rules_enabled"] is True assert me.json()["default_break_minutes"] == 35 def test_workhours_counter_target_warning_banner_is_rendered(app): with TestClient(app) as client: register = client.post( "/auth/register", json={"email": "workcounter-warning@example.com", "password": "strongpasswordCounterWarn1"}, ) assert register.status_code == 200 csrf = register.json()["csrf_token"] today = date.today() start_date = (today - timedelta(days=14)).isoformat() end_date = (today + timedelta(days=14)).isoformat() enable_counter = client.post( "/settings/workhours-counter", data={ "workhours_counter_enabled": "on", "workhours_counter_start_date": start_date, "workhours_counter_end_date": end_date, "workhours_counter_target_hours": "999", "csrf_token": csrf, }, follow_redirects=False, ) assert enable_counter.status_code == 303 dashboard = client.get("/dashboard") assert dashboard.status_code == 200 assert "Achtung: Arbeitsstundenziel wird ggf. nicht erreicht" in dashboard.text month = client.get("/month") assert month.status_code == 200 assert "Achtung: Arbeitsstundenziel wird ggf. nicht erreicht" in month.text def test_register_onboarding_applies_optional_settings(app): with TestClient(app) as client: register_page = client.get("/register") assert register_page.status_code == 200 csrf_match = re.search(r'name="csrf_token" value="([^"]+)"', register_page.text) assert csrf_match is not None csrf = csrf_match.group(1) register_submit = client.post( "/register", data={ "email": "onboarding@example.com", "password": "strongpasswordOnboard1", "federal_state": "HH", "vacation_days_total": "22", "vacation_show_in_header": "on", "preferred_home_view": "month", "entry_mode": "auto_until_today", "overtime_start_date": "2026-02-02", "overtime_expiry_days": "90", "expire_negative_overtime": "on", "workhours_counter_enabled": "on", "workhours_counter_show_in_header": "on", "workhours_counter_start_date": "2026-03-01", "workhours_counter_end_date": "2026-03-31", "workhours_counter_manual_offset_hours": "80", "workhours_counter_target_hours": "120", "workhours_counter_target_email_enabled": "on", "working_days": ["0", "1", "2", "3"], "mfa_preference": "none", "csrf_token": csrf, }, follow_redirects=False, ) assert register_submit.status_code == 303 assert register_submit.headers["location"].startswith("/month") me = client.get("/me") assert me.status_code == 200 payload = me.json() assert payload["federal_state"] == "HH" assert payload["vacation_days_total"] == 22 assert payload["vacation_show_in_header"] is True assert payload["preferred_home_view"] == "month" assert payload["theme_preference"] == "auto" assert payload["entry_mode"] == "auto_until_today" assert payload["overtime_start_date"] == "2026-02-02" assert payload["overtime_expiry_days"] == 90 assert payload["expire_negative_overtime"] is True assert payload["working_days"] == [0, 1, 2, 3] assert payload["workhours_counter_enabled"] is True assert payload["workhours_counter_show_in_header"] is True assert payload["workhours_counter_start_date"] == "2026-03-01" assert payload["workhours_counter_end_date"] == "2026-03-31" assert payload["workhours_counter_manual_offset_minutes"] == 4800 assert payload["workhours_counter_target_minutes"] == 7200 def test_settings_export_all_supports_backup_and_existing_formats(app): with TestClient(app) as client: register = client.post( "/auth/register", json={"email": "settings-export@example.com", "password": "strongpasswordExportAll1"}, ) assert register.status_code == 200 csrf = register.json()["csrf_token"] create = client.post( "/time-entries", headers={"x-csrf-token": csrf}, json={ "date": "2026-03-03", "start_time": "08:30", "end_time": "15:00", "break_minutes": 30, }, ) assert create.status_code == 200 export_xlsx = client.post( "/settings/export-all", data={"format": "xlsx", "csrf_token": csrf}, ) assert export_xlsx.status_code == 200 assert "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" in export_xlsx.headers["content-type"] export_pdf = client.post( "/settings/export-all", data={"format": "pdf", "csrf_token": csrf}, ) assert export_pdf.status_code == 200 assert "application/pdf" in export_pdf.headers["content-type"] assert export_pdf.content.startswith(b"%PDF") export_backup = client.post( "/settings/export-all", data={"format": "backup_json", "csrf_token": csrf}, ) assert export_backup.status_code == 200 assert "application/json" in export_backup.headers["content-type"] payload = export_backup.json() assert payload["backup_version"] == 2 assert payload["settings"]["theme_preference"] == "auto" assert "user" not in payload assert payload["settings"]["weekly_target_minutes"] == 1500 assert len(payload["time_entries"]) == 1 assert "weekly_target_rules" in payload assert "vacation_periods" in payload assert "special_day_statuses" in payload assert "overtime_adjustments" in payload def test_settings_backup_import_preview_and_execute_merge(app): with TestClient(app) as source_client: register = source_client.post( "/auth/register", json={"email": "backup-source@example.com", "password": "strongpasswordBackup1"}, ) assert register.status_code == 200 source_csrf = register.json()["csrf_token"] source_client.post( "/settings/workdays", data={"working_days": ["0", "1", "2", "3"], "csrf_token": source_csrf}, follow_redirects=False, ) create_source_entry = source_client.post( "/time-entries", headers={"x-csrf-token": source_csrf}, json={ "date": "2026-03-04", "start_time": "08:30", "end_time": "14:30", "break_minutes": 30, }, ) assert create_source_entry.status_code == 200 export_backup = source_client.post( "/settings/export-all", data={"format": "backup_json", "csrf_token": source_csrf}, ) assert export_backup.status_code == 200 backup_content = export_backup.content with TestClient(app) as target_client: register = target_client.post( "/auth/register", json={"email": "backup-target@example.com", "password": "strongpasswordBackup2"}, ) assert register.status_code == 200 target_csrf = register.json()["csrf_token"] conflicting_entry = target_client.post( "/time-entries", headers={"x-csrf-token": target_csrf}, json={ "date": "2026-03-04", "start_time": "09:00", "end_time": "15:00", "break_minutes": 30, }, ) assert conflicting_entry.status_code == 200 preview_response = target_client.post( "/settings/import/preview", data={"import_mode": "merge", "csrf_token": target_csrf}, files={"backup_file": ("stundenfuchs-backup.json", backup_content, "application/json")}, ) assert preview_response.status_code == 200 assert "Importvorschau" in preview_response.text assert "Konflikte Arbeitszeiteinträge: 1" in preview_response.text preview_id_match = re.search(r'name="preview_id" value="([^"]+)"', preview_response.text) assert preview_id_match is not None preview_id = preview_id_match.group(1) execute_response = target_client.post( "/settings/import/execute", data={"preview_id": preview_id, "csrf_token": target_csrf}, ) assert execute_response.status_code == 200 assert "Backup importiert." in execute_response.text me = target_client.get("/me") assert me.status_code == 200 assert me.json()["working_days"] == [0, 1, 2, 3] def test_register_can_import_backup_during_signup(app): with TestClient(app) as source_client: register = source_client.post( "/auth/register", json={"email": "register-import-source@example.com", "password": "strongpasswordImport1"}, ) assert register.status_code == 200 source_csrf = register.json()["csrf_token"] source_client.post( "/settings/preferences", data={ "preferred_home_view": "month", "theme_preference": "light", "preferred_month_view_mode": "weeks", "entry_mode": "auto_until_today", "csrf_token": source_csrf, }, follow_redirects=False, ) source_client.post( "/settings/weekly-target", data={ "weekly_target_hours": "25", "automatic_break_rules_enabled": "on", "default_break_minutes": "20", "csrf_token": source_csrf, }, follow_redirects=False, ) source_client.post( "/settings/workdays", data={ "working_days": ["0", "1", "2", "3"], "count_vacation_as_worktime": "on", "csrf_token": source_csrf, }, follow_redirects=False, ) create_source_entry = source_client.post( "/time-entries", headers={"x-csrf-token": source_csrf}, json={ "date": "2026-03-03", "start_time": "08:30", "end_time": "15:00", "break_minutes": 30, }, ) assert create_source_entry.status_code == 200 export_backup = source_client.post( "/settings/export-all", data={"format": "backup_json", "csrf_token": source_csrf}, ) assert export_backup.status_code == 200 backup_content = export_backup.content with TestClient(app) as target_client: register_page = target_client.get("/register") assert register_page.status_code == 200 csrf_match = re.search(r'name="csrf_token" value="([^"]+)"', register_page.text) assert csrf_match is not None register_csrf = csrf_match.group(1) register_submit = target_client.post( "/register", data={ "email": "register-import-target@example.com", "password": "strongpasswordImport2", "entry_mode": "manual", "mfa_preference": "none", "csrf_token": register_csrf, }, files={"backup_file": ("stundenfuchs-backup.json", backup_content, "application/json")}, follow_redirects=False, ) assert register_submit.status_code == 303 me = target_client.get("/me") assert me.status_code == 200 payload = me.json() assert payload["preferred_home_view"] == "month" assert payload["theme_preference"] == "light" assert payload["entry_mode"] == "auto_until_today" assert payload["working_days"] == [0, 1, 2, 3] assert payload["count_vacation_as_worktime"] is True assert payload["automatic_break_rules_enabled"] is True def test_settings_import_accepts_legacy_backup_version_one(app): with TestClient(app) as client: register = client.post( "/auth/register", json={"email": "legacy-import@example.com", "password": "strongpasswordLegacy1"}, ) assert register.status_code == 200 csrf = register.json()["csrf_token"] export_backup = client.post( "/settings/export-all", data={"format": "backup_json", "csrf_token": csrf}, ) assert export_backup.status_code == 200 payload = export_backup.json() legacy_payload = { **payload, "backup_version": 1, "user": { "email": "legacy@example.com", "created_at": "2026-03-01T12:00:00+00:00", "settings": payload["settings"], }, } del legacy_payload["settings"] preview_response = client.post( "/settings/import/preview", data={"import_mode": "merge", "csrf_token": csrf}, files={"backup_file": ("legacy-backup.json", json.dumps(legacy_payload).encode("utf-8"), "application/json")}, ) assert preview_response.status_code == 200 assert "Importvorschau" in preview_response.text def test_user_can_delete_own_account_and_related_data(app): with TestClient(app) as client: register = client.post( "/auth/register", json={"email": "delete-me@example.com", "password": "strongpasswordDelete1"}, ) assert register.status_code == 200 csrf = register.json()["csrf_token"] user_id = register.json()["id"] create = client.post( "/time-entries", headers={"x-csrf-token": csrf}, json={ "date": "2026-03-03", "start_time": "08:30", "end_time": "15:00", "break_minutes": 30, }, ) assert create.status_code == 200 delete_account = client.post( "/settings/account/delete", data={ "confirm_email": "delete-me@example.com", "current_password": "strongpasswordDelete1", "confirm_delete": "on", "csrf_token": csrf, }, follow_redirects=False, ) assert delete_account.status_code == 303 assert delete_account.headers["location"] == "/login?msg=account_deleted" with Session(get_engine()) as db: user = db.execute(select(User).where(User.id == user_id)).scalar_one_or_none() entries = db.execute(select(TimeEntry).where(TimeEntry.user_id == user_id)).scalars().all() assert user is None assert entries == [] def test_settings_default_view_redirect(app): with TestClient(app) as client: register = client.post( "/auth/register", json={"email": "prefs@example.com", "password": "strongpasswordPrefs1"}, ) assert register.status_code == 200 csrf = register.json()["csrf_token"] update_prefs = client.post( "/settings/preferences", data={ "preferred_home_view": "month", "theme_preference": "light", "preferred_month_view_mode": "weeks", "csrf_token": csrf, }, follow_redirects=False, ) assert update_prefs.status_code == 303 root_redirect = client.get("/", follow_redirects=False) assert root_redirect.status_code == 303 assert root_redirect.headers["location"].startswith("/month?view=weeks") dashboard_redirect = client.get("/dashboard", follow_redirects=False) assert dashboard_redirect.status_code == 303 assert dashboard_redirect.headers["location"].startswith("/month?view=weeks") month_page = client.get("/month") assert month_page.status_code == 200 assert 'data-theme="light"' in month_page.text def test_main_navigation_uses_explicit_period_links(app): with TestClient(app) as client: register = client.post( "/auth/register", json={"email": "nav-periods@example.com", "password": "strongpasswordNav1"}, ) assert register.status_code == 200 csrf = register.json()["csrf_token"] update_prefs = client.post( "/settings/preferences", data={ "preferred_home_view": "month", "preferred_month_view_mode": "flat", "csrf_token": csrf, }, follow_redirects=False, ) assert update_prefs.status_code == 303 month_page = client.get("/month", params={"month": "2026-03", "view": "flat"}) assert month_page.status_code == 200 assert f'href="/dashboard?date={date.today().isoformat()}"' in month_page.text assert 'href="/month?month=2026-03&view=flat"' in month_page.text def test_overtime_start_and_expiry_rules(app): with TestClient(app) as client: register = client.post( "/auth/register", json={"email": "overtime@example.com", "password": "strongpasswordOver1"}, ) assert register.status_code == 200 csrf = register.json()["csrf_token"] set_target = client.post( "/weekly-target", data={ "week_start": "2026-03-02", "weekly_target_hours": "10", "scope": "all_weeks", "csrf_token": csrf, }, follow_redirects=False, ) assert set_target.status_code == 303 entry_week1 = client.post( "/time-entries", headers={"x-csrf-token": csrf}, json={ "date": "2026-03-02", "start_time": "08:00", "end_time": "20:00", "break_minutes": 0, }, ) assert entry_week1.status_code == 200 entry_week2 = client.post( "/time-entries", headers={"x-csrf-token": csrf}, json={ "date": "2026-03-09", "start_time": "08:00", "end_time": "16:00", "break_minutes": 0, }, ) assert entry_week2.status_code == 200 baseline = client.get("/reports/week", params={"date": "2026-03-09"}) assert baseline.status_code == 200 assert baseline.json()["cumulative_delta_minutes"] == 0 set_start_date = client.post( "/settings/overtime", data={ "overtime_start_date": "2026-03-09", "overtime_expiry_days": "", "csrf_token": csrf, }, follow_redirects=False, ) assert set_start_date.status_code == 303 with_start = client.get("/reports/week", params={"date": "2026-03-09"}) assert with_start.status_code == 200 assert with_start.json()["cumulative_delta_minutes"] == -120 week_before_start = client.get("/reports/week", params={"date": "2026-03-02"}) assert week_before_start.status_code == 200 assert week_before_start.json()["weekly_ist_minutes"] == 0 assert week_before_start.json()["weekly_soll_minutes"] == 0 assert week_before_start.json()["weekly_delta_minutes"] == 0 assert week_before_start.json()["cumulative_delta_minutes"] == 0 set_expiry_keep_negative = client.post( "/settings/overtime", data={ "overtime_start_date": "", "overtime_expiry_days": "3", "csrf_token": csrf, }, follow_redirects=False, ) assert set_expiry_keep_negative.status_code == 303 expiry_keep_negative = client.get("/reports/week", params={"date": "2026-03-09"}) assert expiry_keep_negative.status_code == 200 assert expiry_keep_negative.json()["cumulative_delta_minutes"] == -960 set_expiry_drop_negative = client.post( "/settings/overtime", data={ "overtime_start_date": "", "overtime_expiry_days": "3", "expire_negative_overtime": "on", "csrf_token": csrf, }, follow_redirects=False, ) assert set_expiry_drop_negative.status_code == 303 expiry_drop_negative = client.get("/reports/week", params={"date": "2026-03-09"}) assert expiry_drop_negative.status_code == 200 assert expiry_drop_negative.json()["cumulative_delta_minutes"] == -240 def test_overtime_adjustment_counts_before_overtime_start_date(app): with TestClient(app) as client: register = client.post( "/auth/register", json={"email": "overtime-adjustment@example.com", "password": "strongpasswordAdjust1"}, ) assert register.status_code == 200 csrf = register.json()["csrf_token"] set_start_date = client.post( "/settings/overtime", data={ "overtime_start_date": "2026-03-09", "overtime_expiry_days": "", "csrf_token": csrf, }, follow_redirects=False, ) assert set_start_date.status_code == 303 adjustment = client.post( "/overtime-adjustment/set", data={ "date": "2026-03-03", "adjustment_mode": "manual", "adjustment_value": "-02:00", "return_to": "/entry/new?date=2026-03-03", "csrf_token": csrf, }, follow_redirects=False, ) assert adjustment.status_code == 303 week_before_start = client.get("/reports/week", params={"date": "2026-03-03"}) assert week_before_start.status_code == 200 payload = week_before_start.json() assert payload["weekly_ist_minutes"] == 0 assert payload["weekly_soll_minutes"] == 0 assert payload["weekly_delta_minutes"] == -120 assert payload["cumulative_delta_minutes"] == -120 assert payload["days"][1]["overtime_adjustment_minutes"] == -120 def test_overtime_adjustment_can_be_combined_with_holiday(app): with TestClient(app) as client: register = client.post( "/auth/register", json={"email": "overtime-adjustment-holiday@example.com", "password": "strongpasswordAdjust2"}, ) assert register.status_code == 200 csrf = register.json()["csrf_token"] set_target = client.post( "/weekly-target", data={ "week_start": "2026-03-02", "weekly_target_hours": "30", "scope": "all_weeks", "csrf_token": csrf, }, follow_redirects=False, ) assert set_target.status_code == 303 update_workdays = client.post( "/settings/workdays", data={ "working_days": ["0", "1", "2", "3"], "csrf_token": csrf, }, follow_redirects=False, ) assert update_workdays.status_code == 303 add_holiday = client.post( "/special-day/toggle", data={ "date": "2026-03-03", "status": "holiday", "return_to": "/dashboard?date=2026-03-03", "csrf_token": csrf, }, follow_redirects=False, ) assert add_holiday.status_code == 303 add_full_day_adjustment = client.post( "/overtime-adjustment/set", data={ "date": "2026-03-03", "adjustment_mode": "full_day", "full_day_direction": "negative", "return_to": "/entry/new?date=2026-03-03", "csrf_token": csrf, }, follow_redirects=False, ) assert add_full_day_adjustment.status_code == 303 week_report = client.get("/reports/week", params={"date": "2026-03-03"}) assert week_report.status_code == 200 payload = week_report.json() assert payload["days"][1]["special_status"] == "holiday" assert payload["days"][1]["overtime_adjustment_minutes"] == -450 assert payload["weekly_ist_minutes"] == 0 assert payload["weekly_soll_minutes"] == 1350 assert payload["weekly_delta_minutes"] == -1800 def test_overtime_adjustment_interval_mode_changes_delta(app): with TestClient(app) as client: register = client.post( "/auth/register", json={"email": "overtime-adjustment-interval@example.com", "password": "strongpasswordAdjust3"}, ) assert register.status_code == 200 csrf = register.json()["csrf_token"] baseline = client.get("/reports/week", params={"date": "2026-03-03"}) assert baseline.status_code == 200 add_interval_adjustment = client.post( "/overtime-adjustment/set", data={ "date": "2026-03-03", "adjustment_mode": "interval", "interval_start_time": "08:15", "interval_end_time": "10:45", "interval_direction": "positive", "return_to": "/overtime-adjustment/edit?date=2026-03-03", "csrf_token": csrf, }, follow_redirects=False, ) assert add_interval_adjustment.status_code == 303 updated = client.get("/reports/week", params={"date": "2026-03-03"}) assert updated.status_code == 200 updated_payload = updated.json() assert updated_payload["weekly_delta_minutes"] == baseline.json()["weekly_delta_minutes"] + 150 assert updated_payload["days"][1]["overtime_adjustment_minutes"] == 150 def test_day_forms_are_split_by_function(app): with TestClient(app) as client: register = client.post( "/auth/register", json={"email": "focused-forms@example.com", "password": "strongpasswordForms1"}, ) assert register.status_code == 200 time_form = client.get("/entry/new", params={"date": "2026-03-03"}) assert time_form.status_code == 200 assert "Arbeitsbeginn" in time_form.text assert "Tagesmodus" not in time_form.text status_form = client.get("/day-status/edit", params={"date": "2026-03-03", "status": "holiday"}) assert status_form.status_code == 200 assert "Feiertag" in status_form.text assert "Arbeitsbeginn" not in status_form.text overtime_form = client.get("/overtime-adjustment/edit", params={"date": "2026-03-03"}) assert overtime_form.status_code == 200 assert "Von-Bis Uhrzeit" in overtime_form.text assert "Arbeitsbeginn" not in overtime_form.text def test_non_working_days_can_count_as_regular_workdays(app): with TestClient(app) as client: register = client.post( "/auth/register", json={"email": "count-special-days@example.com", "password": "strongpasswordCount1"}, ) assert register.status_code == 200 csrf = register.json()["csrf_token"] update_workdays = client.post( "/settings/workdays", data={ "working_days": ["0", "1", "2", "3"], "count_vacation_as_worktime": "on", "count_holiday_as_worktime": "on", "count_sick_as_worktime": "on", "csrf_token": csrf, }, follow_redirects=False, ) assert update_workdays.status_code == 303 add_vacation = client.post( "/vacation/day/toggle", data={"date": "2026-03-03", "return_to": "/dashboard?date=2026-03-03", "csrf_token": csrf}, follow_redirects=False, ) assert add_vacation.status_code == 303 add_holiday = client.post( "/special-day/toggle", data={"date": "2026-03-04", "status": "holiday", "return_to": "/dashboard?date=2026-03-03", "csrf_token": csrf}, follow_redirects=False, ) assert add_holiday.status_code == 303 add_sick = client.post( "/special-day/toggle", data={"date": "2026-03-05", "status": "sick", "return_to": "/dashboard?date=2026-03-03", "csrf_token": csrf}, follow_redirects=False, ) assert add_sick.status_code == 303 week_report = client.get("/reports/week", params={"date": "2026-03-03"}) assert week_report.status_code == 200 payload = week_report.json() assert payload["weekly_soll_minutes"] == 1500 assert payload["weekly_ist_minutes"] == 1125 assert payload["weekly_delta_minutes"] == -375 def test_auto_entry_mode_prefills_only_until_today(app): with TestClient(app) as client: register = client.post( "/auth/register", json={"email": "auto-mode@example.com", "password": "strongpasswordAuto1"}, ) assert register.status_code == 200 csrf = register.json()["csrf_token"] set_auto_mode = client.post( "/settings/preferences", data={ "preferred_home_view": "week", "preferred_month_view_mode": "flat", "entry_mode": "auto_until_today", "csrf_token": csrf, }, follow_redirects=False, ) assert set_auto_mode.status_code == 303 today = date.today() today_items = client.get( "/time-entries", params={"from": today.isoformat(), "to": today.isoformat()}, ) assert today_items.status_code == 200 today_payload = today_items.json() if today.weekday() <= 4: assert len(today_payload["items"]) == 1 assert today_payload["items"][0]["date"] == today.isoformat() assert today_payload["items"][0]["start_time"] == "08:30" else: assert len(today_payload["items"]) == 0 future_workday = today + timedelta(days=1) while future_workday.weekday() > 4: future_workday += timedelta(days=1) future_items = client.get( "/time-entries", params={"from": future_workday.isoformat(), "to": future_workday.isoformat()}, ) assert future_items.status_code == 200 assert len(future_items.json()["items"]) == 0 def test_deleting_auto_entry_keeps_day_empty_in_auto_mode(app): with TestClient(app) as client: register = client.post( "/auth/register", json={"email": "auto-delete@example.com", "password": "strongpasswordAutoDelete1"}, ) assert register.status_code == 200 csrf = register.json()["csrf_token"] update_workdays = client.post( "/settings/workdays", data={ "working_days": ["0", "1", "2", "3", "4", "5", "6"], "csrf_token": csrf, }, follow_redirects=False, ) assert update_workdays.status_code == 303 set_auto_mode = client.post( "/settings/preferences", data={ "preferred_home_view": "week", "preferred_month_view_mode": "flat", "entry_mode": "auto_until_today", "csrf_token": csrf, }, follow_redirects=False, ) assert set_auto_mode.status_code == 303 today = date.today().isoformat() initial_items = client.get("/time-entries", params={"from": today, "to": today}) assert initial_items.status_code == 200 assert len(initial_items.json()["items"]) == 1 entry_id = initial_items.json()["items"][0]["id"] delete_entry = client.delete( f"/time-entries/{entry_id}", headers={"x-csrf-token": csrf}, ) assert delete_entry.status_code == 200 after_delete = client.get("/time-entries", params={"from": today, "to": today}) assert after_delete.status_code == 200 assert after_delete.json()["items"] == [] def test_switching_modes_remove_future_auto_entries(app): with TestClient(app) as client: register = client.post( "/auth/register", json={"email": "auto-manual-switch@example.com", "password": "strongpasswordAuto2"}, ) assert register.status_code == 200 csrf = register.json()["csrf_token"] today = date.today() future_workday = today + timedelta(days=1) while future_workday.weekday() > 4: future_workday += timedelta(days=1) create_future_auto_entry = client.post( "/time-entries", headers={"x-csrf-token": csrf}, json={ "date": future_workday.isoformat(), "start_time": "08:30", "end_time": "14:00", "break_minutes": 0, "notes": "Automatisch vorausgefuellt", }, ) assert create_future_auto_entry.status_code == 200 before_switch = client.get( "/time-entries", params={"from": future_workday.isoformat(), "to": future_workday.isoformat()}, ) assert before_switch.status_code == 200 assert len(before_switch.json()["items"]) == 1 enable_auto_until_today = client.post( "/settings/preferences", data={ "preferred_home_view": "week", "preferred_month_view_mode": "flat", "entry_mode": "auto_until_today", "csrf_token": csrf, }, follow_redirects=False, ) assert enable_auto_until_today.status_code == 303 after_auto_until_today = client.get( "/time-entries", params={"from": future_workday.isoformat(), "to": future_workday.isoformat()}, ) assert after_auto_until_today.status_code == 200 assert len(after_auto_until_today.json()["items"]) == 0 recreate_future_auto_entry = client.post( "/time-entries", headers={"x-csrf-token": csrf}, json={ "date": future_workday.isoformat(), "start_time": "08:30", "end_time": "14:00", "break_minutes": 0, "notes": "Automatisch vorausgefuellt", }, ) assert recreate_future_auto_entry.status_code == 200 disable_auto = client.post( "/settings/preferences", data={ "preferred_home_view": "week", "preferred_month_view_mode": "flat", "entry_mode": "manual", "csrf_token": csrf, }, follow_redirects=False, ) assert disable_auto.status_code == 303 after_disable = client.get( "/time-entries", params={"from": future_workday.isoformat(), "to": future_workday.isoformat()}, ) assert after_disable.status_code == 200 assert len(after_disable.json()["items"]) == 0 def test_help_page_is_available_for_authenticated_users(app): with TestClient(app) as client: register = client.post( "/auth/register", json={"email": "help-page@example.com", "password": "strongpasswordHelp1"}, ) assert register.status_code == 200 help_page = client.get("/hilfe") assert help_page.status_code == 200 assert "Stundenausgleich (S)" in help_page.text assert "Arbeitsstunden-Counter" in help_page.text assert "Schritt-für-Schritt-Anleitungen" in help_page.text assert "gesetzliche Mindestpause" in help_page.text def test_root_renders_guest_landing(app): with TestClient(app) as guest_client: landing = guest_client.get("/") assert landing.status_code == 200 assert "Arbeitszeit, Urlaub und Überstunden an einem Ort" in landing.text assert "Jetzt registrieren" in landing.text assert 'href="/register"' in landing.text assert 'href="/login"' in landing.text