Files
patrickandClaude Sonnet 5 1dcc04ae07 feat(reports): DATEV-Monatsblatt-Export (Blanko-Vorlage) für Steuerberater
Neuer PDF-Export GET /reports/datev-monthly/export: ein Blatt pro
Mitarbeiter/Monat im Layout der DATEV-Stundenaufzeichnungs-Vorlage
(Beginn/Pause/Ende/Dauer + K/U/UU/F/SA/SU-Kürzel + Bemerkungen +
Summe + Unterschriftsfelder). Führt time_entries, Absences und
Feiertage pro Kalendertag zusammen.

Außerdem: Mustervorlagen für neue Backend-Module (Model/Schema/Router/
Migration/Test) und eine Frontend-Page-Vorlage, abgeleitet vom
hours_payouts-Modul als aktuellstem sauberen Muster.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CviFgc739S89xS97pvjszj
2026-08-06 22:19:54 +02:00

67 lines
2.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""TEMPLATE Mustervorlage für Router-Tests. Abgeleitet von test_hours_payouts.py.
Anleitung:
1. Nach backend/tests/test_xxx_things.py kopieren, Endpunkte/Felder anpassen
2. pytest-Konvention: alle Fixtures scope="session" + loop_scope="session"
(asyncpg + pytest-asyncio 1.x Anforderung siehe project_pytest_asyncio)
3. Ausführen NUR auf dem Server (root@192.168.1.137), nie lokal:
ssh root@192.168.1.137 'cd /opt/timemaster/backend && source venv/bin/activate && python -m pytest tests/test_xxx_things.py -v'
"""
import pytest
import pytest_asyncio
from httpx import AsyncClient
@pytest_asyncio.fixture(scope="session", loop_scope="session")
async def xxx_thing_headers(client: AsyncClient):
resp = await client.post("/api/v1/auth/register", json={
"company_name": "XxxThing GmbH",
"first_name": "Test",
"last_name": "User",
"email": "admin@xxxthinggmbh.de",
"password": "Secret123",
})
assert resp.status_code == 201, resp.text
return {"Authorization": f"Bearer {resp.json()['access_token']}"}
@pytest.mark.asyncio(loop_scope="session")
async def test_create_item(client: AsyncClient, xxx_thing_headers):
r = await client.post("/api/v1/xxx-things",
json={"user_id": "...", "note": "Test"}, headers=xxx_thing_headers)
assert r.status_code == 201, r.text
assert r.json()["status"] == "requested"
@pytest.mark.asyncio(loop_scope="session")
async def test_approve_item(client: AsyncClient, xxx_thing_headers):
create = await client.post("/api/v1/xxx-things",
json={"user_id": "...", "note": "Test"}, headers=xxx_thing_headers)
item_id = create.json()["id"]
ap = await client.post(f"/api/v1/xxx-things/{item_id}/approve", json={}, headers=xxx_thing_headers)
assert ap.status_code == 200, ap.text
assert ap.json()["status"] == "approved"
# Doppel-Approve muss scheitern (409)
ap2 = await client.post(f"/api/v1/xxx-things/{item_id}/approve", json={}, headers=xxx_thing_headers)
assert ap2.status_code == 409
@pytest.mark.asyncio(loop_scope="session")
async def test_company_isolation(client: AsyncClient, xxx_thing_headers):
"""Cross-Tenant-Zugriff muss 404 liefern, nicht 200/403 (verrät keine Existenz)."""
other = await client.post("/api/v1/auth/register", json={
"company_name": "Andere Firma GmbH",
"first_name": "Other", "last_name": "Admin",
"email": "admin@andere-firma.de", "password": "Secret123",
})
other_headers = {"Authorization": f"Bearer {other.json()['access_token']}"}
create = await client.post("/api/v1/xxx-things",
json={"user_id": "...", "note": "Geheim"}, headers=xxx_thing_headers)
item_id = create.json()["id"]
r = await client.get("/api/v1/xxx-things", headers=other_headers)
assert all(item["id"] != item_id for item in r.json()["items"])