From fc1ce13d33ae4abaccbe375f4db502c1f6a4f3dc Mon Sep 17 00:00:00 2001
From: nicoayci <nicoayci@users.noreply.github.com>
Date: Mon, 28 Apr 2025 13:22:16 +0200
Subject: [PATCH] feat(import): validate cell content

---
 .../beta/selexpert/dto/ImportFileHeader.java  | 14 ++--
 .../dto/ImportFileHeaderAppealCourt.java      | 64 ++++++++++---------
 .../dto/ImportFileHeaderCassationCourt.java   | 24 ++++---
 .../gouv/beta/selexpert/model/ExpertType.java | 23 -------
 .../beta/selexpert/service/ImportService.java | 25 +++++++-
 .../selexpert/service/ImportServiceTest.java  | 28 ++++++++
 .../import-test/invalid-content-cassation.csv |  3 +
 .../resources/import-test/invalid-content.csv |  3 +
 8 files changed, 114 insertions(+), 70 deletions(-)
 delete mode 100644 src/main/java/fr/gouv/beta/selexpert/model/ExpertType.java
 create mode 100644 src/test/resources/import-test/invalid-content-cassation.csv
 create mode 100644 src/test/resources/import-test/invalid-content.csv

diff --git a/src/main/java/fr/gouv/beta/selexpert/dto/ImportFileHeader.java b/src/main/java/fr/gouv/beta/selexpert/dto/ImportFileHeader.java
index aa3d9b73..87416841 100644
--- a/src/main/java/fr/gouv/beta/selexpert/dto/ImportFileHeader.java
+++ b/src/main/java/fr/gouv/beta/selexpert/dto/ImportFileHeader.java
@@ -1,16 +1,20 @@
 package fr.gouv.beta.selexpert.dto;
 
 import java.util.Arrays;
