Duplikat-Erkennung: SHA-256-Hash je Dokument (Migration 0024), Upload wird mit 409 abgelehnt, wenn dieselbe Datei bereits an derselben Entität liegt (Vergleich bewusst pro Entität, nicht global - gleiche Datei an zwei Objekten ist kein Duplikat). Frontend zeigt die Backend-Fehlermeldung (Dateiname/Datum des bestehenden Dokuments) statt generischem Text. Nebenbei gefunden+gefixt: "Ansehen" (PDF/JPG/PNG online statt Download) funktionierte nicht zuverlässig, weil window.open() erst nach einem await fetch() aufgerufen wurde - Popup-Blocker werten das nicht mehr als direkte Nutzeraktion. Jetzt öffnet der Tab sofort synchron, die Blob-URL wird nachgeladen. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CVgbozhYmuEhiEJHffRXCV
150 lines
5.7 KiB
Python
150 lines
5.7 KiB
Python
import pytest
|
|
|
|
from tests.conftest import auth_header, login
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_dokument_hochladen_und_liste(client, objekt_mit_position, mitarbeiter_user):
|
|
objekt, _material = objekt_mit_position
|
|
token = await login(client, "mitarbeiter1")
|
|
|
|
hochgeladen = await client.post(
|
|
"/api/v1/dokumente",
|
|
data={"entitaet_typ": "objekt", "entitaet_id": str(objekt.id), "beschreibung": "Zulassung"},
|
|
files={"datei": ("zulassung.pdf", b"%PDF-1.4 fake", "application/pdf")},
|
|
headers=auth_header(token),
|
|
)
|
|
assert hochgeladen.status_code == 201
|
|
body = hochgeladen.json()
|
|
assert body["dateiname"] == "zulassung.pdf"
|
|
assert body["groesse_bytes"] > 0
|
|
|
|
liste = await client.get(
|
|
f"/api/v1/dokumente?entitaet_typ=objekt&entitaet_id={objekt.id}", headers=auth_header(token)
|
|
)
|
|
assert len(liste.json()) == 1
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_download_liefert_inhalt(client, objekt_mit_position, mitarbeiter_user):
|
|
objekt, _material = objekt_mit_position
|
|
token = await login(client, "mitarbeiter1")
|
|
hochgeladen = await client.post(
|
|
"/api/v1/dokumente",
|
|
data={"entitaet_typ": "objekt", "entitaet_id": str(objekt.id)},
|
|
files={"datei": ("foto.png", b"\x89PNG fake", "image/png")},
|
|
headers=auth_header(token),
|
|
)
|
|
dokument_id = hochgeladen.json()["id"]
|
|
|
|
download = await client.get(f"/api/v1/dokumente/{dokument_id}/download", headers=auth_header(token))
|
|
assert download.status_code == 200
|
|
assert download.content == b"\x89PNG fake"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_unerlaubter_dateityp_wird_abgelehnt(client, objekt_mit_position, mitarbeiter_user):
|
|
objekt, _material = objekt_mit_position
|
|
token = await login(client, "mitarbeiter1")
|
|
|
|
response = await client.post(
|
|
"/api/v1/dokumente",
|
|
data={"entitaet_typ": "objekt", "entitaet_id": str(objekt.id)},
|
|
files={"datei": ("boese.html", b"<script>alert(1)</script>", "text/html")},
|
|
headers=auth_header(token),
|
|
)
|
|
assert response.status_code == 415
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_materialverantwortlicher_kann_dokument_loeschen(
|
|
client, objekt_mit_position, mitarbeiter_user, materialverantwortlicher_user
|
|
):
|
|
objekt, _material = objekt_mit_position
|
|
mitarbeiter_token = await login(client, "mitarbeiter1")
|
|
hochgeladen = await client.post(
|
|
"/api/v1/dokumente",
|
|
data={"entitaet_typ": "objekt", "entitaet_id": str(objekt.id)},
|
|
files={"datei": ("bericht.pdf", b"%PDF-1.4 x", "application/pdf")},
|
|
headers=auth_header(mitarbeiter_token),
|
|
)
|
|
dokument_id = hochgeladen.json()["id"]
|
|
|
|
verantwortlicher_token = await login(client, "materialverantwortlicher1")
|
|
geloescht = await client.delete(f"/api/v1/dokumente/{dokument_id}", headers=auth_header(verantwortlicher_token))
|
|
assert geloescht.status_code == 204
|
|
|
|
liste = await client.get(
|
|
f"/api/v1/dokumente?entitaet_typ=objekt&entitaet_id={objekt.id}", headers=auth_header(verantwortlicher_token)
|
|
)
|
|
assert liste.json() == []
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_mitarbeiter_darf_dokument_nicht_loeschen(client, objekt_mit_position, mitarbeiter_user):
|
|
objekt, _material = objekt_mit_position
|
|
token = await login(client, "mitarbeiter1")
|
|
hochgeladen = await client.post(
|
|
"/api/v1/dokumente",
|
|
data={"entitaet_typ": "objekt", "entitaet_id": str(objekt.id)},
|
|
files={"datei": ("x.pdf", b"%PDF-1.4 x", "application/pdf")},
|
|
headers=auth_header(token),
|
|
)
|
|
dokument_id = hochgeladen.json()["id"]
|
|
|
|
response = await client.delete(f"/api/v1/dokumente/{dokument_id}", headers=auth_header(token))
|
|
assert response.status_code == 403
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_identische_datei_am_gleichen_objekt_wird_abgelehnt(client, objekt_mit_position, mitarbeiter_user):
|
|
"""DOC-001: gleiche Bytes, gleiche Entität -> 409 statt zweitem Dokument."""
|
|
objekt, _material = objekt_mit_position
|
|
token = await login(client, "mitarbeiter1")
|
|
inhalt = b"%PDF-1.4 identischer inhalt"
|
|
|
|
erster = await client.post(
|
|
"/api/v1/dokumente",
|
|
data={"entitaet_typ": "objekt", "entitaet_id": str(objekt.id)},
|
|
files={"datei": ("original.pdf", inhalt, "application/pdf")},
|
|
headers=auth_header(token),
|
|
)
|
|
assert erster.status_code == 201
|
|
|
|
zweiter = await client.post(
|
|
"/api/v1/dokumente",
|
|
data={"entitaet_typ": "objekt", "entitaet_id": str(objekt.id)},
|
|
files={"datei": ("kopie.pdf", inhalt, "application/pdf")},
|
|
headers=auth_header(token),
|
|
)
|
|
assert zweiter.status_code == 409
|
|
assert "original.pdf" in zweiter.json()["detail"]
|
|
|
|
liste = await client.get(
|
|
f"/api/v1/dokumente?entitaet_typ=objekt&entitaet_id={objekt.id}", headers=auth_header(token)
|
|
)
|
|
assert len(liste.json()) == 1
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_identische_datei_an_anderer_entitaet_ist_kein_duplikat(client, objekt_mit_position, mitarbeiter_user):
|
|
"""Gleiche Datei an ZWEI verschiedenen Objekten ist kein Duplikat - Hash-
|
|
Vergleich ist bewusst pro Entität, nicht global."""
|
|
objekt, _material = objekt_mit_position
|
|
token = await login(client, "mitarbeiter1")
|
|
inhalt = b"%PDF-1.4 gleiche datei anderes objekt"
|
|
|
|
await client.post(
|
|
"/api/v1/dokumente",
|
|
data={"entitaet_typ": "objekt", "entitaet_id": str(objekt.id)},
|
|
files={"datei": ("anleitung.pdf", inhalt, "application/pdf")},
|
|
headers=auth_header(token),
|
|
)
|
|
andere_entitaet = await client.post(
|
|
"/api/v1/dokumente",
|
|
data={"entitaet_typ": "mangel", "entitaet_id": "irgendein-mangel-id"},
|
|
files={"datei": ("anleitung.pdf", inhalt, "application/pdf")},
|
|
headers=auth_header(token),
|
|
)
|
|
assert andere_entitaet.status_code == 201
|