Skip to content

Commit

Permalink
✨(dashboard) add validation and data updates for consent management
Browse files Browse the repository at this point in the history
Add validation for company, control authority, and representative data in consent bulk updates.
Enhanced `ConsentFormView` to handle form errors gracefully and updated consent records with validated fields and related data.
Adjusted tests to incorporate mock form data and validated consent updates.
  • Loading branch information
ssorin committed Feb 6, 2025
1 parent 1cb58a9 commit f653c47
Show file tree
Hide file tree
Showing 9 changed files with 321 additions and 104 deletions.
5 changes: 4 additions & 1 deletion src/dashboard/apps/consent/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,8 @@ class ConsentConfig(AppConfig):
verbose_name = _("Consent")

def ready(self):
"""Register signals."""
"""Register signals and validate CONSENT_CONTROL_AUTHORITY on ready."""
from .signals import handle_new_delivery_point # noqa: F401
from .validators import validate_configured_control_authority

validate_configured_control_authority()
10 changes: 9 additions & 1 deletion src/dashboard/apps/consent/forms.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Dashboard consent app forms."""

from datetime import datetime

from django import forms
from django.forms.widgets import CheckboxInput
from django.utils.translation import gettext_lazy as _
Expand All @@ -20,7 +22,7 @@ class ConsentForm(forms.Form):
"""

# Specific authorisation checkbox
is_authorized_signatory = forms.BooleanField(
is_authoritative_signatory = forms.BooleanField(
required=True,
initial=False,
widget=ConsentCheckboxInput(
Expand Down Expand Up @@ -101,6 +103,12 @@ class ConsentForm(forms.Form):
),
)

signed_at = forms.DateField(
initial=datetime.now().strftime("%d/%m/%Y"),
required=True,
widget=forms.HiddenInput(attrs={"readonly": "readonly"}),
)

# Global authorisation checkbox - this field must be in last position.
consent_agreed = forms.BooleanField(
required=True,
Expand Down
46 changes: 18 additions & 28 deletions src/dashboard/apps/consent/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,14 @@
"maxLength": 5,
"pattern": r"^\d{4}[A-Z]$",
},
"address": {
"type": "object",
"properties": {
"line_1": {"type": ["string", "null"], "maxLength": 255},
"line_2": {"type": ["string", "null"], "maxLength": 255},
"zip_code": {
"type": ["string", "null"],
"maxLength": 5,
"pattern": "^[0-9]{1,5}$",
},
"city": {"type": ["string", "null"], "maxLength": 255},
},
"required": ["line_1", "zip_code", "city"],
"address_1": {"type": ["string", "null"], "maxLength": 255},
"address_2": {"type": ["string", "null"], "maxLength": 255},
"zip_code": {
"type": ["string", "null"],
"maxLength": 5,
"pattern": "^[0-9]{1,5}$",
},
"city": {"type": ["string", "null"], "maxLength": 255},
},
"required": [
"company_type",
Expand All @@ -55,7 +49,9 @@
"trade_name",
"siret",
"naf",
"address",
"address_1",
"zip_code",
"city",
],
"additionalProperties": False,
}
Expand All @@ -78,22 +74,16 @@
"name": {"type": ["string", "null"], "maxLength": 255},
"represented_by": {"type": ["string", "null"], "maxLength": 255},
"email": {"type": ["string", "null"], "format": "email"},
"address": {
"type": "object",
"properties": {
"line_1": {"type": ["string", "null"], "maxLength": 255},
"line_2": {"type": ["string", "null"], "maxLength": 255},
"zip_code": {
"type": ["string", "null"],
"maxLength": 5,
"pattern": "^[0-9]{1,5}$",
},
"city": {"type": ["string", "null"], "maxLength": 255},
},
"required": ["line_1", "zip_code", "city"],
"address_1": {"type": ["string", "null"], "maxLength": 255},
"address_2": {"type": ["string", "null"], "maxLength": 255},
"zip_code": {
"type": ["string", "null"],
"maxLength": 5,
"pattern": "^[0-9]{1,5}$",
},
"city": {"type": ["string", "null"], "maxLength": 255},
},
"required": ["name", "represented_by", "email", "address"],
"required": ["name", "represented_by", "email", "address_1", "zip_code", "city"],
"additionalProperties": False,
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,3 @@
<br />
<strong>Le :</strong> {% now "d/m/Y" %}
</p>

<input class="fr-input"
type="hidden"
id="text-input-entity-name"
name="text-input-entity-name"
value="{% now "d/m/Y" %}"
readonly="readonly">
19 changes: 13 additions & 6 deletions src/dashboard/apps/consent/templates/consent/manage.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,22 @@ <h2>Gérer les autorisations</h2>
<form action="" method="post">
{% csrf_token %}

<div class="fr-messages-group" id="error-messages" aria-live="assertive">
{% if form.errors %}
{% if form.errors %}
<div class="fr-messages-group" id="error-messages" aria-live="assertive">
<div class="{% if form.consent_agreed.errors %}fr-pl-3v{% endif %} fr-mb-6v">
<p class="fr-message fr-message--error" id="message-error">
{% trans "The form contains errors" %}
</p>
<ul id="message-non-field-error">
<li class="fr-message fr-message--error" id="message-error">
{% trans "The form contains errors" %}
</li>
{% if form.non_field_errors %}
{% for error in form.non_field_errors %}
<li class="fr-message fr-message--error">{{ error }}</li>
{% endfor %}
{% endif %}
</ul>
</div>
{% endif %}
</div>
{% endif %}

