From 0c1628f15d5a91065a82e58d78518f7d78182a76 Mon Sep 17 00:00:00 2001 From: philippe tcheriatinsky <philippe.tcherniatinsky@inrae.fr> Date: Tue, 11 Jun 2024 14:54:47 +0200 Subject: [PATCH 1/8] correction #225 --- .../builder/CheckerDescriptionBuilder.java | 3 ++ .../application/configuration/LtreeTest.java | 4 +- .../inra/oresing/rest/OreSiResourcesTest.java | 50 +++++++++++++------ .../configuration/data.result.example.json | 21 ++++++-- ui/src/services/TagService.js | 5 +- 5 files changed, 63 insertions(+), 20 deletions(-) diff --git a/src/main/java/fr/inra/oresing/rest/model/configuration/builder/CheckerDescriptionBuilder.java b/src/main/java/fr/inra/oresing/rest/model/configuration/builder/CheckerDescriptionBuilder.java index 9ee24c252..c011ab33a 100644 --- a/src/main/java/fr/inra/oresing/rest/model/configuration/builder/CheckerDescriptionBuilder.java +++ b/src/main/java/fr/inra/oresing/rest/model/configuration/builder/CheckerDescriptionBuilder.java @@ -22,6 +22,9 @@ public record CheckerDescriptionBuilder(RootBuilder rootBuilder) { final JsonNode checkerNode, final String dataKey) { if (checkerNode == null || checkerNode.isMissingNode()) { + if(required){ + return new StringChecker(CheckerDescription.CheckerDescriptionType.StringChecker, Multiplicity.ONE, required, null); + } return null; } CheckerEnum name = null; diff --git a/src/test/java/fr/inra/oresing/domain/application/configuration/LtreeTest.java b/src/test/java/fr/inra/oresing/domain/application/configuration/LtreeTest.java index 738acaf97..d5282cf17 100644 --- a/src/test/java/fr/inra/oresing/domain/application/configuration/LtreeTest.java +++ b/src/test/java/fr/inra/oresing/domain/application/configuration/LtreeTest.java @@ -32,8 +32,8 @@ class LtreeTest { Ltree nk = Ltree.fromUnescapedString(label); Assert.assertEquals(label, nk.getSql()); }*/ - @ParameterizedTest(name = "'{0}' match an encodingString") - @ValueSource(strings = {"°","%",">","²","؇","?","&","@","€","µ"}) + @ParameterizedTest(name = "{0} match an encodingString") + @ValueSource(strings = {"°","%",">","²","$","?","&","@","@","µ"}) void testIsEcodedString(String aSign){ String aChar = Ltree.fromUnescapedString(aSign).getSql(); Assert.assertTrue(Ltree.isEncodedString(aChar)); diff --git a/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java b/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java index 017b87fc0..8d239465a 100644 --- a/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java +++ b/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java @@ -13,6 +13,7 @@ import fr.inra.oresing.domain.data.deposit.validation.CsvRowValidationCheckResul import fr.inra.oresing.domain.data.deposit.validation.ValidationCheckResult; import fr.inra.oresing.domain.exceptions.OreSiTechnicalException; import fr.inra.oresing.domain.exceptions.SiOreIllegalArgumentException; +import fr.inra.oresing.domain.exceptions.application.NoSuchApplicationException; import fr.inra.oresing.domain.exceptions.authentication.authentication.NotApplicationCanDeleteRightsException; import fr.inra.oresing.domain.exceptions.authentication.authentication.NotApplicationCreatorRightsException; import fr.inra.oresing.domain.exceptions.authorization.AuthorizationRequestException; @@ -62,6 +63,7 @@ import org.springframework.test.web.servlet.ResultMatcher; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.util.NestedServletException; +import org.testcontainers.shaded.com.fasterxml.jackson.core.JsonParseException; import java.io.*; import java.net.URL; @@ -246,20 +248,41 @@ public class OreSiResourcesTest { final MockMultipartFile configuration = new MockMultipartFile("file", "monsore.yaml", "text/plain", in); // on n'a pas le droit de creer de nouvelle application - NotApplicationCreatorRightsException resolvedException = - (NotApplicationCreatorRightsException) fixtures.loadApplicationWithError( - configuration, - monsoreCookie, - "monsore" - ); - addUserRightCreateApplication(monsoreUserId, "monsore"); - assert resolvedException != null; - Assertions.assertEquals("monsore", resolvedException.getApplicationName()); - addUserRightCreateApplication(monsoreUserId, "monsore"); + Exception loadApplicationException = fixtures.loadApplicationWithError( + configuration, + monsoreCookie, + "monsore" + ); + + switch (loadApplicationException) { + case NotApplicationCreatorRightsException notApplicationCreatorRightsException -> { + Assertions.assertEquals("monsore", notApplicationCreatorRightsException.getApplicationName()); + addUserRightCreateApplication(monsoreUserId, "monsore"); + } + case NoSuchApplicationException noSuchApplicationException -> { + addUserRightCreateApplication(monsoreUserId, "monsore"); + + } + case JsonParseException jsonParseException -> { + log.debug("******************jsonparseexception*****************"); + jsonParseException.getMessage(); + log.debug("******************jsonparseexception*****************"); + try (InputStream stream = resource.openStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(stream))) { + char[] buffer = new char[100]; // Changer cette valeur selon le nombre de caractères souhaités + int n = reader.read(buffer); + System.out.println(new String(buffer, 0, n)); + log.debug("******************jsonparseexception*****************"); + } + addUserRightCreateApplication(monsoreUserId, "monsore"); + } + default -> throw loadApplicationException; + } final MvcResult resultApplication = fixtures.loadApplication(configuration, monsoreCookie, "monsore", ""); appId = fixtures.getIdFromApplicationResult(resultApplication); } catch (final Throwable e) { + e.printStackTrace(); throw new RuntimeException(e); } @@ -1641,7 +1664,7 @@ public class OreSiResourcesTest { .andReturn().getResponse().getContentAsString(); //pour le createur auth on a les fichiers de scarff - response = mockMvc.perform(get("/api/v1/applications/monsore/data/pem/json") + response = mockMvc.perform(get("/api/v1/applications/monsore/data/pem/json") .cookie(authCookie)) .andExpect(status().is2xxSuccessful()) .andExpect(jsonPath("$.rows[*].values[?(@.chemin=='nivelle__p1' && @.projet == 'projet_manche')].chemin", hasSize(34))) @@ -1660,7 +1683,7 @@ public class OreSiResourcesTest { "to": "1984-01-02" }, "requiredAuthorizations": { - "localization": "plateforme.scarff" + "sites": "type_de_sitesKplateforme.sitesKscarff" } } ] @@ -1670,8 +1693,7 @@ public class OreSiResourcesTest { .param("downloadDatasetQuery", filter) .cookie(authCookie)) .andExpect(status().is2xxSuccessful()) - .andExpect(status().is2xxSuccessful()) - .andExpect(jsonPath("$.rows.length()", is(15))) + .andExpect(jsonPath("$.rows.length()", is(14))) .andExpect(jsonPath("$.rows[*].values[?(@.chemin=='scarff__p1' && @.projet == 'projet_manche')]", hasSize(7))) .andExpect(jsonPath("$.rows[*].values[?(@.date=='date:1984-01-01T00:00:00:dd/MM/yyyy' && @.projet == 'projet_manche')]", hasSize(7))); diff --git a/src/test/resources/data/configuration/data.result.example.json b/src/test/resources/data/configuration/data.result.example.json index a120244a0..5b82415cd 100644 --- a/src/test/resources/data/configuration/data.result.example.json +++ b/src/test/resources/data/configuration/data.result.example.json @@ -502,7 +502,12 @@ "exportHeaderName" : "ptx_propriete", "required" : true, "mandatory" : "OPTIONAL", - "checker" : null, + "checker" : { + "type" : "StringChecker", + "multiplicity" : "ONE", + "required" : true, + "pattern" : null + }, "submissionAuthorizationScope" : null, "hidden" : false, "referenceCheckerType" : { @@ -725,7 +730,12 @@ "exportHeaderName" : "tax_taxon", "required" : true, "mandatory" : "OPTIONAL", - "checker" : null, + "checker" : { + "type" : "StringChecker", + "multiplicity" : "ONE", + "required" : true, + "pattern" : null + }, "submissionAuthorizationScope" : null, "hidden" : false, "referenceCheckerType" : { @@ -980,7 +990,12 @@ } ], "required" : true, "mandatory" : "OPTIONAL", - "checker" : null, + "checker" : { + "type" : "StringChecker", + "multiplicity" : "ONE", + "required" : true, + "pattern" : null + }, "constantImportHeader" : { "type" : "FileConstantHeader", "rowNumber" : 1, diff --git a/ui/src/services/TagService.js b/ui/src/services/TagService.js index 95122a5db..ee2a4fc36 100644 --- a/ui/src/services/TagService.js +++ b/ui/src/services/TagService.js @@ -8,10 +8,13 @@ export class TagService extends Fetcher { toBeShown(tags, datas) { datas = Object.values(datas || {}) .sort((a,b) => { + if(!a.order) return 1 if (a.order && b.order) { + if(a.order === b.order) return 0 if (a.order > b.order) return 1; - return -1; + return -1 } + return -1; }) if (!tags || !Object.keys(tags || {}).length) { return datas.map((data)=> { -- GitLab From 1268b803c08c8ba3b406ea85f16eb2ff65dc0d9b Mon Sep 17 00:00:00 2001 From: philippe tcheriatinsky <philippe.tcherniatinsky@inrae.fr> Date: Wed, 12 Jun 2024 14:48:39 +0200 Subject: [PATCH 2/8] =?UTF-8?q?Client=20d'import=20de=20donn=C3=A9es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 5 + .../java/fr/inra/oresing/client/Client.java | 427 ++++++++++++++++++ 2 files changed, 432 insertions(+) create mode 100644 src/main/java/fr/inra/oresing/client/Client.java diff --git a/pom.xml b/pom.xml index 85ed3aa1b..ca04329b1 100644 --- a/pom.xml +++ b/pom.xml @@ -245,6 +245,11 @@ <groupId>org.junit.platform</groupId> <artifactId>junit-platform-suite-engine</artifactId> </dependency> + <dependency> + <groupId>org.apache.httpcomponents.client5</groupId> + <artifactId>httpclient5</artifactId> + <version>5.2.1</version> + </dependency> </dependencies> diff --git a/src/main/java/fr/inra/oresing/client/Client.java b/src/main/java/fr/inra/oresing/client/Client.java new file mode 100644 index 000000000..d09f59f65 --- /dev/null +++ b/src/main/java/fr/inra/oresing/client/Client.java @@ -0,0 +1,427 @@ +package fr.inra.oresing.client; + +import com.fasterxml.jackson.core.exc.StreamReadException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DatabindException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.io.file.AccumulatorPathVisitor; +import org.apache.commons.io.file.Counters; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.cookie.BasicCookieStore; +import org.apache.hc.client5.http.cookie.CookieStore; +import org.apache.hc.client5.http.entity.mime.FileBody; +import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.core5.http.ClassicHttpRequest; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.io.HttpClientResponseHandler; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.hc.core5.http.io.support.ClassicRequestBuilder; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Scanner; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.stream.Collectors; + +public class Client { + + public static void main(String[] args) { + new Client().run(); + } + + public void run() { + String applicationName = "foret"; + String openAdomInstanceUrl = "http://localhost:8081"; + String login; + String password; + boolean interactive = false; + Scanner scanner = new Scanner(System.in); + if (interactive) { + System.out.println("Veuillez saisir les informations de connexion à " + openAdomInstanceUrl); + System.out.print("identifiant : "); + login = scanner.nextLine(); + System.out.print("mot de passe : "); + password = scanner.nextLine(); + } else { + login = "poussin"; + password = "xxxx"; + } + + CookieStore cookieStore = new BasicCookieStore(); + + UriFactory uriFactory = new UriFactory(openAdomInstanceUrl, applicationName); + + try (CloseableHttpClient httpclient = HttpClients.custom() + .setDefaultCookieStore(cookieStore) + .build()) { + + ClassicHttpRequest loginRequest = ClassicRequestBuilder.post() + .setUri(uriFactory.forLogin()) + .addParameter("login", login) + .addParameter("password", password) + .build(); + + httpclient.execute(loginRequest, response -> { + switch (response.getCode()) { + case HttpURLConnection.HTTP_OK -> { + EntityUtils.consume(response.getEntity()); + switch (cookieStore.getCookies().size()) { + case 0 -> fail("authentification échouée : pas de cookie d’authentification retourné"); + case 1 -> { + if (cookieStore.getCookies().get(0).getName().equals("si-ore-jwt")) { + log("authentification OK"); + } else { + fail("authentification échouée : pas de cookie d’authentification retourné"); + } + } + default -> + fail(cookieStore.getCookies().size() + " cookies retournés à l’authentification"); + } + } + case HttpURLConnection.HTTP_UNAUTHORIZED -> + fail("authentification échouée : identifiant ou mot de passe faux ?"); + default -> + fail("%d en code de retour HTTP inattendu à l’authentification".formatted(response.getCode())); + } + return null; + }); + + ClassicHttpRequest getApplicationReferenceTypesRequest = ClassicRequestBuilder + .get(uriFactory.forApplicationReferenceTypes(applicationName)) + .build(); + + List<String> refTypes = httpclient.execute(getApplicationReferenceTypesRequest, response -> { + switch (response.getCode()) { + case HttpURLConnection.HTTP_OK -> { + return parseJsonInResponseBody( + response, + new TypeReference<List<String>>() {} + ); + } + case HttpURLConnection.HTTP_UNAUTHORIZED -> + fail("pas l’autorisation pour l’application " + applicationName); + default -> + fail("%d en code de retour HTTP inattendu".formatted(response.getCode())); + } + return null; + }); + + log("référentiels à importer :%n" + String.join(System.lineSeparator(), refTypes)); + + ClassicHttpRequest getApplicationDataTypesRequest = ClassicRequestBuilder + .get(uriFactory.forApplicationDataTypes(applicationName)) + .build(); + + List<String> dataTypes = httpclient.execute(getApplicationDataTypesRequest, response -> { + switch (response.getCode()) { + case HttpURLConnection.HTTP_OK -> { + return parseJsonInResponseBody( + response, + new TypeReference<List<String>>() {} + ); + } + case HttpURLConnection.HTTP_UNAUTHORIZED -> + fail("pas l’autorisation pour l’application " + applicationName); + default -> + fail("%d en code de retour HTTP inattendu".formatted(response.getCode())); + } + return null; + }); + + log("données expérimentales à importer :%n" + String.join(System.lineSeparator(), dataTypes)); + + List<Command> commands = newCommands(refTypes, dataTypes); + + if (interactive) { + String plan = commands.stream() + .map(Command::getDescription) + .collect(Collectors.joining(System.lineSeparator())); + log("Plan :"); + log(plan); + System.out.print("est-ce que le plan convient ? [O/n]"); + String planIsOkString = scanner.nextLine(); + boolean planIsOk = Set.of("o", "oui", "").contains(planIsOkString.toLowerCase()); + if (!planIsOk) { + fail("Abandon"); + } + } + + for (Command command : commands) { + log("va traiter " + command.getDescription()); + ClassicHttpRequest request = command.getRequest(uriFactory); + httpclient.execute(request, command); + log("a traité " + command.getDescription()); + } + } catch (IOException e) { + fail("Erreur réseau HTTP : " + e.getMessage()); + } + } + + private List<Command> newCommands(List<String> refTypes, List<String> dataTypes) { + List<Command> referenceCommands = refTypes.stream() + .flatMap(refType -> getReferenceCommands(refType).stream()) + .toList(); + List<Command> dataCommands = dataTypes.stream() + .flatMap(dataType -> getUploadDataCommands(dataType).stream()) + .toList(); + List<Command> commands = new LinkedList<>(); + commands.addAll(referenceCommands); + commands.addAll(dataCommands); + return commands; + } + + private List<Command> getUploadDataCommands(String dataType) { + Path dataDirectoryForDataType = Path.of(dataType); + List<Command> commands; + if (dataDirectoryForDataType.toFile().exists()) { + if (dataDirectoryForDataType.toFile().isDirectory()) { + SortedSet<Path> csvFilePaths = findCsvFilePathsInDirectory(dataDirectoryForDataType); + commands = csvFilePaths.stream() + .map(Path::toFile) + .map(dataFile -> newUploadDataCommand(dataType, dataFile)) + .toList(); + } else { + logError("le répertoire " + dataDirectoryForDataType + " est un fichier mais il devrait être un dossier. On l’ignore."); + commands = Collections.emptyList(); + } + } else { + log("le répertoire " + dataDirectoryForDataType + " n’existe pas. Pas de données à importer pour " + dataType); + commands = Collections.emptyList(); + } + return commands; + } + + private Command newUploadDataCommand(String dataType, File dataFile) { + return new Command() { + @Override + public String getDescription() { + return "Téléversement de %s pour alimenter le types de données %s".formatted(dataFile, dataType); + } + + @Override + public ClassicHttpRequest getRequest(UriFactory uriFactory) { + HttpPost httpPost = new HttpPost(uriFactory.forUploadingData(dataType)); + FileBody dataFileBody = new FileBody(dataFile); + HttpEntity reqEntity = MultipartEntityBuilder.create() + .addPart("file", dataFileBody) + .build(); + httpPost.setEntity(reqEntity); + return httpPost; + } + + @Override + public Void handleResponse(ClassicHttpResponse response) { + switch (response.getCode()) { + case HttpURLConnection.HTTP_CREATED -> { + log("import de %s terminé".formatted(dataFile)); + } + case HttpURLConnection.HTTP_BAD_REQUEST -> { + List<CsvRowValidationCheckResult> csvRowValidationCheckResults = + parseJsonInResponseBody( + response, + new TypeReference<List<CsvRowValidationCheckResult>>() {} + ); + logError(csvRowValidationCheckResults.toString()); + } + default -> fail( + "%d en code de retour HTTP inattendu à l’import des données %s avec le fichier %s" + .formatted(response.getCode(), dataFile, dataFile) + ); + } + return null; + } + }; + } + + private List<Command> getReferenceCommands(String refType) { + File refFile = new File(new File("references"), refType + ".csv"); + List<Command> commands; + if (refFile.exists()) { + Set<Path> csvFilePathsInDirectory; + if (refFile.isFile()) { + csvFilePathsInDirectory = Collections.singleton(refFile.toPath()); + } else if (refFile.isDirectory()) { + csvFilePathsInDirectory = findCsvFilePathsInDirectory(refFile.toPath()); + } else { + throw new IllegalStateException("ne comprend pas de quel type est " + refFile); + } + commands = csvFilePathsInDirectory.stream() + .map(path -> newUploadReferenceCommand(refType, path.toFile())) + .collect(Collectors.toList()); + } else { + logError("le fichier %s n’existe pas, on ignore l’import du référentiel %s".formatted(refFile, refType)); + commands = Collections.emptyList(); + } + return commands; + } + + private Command newUploadReferenceCommand(String refType, File refFile) { + return new Command() { + @Override + public String getDescription() { + return "Téléversement de %s pour alimenter le référentiel %s".formatted(refFile, refType); + } + + @Override + public ClassicHttpRequest getRequest(UriFactory uriFactory) { + HttpPost httpPost = new HttpPost(uriFactory.forUploadingReference(refType)); + FileBody refFileBody = new FileBody(refFile); + HttpEntity reqEntity = MultipartEntityBuilder.create() + .addPart("file", refFileBody) + .build(); + httpPost.setEntity(reqEntity); + return httpPost; + } + + @Override + public Void handleResponse(ClassicHttpResponse response) { + switch (response.getCode()) { + case HttpURLConnection.HTTP_CREATED -> { + String message = "import de " + refFile + " terminé"; + log(message); + } + case HttpURLConnection.HTTP_BAD_REQUEST -> { + List<CsvRowValidationCheckResult> csvRowValidationCheckResults = + parseJsonInResponseBody( + response, + new TypeReference<List<CsvRowValidationCheckResult>>() {} + ); + logError(csvRowValidationCheckResults.toString()); + } + default -> fail( + "%d en code de retour HTTP inattendu à l’import du référentiel %s avec le fichier %s" + .formatted(response.getCode(), refType, refFile) + ); + } + return null; + } + }; + } + + private SortedSet<Path> findCsvFilePathsInDirectory(Path directory) { + try { + AccumulatorPathVisitor accumulatorPathVisitor = new AccumulatorPathVisitor(Counters.longPathCounters()); + Files.walkFileTree(directory, accumulatorPathVisitor); + SortedSet<Path> csvFilePathsInDirectory = accumulatorPathVisitor.getFileList().stream() + .filter(path -> path.getFileName().toString().endsWith(".csv")) + .collect(Collectors.toCollection(TreeSet::new)); + return Collections.unmodifiableSortedSet(csvFilePathsInDirectory); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void fail(String message) { + logError(message); + System.exit(1); + } + + private static void logError(String string) { + System.err.println(string); + } + + private static void log(String message) { + System.out.println(message); + } + + private <T> T parseJsonInResponseBody(ClassicHttpResponse response, TypeReference<T> valueTypeRef) { + try (InputStream inputStream = response.getEntity().getContent()) { + T parsingResult = new ObjectMapper() + .readValue( + inputStream, + valueTypeRef + ); + return parsingResult; + } catch (DatabindException e) { + throw new RuntimeException(e); + } catch (StreamReadException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + record CsvRowValidationCheckResult( + int lineNumber, + Client.ValidationCheckResult validationCheckResult + ) { + } + + record ValidationCheckResult( + ValidationLevel level, + ValidationMessage message, + Map<String, Object> messageParams, + String target + ) { + } + + enum ValidationLevel { + SUCCESS, WARN, ERROR; + } + + enum ValidationMessage { + unexpectedHeaderColumn, + headerColumnPatternNotMatching, + unexpectedTokenCount, + invalidHeaders, + duplicatedHeaders, + emptyHeader + } + + record UriFactory(String openAdomInstanceUrl, String applicationName) { + + private URI newUri(String endpoint) { + try { + return new URI("%s/api/v1/%s".formatted(openAdomInstanceUrl, endpoint)); + } catch (URISyntaxException e) { + throw new RuntimeException("ne devrait pas arriver", e); + } + } + + public URI forLogin() { + return newUri("login"); + } + + public URI forUploadingReference(String refType) { + String endpoint = "applications/%s/references/%s".formatted(applicationName, refType); + return newUri(endpoint); + } + + public URI forUploadingData(String dataType) { + String endpoint = "applications/%s/data/%s".formatted(applicationName, dataType); + return newUri(endpoint); + } + + public URI forApplicationReferenceTypes(String applicationName) { + String endpoint = "applications/%s/references".formatted(applicationName); + return newUri(endpoint); + } + + public URI forApplicationDataTypes(String applicationName) { + String endpoint = "applications/%s/data".formatted(applicationName); + return newUri(endpoint); + } + } + + interface Command extends HttpClientResponseHandler<Void> { + + String getDescription(); + + ClassicHttpRequest getRequest(UriFactory uriFactory); + } +} \ No newline at end of file -- GitLab From 7ed4504e273b75e05f14567a47aeebee46ab730f Mon Sep 17 00:00:00 2001 From: philippe tcheriatinsky <philippe.tcherniatinsky@inrae.fr> Date: Wed, 12 Jun 2024 15:11:14 +0200 Subject: [PATCH 3/8] =?UTF-8?q?Bouton=20dans=20l'interface=20pour=20t?= =?UTF-8?q?=C3=A9l=C3=A9charger=20le=20kit=20de=20t=C3=A9l=C3=A9versement?= =?UTF-8?q?=20en=20masse?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DetailApplicationModalCard.vue | 141 ++++++++++-------- ui/src/composable/applications/useFunction.js | 6 + ui/src/locales/en.json | 1 + ui/src/locales/fr.json | 1 + ui/src/services/rest/ApplicationService.js | 4 + 5 files changed, 88 insertions(+), 65 deletions(-) diff --git a/ui/src/components/application/DetailApplicationModalCard.vue b/ui/src/components/application/DetailApplicationModalCard.vue index 33542631f..8e2246431 100644 --- a/ui/src/components/application/DetailApplicationModalCard.vue +++ b/ui/src/components/application/DetailApplicationModalCard.vue @@ -1,9 +1,9 @@ <template> <ModalCard - v-show="open || url" - :close-cb="closeCb" - :open="open || !!url" - :title="application && (application.localName || application.name)" + v-show="open || url" + :close-cb="closeCb" + :open="open || !!url" + :title="application && (application.localName || application.name)" > <div v-if="getUrl" id="charteValidate" class="modal-card" style="width: auto"> <header class="modal-card-head"> @@ -12,7 +12,7 @@ </p> </header> <div class="modal-card-body"> - <CharteValidator :href="getUrl" /> + <CharteValidator :href="getUrl"/> </div> <footer class="modal-card-foot"> <form id="validateForm"> @@ -26,8 +26,8 @@ <div class="card"> <div class="infos card-content"> <p - class="columns" - v-html=" + class="columns" + v-html=" $t('applications.version', { applicationName: application.localName, version: application.version, @@ -48,42 +48,51 @@ <b-tab-item aria-description="application"> <template #header> <span class="rubriqueTitle">{{ - $t("applications.functions.application-manage") - }}</span> + $t("applications.functions.application-manage") + }}</span> </template> <div v-if="canCreateApplication" class="buttonWarper"> <b-button - icon-left="pen-square" - type="is-warning" - @click="updateApplication(application.name, application.version)" + icon-left="pen-square" + type="is-warning" + @click="updateApplication(application.name, application.version)" > {{ $t("applications.change") }} </b-button> </div> <div v-if="canCreateApplication" class="buttonWarper"> <b-button - icon-left="download" - type="is-primary" - @click="downloadYamlApplication(application)" + icon-left="download" + type="is-primary" + @click="downloadApplicationUploadBundle(application)" + > + {{ $t("applications.downloadBundle") }} + </b-button> + </div> + <div v-if="canCreateApplication" class="buttonWarper"> + <b-button + icon-left="download" + type="is-primary" + @click="downloadYamlApplication(application)" > {{ $t("referencesManagement.download") }} </b-button> </div> <div class="buttonWarper"> <b-button - icon-left="download" - @click="charteHREF = downloadCharteApplication(application.id)" + icon-left="download" + @click="charteHREF = downloadCharteApplication(application.id)" > {{ $t("applications.functions.charte-consult") }} </b-button> </div> <div v-if="canCreateApplication" class="buttonWarper"> <b-upload - v-model="tmpCharte" - :validationMessage="$t('applications.functions.file-select')" - class="file-label" - required - @input="uploadCharteApplication(application)" + v-model="tmpCharte" + :validationMessage="$t('applications.functions.file-select')" + class="file-label" + required + @input="uploadCharteApplication(application)" > <span class="file-cta"> <b-icon class="file-icon" icon="upload"></b-icon> @@ -95,21 +104,21 @@ <b-tab-item aria-description="references"> <template #header> <span class="rubriqueTitle">{{ - $t("applications.functions.references-manage") - }}</span> + $t("applications.functions.references-manage") + }}</span> </template> <div class="buttonWarper"> <b-button - icon-left="drafting-compass" - @click="displayReferencesManagement(application.name)" - >{{ $t("applications.references") }} + icon-left="drafting-compass" + @click="displayReferencesManagement(application.name)" + >{{ $t("applications.references") }} </b-button> </div> <div class="buttonWarper"> <b-button - icon-left="drafting-compass" - @click="showReferenceRights(application.name)" - >{{ $t("applications.functions.references-rights") }} + icon-left="drafting-compass" + @click="showReferenceRights(application.name)" + >{{ $t("applications.functions.references-rights") }} </b-button> </div> </b-tab-item> @@ -119,35 +128,35 @@ </template> <div class="buttonWarper"> <b-button icon-left="poll" @click="displayDataSetManagement(application.name)" - >{{ $t("applications.dataset") }} + >{{ $t("applications.dataset") }} </b-button> </div> <div class="buttonWarper"> <b-button icon-left="drafting-compass" @click="showDataRights(application.name)" - >{{ $t("applications.functions.data-rights") }} + >{{ $t("applications.functions.data-rights") }} </b-button> </div> </b-tab-item> <b-tab-item aria-description="authorization"> <template #header> <span class="rubriqueTitle">{{ - $t("applications.functions.right-requests-manage") - }}</span> + $t("applications.functions.right-requests-manage") + }}</span> </template> <span v-if="!canCreateApplication" class="buttonWarper"> <b-button - icon-left="users-cog" - type="is-primary" - @click="showRequestRights(application.name)" + icon-left="users-cog" + type="is-primary" + @click="showRequestRights(application.name)" > {{ $t("dataTypeAuthorizations.showRequests") }} </b-button> </span> <div v-else class="buttonWarper"> <b-button - icon-left="users-cog" - type="is-primary" - @click="requestRights(application.name)" + icon-left="users-cog" + type="is-primary" + @click="requestRights(application.name)" > {{ $t("dataTypeAuthorizations.request") }} </b-button> @@ -156,27 +165,27 @@ <b-tab-item aria-description="additional files"> <template #header> <span class="rubriqueTitle">{{ - $t("applications.functions.additional-files-manage") - }}</span> + $t("applications.functions.additional-files-manage") + }}</span> </template> <div - v-if="application.additionalFile && application.additionalFile.length !== 0" - class="buttonWarper" + v-if="application.additionalFile && application.additionalFile.length !== 0" + class="buttonWarper" > <b-button - icon-left="file" - @click="displayAdditionalFilesManagement(application.name)" + icon-left="file" + @click="displayAdditionalFilesManagement(application.name)" > {{ $t("applications.additionalFile") }} </b-button> </div> <div - v-if="application.additionalFile && application.additionalFile.length !== 0" - class="buttonWarper" + v-if="application.additionalFile && application.additionalFile.length !== 0" + class="buttonWarper" > <b-button - icon-left="file" - @click="showAdditionalFilesManagementRights(application.name)" + icon-left="file" + @click="showAdditionalFilesManagementRights(application.name)" > {{ $t("applications.functions.additional-files-rights") }} </b-button> @@ -191,15 +200,15 @@ <script> import ModalCard from "@/components/charts/ModalCard"; -import { useRedirections } from "@/composable/applications/useFunction"; +import {useRedirections} from "@/composable/applications/useFunction"; import useText from "@/composable/components/text"; import CharteValidator from "@/components/application/CharteValidator.vue"; -import { computed } from "vue"; +import {computed} from "vue"; import services from "@/composable/services"; export default { name: "DetailApplicationModalCard", - components: { CharteValidator, ModalCard }, + components: {CharteValidator, ModalCard}, emits: ["setValidatedCharte"], props: { open: { @@ -240,9 +249,9 @@ export default { get() { const authenticatedUser = () => services.loginService.getAuthenticatedUser(); if ( - authenticatedUser() && - authenticatedUser().chartes && - authenticatedUser().chartes[props.application.id] + authenticatedUser() && + authenticatedUser().chartes && + authenticatedUser().chartes[props.application.id] ) { return new Date().getTime() > authenticatedUser().chartes[props.application.id]; } @@ -251,22 +260,23 @@ export default { set(bool) { const authenticatedUser = () => services.loginService.getAuthenticatedUser(); bool && - services.loginService.modifAcount({ - login: authenticatedUser().login, - email: authenticatedUser().email, - charte: props.application.id, - }); + services.loginService.modifAcount({ + login: authenticatedUser().login, + email: authenticatedUser().email, + charte: props.application.id, + }); bool && - ctx.emit("setValidatedCharte", { - applicationId: props.application.id, - validated: bool, - }); + ctx.emit("setValidatedCharte", { + applicationId: props.application.id, + validated: bool, + }); }, }); const { createApplication, updateApplication, downloadYamlApplication, + downloadApplicationUploadBundle, downloadCharteApplication, uploadCharteApplication, tmpCharte, @@ -289,6 +299,7 @@ export default { updateApplication, downloadYamlApplication, downloadCharteApplication, + downloadApplicationUploadBundle, charteHREF, getUrl, uploadCharteApplication, diff --git a/ui/src/composable/applications/useFunction.js b/ui/src/composable/applications/useFunction.js index 76b3856a0..43508f365 100644 --- a/ui/src/composable/applications/useFunction.js +++ b/ui/src/composable/applications/useFunction.js @@ -36,6 +36,12 @@ export function useRedirections(application = { authorizations: [] }) { return false; } + async function downloadApplicationUploadBundle(application) { + await (canCreateApplication() && + services.applicationService.downloadApplicationUploadBundle(application.name)); + return false; + } + /* forAll */ diff --git a/ui/src/locales/en.json b/ui/src/locales/en.json index 6cacdfe45..b3646ba6e 100644 --- a/ui/src/locales/en.json +++ b/ui/src/locales/en.json @@ -125,6 +125,7 @@ "create": "Create application", "creation-date": "Creation date", "dataset": "Data types", + "downloadBundle": "Download the mass-upload kit", "filter": "Filter by", "functions": { "additional-files-manage": "Additional files management", diff --git a/ui/src/locales/fr.json b/ui/src/locales/fr.json index 76f585558..9bafc01a7 100644 --- a/ui/src/locales/fr.json +++ b/ui/src/locales/fr.json @@ -125,6 +125,7 @@ "create": "Créer l'application", "creation-date": "Date de création", "dataset": "Données", + "downloadBundle": "Télécharger le kit de téléversement en masse", "filter": "Filtrer", "functions": { "additional-files-manage": "Gestion des fichiers additionnels", diff --git a/ui/src/services/rest/ApplicationService.js b/ui/src/services/rest/ApplicationService.js index 5337a71c0..052703ed0 100644 --- a/ui/src/services/rest/ApplicationService.js +++ b/ui/src/services/rest/ApplicationService.js @@ -43,4 +43,8 @@ export class ApplicationService extends Fetcher { async getValidateConfiguration() { return this.post("validate-configuration"); } + + async downloadApplicationUploadBundle(applicationName) { + return this.downloadFile(`applications/${applicationName}/upload-bundle`); + } } -- GitLab From c9037750f373a585abbee01d045c46568938e203 Mon Sep 17 00:00:00 2001 From: philippe tcheriatinsky <philippe.tcherniatinsky@inrae.fr> Date: Wed, 12 Jun 2024 15:32:42 +0200 Subject: [PATCH 4/8] =?UTF-8?q?Prototype=20du=20service=20de=20t=C3=A9l?= =?UTF-8?q?=C3=A9chargement=20du=20kit=20de=20t=C3=A9l=C3=A9versement=20en?= =?UTF-8?q?=20masse?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fr/inra/oresing/rest/OreSiResources.java | 30 +++++++++++++++++++ .../fr/inra/oresing/rest/OreSiService.java | 20 +++++++++++++ .../inra/oresing/rest/OreSiResourcesTest.java | 27 +++++++++++++++++ 3 files changed, 77 insertions(+) diff --git a/src/main/java/fr/inra/oresing/rest/OreSiResources.java b/src/main/java/fr/inra/oresing/rest/OreSiResources.java index f8b091679..9c314a654 100644 --- a/src/main/java/fr/inra/oresing/rest/OreSiResources.java +++ b/src/main/java/fr/inra/oresing/rest/OreSiResources.java @@ -56,6 +56,7 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.enums.Explode; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.ExampleObject; +import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.MapUtils; @@ -1038,4 +1039,33 @@ public class OreSiResources { @PathVariable("dataType") final String dataType) throws IOException { return buidSynthesis(nameOrId, dataType, null); } + + + @GetMapping(value = "/applications/{nameOrId}/upload-bundle", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) + public ResponseEntity<StreamingResponseBody> getUploadBundle( + @PathVariable("nameOrId") String nameOrId, + HttpServletRequest request, + HttpServletResponse response) { + String instanceUrl = "%s://%s:%s".formatted( + request.getScheme(), + request.getServerName(), + request.getServerPort() + ); + StreamingResponseBody streamResponseBody = out -> { + try (ZipOutputStream zipOutputStream = new ZipOutputStream(out, StandardCharsets.UTF_8)) { + service.writeUploadBundle(instanceUrl, nameOrId, zipOutputStream); + } catch (IOException ioe) { + log.error("error while generating upload bundle for " + nameOrId, ioe); + } + }; + response.setContentType("application/zip"); + String fileName = "%s-upload-bundle.zip".formatted(nameOrId); + response.setHeader("Content-Disposition", "attachment; filename=%s".formatted(fileName)); + response.addHeader("Pragma", "no-cache"); + response.addHeader("Expires", "0"); + response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); + return ResponseEntity.ok() + .contentType(MediaType.APPLICATION_OCTET_STREAM) + .body(streamResponseBody); + } } \ No newline at end of file diff --git a/src/main/java/fr/inra/oresing/rest/OreSiService.java b/src/main/java/fr/inra/oresing/rest/OreSiService.java index 8ac1fea3e..29dc5079d 100644 --- a/src/main/java/fr/inra/oresing/rest/OreSiService.java +++ b/src/main/java/fr/inra/oresing/rest/OreSiService.java @@ -74,6 +74,7 @@ import javax.annotation.Nullable; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.charset.StandardCharsets; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.*; @@ -81,6 +82,7 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; +import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @Slf4j @@ -1177,5 +1179,23 @@ public class OreSiService { return store; } + public void writeUploadBundle(String instanceUrl, String nameOrId, ZipOutputStream zipOutputStream) throws IOException { + Application application = applicationService.getApplication(nameOrId); + //application.getConfiguration().getReferences(); + zipOutputStream.putNextEntry(new ZipEntry("OpenAdomClient.groovy")); + zipOutputStream.write("TAISTE".getBytes(StandardCharsets.UTF_8)); + zipOutputStream.closeEntry(); + zipOutputStream.putNextEntry(new ZipEntry("openAdom-client-configuration.json")); + String configurationJson = """ + { + "instanceUrl": "%s", + "applicationName": "%s" + } + """.formatted(instanceUrl, nameOrId); + zipOutputStream.write(configurationJson.getBytes(StandardCharsets.UTF_8)); + zipOutputStream.closeEntry(); + zipOutputStream.putNextEntry(new ZipEntry("references/")); + } + } diff --git a/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java b/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java index 8d239465a..f137c04ee 100644 --- a/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java +++ b/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java @@ -3163,4 +3163,31 @@ on test le dépôt d'un fichier récursif log.debug(StringUtils.abbreviate(response, 50)); } } + + @Test + public void testGetUploadBundle() throws Exception { + addApplicationMonsore(); + mockMvc.perform(asyncDispatch(mockMvc.perform(get("/api/v1/applications/monsore/upload-bundle") + .accept(MediaType.APPLICATION_OCTET_STREAM) + .cookie(authCookie)) + .andExpect(status().is2xxSuccessful()) + .andExpect(request().asyncStarted()) + .andReturn())) + .andExpect(result -> { + List<ZipEntry> entries = new ArrayList<>(); + try (ZipInputStream zi = new ZipInputStream(new ByteArrayInputStream(result.getResponse().getContentAsByteArray()))) { + ZipEntry zipEntry; + while ((zipEntry = zi.getNextEntry()) != null) { + entries.add(zipEntry); + zi.readAllBytes(); + } + } + final List<String> entryNames = entries.stream() + .map(ZipEntry::getName) + .toList(); + Assertions.assertTrue(() -> entryNames.contains("OpenAdomClient.groovy")); + Assertions.assertTrue(() -> entryNames.contains("openAdom-client-configuration.json")); + Assertions.assertTrue(() -> entryNames.contains("references/")); + }); + } } -- GitLab From 5e474c7248658a648cf35d2f99d9881a6cf3891c Mon Sep 17 00:00:00 2001 From: philippe tcheriatinsky <philippe.tcherniatinsky@inrae.fr> Date: Wed, 12 Jun 2024 15:42:12 +0200 Subject: [PATCH 5/8] =?UTF-8?q?Am=C3=A9liore=20le=20client?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/fr/inra/oresing/client/Client.java | 72 ++++++++++++++----- 1 file changed, 53 insertions(+), 19 deletions(-) diff --git a/src/main/java/fr/inra/oresing/client/Client.java b/src/main/java/fr/inra/oresing/client/Client.java index d09f59f65..2d776da0f 100644 --- a/src/main/java/fr/inra/oresing/client/Client.java +++ b/src/main/java/fr/inra/oresing/client/Client.java @@ -40,19 +40,22 @@ import java.util.stream.Collectors; public class Client { - public static void main(String[] args) { + public static void main(String[] args) throws IOException { new Client().run(); } - public void run() { - String applicationName = "foret"; - String openAdomInstanceUrl = "http://localhost:8081"; + public void run() throws IOException { + + ClientConfiguration clientConfiguration = readConfiguration(); + + String applicationName = clientConfiguration.applicationName(); + URI instanceUrl = clientConfiguration.instanceUrl(); String login; String password; - boolean interactive = false; + boolean interactive = true; Scanner scanner = new Scanner(System.in); if (interactive) { - System.out.println("Veuillez saisir les informations de connexion à " + openAdomInstanceUrl); + System.out.println("Veuillez saisir les informations de connexion à " + instanceUrl); System.out.print("identifiant : "); login = scanner.nextLine(); System.out.print("mot de passe : "); @@ -64,7 +67,7 @@ public class Client { CookieStore cookieStore = new BasicCookieStore(); - UriFactory uriFactory = new UriFactory(openAdomInstanceUrl, applicationName); + UriFactory uriFactory = new UriFactory(instanceUrl, applicationName); try (CloseableHttpClient httpclient = HttpClients.custom() .setDefaultCookieStore(cookieStore) @@ -110,18 +113,19 @@ public class Client { case HttpURLConnection.HTTP_OK -> { return parseJsonInResponseBody( response, - new TypeReference<List<String>>() {} + new TypeReference<List<String>>() { + } ); } case HttpURLConnection.HTTP_UNAUTHORIZED -> fail("pas l’autorisation pour l’application " + applicationName); - default -> - fail("%d en code de retour HTTP inattendu".formatted(response.getCode())); + default -> fail("%d en code de retour HTTP inattendu".formatted(response.getCode())); } return null; }); - log("référentiels à importer :%n" + String.join(System.lineSeparator(), refTypes)); + log("référentiels à importer :" + System.lineSeparator() + + String.join(System.lineSeparator(), refTypes)); ClassicHttpRequest getApplicationDataTypesRequest = ClassicRequestBuilder .get(uriFactory.forApplicationDataTypes(applicationName)) @@ -132,18 +136,19 @@ public class Client { case HttpURLConnection.HTTP_OK -> { return parseJsonInResponseBody( response, - new TypeReference<List<String>>() {} + new TypeReference<List<String>>() { + } ); } case HttpURLConnection.HTTP_UNAUTHORIZED -> fail("pas l’autorisation pour l’application " + applicationName); - default -> - fail("%d en code de retour HTTP inattendu".formatted(response.getCode())); + default -> fail("%d en code de retour HTTP inattendu".formatted(response.getCode())); } return null; }); - log("données expérimentales à importer :%n" + String.join(System.lineSeparator(), dataTypes)); + log("données expérimentales à importer :" + System.lineSeparator() + + String.join(System.lineSeparator(), dataTypes)); List<Command> commands = newCommands(refTypes, dataTypes); @@ -172,6 +177,16 @@ public class Client { } } + private ClientConfiguration readConfiguration() throws IOException { + File configurationFile = new File("openAdom-client-configuration.json"); + ClientConfiguration clientConfiguration = new ObjectMapper() + .readValue( + configurationFile, + ClientConfiguration.class + ); + return clientConfiguration; + } + private List<Command> newCommands(List<String> refTypes, List<String> dataTypes) { List<Command> referenceCommands = refTypes.stream() .flatMap(refType -> getReferenceCommands(refType).stream()) @@ -234,7 +249,8 @@ public class Client { List<CsvRowValidationCheckResult> csvRowValidationCheckResults = parseJsonInResponseBody( response, - new TypeReference<List<CsvRowValidationCheckResult>>() {} + new TypeReference<List<CsvRowValidationCheckResult>>() { + } ); logError(csvRowValidationCheckResults.toString()); } @@ -299,7 +315,8 @@ public class Client { List<CsvRowValidationCheckResult> csvRowValidationCheckResults = parseJsonInResponseBody( response, - new TypeReference<List<CsvRowValidationCheckResult>>() {} + new TypeReference<List<CsvRowValidationCheckResult>>() { + } ); logError(csvRowValidationCheckResults.toString()); } @@ -383,11 +400,14 @@ public class Client { emptyHeader } - record UriFactory(String openAdomInstanceUrl, String applicationName) { + /** + * Les routes disponibles sur le serveur. + */ + record UriFactory(URI instanceUrl, String applicationName) { private URI newUri(String endpoint) { try { - return new URI("%s/api/v1/%s".formatted(openAdomInstanceUrl, endpoint)); + return new URI("%s/api/v1/%s".formatted(instanceUrl, endpoint)); } catch (URISyntaxException e) { throw new RuntimeException("ne devrait pas arriver", e); } @@ -418,10 +438,24 @@ public class Client { } } + + /** + * Une étape du téléversement soit un fichier à téléverser, une requête HTTP et comment traiter la réponse. + */ interface Command extends HttpClientResponseHandler<Void> { String getDescription(); ClassicHttpRequest getRequest(UriFactory uriFactory); } + + /** + * Le contenu du fichier de configuration du client. + * + * @param instanceUrl l’adresse du serveur au format "http://hote:port" + * @param applicationName le nom de l’application + */ + private record ClientConfiguration(URI instanceUrl, String applicationName) { + + } } \ No newline at end of file -- GitLab From 3ae2b54a7aa764cb052a9529552361644a2c39da Mon Sep 17 00:00:00 2001 From: philippe tcheriatinsky <philippe.tcherniatinsky@inrae.fr> Date: Wed, 12 Jun 2024 16:11:31 +0200 Subject: [PATCH 6/8] =?UTF-8?q?Inclus=20le=20vrai=20groovy=20dans=20le=20k?= =?UTF-8?q?it=20de=20t=C3=A9l=C3=A9chargement=20en=20masse?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/fr/inra/oresing/client/Client.java | 75 +-- .../fr/inra/oresing/rest/OreSiResources.java | 7 +- .../fr/inra/oresing/rest/OreSiService.java | 34 +- .../inra/oresing/client/OpenAdomClient.groovy | 435 ++++++++++++++++++ .../inra/oresing/rest/OreSiResourcesTest.java | 17 +- 5 files changed, 523 insertions(+), 45 deletions(-) create mode 100644 src/main/resources/fr/inra/oresing/client/OpenAdomClient.groovy diff --git a/src/main/java/fr/inra/oresing/client/Client.java b/src/main/java/fr/inra/oresing/client/Client.java index 2d776da0f..24100b522 100644 --- a/src/main/java/fr/inra/oresing/client/Client.java +++ b/src/main/java/fr/inra/oresing/client/Client.java @@ -38,6 +38,14 @@ import java.util.SortedSet; import java.util.TreeSet; import java.util.stream.Collectors; +/** + * Cette classe n’est pas utile en soi, elle a vocation à être portée en Groovy pour + * devenir le script qui est inclus dans le kit de téléversement. + */ +// à ajouter en tête du fichier Groovy +// +//@Grab(group = "commons-io", module = "commons-io", version = "2.8.0") +//@Grab(group = "org.apache.httpcomponents.client5", module = "httpclient5", version = "5.2.1") public class Client { public static void main(String[] args) throws IOException { @@ -50,6 +58,7 @@ public class Client { String applicationName = clientConfiguration.applicationName(); URI instanceUrl = clientConfiguration.instanceUrl(); + String login; String password; boolean interactive = true; @@ -66,7 +75,6 @@ public class Client { } CookieStore cookieStore = new BasicCookieStore(); - UriFactory uriFactory = new UriFactory(instanceUrl, applicationName); try (CloseableHttpClient httpclient = HttpClients.custom() @@ -108,21 +116,20 @@ public class Client { .get(uriFactory.forApplicationReferenceTypes(applicationName)) .build(); - List<String> refTypes = httpclient.execute(getApplicationReferenceTypesRequest, response -> { - switch (response.getCode()) { - case HttpURLConnection.HTTP_OK -> { - return parseJsonInResponseBody( - response, - new TypeReference<List<String>>() { - } - ); + List<String> refTypes = httpclient.execute(getApplicationReferenceTypesRequest, response -> + switch (response.getCode()) { + case HttpURLConnection.HTTP_OK -> + parseJsonInResponseBody( + response, + new TypeReference<List<String>>() { + } + ); + case HttpURLConnection.HTTP_UNAUTHORIZED -> + throw new IllegalStateException("pas l’autorisation pour l’application " + applicationName); + default -> + throw new IllegalStateException("%d en code de retour HTTP inattendu".formatted(response.getCode())); } - case HttpURLConnection.HTTP_UNAUTHORIZED -> - fail("pas l’autorisation pour l’application " + applicationName); - default -> fail("%d en code de retour HTTP inattendu".formatted(response.getCode())); - } - return null; - }); + ); log("référentiels à importer :" + System.lineSeparator() + String.join(System.lineSeparator(), refTypes)); @@ -131,21 +138,18 @@ public class Client { .get(uriFactory.forApplicationDataTypes(applicationName)) .build(); - List<String> dataTypes = httpclient.execute(getApplicationDataTypesRequest, response -> { - switch (response.getCode()) { - case HttpURLConnection.HTTP_OK -> { - return parseJsonInResponseBody( - response, - new TypeReference<List<String>>() { - } - ); - } - case HttpURLConnection.HTTP_UNAUTHORIZED -> - fail("pas l’autorisation pour l’application " + applicationName); - default -> fail("%d en code de retour HTTP inattendu".formatted(response.getCode())); - } - return null; - }); + List<String> dataTypes = httpclient.execute(getApplicationDataTypesRequest, response -> + switch (response.getCode()) { + case HttpURLConnection.HTTP_OK -> + parseJsonInResponseBody( + response, + new TypeReference<List<String>>() {} + ); + case HttpURLConnection.HTTP_UNAUTHORIZED -> + throw new IllegalStateException("pas l’autorisation pour l’application " + applicationName); + default -> + throw new IllegalStateException("%d en code de retour HTTP inattendu".formatted(response.getCode())); + }); log("données expérimentales à importer :" + System.lineSeparator() + String.join(System.lineSeparator(), dataTypes)); @@ -249,8 +253,7 @@ public class Client { List<CsvRowValidationCheckResult> csvRowValidationCheckResults = parseJsonInResponseBody( response, - new TypeReference<List<CsvRowValidationCheckResult>>() { - } + new TypeReference<List<CsvRowValidationCheckResult>>() {} ); logError(csvRowValidationCheckResults.toString()); } @@ -315,8 +318,7 @@ public class Client { List<CsvRowValidationCheckResult> csvRowValidationCheckResults = parseJsonInResponseBody( response, - new TypeReference<List<CsvRowValidationCheckResult>>() { - } + new TypeReference<List<CsvRowValidationCheckResult>>() {} ); logError(csvRowValidationCheckResults.toString()); } @@ -438,7 +440,6 @@ public class Client { } } - /** * Une étape du téléversement soit un fichier à téléverser, une requête HTTP et comment traiter la réponse. */ @@ -452,10 +453,10 @@ public class Client { /** * Le contenu du fichier de configuration du client. * - * @param instanceUrl l’adresse du serveur au format "http://hote:port" + * @param instanceUrl l’adresse du serveur au format "http://hote:port" * @param applicationName le nom de l’application */ private record ClientConfiguration(URI instanceUrl, String applicationName) { } -} \ No newline at end of file +} diff --git a/src/main/java/fr/inra/oresing/rest/OreSiResources.java b/src/main/java/fr/inra/oresing/rest/OreSiResources.java index 9c314a654..d61b6673b 100644 --- a/src/main/java/fr/inra/oresing/rest/OreSiResources.java +++ b/src/main/java/fr/inra/oresing/rest/OreSiResources.java @@ -326,12 +326,13 @@ public class OreSiResources { * Liste les noms des types de referenciels disponible * * @param nameOrId l'id ou le nom de l'application - * @return un tableau de chaine + * @return les noms triés selon l’ordre dans lequel il faut faire les imports (selon les dépendances entre + * référentiels). */ @GetMapping(value = "/applications/{nameOrId}/references", produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity<List<String>> listNameReferences(@PathVariable("nameOrId") final String nameOrId) { + public ResponseEntity<Set<String>> listNameReferences(@PathVariable("nameOrId") String nameOrId) { final Application application = applicationService.getApplication(nameOrId); - return ResponseEntity.ok(application.getData()); + return ResponseEntity.ok(application.getConfiguration().dataDescription().keySet()); } /** diff --git a/src/main/java/fr/inra/oresing/rest/OreSiService.java b/src/main/java/fr/inra/oresing/rest/OreSiService.java index 29dc5079d..cb758ca06 100644 --- a/src/main/java/fr/inra/oresing/rest/OreSiService.java +++ b/src/main/java/fr/inra/oresing/rest/OreSiService.java @@ -3,7 +3,9 @@ package fr.inra.oresing.rest; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.collect.*; +import com.google.common.io.Resources; import com.google.common.primitives.Ints; +import fr.inra.oresing.client.Client; import fr.inra.oresing.domain.*; import fr.inra.oresing.domain.additionalfiles.AdditionalBinaryFile; import fr.inra.oresing.domain.additionalfiles.AdditionalFilesInfos; @@ -74,6 +76,7 @@ import javax.annotation.Nullable; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.net.URL; import java.nio.charset.StandardCharsets; import java.time.LocalDate; import java.time.LocalDateTime; @@ -1181,9 +1184,11 @@ public class OreSiService { public void writeUploadBundle(String instanceUrl, String nameOrId, ZipOutputStream zipOutputStream) throws IOException { Application application = applicationService.getApplication(nameOrId); - //application.getConfiguration().getReferences(); - zipOutputStream.putNextEntry(new ZipEntry("OpenAdomClient.groovy")); - zipOutputStream.write("TAISTE".getBytes(StandardCharsets.UTF_8)); + String groovyScriptFileName = "OpenAdomClient.groovy"; + zipOutputStream.putNextEntry(new ZipEntry(groovyScriptFileName)); + URL scriptAsResource = Resources.getResource(Client.class, groovyScriptFileName); + byte[] scriptAsBytes = Resources.toByteArray(scriptAsResource); + zipOutputStream.write(scriptAsBytes); zipOutputStream.closeEntry(); zipOutputStream.putNextEntry(new ZipEntry("openAdom-client-configuration.json")); String configurationJson = """ @@ -1194,7 +1199,28 @@ public class OreSiService { """.formatted(instanceUrl, nameOrId); zipOutputStream.write(configurationJson.getBytes(StandardCharsets.UTF_8)); zipOutputStream.closeEntry(); - zipOutputStream.putNextEntry(new ZipEntry("references/")); + zipOutputStream.putNextEntry(new ZipEntry("LISEZ-MOI.txt")); + String readmeContent = """ + Instructions : + + 1. installer Groovy version 4 minimum https://groovy.apache.org/download.html#osinstall + + 2. vérifier que Groovy fonctionne en lançant groovy --version + + Exemple de retour correct : + Groovy Version: 4.0.15 JVM: 17.0.8.1 Vendor: Private Build OS: Linux + + 3. lancer le script d’import en masse + + groovy %s + """.formatted(groovyScriptFileName); + zipOutputStream.write(readmeContent.getBytes(StandardCharsets.UTF_8)); + zipOutputStream.closeEntry(); + for (String reference : application.getConfiguration().dataDescription().keySet()) { + String referenceCsvFilePath = "references/%s.csv".formatted(reference); + zipOutputStream.putNextEntry(new ZipEntry(referenceCsvFilePath)); + zipOutputStream.closeEntry(); + } } diff --git a/src/main/resources/fr/inra/oresing/client/OpenAdomClient.groovy b/src/main/resources/fr/inra/oresing/client/OpenAdomClient.groovy new file mode 100644 index 000000000..175ce3005 --- /dev/null +++ b/src/main/resources/fr/inra/oresing/client/OpenAdomClient.groovy @@ -0,0 +1,435 @@ +package fr.inra.oresing.client; + +import com.fasterxml.jackson.core.exc.StreamReadException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DatabindException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.io.file.AccumulatorPathVisitor; +import org.apache.commons.io.file.Counters; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.cookie.BasicCookieStore; +import org.apache.hc.client5.http.cookie.CookieStore; +import org.apache.hc.client5.http.entity.mime.FileBody; +import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.core5.http.ClassicHttpRequest; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.io.HttpClientResponseHandler; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.hc.core5.http.io.support.ClassicRequestBuilder +import java.net.HttpURLConnection +import java.io.InputStream + +import java.nio.file.Files; +import java.nio.file.Path +import java.util.stream.Collectors; + +@Grab(group = "commons-io", module = "commons-io", version = "2.8.0") +@Grab(group = "org.apache.httpcomponents.client5", module = "httpclient5", version = "5.2.1") + +ClientConfiguration clientConfiguration = readConfiguration(); + +String applicationName = clientConfiguration.applicationName(); +URI instanceUrl = clientConfiguration.instanceUrl(); + +String login; +String password; +boolean interactive = true; +Scanner scanner = new Scanner(System.in); +if (interactive) { + System.out.println("Veuillez saisir les informations de connexion à " + instanceUrl); + System.out.print("identifiant : "); + login = scanner.nextLine(); + System.out.print("mot de passe : "); + password = scanner.nextLine(); +} else { + login = "poussin"; + password = "xxxx"; +} + +CookieStore cookieStore = new BasicCookieStore(); +UriFactory uriFactory = new UriFactory(instanceUrl, applicationName); + +try (CloseableHttpClient httpclient = HttpClients.custom() + .setDefaultCookieStore(cookieStore) + .build()) { + + ClassicHttpRequest loginRequest = ClassicRequestBuilder.post() + .setUri(uriFactory.forLogin()) + .addParameter("login", login) + .addParameter("password", password) + .build(); + + httpclient.execute(loginRequest, response -> { + switch (response.getCode()) { + case HttpURLConnection.HTTP_OK -> { + EntityUtils.consume(response.getEntity()); + switch (cookieStore.getCookies().size()) { + case 0 -> fail("authentification échouée : pas de cookie d’authentification retourné"); + case 1 -> { + if (cookieStore.getCookies().get(0).getName().equals("si-ore-jwt")) { + log("authentification OK"); + } else { + fail("authentification échouée : pas de cookie d’authentification retourné"); + } + } + default -> + fail(cookieStore.getCookies().size() + " cookies retournés à l’authentification"); + } + } + case HttpURLConnection.HTTP_UNAUTHORIZED -> + fail("authentification échouée : identifiant ou mot de passe faux ?"); + default -> + fail("%d en code de retour HTTP inattendu à l’authentification".formatted(response.getCode())); + } + return null; + }); + + ClassicHttpRequest getApplicationReferenceTypesRequest = ClassicRequestBuilder + .get(uriFactory.forApplicationReferenceTypes(applicationName)) + .build(); + + List<String> refTypes = httpclient.execute(getApplicationReferenceTypesRequest, response -> + switch (response.getCode()) { + case HttpURLConnection.HTTP_OK -> + parseJsonInResponseBody( + response, + new TypeReference<List<String>>() { + } + ); + case HttpURLConnection.HTTP_UNAUTHORIZED -> + throw new IllegalStateException("pas l’autorisation pour l’application " + applicationName); + default -> + throw new IllegalStateException("%d en code de retour HTTP inattendu".formatted(response.getCode())); + } + ); + + log("référentiels à importer :" + System.lineSeparator() + + String.join(System.lineSeparator(), refTypes)); + + ClassicHttpRequest getApplicationDataTypesRequest = ClassicRequestBuilder + .get(uriFactory.forApplicationDataTypes(applicationName)) + .build(); + + List<String> dataTypes = httpclient.execute(getApplicationDataTypesRequest, response -> + switch (response.getCode()) { + case HttpURLConnection.HTTP_OK -> + parseJsonInResponseBody( + response, + new TypeReference<List<String>>() {} + ); + case HttpURLConnection.HTTP_UNAUTHORIZED -> + throw new IllegalStateException("pas l’autorisation pour l’application " + applicationName); + default -> + throw new IllegalStateException("%d en code de retour HTTP inattendu".formatted(response.getCode())); + }); + + log("données expérimentales à importer :" + System.lineSeparator() + + String.join(System.lineSeparator(), dataTypes)); + + List<Command> commands = newCommands(refTypes, dataTypes); + + if (interactive) { + String plan = commands.stream() + .map(Command::getDescription) + .collect(Collectors.joining(System.lineSeparator())); + log("Plan :"); + log(plan); + System.out.print("est-ce que le plan convient ? [O/n]"); + String planIsOkString = scanner.nextLine(); + boolean planIsOk = Set.of("o", "oui", "").contains(planIsOkString.toLowerCase()); + if (!planIsOk) { + fail("Abandon"); + } + } + + for (Command command : commands) { + log("va traiter " + command.getDescription()); + ClassicHttpRequest request = command.getRequest(uriFactory); + httpclient.execute(request, command); + log("a traité " + command.getDescription()); + } +} catch (IOException e) { + fail("Erreur réseau HTTP : " + e.getMessage()); +} + +private ClientConfiguration readConfiguration() throws IOException { + File configurationFile = new File("openAdom-client-configuration.json"); + ClientConfiguration clientConfiguration = new ObjectMapper() + .readValue( + configurationFile, + ClientConfiguration.class + ); + return clientConfiguration; +} + +private List<Command> newCommands(List<String> refTypes, List<String> dataTypes) { + List<Command> referenceCommands = refTypes.stream() + .flatMap(refType -> getReferenceCommands(refType).stream()) + .toList(); + List<Command> dataCommands = dataTypes.stream() + .flatMap(dataType -> getUploadDataCommands(dataType).stream()) + .toList(); + List<Command> commands = new LinkedList<>(); + commands.addAll(referenceCommands); + commands.addAll(dataCommands); + return commands; +} + +private List<Command> getUploadDataCommands(String dataType) { + Path dataDirectoryForDataType = Path.of(dataType); + List<Command> commands; + if (dataDirectoryForDataType.toFile().exists()) { + if (dataDirectoryForDataType.toFile().isDirectory()) { + SortedSet<Path> csvFilePaths = findCsvFilePathsInDirectory(dataDirectoryForDataType); + commands = csvFilePaths.stream() + .map(Path::toFile) + .map(dataFile -> newUploadDataCommand(dataType, dataFile)) + .toList(); + } else { + logError("le répertoire " + dataDirectoryForDataType + " est un fichier mais il devrait être un dossier. On l’ignore."); + commands = Collections.emptyList(); + } + } else { + log("le répertoire " + dataDirectoryForDataType + " n’existe pas. Pas de données à importer pour " + dataType); + commands = Collections.emptyList(); + } + return commands; +} + +private Command newUploadDataCommand(String dataType, File dataFile) { + return new Command() { + @Override + String getDescription() { + return "Téléversement de %s pour alimenter le types de données %s".formatted(dataFile, dataType); + } + + @Override + ClassicHttpRequest getRequest(UriFactory uriFactory) { + HttpPost httpPost = new HttpPost(uriFactory.forUploadingData(dataType)); + FileBody dataFileBody = new FileBody(dataFile); + HttpEntity reqEntity = MultipartEntityBuilder.create() + .addPart("file", dataFileBody) + .build(); + httpPost.setEntity(reqEntity); + return httpPost; + } + + @Override + Void handleResponse(ClassicHttpResponse response) { + switch (response.getCode()) { + case HttpURLConnection.HTTP_CREATED -> { + log("import de %s terminé".formatted(dataFile)); + } + case HttpURLConnection.HTTP_BAD_REQUEST -> { + List<CsvRowValidationCheckResult> csvRowValidationCheckResults = + parseJsonInResponseBody( + response, + new TypeReference<List<CsvRowValidationCheckResult>>() {} + ); + logError(csvRowValidationCheckResults.toString()); + } + default -> fail( + "%d en code de retour HTTP inattendu à l’import des données %s avec le fichier %s" + .formatted(response.getCode(), dataFile, dataFile) + ); + } + return null; + } + }; +} + +private List<Command> getReferenceCommands(String refType) { + File refFile = new File(new File("references"), refType + ".csv"); + List<Command> commands; + if (refFile.exists()) { + Set<Path> csvFilePathsInDirectory; + if (refFile.isFile()) { + csvFilePathsInDirectory = Collections.singleton(refFile.toPath()); + } else if (refFile.isDirectory()) { + csvFilePathsInDirectory = findCsvFilePathsInDirectory(refFile.toPath()); + } else { + throw new IllegalStateException("ne comprend pas de quel type est " + refFile); + } + commands = csvFilePathsInDirectory.stream() + .map(path -> newUploadReferenceCommand(refType, path.toFile())) + .collect(Collectors.toList()); + } else { + logError("le fichier %s n’existe pas, on ignore l’import du référentiel %s".formatted(refFile, refType)); + commands = Collections.emptyList(); + } + return commands; +} + +private Command newUploadReferenceCommand(String refType, File refFile) { + return new Command() { + @Override + String getDescription() { + return "Téléversement de %s pour alimenter le référentiel %s".formatted(refFile, refType); + } + + @Override + ClassicHttpRequest getRequest(UriFactory uriFactory) { + HttpPost httpPost = new HttpPost(uriFactory.forUploadingReference(refType)); + FileBody refFileBody = new FileBody(refFile); + HttpEntity reqEntity = MultipartEntityBuilder.create() + .addPart("file", refFileBody) + .build(); + httpPost.setEntity(reqEntity); + return httpPost; + } + + @Override + Void handleResponse(ClassicHttpResponse response) { + switch (response.getCode()) { + case HttpURLConnection.HTTP_CREATED -> { + String message = "import de " + refFile + " terminé"; + log(message); + } + case HttpURLConnection.HTTP_BAD_REQUEST -> { + List<CsvRowValidationCheckResult> csvRowValidationCheckResults = + parseJsonInResponseBody( + response, + new TypeReference<List<CsvRowValidationCheckResult>>() {} + ); + logError(csvRowValidationCheckResults.toString()); + } + default -> fail( + "%d en code de retour HTTP inattendu à l’import du référentiel %s avec le fichier %s" + .formatted(response.getCode(), refType, refFile) + ); + } + return null; + } + }; +} + +private SortedSet<Path> findCsvFilePathsInDirectory(Path directory) { + try { + AccumulatorPathVisitor accumulatorPathVisitor = new AccumulatorPathVisitor(Counters.longPathCounters()); + Files.walkFileTree(directory, accumulatorPathVisitor); + SortedSet<Path> csvFilePathsInDirectory = accumulatorPathVisitor.getFileList().stream() + .filter(path -> path.getFileName().toString().endsWith(".csv")) + .collect(Collectors.toCollection(TreeSet::new)); + return Collections.unmodifiableSortedSet(csvFilePathsInDirectory); + } catch (IOException e) { + throw new RuntimeException(e); + } +} + +private void fail(String message) { + logError(message); + System.exit(1); +} + +private static void logError(String string) { + System.err.println(string); +} + +private static void log(String message) { + System.out.println(message); +} + +private <T> T parseJsonInResponseBody(ClassicHttpResponse response, TypeReference<T> valueTypeRef) { + try (InputStream inputStream = response.getEntity().getContent()) { + T parsingResult = new ObjectMapper() + .readValue( + inputStream, + valueTypeRef + ); + return parsingResult; + } catch (DatabindException e) { + throw new RuntimeException(e); + } catch (StreamReadException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } +} + +record CsvRowValidationCheckResult( + int lineNumber, + ValidationCheckResult validationCheckResult +) { +} + +record ValidationCheckResult( + ValidationLevel level, + ValidationMessage message, + Map<String, Object> messageParams, + String target +) { +} + +enum ValidationLevel { + SUCCESS, WARN, ERROR; +} + +enum ValidationMessage { + unexpectedHeaderColumn, + headerColumnPatternNotMatching, + unexpectedTokenCount, + invalidHeaders, + duplicatedHeaders, + emptyHeader +} + +/** + * Les routes disponibles sur le serveur. + */ +record UriFactory(URI instanceUrl, String applicationName) { + + private URI newUri(String endpoint) { + try { + return new URI("%s/api/v1/%s".formatted(instanceUrl, endpoint)); + } catch (URISyntaxException e) { + throw new RuntimeException("ne devrait pas arriver", e); + } + } + + URI forLogin() { + return newUri("login"); + } + + URI forUploadingReference(String refType) { + String endpoint = "applications/%s/references/%s".formatted(applicationName, refType); + return newUri(endpoint); + } + + URI forUploadingData(String dataType) { + String endpoint = "applications/%s/data/%s".formatted(applicationName, dataType); + return newUri(endpoint); + } + + URI forApplicationReferenceTypes(String applicationName) { + String endpoint = "applications/%s/references".formatted(applicationName); + return newUri(endpoint); + } + + URI forApplicationDataTypes(String applicationName) { + String endpoint = "applications/%s/data".formatted(applicationName); + return newUri(endpoint); + } +} + +/** + * Une étape du téléversement soit un fichier à téléverser, une requête HTTP et comment traiter la réponse. + */ +interface Command extends HttpClientResponseHandler<Void> { + + String getDescription(); + + ClassicHttpRequest getRequest(UriFactory uriFactory); +} + +/** + * Le contenu du fichier de configuration du client. + * + * @param instanceUrl l’adresse du serveur au format "http://hote:port" + * @param applicationName le nom de l’application + */ +record ClientConfiguration(URI instanceUrl, String applicationName) { + +} diff --git a/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java b/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java index f137c04ee..dd0c33130 100644 --- a/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java +++ b/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java @@ -3174,6 +3174,9 @@ on test le dépôt d'un fichier récursif .andExpect(request().asyncStarted()) .andReturn())) .andExpect(result -> { +// try (FileOutputStream output = new FileOutputStream("/tmp/test.zip")) { +// IOUtils.write(result.getResponse().getContentAsByteArray(), output); +// } List<ZipEntry> entries = new ArrayList<>(); try (ZipInputStream zi = new ZipInputStream(new ByteArrayInputStream(result.getResponse().getContentAsByteArray()))) { ZipEntry zipEntry; @@ -3187,7 +3190,19 @@ on test le dépôt d'un fichier récursif .toList(); Assertions.assertTrue(() -> entryNames.contains("OpenAdomClient.groovy")); Assertions.assertTrue(() -> entryNames.contains("openAdom-client-configuration.json")); - Assertions.assertTrue(() -> entryNames.contains("references/")); + Assertions.assertTrue(() -> entryNames.contains("LISEZ-MOI.txt")); + Assertions.assertTrue(() -> entryNames.contains("references/pem.csv")); + Assertions.assertTrue(() -> entryNames.contains("references/sites.csv")); + Assertions.assertTrue(() -> entryNames.contains("references/projet.csv")); + Assertions.assertTrue(() -> entryNames.contains("references/themes.csv")); + Assertions.assertTrue(() -> entryNames.contains("references/unites.csv")); + Assertions.assertTrue(() -> entryNames.contains("references/especes.csv")); + Assertions.assertTrue(() -> entryNames.contains("references/variables.csv")); + Assertions.assertTrue(() -> entryNames.contains("references/type_de_sites.csv")); + Assertions.assertTrue(() -> entryNames.contains("references/type_de_fichiers.csv")); + Assertions.assertTrue(() -> entryNames.contains("references/site_theme_datatype.csv")); + Assertions.assertTrue(() -> entryNames.contains("references/valeurs_qualitatives.csv")); + Assertions.assertTrue(() -> entryNames.contains("references/variables_et_unites_par_types_de_donnees.csv")); }); } } -- GitLab From 0faceb49f91548956372f730358359a99ceac94f Mon Sep 17 00:00:00 2001 From: philippe tcheriatinsky <philippe.tcherniatinsky@inrae.fr> Date: Thu, 13 Jun 2024 09:10:08 +0200 Subject: [PATCH 7/8] =?UTF-8?q?G=C3=A9n=C3=A8re=20des=20dossiers=20pour=20?= =?UTF-8?q?chaque=20type=20de=20donn=C3=A9es=20exp=C3=A9rimentales=20plut?= =?UTF-8?q?=C3=B4t=20qu'un=20fichier=20dans=20le=20kit=20de=20t=C3=A9l?= =?UTF-8?q?=C3=A9chargement?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fr/inra/oresing/rest/OreSiService.java | 4 ++-- .../inra/oresing/rest/OreSiResourcesTest.java | 24 +++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/main/java/fr/inra/oresing/rest/OreSiService.java b/src/main/java/fr/inra/oresing/rest/OreSiService.java index cb758ca06..74ffab825 100644 --- a/src/main/java/fr/inra/oresing/rest/OreSiService.java +++ b/src/main/java/fr/inra/oresing/rest/OreSiService.java @@ -1217,8 +1217,8 @@ public class OreSiService { zipOutputStream.write(readmeContent.getBytes(StandardCharsets.UTF_8)); zipOutputStream.closeEntry(); for (String reference : application.getConfiguration().dataDescription().keySet()) { - String referenceCsvFilePath = "references/%s.csv".formatted(reference); - zipOutputStream.putNextEntry(new ZipEntry(referenceCsvFilePath)); + String dataCsvFilePath = "%s/".formatted(reference); + zipOutputStream.putNextEntry(new ZipEntry(dataCsvFilePath)); zipOutputStream.closeEntry(); } } diff --git a/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java b/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java index dd0c33130..59a76a25f 100644 --- a/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java +++ b/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java @@ -3191,18 +3191,18 @@ on test le dépôt d'un fichier récursif Assertions.assertTrue(() -> entryNames.contains("OpenAdomClient.groovy")); Assertions.assertTrue(() -> entryNames.contains("openAdom-client-configuration.json")); Assertions.assertTrue(() -> entryNames.contains("LISEZ-MOI.txt")); - Assertions.assertTrue(() -> entryNames.contains("references/pem.csv")); - Assertions.assertTrue(() -> entryNames.contains("references/sites.csv")); - Assertions.assertTrue(() -> entryNames.contains("references/projet.csv")); - Assertions.assertTrue(() -> entryNames.contains("references/themes.csv")); - Assertions.assertTrue(() -> entryNames.contains("references/unites.csv")); - Assertions.assertTrue(() -> entryNames.contains("references/especes.csv")); - Assertions.assertTrue(() -> entryNames.contains("references/variables.csv")); - Assertions.assertTrue(() -> entryNames.contains("references/type_de_sites.csv")); - Assertions.assertTrue(() -> entryNames.contains("references/type_de_fichiers.csv")); - Assertions.assertTrue(() -> entryNames.contains("references/site_theme_datatype.csv")); - Assertions.assertTrue(() -> entryNames.contains("references/valeurs_qualitatives.csv")); - Assertions.assertTrue(() -> entryNames.contains("references/variables_et_unites_par_types_de_donnees.csv")); + Assertions.assertTrue(() -> entryNames.contains("pem/")); + Assertions.assertTrue(() -> entryNames.contains("sites/")); + Assertions.assertTrue(() -> entryNames.contains("projet/")); + Assertions.assertTrue(() -> entryNames.contains("themes/")); + Assertions.assertTrue(() -> entryNames.contains("unites/")); + Assertions.assertTrue(() -> entryNames.contains("especes/")); + Assertions.assertTrue(() -> entryNames.contains("variables/")); + Assertions.assertTrue(() -> entryNames.contains("type_de_sites/")); + Assertions.assertTrue(() -> entryNames.contains("type_de_fichiers/")); + Assertions.assertTrue(() -> entryNames.contains("site_theme_datatype/")); + Assertions.assertTrue(() -> entryNames.contains("valeurs_qualitatives/")); + Assertions.assertTrue(() -> entryNames.contains("variables_et_unites_par_types_de_donnees/")); }); } } -- GitLab From e83e5cb2ef6307f2b7f8f3cce426df72d2f16aa7 Mon Sep 17 00:00:00 2001 From: philippe tcheriatinsky <philippe.tcherniatinsky@inrae.fr> Date: Thu, 13 Jun 2024 15:34:17 +0200 Subject: [PATCH 8/8] Modification de la section OA_fileName MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Modification des tests pour s'adapter à la nouvelle section Modification tu test monsoere en utilisant un nom différent monsoresimple --- .../ConfigurationSchemaNode.java | 5 +- .../SubmissionFileNameTypeExampleBuilder.java | 22 ++- .../configuration/type/FileNameType.java | 4 +- .../configuration/type/StaticMapType.java | 2 +- .../builder/SubmissionFileNameBuilder.java | 34 ++--- .../application/configuration/LtreeTest.java | 4 +- .../ApplicationConfigurationServiceTest.java | 16 +-- .../inra/oresing/rest/OreSiResourcesTest.java | 125 ++++++++---------- .../data/configuration/configuration.yaml | 8 +- .../configuration/data.result.example.json | 4 +- .../data/configuration/data.result.json | 4 +- .../configuration/data.result.monsore.json | 4 +- .../data/configuration/schemaExample.yaml | 6 +- .../data/monsore/monsore-with-repository.yaml | 8 +- src/test/resources/data/monsore/monsore.yaml | 2 +- .../data/validation/broken-fake-app.yaml | 6 +- 16 files changed, 123 insertions(+), 131 deletions(-) diff --git a/src/main/java/fr/inra/oresing/domain/application/configuration/ConfigurationSchemaNode.java b/src/main/java/fr/inra/oresing/domain/application/configuration/ConfigurationSchemaNode.java index ef4b2c995..e19cb150d 100644 --- a/src/main/java/fr/inra/oresing/domain/application/configuration/ConfigurationSchemaNode.java +++ b/src/main/java/fr/inra/oresing/domain/application/configuration/ConfigurationSchemaNode.java @@ -48,12 +48,13 @@ public class ConfigurationSchemaNode { public static final String OA_TOKEN = "OA_token"; public static final String OA_AUTHORIZATION_SCOPES = "OA_authorizationScope"; public static final String OA_AUTHORIZATION = "OA_authorizations"; - public static final String OA_START_DATE = "OA_startDate"; - public static final String OA_END_DATE = "OA_endDate"; + public static final String OA_START_DATE_MATCH_PATTERN = "__START_DATE__"; + public static final String OA_END_DATE_MATCH_PATTERN = "__END_DATE__"; public static final String OA_FILE_PATTERN = "OA_filePattern"; public static final String OA_COMPONENT = "OA_component"; public static final String OA_TIME_SCOPE = "OA_timeScope"; public static final String OA_REFERENCE_SCOPES = "OA_referenceScopes"; + public static final String OA_MATCH_PATTERN_SCOPES = "OA_matchPatternScopes"; public static final String OA_SUBMISSION_SCOPE = "OA_submissionScope"; public static final String OA_FILE_NAME = "OA_fileName"; public static final String OA_STRATEGY = "OA_strategy"; diff --git a/src/main/java/fr/inra/oresing/domain/application/configuration/examples/SubmissionFileNameTypeExampleBuilder.java b/src/main/java/fr/inra/oresing/domain/application/configuration/examples/SubmissionFileNameTypeExampleBuilder.java index cb43cc0fd..39d99841d 100644 --- a/src/main/java/fr/inra/oresing/domain/application/configuration/examples/SubmissionFileNameTypeExampleBuilder.java +++ b/src/main/java/fr/inra/oresing/domain/application/configuration/examples/SubmissionFileNameTypeExampleBuilder.java @@ -10,27 +10,25 @@ public class SubmissionFileNameTypeExampleBuilder { public static final FileNameType DATA_FILE_NAME = buildFileName( new StringType("(.*)_(.*)_(.*).csv", true), new CollectionType.ArrayType<>( - List.of(new StringType("dat_site", false)), + List.of( + new StringType("dat_site", false), + new StringType(ConfigurationSchemaNode.OA_START_DATE_MATCH_PATTERN, false), + new StringType(ConfigurationSchemaNode.OA_END_DATE_MATCH_PATTERN, false) + ), false, true, StringType.EMPTY_INSTANCE() - ), - new IntegerType(2), - new IntegerType(3) - ); + ) + ); private static FileNameType buildFileName( StringType fileNamePattern, - CollectionType.ArrayType<StringType> referenceScopeType, - IntegerType startDate, - IntegerType endDate + CollectionType.ArrayType<StringType> referenceScopeType ) { return new FileNameType( - new HashMap<>(){{ + new HashMap<>() {{ put(ConfigurationSchemaNode.OA_FILE_PATTERN, fileNamePattern); - put(ConfigurationSchemaNode.OA_REFERENCE_SCOPES, referenceScopeType); - put(ConfigurationSchemaNode.OA_START_DATE, startDate); - put(ConfigurationSchemaNode.OA_END_DATE, endDate); + put(ConfigurationSchemaNode.OA_MATCH_PATTERN_SCOPES, referenceScopeType); }} ); } diff --git a/src/main/java/fr/inra/oresing/domain/application/configuration/type/FileNameType.java b/src/main/java/fr/inra/oresing/domain/application/configuration/type/FileNameType.java index 684341768..dee15eb36 100644 --- a/src/main/java/fr/inra/oresing/domain/application/configuration/type/FileNameType.java +++ b/src/main/java/fr/inra/oresing/domain/application/configuration/type/FileNameType.java @@ -14,9 +14,7 @@ public record FileNameType(SectionBuilder sectionBuilder, Map<String, Configurat new LabelDescription(ConfigurationSchemaNode.OA_FILE_PATTERN, StringType.EMPTY_INSTANCE()) ) .withOptionalSections( - new LabelDescription(ConfigurationSchemaNode.OA_REFERENCE_SCOPES, StaticMapType.FILE_AUTHORIZATION_SCOPE().type), - new LabelDescription(ConfigurationSchemaNode.OA_START_DATE, IntegerType.EMPTY_INSTANCE()), - new LabelDescription(ConfigurationSchemaNode.OA_END_DATE, IntegerType.EMPTY_INSTANCE()) + new LabelDescription(ConfigurationSchemaNode.OA_MATCH_PATTERN_SCOPES, StaticMapType.FILE_MATCH_PATTERN_SCOPES().type) ); } public static FileNameType EMPTY_INSTANCE(){ diff --git a/src/main/java/fr/inra/oresing/domain/application/configuration/type/StaticMapType.java b/src/main/java/fr/inra/oresing/domain/application/configuration/type/StaticMapType.java index 5c3dd7bd4..f5bb6cc51 100644 --- a/src/main/java/fr/inra/oresing/domain/application/configuration/type/StaticMapType.java +++ b/src/main/java/fr/inra/oresing/domain/application/configuration/type/StaticMapType.java @@ -36,7 +36,7 @@ public class StaticMapType { - public static final StaticMapType FILE_AUTHORIZATION_SCOPE() { + public static final StaticMapType FILE_MATCH_PATTERN_SCOPES() { return new StaticMapType( new CollectionType.ArrayType<StringType>( List.of(), diff --git a/src/main/java/fr/inra/oresing/rest/model/configuration/builder/SubmissionFileNameBuilder.java b/src/main/java/fr/inra/oresing/rest/model/configuration/builder/SubmissionFileNameBuilder.java index 4b5951c04..ec54792ff 100644 --- a/src/main/java/fr/inra/oresing/rest/model/configuration/builder/SubmissionFileNameBuilder.java +++ b/src/main/java/fr/inra/oresing/rest/model/configuration/builder/SubmissionFileNameBuilder.java @@ -20,25 +20,29 @@ public record SubmissionFileNameBuilder(RootBuilder rootBuilder) { final String pattern = Optional.ofNullable(filenameNode.get(ConfigurationSchemaNode.OA_FILE_PATTERN)) .map(JsonNode::asText) .orElseThrow(() -> new IllegalArgumentException("MISSING_PATTERN_FOR_FILENAME_SUBMISSION")); - final List<String> referenceScope = Optional.ofNullable(filenameNode.get(ConfigurationSchemaNode.OA_REFERENCE_SCOPES)) + List<String> referenceScope = Optional.ofNullable(filenameNode.get(ConfigurationSchemaNode.OA_MATCH_PATTERN_SCOPES)) .map(j -> rootBuilder.getMapper().convertValue(j, List.class)) - .orElse(null); - for (final String key : referenceScope){ - if(!listReferenceScope.contains(key)) { + .orElse(List.of()); + Integer startDate = -1; + Integer endDate = -1; + for (int i = 0; i < referenceScope.size(); i++) { + String key = referenceScope.get(i); + if (ConfigurationSchemaNode.OA_START_DATE_MATCH_PATTERN.equals(key)) { + startDate = i + 1; + continue; + } + if (ConfigurationSchemaNode.OA_END_DATE_MATCH_PATTERN.equals(key)) { + endDate = i + 1; + continue; + } + if (!listReferenceScope.contains(key)) { rootBuilder.buildError(ConfigurationException.UNKNOWN_NAME_REFERENCE_SCOPE, Map.of( - "unknownAuthorizationScope", key, - "knownAuthorizationScope", listReferenceScope), - "OA_submission > OA_fileName > OA_referenceScopes > %1$s".formatted(key)); + "unknownAuthorizationScope", key, + "knownAuthorizationScope", listReferenceScope), + "OA_submission > OA_fileName > OA_referenceScopes > %1$s".formatted(key)); } } - final Integer startDate = Optional.ofNullable(filenameNode.get(ConfigurationSchemaNode.OA_START_DATE)) - .map(j -> j.get(ConfigurationSchemaNode.OA_TOKEN)) - .map(JsonNode::asInt) - .orElse(null); - final Integer endDate = Optional.ofNullable(filenameNode.get(ConfigurationSchemaNode.OA_END_DATE)) - .map(j -> j.get(ConfigurationSchemaNode.OA_TOKEN)) - .map(JsonNode::asInt) - .orElse(null); + referenceScope = referenceScope.stream().filter(element->!element.startsWith("__")).toList(); return new Parsing<Submission.SubmissionFileNameParsing>(i18n, new Submission.SubmissionFileNameParsing(pattern, referenceScope, startDate, endDate)); } } \ No newline at end of file diff --git a/src/test/java/fr/inra/oresing/domain/application/configuration/LtreeTest.java b/src/test/java/fr/inra/oresing/domain/application/configuration/LtreeTest.java index d5282cf17..8974d6d93 100644 --- a/src/test/java/fr/inra/oresing/domain/application/configuration/LtreeTest.java +++ b/src/test/java/fr/inra/oresing/domain/application/configuration/LtreeTest.java @@ -33,7 +33,7 @@ class LtreeTest { Assert.assertEquals(label, nk.getSql()); }*/ @ParameterizedTest(name = "{0} match an encodingString") - @ValueSource(strings = {"°","%",">","²","$","?","&","@","@","µ"}) + @ValueSource(strings = {"°","%",">","²","$","?","&","@","°","µ"}) void testIsEcodedString(String aSign){ String aChar = Ltree.fromUnescapedString(aSign).getSql(); Assert.assertTrue(Ltree.isEncodedString(aChar)); @@ -44,4 +44,4 @@ class LtreeTest { String aChar = Ltree.fromUnescapedString(aSign).getSql(); Assert.assertFalse(Ltree.isEncodedString(aChar)); } -} \ No newline at end of file +} diff --git a/src/test/java/fr/inra/oresing/rest/ApplicationConfigurationServiceTest.java b/src/test/java/fr/inra/oresing/rest/ApplicationConfigurationServiceTest.java index a8009269a..d5cf52615 100644 --- a/src/test/java/fr/inra/oresing/rest/ApplicationConfigurationServiceTest.java +++ b/src/test/java/fr/inra/oresing/rest/ApplicationConfigurationServiceTest.java @@ -1023,22 +1023,22 @@ public class ApplicationConfigurationServiceTest { @Test public void testUnknownNameAuthorizationScopeInFileNameInSubmission() { CONFIGURATION_INSTANCE.builder("testUnknownNameAuthorizationScopeInFileNameSubmission") - .withReplace(" OA_referenceScopes:\n" + - " - projet\n" + - " - site_bassin", - " OA_referenceScopes:\n" + - " - proj\n" + - " - site_bassin") + .withReplace(" OA_matchPatternScopes:\n" + + " - projet\n" + + " - site_bassin", + " OA_matchPatternScopes:\n" + + " - projet\n" + + " - site_bassine") .test(errors -> { assertEquals(1, errors.size() ); final ValidationError validationError = errors.get(0); assertEquals(ConfigurationException.UNKNOWN_NAME_REFERENCE_SCOPE.getMessage(), validationError.getMessage()); - assertEquals("proj", validationError.getParam("unknownAuthorizationScope")); + assertEquals("site_bassine", validationError.getParam("unknownAuthorizationScope")); final Set<String> expected = Arrays.stream(new String[]{"site_bassin", "projet"}) .collect(Collectors.toSet()); final Set<String> given = (Set<String>) validationError.getParam("knownAuthorizationScope"); assertEquals(expected, given); - assertEquals("OA_submission > OA_fileName > OA_referenceScopes > proj", validationError.getParam(("path"))); + assertEquals("OA_submission > OA_fileName > OA_referenceScopes > site_bassine", validationError.getParam(("path"))); }); } diff --git a/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java b/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java index 59a76a25f..3f93d9325 100644 --- a/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java +++ b/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java @@ -45,6 +45,7 @@ import org.json.JSONArray; import org.junit.Assert; import org.junit.jupiter.api.*; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureWebMvc; @@ -54,8 +55,12 @@ import org.springframework.http.MediaType; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.support.AnnotationConfigContextLoader; +import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultActions; @@ -222,17 +227,18 @@ public class OreSiResourcesTest { @Test @Tag("OTHERS_TEST") @Tag("SUITE") + @Tag("MONSOERE") public void addApplicationMonsore() throws Exception { final String appId; - CreateUserResult monsoereUser = createUserIfNotExists("monsore", "xxxxxxxx", "monsore@inrae.fr"); + CreateUserResult monsoereUser = createUserIfNotExists("monsoresimple", "xxxxxxxx", "monsoresimple@inrae.fr"); setToActive(monsoereUser.userId()); final UUID monsoreUserId = monsoereUser.userId(); OreSiUser publicUser = userRepository.findByLogin("_public_").orElse(null); assert publicUser != null; UUID publicUserId = publicUser.getId(); final Cookie monsoreCookie = mockMvc.perform(post("/api/v1/login") - .param("login", "monsore") + .param("login", "monsoresimple") .param("password", "xxxxxxxx")) .andReturn().getResponse().getCookie(AuthHelper.JWT_COOKIE_NAME); final CreateUserResult withRightsUserResult = createUserIfNotExists("withrigths", "xxxxxxxx", "withrigths@inrae.fr"); @@ -245,41 +251,21 @@ public class OreSiResourcesTest { URL resource = getClass().getResource(Fixtures.getMonsoreApplicationConfigurationResourceName()); try (final InputStream in = Objects.requireNonNull(resource).openStream()) { - final MockMultipartFile configuration = new MockMultipartFile("file", "monsore.yaml", "text/plain", in); + final MockMultipartFile configuration = new MockMultipartFile("file", "monsoresimple.yaml", "text/plain", in); // on n'a pas le droit de creer de nouvelle application - Exception loadApplicationException = fixtures.loadApplicationWithError( - configuration, - monsoreCookie, - "monsore" - ); - - switch (loadApplicationException) { - case NotApplicationCreatorRightsException notApplicationCreatorRightsException -> { - Assertions.assertEquals("monsore", notApplicationCreatorRightsException.getApplicationName()); - addUserRightCreateApplication(monsoreUserId, "monsore"); - } - case NoSuchApplicationException noSuchApplicationException -> { - addUserRightCreateApplication(monsoreUserId, "monsore"); - - } - case JsonParseException jsonParseException -> { - log.debug("******************jsonparseexception*****************"); - jsonParseException.getMessage(); - log.debug("******************jsonparseexception*****************"); - try (InputStream stream = resource.openStream(); - BufferedReader reader = new BufferedReader(new InputStreamReader(stream))) { - char[] buffer = new char[100]; // Changer cette valeur selon le nombre de caractères souhaités - int n = reader.read(buffer); - System.out.println(new String(buffer, 0, n)); - log.debug("******************jsonparseexception*****************"); - } - addUserRightCreateApplication(monsoreUserId, "monsore"); - - } - default -> throw loadApplicationException; - } - final MvcResult resultApplication = fixtures.loadApplication(configuration, monsoreCookie, "monsore", ""); + NotApplicationCreatorRightsException resolvedException = + (NotApplicationCreatorRightsException) fixtures.loadApplicationWithError( + configuration, + monsoreCookie, + "monsoresimple" + ); + addUserRightCreateApplication(monsoreUserId, "monsoresimple"); + assert resolvedException != null; + Assertions.assertEquals("monsoresimple", resolvedException.getApplicationName()); + addUserRightCreateApplication(monsoreUserId, "monsoresimple"); + + final MvcResult resultApplication = fixtures.loadApplication(configuration, monsoreCookie, "monsoresimple", ""); appId = fixtures.getIdFromApplicationResult(resultApplication); } catch (final Throwable e) { e.printStackTrace(); @@ -299,7 +285,7 @@ public class OreSiResourcesTest { final ApplicationResult applicationResult = (ApplicationResult) jsonRowMapper.readValue(response, ApplicationResult.class); Assertions.assertEquals("Fichier de test de l'application brokenADOM", applicationResult.comment()); - Assertions.assertEquals("monsore", applicationResult.name()); + Assertions.assertEquals("monsoresimple", applicationResult.name()); Assert.assertEquals( new TreeSet<>(Set.of("themes", "especes", "site_theme_datatype", "variables", "type_de_sites", "unites", "projet", "valeurs_qualitatives", "type_de_fichiers", "variables_et_unites_par_types_de_donnees")), new TreeSet<>(applicationResult.references().keySet()) @@ -312,7 +298,7 @@ public class OreSiResourcesTest { final MockMultipartFile refFile = new MockMultipartFile("file", e.getValue(), "text/plain", refStream); - response = mockMvc.perform(multipart("/api/v1/applications/monsore/data/{refType}", e.getKey()) + response = mockMvc.perform(multipart("/api/v1/applications/monsoresimple/data/{refType}", e.getKey()) .file(refFile) .cookie(monsoreCookie)) .andExpect(status().isCreated()) @@ -323,7 +309,7 @@ public class OreSiResourcesTest { } } //test de la reference especetoTrim - final String getEspecesResponse = mockMvc.perform(get("/api/v1/applications/monsore/data/especes/json") + final String getEspecesResponse = mockMvc.perform(get("/api/v1/applications/monsoresimple/data/especes/json") .contentType(MediaType.APPLICATION_JSON) .cookie(monsoreCookie)) .andExpect(status().isOk()) @@ -339,7 +325,7 @@ public class OreSiResourcesTest { for (final Map.Entry<String, String> e : Fixtures.getMonsoreReferentielFiles().entrySet()) { try (final InputStream refStream = getClass().getResourceAsStream(e.getValue())) { final MockMultipartFile refFile = new MockMultipartFile("file", e.getValue(), "text/plain", refStream); - response = mockMvc.perform(multipart("/api/v1/applications/monsore/data/{refType}", e.getKey()) + response = mockMvc.perform(multipart("/api/v1/applications/monsoresimple/data/{refType}", e.getKey()) .file(refFile) .cookie(monsoreCookie)) .andDo(result -> { @@ -367,7 +353,7 @@ public class OreSiResourcesTest { .cookie(monsoreCookie)) .andExpect(status().isOk()); - final String getReferencesResponse = mockMvc.perform(get("/api/v1/applications/monsore/data/sites/json") + final String getReferencesResponse = mockMvc.perform(get("/api/v1/applications/monsoresimple/data/sites/json") .contentType(MediaType.APPLICATION_JSON) .cookie(monsoreCookie)) .andExpect(status().isOk()) @@ -376,7 +362,7 @@ public class OreSiResourcesTest { .andReturn().getResponse().getContentAsString(); mockMvc.perform( - asyncDispatch(mockMvc.perform(get("/api/v1/applications/monsore/data/sites/csv") + asyncDispatch(mockMvc.perform(get("/api/v1/applications/monsoresimple/data/sites/csv") .contentType(MediaType.APPLICATION_OCTET_STREAM) .cookie(monsoreCookie)) .andExpect(status().isOk()) @@ -408,7 +394,7 @@ public class OreSiResourcesTest { try (final InputStream refStream = Objects.requireNonNull(resource).openStream()) { final MockMultipartFile refFile = new MockMultipartFile("file", "data-pem.csv", "text/plain", refStream); - response = mockMvc.perform(multipart("/api/v1/applications/monsore/data/{refType}", "pem") + response = mockMvc.perform(multipart("/api/v1/applications/monsoresimple/data/{refType}", "pem") .file(refFile) .cookie(monsoreCookie)) .andExpect(status().isCreated()) @@ -423,7 +409,7 @@ public class OreSiResourcesTest { try (final InputStream refStream = Objects.requireNonNull(resource).openStream()) { final MockMultipartFile refFile = new MockMultipartFile("file", "data-pem.csv", "text/plain", refStream); // sans droit on ne peut pas - response = mockMvc.perform(multipart("/api/v1/applications/monsore/data/pem") + response = mockMvc.perform(multipart("/api/v1/applications/monsoresimple/data/pem") .file(refFile) .cookie(withRigthsCookie)) .andDo(result -> { @@ -433,7 +419,7 @@ public class OreSiResourcesTest { } }) .andExpect(status().is4xxClientError()) - .andExpect(content().string("application inconnue 'monsore'")) + .andExpect(content().string("application inconnue 'monsoresimple'")) .andReturn().getResponse().getContentAsString(); //ajout de droits withRignesthsUserId if (true) { @@ -442,7 +428,7 @@ public class OreSiResourcesTest { String jsonRightsForMonsoere = setJsonRightsForMonsoere(monsoreCookie, withRigthsUserId, OperationType.publication.name(), "pem"); String jsonRightsForMonsoereId = JsonPath.parse(jsonRightsForMonsoere).read("$.authorizationId"); //avec les droits on peut publier - response = mockMvc.perform(multipart("/api/v1/applications/monsore/data/pem") + response = mockMvc.perform(multipart("/api/v1/applications/monsoresimple/data/pem") .file(refFile) .cookie(monsoreCookie/*withRigthsCookie*/)) .andDo(result -> { @@ -457,7 +443,7 @@ public class OreSiResourcesTest { log.debug(StringUtils.abbreviate(response, 50)); //sans droit on ne peut pas - response = mockMvc.perform(multipart("/api/v1/applications/monsore/data/pem") + response = mockMvc.perform(multipart("/api/v1/applications/monsoresimple/data/pem") .file(refFile) .cookie(lambdaCookie)) .andExpect(status().is4xxClientError()) @@ -468,18 +454,18 @@ public class OreSiResourcesTest { //avec les droits public on peut publier même sans droit - response = mockMvc.perform(multipart("/api/v1/applications/monsore/data/pem") + response = mockMvc.perform(multipart("/api/v1/applications/monsoresimple/data/pem") .file(refFile) .cookie(lambdaCookie)) .andExpect(status().is2xxSuccessful()) .andReturn().getResponse().getContentAsString(); // on supprime les droits public - mockMvc.perform(delete("/api/v1/applications/monsore/authorization/" + publicRightsId) + mockMvc.perform(delete("/api/v1/applications/monsoresimple/authorization/" + publicRightsId) .cookie(monsoreCookie)) .andExpect(status().is2xxSuccessful()); - response = mockMvc.perform(multipart("/api/v1/applications/monsore/data/pem") + response = mockMvc.perform(multipart("/api/v1/applications/monsoresimple/data/pem") .file(refFile) .cookie(lambdaCookie)) .andExpect(status().is4xxClientError()) @@ -491,7 +477,7 @@ public class OreSiResourcesTest { resource = getClass().getResource(Fixtures.getPemDataToTrimResourceName()); try (final InputStream refStream = Objects.requireNonNull(resource).openStream()) { final MockMultipartFile refFile = new MockMultipartFile("file", "data-pem.csv", "text/plain", refStream); - response = mockMvc.perform(multipart("/api/v1/applications/monsore/data/pem") + response = mockMvc.perform(multipart("/api/v1/applications/monsoresimple/data/pem") .file(refFile) .cookie(withRigthsCookie)) .andExpect(status().is2xxSuccessful()) @@ -499,7 +485,7 @@ public class OreSiResourcesTest { final String actualJson = getPemData(monsoreCookie); log.debug(StringUtils.abbreviate(actualJson, 50)); } - final String contentAsString = mockMvc.perform(get("/api/v1/applications/monsore/data/pem/json") + final String contentAsString = mockMvc.perform(get("/api/v1/applications/monsoresimple/data/pem/json") .cookie(monsoreCookie)) .andExpect(jsonPath("$.rows[*].values" + "[?(@.projet.value=='projet_atlantique' )]" + @@ -528,7 +514,7 @@ public class OreSiResourcesTest { "%s" """, s)) .collect(Collectors.joining(","))); - mockMvc.perform(get("/api/v1/applications/monsore/data/pem/json") + mockMvc.perform(get("/api/v1/applications/monsoresimple/data/pem/json") .param("downloadDatasetQuery", query) .cookie(monsoreCookie)) .andExpect(status().is2xxSuccessful()) @@ -543,7 +529,7 @@ public class OreSiResourcesTest { final String wrongData = data.replace("plateforme", "entete_inconnu"); final byte[] bytes = wrongData.getBytes(StandardCharsets.UTF_8); final MockMultipartFile refFile = new MockMultipartFile("file", "data-pem.csv", "text/plain", bytes); - response = mockMvc.perform(multipart("/api/v1/applications/monsore/data/pem") + response = mockMvc.perform(multipart("/api/v1/applications/monsoresimple/data/pem") .file(refFile) .cookie(monsoreCookie)) .andExpect(status().isBadRequest()) @@ -554,7 +540,7 @@ public class OreSiResourcesTest { } // list des types de data - response = mockMvc.perform(get("/api/v1/applications/monsore/data") + response = mockMvc.perform(get("/api/v1/applications/monsoresimple/data") .cookie(monsoreCookie)) .andExpect(status().isOk()) .andReturn().getResponse().getContentAsString(); @@ -562,7 +548,7 @@ public class OreSiResourcesTest { log.debug(StringUtils.abbreviate(response, 50)); { - final String expectedJson = Resources.toString(Objects.requireNonNull(getClass().getResource("/data/monsore/compare/export.json")), Charsets.UTF_8); + final String expectedJson = Resources.toString(Objects.requireNonNull(getClass().getResource("/data/monsoresimple/compare/export.json")), Charsets.UTF_8); final JSONArray jsonArray = new JSONArray(expectedJson); final List<String> list = new ArrayList<>(); @@ -613,8 +599,8 @@ public class OreSiResourcesTest { ] }"""; { - Resources.toString(Objects.requireNonNull(getClass().getResource("/data/monsore/compare/export.json")), Charsets.UTF_8); - final String actualJson = mockMvc.perform(get("/api/v1/applications/monsore/data/pem/json") + Resources.toString(Objects.requireNonNull(getClass().getResource("/data/monsoresimple/compare/export.json")), Charsets.UTF_8); + final String actualJson = mockMvc.perform(get("/api/v1/applications/monsoresimple/data/pem/json") .cookie(monsoreCookie) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) @@ -629,8 +615,8 @@ public class OreSiResourcesTest { // restitution de data csv { - final String expectedCsv = Resources.toString(Objects.requireNonNull(getClass().getResource("/data/monsore/compare/export.csv")), Charsets.UTF_8); - mockMvc.perform(asyncDispatch(mockMvc.perform(get("/api/v1/applications/monsore/data/pem/zip") + final String expectedCsv = Resources.toString(Objects.requireNonNull(getClass().getResource("/data/monsoresimple/compare/export.csv")), Charsets.UTF_8); + mockMvc.perform(asyncDispatch(mockMvc.perform(get("/api/v1/applications/monsoresimple/data/pem/zip") .cookie(monsoreCookie) .accept(MediaType.APPLICATION_OCTET_STREAM_VALUE)) .andExpect(status().isOk()) @@ -652,7 +638,7 @@ public class OreSiResourcesTest { .replace("projet_manche", "projet_manch") .replace("projet_atlantique", "projet_atlantiqu"); final MockMultipartFile refFile = new MockMultipartFile("file", "data-pem.csv", "text/plain", invalidCsv.getBytes(StandardCharsets.UTF_8)); - response = mockMvc.perform(multipart("/api/v1/applications/monsore/data/pem") + response = mockMvc.perform(multipart("/api/v1/applications/monsoresimple/data/pem") .file(refFile) .cookie(monsoreCookie)) .andExpect(status().is4xxClientError()) @@ -669,31 +655,31 @@ public class OreSiResourcesTest { Assertions.assertFalse(() -> finalResponse.contains("142"), "L'erreur doit être tronquée"); Assertions.assertFalse(() -> finalResponse.contains("143"), "L'erreur doit être tronquée"); } - String getMonsoere = mockMvc.perform(get("/api/v1/applications/monsore") + String getMonsoere = mockMvc.perform(get("/api/v1/applications/monsoresimple") .cookie(authCookie) .accept(MediaType.APPLICATION_JSON)) .andReturn().getResponse().getContentAsString(); - String getSites = mockMvc.perform(get("/api/v1/applications/monsore/data/sites") + String getSites = mockMvc.perform(get("/api/v1/applications/monsoresimple/data/sites") .cookie(monsoreCookie) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().is2xxSuccessful()) .andReturn().getResponse().getContentAsString(); - String getTypeSites = mockMvc.perform(get("/api/v1/applications/monsore/data/type_de_sites") + String getTypeSites = mockMvc.perform(get("/api/v1/applications/monsoresimple/data/type_de_sites") .cookie(monsoreCookie) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().is2xxSuccessful()) .andReturn().getResponse().getContentAsString(); - String getProjet = mockMvc.perform(get("/api/v1/applications/monsore/data/projet") + String getProjet = mockMvc.perform(get("/api/v1/applications/monsoresimple/data/projet") .cookie(monsoreCookie) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().is2xxSuccessful()) .andReturn().getResponse().getContentAsString(); - String getPem = mockMvc.perform(get("/api/v1/applications/monsore/data/pem/json") + String getPem = mockMvc.perform(get("/api/v1/applications/monsoresimple/data/pem/json") .cookie(monsoreCookie) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().is2xxSuccessful()) .andReturn().getResponse().getContentAsString(); - mockMvc.perform(delete("/api/v1/applications/monsore/data/pem") + mockMvc.perform(delete("/api/v1/applications/monsoresimple/data/pem") .param("downloadDatasetQuery", filter) .cookie(monsoreCookie)) .andExpect(status().is2xxSuccessful()) @@ -735,7 +721,7 @@ public class OreSiResourcesTest { .andExpect(jsonPath("$.rows", Matchers.not(Matchers.empty())));*/ filter = filterPattern.formatted(null, "{\"from\":\"15\",\"to\":\"15\"}"); - mockMvc.perform(get("/api/v1/applications/monsore/data/pem/json") + mockMvc.perform(get("/api/v1/applications/monsoresimple/data/pem/json") .cookie(monsoreCookie) .param("downloadDatasetQuery", filter) .accept(MediaType.APPLICATION_JSON)) @@ -751,12 +737,12 @@ public class OreSiResourcesTest { "1984,1,6", monsoreCookie); - String getAuthorizations = mockMvc.perform(get("/api/v1/applications/monsore/authorization") + String getAuthorizations = mockMvc.perform(get("/api/v1/applications/monsoresimple/authorization") .cookie(withRigthsCookie) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().is2xxSuccessful()) .andReturn().getResponse().getContentAsString(); - String getGrantable = mockMvc.perform(get("/api/v1/applications/monsore/grantable") + String getGrantable = mockMvc.perform(get("/api/v1/applications/monsoresimple/grantable") .cookie(withRigthsCookie) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().is2xxSuccessful()) @@ -933,6 +919,7 @@ public class OreSiResourcesTest { @Test @Tag("OTHERS_TEST") @Tag("SUITE") + @Tag("MONSOERE") public void addApplicationMonsoreWithRepository() throws Exception { URL resource = getClass().getResource(Fixtures.getMonsoreApplicationConfigurationWithRepositoryResourceName()); final String oirFilesUUID; diff --git a/src/test/resources/data/configuration/configuration.yaml b/src/test/resources/data/configuration/configuration.yaml index 67bc55cd6..44865d3eb 100644 --- a/src/test/resources/data/configuration/configuration.yaml +++ b/src/test/resources/data/configuration/configuration.yaml @@ -717,9 +717,11 @@ OA_data: OA_strategy: OA_VERSIONING OA_fileName: OA_filePattern: "(.*)_(.*)_(.*)_(.*).csv" - OA_referenceScopes: [projet, site_bassin] - OA_startDate: 3 - OA_endDate: 4 + OA_matchPatternScopes: + - projet + - site_bassin + - __START_DATE__ #optional + - __END_DATE__ #optional OA_submissionScope: OA_referenceScopes: - OA_i18n: diff --git a/src/test/resources/data/configuration/data.result.example.json b/src/test/resources/data/configuration/data.result.example.json index 5b82415cd..d590ffc7e 100644 --- a/src/test/resources/data/configuration/data.result.example.json +++ b/src/test/resources/data/configuration/data.result.example.json @@ -1311,8 +1311,8 @@ "fileNameParsing" : { "pattern" : "(.*)_(.*)_(.*).csv", "authorizationScopes" : [ "dat_site" ], - "startDate" : null, - "endDate" : null + "startDate" : 2, + "endDate" : 3 }, "submissionScope" : { "referenceScopes" : [ { diff --git a/src/test/resources/data/configuration/data.result.json b/src/test/resources/data/configuration/data.result.json index f0d97494d..121d20efd 100644 --- a/src/test/resources/data/configuration/data.result.json +++ b/src/test/resources/data/configuration/data.result.json @@ -3267,8 +3267,8 @@ "fileNameParsing" : { "pattern" : "(.*)_(.*)_(.*)_(.*).csv", "authorizationScopes" : [ "projet", "site_bassin" ], - "startDate" : null, - "endDate" : null + "startDate" : 3, + "endDate" : 4 }, "submissionScope" : { "referenceScopes" : [ { diff --git a/src/test/resources/data/configuration/data.result.monsore.json b/src/test/resources/data/configuration/data.result.monsore.json index 93589b30b..98fd92b22 100644 --- a/src/test/resources/data/configuration/data.result.monsore.json +++ b/src/test/resources/data/configuration/data.result.monsore.json @@ -2269,8 +2269,8 @@ "fileNameParsing" : { "pattern" : "(.*)!(.*)!(.*)!(.*).csv", "authorizationScopes" : [ "projet", "chemin" ], - "startDate" : null, - "endDate" : null + "startDate" : 3, + "endDate" : 4 }, "submissionScope" : { "referenceScopes" : [ { diff --git a/src/test/resources/data/configuration/schemaExample.yaml b/src/test/resources/data/configuration/schemaExample.yaml index 92d20ae6d..22abb091c 100644 --- a/src/test/resources/data/configuration/schemaExample.yaml +++ b/src/test/resources/data/configuration/schemaExample.yaml @@ -621,10 +621,10 @@ OA_data: #optional OA_component: dat_date_heure #mandatory OA_fileName: #optional OA_filePattern: (.*)_(.*)_(.*).csv #mandatory - OA_startDate: 2 #optional - OA_referenceScopes: #optional + OA_matchPatternScopes: #optional - dat_site #optional - OA_endDate: 3 #optional + - __START_DATE__ #optional + - __END_DATE__ #optional OA_authorizations: #mandatory OA_authorizationScope: #optional - dat_site #optional diff --git a/src/test/resources/data/monsore/monsore-with-repository.yaml b/src/test/resources/data/monsore/monsore-with-repository.yaml index e8f0caf2c..652cdc4c9 100644 --- a/src/test/resources/data/monsore/monsore-with-repository.yaml +++ b/src/test/resources/data/monsore/monsore-with-repository.yaml @@ -654,9 +654,11 @@ OA_data: OA_strategy: OA_VERSIONING OA_fileName: OA_filePattern: "(.*)!(.*)!(.*)!(.*).csv" - OA_referenceScopes: [projet, chemin] - OA_startDate: 3 - OA_endDate: 4 + OA_matchPatternScopes: + - projet + - chemin + - __START_DATE__ #optional + - __END_DATE__ #optional OA_submissionScope: OA_referenceScopes: - OA_component: projet diff --git a/src/test/resources/data/monsore/monsore.yaml b/src/test/resources/data/monsore/monsore.yaml index 5f87bb790..27ca85ef0 100644 --- a/src/test/resources/data/monsore/monsore.yaml +++ b/src/test/resources/data/monsore/monsore.yaml @@ -5,7 +5,7 @@ OA_application: fr: SOERE mon SOERE en: SOERE my SOERE OA_comment: Fichier de test de l'application brokenADOM - OA_name: monsore + OA_name: monsoresimple OA_version: 3.0.1 OA_tags: context: diff --git a/src/test/resources/data/validation/broken-fake-app.yaml b/src/test/resources/data/validation/broken-fake-app.yaml index b525af605..e557d18b7 100644 --- a/src/test/resources/data/validation/broken-fake-app.yaml +++ b/src/test/resources/data/validation/broken-fake-app.yaml @@ -353,11 +353,11 @@ OA_data: OA_strategy: OA_VERSIONING OA_fileName: OA_filePattern: "(.*)_(.*)_(.*)_(.*).csv" - OA_referenceScopes: + OA_matchPatternScopes: - projet - site_bassin - OA_startDate: 3 - OA_endDate: 4 + - __START_DATE__ #optional + - __END_DATE__ #optional OA_submissionScope: OA_referenceScopes: - OA_i18n: -- GitLab