generated from coulomb/repo-seed
290 lines
11 KiB
Python
290 lines
11 KiB
Python
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.fixture
|
|
def tmp_issue_db(tmp_path, settings):
|
|
settings.ISSUE_FACADE_LOCAL_DB = tmp_path / 'test_issues.db'
|
|
settings.ISSUE_FACADE_GITEA = None
|
|
return settings.ISSUE_FACADE_LOCAL_DB
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_external_issue_erstellen(client, tmp_issue_db):
|
|
aufgabe = AufgabeFactory()
|
|
url = reverse('ausschreibungen:aufgaben:external_issue',
|
|
kwargs={'ausschreibung_id': aufgabe.ausschreibung_id, 'pk': aufgabe.pk})
|
|
response = client.post(url, {'notizen': ''}, HTTP_HX_REQUEST='true')
|
|
assert response.status_code == 200
|
|
assert ExternalIssue.objects.filter(aufgabe=aufgabe, issue_facade_backend='local').exists()
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_external_issue_loeschen(client):
|
|
aufgabe = AufgabeFactory()
|
|
ei = ExternalIssue.objects.create(
|
|
aufgabe=aufgabe, issue_facade_backend='local', issue_key='#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
|
|
|
|
|
|
# ─── Issue-Facade Integration ─────────────────────────────────────────────────
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_aufgabe_zu_issue_mapping(tmp_issue_db):
|
|
from .issue_facade import aufgabe_zu_issue
|
|
aufgabe = AufgabeFactory(titel='Test-Aufgabe', beschreibung='Beschreibung', prioritaet=1)
|
|
issue = aufgabe_zu_issue(aufgabe)
|
|
assert issue.title == 'Test-Aufgabe'
|
|
assert issue.description == 'Beschreibung'
|
|
label_names = [l.name for l in issue.labels]
|
|
assert 'task' in label_names
|
|
assert 'priority:high' in label_names
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_lokales_issue_erstellen(tmp_issue_db):
|
|
from .issue_facade import lokales_issue_erstellen
|
|
aufgabe = AufgabeFactory()
|
|
daten = lokales_issue_erstellen(aufgabe)
|
|
assert daten['issue_facade_id']
|
|
assert daten['issue_key'].startswith('#')
|
|
assert daten['sync_status'] == 'open'
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_status_synchronisieren(tmp_issue_db):
|
|
from .issue_facade import lokales_issue_erstellen, status_synchronisieren
|
|
aufgabe = AufgabeFactory()
|
|
daten = lokales_issue_erstellen(aufgabe)
|
|
ei = ExternalIssue.objects.create(
|
|
aufgabe=aufgabe,
|
|
issue_facade_backend='local',
|
|
issue_facade_id=daten['issue_facade_id'],
|
|
issue_key=daten['issue_key'],
|
|
sync_status=daten['sync_status'],
|
|
)
|
|
neuer_status = status_synchronisieren(ei)
|
|
assert neuer_status == 'open'
|
|
ei.refresh_from_db()
|
|
assert ei.sync_status == 'open'
|
|
assert ei.letzter_sync is not None
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_external_issue_bearbeiten_view_erstellt_issue(client, tmp_issue_db):
|
|
aufgabe = AufgabeFactory()
|
|
url = reverse('ausschreibungen:aufgaben:external_issue',
|
|
kwargs={'ausschreibung_id': aufgabe.ausschreibung_id, 'pk': aufgabe.pk})
|
|
response = client.post(url, {'notizen': 'Test-Notiz'}, HTTP_HX_REQUEST='true')
|
|
assert response.status_code == 200
|
|
ei = ExternalIssue.objects.get(aufgabe=aufgabe)
|
|
assert ei.issue_facade_backend == 'local'
|
|
assert ei.issue_facade_id
|
|
assert ei.notizen == 'Test-Notiz'
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_external_issue_sync_view(client, tmp_issue_db):
|
|
from .issue_facade import lokales_issue_erstellen
|
|
aufgabe = AufgabeFactory()
|
|
daten = lokales_issue_erstellen(aufgabe)
|
|
ei = ExternalIssue.objects.create(
|
|
aufgabe=aufgabe,
|
|
issue_facade_backend='local',
|
|
issue_facade_id=daten['issue_facade_id'],
|
|
issue_key=daten['issue_key'],
|
|
sync_status=daten['sync_status'],
|
|
)
|
|
url = reverse('ausschreibungen:aufgaben:external_issue_sync',
|
|
kwargs={'ausschreibung_id': aufgabe.ausschreibung_id, 'pk': aufgabe.pk})
|
|
response = client.post(url, {}, HTTP_HX_REQUEST='true')
|
|
assert response.status_code == 200
|
|
ei.refresh_from_db()
|
|
assert ei.sync_status == 'open'
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_get_adapter_stub():
|
|
from .issue_facade import get_adapter
|
|
assert get_adapter('github') is None
|
|
assert get_adapter('gitea') is None
|