import factory import pytest from django.urls import reverse from vergabe_teilnahme.apps.ausschreibungen.tests import AusschreibungFactory from vergabe_teilnahme.apps.lose.tests import AnforderungFactory from .models import Aufgabe, AufgabenVerknuepfung, Bieterfrage, ExternalIssue class AufgabeFactory(factory.django.DjangoModelFactory): class Meta: model = Aufgabe ausschreibung = factory.SubFactory(AusschreibungFactory) titel = factory.Sequence(lambda n: f"Aufgabe {n}") typ = 'fachlich' status = 'offen' prioritaet = 2 class BieterfragenFactory(factory.django.DjangoModelFactory): class Meta: model = Bieterfrage ausschreibung = factory.SubFactory(AusschreibungFactory) fragentext = factory.Sequence(lambda n: f"Frage {n}: Bitte klären Sie...") status = 'entwurf' prioritaet = 2 # ─── Aufgaben ────────────────────────────────────────────────────────────── @pytest.mark.django_db def test_aufgaben_liste_get(client): a = AusschreibungFactory() url = reverse('ausschreibungen:aufgaben:liste', kwargs={'ausschreibung_id': a.pk}) response = client.get(url) assert response.status_code == 200 @pytest.mark.django_db def test_aufgabe_neu_post(client): a = AusschreibungFactory() url = reverse('ausschreibungen:aufgaben:neu', kwargs={'ausschreibung_id': a.pk}) response = client.post(url, {'titel': 'Neue Aufgabe', 'typ': 'fachlich', 'prioritaet': 2}) assert response.status_code == 302 assert Aufgabe.objects.filter(ausschreibung=a, titel='Neue Aufgabe').exists() @pytest.mark.django_db def test_aufgabe_status_htmx(client): aufgabe = AufgabeFactory() url = reverse('ausschreibungen:aufgaben:status', kwargs={'ausschreibung_id': aufgabe.ausschreibung_id, 'pk': aufgabe.pk}) response = client.post(url, {'status': 'erledigt'}, HTTP_HX_REQUEST='true') assert response.status_code == 200 aufgabe.refresh_from_db() assert aufgabe.status == 'erledigt' @pytest.mark.django_db def test_ueberfaellige_aufgabe_auto_update(client): from datetime import date, timedelta a = AusschreibungFactory() aufgabe = AufgabeFactory(ausschreibung=a, frist=date.today() - timedelta(days=1), status='offen') url = reverse('ausschreibungen:aufgaben:liste', kwargs={'ausschreibung_id': a.pk}) client.get(url) aufgabe.refresh_from_db() assert aufgabe.status == 'ueberfaellig' # ─── Bieterfragen ───────────────────────────────────────────────────────── @pytest.mark.django_db def test_bieterfrage_neu_prefill_anforderung(client): a = AusschreibungFactory() anf = AnforderungFactory(ausschreibung=a) url = reverse('ausschreibungen:bieterfragen:neu', kwargs={'ausschreibung_id': a.pk}) response = client.get(url, {'anforderung_id': anf.pk}) assert response.status_code == 200 assert str(anf.pk).encode() in response.content @pytest.mark.django_db def test_bieterfrage_antwort_speichern(client): bf = BieterfragenFactory(status='eingereicht') url = reverse('ausschreibungen:bieterfragen:antwort', kwargs={'ausschreibung_id': bf.ausschreibung_id, 'pk': bf.pk}) response = client.post(url, {'antwort': 'Die Antwort lautet 42.', 'auswirkung_angebot': ''}) assert response.status_code == 302 bf.refresh_from_db() assert bf.antwort == 'Die Antwort lautet 42.' assert bf.status == 'beantwortet' # ─── Implizite Fälligkeit ───────────────────────────────────────────────────── @pytest.mark.django_db def test_frist_effektiv_mit_frist(): from datetime import date aufgabe = AufgabeFactory(frist=date(2026, 6, 1)) assert aufgabe.frist_effektiv == date(2026, 6, 1) @pytest.mark.django_db def test_frist_effektiv_ohne_frist(): from datetime import timedelta from django.utils import timezone aufgabe = AufgabeFactory(frist=None) expected = (aufgabe.erstellt_am + timedelta(days=7)).date() assert aufgabe.frist_effektiv == expected @pytest.mark.django_db def test_ueberfaellig_ohne_frist_nach_7_tagen(client): from datetime import timedelta from django.utils import timezone a = AusschreibungFactory() alte_erstellung = timezone.now() - timedelta(days=8) aufgabe = AufgabeFactory(ausschreibung=a, frist=None, status='offen') Aufgabe.objects.filter(pk=aufgabe.pk).update(erstellt_am=alte_erstellung) url = reverse('ausschreibungen:aufgaben:liste', kwargs={'ausschreibung_id': a.pk}) client.get(url) aufgabe.refresh_from_db() assert aufgabe.status == 'ueberfaellig' # ─── Aufgaben-Verknüpfungen ─────────────────────────────────────────────────── @pytest.mark.django_db def test_aufgaben_verknuepfung_erstellen(client): from django.contrib.contenttypes.models import ContentType aufgabe = AufgabeFactory() anf = AnforderungFactory(ausschreibung=aufgabe.ausschreibung) ct = ContentType.objects.get_for_model(anf) url = reverse('ausschreibungen:aufgaben:verknuepfung_neu', kwargs={'ausschreibung_id': aufgabe.ausschreibung_id, 'pk': aufgabe.pk}) response = client.post(url, { 'ziel_typ': ct.pk, 'ziel_id': anf.pk, 'kommentar': 'Testverknüpfung', }, HTTP_HX_REQUEST='true') assert response.status_code == 200 assert AufgabenVerknuepfung.objects.filter(aufgabe=aufgabe, object_id=anf.pk).exists() @pytest.mark.django_db def test_aufgaben_verknuepfung_loeschen(client): from django.contrib.contenttypes.models import ContentType aufgabe = AufgabeFactory() anf = AnforderungFactory(ausschreibung=aufgabe.ausschreibung) ct = ContentType.objects.get_for_model(anf) vk = AufgabenVerknuepfung.objects.create( aufgabe=aufgabe, content_type=ct, object_id=anf.pk ) url = reverse('ausschreibungen:aufgaben:verknuepfung_loeschen', kwargs={'ausschreibung_id': aufgabe.ausschreibung_id, 'pk': aufgabe.pk, 'vk_pk': vk.pk}) response = client.post(url, {}, HTTP_HX_REQUEST='true') assert response.status_code == 200 assert not AufgabenVerknuepfung.objects.filter(pk=vk.pk).exists() # ─── ExternalIssue ──────────────────────────────────────────────────────────── @pytest.mark.django_db def test_external_issue_erstellen(client): aufgabe = AufgabeFactory() url = reverse('ausschreibungen:aufgaben:external_issue', kwargs={'ausschreibung_id': aufgabe.ausschreibung_id, 'pk': aufgabe.pk}) response = client.post(url, { 'system': 'github', 'issue_url': 'https://github.com/org/repo/issues/42', 'issue_key': 'GH-42', 'notizen': '', }, HTTP_HX_REQUEST='true') assert response.status_code == 200 assert ExternalIssue.objects.filter(aufgabe=aufgabe, system='github').exists() @pytest.mark.django_db def test_external_issue_loeschen(client): aufgabe = AufgabeFactory() ei = ExternalIssue.objects.create( aufgabe=aufgabe, system='jira', issue_key='PROJ-1' ) url = reverse('ausschreibungen:aufgaben:external_issue_loeschen', kwargs={'ausschreibung_id': aufgabe.ausschreibung_id, 'pk': aufgabe.pk}) response = client.post(url, {}, HTTP_HX_REQUEST='true') assert response.status_code == 200 assert not ExternalIssue.objects.filter(pk=ei.pk).exists() @pytest.mark.django_db def test_issue_adapter_interface(): from .issue_facade import get_adapter assert get_adapter('github') is None assert get_adapter('nichtexistent') is None