-import java.util.List;
-import java.util.stream.Collectors;
+import java.util.function.Predicate;
 import java.util.stream.IntStream;
 
 public interface ImportFileHeader {
 
-  static <T extends Enum<T> & ImportFileHeader> List<String> headers(Class<T> clazz) {
+  static <T extends Enum<T> & ImportFileHeader> String[] headerLabels(Class<T> clazz) {
     return Arrays.stream(clazz.getEnumConstants())
         .map(ImportFileHeader::getLabel)
-        .collect(Collectors.toList());
+        .toList()
+        .toArray(new String[0]);
+  }
+
+  static <T extends Enum<T> & ImportFileHeader> ImportFileHeader[] headers(Class<T> clazz) {
+    return clazz.getEnumConstants();
   }
 
   static <T extends Enum<T> & ImportFileHeader> int pos(T header) {
@@ -21,4 +25,6 @@ public interface ImportFileHeader {
   }
 
   String getLabel();
+
+  Predicate<String> getCellContentValidator();
 }
diff --git a/src/main/java/fr/gouv/beta/selexpert/dto/ImportFileHeaderAppealCourt.java b/src/main/java/fr/gouv/beta/selexpert/dto/ImportFileHeaderAppealCourt.java
index cbaf2a10..386d2b9c 100644
--- a/src/main/java/fr/gouv/beta/selexpert/dto/ImportFileHeaderAppealCourt.java
+++ b/src/main/java/fr/gouv/beta/selexpert/dto/ImportFileHeaderAppealCourt.java
@@ -1,41 +1,45 @@
 package fr.gouv.beta.selexpert.dto;
 
+import java.util.function.Predicate;
 import lombok.Getter;
 import lombok.RequiredArgsConstructor;
+import org.apache.commons.lang3.StringUtils;
 
 @Getter
 @RequiredArgsConstructor
 public enum ImportFileHeaderAppealCourt implements ImportFileHeader {
-  ID_EXPERT_CATEGORIE("id_expert_categorie"),
-  ID_CA("id_ca"),
-  CA("ca"),
-  CIVILITE("civilite"),
-  NOM("nom"),
-  NOM_USAGE("nom_usage"),
-  PRENOM("prenom"),
-  CATEGORIE("categorie"),
-  RUBRIQUE("rubrique"),
-  SPECIALITE("specialite"),
-  SOUS_SPECIALITE("sous-specialite"),
-  PERIODE_PROBATOIRE("periode_probatoire"),
-  DATE_PREMIERE_INSCRIPTION("date_premiere_inscription"),
-  DATE_REINSCRIPTION("date_reinscription"),
-  DATE_DEBUT_PERIODE_PROBATOIRE("date_debut_periode_probatoire"),
-  DATE_FIN_PERIODE_PROBATOIRE("date_fin_periode_probatoire"),
-  ADRESSE1("adresse1"),
-  ADRESSE2("adresse2"),
-  ADRESSE3("adresse3"),
-  LIEU_DIT("lieu_dit"),
-  VILLE("ville"),
-  CODE_POSTAL("code_postal"),
-  DEPARTEMENT("departement"),
-  COURRIEL("courriel"),
-  FAX("fax"),
-  MOBILE("mobile"),
-  FIXE("fixe"),
-  DATE_APPLICATION("date_application"),
-  DATE_FERMETURE("date_fermeture"),
-  ID_EXPERT("id_expert");
+  ID_EXPERT_CATEGORIE("id_expert_categorie", null),
+  ID_CA("id_ca", null),
+  CA("ca", null),
+  CIVILITE("civilite", (String content) -> content.matches("Madame|Monsieur|Société")),
+  NOM("nom", StringUtils::isNotBlank),
+  NOM_USAGE("nom_usage", null),
+  PRENOM("prenom", null),
+  CATEGORIE("categorie", StringUtils::isNotBlank),
+  RUBRIQUE("rubrique", StringUtils::isNotBlank),
+  SPECIALITE("specialite", null),
+  SOUS_SPECIALITE("sous-specialite", null),
+  PERIODE_PROBATOIRE("periode_probatoire", null),
+  DATE_PREMIERE_INSCRIPTION("date_premiere_inscription", null),
+  DATE_REINSCRIPTION("date_reinscription", null),
+  DATE_DEBUT_PERIODE_PROBATOIRE("date_debut_periode_probatoire", null),
+  DATE_FIN_PERIODE_PROBATOIRE("date_fin_periode_probatoire", null),
+  ADRESSE1("adresse1", null),
+  ADRESSE2("adresse2", null),
+  ADRESSE3("adresse3", null),
+  LIEU_DIT("lieu_dit", null),
+  VILLE("ville", null),
+  CODE_POSTAL("code_postal", null),
+  DEPARTEMENT("departement", null),
+  COURRIEL("courriel", null),
+  FAX("fax", null),
+  MOBILE("mobile", null),
+  FIXE("fixe", null),
+  DATE_APPLICATION("date_application", null),
+  DATE_FERMETURE("date_fermeture", null),
+  ID_EXPERT("id_expert", StringUtils::isNotBlank);
 
   private final String label;
+
+  private final Predicate<String> cellContentValidator;
 }
diff --git a/src/main/java/fr/gouv/beta/selexpert/dto/ImportFileHeaderCassationCourt.java b/src/main/java/fr/gouv/beta/selexpert/dto/ImportFileHeaderCassationCourt.java
index 40e646be..5a20e205 100644
--- a/src/main/java/fr/gouv/beta/selexpert/dto/ImportFileHeaderCassationCourt.java
+++ b/src/main/java/fr/gouv/beta/selexpert/dto/ImportFileHeaderCassationCourt.java
@@ -1,21 +1,25 @@
 package fr.gouv.beta.selexpert.dto;
 
+import java.util.function.Predicate;
 import lombok.Getter;
 import lombok.RequiredArgsConstructor;
+import org.apache.commons.lang3.StringUtils;
 
 @Getter
 @RequiredArgsConstructor
 public enum ImportFileHeaderCassationCourt implements ImportFileHeader {
-  SITUATION("Situation"),
-  NOM_IDENTITE("Nom identité"),
-  NOM_DENOMINATION("Nom/Dénomination"),
-  PRENOM("Prénom"),
-  NOM_USAGE("Nom d'usage"),
-  ORGANISATION("Organisation d'origine"),
-  CODE("Code"),
-  RUBRIQUE("Rubrique"),
-  ANCIENNE_NOMENCLATURE("Ancienne nomenclature"),
-  MEL_PRO("Mél pro.");
+  SITUATION("Situation", "En activité"::equals),
+  NOM_IDENTITE("Nom identité", null),
+  NOM_DENOMINATION("Nom/Dénomination", StringUtils::isNotBlank),
+  PRENOM("Prénom", null),
+  NOM_USAGE("Nom d'usage", null),
+  ORGANISATION("Organisation d'origine", StringUtils::isNotBlank),
+  CODE("Code", StringUtils::isNotBlank),
+  RUBRIQUE("Rubrique", StringUtils::isNotBlank),
+  ANCIENNE_NOMENCLATURE("Ancienne nomenclature", null),
+  MEL_PRO("Mél pro.", null);
 
   private final String label;
+
+  private final Predicate<String> cellContentValidator;
 }
diff --git a/src/main/java/fr/gouv/beta/selexpert/model/ExpertType.java b/src/main/java/fr/gouv/beta/selexpert/model/ExpertType.java
deleted file mode 100644
index 5814f37d..00000000
--- a/src/main/java/fr/gouv/beta/selexpert/model/ExpertType.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package fr.gouv.beta.selexpert.model;
-
-import fr.gouv.beta.selexpert.AppException;
-import java.util.Arrays;
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-
-@AllArgsConstructor
-@Getter
-public enum ExpertType {
-  FEMALE("Madame"),
-  MALE("Monsieur"),
-  COMPANY("Société"),
-  UNKNOWN("");
-  private final String label;
-
-  public static ExpertType fromLabel(String label) {
-    return Arrays.stream(ExpertType.values())
-        .filter(e -> e.label.equals(label))
-        .findFirst()
-        .orElseThrow(() -> new AppException("expert type is invalid"));
-  }
-}
diff --git a/src/main/java/fr/gouv/beta/selexpert/service/ImportService.java b/src/main/java/fr/gouv/beta/selexpert/service/ImportService.java
index 0b314161..f4992e2d 100644
--- a/src/main/java/fr/gouv/beta/selexpert/service/ImportService.java
+++ b/src/main/java/fr/gouv/beta/selexpert/service/ImportService.java
@@ -68,7 +68,7 @@ public class ImportService {
   public static final String SAINT_DENIS_CA_CODE = "100033";
   static final String ID_CA_CASSATION = "000000";
   private static final Pattern FILENAME_PATTERN =
-      Pattern.compile("Ref_Expert_CA_(\\d{6})_(.+)_\\d{4}-\\d{2}-\\d{2}-\\d{2}-\\d{2}.csv");
+      Pattern.compile("Ref_Expert_CA_(1000\\d{2})_(.+)_\\d{4}-\\d{2}-\\d{2}-\\d{2}-\\d{2}.csv");
   private final ExpertiseNewRepository expertiseNewRepository;
   private final ExpertiseRepository expertiseRepository;
   private final ExpertRawDataRepository expertRawDataRepository;
@@ -458,11 +458,30 @@ public class ImportService {
         addToReport(report, "ERREUR: FICHIER VIDE");
         return List.of();
       }
-      if (!ImportFileHeader.headers(fileHeaderEnum).equals(Arrays.asList(headerRow))) {
+      if (!Arrays.equals(ImportFileHeader.headerLabels(fileHeaderEnum), headerRow)) {
         addToReport(report, "ERREUR: COLONNES INVALIDES");
         return List.of();
       }
-      return csvReader.readAll().stream().map(toExpertRawData).collect(Collectors.toList());
+      List<String[]> rows = new ArrayList<>();
+      ImportFileHeader[] headers = ImportFileHeader.headers(fileHeaderEnum);
+      String[] nextRow;
+      boolean contentError = false;
+      int rowNumber = 2;
+      while ((nextRow = csvReader.readNext()) != null) {
+        for (int i = 0; i < nextRow.length; i++) {
+          if (Objects.nonNull(headers[i].getCellContentValidator())
+              && !headers[i].getCellContentValidator().test(nextRow[i])) {
+            addToReport(report, "ERREUR: LIGNE {} COLONNE {}", rowNumber, headers[i].getLabel());
+            contentError = true;
+          }
+        }
+        rows.add(nextRow);
+        rowNumber++;
+      }
+      if (contentError) {
+        return List.of();
+      }
+      return rows.stream().map(toExpertRawData).collect(Collectors.toList());
     } catch (IOException | CsvException e) {
       addToReport(report, "ERREUR: ERREUR LECTURE FICHIER");
       return List.of();
diff --git a/src/test/java/fr/gouv/beta/selexpert/service/ImportServiceTest.java b/src/test/java/fr/gouv/beta/selexpert/service/ImportServiceTest.java
index ee0dbd9c..7940f92a 100644
--- a/src/test/java/fr/gouv/beta/selexpert/service/ImportServiceTest.java
+++ b/src/test/java/fr/gouv/beta/selexpert/service/ImportServiceTest.java
@@ -151,6 +151,34 @@ class ImportServiceTest {
     then(report).contains("NOM DE FICHIER INVALIDE");
   }
 
+  @Test
+  public void importFile_invalid_content() {
+    // GIVEN
+    InputStream fileInputStream = getFileAsInputStream("invalid-content");
+    // WHEN
+    String report = importService.importFile(FILENAME, fileInputStream, 1, true);
+    // THEN
+    then(report).contains("ERREUR: LIGNE 2 COLONNE civilite");
+    then(report).contains("ERREUR: LIGNE 2 COLONNE nom");
+    then(report).contains("ERREUR: LIGNE 3 COLONNE categorie");
+    then(report).contains("ERREUR: LIGNE 3 COLONNE rubrique");
+    then(report).contains("ERREUR: LIGNE 3 COLONNE id_expert");
+  }
+
+  @Test
+  public void importFile_invalid_content_cassation() {
+    // GIVEN
+    InputStream fileInputStream = getFileAsInputStream("invalid-content-cassation");
+    // WHEN
+    String report = importService.importFile(FILENAME, fileInputStream, 0, true);
+    // THEN
+    then(report).contains("ERREUR: LIGNE 2 COLONNE Situation");
+    then(report).contains("ERREUR: LIGNE 2 COLONNE Nom/Dénomination");
+    then(report).contains("ERREUR: LIGNE 2 COLONNE Organisation d'origine");
+    then(report).contains("ERREUR: LIGNE 3 COLONNE Code");
+    then(report).contains("ERREUR: LIGNE 3 COLONNE Rubrique");
+  }
+
   @Test
   public void importFile_empty_file() {
     // GIVEN
diff --git a/src/test/resources/import-test/invalid-content-cassation.csv b/src/test/resources/import-test/invalid-content-cassation.csv
new file mode 100644
index 00000000..bd9d3822
--- /dev/null
+++ b/src/test/resources/import-test/invalid-content-cassation.csv
@@ -0,0 +1,3 @@
+Situation;Nom identité;Nom/Dénomination;Prénom;Nom d'usage;Organisation d'origine;Code;Rubrique;Ancienne nomenclature;Mél pro.
+XXX;Expert1;;1;;;A-01.01;label_A.1.1;Non;expert1@expert.fr
+En activité;Expert1;Expert;1;;cour d'appel d'Orléans;;;Non;expert1@expert.fr
diff --git a/src/test/resources/import-test/invalid-content.csv b/src/test/resources/import-test/invalid-content.csv
new file mode 100644
index 00000000..cb9fd36f
--- /dev/null
+++ b/src/test/resources/import-test/invalid-content.csv
@@ -0,0 +1,3 @@
+id_expert_categorie;id_ca;ca;civilite;nom;nom_usage;prenom;categorie;rubrique;specialite;sous-specialite;periode_probatoire;date_premiere_inscription;date_reinscription;date_debut_periode_probatoire;date_fin_periode_probatoire;adresse1;adresse2;adresse3;lieu_dit;ville;code_postal;departement;courriel;fax;mobile;fixe;date_application;date_fermeture;id_expert
+001;100026;ROUEN;;;W;Walter;A-01.01;label_A.1.1;spe1;sousspe1;Non;01/01/1983;01/01/2020;01/01/2021;01/01/2027;ad1;ad2;ad3;ad4;ALBUQUERQUE;555;;walter@gmail.com;;06.80.33.44.84;;01/01/2020;;123
+002;100026;ROUEN;Monsieur;WHITE;W;Walter;;;;;Non;01/01/1983;01/01/2020;01/01/2021;01/01/2027;ad1;ad2;ad3;ad4;ALBUQUERQUE;555;;walter@gmail.com;;06.80.33.44.84;;01/01/2020;;
-- 
GitLab