Skip to content

Commit bb18e64

Browse files
committed
👔(dashboard) enforce immutability of validated consents
Introduced a validation mechanism to block any changes to consents with a `VALIDATED` status, raising a `ValidationError` if attempted. This preserves contractual integrity by ensuring such consents remain unchanged. Updated tests to confirm this behavior.
1 parent 813d72f commit bb18e64

File tree

2 files changed

+60
-9
lines changed

2 files changed

+60
-9
lines changed

src/dashboard/apps/consent/models.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
"""Dashboard consent app models."""
22

3+
from django.core.exceptions import ValidationError
34
from django.db import models
45
from django.utils import timezone
56
from django.utils.translation import gettext_lazy as _
67

78
from apps.core.abstract_models import DashboardBase
89

9-
from . import AWAITING, CONSENT_STATUS_CHOICE, REVOKED
10+
from . import AWAITING, CONSENT_STATUS_CHOICE, REVOKED, VALIDATED
1011
from .managers import ConsentManager
1112
from .utils import consent_end_date
1213

@@ -57,11 +58,37 @@ class Meta: # noqa: D106
5758
def __str__(self): # noqa: D105
5859
return f"{self.delivery_point} - {self.updated_at}: {self.status}"
5960

61+
def clean(self):
62+
"""Custom validation logic.
63+
64+
Validates and restricts updates to the Consent object if its status is set
65+
to `VALIDATED`. This ensures that validated consents cannot be modified
66+
after their status are defined to `VALIDATED` (We prevent this update
67+
for contractual reasons).
68+
69+
Raises:
70+
------
71+
ValidationError
72+
If the Consent object's status is `VALIDATED`.
73+
"""
74+
if self.pk:
75+
try:
76+
current_instance = Consent.objects.get(id=self.pk)
77+
except Consent.DoesNotExist:
78+
return
79+
80+
if current_instance.status == VALIDATED:
81+
raise ValidationError(
82+
message="Validated consent cannot be modified once defined."
83+
)
84+
6085
def save(self, *args, **kwargs):
6186
"""Saves with custom logic.
6287
6388
If the consent status is `REVOKED`, `revoked_at` is updated to the current time.
6489
"""
90+
self.clean()
91+
6592
if self.status == REVOKED:
6693
self.revoked_at = timezone.now()
6794

src/dashboard/apps/consent/tests/test_models.py

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import datetime
44

55
import pytest
6+
from django.core.exceptions import ValidationError
67
from django.db.models import signals
78

89
from apps.consent import AWAITING, REVOKED, VALIDATED
@@ -100,27 +101,50 @@ def test_create_consent_with_custom_period_date():
100101

101102
@pytest.mark.django_db
102103
def test_update_consent_status():
103-
"""Tests updating a consent status."""
104+
"""Tests updating a consent status.
105+
106+
Test that consents can no longer be modified once their status is passed to
107+
`VALIDATED` (raise ValidationError).
108+
"""
104109
from apps.consent.models import Consent
105110

106111
# create one `delivery_point` and consequently one `consent`
112+
assert Consent.objects.count() == 0
107113
delivery_point = DeliveryPointFactory()
114+
assert Consent.objects.count() == 1
108115

109116
# get the created consent
110117
consent = Consent.objects.get(delivery_point=delivery_point)
111118
consent_updated_at = consent.updated_at
119+
assert consent.status == AWAITING
120+
assert consent.revoked_at is None
112121

113-
# update status to VALIDATED
114-
consent.status = VALIDATED
122+
# update status to REVOKED
123+
consent.status = REVOKED
115124
consent.save()
116-
assert consent.status == VALIDATED
125+
assert consent.status == REVOKED
117126
assert consent.updated_at > consent_updated_at
127+
assert consent.revoked_at is not None
128+
new_updated_at = consent.updated_at
129+
130+
# Update the consent to AWAITING
131+
consent.status = AWAITING
132+
consent.revoked_at = None
133+
consent.save()
134+
assert consent.status == AWAITING
135+
assert consent.updated_at > new_updated_at
118136
assert consent.revoked_at is None
119137
new_updated_at = consent.updated_at
120138

121-
# update status to REVOKED
122-
consent.status = REVOKED
139+
# update status to VALIDATED
140+
consent.status = VALIDATED
141+
consent.revoked_at = None
123142
consent.save()
124-
assert consent.status == REVOKED
143+
assert consent.status == VALIDATED
125144
assert consent.updated_at > new_updated_at
126-
assert consent.revoked_at is not None
145+
assert consent.revoked_at is None
146+
147+
# The consent status is `VALIDATED`, so it cannot be changed anymore.
148+
with pytest.raises(ValidationError):
149+
consent.status = AWAITING
150+
consent.save()

0 commit comments

Comments
 (0)