{% include "consent/includes/_manage_consents.html" %}
{% include "consent/includes/_manage_company_informations.html" %}
Expand Down
92 changes: 62 additions & 30 deletions src/dashboard/apps/consent/tests/test_validators.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
"""Dashboard consent validators tests."""

import pytest
from django.core.exceptions import ValidationError
from django.core.exceptions import ImproperlyConfigured, ValidationError

from apps.consent.validators import (
validate_company_schema,
validate_configured_control_authority,
validate_control_authority_schema,
validate_representative_schema,
)
Expand All @@ -16,12 +17,10 @@
"trade_name": "The test company",
"siret": "12345678901234",
"naf": "1234A",
"address": {
"line_1": "1 rue Exemple",
"line_2": None,
"zip_code": "75000",
"city": "Paris",
},
"address_1": "1 rue Exemple",
"address_2": None,
"zip_code": "75000",
"city": "Paris",
}

VALID_REPRESENTATIVE_DATA = {
Expand All @@ -35,12 +34,10 @@
"name": "QualiCharge",
"represented_by": "John Doe",
"email": "[email protected]",
"address": {
"line_1": "1 Rue Exemple",
"line_2": None,
"zip_code": "75000",
"city": "Paris",
},
"address_1": "1 Rue Exemple",
"address_2": None,
"zip_code": "75000",
"city": "Paris",
}


Expand Down Expand Up @@ -111,38 +108,38 @@ def test_validate_compnay_naf_code_invalid(value):
def test_validate_zip_code_valid(value):
"""Tests validation of valid zip codes does not raise an exception."""
valid_company_data = VALID_COMPANY_DATA
valid_company_data["address"]["zip_code"] = value
valid_company_data["zip_code"] = value
assert validate_company_schema(valid_company_data) is None

valid_authority_data = VALID_CONTROL_AUTHORITY_DATA
valid_authority_data["address"]["zip_code"] = value
valid_authority_data["zip_code"] = value
assert validate_control_authority_schema(valid_authority_data) is None


@pytest.mark.parametrize("value", ["123456", "12a45", "abcde", "", " ", 12345])
def test_validate_zip_code_invalid(value):
"""Tests validation of invalid zip codes raise a ValidationError."""
valid_company_data = VALID_COMPANY_DATA
valid_company_data["address"]["zip_code"] = value
valid_company_data["zip_code"] = value
with pytest.raises(ValidationError):
validate_company_schema(valid_company_data)
# reset with valid zip
VALID_COMPANY_DATA["address"]["zip_code"] = "12345"
VALID_COMPANY_DATA["zip_code"] = "12345"

valid_authority_data = VALID_CONTROL_AUTHORITY_DATA
valid_authority_data["address"]["zip_code"] = value
valid_authority_data["zip_code"] = value
with pytest.raises(ValidationError):
validate_control_authority_schema(valid_authority_data)
# reset with valid zip
VALID_CONTROL_AUTHORITY_DATA["address"]["zip_code"] = "12345"
VALID_CONTROL_AUTHORITY_DATA["zip_code"] = "12345"


def test_validate_company_schema_valid():
"""Test the json schema validator with a valid company data."""
assert validate_company_schema(VALID_COMPANY_DATA) is None

# valid with specific zip code
VALID_COMPANY_DATA["address"]["zip_code"] = "978"
VALID_COMPANY_DATA["zip_code"] = "978"
assert validate_company_schema(VALID_COMPANY_DATA) is None

# test with null values
Expand All @@ -153,11 +150,9 @@ def test_validate_company_schema_valid():
"trade_name": None,
"siret": None,
"naf": None,
"address": {
"line_1": None,
"zip_code": None,
"city": None,
},
"address_1": None,
"zip_code": None,
"city": None,
}
assert validate_company_schema(valid_company_data) is None

Expand Down Expand Up @@ -218,11 +213,9 @@ def test_validate_control_authority_schema_valid():
"name": None,
"represented_by": None,
"email": None,
"address": {
"line_1": None,
"zip_code": None,
"city": None,
},
"address_1": None,
"zip_code": None,
"city": None,
}
assert validate_control_authority_schema(validate_control_authority_data) is None

Expand All @@ -245,3 +238,42 @@ def test_validate_control_authority_schema_invalid():
invalid_value["additional_property"] = ""
with pytest.raises(ValidationError):
validate_control_authority_schema(invalid_value)


def test_validate_configured_control_authority_is_valid(settings):
"""Test validate_configured_control_authority with valid data."""
# Change temporally settings.CONSENT_CONTROL_AUTHORITY.
settings.CONSENT_CONTROL_AUTHORITY = {
"name": "Control Authority Name",
"represented_by": "John Doe",
"email": "[email protected]",
"address_1": "123 Street Name",
"address_2": "",
"zip_code": "12345",
"city": "City Name",
}

# ImproperlyConfigured should not be raised
try:
validate_configured_control_authority()
except ImproperlyConfigured as e:
pytest.fail(f"settings.CONSENT_CONTROL_AUTHORITY validation error: {e.message}")


def test_validate_configured_control_authority_raise_error(settings):
"""Test validate_configured_control_authority with invalid data."""
# Change temporally settings.CONSENT_CONTROL_AUTHORITY with invalid data.
# invalid: the key 'name' is expected, not 'firstname'
settings.CONSENT_CONTROL_AUTHORITY = {
"firstname": "Control Authority Name",
"represented_by": "John Doe",
"email": "[email protected]",
"address_1": "",
"address_2": "",
"zip_code": "12345",
"city": "City Name",
}

# must raise ImproperlyConfigured
with pytest.raises(ImproperlyConfigured):
validate_configured_control_authority()
Loading

0 comments on commit f653c47

Please sign in to comment.