Repository: pagopa/rtd-ms-file-reporter
Pull request #158: fix: [TAE-242] generate report post error event
Author: login: carava-dt
Labels: name: released
Created: 2025-01-31T11:25:13Z | Merged: 2025-03-07T14:47:49Z

#### Description
<!--- Please always add a PR description as if nobody knows anything about the context these changes come from. -->
<!--- Even if we are all from our internal team, we may not be on the same page. -->
<!--- Write this PR as you were contributing to a public OSS project, where nobody knows you and you have to earn their trust. -->
<!--- This will improve our projects in the long run! Thanks. -->
An API has been implemented to recover metadata in case of DecryptedEvent failure.

#### List of Changes

<!--- Describe your changes in detail -->
- add api /getMetadata on FileReportController

#### Motivation and Context

<!--- Why is this change required? What problem does it solve? -->

#### How Has This Been Tested?

<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, tests ran to see how -->
<!--- your change affects other areas of the code, etc. -->
- [ ] Unit Test Suite
- [ ] Integration Test Suite
- [ ] Load Tests

#### Types of changes

<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->

- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Chore change (non-breaking change which doesn't provide a direct value to users)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)

#### Checklist:

<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->

- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.



Diff:
diff --git a/pom.xml b/pom.xml
index 337a2937..cf2608d8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -11,7 +11,7 @@
 
 	<groupId>it.gov.pagopa.rtd.ms</groupId>
 	<artifactId>rtd-ms-file-reporter</artifactId>
-	<version>2.0.2</version>
+	<version>2.0.3</version>
 	<name>rtd-ms-file-reporter</name>
 	<description>Micro-service to retrieve and keep updated the file reports for senders</description>
 
diff --git a/src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/RtdMsFileReporterApplication.java b/src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/RtdMsFileReporterApplication.java
index 0069b04e..cf78fc7b 100644
--- a/src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/RtdMsFileReporterApplication.java
+++ b/src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/RtdMsFileReporterApplication.java
@@ -12,5 +12,4 @@ public class RtdMsFileReporterApplication {
   public static void main(String[] args) {
     SpringApplication.run(RtdMsFileReporterApplication.class, args);
   }
-
 }
diff --git a/src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/configuration/AppConfiguration.java b/src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/configuration/AppConfiguration.java
index 5f02f04d..8471d9f2 100644
--- a/src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/configuration/AppConfiguration.java
+++ b/src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/configuration/AppConfiguration.java
@@ -5,9 +5,12 @@
 import it.gov.pagopa.rtd.ms.rtdmsfilereporter.domain.service.DecryptedEventCommand;
 import it.gov.pagopa.rtd.ms.rtdmsfilereporter.domain.service.FileReportService;
 import it.gov.pagopa.rtd.ms.rtdmsfilereporter.domain.service.FileReportServiceImpl;
+import it.gov.pagopa.rtd.ms.rtdmsfilereporter.domain.service.StorageAccountService;
+import it.gov.pagopa.rtd.ms.rtdmsfilereporter.feign.config.ReportConfiguration;
 import it.gov.pagopa.rtd.ms.rtdmsfilereporter.persistance.FileReportDao;
 import it.gov.pagopa.rtd.ms.rtdmsfilereporter.persistance.FileReportRepositoryImpl;
 import it.gov.pagopa.rtd.ms.rtdmsfilereporter.persistance.model.FileReportEntityMapper;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
@@ -16,11 +19,12 @@
  * the domain are not bounded to the specific framework.
  */
 @Configuration
+@EnableConfigurationProperties(ReportConfiguration.class)
 public class AppConfiguration {
 
   @Bean
-  public FileReportService getFileReportService(FileReportRepository repository) {
-    return new FileReportServiceImpl(repository);
+  public FileReportService getFileReportService(FileReportRepository repository, StorageAccountService service, ReportConfiguration report) {
+    return new FileReportServiceImpl(repository, service, report);
   }
 
   @Bean
diff --git a/src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/controller/FileReportController.java b/src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/controller/FileReportController.java
index 6c9a478b..8f4f726c 100644
--- a/src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/controller/FileReportController.java
+++ b/src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/controller/FileReportController.java
@@ -4,10 +4,15 @@
 import it.gov.pagopa.rtd.ms.rtdmsfilereporter.controller.model.v1.FileReportDto;
 import it.gov.pagopa.rtd.ms.rtdmsfilereporter.controller.model.v2.FileReportV2Dto;
 import jakarta.validation.constraints.NotNull;
+
 import java.util.Collection;
+
+import org.springframework.http.HttpStatus;
 import org.springframework.http.MediaType;
 import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PatchMapping;
 import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseStatus;
 
 public interface FileReportController {
 
@@ -21,4 +26,10 @@ FileReportV2Dto getFileReportV2(
   @GetMapping(value = "/sender-ade-ack", produces = MediaType.APPLICATION_JSON_VALUE)
   SenderAdeAckListDto getSenderAdeAckList(
       @NotNull @RequestParam(name = "senderCodes") Collection<String> senderCodes);
+
+  @PatchMapping(value = "/report/metadata", produces = MediaType.APPLICATION_JSON_VALUE)
+  @ResponseStatus(HttpStatus.OK)
+  void saveMetadata(
+      @NotNull @RequestParam(name = "basePath") String path,
+      @NotNull @RequestParam(name = "fileName") String fileName);
 }
diff --git a/src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/controller/FileReportControllerImpl.java b/src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/controller/FileReportControllerImpl.java
index 56e2f3eb..6232841d 100644
--- a/src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/controller/FileReportControllerImpl.java
+++ b/src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/controller/FileReportControllerImpl.java
@@ -7,7 +7,11 @@
 import it.gov.pagopa.rtd.ms.rtdmsfilereporter.controller.model.v2.FileReportV2DtoMapper;
 import it.gov.pagopa.rtd.ms.rtdmsfilereporter.domain.service.FileReportService;
 import jakarta.validation.constraints.NotNull;
+
 import java.util.Collection;
+import java.util.InputMismatchException;
+import java.util.regex.Pattern;
+
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.web.bind.annotation.RestController;
@@ -40,4 +44,24 @@ public SenderAdeAckListDto getSenderAdeAckList(@NotNull Collection<String> sende
         .fileNameList(fileReportService.getAckToDownloadList(senderCodes))
         .build();
   }
+
+  @Override
+  public void saveMetadata(String basePath, String fileName) {
+    String sanitizedFileName = fileName.replace("\n", "").replace("\r", "");
+    if (validateFileName(sanitizedFileName)) {
+      log.info("Enrich metadata on file : {}", sanitizedFileName);
+      fileReportService.saveMetadata(basePath, sanitizedFileName);
+    } else {
+      throw new InputMismatchException("Filename " + sanitizedFileName + " malformed");
+    }
+  }
+
+  public boolean validateFileName(String filename) {
+    // Well formed filename
+    // ADE.[Sender Code].TRNLOG.[yyyymmdd].[hhmmss].[acquirer internal id].[chunk number].csv
+    String app = filename.substring(0, filename.indexOf(".csv.pgp"));
+    Pattern p = Pattern.compile("ADE.\\d{5}.TRNLOG.\\d{8}.\\d{6}.\\d{3}");
+    Pattern p2 = Pattern.compile("ADE.\\d{5}.TRNLOG.\\d{8}.\\d{6}.\\d{3}.\\d*");
+    return (p.matcher(app).matches() || p2.matcher(app).matches());
+  }
 }
diff --git a/src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/controller/RestExceptionHandler.java b/src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/controller/RestExceptionHandler.java
new file mode 100644
index 00000000..5391a927
--- /dev/null
+++ b/src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/controller/RestExceptionHandler.java
@@ -0,0 +1,30 @@
+package it.gov.pagopa.rtd.ms.rtdmsfilereporter.controller;
+
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import it.gov.pagopa.rtd.ms.rtdmsfilereporter.domain.exception.FileMetadataNotFoundException;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+
+import java.util.InputMismatchException;
+import java.util.NoSuchElementException;
+
+@RestControllerAdvice
+public class RestExceptionHandler {
+
+  @ExceptionHandler(FileMetadataNotFoundException.class)
+  protected ResponseEntity<String> handleFileMetadataNotFound(FileMetadataNotFoundException ex) {
+    return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage());
+  }
+
+  @ExceptionHandler(NoSuchElementException.class)
+  protected ResponseEntity<String> handleNoSuchElement(NoSuchElementException ex) {
+    return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ex.getMessage());
+  }
+
+  @ExceptionHandler(InputMismatchException.class)
+  protected ResponseEntity<String> handleInputMismatch(InputMismatchException ex) {
+    return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ex.getMessage());
+  }
+
+}
diff --git a/src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/domain/exception/FileMetadataNotFoundException.java b/src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/domain/exception/FileMetadataNotFoundException.java
new file mode 100644
index 00000000..0933f8c8
--- /dev/null
+++ b/src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/domain/exception/FileMetadataNotFoundException.java
@@ -0,0 +1,7 @@
+package it.gov.pagopa.rtd.ms.rtdmsfilereporter.domain.exception;
+
+public class FileMetadataNotFoundException extends RuntimeException {
+  public FileMetadataNotFoundException(String message) {
+    super(message);
+  }
+}
diff --git a/src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/domain/service/FileReportService.java b/src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/domain/service/FileReportService.java
index 79fb8361..05c394f7 100644
--- a/src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/domain/service/FileReportService.java
+++ b/src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/domain/service/FileReportService.java
@@ -13,4 +13,6 @@ public interface FileReportService {
   Collection<String> getAckToDownloadList(Collection<String> senderCodes);
 
   void save(FileReport fileReport);
+
+  void saveMetadata(String basePath, String fileName);
 }
