Skip to content

Commit

Permalink
👔(dashboard) enforce immutability of validated consents
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ssorin committed Jan 13, 2025
1 parent 813d72f commit c954de1
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 9 deletions.
1 change: 1 addition & 0 deletions src/dashboard/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ and this project adheres to
- add consent form to manage consents of one or many entities
- add admin integration for Entity, DeliveryPoint and Consent
- add mass admin action (make revoked and make awaiting) for consents
- block the updates of all new data if a consentement has the status `VALIDATED`
- integration of custom 403, 404 and 500 pages
- sentry integration
- added a signal on the creation of a delivery point. This signal allows the creation
Expand Down
29 changes: 28 additions & 1 deletion src/dashboard/apps/consent/models.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
"""Dashboard consent app models."""

from django.core.exceptions import ValidationError
from django.db import models
from django.utils import timezone
from django.utils.translation import gettext_lazy as _

from apps.core.abstract_models import DashboardBase

from . import AWAITING, CONSENT_STATUS_CHOICE, REVOKED
from . import AWAITING, CONSENT_STATUS_CHOICE, REVOKED, VALIDATED
from .managers import ConsentManager
from .utils import consent_end_date

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

def clean(self):
"""Custom validation logic.
Validates and restricts updates to the Consent object if its status is set
to `VALIDATED`. This ensures that validated consents cannot be modified
after their status are defined to `VALIDATED` (We prevent this update
for contractual reasons).
Raises:
------
ValidationError
If the Consent object's status is `VALIDATED`.
"""
if self.pk:
try:
current_instance = Consent.objects.get(id=self.pk)
except Consent.DoesNotExist:
return

if current_instance.status == VALIDATED:
raise ValidationError(
message=_("Validated consent cannot be modified once defined.")
)

def save(self, *args, **kwargs):
"""Saves with custom logic.
If the consent status is `REVOKED`, `revoked_at` is updated to the current time.
"""
self.clean()

if self.status == REVOKED:
self.revoked_at = timezone.now()

Expand Down
40 changes: 32 additions & 8 deletions src/dashboard/apps/consent/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import datetime

import pytest
from django.core.exceptions import ValidationError
from django.db.models import signals

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

@pytest.mark.django_db
def test_update_consent_status():
"""Tests updating a consent status."""
"""Tests updating a consent status.
Test that consents can no longer be modified once their status is passed to
`VALIDATED` (raise ValidationError).
"""
from apps.consent.models import Consent

# create one `delivery_point` and consequently one `consent`
assert Consent.objects.count() == 0
delivery_point = DeliveryPointFactory()
assert Consent.objects.count() == 1

# get the created consent
consent = Consent.objects.get(delivery_point=delivery_point)
consent_updated_at = consent.updated_at
assert consent.status == AWAITING
assert consent.revoked_at is None

# update status to VALIDATED
consent.status = VALIDATED
# update status to REVOKED
consent.status = REVOKED
consent.save()
assert consent.status == VALIDATED
assert consent.status == REVOKED
assert consent.updated_at > consent_updated_at
assert consent.revoked_at is not None
new_updated_at = consent.updated_at

# Update the consent to AWAITING
consent.status = AWAITING
consent.revoked_at = None
consent.save()
assert consent.status == AWAITING
assert consent.updated_at > new_updated_at
assert consent.revoked_at is None
new_updated_at = consent.updated_at

# update status to REVOKED
consent.status = REVOKED
# update status to VALIDATED
consent.status = VALIDATED
consent.revoked_at = None
consent.save()
assert consent.status == REVOKED
assert consent.status == VALIDATED
assert consent.updated_at > new_updated_at
assert consent.revoked_at is not None
assert consent.revoked_at is None

# The consent status is `VALIDATED`, so it cannot be changed anymore.
with pytest.raises(ValidationError):
consent.status = AWAITING
consent.save()

0 comments on commit c954de1

Please sign in to comment.