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