Files
stundenfuchs/tests/test_vacations_and_settings.py
T
maddin 9794362f39
CI / checks (push) Has been cancelled
chore: initialize public repository
2026-03-22 12:55:55 +00:00

1904 lines
69 KiB
Python

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["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 "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",
"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["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",
"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")
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&amp;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