Poser les modèles de documents et le dictionnaire RGPD
Deux livrables de WP-10 manquaient, et le second n'existait nulle part. Les modèles de documents produisent attestations et courriers types en résolvant les variables du dossier. Le rendu vit dans le domaine, avec une règle qui n'est pas négociable : une variable citée sans valeur fait échouer la génération. Une attestation trouée est un document faux, pas un document incomplet — le blanc se remarque à la relecture une fois sur deux, et la pièce part signée. De même, une variable inconnue est refusée à l'enregistrement du modèle, par l'administrateur qui l'écrit, plutôt que découverte par le gestionnaire devant le salarié qui attend. Le corps reste de l'HTML rédigé par un administrateur ; ce sont les valeurs venues du dossier qui sont échappées à l'insertion. Un nom de famille importé d'un autre outil ne doit pas pouvoir ouvrir une balise. `Document.templateId` est en ON DELETE SET NULL : une pièce déjà remise ne disparaît pas parce qu'on a effacé le modèle qui l'a produite. L'écran RGPD livre le dictionnaire donnée → finalité → base légale → destinataire → durée qu'exige la matrice n° 14. Le dictionnaire vit dans le code parce qu'il décrit le schéma ; les durées viennent de RetentionPolicy parce qu'elles se paramètrent et s'auditent. Une catégorie sans durée déclarée est signalée en rouge : le plan interdit d'appliquer une durée par défaut. L'affectation des bases légales porte un avertissement explicite. C'est une qualification juridique, pas un réglage : elle doit être confirmée par le responsable de traitement avant d'être opposée à quiconque. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5b60e7ddb6
commit
db03768025
10 files changed
+1090
-1
No files matched your search
@@ -0,0 +1,82 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import {
|
||||
MissingTemplateValue,
|
||||
referencedFields,
|
||||
renderTemplate,
|
||||
TEMPLATE_FIELD_KEYS,
|
||||
unknownFields,
|
||||
} from '@/domain/documents/template';
|
||||
|
||||
describe('referencedFields', () => {
|
||||
it('relève les variables citées, sans doublon', () => {
|
||||
const body = '<p>{{salarie.nom}} — {{salarie.nom}} {{contrat.debut}}</p>';
|
||||
expect(referencedFields(body)).toEqual(['salarie.nom', 'contrat.debut']);
|
||||
});
|
||||
|
||||
it('tolère les espaces dans les accolades', () => {
|
||||
expect(referencedFields('{{ salarie.nom }}')).toEqual(['salarie.nom']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('unknownFields', () => {
|
||||
it('signale une variable que l’application ne sait pas résoudre', () => {
|
||||
// Le cas qui compte : la faute de frappe. Sans ce contrôle, le modèle
|
||||
// s'enregistre et échoue au moment de générer la pièce, devant le salarié.
|
||||
expect(unknownFields('{{salarie.salire}}')).toEqual(['salarie.salire']);
|
||||
});
|
||||
|
||||
it('accepte toutes les variables du catalogue', () => {
|
||||
const body = TEMPLATE_FIELD_KEYS.map((key) => `{{${key}}}`).join(' ');
|
||||
expect(unknownFields(body)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('renderTemplate', () => {
|
||||
it('remplace les variables par leur valeur', () => {
|
||||
const out = renderTemplate('<p>{{salarie.nom}}</p>', {
|
||||
'salarie.nom': 'Ferrand',
|
||||
});
|
||||
expect(out).toBe('<p>Ferrand</p>');
|
||||
});
|
||||
|
||||
it('échappe la valeur, jamais le gabarit', () => {
|
||||
// Le corps est rédigé par un administrateur et reste de l'HTML ; la valeur
|
||||
// vient du dossier et pourrait porter un chevron.
|
||||
const out = renderTemplate('<p><strong>{{salarie.nom}}</strong></p>', {
|
||||
'salarie.nom': '<script>alert(1)</script>',
|
||||
});
|
||||
expect(out).toBe(
|
||||
'<p><strong><script>alert(1)</script></strong></p>',
|
||||
);
|
||||
});
|
||||
|
||||
it('refuse de rendre une variable sans valeur', () => {
|
||||
// Une attestation trouée est un document faux, pas un document incomplet :
|
||||
// le blanc se remarque une fois sur deux, et la pièce part signée.
|
||||
expect(() =>
|
||||
renderTemplate('{{salarie.nom}} {{contrat.debut}}', {
|
||||
'salarie.nom': 'Ferrand',
|
||||
}),
|
||||
).toThrow(MissingTemplateValue);
|
||||
});
|
||||
|
||||
it('traite la chaîne vide comme une valeur manquante', () => {
|
||||
expect(() =>
|
||||
renderTemplate('{{salarie.nom}}', { 'salarie.nom': '' }),
|
||||
).toThrow(MissingTemplateValue);
|
||||
});
|
||||
|
||||
it('nomme les variables manquantes dans l’erreur', () => {
|
||||
try {
|
||||
renderTemplate('{{salarie.nom}} {{contrat.debut}}', {});
|
||||
expect.unreachable('le rendu aurait dû échouer');
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(MissingTemplateValue);
|
||||
expect((error as MissingTemplateValue).keys).toEqual([
|
||||
'salarie.nom',
|
||||
'contrat.debut',
|
||||
]);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user