import factory import pytest from django.urls import reverse from .models import Ausschreibung class AusschreibungFactory(factory.django.DjangoModelFactory): class Meta: model = Ausschreibung titel = factory.Sequence(lambda n: f"Ausschreibung {n}") ausschreiber = "Testausschreiber GmbH" status = 1 # --- Model tests --- @pytest.mark.django_db def test_ausschreibung_str(): a = AusschreibungFactory(titel="Test Ausschreibung") assert str(a) == "Test Ausschreibung" @pytest.mark.django_db @pytest.mark.parametrize("status,expected", [ (1, True), (5, True), (9, True), (10, False), (11, False), (13, False), ]) def test_ist_aktiv(status, expected): a = AusschreibungFactory(status=status) assert a.ist_aktiv == expected @pytest.mark.django_db def test_naechste_frist_returns_earlier(): from datetime import date, timedelta heute = date.today() a = AusschreibungFactory( bieterfragen_bis=heute + timedelta(days=5), abgabe_bis=heute + timedelta(days=10), ) assert a.naechste_frist == heute + timedelta(days=5) @pytest.mark.django_db def test_naechste_frist_none_when_past(): from datetime import date, timedelta gestern = date.today() - timedelta(days=1) a = AusschreibungFactory(bieterfragen_bis=gestern, abgabe_bis=None) assert a.naechste_frist is None # --- View tests --- @pytest.mark.django_db def test_liste_get(admitted_client): response = admitted_client.get(reverse("ausschreibungen:liste")) assert response.status_code == 200 @pytest.mark.django_db def test_neu_get(admitted_client): response = admitted_client.get(reverse("ausschreibungen:neu")) assert response.status_code == 200 @pytest.mark.django_db def test_neu_post_valid(admitted_client): data = {"titel": "Neue Ausschreibung", "ausschreiber": "Stadt XY"} response = admitted_client.post(reverse("ausschreibungen:neu"), data) assert response.status_code == 302 a = Ausschreibung.objects.get(titel="Neue Ausschreibung") assert response.url == reverse("ausschreibungen:detail", kwargs={"pk": a.pk}) @pytest.mark.django_db def test_detail_get(admitted_client): a = AusschreibungFactory() response = admitted_client.get(reverse("ausschreibungen:detail", kwargs={"pk": a.pk})) assert response.status_code == 200 @pytest.mark.django_db def test_status_post(admitted_client): a = AusschreibungFactory(status=1) url = reverse("ausschreibungen:status", kwargs={"pk": a.pk}) response = admitted_client.post(url, {"status": "4"}) assert response.status_code == 200 a.refresh_from_db() assert a.status == 4 @pytest.mark.django_db def test_status_htmx_returns_partial(admitted_client): a = AusschreibungFactory(status=1) url = reverse("ausschreibungen:status", kwargs={"pk": a.pk}) response = admitted_client.post(url, {"status": "3"}, HTTP_HX_REQUEST="true") assert response.status_code == 200 assert b"status-widget" in response.content @pytest.mark.django_db def test_dashboard_expiry_boundary(admitted_client): from datetime import date, timedelta from vergabe_teilnahme.apps.bibliothek.models import Nachweis for title, days in [('Expired', -1), ('Boundary', 60), ('Later', 61)]: Nachweis.objects.create(titel=title, gueltig_bis=date.today() + timedelta(days=days)) Nachweis.objects.create(titel='Undated') response = admitted_client.get(reverse('ausschreibungen:dashboard')) assert response.status_code == 200 assert [n.titel for n in response.context['ablaufende_nachweise']] == ['Expired', 'Boundary'] assert 'Boundary' in response.content.decode() @pytest.mark.django_db def test_list_search_combines_filters_and_htmx(admitted_client): matching = AusschreibungFactory(titel='Network upgrade', ausschreiber='City', status=4) AusschreibungFactory(titel='Other', ausschreiber='City', status=3) AusschreibungFactory(titel='Archived', ausschreiber='City', status=4, archiviert=True) for query in ['Network', 'City']: response = admitted_client.get(reverse('ausschreibungen:liste'), {'q': query, 'status': '4'}, HTTP_HX_REQUEST='true') assert list(response.context['ausschreibungen']) == [matching] assert 'ausschreibungen/liste_partial.html' in [t.name for t in response.templates] @pytest.mark.django_db def test_historical_result_validation_and_save(admitted_client): url = reverse('ausschreibungen:neu') response = admitted_client.get(url, {'historisch': '1'}) assert 'name="ergebnis"' in response.content.decode() data = {'titel': 'Historical award', 'ausschreiber': 'City', 'historisch_erfassen': '1', 'teilnahmeentscheidung': 'teilnahme', 'ergebnis': 'invalid'} response = admitted_client.post(url, data) assert response.status_code == 200 assert response.context['form'].errors['ergebnis'] assert not Ausschreibung.objects.filter(titel=data['titel']).exists() data['ergebnis'] = 'gewonnen' response = admitted_client.post(url, data) assert response.status_code == 302 tender = Ausschreibung.objects.get(titel=data['titel']) assert tender.nachbetrachtung.ergebnis == 'gewonnen' assert tender.status == 10 assert tender.teilnahmeentscheidung == 'teilnahme' @pytest.mark.django_db def test_global_search_tasks_partners_and_issuer(admitted_client): from vergabe_teilnahme.apps.aufgaben.models import Aufgabe from vergabe_teilnahme.apps.partner.models import Subunternehmer tender = AusschreibungFactory(titel='Tender', ausschreiber='UniqueIssuer') task = Aufgabe.objects.create(titel='UniqueTask', ausschreibung=tender) partner = Subunternehmer.objects.create(name='UniquePartner') cases = [ ('UniqueIssuer', reverse('ausschreibungen:detail', kwargs={'pk': tender.pk})), ('UniqueTask', reverse('ausschreibungen:aufgaben:detail', kwargs={'ausschreibung_id': tender.pk, 'pk': task.pk})), ('UniquePartner', reverse('partner:su_detail', kwargs={'pk': partner.pk})), ] for query, target in cases: response = admitted_client.get('/suche/', {'q': query}) assert response.status_code == 200 assert f'href="{target}"' in response.content.decode() @pytest.mark.django_db def test_inline_status_with_rendered_csrf_header(mitarbeiter): import json import re from django.test import Client client = Client(enforce_csrf_checks=True) client.force_login(mitarbeiter) tender = AusschreibungFactory(status=1) response = client.get(reverse('ausschreibungen:detail', kwargs={'pk': tender.pk})) headers = json.loads(re.search(r"hx-headers='([^']+)'", response.content.decode())[1]) url = reverse('ausschreibungen:status', kwargs={'pk': tender.pk}) assert client.post(url, {'status': '4'}).status_code == 403 response = client.post(url, {'status': '4'}, HTTP_HX_REQUEST='true', HTTP_X_CSRFTOKEN=headers['X-CSRFToken']) assert response.status_code == 200 tender.refresh_from_db() assert tender.status == 4