diff --git a/greg/api/filters.py b/greg/api/filters.py index 307fe3202146880b9c943e485356ac6e95558476..14ef355eb0bc7935dc090f5cf189055e2df78765 100644 --- a/greg/api/filters.py +++ b/greg/api/filters.py @@ -1,6 +1,6 @@ from django_filters import rest_framework as filters -from greg.models import Person, PersonRole +from greg.models import Person, PersonRole, PersonIdentity class PersonRoleFilter(filters.FilterSet): @@ -19,3 +19,9 @@ class PersonFilter(filters.FilterSet): class Meta: model = Person fields = ["first_name", "last_name", "verified"] + + +class PersonIdentityFilter(filters.FilterSet): + class Meta: + model = PersonIdentity + fields = ["type", "verified_by_id"] diff --git a/greg/api/serializers/consent.py b/greg/api/serializers/consent.py new file mode 100644 index 0000000000000000000000000000000000000000..5c1663f2da723d94f477a82e3fb97d9351892e35 --- /dev/null +++ b/greg/api/serializers/consent.py @@ -0,0 +1,9 @@ +from rest_framework.serializers import ModelSerializer + +from greg.models import Consent + + +class ConsentSerializer(ModelSerializer): + class Meta: + model = Consent + fields = "__all__" diff --git a/greg/api/serializers/organizational_unit.py b/greg/api/serializers/organizational_unit.py new file mode 100644 index 0000000000000000000000000000000000000000..12251bffff79f9119d76e9e1f7ddcc58c2984b68 --- /dev/null +++ b/greg/api/serializers/organizational_unit.py @@ -0,0 +1,9 @@ +from rest_framework.serializers import ModelSerializer + +from greg.models import OrganizationalUnit + + +class OrganizationalUnitSerializer(ModelSerializer): + class Meta: + model = OrganizationalUnit + fields = "__all__" diff --git a/greg/api/serializers/person.py b/greg/api/serializers/person.py index 77eec13afec9c235552e84b503b085ee80c44a77..f4e5f2b29452ff926c536ebcd13c313aaa510551 100644 --- a/greg/api/serializers/person.py +++ b/greg/api/serializers/person.py @@ -1,6 +1,6 @@ from rest_framework import serializers -from greg.models import Person, PersonRole, Role +from greg.models import Person, PersonRole, Role, PersonIdentity class PersonSerializer(serializers.ModelSerializer): @@ -31,3 +31,9 @@ class PersonRoleSerializer(serializers.ModelSerializer): "updated", "role", ] + + +class PersonIdentitySerializer(serializers.ModelSerializer): + class Meta: + model = PersonIdentity + fields = "__all__" diff --git a/greg/api/serializers/sponsor.py b/greg/api/serializers/sponsor.py new file mode 100644 index 0000000000000000000000000000000000000000..57326558e9999d307412bed70d79665ae63c8821 --- /dev/null +++ b/greg/api/serializers/sponsor.py @@ -0,0 +1,9 @@ +from rest_framework import serializers + +from greg.models import Sponsor + + +class SponsorSerializer(serializers.ModelSerializer): + class Meta: + model = Sponsor + fields = ["id", "feide_id"] diff --git a/greg/api/urls.py b/greg/api/urls.py index d05c87ac68a744f41d1e6a0569abee080d840a3e..a589fe46911f3cc8193e20e7516b49d1d96d2598 100644 --- a/greg/api/urls.py +++ b/greg/api/urls.py @@ -8,17 +8,24 @@ from drf_spectacular.views import ( SpectacularSwaggerView, ) +from greg.api.views.consent import ConsentViewSet +from greg.api.views.organizational_unit import OrganizationalUnitViewSet from greg.api.views.person import ( PersonRoleViewSet, PersonViewSet, + PersonIdentityViewSet, ) from greg.api.views.role import RoleViewSet from greg.api.views.health import Health - +from greg.api.views.sponsor import SponsorViewSet router = DefaultRouter() router.register(r"persons", PersonViewSet, basename="person") router.register(r"roles", RoleViewSet, basename="role") +router.register(r"consents", ConsentViewSet, basename="consent") +router.register(r"sponsors", SponsorViewSet, basename="sponsor") +router.register(r"orgunit", OrganizationalUnitViewSet, basename="orgunit") + urlpatterns = router.urls @@ -42,4 +49,14 @@ urlpatterns += [ ), name="person_role-detail", ), + re_path( + r"^persons/(?P<person_id>[0-9]+)/identities/$", + PersonIdentityViewSet.as_view({"get": "list", "post": "create"}), + name="person_identity-list", + ), + re_path( + r"^persons/(?P<person_id>[0-9]+)/identities/(?P<id>[0-9]+)$", + PersonIdentityViewSet.as_view({"delete": "destroy", "patch": "partial_update"}), + name="person_identity-detail", + ), ] diff --git a/greg/api/views/consent.py b/greg/api/views/consent.py new file mode 100644 index 0000000000000000000000000000000000000000..4996325e7cb9b40ffb64fde25a8d6b374e81d3e6 --- /dev/null +++ b/greg/api/views/consent.py @@ -0,0 +1,14 @@ +from rest_framework import viewsets + +from greg.api.pagination import PrimaryKeyCursorPagination +from greg.api.serializers.consent import ConsentSerializer +from greg.models import Consent + + +class ConsentViewSet(viewsets.ModelViewSet): + """Consent API""" + + queryset = Consent.objects.all().order_by("id") + serializer_class = ConsentSerializer + pagination_class = PrimaryKeyCursorPagination + lookup_field = "id" diff --git a/greg/api/views/organizational_unit.py b/greg/api/views/organizational_unit.py new file mode 100644 index 0000000000000000000000000000000000000000..c6d2236cd68f3318629a4595f4ee6d3f1755918e --- /dev/null +++ b/greg/api/views/organizational_unit.py @@ -0,0 +1,14 @@ +from rest_framework import viewsets + +from greg.api.pagination import PrimaryKeyCursorPagination +from greg.api.serializers.organizational_unit import OrganizationalUnitSerializer +from greg.models import OrganizationalUnit + + +class OrganizationalUnitViewSet(viewsets.ModelViewSet): + """OrganizationalUnit API""" + + queryset = OrganizationalUnit.objects.all().order_by("id") + serializer_class = OrganizationalUnitSerializer + pagination_class = PrimaryKeyCursorPagination + lookup_field = "id" diff --git a/greg/api/views/person.py b/greg/api/views/person.py index 9e490665c600b778ff281974174bace017977488..9918256b5f05aba0416213c34d3245d08728f21a 100644 --- a/greg/api/views/person.py +++ b/greg/api/views/person.py @@ -1,12 +1,17 @@ from django.core.exceptions import ValidationError from django_filters import rest_framework as filters from drf_spectacular.utils import extend_schema, OpenApiParameter -from rest_framework import viewsets +from rest_framework import viewsets, status +from rest_framework.response import Response -from greg.api.filters import PersonFilter, PersonRoleFilter +from greg.api.filters import PersonFilter, PersonRoleFilter, PersonIdentityFilter from greg.api.pagination import PrimaryKeyCursorPagination -from greg.api.serializers.person import PersonSerializer, PersonRoleSerializer -from greg.models import Person, PersonRole +from greg.api.serializers.person import ( + PersonSerializer, + PersonRoleSerializer, + PersonIdentitySerializer, +) +from greg.models import Person, PersonRole, PersonIdentity class PersonViewSet(viewsets.ModelViewSet): @@ -63,3 +68,45 @@ class PersonRoleViewSet(viewsets.ModelViewSet): raise ValidationError("No person id") serializer.save(person_id=person_id) + + +class PersonIdentityViewSet(viewsets.ModelViewSet): + """ + Person identity API + """ + + queryset = PersonIdentity.objects.all().order_by("id") + serializer_class = PersonIdentitySerializer + pagination_class = PrimaryKeyCursorPagination + filter_backends = (filters.DjangoFilterBackend,) + filterset_class = PersonIdentityFilter + # This is set so that the id parameter in the path of the URL is used for looking up objects + lookup_url_kwarg = "id" + + def get_queryset(self): + qs = self.queryset + if not self.kwargs: + return qs.none() + person_id = self.kwargs["person_id"] + qs = qs.filter(person_id=person_id) + return qs + + def create(self, request, *args, **kwargs): + # Want to get the person id which is part of the API path and then + # include this with the data used to create the identity for the person + person_id = self.kwargs["person_id"] + + if person_id is None: + # Should not happen, the person ID is part of the API path + raise ValidationError("No person id") + + input_data = request.data.copy() + input_data["person"] = person_id + + serializer = self.get_serializer(data=input_data) + serializer.is_valid(raise_exception=True) + self.perform_create(serializer) + headers = self.get_success_headers(serializer.data) + return Response( + serializer.data, status=status.HTTP_201_CREATED, headers=headers + ) diff --git a/greg/api/views/sponsor.py b/greg/api/views/sponsor.py new file mode 100644 index 0000000000000000000000000000000000000000..0251a2e86274d368d61508c2c937b1205e8ea6e3 --- /dev/null +++ b/greg/api/views/sponsor.py @@ -0,0 +1,14 @@ +from rest_framework.viewsets import ReadOnlyModelViewSet + +from greg.api.pagination import PrimaryKeyCursorPagination +from greg.api.serializers.sponsor import SponsorSerializer +from greg.models import Sponsor + + +class SponsorViewSet(ReadOnlyModelViewSet): + """Sponsor API""" + + queryset = Sponsor.objects.all().order_by("id") + serializer_class = SponsorSerializer + pagination_class = PrimaryKeyCursorPagination + lookup_field = "id" diff --git a/greg/migrations/0001_initial.py b/greg/migrations/0001_initial.py index 45ff7241e90d4f660c2c1dccde38bcbddf44fd76..7504a4e320ce449103f0aa674c34a41fbef8f1da 100644 --- a/greg/migrations/0001_initial.py +++ b/greg/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 3.2.5 on 2021-07-15 13:31 +# Generated by Django 3.2.5 on 2021-08-04 11:07 import datetime import dirtyfields.dirtyfields @@ -158,7 +158,7 @@ class Migration(migrations.Migration): ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('created', models.DateTimeField(auto_now_add=True)), ('updated', models.DateTimeField(auto_now=True)), - ('type', models.CharField(choices=[('PASSPORT_NUMBER', 'Passport Number'), ('FEIDE_ID', 'Feide Id')], max_length=15)), + ('type', models.CharField(choices=[('ID_PORTEN', 'Id Porten'), ('FEIDE_ID', 'Feide Id'), ('PASSPORT', 'Passport'), ('DRIVERS_LICENSE', 'Drivers License'), ('NATIONAL_ID_CARD', 'National Id Card'), ('OTHER', 'Other')], max_length=16)), ('source', models.CharField(max_length=256)), ('value', models.CharField(max_length=256)), ('verified', models.CharField(blank=True, choices=[('AUTOMATIC', 'Automatic'), ('MANUAL', 'Manual')], max_length=9)), @@ -177,7 +177,7 @@ class Migration(migrations.Migration): ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('created', models.DateTimeField(auto_now_add=True)), ('updated', models.DateTimeField(auto_now=True)), - ('consent_given_at', models.DateField()), + ('consent_given_at', models.DateField(null=True)), ('consent', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='link_person_consent', to='greg.consent')), ('person', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='link_person_consent', to='greg.person')), ], diff --git a/greg/models.py b/greg/models.py index fc471b9133b4e04fb5d530d02d54441e70711bb3..73e5369760975e089e7f9f2243996f6d402260ef 100644 --- a/greg/models.py +++ b/greg/models.py @@ -131,10 +131,14 @@ class Notification(BaseModel): class PersonIdentity(BaseModel): - # TODO: Add more types class IdentityType(models.TextChoices): - PASSPORT_NUMBER = "PASSPORT_NUMBER" + ID_PORTEN = "ID_PORTEN" FEIDE_ID = "FEIDE_ID" + PASSPORT = "PASSPORT" + DRIVERS_LICENSE = "DRIVERS_LICENSE" + NATIONAL_ID_CARD = "NATIONAL_ID_CARD" + # Sponsor writes what is used in the value column + OTHER = "OTHER" class Verified(models.TextChoices): AUTOMATIC = "AUTOMATIC" @@ -143,7 +147,7 @@ class PersonIdentity(BaseModel): person = models.ForeignKey( "Person", on_delete=models.PROTECT, related_name="person" ) - type = models.CharField(max_length=15, choices=IdentityType.choices) + type = models.CharField(max_length=16, choices=IdentityType.choices) source = models.CharField(max_length=256) value = models.CharField(max_length=256) verified = models.CharField(max_length=9, choices=Verified.choices, blank=True) @@ -202,7 +206,8 @@ class PersonConsent(BaseModel): consent = models.ForeignKey( "Consent", on_delete=models.PROTECT, related_name="link_person_consent" ) - consent_given_at = models.DateField() + # If the date is blank it means the person has not given consent yet + consent_given_at = models.DateField(null=True) class Meta: constraints = [ diff --git a/greg/tests/api/test_consent.py b/greg/tests/api/test_consent.py new file mode 100644 index 0000000000000000000000000000000000000000..24ae8c837c73054d8edcad98abcefa1f39624e59 --- /dev/null +++ b/greg/tests/api/test_consent.py @@ -0,0 +1,30 @@ +import pytest +from rest_framework import status +from rest_framework.reverse import reverse + +from greg.models import Consent + + +@pytest.fixture +def consent_foo() -> Consent: + return Consent.objects.create( + type="test_consent", + consent_name_en="Test1", + consent_name_nb="Test2", + consent_description_en="Test description", + consent_description_nb="Test beskrivelse", + consent_link_en="https://example.org", + consent_link_nb="https://example.org", + valid_from="2018-01-20", + user_allowed_to_change=True, + ) + + +@pytest.mark.django_db +def test_get_consent(client, consent_foo): + resp = client.get(reverse("consent-detail", kwargs={"id": consent_foo.id})) + assert resp.status_code == status.HTTP_200_OK + data = resp.json() + assert data.get("id") == consent_foo.id + assert data.get("type") == consent_foo.type + assert data.get("consent_name_en") == consent_foo.consent_name_en diff --git a/greg/tests/api/test_person.py b/greg/tests/api/test_person.py index 167113d2aa391a5608635eec4160e34a4789fa26..715e26a1ed90f9f34a411430fe05ff2d5eb057dd 100644 --- a/greg/tests/api/test_person.py +++ b/greg/tests/api/test_person.py @@ -1,24 +1,11 @@ from typing import Dict import pytest - -from django.contrib.auth import get_user_model from rest_framework import status -from rest_framework.authtoken.models import Token from rest_framework.reverse import reverse from rest_framework.status import HTTP_200_OK -from rest_framework.test import APIClient - -from greg.models import Person, Role, Sponsor, OrganizationalUnit - -@pytest.fixture -def client() -> APIClient: - user, _ = get_user_model().objects.get_or_create(username="test") - token, _ = Token.objects.get_or_create(user=user) - client = APIClient() - client.credentials(HTTP_AUTHORIZATION=f"Token {token.key}") - return client +from greg.models import Person, PersonIdentity, Sponsor, Role, OrganizationalUnit @pytest.fixture @@ -43,6 +30,53 @@ def person_bar() -> Person: ) +@pytest.fixture +def sponsor_guy() -> Sponsor: + return Sponsor.objects.create(feide_id="guy@example.org") + + +@pytest.fixture +def person_foo_verified(person_foo, sponsor_guy) -> PersonIdentity: + return PersonIdentity.objects.create( + person=person_foo, + type=PersonIdentity.IdentityType.PASSPORT, + source="Test", + value="12345", + verified=PersonIdentity.Verified.MANUAL, + verified_by=sponsor_guy, + verified_when="2021-06-15", + ) + + +@pytest.fixture +def person_foo_not_verified(person_foo) -> PersonIdentity: + return PersonIdentity.objects.create( + person=person_foo, + type=PersonIdentity.IdentityType.DRIVERS_LICENSE, + source="Test", + value="12345", + ) + + +@pytest.fixture +def role_visiting_professor() -> Role: + return Role.objects.create( + type="visiting_professor", + name_nb="Gjesteprofessor", + name_en="Visiting professor", + description_nb="Gjesteprofessor", + description_en="Visiting professor", + default_duration_days=180, + ) + + +@pytest.fixture +def unit_human_resources() -> OrganizationalUnit: + return OrganizationalUnit.objects.create( + orgreg_id="org_unit_1", name_nb="Personal", name_en="Human Resources" + ) + + @pytest.fixture() def role_test_guest() -> Role: return Role.objects.create(type="Test Guest") @@ -88,36 +122,47 @@ def test_persons(client, person_foo, person_bar): @pytest.mark.django_db -def test_persons_verified_filter_include(client, setup_db_test_data): +def test_persons_verified_filter_include( + client, person_bar, person_foo, person_foo_verified +): url = reverse("person-list") response = client.get(url, {"verified": "true"}) results = response.json()["results"] assert len(results) == 1 - # The following person will have a verified identity set up for him - # in the test data - assert results[0]["first_name"] == "Christopher" - assert results[0]["last_name"] == "Flores" + assert results[0]["first_name"] == "Foo" + assert results[0]["last_name"] == "Foo" @pytest.mark.django_db -def test_persons_verified_filter_exclude(client, setup_db_test_data): +def test_persons_verified_filter_exclude( + client, person_bar, person_foo, person_foo_verified +): url = reverse("person-list") response = client.get(url, {"verified": "false"}) results = response.json()["results"] - names = [(result["first_name"], result["last_name"]) for result in results] - assert len(results) == 9 - assert ("Christopher", "Flores") not in names + assert len(results) == 1 + assert results[0]["first_name"] == "Bar" + assert results[0]["last_name"] == "Bar" @pytest.mark.django_db -def test_add_role(client, person_foo, role_data_guest): +def test_add_role( + client, person_foo, role_visiting_professor, sponsor_guy, unit_human_resources +): url = reverse("person_role-list", kwargs={"person_id": person_foo.id}) roles_for_person = client.get(url).json()["results"] # Check that there are no roles for the person, and then add one assert len(roles_for_person) == 0 - response = client.post(url, role_data_guest) + role_data = { + "role": "visiting_professor", + "start_date": "2021-06-10", + "end_date": "2021-08-10", + "registered_by": "1", + "unit": "1", + } + response = client.post(url, role_data) assert response.status_code == status.HTTP_201_CREATED @@ -168,3 +213,150 @@ def test_delete_role(client, person_foo, role_data_guest): client.delete(url_detail) assert len(client.get(url).json()["results"]) == 0 + + +@pytest.mark.django_db +def test_identity_list( + client, person_foo, person_foo_verified, person_foo_not_verified +): + response = client.get( + reverse("person-list"), + data={"first_name": person_foo.first_name, "last_name": person_foo.last_name}, + ) + person_id = response.json()["results"][0]["id"] + response = client.get( + reverse("person_identity-list", kwargs={"person_id": person_id}) + ) + data = response.json()["results"] + assert len(data) == 2 + + +@pytest.mark.django_db +def test_identity_add(client, person_foo): + response = client.get( + reverse("person_identity-list", kwargs={"person_id": person_foo.id}) + ) + results = response.json()["results"] + assert len(results) == 0 + + data = { + "type": PersonIdentity.IdentityType.FEIDE_ID, + "source": "Test source", + "value": "12345", + } + client.post( + reverse("person_identity-list", kwargs={"person_id": person_foo.id}), data=data + ) + + response = client.get( + reverse("person_identity-list", kwargs={"person_id": person_foo.id}) + ) + results = response.json()["results"] + assert len(results) == 1 + + +@pytest.mark.django_db +def test_identity_delete(client, person_foo): + response = client.get( + reverse("person_identity-list", kwargs={"person_id": person_foo.id}) + ) + results = response.json()["results"] + assert len(results) == 0 + + data = { + "type": PersonIdentity.IdentityType.FEIDE_ID, + "source": "Test source", + "value": "12345", + } + post_response = client.post( + reverse("person_identity-list", kwargs={"person_id": person_foo.id}), data=data + ) + identity_id = post_response.json()["id"] + + # Create two identities for the user + data = { + "type": PersonIdentity.IdentityType.PASSPORT, + "source": "Test", + "value": "1234413241235", + } + post_response2 = client.post( + reverse("person_identity-list", kwargs={"person_id": person_foo.id}), data=data + ) + identity_id2 = post_response2.json()["id"] + + response = client.get( + reverse("person_identity-list", kwargs={"person_id": person_foo.id}) + ) + results = response.json()["results"] + assert len(results) == 2 + + # Delete the first identity created + client.delete( + reverse( + "person_identity-detail", + kwargs={"person_id": person_foo.id, "id": identity_id}, + ) + ) + + # Check that the other identity is still there + response = client.get( + reverse("person_identity-list", kwargs={"person_id": person_foo.id}) + ) + results = response.json()["results"] + + assert len(results) == 1 + assert results[0]["id"] == identity_id2 + + +@pytest.mark.django_db +def test_identity_update(client, person_foo): + response = client.get( + reverse("person_identity-list", kwargs={"person_id": person_foo.id}) + ) + results = response.json()["results"] + assert len(results) == 0 + + data = { + "type": PersonIdentity.IdentityType.FEIDE_ID, + "source": "Test source", + "value": "12345", + } + client.post( + reverse("person_identity-list", kwargs={"person_id": person_foo.id}), data=data + ) + + response = client.get( + reverse("person_identity-list", kwargs={"person_id": person_foo.id}) + ) + results = response.json()["results"] + assert len(results) == 1 + + identity_id = results[0]["id"] + + assert results[0]["type"] == data["type"] + assert results[0]["source"] == data["source"] + assert results[0]["value"] == data["value"] + + data_updated = { + "type": PersonIdentity.IdentityType.PASSPORT, + "source": "Test", + "value": "10000", + } + patch_response = client.patch( + reverse( + "person_identity-detail", + kwargs={"person_id": person_foo.id, "id": identity_id}, + ), + data=data_updated, + ) + assert patch_response.status_code == status.HTTP_200_OK + + response = client.get( + reverse("person_identity-list", kwargs={"person_id": person_foo.id}) + ) + results = response.json()["results"] + assert len(results) == 1 + + assert results[0]["type"] == data_updated["type"] + assert results[0]["source"] == data_updated["source"] + assert results[0]["value"] == data_updated["value"] diff --git a/greg/tests/conftest.py b/greg/tests/conftest.py index 290c0a14a871798caed88c5157ac86b2d20a7122..9280ae7c911ecba5bece2b7cfc414d49e183cb69 100644 --- a/greg/tests/conftest.py +++ b/greg/tests/conftest.py @@ -1,21 +1,21 @@ import logging +from rest_framework.authtoken.models import Token +from rest_framework.test import APIClient +from django.contrib.auth import get_user_model + # faker spams the logs with localisation warnings # see https://github.com/joke2k/faker/issues/753 import pytest -from greg.tests.populate_database import DatabasePopulation - logging.getLogger("faker").setLevel(logging.ERROR) -# The database is populated once when scope is session. -# If the scope is changed to function some additional -# logic is needed to make sure the old data is cleaned -# before the seeding is run again -@pytest.fixture(scope="session") -def setup_db_test_data(django_db_setup, django_db_blocker): - with django_db_blocker.unblock(): - database_seeder = DatabasePopulation() - database_seeder.populate_database() +@pytest.fixture +def client() -> APIClient: + user, _ = get_user_model().objects.get_or_create(username="test") + token, _ = Token.objects.get_or_create(user=user) + client = APIClient() + client.credentials(HTTP_AUTHORIZATION=f"Token {token.key}") + return client diff --git a/greg/tests/models/test_consent.py b/greg/tests/models/test_consent.py index ad800bd596686e43520556a1acb2ec22aa2a76c6..8f7711e08ff37e9e20dc7f8ce9eacca45f51ce34 100644 --- a/greg/tests/models/test_consent.py +++ b/greg/tests/models/test_consent.py @@ -1,8 +1,11 @@ +import datetime + import pytest from greg.models import ( Person, Consent, + PersonConsent, ) @@ -17,9 +20,9 @@ def person() -> Person: ) -@pytest.mark.django_db -def test_add_consent_to_person(person): - consent = Consent.objects.create( +@pytest.fixture() +def consent() -> Consent: + return Consent.objects.create( type="it_guidelines", consent_name_en="IT Guidelines", consent_name_nb="IT Regelverk", @@ -28,4 +31,25 @@ def test_add_consent_to_person(person): consent_link_en="https://example.org/it_guidelines", user_allowed_to_change=False, ) - person.consents.add(consent, through_defaults={"consent_given_at": "2021-06-20"}) + + +@pytest.mark.django_db +def test_add_consent_to_person(person: Person, consent: Consent): + consent_given_date = "2021-06-20" + person.consents.add(consent, through_defaults={"consent_given_at": consent_given_date}) # type: ignore + person_consent_links = PersonConsent.objects.filter(person_id=person.id) + + assert len(person_consent_links) == 1 + assert person_consent_links[0].person_id == person.id + assert person_consent_links[0].consent_id == consent.id + assert person_consent_links[0].consent_given_at == datetime.date(2021, 6, 20) + + +@pytest.mark.django_db +def test_add_not_acknowledged_consent_to_person(person: Person, consent: Consent): + person.consents.add(consent) + person_consent_links = PersonConsent.objects.filter(person_id=person.id) + assert len(person_consent_links) == 1 + assert person_consent_links[0].person_id == person.id + assert person_consent_links[0].consent_id == consent.id + assert person_consent_links[0].consent_given_at is None