diff --git a/src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/domain/service/FileReportServiceImpl.java b/src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/domain/service/FileReportServiceImpl.java
index 19e6eea6..3621c049 100644
--- a/src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/domain/service/FileReportServiceImpl.java
+++ b/src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/domain/service/FileReportServiceImpl.java
@@ -1,23 +1,33 @@
 package it.gov.pagopa.rtd.ms.rtdmsfilereporter.domain.service;
 
+import it.gov.pagopa.rtd.ms.rtdmsfilereporter.domain.exception.FileMetadataNotFoundException;
+
 import static it.gov.pagopa.rtd.ms.rtdmsfilereporter.domain.model.FileReportAggregator.aggregateFileReports;
 
+import it.gov.pagopa.rtd.ms.rtdmsfilereporter.domain.model.FileMetadata;
 import it.gov.pagopa.rtd.ms.rtdmsfilereporter.domain.model.FileReport;
 import it.gov.pagopa.rtd.ms.rtdmsfilereporter.domain.repository.FileReportRepository;
+
+import java.io.File;
 import java.util.Collection;
+import java.util.NoSuchElementException;
 import java.util.Optional;
-import lombok.RequiredArgsConstructor;
 
+import it.gov.pagopa.rtd.ms.rtdmsfilereporter.feign.config.ReportConfiguration;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 
+@Slf4j
 @RequiredArgsConstructor
 public class FileReportServiceImpl implements FileReportService {
 
   private final FileReportRepository fileReportRepository;
+  private final StorageAccountService service;
+  private final ReportConfiguration reportConfig;
 
   @Override
   public FileReport getAggregateFileReport(Collection<String> senderCodes) {
-    return fileReportRepository.getReportsBySenderCodes(senderCodes)
-        .stream()
+    return fileReportRepository.getReportsBySenderCodes(senderCodes).stream()
         .collect(aggregateFileReports());
   }
 
@@ -35,4 +45,39 @@ public Collection<String> getAckToDownloadList(Collection<String> senderCodes) {
   public void save(FileReport fileReport) {
     fileReportRepository.save(fileReport);
   }
+
+  @Override
+  public void saveMetadata(String basePath, String fileName) {
+    // get senderCode from filename
+    String senderCode = fileName.split("\\.")[1];
+    FileReport fileReport;
+    fileReport =
+        getFileReport(senderCode)
+            .orElseThrow(
+                () -> {
+                  String errorMsg = String.format("FileReport not found for sender %s", senderCode);
+                  return new NoSuchElementException(errorMsg);
+                });
+
+    Optional<FileMetadata> result =
+        fileReport.getFilesUploaded().stream()
+            .filter(f -> f.getName().equals(fileName))
+            .findFirst();
+
+    FileMetadata fileMetadata;
+    if (result.isEmpty()) {
+      String errorMsg =
+          String.format(
+              "File %s not found in latest %d days report of sender %s",
+              fileName, reportConfig.fileTTL(), senderCode);
+      throw new FileMetadataNotFoundException(errorMsg);
+    } else fileMetadata = result.get();
+
+    String path = File.separator + basePath + File.separator;
+    fileMetadata.setPath(path);
+    var dataSummary = service.getMetadata(path, fileName);
+    log.debug("DataSummary : {}", dataSummary.toString());
+    fileMetadata.enrichWithSquaringData(dataSummary);
+    save(fileReport);
+  }
 }
diff --git a/src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/domain/service/StorageAccountService.java b/src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/domain/service/StorageAccountService.java
index 3a69940d..50fc1498 100644
--- a/src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/domain/service/StorageAccountService.java
+++ b/src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/domain/service/StorageAccountService.java
@@ -23,7 +23,13 @@ public AggregatesDataSummary getMetadata(String basePath, String fileName) {
     try {
       response = connector.getBlobMetadata(basePath, fileName);
     } catch (IOException e) {
-      log.warn("Failed to retrieve the file metadata from the storage! Error: {}", e.getMessage());
+      String sanitizedBasePath = basePath.replaceAll("[^a-zA-Z0-9.-]", "");
+      String sanitizedFileName = fileName.replaceAll("[^a-zA-Z0-9.-]", "");
+      log.warn(
+          "Failed to retrieve the file metadata from the storage! Path: {} File:{} Error: {}",
+          sanitizedBasePath,
+          sanitizedFileName,
+          e.getMessage());
       return AggregatesDataSummary.createInvalidDataSummary();
     }
 
diff --git a/src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/feign/config/ReportConfiguration.java b/src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/feign/config/ReportConfiguration.java
new file mode 100644
index 00000000..bf35c2bb
--- /dev/null
+++ b/src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/feign/config/ReportConfiguration.java
@@ -0,0 +1,13 @@
+package it.gov.pagopa.rtd.ms.rtdmsfilereporter.feign.config;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@ConfigurationProperties(prefix = "report")
+public record ReportConfiguration
+        (@NotNull
+        @NotBlank
+        Integer fileTTL) {
+}
+
diff --git a/src/test/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/controller/FileReportControllerImplTest.java b/src/test/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/controller/FileReportControllerImplTest.java
index ca27f806..16a2f20e 100644
--- a/src/test/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/controller/FileReportControllerImplTest.java
+++ b/src/test/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/controller/FileReportControllerImplTest.java
@@ -1,7 +1,8 @@
 package it.gov.pagopa.rtd.ms.rtdmsfilereporter.controller;
 
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.jupiter.api.Assertions.*;
 import static org.mockito.ArgumentMatchers.any;
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -15,8 +16,11 @@
 import it.gov.pagopa.rtd.ms.rtdmsfilereporter.domain.model.AggregatesDataSummary;
 import it.gov.pagopa.rtd.ms.rtdmsfilereporter.domain.model.FileReport;
 import it.gov.pagopa.rtd.ms.rtdmsfilereporter.domain.service.FileReportService;
+
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.InputMismatchException;
+
 import lombok.SneakyThrows;
 import org.assertj.core.util.Sets;
 import org.junit.jupiter.api.Test;
@@ -26,8 +30,8 @@
 import org.mockito.Mockito;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
-import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.http.MediaType;
+import org.springframework.test.context.bean.override.mockito.MockitoBean;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.MvcResult;
 import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
@@ -35,34 +39,32 @@
 @WebMvcTest(FileReportControllerImpl.class)
 class FileReportControllerImplTest {
 
-  private final String FILE_REPORT_URL = "/file-report";
-  private final String SENDER_ADE_ACK_URL = "/sender-ade-ack";
-  private final String FILE_REPORT_URL_V2 = "/v2/file-report";
+  private final String fileReportUrl = "/file-report";
+  private final String fileReportUrlV2 = "/v2/file-report";
   ObjectMapper objectMapper = new ObjectMapper();
 
-  @Autowired
-  private MockMvc mockMvc;
-  @MockBean
-  private FileReportDtoMapper mapper;
-  @MockBean
-  private FileReportV2DtoMapper mapperV2;
-  @MockBean
-  private FileReportService fileReportService;
+  @Autowired private MockMvc mockMvc;
+  @Autowired FileReportControllerImpl frControllerImpl;
+
+  @MockitoBean private FileReportDtoMapper mapper;
+  @MockitoBean private FileReportV2DtoMapper mapperV2;
+  @MockitoBean private FileReportService fileReportService;
 
   @SneakyThrows
   @Test
   void givenEmptyReportWhenGetFileReportThenReturnEmptyListJson() {
     var emptyReportDto = new FileReportDto();
     emptyReportDto.setFilesRecentlyUploaded(Collections.emptyList());
-    Mockito.when(mapper.fileReportToDto(any()))
-        .thenReturn(emptyReportDto);
+    Mockito.when(mapper.fileReportToDto(any())).thenReturn(emptyReportDto);
 
     FileReportDto fileReportDto = new FileReportDto();
     fileReportDto.setFilesRecentlyUploaded(new HashSet<>());
     String emptyFileReportAsJson = objectMapper.writeValueAsString(fileReportDto);
 
-    mockMvc.perform(MockMvcRequestBuilders.get(FILE_REPORT_URL).param("senderCodes", "12345"))
-        .andExpectAll(status().isOk(),
+    mockMvc
+        .perform(MockMvcRequestBuilders.get(fileReportUrl).param("senderCodes", "12345"))
+        .andExpectAll(
+            status().isOk(),
             content().contentType(MediaType.APPLICATION_JSON_VALUE),
             content().string(emptyFileReportAsJson))
         .andReturn();
@@ -75,13 +77,13 @@ void givenReportWhenGetFileReportThenReturnCorrectJson() {
     var reportDto = Mappers.getMapper(FileReportDtoMapper.class).fileReportToDto(reportMock);
     Mockito.when(mapper.fileReportToDto(any())).thenReturn(reportDto);
 
-    MvcResult result = mockMvc.perform(
-            MockMvcRequestBuilders.get(FILE_REPORT_URL).param("senderCodes", "12345"))
-        .andExpectAll(status().isOk(),
-            content().contentType(MediaType.APPLICATION_JSON_VALUE))
-        .andReturn();
-    FileReportDto fileReportResponse = objectMapper.readValue(
-        result.getResponse().getContentAsString(), FileReportDto.class);
+    MvcResult result =
+        mockMvc
+            .perform(MockMvcRequestBuilders.get(fileReportUrl).param("senderCodes", "12345"))
+            .andExpectAll(status().isOk(), content().contentType(MediaType.APPLICATION_JSON_VALUE))
+            .andReturn();
+    FileReportDto fileReportResponse =
+        objectMapper.readValue(result.getResponse().getContentAsString(), FileReportDto.class);
 
     assertThat(fileReportResponse.getFilesRecentlyUploaded()).isNotNull().hasSize(2);
   }
@@ -92,25 +94,31 @@ void givenNoQueryParamsWhenGetFileReportThenReturn400() {
     Mockito.when(fileReportService.getAggregateFileReport(any()))
         .thenReturn(FileReport.createFileReport());
 
-    mockMvc.perform(MockMvcRequestBuilders.get(FILE_REPORT_URL))
+    mockMvc
+        .perform(MockMvcRequestBuilders.get(fileReportUrl))
         .andExpect(status().isBadRequest())
         .andReturn();
   }
 
   @Test
   void whenGetAdeAckToDownloadThenReturns200AndCorrectBody() throws Exception {
-    String senderAdeAckFileName1 = "ADEACK.99999.12345.2022-09-13.709f29ed-2a34-4c73-9a23-397e2e768ecf.csv";
-    String senderAdeAckFileName2 = "ADEACK.11111.12345.2022-09-13.709f29ed-2a34-4c73-9a23-397e2e768ecf.csv";
+    String senderAdeAckFileName1 =
+        "ADEACK.99999.12345.2022-09-13.709f29ed-2a34-4c73-9a23-397e2e768ecf.csv";
+    String senderAdeAckFileName2 =
+        "ADEACK.11111.12345.2022-09-13.709f29ed-2a34-4c73-9a23-397e2e768ecf.csv";
     Mockito.when(fileReportService.getAckToDownloadList(any()))
         .thenReturn(Sets.set(senderAdeAckFileName1, senderAdeAckFileName2));
 
-    MvcResult result = mockMvc.perform(MockMvcRequestBuilders
-            .get(SENDER_ADE_ACK_URL)
-            .contentType(MediaType.APPLICATION_JSON_VALUE)
-            .param("senderCodes", "99999", "11111")
-            .accept(MediaType.APPLICATION_JSON_VALUE))
-        .andExpect(status().isOk())
-        .andReturn();
+    String senderAdeAckUrl = "/sender-ade-ack";
+    MvcResult result =
+        mockMvc
+            .perform(
+                MockMvcRequestBuilders.get(senderAdeAckUrl)
+                    .contentType(MediaType.APPLICATION_JSON_VALUE)
+                    .param("senderCodes", "99999", "11111")
+                    .accept(MediaType.APPLICATION_JSON_VALUE))
+            .andExpect(status().isOk())
+            .andReturn();
 
     assertEquals(
         "{\"fileNameList\":[\"" + senderAdeAckFileName1 + "\",\"" + senderAdeAckFileName2 + "\"]}",
@@ -127,21 +135,22 @@ void givenReportWhenGetFileReportV2ThenReturnCorrectJson() {
     var reportDto = Mappers.getMapper(FileReportV2DtoMapper.class).fileReportToDto(reportMock);
     reportDto.getFilesRecentlyUploaded().stream()
         .findAny()
-        .ifPresent(file -> file.setDataSummary(
-            AggregatesDataSummary.builder()
-                .countPositiveTransactions(10)
-                .sumAmountPositiveTransactions(1000)
-                .build()));
+        .ifPresent(
+            file ->
+                file.setDataSummary(
+                    AggregatesDataSummary.builder()
+                        .countPositiveTransactions(10)
+                        .sumAmountPositiveTransactions(1000)
+                        .build()));
     Mockito.when(mapperV2.fileReportToDto(any())).thenReturn(reportDto);
 
-    MvcResult result = mockMvc.perform(
-            MockMvcRequestBuilders.get(FILE_REPORT_URL_V2).param("senderCodes", "12345"))
-        .andExpectAll(status().isOk(),
-            content().contentType(MediaType.APPLICATION_JSON_VALUE))
-        .andReturn();
-    FileReportV2Dto fileReportResponse = objectMapper.readValue(
-        result.getResponse().getContentAsString(),
-        FileReportV2Dto.class);
+    MvcResult result =
+        mockMvc
+            .perform(MockMvcRequestBuilders.get(fileReportUrlV2).param("senderCodes", "12345"))
+            .andExpectAll(status().isOk(), content().contentType(MediaType.APPLICATION_JSON_VALUE))
+            .andReturn();
+    FileReportV2Dto fileReportResponse =
+        objectMapper.readValue(result.getResponse().getContentAsString(), FileReportV2Dto.class);
 
     assertThat(fileReportResponse.getFilesRecentlyUploaded()).isNotNull().hasSize(1);
     var file = fileReportResponse.getFilesRecentlyUploaded().stream().findAny();
@@ -158,8 +167,60 @@ void givenNoQueryParamsWhenGetFileReportV2ThenReturn400() {
     Mockito.when(fileReportService.getAggregateFileReport(any()))
         .thenReturn(FileReport.createFileReport());
 
-    mockMvc.perform(MockMvcRequestBuilders.get(FILE_REPORT_URL_V2))
+    mockMvc
+        .perform(MockMvcRequestBuilders.get(fileReportUrlV2))
         .andExpect(status().isBadRequest())
         .andReturn();
   }
+
+  @SneakyThrows
+  @Test
+  void givenValidFilenameWhenSaveMetadataEndpointThenStatusOkAndServiceCalled() {
+    String basePath = "myBasePath";
+    String fileName = "ADE.12345.TRNLOG.20230101.130000.001.01.csv.pgp";
+
+    mockMvc
+        .perform(
+            MockMvcRequestBuilders.patch("/report/metadata")
+                .param("basePath", basePath)
+                .param("fileName", fileName))
+        .andExpect(status().isOk());
+
+    Mockito.verify(fileReportService).saveMetadata(basePath, fileName);
+  }
+
+  @SneakyThrows
+  @Test
+  void whenFileNameIsNotValidThenThrowMalformedParameter() {
+    String basePath = "myBasePath";
+    String fileName = "ABCD.123.TRNLOG.20230101.130000.001.01.csv.pgp";
+
+    assertThatThrownBy(() -> frControllerImpl.saveMetadata(basePath, fileName))
+        .isInstanceOf(InputMismatchException.class)
+        .hasMessageContaining("Filename " + fileName + " malformed");
+  }
+
+  @Test
+  void testValidateFilenameMatcher1ThenTrue() {
+    String fileName = "ADE.12300.TRNLOG.20230101.130000.001.csv.pgp";
+    assertTrue(frControllerImpl.validateFileName(fileName));
+  }
+
+  @Test
+  void testValidateFilenameMatcher1ThenFalse() {
+    String fileName = "ABCD.123.TRNLOG.20230101.130000.001.csv.pgp";
+    assertFalse(frControllerImpl.validateFileName(fileName));
+  }
+
+  @Test
+  void testValidateFilenameMatcher2ThenTrue() {
+    String fileName = "ADE.12300.TRNLOG.20230101.130000.001.01.csv.pgp";
+    assertTrue(frControllerImpl.validateFileName(fileName));
+  }
+
+  @Test
+  void testValidateFilenameMatcher2ThenFalse() {
+    String fileName = "ABCD.123.TRNLOG.20230101.130000.001.01.csv.pgp";
+    assertFalse(frControllerImpl.validateFileName(fileName));
+  }
 }
diff --git a/src/test/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/controller/RestExceptionHandlerTest.java b/src/test/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/controller/RestExceptionHandlerTest.java
new file mode 100644
index 00000000..94439f6d
--- /dev/null
+++ b/src/test/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/controller/RestExceptionHandlerTest.java
@@ -0,0 +1,65 @@
+package it.gov.pagopa.rtd.ms.rtdmsfilereporter.controller;
+
+import it.gov.pagopa.rtd.ms.rtdmsfilereporter.domain.exception.FileMetadataNotFoundException;
+import lombok.SneakyThrows;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+
+import java.util.InputMismatchException;
+import java.util.NoSuchElementException;
+
+class RestExceptionHandlerTest {
+
+  @Mock RestExceptionHandler restExceptionHandler;
+  AutoCloseable autoCloseable;
+
+  @BeforeEach
+  void setUp() {
+    autoCloseable = MockitoAnnotations.openMocks(this);
+    restExceptionHandler = new RestExceptionHandler();
+  }
+
+  @SneakyThrows
+  @AfterEach
+  void tearDown() {
+    autoCloseable.close();
+  }
+
+  @Test
+  void testHandleFileMetadataNotFound() {
+    FileMetadataNotFoundException exception =
+        new FileMetadataNotFoundException("File metadata not found");
+
+    ResponseEntity<String> response = restExceptionHandler.handleFileMetadataNotFound(exception);
+
+    Assertions.assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode());
+    Assertions.assertEquals("File metadata not found", response.getBody());
+  }
+
+  @Test
+  void testHandleNoSuchElementFound() {
+    NoSuchElementException exception = new NoSuchElementException("File metadata not found");
+
+    ResponseEntity<String> response = restExceptionHandler.handleNoSuchElement(exception);
+
+    Assertions.assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
+    Assertions.assertEquals("File metadata not found", response.getBody());
+  }
+
+  @Test
+  void testHandleInputMismatch() {
+    InputMismatchException exception =
+        new InputMismatchException("Filename  malformed");
+
+    ResponseEntity<String> response = restExceptionHandler.handleInputMismatch(exception);
+
+    Assertions.assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
+    Assertions.assertEquals("Filename  malformed", response.getBody());
+  }
+}
diff --git a/src/test/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/service/FileReportServiceImplTest.java b/src/test/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/service/FileReportServiceImplTest.java
index d498ee86..00371df6 100644
--- a/src/test/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/service/FileReportServiceImplTest.java
+++ b/src/test/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/service/FileReportServiceImplTest.java
@@ -2,18 +2,28 @@
 
 import static it.gov.pagopa.rtd.ms.rtdmsfilereporter.TestUtils.createFileReport;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.verify;
 
+import it.gov.pagopa.rtd.ms.rtdmsfilereporter.domain.exception.FileMetadataNotFoundException;
+import it.gov.pagopa.rtd.ms.rtdmsfilereporter.domain.model.FileMetadata;
 import it.gov.pagopa.rtd.ms.rtdmsfilereporter.domain.model.FileReport;
+import it.gov.pagopa.rtd.ms.rtdmsfilereporter.domain.model.AggregatesDataSummary;
+
 import it.gov.pagopa.rtd.ms.rtdmsfilereporter.domain.repository.FileReportRepository;
 import it.gov.pagopa.rtd.ms.rtdmsfilereporter.domain.service.FileReportService;
 import it.gov.pagopa.rtd.ms.rtdmsfilereporter.domain.service.FileReportServiceImpl;
+import it.gov.pagopa.rtd.ms.rtdmsfilereporter.domain.service.StorageAccountService;
+
 import java.util.Collection;
 import java.util.Collections;
+import java.util.NoSuchElementException;
 import java.util.Optional;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
+
+import it.gov.pagopa.rtd.ms.rtdmsfilereporter.feign.config.ReportConfiguration;
 import lombok.SneakyThrows;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
@@ -24,8 +34,12 @@
 
 class FileReportServiceImplTest {
 
-  @Mock
-  FileReportRepository fileReportRepository;
+  @Mock FileReportRepository fileReportRepository;
+
+  @Mock StorageAccountService storageAccountService;
+
+  private final ReportConfiguration reportConfiguration = new ReportConfiguration(15);
+
   FileReportService fileReportService;
   AutoCloseable autoCloseable;
 
@@ -33,7 +47,8 @@ class FileReportServiceImplTest {
   void setUp() {
     autoCloseable = MockitoAnnotations.openMocks(this);
 
-    fileReportService = new FileReportServiceImpl(fileReportRepository);
+    fileReportService =
+        new FileReportServiceImpl(fileReportRepository, storageAccountService, reportConfiguration);
   }
 
   @SneakyThrows
@@ -46,8 +61,8 @@ void tearDown() {
   void whenGetFileReportForManySenderCodesThenMergeTheReportsCorrectly() {
     Mockito.when(fileReportRepository.getReportsBySenderCodes(any())).thenReturn(getReportList());
 
-    FileReport filereport = fileReportService.getAggregateFileReport(
-        Collections.singleton("12345"));
+    FileReport filereport =
+        fileReportService.getAggregateFileReport(Collections.singleton("12345"));
 
     assertThat(filereport).isNotNull();
     assertThat(filereport.getFilesUploaded()).isNotNull().hasSize(4);
@@ -59,8 +74,8 @@ void givenEmptyListWhenGetFileReportForManySenderCodesThenReturnsEmptyReport() {
     Mockito.when(fileReportRepository.getReportsBySenderCodes(any()))
         .thenReturn(Collections.emptyList());
 
-    FileReport filereport = fileReportService.getAggregateFileReport(
-        Collections.singleton("12345"));
+    FileReport filereport =
+        fileReportService.getAggregateFileReport(Collections.singleton("12345"));
 
     assertThat(filereport).isNotNull();
     assertThat(filereport.getFilesUploaded()).isNotNull().isEmpty();
@@ -69,12 +84,11 @@ void givenEmptyListWhenGetFileReportForManySenderCodesThenReturnsEmptyReport() {
 
   @Test
   void whenGetFileReportThenReturnsAReport() {
-    Mockito.when(fileReportRepository.getReportBySenderCode(any()))
-        .thenReturn(Optional.empty());
+    Mockito.when(fileReportRepository.getReportBySenderCode(any())).thenReturn(Optional.empty());
 
-    var filereport = fileReportService.getFileReport("12345");
+    var fileReport = fileReportService.getFileReport("12345");
 
-    assertThat(filereport).isEmpty();
+    assertThat(fileReport).isEmpty();
   }
 
   @Test
@@ -91,10 +105,11 @@ void whenGetAckToDownloadThenReturnsACollectionOfStrings() {
 
     var ackToDownloadList = fileReportService.getAckToDownloadList(Collections.singleton("12345"));
 
-    var expectedList = reportMock.stream()
-        .map(FileReport::getAckToDownload)
-        .flatMap(Collection::stream)
-        .collect(Collectors.toSet());
+    var expectedList =
+        reportMock.stream()
+            .map(FileReport::getAckToDownload)
+            .flatMap(Collection::stream)
+            .collect(Collectors.toSet());
     assertThat(ackToDownloadList)
         .hasSize(expectedList.size())
         .containsExactlyInAnyOrderElementsOf(expectedList);
@@ -102,7 +117,71 @@ void whenGetAckToDownloadThenReturnsACollectionOfStrings() {
 
   Collection<FileReport> getReportList() {
     return Stream.of(createFileReport(3, 1), createFileReport(1, 2), createFileReport(2, 1))
-        .collect(Collectors.toList());
+        .toList();
+  }
+
+  @Test
+  void whenSaveMetadataGivenWellFormedFilenameThenExtractSenderCodeAndSave() {
+    String basePath = "basePath";
+    String fileName = "ADE.12345.TRNLOG.20230101.130000.001.01.csv";
+    String senderCode = "12345";
+
+    FileReport fileReport = FileReport.createFileReportWithSenderCode(senderCode);
+
+    var fileMetadata = FileMetadata.createNewFileMetadata(fileName);
+    fileReport.addFileOrUpdateStatusIfPresent(fileMetadata);
+
+    Mockito.when(fileReportService.getFileReport(senderCode)).thenReturn(Optional.of(fileReport));
+
+    AggregatesDataSummary dataSummaryMock =
+        AggregatesDataSummary.builder()
+            .countPositiveTransactions(100)
+            .sumAmountPositiveTransactions(1000)
+            .build();
+    Mockito.when(storageAccountService.getMetadata("/" + basePath + "/", fileName))
+        .thenReturn(dataSummaryMock);
+
+    fileReportService.saveMetadata(basePath, fileName);
+
+    Mockito.verify(fileReportRepository).save(fileReport);
+    assertThat(fileMetadata.getPath()).isEqualTo("/" + basePath + "/");
+  }
+
+  @Test
+  void whenSaveMetadataGivenNoMatchingFileMetadataThenThrowNotFound() {
+
+    String basePath = "basePath";
+    String fileName = "ADE.12345.TRNLOG.20230101.130000.001.01.csv";
+    String senderCode = "12345";
+
+    FileReport fileReport = FileReport.createFileReportWithSenderCode(senderCode);
+
+    Mockito.when(fileReportService.getFileReport(senderCode)).thenReturn(Optional.of(fileReport));
+
+    assertThatThrownBy(() -> fileReportService.saveMetadata(basePath, fileName))
+        .isInstanceOf(FileMetadataNotFoundException.class)
+        .hasMessageContaining("not found in latest")
+        .hasMessageContaining(fileName)
+        .hasMessageContaining(senderCode);
+
+    Mockito.verify(fileReportRepository, Mockito.never()).save(any());
   }
 
-}
\ No newline at end of file
+  @Test
+  void whenSaveMetadataGivenNotWellFormedFilenameThenThrowException() {
+    String basePath = "basePath";
+    String fileName = "ADE.12345.TRNLOG.20230101.130000.001.01.csv";
+    String senderCode = "09876";
+
+    FileReport fileReport = FileReport.createFileReportWithSenderCode(senderCode);
+
+    String senderCodeFromFile = fileName.split("\\.")[1];
+    Mockito.when(fileReportService.getFileReport(senderCode)).thenReturn(Optional.of(fileReport));
+    assertThatThrownBy(() -> fileReportService.saveMetadata(basePath, fileName))
+        .isInstanceOf(NoSuchElementException.class)
+        .hasMessageContaining("FileReport not found for sender")
+        .hasMessageContaining(senderCodeFromFile);
+
+    Mockito.verify(fileReportRepository, Mockito.never()).save(any());
+  }
+}


Reviews:
user: login: github-advanced-security[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-01-31T11:27:21Z
user: login: and-mora
body: use [java google style](https://google.github.io/styleguide/javaguide.html)
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-12T16:38:20Z
user: login: and-mora
body: for the PR to be mergeable the following points must be addressed:
- java google style to apply to all files changed (+334 −127 rows changed at the moment which 1/3 are formatting changes)
- 14 sonar issues to be fixed
- negative test cases to implement
- api is not compliant to restFul principle
- method names are misleading
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-19T12:24:48Z
user: login: and-mora
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-25T15:39:50Z
user: login: and-mora
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-25T15:45:38Z
user: login: carava-dt
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-26T11:45:04Z
user: login: carava-dt
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-26T13:11:05Z
user: login: carava-dt
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-26T13:17:47Z
user: login: carava-dt
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-26T13:52:34Z
user: login: carava-dt
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-26T13:54:33Z
user: login: carava-dt
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-26T14:14:38Z
user: login: and-mora
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-26T15:19:50Z
user: login: TommasoLencioni
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-26T16:04:21Z
user: login: carava-dt
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-27T10:29:24Z
user: login: carava-dt
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T09:07:22Z
user: login: and-mora
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T09:13:10Z
user: login: carava-dt
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T09:48:06Z
user: login: carava-dt
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T11:52:08Z
user: login: carava-dt
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T11:55:25Z
user: login: and-mora
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T13:56:32Z
user: login: and-mora
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T15:02:27Z
user: login: carava-dt
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T11:13:29Z
user: login: carava-dt
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T11:54:28Z
user: login: TommasoLencioni
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T14:28:00Z
user: login: and-mora
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T14:45:38Z

Review comments:
diff_hunk: @@ -40,4 +40,10 @@
         .fileNameList(fileReportService.getAckToDownloadList(senderCodes))
         .build();
   }
+
+  @Override
+  public void getMetadata(@NotNull String basePath, @NotNull String fileName) {
+    log.info("Enrich metadata on file : {}", fileName);
path: src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/controller/FileReportControllerImpl.java
user: login: github-advanced-security[bot]
body: ## Log Injection

This log entry depends on a [user-provided value](1).

[Show more details](https://github.com/pagopa/rtd-ms-file-reporter/security/code-scanning/210)
created_at: 2025-01-31T11:27:21Z
author_association: NONE
diff_hunk: @@ -23,7 +23,8 @@
     try {
       response = connector.getBlobMetadata(basePath, fileName);
     } catch (IOException e) {
-      log.warn("Failed to retrieve the file metadata from the storage! Error: {}", e.getMessage());
+
+      log.warn("Failed to retrieve the file metadata from the storage! Filename {} Error: {}", basePath.concat(fileName), e.getMessage());
path: src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/domain/service/StorageAccountService.java
user: login: github-advanced-security[bot]
body: ## Log Injection

This log entry depends on a [user-provided value](1).
This log entry depends on a [user-provided value](2).

[Show more details](https://github.com/pagopa/rtd-ms-file-reporter/security/code-scanning/211)
created_at: 2025-01-31T11:27:21Z
author_association: NONE
diff_hunk: @@ -40,4 +44,24 @@ public SenderAdeAckListDto getSenderAdeAckList(@NotNull Collection<String> sende
         .fileNameList(fileReportService.getAckToDownloadList(senderCodes))
         .build();
   }
+
+  @Override
+  public void saveMetadata(String basePath, String fileName) throws MalformedURLException {
+    String sanitizedFileName = fileName.replace("\n", "").replace("\r", "");
+    if (validateFileName(sanitizedFileName)) {
+      log.info("Enrich metadata on file : {}", sanitizedFileName);
+      fileReportService.saveMetadata(basePath, fileName);
path: src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/controller/FileReportControllerImpl.java
user: login: and-mora
body: why sanitize the input data if is it not used here?
created_at: 2025-02-25T15:11:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -40,4 +44,24 @@ public SenderAdeAckListDto getSenderAdeAckList(@NotNull Collection<String> sende
         .fileNameList(fileReportService.getAckToDownloadList(senderCodes))
         .build();
   }
+
+  @Override
+  public void saveMetadata(String basePath, String fileName) throws MalformedURLException {
+    String sanitizedFileName = fileName.replace("\n", "").replace("\r", "");
path: src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/controller/FileReportControllerImpl.java
user: login: and-mora
body: what's the utility of this 'sanitization' if later the string pass through a regex? Are we sure that we want to change the input string?
created_at: 2025-02-25T15:13:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -40,4 +44,24 @@ public SenderAdeAckListDto getSenderAdeAckList(@NotNull Collection<String> sende
         .fileNameList(fileReportService.getAckToDownloadList(senderCodes))
         .build();
   }
+
+  @Override
+  public void saveMetadata(String basePath, String fileName) throws MalformedURLException {
+    String sanitizedFileName = fileName.replace("\n", "").replace("\r", "");
+    if (validateFileName(sanitizedFileName)) {
+      log.info("Enrich metadata on file : {}", sanitizedFileName);
+      fileReportService.saveMetadata(basePath, fileName);
+    } else {
+      throw new MalformedURLException("Filename " + fileName + " malformed");
path: src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/controller/FileReportControllerImpl.java
user: login: and-mora
body: I can't find a test that assert which status code is returned when the validation routine fails
created_at: 2025-02-25T15:15:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -35,4 +44,39 @@ public Collection<String> getAckToDownloadList(Collection<String> senderCodes) {
   public void save(FileReport fileReport) {
     fileReportRepository.save(fileReport);
   }
+
+  @Override
+  public void saveMetadata(String basePath, String fileName) {
+    // get senderCode from filename
+    String senderCode = fileName.split("\\.")[1];
+    FileReport fileReport =
+        getFileReport(senderCode).orElse(FileReport.createFileReportWithSenderCode(senderCode));
path: src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/domain/service/FileReportServiceImpl.java
user: login: and-mora
body: what's the point in creating a new filereport if there is none found in the database?
created_at: 2025-02-25T15:23:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -35,4 +44,39 @@ public Collection<String> getAckToDownloadList(Collection<String> senderCodes) {
   public void save(FileReport fileReport) {
     fileReportRepository.save(fileReport);
   }
+
+  @Override
+  public void saveMetadata(String basePath, String fileName) {
+    // get senderCode from filename
+    String senderCode = fileName.split("\\.")[1];
+    FileReport fileReport =
+        getFileReport(senderCode).orElse(FileReport.createFileReportWithSenderCode(senderCode));
+
+    Optional<FileMetadata> result =
+        fileReport.getFilesUploaded().stream()
+            .filter(f -> f.getName().equals(fileName))
+            .findFirst();
+
+    FileMetadata fileMetadata;
+    if (result.isEmpty()) {
+      String errorMsg =
+          String.format(
+              "File %s not found in latest %d days report of sender %s",
+              fileName, report.fileTTL(), senderCode);
+      throw new FileMetadataNotFoundException(errorMsg);
+    } else fileMetadata = result.get();
+
+    String path = File.separator + basePath + File.separator;
+    fileMetadata.setPath(path);
+    var dataSummary = service.getMetadata(path, fileName);
+
+    log.debug("DataSummary : {}", dataSummary.toString());
+
+    fileMetadata.enrichWithSquaringData(dataSummary);
+    // two operations are needed: update status + add aggregates summary
+    fileReport.addFileOrUpdateStatusIfPresent(fileMetadata);
+    fileReport.addSquaringDataToFile(fileMetadata);
path: src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/domain/service/FileReportServiceImpl.java
user: login: and-mora
body: what's the purpose of this 2 rows? isn't `fileMetadata` already in the `fileReport`?
created_at: 2025-02-25T15:38:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,13 @@
+package it.gov.pagopa.rtd.ms.rtdmsfilereporter.feign.config;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@ConfigurationProperties(prefix = "report")
+public record ReportFileTTL
path: src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/feign/config/ReportFileTTL.java
user: login: and-mora
body: I found `ReportConfiguration` to be much more clear and extensible. Do you agree?
created_at: 2025-02-25T15:44:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -40,4 +44,24 @@ public SenderAdeAckListDto getSenderAdeAckList(@NotNull Collection<String> sende
         .fileNameList(fileReportService.getAckToDownloadList(senderCodes))
         .build();
   }
+
+  @Override
+  public void saveMetadata(String basePath, String fileName) throws MalformedURLException {
+    String sanitizedFileName = fileName.replace("\n", "").replace("\r", "");
+    if (validateFileName(sanitizedFileName)) {
+      log.info("Enrich metadata on file : {}", sanitizedFileName);
+      fileReportService.saveMetadata(basePath, fileName);
path: src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/controller/FileReportControllerImpl.java
user: login: carava-dt
body: It's a typo
created_at: 2025-02-26T11:45:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -40,4 +44,24 @@ public SenderAdeAckListDto getSenderAdeAckList(@NotNull Collection<String> sende
         .fileNameList(fileReportService.getAckToDownloadList(senderCodes))
         .build();
   }
+
+  @Override
+  public void saveMetadata(String basePath, String fileName) throws MalformedURLException {
+    String sanitizedFileName = fileName.replace("\n", "").replace("\r", "");
path: src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/controller/FileReportControllerImpl.java
user: login: carava-dt
body: The sanitization was done to fix an issue on the fileName input parameter to avoid SSRF or injection attacks
created_at: 2025-02-26T13:11:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -40,4 +44,24 @@ public SenderAdeAckListDto getSenderAdeAckList(@NotNull Collection<String> sende
         .fileNameList(fileReportService.getAckToDownloadList(senderCodes))
         .build();
   }
+
+  @Override
+  public void saveMetadata(String basePath, String fileName) throws MalformedURLException {
+    String sanitizedFileName = fileName.replace("\n", "").replace("\r", "");
+    if (validateFileName(sanitizedFileName)) {
+      log.info("Enrich metadata on file : {}", sanitizedFileName);
+      fileReportService.saveMetadata(basePath, fileName);
+    } else {
+      throw new MalformedURLException("Filename " + fileName + " malformed");
path: src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/controller/FileReportControllerImpl.java
user: login: carava-dt
body: The test is implemented in the file FileReportControllerImplTest.whenFileNameIsNotValidThenThrowMalformed
created_at: 2025-02-26T13:17:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -35,4 +44,39 @@ public Collection<String> getAckToDownloadList(Collection<String> senderCodes) {
   public void save(FileReport fileReport) {
     fileReportRepository.save(fileReport);
   }
+
+  @Override
+  public void saveMetadata(String basePath, String fileName) {
+    // get senderCode from filename
+    String senderCode = fileName.split("\\.")[1];
+    FileReport fileReport =
+        getFileReport(senderCode).orElse(FileReport.createFileReportWithSenderCode(senderCode));
path: src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/domain/service/FileReportServiceImpl.java
user: login: carava-dt
body: This is a typo. In the first version we thought we would have to create it if not found.
created_at: 2025-02-26T13:52:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -35,4 +44,39 @@ public Collection<String> getAckToDownloadList(Collection<String> senderCodes) {
   public void save(FileReport fileReport) {
     fileReportRepository.save(fileReport);
   }
+
+  @Override
+  public void saveMetadata(String basePath, String fileName) {
+    // get senderCode from filename
+    String senderCode = fileName.split("\\.")[1];
+    FileReport fileReport =
+        getFileReport(senderCode).orElse(FileReport.createFileReportWithSenderCode(senderCode));
+
+    Optional<FileMetadata> result =
+        fileReport.getFilesUploaded().stream()
+            .filter(f -> f.getName().equals(fileName))
+            .findFirst();
+
+    FileMetadata fileMetadata;
+    if (result.isEmpty()) {
+      String errorMsg =
+          String.format(
+              "File %s not found in latest %d days report of sender %s",
+              fileName, report.fileTTL(), senderCode);
+      throw new FileMetadataNotFoundException(errorMsg);
+    } else fileMetadata = result.get();
+
+    String path = File.separator + basePath + File.separator;
+    fileMetadata.setPath(path);
+    var dataSummary = service.getMetadata(path, fileName);
+
+    log.debug("DataSummary : {}", dataSummary.toString());
+
+    fileMetadata.enrichWithSquaringData(dataSummary);
+    // two operations are needed: update status + add aggregates summary
+    fileReport.addFileOrUpdateStatusIfPresent(fileMetadata);
+    fileReport.addSquaringDataToFile(fileMetadata);
path: src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/domain/service/FileReportServiceImpl.java
user: login: carava-dt
body: No, filemetadata is not present on fileReport. The purpose of this api is to retrieve filemetadata and save it on filereport.
created_at: 2025-02-26T13:54:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,13 @@
+package it.gov.pagopa.rtd.ms.rtdmsfilereporter.feign.config;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@ConfigurationProperties(prefix = "report")
+public record ReportFileTTL
path: src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/feign/config/ReportFileTTL.java
user: login: carava-dt
body: Yes, I agree. I'll change the class name.
created_at: 2025-02-26T14:14:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -35,4 +44,38 @@ public Collection<String> getAckToDownloadList(Collection<String> senderCodes) {
   public void save(FileReport fileReport) {
     fileReportRepository.save(fileReport);
   }
+
+  @Override
+  public void saveMetadata(String basePath, String fileName) {
+    // get senderCode from filename
+    String senderCode = fileName.split("\\.")[1];
+    FileReport fileReport = getFileReport(senderCode).orElseThrow();
path: src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/domain/service/FileReportServiceImpl.java
user: login: and-mora
body: what's the exception thrown? and where is handled?
created_at: 2025-02-26T14:29:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -40,4 +44,24 @@ public SenderAdeAckListDto getSenderAdeAckList(@NotNull Collection<String> sende
         .fileNameList(fileReportService.getAckToDownloadList(senderCodes))
         .build();
   }
+
+  @Override
+  public void saveMetadata(String basePath, String fileName) throws MalformedURLException {
+    String sanitizedFileName = fileName.replace("\n", "").replace("\r", "");
+    if (validateFileName(sanitizedFileName)) {
+      log.info("Enrich metadata on file : {}", sanitizedFileName);
+      fileReportService.saveMetadata(basePath, fileName);
+    } else {
+      throw new MalformedURLException("Filename " + fileName + " malformed");
path: src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/controller/FileReportControllerImpl.java
user: login: and-mora
body: and which is the status code returned to the client?
created_at: 2025-02-26T14:34:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,23 +1,32 @@
 package it.gov.pagopa.rtd.ms.rtdmsfilereporter.domain.service;
 
+import it.gov.pagopa.rtd.ms.rtdmsfilereporter.domain.exception.FileMetadataNotFoundException;
+
 import static it.gov.pagopa.rtd.ms.rtdmsfilereporter.domain.model.FileReportAggregator.aggregateFileReports;
 
+import it.gov.pagopa.rtd.ms.rtdmsfilereporter.domain.model.FileMetadata;
 import it.gov.pagopa.rtd.ms.rtdmsfilereporter.domain.model.FileReport;
 import it.gov.pagopa.rtd.ms.rtdmsfilereporter.domain.repository.FileReportRepository;
+
+import java.io.File;
 import java.util.Collection;
 import java.util.Optional;
-import lombok.RequiredArgsConstructor;
 
+import it.gov.pagopa.rtd.ms.rtdmsfilereporter.feign.config.ReportConfiguration;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 
+@Slf4j
 @RequiredArgsConstructor
 public class FileReportServiceImpl implements FileReportService {
 
   private final FileReportRepository fileReportRepository;
+  private final StorageAccountService service;
+  private final ReportConfiguration report;
path: src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/domain/service/FileReportServiceImpl.java
user: login: and-mora
body: ```suggestion
  private final ReportConfiguration reportConfig;
```
much more readable, isn't?
created_at: 2025-02-26T14:39:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -35,4 +44,39 @@ public Collection<String> getAckToDownloadList(Collection<String> senderCodes) {
   public void save(FileReport fileReport) {
     fileReportRepository.save(fileReport);
   }
+
+  @Override
+  public void saveMetadata(String basePath, String fileName) {
+    // get senderCode from filename
+    String senderCode = fileName.split("\\.")[1];
+    FileReport fileReport =
+        getFileReport(senderCode).orElse(FileReport.createFileReportWithSenderCode(senderCode));
+
+    Optional<FileMetadata> result =
+        fileReport.getFilesUploaded().stream()
+            .filter(f -> f.getName().equals(fileName))
+            .findFirst();
+
+    FileMetadata fileMetadata;
+    if (result.isEmpty()) {
+      String errorMsg =
+          String.format(
+              "File %s not found in latest %d days report of sender %s",
+              fileName, report.fileTTL(), senderCode);
+      throw new FileMetadataNotFoundException(errorMsg);
+    } else fileMetadata = result.get();
+
+    String path = File.separator + basePath + File.separator;
+    fileMetadata.setPath(path);
+    var dataSummary = service.getMetadata(path, fileName);
+
+    log.debug("DataSummary : {}", dataSummary.toString());
+
+    fileMetadata.enrichWithSquaringData(dataSummary);
+    // two operations are needed: update status + add aggregates summary
+    fileReport.addFileOrUpdateStatusIfPresent(fileMetadata);
+    fileReport.addSquaringDataToFile(fileMetadata);
path: src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/domain/service/FileReportServiceImpl.java
user: login: and-mora
body: at row 60, if the `fileMetadata` is not present in the report then  `FileMetadataNotFoundException` is thrown. In your code the `fileMetadata` is never created from zero and added to a report then row 77 and 78 are useless in the best case.
created_at: 2025-02-26T15:19:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -158,8 +167,36 @@ void givenNoQueryParamsWhenGetFileReportV2ThenReturn400() {
     Mockito.when(fileReportService.getAggregateFileReport(any()))
         .thenReturn(FileReport.createFileReport());
 
-    mockMvc.perform(MockMvcRequestBuilders.get(FILE_REPORT_URL_V2))
+    mockMvc
+        .perform(MockMvcRequestBuilders.get(fileReportUrlV2))
         .andExpect(status().isBadRequest())
         .andReturn();
   }
+
+  @SneakyThrows
+  @Test
+  void givenValidFilenameWhenSaveMetadataEndpointThenStatusOkAndServiceCalled() {
+    String basePath = "myBasePath";
+    String fileName = "ADE.12345.TRNLOG.20230101.130000.001.01.csv.pgp";
+
+    mockMvc
+        .perform(
+            MockMvcRequestBuilders.get("/report/metadata")
path: src/test/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/controller/FileReportControllerImplTest.java
user: login: TommasoLencioni
body: @carava-dt this should be PATCH
created_at: 2025-02-26T16:04:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -158,8 +167,36 @@ void givenNoQueryParamsWhenGetFileReportV2ThenReturn400() {
     Mockito.when(fileReportService.getAggregateFileReport(any()))
         .thenReturn(FileReport.createFileReport());
 
-    mockMvc.perform(MockMvcRequestBuilders.get(FILE_REPORT_URL_V2))
+    mockMvc
+        .perform(MockMvcRequestBuilders.get(fileReportUrlV2))
         .andExpect(status().isBadRequest())
         .andReturn();
   }
+
+  @SneakyThrows
+  @Test
+  void givenValidFilenameWhenSaveMetadataEndpointThenStatusOkAndServiceCalled() {
+    String basePath = "myBasePath";
+    String fileName = "ADE.12345.TRNLOG.20230101.130000.001.01.csv.pgp";
+
+    mockMvc
+        .perform(
+            MockMvcRequestBuilders.get("/report/metadata")
path: src/test/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/controller/FileReportControllerImplTest.java
user: login: carava-dt
body: done
created_at: 2025-02-27T10:29:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -40,4 +44,24 @@ public SenderAdeAckListDto getSenderAdeAckList(@NotNull Collection<String> sende
         .fileNameList(fileReportService.getAckToDownloadList(senderCodes))
         .build();
   }
+
+  @Override
+  public void saveMetadata(String basePath, String fileName) throws MalformedURLException {
+    String sanitizedFileName = fileName.replace("\n", "").replace("\r", "");
+    if (validateFileName(sanitizedFileName)) {
+      log.info("Enrich metadata on file : {}", sanitizedFileName);
+      fileReportService.saveMetadata(basePath, fileName);
+    } else {
+      throw new MalformedURLException("Filename " + fileName + " malformed");
path: src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/controller/FileReportControllerImpl.java
user: login: carava-dt
body: In this case it returns a http error 500. Maybe it is better to implement a more user-friendly error message. In fact it is not clear like this.
created_at: 2025-03-03T09:07:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -40,4 +44,24 @@ public SenderAdeAckListDto getSenderAdeAckList(@NotNull Collection<String> sende
         .fileNameList(fileReportService.getAckToDownloadList(senderCodes))
         .build();
   }
+
+  @Override
+  public void saveMetadata(String basePath, String fileName) throws MalformedURLException {
+    String sanitizedFileName = fileName.replace("\n", "").replace("\r", "");
+    if (validateFileName(sanitizedFileName)) {
+      log.info("Enrich metadata on file : {}", sanitizedFileName);
+      fileReportService.saveMetadata(basePath, fileName);
+    } else {
+      throw new MalformedURLException("Filename " + fileName + " malformed");
path: src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/controller/FileReportControllerImpl.java
user: login: and-mora
body: Is http error 500 what the user should expect for this kind of error?
created_at: 2025-03-03T09:13:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -35,4 +44,39 @@ public Collection<String> getAckToDownloadList(Collection<String> senderCodes) {
   public void save(FileReport fileReport) {
     fileReportRepository.save(fileReport);
   }
+
+  @Override
+  public void saveMetadata(String basePath, String fileName) {
+    // get senderCode from filename
+    String senderCode = fileName.split("\\.")[1];
+    FileReport fileReport =
+        getFileReport(senderCode).orElse(FileReport.createFileReportWithSenderCode(senderCode));
+
+    Optional<FileMetadata> result =
+        fileReport.getFilesUploaded().stream()
+            .filter(f -> f.getName().equals(fileName))
+            .findFirst();
+
+    FileMetadata fileMetadata;
+    if (result.isEmpty()) {
+      String errorMsg =
+          String.format(
+              "File %s not found in latest %d days report of sender %s",
+              fileName, report.fileTTL(), senderCode);
+      throw new FileMetadataNotFoundException(errorMsg);
+    } else fileMetadata = result.get();
+
+    String path = File.separator + basePath + File.separator;
+    fileMetadata.setPath(path);
+    var dataSummary = service.getMetadata(path, fileName);
+
+    log.debug("DataSummary : {}", dataSummary.toString());
+
+    fileMetadata.enrichWithSquaringData(dataSummary);
+    // two operations are needed: update status + add aggregates summary
+    fileReport.addFileOrUpdateStatusIfPresent(fileMetadata);
+    fileReport.addSquaringDataToFile(fileMetadata);
path: src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/domain/service/FileReportServiceImpl.java
user: login: carava-dt
body: Ok. I remove this row. 
created_at: 2025-03-03T09:48:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -40,4 +44,24 @@ public SenderAdeAckListDto getSenderAdeAckList(@NotNull Collection<String> sende
         .fileNameList(fileReportService.getAckToDownloadList(senderCodes))
         .build();
   }
+
+  @Override
+  public void saveMetadata(String basePath, String fileName) throws MalformedURLException {
+    String sanitizedFileName = fileName.replace("\n", "").replace("\r", "");
+    if (validateFileName(sanitizedFileName)) {
+      log.info("Enrich metadata on file : {}", sanitizedFileName);
+      fileReportService.saveMetadata(basePath, fileName);
+    } else {
+      throw new MalformedURLException("Filename " + fileName + " malformed");
path: src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/controller/FileReportControllerImpl.java
user: login: carava-dt
body: No, I implement the handler for the MalformedParameterException exception, which seems more appropriate to me, returning a httpError 400
created_at: 2025-03-03T11:52:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -35,4 +44,38 @@ public Collection<String> getAckToDownloadList(Collection<String> senderCodes) {
   public void save(FileReport fileReport) {
     fileReportRepository.save(fileReport);
   }
+
+  @Override
+  public void saveMetadata(String basePath, String fileName) {
+    // get senderCode from filename
+    String senderCode = fileName.split("\\.")[1];
+    FileReport fileReport = getFileReport(senderCode).orElseThrow();
path: src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/domain/service/FileReportServiceImpl.java
user: login: carava-dt
body: I added the NoSuchElementFoundException handler with a clear message for the user
created_at: 2025-03-03T11:55:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -35,4 +45,38 @@ public Collection<String> getAckToDownloadList(Collection<String> senderCodes) {
   public void save(FileReport fileReport) {
     fileReportRepository.save(fileReport);
   }
+
+  @Override
+  public void saveMetadata(String basePath, String fileName) {
+    // get senderCode from filename
+    String senderCode = fileName.split("\\.")[1];
+    FileReport fileReport;
+    try {
+      fileReport = getFileReport(senderCode).orElseThrow();
+    } catch (NoSuchElementException e) {
+      String errorMsg = String.format("FileReport not found for sender %s", senderCode);
+      throw new NoSuchElementException(errorMsg);
+    }
path: src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/domain/service/FileReportServiceImpl.java
user: login: and-mora
body: There are 2 exception throws in 3 statements, refactor please
created_at: 2025-03-05T13:28:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -40,4 +44,24 @@ public SenderAdeAckListDto getSenderAdeAckList(@NotNull Collection<String> sende
         .fileNameList(fileReportService.getAckToDownloadList(senderCodes))
         .build();
   }
+
+  @Override
+  public void saveMetadata(String basePath, String fileName) {
+    String sanitizedFileName = fileName.replace("\n", "").replace("\r", "");
+    if (validateFileName(sanitizedFileName)) {
+      log.info("Enrich metadata on file : {}", sanitizedFileName);
+      fileReportService.saveMetadata(basePath, sanitizedFileName);
+    } else {
+      throw new MalformedParametersException("Filename " + sanitizedFileName + " malformed");
path: src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/controller/FileReportControllerImpl.java
user: login: and-mora
body: This exception comes from `java.lang.reflect` package, quoting the docs:
```
Thrown when the java.lang.reflect package attempts to read method parameters from a class file and determines that one or more parameters are malformed.
The following is a list of conditions under which this exception can be thrown:
The number of parameters (parameter_count) is wrong for the method
A constant pool index is out of bounds.
A constant pool index does not refer to a UTF-8 entry
A parameter's name is "", or contains an illegal character
The flags field contains an illegal flag (something other than FINAL, SYNTHETIC, or MANDATED)
```
The use of this exception in a controller context may be very misleading. Can we find a better fit?
created_at: 2025-03-05T15:02:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -35,4 +45,38 @@ public Collection<String> getAckToDownloadList(Collection<String> senderCodes) {
   public void save(FileReport fileReport) {
     fileReportRepository.save(fileReport);
   }
+
+  @Override
+  public void saveMetadata(String basePath, String fileName) {
+    // get senderCode from filename
+    String senderCode = fileName.split("\\.")[1];
+    FileReport fileReport;
+    try {
+      fileReport = getFileReport(senderCode).orElseThrow();
+    } catch (NoSuchElementException e) {
+      String errorMsg = String.format("FileReport not found for sender %s", senderCode);
+      throw new NoSuchElementException(errorMsg);
+    }
path: src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/domain/service/FileReportServiceImpl.java
user: login: carava-dt
body: Done
created_at: 2025-03-07T11:13:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -40,4 +44,24 @@ public SenderAdeAckListDto getSenderAdeAckList(@NotNull Collection<String> sende
         .fileNameList(fileReportService.getAckToDownloadList(senderCodes))
         .build();
   }
+
+  @Override
+  public void saveMetadata(String basePath, String fileName) {
+    String sanitizedFileName = fileName.replace("\n", "").replace("\r", "");
+    if (validateFileName(sanitizedFileName)) {
+      log.info("Enrich metadata on file : {}", sanitizedFileName);
+      fileReportService.saveMetadata(basePath, sanitizedFileName);
+    } else {
+      throw new MalformedParametersException("Filename " + sanitizedFileName + " malformed");
path: src/main/java/it/gov/pagopa/rtd/ms/rtdmsfilereporter/controller/FileReportControllerImpl.java
user: login: carava-dt
body: Ho utilizzato l'exception InputMismatchException al posto di MalformedParametersException.
created_at: 2025-03-07T11:54:28Z
author_association: CONTRIBUTOR

PR comments:
user: login: and-mora
created_at: 2025-02-26T15:46:31Z
body: may you cover the 100% of code conditions please? The ones in the controller are the most important and would need some more tests to effectively test how the application behave
author_association: CONTRIBUTOR
user: login: sonarqubecloud[bot]
created_at: 2025-03-07T11:55:05Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=pagopa_rtd-ms-file-reporter&pullRequest=158) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=pagopa_rtd-ms-file-reporter&pullRequest=158&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=pagopa_rtd-ms-file-reporter&pullRequest=158&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=pagopa_rtd-ms-file-reporter&pullRequest=158&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [100.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=pagopa_rtd-ms-file-reporter&pullRequest=158&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=pagopa_rtd-ms-file-reporter&pullRequest=158&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=pagopa_rtd-ms-file-reporter&pullRequest=158)


author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-12T14:25:15Z
body: :tada: This PR is included in version 2.0.3 :tada:

The release is available on [GitHub release](https://github.com/pagopa/rtd-ms-file-reporter/releases/tag/v2.0.3)

Your **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package::rocket:
author_association: NONE