Initial commit – TimeMaster Zeiterfassung & HR-Tool

Stand: agent-06 (Audit-Log), agent-05 (Krankmeldung), agent-07 Phase 1 (Personalnummer),
Busylight-Pull-Integration, TOTP/2FA, Abwesenheiten, Zeiterfassung, Kiosk-Grundgerüst.
Migrations 0001–0023 deployed auf 192.168.1.137 + .164.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
sysops
2026-05-23 20:03:27 +02:00
commit 1fedd683e0
178 changed files with 29896 additions and 0 deletions
+128
View File
@@ -0,0 +1,128 @@
import pytest
from httpx import AsyncClient
pytestmark = pytest.mark.asyncio
USERS_URL = "/api/v1/users"
INVITE_URL = "/api/v1/users/invite"
def auth(tokens):
return {"Authorization": f"Bearer {tokens['access_token']}"}
async def test_list_users_as_admin(client: AsyncClient, registered_user):
resp = await client.get(USERS_URL + "/", headers=auth(registered_user["tokens"]))
assert resp.status_code == 200
body = resp.json()
assert "total" in body
assert "items" in body
assert body["total"] >= 1
async def test_list_users_forbidden_for_employee(client: AsyncClient, registered_user):
# Invite an employee first
inv = await client.post(INVITE_URL, headers=auth(registered_user["tokens"]), json={
"email": "employee@test.de",
"first_name": "Anna",
"last_name": "Arbeit",
"role": "EMPLOYEE",
})
assert inv.status_code == 201
# Employee tries to list users should fail
# (We can't log in as employee here without accepting invite;
# so we test the role check via schema validation only)
assert inv.json()["role"] == "EMPLOYEE"
async def test_invite_user(client: AsyncClient, registered_user):
resp = await client.post(INVITE_URL, headers=auth(registered_user["tokens"]), json={
"email": "newcolleague@test.de",
"first_name": "Birgit",
"last_name": "Neu",
"role": "MANAGER",
})
assert resp.status_code == 201
body = resp.json()
assert body["email"] == "newcolleague@test.de"
assert body["role"] == "MANAGER"
assert body["is_active"] is False # not yet accepted
async def test_invite_duplicate_email(client: AsyncClient, registered_user):
await client.post(INVITE_URL, headers=auth(registered_user["tokens"]), json={
"email": "dup@test.de", "first_name": "D", "last_name": "U", "role": "EMPLOYEE",
})
resp = await client.post(INVITE_URL, headers=auth(registered_user["tokens"]), json={
"email": "dup@test.de", "first_name": "D", "last_name": "U", "role": "EMPLOYEE",
})
assert resp.status_code == 400
async def test_get_me(client: AsyncClient, registered_user):
resp = await client.get(USERS_URL + "/me", headers=auth(registered_user["tokens"]))
assert resp.status_code == 200
assert resp.json()["id"] == registered_user["user"]["id"]
async def test_update_user(client: AsyncClient, registered_user):
user_id = registered_user["user"]["id"]
resp = await client.patch(
f"{USERS_URL}/{user_id}",
headers=auth(registered_user["tokens"]),
json={"first_name": "Maximilian"},
)
assert resp.status_code == 200
assert resp.json()["first_name"] == "Maximilian"
async def test_deactivate_and_reactivate(client: AsyncClient, registered_user):
# Invite a second user to deactivate
inv = await client.post(INVITE_URL, headers=auth(registered_user["tokens"]), json={
"email": "temp@test.de", "first_name": "T", "last_name": "Emp", "role": "EMPLOYEE",
})
user_id = inv.json()["id"]
deact = await client.post(
f"{USERS_URL}/{user_id}/deactivate",
headers=auth(registered_user["tokens"]),
)
assert deact.status_code == 200
assert deact.json()["is_active"] is False
react = await client.post(
f"{USERS_URL}/{user_id}/reactivate",
headers=auth(registered_user["tokens"]),
)
assert react.status_code == 200
assert react.json()["is_active"] is True
async def test_cannot_deactivate_self(client: AsyncClient, registered_user):
user_id = registered_user["user"]["id"]
resp = await client.post(
f"{USERS_URL}/{user_id}/deactivate",
headers=auth(registered_user["tokens"]),
)
assert resp.status_code == 400
async def test_set_kiosk_pin(client: AsyncClient, registered_user):
user_id = registered_user["user"]["id"]
resp = await client.post(
f"{USERS_URL}/{user_id}/kiosk-pin",
headers=auth(registered_user["tokens"]),
json={"pin": "1234"},
)
assert resp.status_code == 200
async def test_kiosk_pin_must_be_numeric(client: AsyncClient, registered_user):
user_id = registered_user["user"]["id"]
resp = await client.post(
f"{USERS_URL}/{user_id}/kiosk-pin",
headers=auth(registered_user["tokens"]),
json={"pin": "abcd"},
)
assert resp.status_code == 422