Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨(dashboard) add validation and data updates for consent management #370

Merged
merged 1 commit into from
Feb 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading