From ff16d303d2fb16f84ad85c27eaee34dc4ffdd4d1 Mon Sep 17 00:00:00 2001 From: DanieleRanaldo Date: Wed, 30 Oct 2024 15:25:42 +0100 Subject: [PATCH 01/20] added connector,modified model,dto,mapper,repository, added pattern on controller --- .../citizen/configuration/ExceptionMap.java | 8 ++ .../citizen/connector/tpp/TppConnector.java | 8 ++ .../connector/tpp/TppConnectorImpl.java | 27 ++++++ .../citizen/constants/CitizenConstants.java | 11 +++ .../citizen/controller/CitizenController.java | 28 +++--- .../controller/CitizenControllerImpl.java | 9 +- .../citizen/dto/CitizenConsentDTO.java | 17 +++- .../dto/CitizenConsentStateUpdateDTO.java | 16 ++++ .../onboarding/citizen/dto/Contact.java | 15 +++ .../pagopa/onboarding/citizen/dto/TppDTO.java | 26 ++++++ .../CitizenConsentObjectToDTOMapper.java | 21 +++-- .../citizen/enums/AuthenticationType.java | 16 ++++ .../citizen/model/CitizenConsent.java | 8 +- .../citizen/model/ConsentDetails.java | 18 ++++ .../CitizenConsentDTOToObjectMapper.java | 19 +++- .../citizen/repository/CitizenRepository.java | 7 +- .../repository/CitizenSpecificRepository.java | 10 ++ .../CitizenSpecificRepositoryImpl.java | 61 ++++++++++++ .../citizen/service/CitizenServiceImpl.java | 67 ++++++++++---- .../controller/CitizenControllerTest.java | 20 ++-- .../citizen/faker/CitizenConsentDTOFaker.java | 17 ++-- .../citizen/faker/CitizenConsentFaker.java | 22 ++++- .../CitizenConsentStateUpdateDTOFaker.java | 16 ++++ .../onboarding/citizen/faker/TppDTOFaker.java | 12 +++ .../CitizenSpecificRepositoryImplTest.java | 78 ++++++++++++++++ .../citizen/service/CitizenServiceTest.java | 92 +++++++++++++++++-- 26 files changed, 562 insertions(+), 87 deletions(-) create mode 100644 src/main/java/it/gov/pagopa/onboarding/citizen/connector/tpp/TppConnector.java create mode 100644 src/main/java/it/gov/pagopa/onboarding/citizen/connector/tpp/TppConnectorImpl.java create mode 100644 src/main/java/it/gov/pagopa/onboarding/citizen/dto/CitizenConsentStateUpdateDTO.java create mode 100644 src/main/java/it/gov/pagopa/onboarding/citizen/dto/Contact.java create mode 100644 src/main/java/it/gov/pagopa/onboarding/citizen/dto/TppDTO.java create mode 100644 src/main/java/it/gov/pagopa/onboarding/citizen/enums/AuthenticationType.java create mode 100644 src/main/java/it/gov/pagopa/onboarding/citizen/model/ConsentDetails.java create mode 100644 src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepository.java create mode 100644 src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImpl.java create mode 100644 src/test/java/it/gov/pagopa/onboarding/citizen/faker/CitizenConsentStateUpdateDTOFaker.java create mode 100644 src/test/java/it/gov/pagopa/onboarding/citizen/faker/TppDTOFaker.java create mode 100644 src/test/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImplTest.java diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/configuration/ExceptionMap.java b/src/main/java/it/gov/pagopa/onboarding/citizen/configuration/ExceptionMap.java index 61cda9e..1187e1a 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/configuration/ExceptionMap.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/configuration/ExceptionMap.java @@ -26,6 +26,14 @@ public ExceptionMap() { message ) ); + + exceptions.put(CitizenConstants.ExceptionName.TPP_NOT_FOUND, message -> + new ClientExceptionWithBody( + HttpStatus.NOT_FOUND, + CitizenConstants.ExceptionCode.TPP_NOT_FOUND, + message + ) + ); } public RuntimeException throwException(String exceptionKey, String message) { diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/connector/tpp/TppConnector.java b/src/main/java/it/gov/pagopa/onboarding/citizen/connector/tpp/TppConnector.java new file mode 100644 index 0000000..a712bf1 --- /dev/null +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/connector/tpp/TppConnector.java @@ -0,0 +1,8 @@ +package it.gov.pagopa.onboarding.citizen.connector.tpp; + +import it.gov.pagopa.onboarding.citizen.dto.TppDTO; +import reactor.core.publisher.Mono; + +public interface TppConnector { + Mono get(String tppId); +} \ No newline at end of file diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/connector/tpp/TppConnectorImpl.java b/src/main/java/it/gov/pagopa/onboarding/citizen/connector/tpp/TppConnectorImpl.java new file mode 100644 index 0000000..d3f09d7 --- /dev/null +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/connector/tpp/TppConnectorImpl.java @@ -0,0 +1,27 @@ +package it.gov.pagopa.onboarding.citizen.connector.tpp; + +import it.gov.pagopa.onboarding.citizen.dto.TppDTO; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.stereotype.Service; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; + +@Service +public class TppConnectorImpl implements TppConnector { + private final WebClient webClient; + + public TppConnectorImpl(WebClient.Builder webClientBuilder, + @Value("${rest-client.tpp.baseUrl}") String baseUrl) { + this.webClient = webClientBuilder.baseUrl(baseUrl).build(); + } + + @Override + public Mono get(String tppId) { + return webClient.get() + .uri("/emd/tpp/" + tppId) + .retrieve() + .bodyToMono(new ParameterizedTypeReference<>() { + }); + } +} diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/constants/CitizenConstants.java b/src/main/java/it/gov/pagopa/onboarding/citizen/constants/CitizenConstants.java index 349f395..0c80794 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/constants/CitizenConstants.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/constants/CitizenConstants.java @@ -5,6 +5,8 @@ public static final class ExceptionCode { public static final String CITIZEN_NOT_ONBOARDED = "CITIZEN_NOT_ONBOARDED"; public static final String GENERIC_ERROR = "GENERIC_ERROR"; + public static final String TPP_NOT_FOUND = "TPP_NOT_FOUND"; + private ExceptionCode() {} } @@ -19,9 +21,18 @@ public static final class ExceptionName { public static final String CITIZEN_NOT_ONBOARDED = "CITIZEN_NOT_ONBOARDED"; public static final String GENERIC_ERROR = "GENERIC_ERROR"; + public static final String TPP_NOT_FOUND = "TPP_NOT_FOUND"; + private ExceptionName() {} } + public static final class ValidationRegex { + + public static final String FISCAL_CODE_STRUCTURE_REGEX = "(^([A-Za-z]{6}[0-9lmnpqrstuvLMNPQRSTUV]{2}[abcdehlmprstABCDEHLMPRST][0-9lmnpqrstuvLMNPQRSTUV]{2}[A-Za-z][0-9lmnpqrstuvLMNPQRSTUV]{3}[A-Za-z])$)|(^(\\d{11})$)"; + + private ValidationRegex() {} + } + private CitizenConstants() {} } diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/controller/CitizenController.java b/src/main/java/it/gov/pagopa/onboarding/citizen/controller/CitizenController.java index 505f2a5..51a320f 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/controller/CitizenController.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/controller/CitizenController.java @@ -1,14 +1,18 @@ package it.gov.pagopa.onboarding.citizen.controller; import it.gov.pagopa.onboarding.citizen.dto.CitizenConsentDTO; +import it.gov.pagopa.onboarding.citizen.dto.CitizenConsentStateUpdateDTO; import jakarta.validation.Valid; +import jakarta.validation.constraints.Pattern; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import reactor.core.publisher.Mono; import java.util.List; +import static it.gov.pagopa.onboarding.citizen.constants.CitizenConstants.ValidationRegex.FISCAL_CODE_STRUCTURE_REGEX; + @RequestMapping("/emd/citizen") public interface CitizenController { @@ -16,15 +20,6 @@ public interface CitizenController { @PostMapping("") Mono> saveCitizenConsent(@Valid @RequestBody CitizenConsentDTO citizenConsentDTO); - /** - * Update the state of a citizen's consent. - * - * @param citizenConsentDTO contains the hashedFiscalCode, channelId, and channelState to update - * @return the updated citizen consents - */ - @PutMapping("/stateUpdate") - Mono> stateUpdate(@Valid @RequestBody CitizenConsentDTO citizenConsentDTO); - /** * Get the consent status for a specific citizen and channel. * @@ -33,7 +28,16 @@ public interface CitizenController { * @return the citizen consent status */ @GetMapping("/{fiscalCode}/{tppId}") - Mono> getConsentStatus(@PathVariable String fiscalCode, @PathVariable String tppId); + Mono> getConsentStatus(@PathVariable @Pattern(regexp = FISCAL_CODE_STRUCTURE_REGEX, message = "Invalid fiscal code format") String fiscalCode, @PathVariable String tppId); + + /** + * Update the state of a citizen's consent. + * + * @param citizenConsentStateUpdateDTO contains the hashedFiscalCode, channelId, and channelState to update + * @return the updated citizen consents + */ + @PutMapping("/stateUpdate") + Mono> stateUpdate(@Valid @RequestBody CitizenConsentStateUpdateDTO citizenConsentStateUpdateDTO); /** * List all channels with enabled consents for a specific citizen. @@ -42,7 +46,7 @@ public interface CitizenController { * @return a list of channels with enabled consents */ @GetMapping("/list/{fiscalCode}/enabled") - Mono>> getCitizenConsentsEnabled(@PathVariable String fiscalCode); + Mono>> getCitizenConsentsEnabled(@PathVariable @Pattern(regexp = FISCAL_CODE_STRUCTURE_REGEX, message = "Invalid fiscal code format") String fiscalCode); /** * List all channels and their consent status for a specific citizen. @@ -51,6 +55,6 @@ public interface CitizenController { * @return a list of all channels with their consent statuses */ @GetMapping("/list/{fiscalCode}") - Mono>> getCitizenConsents(@PathVariable String fiscalCode); + Mono>> getCitizenConsents(@PathVariable @Pattern(regexp = FISCAL_CODE_STRUCTURE_REGEX, message = "Invalid fiscal code format") String fiscalCode); } diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/controller/CitizenControllerImpl.java b/src/main/java/it/gov/pagopa/onboarding/citizen/controller/CitizenControllerImpl.java index 668325d..85b5156 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/controller/CitizenControllerImpl.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/controller/CitizenControllerImpl.java @@ -1,6 +1,7 @@ package it.gov.pagopa.onboarding.citizen.controller; import it.gov.pagopa.onboarding.citizen.dto.CitizenConsentDTO; +import it.gov.pagopa.onboarding.citizen.dto.CitizenConsentStateUpdateDTO; import it.gov.pagopa.onboarding.citizen.service.CitizenServiceImpl; import jakarta.validation.Valid; import org.springframework.http.ResponseEntity; @@ -25,11 +26,11 @@ public Mono> saveCitizenConsent(@Valid Citizen } @Override - public Mono> stateUpdate(@Valid CitizenConsentDTO citizenConsentDTO) { + public Mono> stateUpdate(@Valid CitizenConsentStateUpdateDTO citizenConsentStateUpdateDTO) { return citizenService.updateChannelState( - citizenConsentDTO.getHashedFiscalCode(), //at this stage the fiscalCode has not yet been hashed - citizenConsentDTO.getTppId(), - citizenConsentDTO.getTppState()) + citizenConsentStateUpdateDTO.getHashedFiscalCode(), + citizenConsentStateUpdateDTO.getTppId(), + citizenConsentStateUpdateDTO.getTppState()) .map(ResponseEntity::ok); } diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/dto/CitizenConsentDTO.java b/src/main/java/it/gov/pagopa/onboarding/citizen/dto/CitizenConsentDTO.java index c35fec5..931a4e3 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/dto/CitizenConsentDTO.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/dto/CitizenConsentDTO.java @@ -7,6 +7,7 @@ import lombok.experimental.SuperBuilder; import java.time.LocalDateTime; +import java.util.Map; @Data @SuperBuilder @@ -15,8 +16,16 @@ public class CitizenConsentDTO { @JsonAlias("fiscalCode") private String hashedFiscalCode; - private String tppId; - private Boolean tppState; - private LocalDateTime creationDate; - private LocalDateTime lastUpdateDate; + private Map consents; + + @Data + @SuperBuilder + @NoArgsConstructor + @AllArgsConstructor + public static class ConsentDTO { + private Boolean tc; + private Boolean tppState; + private LocalDateTime creationDate; + private LocalDateTime lastUpdateDate; + } } diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/dto/CitizenConsentStateUpdateDTO.java b/src/main/java/it/gov/pagopa/onboarding/citizen/dto/CitizenConsentStateUpdateDTO.java new file mode 100644 index 0000000..973eb2a --- /dev/null +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/dto/CitizenConsentStateUpdateDTO.java @@ -0,0 +1,16 @@ +package it.gov.pagopa.onboarding.citizen.dto; + +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.AllArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class CitizenConsentStateUpdateDTO { + private String hashedFiscalCode; + private String tppId; + private Boolean tppState; +} diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/dto/Contact.java b/src/main/java/it/gov/pagopa/onboarding/citizen/dto/Contact.java new file mode 100644 index 0000000..fa6c88d --- /dev/null +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/dto/Contact.java @@ -0,0 +1,15 @@ +package it.gov.pagopa.onboarding.citizen.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class Contact { + + private String name; + private String number; + private String email; +} \ No newline at end of file diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/dto/TppDTO.java b/src/main/java/it/gov/pagopa/onboarding/citizen/dto/TppDTO.java new file mode 100644 index 0000000..bc22e8b --- /dev/null +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/dto/TppDTO.java @@ -0,0 +1,26 @@ +package it.gov.pagopa.onboarding.citizen.dto; + + +import it.gov.pagopa.onboarding.citizen.enums.AuthenticationType; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +public class TppDTO { + @NotNull + private String tppId; + private String entityId; + private String businessName; + private String messageUrl; + private String authenticationUrl; + private AuthenticationType authenticationType; + private Contact contact; + private Boolean state; + + + +} diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/dto/mapper/CitizenConsentObjectToDTOMapper.java b/src/main/java/it/gov/pagopa/onboarding/citizen/dto/mapper/CitizenConsentObjectToDTOMapper.java index 022d91d..f663d34 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/dto/mapper/CitizenConsentObjectToDTOMapper.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/dto/mapper/CitizenConsentObjectToDTOMapper.java @@ -1,20 +1,29 @@ package it.gov.pagopa.onboarding.citizen.dto.mapper; - import it.gov.pagopa.onboarding.citizen.dto.CitizenConsentDTO; +import it.gov.pagopa.onboarding.citizen.dto.CitizenConsentDTO.ConsentDTO; import it.gov.pagopa.onboarding.citizen.model.CitizenConsent; import org.springframework.stereotype.Service; +import java.util.HashMap; +import java.util.Map; + @Service public class CitizenConsentObjectToDTOMapper { - public CitizenConsentDTO map(CitizenConsent citizenConsent){ + public CitizenConsentDTO map(CitizenConsent citizenConsent) { + Map consentsDTO = new HashMap<>(); + + citizenConsent.getConsents().forEach((tppId, consentDetails) -> consentsDTO.put(tppId, ConsentDTO.builder() + .tc(consentDetails.getTc()) + .tppState(consentDetails.getTppState()) + .creationDate(consentDetails.getCreationDate()) + .lastUpdateDate(consentDetails.getLastUpdateDate()) + .build())); + return CitizenConsentDTO.builder() .hashedFiscalCode(citizenConsent.getHashedFiscalCode()) - .tppState(citizenConsent.getTppState()) - .tppId(citizenConsent.getTppId()) - .creationDate(citizenConsent.getCreationDate()) - .lastUpdateDate(citizenConsent.getLastUpdateDate()) + .consents(consentsDTO) .build(); } } diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/enums/AuthenticationType.java b/src/main/java/it/gov/pagopa/onboarding/citizen/enums/AuthenticationType.java new file mode 100644 index 0000000..26c74ce --- /dev/null +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/enums/AuthenticationType.java @@ -0,0 +1,16 @@ +package it.gov.pagopa.onboarding.citizen.enums; + +import lombok.Getter; + +@Getter +public enum AuthenticationType { + OAUTH2("OAUTH2"); + + + private final String status; + + AuthenticationType(String status) { + this.status = status; + } + +} diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/model/CitizenConsent.java b/src/main/java/it/gov/pagopa/onboarding/citizen/model/CitizenConsent.java index 64ebd8e..5b37004 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/model/CitizenConsent.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/model/CitizenConsent.java @@ -5,7 +5,7 @@ import lombok.experimental.SuperBuilder; import org.springframework.data.mongodb.core.mapping.Document; -import java.time.LocalDateTime; +import java.util.Map; @Document(collection = "citizen_consents") @Data @@ -15,9 +15,7 @@ public class CitizenConsent { private String id; private String hashedFiscalCode; - private String tppId; - private Boolean tppState; - private LocalDateTime creationDate; - private LocalDateTime lastUpdateDate; + private Map consents; } + diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/model/ConsentDetails.java b/src/main/java/it/gov/pagopa/onboarding/citizen/model/ConsentDetails.java new file mode 100644 index 0000000..27aacf0 --- /dev/null +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/model/ConsentDetails.java @@ -0,0 +1,18 @@ +package it.gov.pagopa.onboarding.citizen.model; + +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@SuperBuilder +public class ConsentDetails { + private Boolean tc; + private Boolean tppState; + private LocalDateTime creationDate; + private LocalDateTime lastTcUpdateDate; + private LocalDateTime lastUpdateDate; +} diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/model/mapper/CitizenConsentDTOToObjectMapper.java b/src/main/java/it/gov/pagopa/onboarding/citizen/model/mapper/CitizenConsentDTOToObjectMapper.java index 96caf6a..4c3aef2 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/model/mapper/CitizenConsentDTOToObjectMapper.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/model/mapper/CitizenConsentDTOToObjectMapper.java @@ -1,18 +1,29 @@ package it.gov.pagopa.onboarding.citizen.model.mapper; - import it.gov.pagopa.onboarding.citizen.dto.CitizenConsentDTO; import it.gov.pagopa.onboarding.citizen.model.CitizenConsent; +import it.gov.pagopa.onboarding.citizen.model.ConsentDetails; import org.springframework.stereotype.Service; +import java.util.HashMap; +import java.util.Map; + @Service public class CitizenConsentDTOToObjectMapper { - public CitizenConsent map(CitizenConsentDTO citizenConsentDTO){ + public CitizenConsent map(CitizenConsentDTO citizenConsentDTO) { + Map consents = new HashMap<>(); + + citizenConsentDTO.getConsents().forEach((tppId, consentDTO) -> consents.put(tppId, ConsentDetails.builder() + .tc(consentDTO.getTc()) + .tppState(consentDTO.getTppState()) + .creationDate(consentDTO.getCreationDate()) + .lastTcUpdateDate(consentDTO.getLastUpdateDate()) + .build())); + return CitizenConsent.builder() - .tppState(true) - .tppId(citizenConsentDTO.getTppId()) .hashedFiscalCode(citizenConsentDTO.getHashedFiscalCode()) + .consents(consents) .build(); } } diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenRepository.java b/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenRepository.java index 103dc2f..fb58a14 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenRepository.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenRepository.java @@ -3,14 +3,13 @@ import it.gov.pagopa.onboarding.citizen.model.CitizenConsent; import org.springframework.data.mongodb.repository.ReactiveMongoRepository; import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -public interface CitizenRepository extends ReactiveMongoRepository { +public interface CitizenRepository extends ReactiveMongoRepository, CitizenSpecificRepository { Flux findByHashedFiscalCode(String hashedFiscalCode); - Flux findByHashedFiscalCodeAndTppStateTrue(String hashedFiscalCode); + //Flux findByHashedFiscalCodeAndTppStateTrue(String hashedFiscalCode); - Mono findByHashedFiscalCodeAndTppId(String hashedFiscalCode, String tppId); + //Mono findByHashedFiscalCodeAndTppId(String hashedFiscalCode, String tppId); } diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepository.java b/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepository.java new file mode 100644 index 0000000..43932e7 --- /dev/null +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepository.java @@ -0,0 +1,10 @@ +package it.gov.pagopa.onboarding.citizen.repository; + +import it.gov.pagopa.onboarding.citizen.model.CitizenConsent; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +public interface CitizenSpecificRepository { + Flux findByHashedFiscalCodeAndTppStateTrue(String hashedFiscalCode); + Mono findByHashedFiscalCodeAndTppId(String hashedFiscalCode, String tppId); +} diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImpl.java b/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImpl.java new file mode 100644 index 0000000..81db2b4 --- /dev/null +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImpl.java @@ -0,0 +1,61 @@ +package it.gov.pagopa.onboarding.citizen.repository; + +import it.gov.pagopa.onboarding.citizen.model.CitizenConsent; +import it.gov.pagopa.onboarding.citizen.model.ConsentDetails; +import org.springframework.data.mongodb.core.ReactiveMongoTemplate; +import org.springframework.data.mongodb.core.aggregation.Aggregation; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.stereotype.Repository; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.Map; +import java.util.stream.Collectors; + +@Repository +public class CitizenSpecificRepositoryImpl implements CitizenSpecificRepository { + + private final ReactiveMongoTemplate mongoTemplate; + + public CitizenSpecificRepositoryImpl(ReactiveMongoTemplate mongoTemplate) { + this.mongoTemplate = mongoTemplate; + } + + public Flux findByHashedFiscalCodeAndTppStateTrue(String hashedFiscalCode) { + Aggregation aggregation = Aggregation.newAggregation( + Aggregation.match(Criteria.where("hashedFiscalCode").is(hashedFiscalCode)), + Aggregation.unwind("consents"), + Aggregation.match(Criteria.where("consents.tppState").is(true)) + ); + + return mongoTemplate.aggregate(aggregation, CitizenConsent.class, CitizenConsent.class) + .flatMap(result -> { + Map validConsents = result.getConsents() + .entrySet() + .stream() + .filter(entry -> entry.getValue().getTppState()) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + return Flux.just(CitizenConsent.builder() + .id(result.getId()) + .hashedFiscalCode(result.getHashedFiscalCode()) + .consents(validConsents) + .build()); + }); + } + + public Mono findByHashedFiscalCodeAndTppId(String hashedFiscalCode, String tppId) { + if (tppId == null) { + return Mono.empty(); + } + + Aggregation aggregation = Aggregation.newAggregation( + Aggregation.match(Criteria.where("hashedFiscalCode").is(hashedFiscalCode)), + Aggregation.match(Criteria.where("consents." + tppId).exists(true)) + ); + + return mongoTemplate.aggregate(aggregation, CitizenConsent.class, CitizenConsent.class) + .next(); + } + +} diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceImpl.java b/src/main/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceImpl.java index ddcc279..16fbafa 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceImpl.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceImpl.java @@ -2,10 +2,12 @@ import it.gov.pagopa.common.utils.Utils; import it.gov.pagopa.onboarding.citizen.configuration.ExceptionMap; +import it.gov.pagopa.onboarding.citizen.connector.tpp.TppConnectorImpl; import it.gov.pagopa.onboarding.citizen.constants.CitizenConstants.ExceptionName; import it.gov.pagopa.onboarding.citizen.dto.CitizenConsentDTO; import it.gov.pagopa.onboarding.citizen.dto.mapper.CitizenConsentObjectToDTOMapper; import it.gov.pagopa.onboarding.citizen.model.CitizenConsent; +import it.gov.pagopa.onboarding.citizen.model.ConsentDetails; import it.gov.pagopa.onboarding.citizen.model.mapper.CitizenConsentDTOToObjectMapper; import it.gov.pagopa.onboarding.citizen.repository.CitizenRepository; import lombok.extern.slf4j.Slf4j; @@ -25,30 +27,49 @@ public class CitizenServiceImpl implements CitizenService { private final CitizenConsentObjectToDTOMapper mapperToDTO; private final CitizenConsentDTOToObjectMapper mapperToObject; private final ExceptionMap exceptionMap; + private final TppConnectorImpl tppConnector; - public CitizenServiceImpl(CitizenRepository citizenRepository, CitizenConsentObjectToDTOMapper mapperToDTO, CitizenConsentDTOToObjectMapper mapperToObject, ExceptionMap exceptionMap) { + public CitizenServiceImpl(CitizenRepository citizenRepository, CitizenConsentObjectToDTOMapper mapperToDTO, CitizenConsentDTOToObjectMapper mapperToObject, ExceptionMap exceptionMap, TppConnectorImpl tppConnector) { this.citizenRepository = citizenRepository; this.mapperToDTO = mapperToDTO; this.mapperToObject = mapperToObject; this.exceptionMap = exceptionMap; + this.tppConnector = tppConnector; } + @Override public Mono createCitizenConsent(CitizenConsentDTO citizenConsentDTO) { CitizenConsent citizenConsent = mapperToObject.map(citizenConsentDTO); String hashedFiscalCode = Utils.createSHA256(citizenConsent.getHashedFiscalCode()); citizenConsent.setHashedFiscalCode(hashedFiscalCode); - citizenConsent.setCreationDate(LocalDateTime.now()); - citizenConsent.setLastUpdateDate(LocalDateTime.now()); - log.info("[EMD-CITIZEN][CREATE] Received consent: {}",inputSanify(citizenConsent.toString())); - return citizenRepository.findByHashedFiscalCodeAndTppId(hashedFiscalCode,citizenConsent.getTppId()) + + citizenConsent.getConsents().forEach((tppId, consentDetails) -> { + consentDetails.setCreationDate(LocalDateTime.now()); + consentDetails.setLastUpdateDate(LocalDateTime.now()); + }); + + log.info("[EMD-CITIZEN][CREATE] Received consent: {}", inputSanify(citizenConsent.toString())); + + String tppId = citizenConsent.getConsents().keySet().stream().findFirst().orElse(null); + if (tppId == null) { + return Mono.error(exceptionMap.throwException(ExceptionName.TPP_NOT_FOUND, "TPP does not exist or is not active")); + } + + return citizenRepository.findByHashedFiscalCodeAndTppId(hashedFiscalCode, tppId) .flatMap(existingConsent -> { log.info("[EMD][CREATE-CITIZEN-CONSENT] Citizen consent already exists"); return Mono.just(mapperToDTO.map(existingConsent)); }) - .switchIfEmpty( - citizenRepository.save(citizenConsent) - .doOnSuccess(savedConsent -> log.info("[EMD][CREATE-CITIZEN-CONSENT] Created new citizen consent")) - .flatMap(savedConsent -> Mono.just(mapperToDTO.map(savedConsent))) + .switchIfEmpty(tppConnector.get(tppId) + .flatMap(tppResponse -> { + if (tppResponse != null && Boolean.TRUE.equals(tppResponse.getState())) { + return citizenRepository.save(citizenConsent) + .doOnSuccess(savedConsent -> log.info("[EMD][CREATE-CITIZEN-CONSENT] Created new citizen consent")) + .flatMap(savedConsent -> Mono.just(mapperToDTO.map(savedConsent))); + } else { + return Mono.error(exceptionMap.throwException(ExceptionName.TPP_NOT_FOUND, "TPP does not exist or is not active")); + } + }) ); } @@ -56,13 +77,19 @@ public Mono createCitizenConsent(CitizenConsentDTO citizenCon public Mono updateChannelState(String fiscalCode, String tppId, boolean tppState) { String hashedFiscalCode = Utils.createSHA256(fiscalCode); log.info("[EMD][[CITIZEN][UPDATE-CHANNEL-STATE] Received hashedFiscalCode: {} and tppId: {} with state: {}" - ,hashedFiscalCode, inputSanify(tppId), tppState); + , hashedFiscalCode, inputSanify(tppId), tppState); return citizenRepository.findByHashedFiscalCodeAndTppId(hashedFiscalCode, tppId) .switchIfEmpty(Mono.error(exceptionMap.throwException - (ExceptionName.CITIZEN_NOT_ONBOARDED,"Citizen consent not founded during update state process"))) + (ExceptionName.CITIZEN_NOT_ONBOARDED, "Citizen consent not founded during update state process"))) .flatMap(citizenConsent -> { - citizenConsent.setTppState(tppState); - citizenConsent.setLastUpdateDate(LocalDateTime.now()); + ConsentDetails consentDetails = citizenConsent.getConsents().get(tppId); + if (consentDetails != null) { + consentDetails.setTppState(tppState); + consentDetails.setLastUpdateDate(LocalDateTime.now()); + } else { + return Mono.error(exceptionMap.throwException + (ExceptionName.CITIZEN_NOT_ONBOARDED, "ConsentDetails is null for this tppId")); + } return citizenRepository.save(citizenConsent); }) .map(mapperToDTO::map) @@ -72,39 +99,39 @@ public Mono updateChannelState(String fiscalCode, String tppI @Override public Mono getConsentStatus(String fiscalCode, String tppId) { String hashedFiscalCode = Utils.createSHA256(fiscalCode); - log.info("[EMD-CITIZEN][GET-CONSENT-STATUS] Received hashedFiscalCode: {} and tppId: {}",hashedFiscalCode,inputSanify(tppId)); + log.info("[EMD-CITIZEN][GET-CONSENT-STATUS] Received hashedFiscalCode: {} and tppId: {}", hashedFiscalCode, inputSanify(tppId)); return citizenRepository.findByHashedFiscalCodeAndTppId(hashedFiscalCode, tppId) .switchIfEmpty(Mono.error(exceptionMap.throwException - (ExceptionName.CITIZEN_NOT_ONBOARDED,"Citizen consent not founded during get process "))) + (ExceptionName.CITIZEN_NOT_ONBOARDED, "Citizen consent not founded during get process "))) .map(mapperToDTO::map) - .doOnSuccess(consent -> log.info("[EMD-CITIZEN][GET-CONSENT-STATUS] Consent consent found:: {}",consent)); + .doOnSuccess(consent -> log.info("[EMD-CITIZEN][GET-CONSENT-STATUS] Consent consent found:: {}", consent)); } @Override public Mono> getListEnabledConsents(String fiscalCode) { String hashedFiscalCode = Utils.createSHA256(fiscalCode); - log.info("[EMD-CITIZEN][FIND-CITIZEN-CONSENTS-ENABLED] Received hashedFiscalCode: {}",hashedFiscalCode); + log.info("[EMD-CITIZEN][FIND-CITIZEN-CONSENTS-ENABLED] Received hashedFiscalCode: {}", hashedFiscalCode); return citizenRepository.findByHashedFiscalCodeAndTppStateTrue(hashedFiscalCode) .collectList() .map(consentList -> consentList.stream() .map(mapperToDTO::map) .toList() ) - .doOnSuccess(consentList -> log.info("EMD][CITIZEN][FIND-CITIZEN-CONSENTS-ENABLED] Consents founded: {}",(consentList.size()))); + .doOnSuccess(consentList -> log.info("EMD][CITIZEN][FIND-CITIZEN-CONSENTS-ENABLED] Consents founded: {}", (consentList.size()))); } @Override public Mono> getListAllConsents(String fiscalCode) { String hashedFiscalCode = Utils.createSHA256(fiscalCode); - log.info("[EMD-CITIZEN][FIND-ALL-CITIZEN-CONSENTS] Received hashedFiscalCode: {}",(hashedFiscalCode)); + log.info("[EMD-CITIZEN][FIND-ALL-CITIZEN-CONSENTS] Received hashedFiscalCode: {}", (hashedFiscalCode)); return citizenRepository.findByHashedFiscalCode(hashedFiscalCode) .collectList() .map(consentList -> consentList.stream() .map(mapperToDTO::map) .toList() ) - .doOnSuccess(consentList -> log.info("[EMD-CITIZEN][FIND-ALL-CITIZEN-CONSENTS] Consents found:: {}",consentList)); + .doOnSuccess(consentList -> log.info("[EMD-CITIZEN][FIND-ALL-CITIZEN-CONSENTS] Consents found:: {}", consentList)); } } diff --git a/src/test/java/it/gov/pagopa/onboarding/citizen/controller/CitizenControllerTest.java b/src/test/java/it/gov/pagopa/onboarding/citizen/controller/CitizenControllerTest.java index f49c259..d955423 100644 --- a/src/test/java/it/gov/pagopa/onboarding/citizen/controller/CitizenControllerTest.java +++ b/src/test/java/it/gov/pagopa/onboarding/citizen/controller/CitizenControllerTest.java @@ -1,7 +1,9 @@ package it.gov.pagopa.onboarding.citizen.controller; import it.gov.pagopa.onboarding.citizen.dto.CitizenConsentDTO; +import it.gov.pagopa.onboarding.citizen.dto.CitizenConsentStateUpdateDTO; import it.gov.pagopa.onboarding.citizen.faker.CitizenConsentDTOFaker; +import it.gov.pagopa.onboarding.citizen.faker.CitizenConsentStateUpdateDTOFaker; import it.gov.pagopa.onboarding.citizen.service.CitizenServiceImpl; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -26,7 +28,7 @@ class CitizenControllerTest { - private static final String FISCAL_CODE = "fiscalCode"; + private static final String FISCAL_CODE = "MLXHZZ43A70H203T"; private static final String TPP_ID = "tppId"; @@ -52,25 +54,27 @@ void saveCitizenConsent_Ok() { @Test void stateUpdate_Ok() { - CitizenConsentDTO citizenConsentDTO = CitizenConsentDTOFaker.mockInstance(true); + CitizenConsentStateUpdateDTO citizenConsentStateUpdateDTO = CitizenConsentStateUpdateDTOFaker.mockInstance(true); + + CitizenConsentDTO expectedResponseDTO = CitizenConsentDTOFaker.mockInstance(true); Mockito.when(citizenService.updateChannelState( - citizenConsentDTO.getHashedFiscalCode(), - citizenConsentDTO.getTppId(), - citizenConsentDTO.getTppState())) - .thenReturn(Mono.just(citizenConsentDTO)); + citizenConsentStateUpdateDTO.getHashedFiscalCode(), + citizenConsentStateUpdateDTO.getTppId(), + citizenConsentStateUpdateDTO.getTppState())) + .thenReturn(Mono.just(expectedResponseDTO)); webClient.put() .uri("/emd/citizen/stateUpdate") .contentType(MediaType.APPLICATION_JSON) - .bodyValue(citizenConsentDTO) + .bodyValue(citizenConsentStateUpdateDTO) .exchange() .expectStatus().isOk() .expectBody(CitizenConsentDTO.class) .consumeWith(response -> { CitizenConsentDTO resultResponse = response.getResponseBody(); Assertions.assertNotNull(resultResponse); - Assertions.assertEquals(citizenConsentDTO, resultResponse); + Assertions.assertEquals(expectedResponseDTO, resultResponse); }); } diff --git a/src/test/java/it/gov/pagopa/onboarding/citizen/faker/CitizenConsentDTOFaker.java b/src/test/java/it/gov/pagopa/onboarding/citizen/faker/CitizenConsentDTOFaker.java index 2c639cc..a135012 100644 --- a/src/test/java/it/gov/pagopa/onboarding/citizen/faker/CitizenConsentDTOFaker.java +++ b/src/test/java/it/gov/pagopa/onboarding/citizen/faker/CitizenConsentDTOFaker.java @@ -1,21 +1,22 @@ package it.gov.pagopa.onboarding.citizen.faker; - import it.gov.pagopa.onboarding.citizen.dto.CitizenConsentDTO; - import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.Map; public class CitizenConsentDTOFaker { - private CitizenConsentDTOFaker(){} + private CitizenConsentDTOFaker() {} + public static CitizenConsentDTO mockInstance(Boolean bias) { + Map consents = new HashMap<>(); + + consents.put("tppId", new CitizenConsentDTO.ConsentDTO(bias, bias, LocalDateTime.now(), LocalDateTime.now())); + return CitizenConsentDTO.builder() - .tppId("tppId") - .tppState(bias) .hashedFiscalCode("hashedFiscalCode") - .creationDate(LocalDateTime.now()) - .lastUpdateDate(LocalDateTime.now()) + .consents(consents) .build(); - } } diff --git a/src/test/java/it/gov/pagopa/onboarding/citizen/faker/CitizenConsentFaker.java b/src/test/java/it/gov/pagopa/onboarding/citizen/faker/CitizenConsentFaker.java index 195afad..fec5c44 100644 --- a/src/test/java/it/gov/pagopa/onboarding/citizen/faker/CitizenConsentFaker.java +++ b/src/test/java/it/gov/pagopa/onboarding/citizen/faker/CitizenConsentFaker.java @@ -1,20 +1,32 @@ package it.gov.pagopa.onboarding.citizen.faker; - import it.gov.pagopa.onboarding.citizen.model.CitizenConsent; +import it.gov.pagopa.onboarding.citizen.model.ConsentDetails; import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.Map; public class CitizenConsentFaker { - private CitizenConsentFaker(){} + private CitizenConsentFaker() {} + public static CitizenConsent mockInstance(Boolean bias) { - return CitizenConsent.builder() - .tppId("tppId") + Map consents = new HashMap<>(); + + ConsentDetails consentDetails = ConsentDetails.builder() + .tc(bias) .tppState(bias) - .hashedFiscalCode("hashedFiscalCode") .creationDate(LocalDateTime.now()) + .lastTcUpdateDate(LocalDateTime.now()) .lastUpdateDate(LocalDateTime.now()) .build(); + + consents.put("tppId", consentDetails); + + return CitizenConsent.builder() + .hashedFiscalCode("hashedFiscalCode") + .consents(consents) + .build(); } } diff --git a/src/test/java/it/gov/pagopa/onboarding/citizen/faker/CitizenConsentStateUpdateDTOFaker.java b/src/test/java/it/gov/pagopa/onboarding/citizen/faker/CitizenConsentStateUpdateDTOFaker.java new file mode 100644 index 0000000..6685dcb --- /dev/null +++ b/src/test/java/it/gov/pagopa/onboarding/citizen/faker/CitizenConsentStateUpdateDTOFaker.java @@ -0,0 +1,16 @@ +package it.gov.pagopa.onboarding.citizen.faker; + +import it.gov.pagopa.onboarding.citizen.dto.CitizenConsentStateUpdateDTO; + +public class CitizenConsentStateUpdateDTOFaker { + + private CitizenConsentStateUpdateDTOFaker() {} + + public static CitizenConsentStateUpdateDTO mockInstance(Boolean tppState) { + return CitizenConsentStateUpdateDTO.builder() + .hashedFiscalCode("hashedFiscalCode") + .tppId("tppId") + .tppState(tppState) + .build(); + } +} diff --git a/src/test/java/it/gov/pagopa/onboarding/citizen/faker/TppDTOFaker.java b/src/test/java/it/gov/pagopa/onboarding/citizen/faker/TppDTOFaker.java new file mode 100644 index 0000000..b5befd4 --- /dev/null +++ b/src/test/java/it/gov/pagopa/onboarding/citizen/faker/TppDTOFaker.java @@ -0,0 +1,12 @@ +package it.gov.pagopa.onboarding.citizen.faker; + +import it.gov.pagopa.onboarding.citizen.dto.TppDTO; + +public class TppDTOFaker { + private TppDTOFaker(){} + public static TppDTO mockInstance() { + return TppDTO.builder() + .tppId("id") + .build(); + } +} diff --git a/src/test/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImplTest.java b/src/test/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImplTest.java new file mode 100644 index 0000000..f3ca0cd --- /dev/null +++ b/src/test/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImplTest.java @@ -0,0 +1,78 @@ +package it.gov.pagopa.onboarding.citizen.repository; + +import it.gov.pagopa.onboarding.citizen.model.CitizenConsent; +import it.gov.pagopa.onboarding.citizen.model.ConsentDetails; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.mongodb.core.ReactiveMongoTemplate; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.HashMap; +import java.util.Map; + +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class CitizenSpecificRepositoryImplTest { + + private ReactiveMongoTemplate mongoTemplate; + private CitizenSpecificRepositoryImpl repository; + + @BeforeEach + public void setUp() { + mongoTemplate = Mockito.mock(ReactiveMongoTemplate.class); + repository = new CitizenSpecificRepositoryImpl(mongoTemplate); + } + + @Test + void testFindByHashedFiscalCodeAndTppStateTrue() { + String hashedFiscalCode = "hashedCode"; + CitizenConsent citizenConsent = new CitizenConsent(); + citizenConsent.setId("1"); + citizenConsent.setHashedFiscalCode(hashedFiscalCode); + + Map consents = new HashMap<>(); + ConsentDetails consentDetails = new ConsentDetails(); + consentDetails.setTppState(true); + consents.put("tpp1", consentDetails); + citizenConsent.setConsents(consents); + + when(mongoTemplate.aggregate(Mockito.any(), Mockito.eq(CitizenConsent.class), Mockito.eq(CitizenConsent.class))) + .thenReturn(Flux.just(citizenConsent)); + + Flux result = repository.findByHashedFiscalCodeAndTppStateTrue(hashedFiscalCode); + + Assertions.assertEquals(1, result.count().block()); + Assertions.assertEquals(hashedFiscalCode, result.blockFirst().getHashedFiscalCode()); + Mockito.verify(mongoTemplate).aggregate(Mockito.any(), Mockito.eq(CitizenConsent.class), Mockito.eq(CitizenConsent.class)); + } + + @Test + void testFindByHashedFiscalCodeAndTppId() { + String hashedFiscalCode = "hashedCode"; + String tppId = "tpp1"; + CitizenConsent citizenConsent = new CitizenConsent(); + citizenConsent.setId("1"); + citizenConsent.setHashedFiscalCode(hashedFiscalCode); + + Map consents = new HashMap<>(); + ConsentDetails consentDetails = new ConsentDetails(); + consentDetails.setTppState(true); + consents.put(tppId, consentDetails); + citizenConsent.setConsents(consents); + + when(mongoTemplate.aggregate(Mockito.any(), Mockito.eq(CitizenConsent.class), Mockito.eq(CitizenConsent.class))) + .thenReturn(Flux.just(citizenConsent)); + + Mono result = repository.findByHashedFiscalCodeAndTppId(hashedFiscalCode, tppId); + + Assertions.assertEquals(hashedFiscalCode, result.block().getHashedFiscalCode()); + Mockito.verify(mongoTemplate).aggregate(Mockito.any(), Mockito.eq(CitizenConsent.class), Mockito.eq(CitizenConsent.class)); + } + +} \ No newline at end of file diff --git a/src/test/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceTest.java b/src/test/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceTest.java index 5f3b54e..0f6cf85 100644 --- a/src/test/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceTest.java +++ b/src/test/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceTest.java @@ -2,12 +2,15 @@ import it.gov.pagopa.common.utils.Utils; import it.gov.pagopa.common.web.exception.ClientExceptionWithBody; -import it.gov.pagopa.onboarding.citizen.dto.CitizenConsentDTO; -import it.gov.pagopa.onboarding.citizen.dto.mapper.CitizenConsentObjectToDTOMapper; import it.gov.pagopa.common.web.exception.EmdEncryptionException; import it.gov.pagopa.onboarding.citizen.configuration.ExceptionMap; +import it.gov.pagopa.onboarding.citizen.connector.tpp.TppConnectorImpl; +import it.gov.pagopa.onboarding.citizen.dto.CitizenConsentDTO; +import it.gov.pagopa.onboarding.citizen.dto.TppDTO; +import it.gov.pagopa.onboarding.citizen.dto.mapper.CitizenConsentObjectToDTOMapper; import it.gov.pagopa.onboarding.citizen.faker.CitizenConsentDTOFaker; import it.gov.pagopa.onboarding.citizen.faker.CitizenConsentFaker; +import it.gov.pagopa.onboarding.citizen.faker.TppDTOFaker; import it.gov.pagopa.onboarding.citizen.model.CitizenConsent; import it.gov.pagopa.onboarding.citizen.model.mapper.CitizenConsentDTOToObjectMapper; import it.gov.pagopa.onboarding.citizen.repository.CitizenRepository; @@ -45,6 +48,9 @@ class CitizenServiceTest { @MockBean CitizenRepository citizenRepository; + @MockBean + TppConnectorImpl tppConnector; + @Autowired CitizenConsentObjectToDTOMapper dtoMapper; @@ -54,40 +60,91 @@ class CitizenServiceTest { private static final boolean TPP_STATE = true; private static final CitizenConsent CITIZEN_CONSENT = CitizenConsentFaker.mockInstance(true); private static final CitizenConsentDTO CITIZEN_CONSENT_DTO = CitizenConsentDTOFaker.mockInstance(true); + @Test void createCitizenConsent_Ok() { - CitizenConsentDTO citizenConsentDTO = dtoMapper.map(CITIZEN_CONSENT); + TppDTO mockTppDTO = new TppDTO(); + mockTppDTO.setTppId(CITIZEN_CONSENT_DTO.getConsents().keySet().stream().findFirst().orElse(null)); + mockTppDTO.setState(true); + + Mockito.when(tppConnector.get(anyString())) + .thenReturn(Mono.just(mockTppDTO)); + Mockito.when(citizenRepository.save(Mockito.any())) .thenReturn(Mono.just(CITIZEN_CONSENT)); Mockito.when(citizenRepository.findById(anyString())) .thenReturn(Mono.empty()); + Mockito.when(citizenRepository.findByHashedFiscalCodeAndTppId(anyString(), anyString())) + .thenReturn(Mono.empty()); + CitizenConsentDTO response = citizenService.createCitizenConsent(CITIZEN_CONSENT_DTO).block(); assertNotNull(response); - assertEquals(citizenConsentDTO, response); } @Test void createCitizenConsent_AlreadyExists() { - CitizenConsentDTO citizenConsentDTO = dtoMapper.map(CITIZEN_CONSENT); + TppDTO mockTppDTO = TppDTOFaker.mockInstance(); + mockTppDTO.setState(true); + + Mockito.when(tppConnector.get(anyString())) + .thenReturn(Mono.just(mockTppDTO)); + Mockito.when(citizenRepository.save(Mockito.any())) .thenReturn(Mono.empty()); Mockito.when(citizenRepository.findById(anyString())) .thenReturn(Mono.just(CITIZEN_CONSENT)); + Mockito.when(citizenRepository.findByHashedFiscalCodeAndTppId(anyString(), anyString())) + .thenReturn(Mono.just(CITIZEN_CONSENT)); + CitizenConsentDTO response = citizenService.createCitizenConsent(CITIZEN_CONSENT_DTO).block(); assertNotNull(response); - assertEquals(citizenConsentDTO, response); } + @Test + void createCitizenConsent_Ko_TppNull() { + + CitizenConsentDTO citizenConsentDTO = CitizenConsentDTOFaker.mockInstance(true); + citizenConsentDTO.getConsents().clear(); + + Mockito.when(tppConnector.get(anyString())).thenReturn(Mono.empty()); + Mockito.when(citizenRepository.findByHashedFiscalCodeAndTppId(anyString(), anyString())) + .thenReturn(Mono.empty()); + + ClientExceptionWithBody exception = assertThrows(ClientExceptionWithBody.class, + () -> citizenService.createCitizenConsent(citizenConsentDTO).block()); + + assertEquals("TPP does not exist or is not active", exception.getMessage()); + } + + @Test + void createCitizenConsent_Ko_TppInactive() { + + TppDTO mockTppDTO = TppDTOFaker.mockInstance(); + mockTppDTO.setState(false); + + Mockito.when(tppConnector.get(anyString())).thenReturn(Mono.just(mockTppDTO)); + Mockito.when(citizenRepository.findByHashedFiscalCodeAndTppId(anyString(), anyString())) + .thenReturn(Mono.empty()); + + CitizenConsentDTO citizenConsentDTO = dtoMapper.map(CITIZEN_CONSENT); + + ClientExceptionWithBody exception = assertThrows(ClientExceptionWithBody.class, + () -> citizenService.createCitizenConsent(citizenConsentDTO).block()); + + assertEquals("TPP does not exist or is not active", exception.getMessage()); + Mockito.verify(citizenRepository, Mockito.never()).save(Mockito.any()); + } + @Test void createCitizenConsent_Ko_EmdEncryptError() { CitizenConsentDTO citizenConsentDTO = CitizenConsentDTOFaker.mockInstance(true); @@ -106,12 +163,15 @@ void updateChannelState_Ok() { Mockito.when(citizenRepository.findByHashedFiscalCodeAndTppId(HASHED_FISCAL_CODE, TPP_ID)) .thenReturn(Mono.just(CITIZEN_CONSENT)); + Mockito.when(citizenRepository.save(Mockito.any())) .thenReturn(Mono.just(CITIZEN_CONSENT)); CitizenConsentDTO response = citizenService.updateChannelState(FISCAL_CODE, TPP_ID, TPP_STATE).block(); + assertNotNull(response); - assertEquals(TPP_STATE, response.getTppState()); + + assertEquals(TPP_STATE, response.getConsents().get(TPP_ID).getTppState()); } @Test @@ -127,6 +187,24 @@ void updateChannelState_Ko_CitizenNotOnboarded() { assertEquals("Citizen consent not founded during update state process", exception.getMessage()); } + @Test + void updateChannelState_Ok_ConsentDetailsIsNull() { + CitizenConsent citizenConsentWithConsentDetailNull = CitizenConsentFaker.mockInstance(true); + citizenConsentWithConsentDetailNull.getConsents().put(TPP_ID, null); + + Mockito.when(citizenRepository.findByHashedFiscalCodeAndTppId(HASHED_FISCAL_CODE, TPP_ID)) + .thenReturn(Mono.just(citizenConsentWithConsentDetailNull)); + + Mockito.when(citizenRepository.save(Mockito.any())) + .thenReturn(Mono.just(citizenConsentWithConsentDetailNull)); + + Executable executable = () -> citizenService.updateChannelState(FISCAL_CODE, TPP_ID, TPP_STATE).block(); + ClientExceptionWithBody exception = assertThrows(ClientExceptionWithBody.class, executable); + + assertEquals("ConsentDetails is null for this tppId", exception.getMessage()); + } + + @Test void getConsentStatus_Ok() { From 94d6150a836ca37aba728838506b13cddb1761e7 Mon Sep 17 00:00:00 2001 From: DanieleRanaldo Date: Tue, 5 Nov 2024 15:47:35 +0100 Subject: [PATCH 02/20] modified model, dto, controller, service --- .../controller/CitizenControllerImpl.java | 2 +- .../citizen/dto/CitizenConsentDTO.java | 6 +- .../dto/CitizenConsentStateUpdateDTO.java | 2 +- .../CitizenConsentObjectToDTOMapper.java | 6 +- .../citizen/model/CitizenConsent.java | 2 +- .../citizen/model/ConsentDetails.java | 5 +- .../CitizenConsentDTOToObjectMapper.java | 6 +- .../citizen/repository/CitizenRepository.java | 2 +- .../repository/CitizenSpecificRepository.java | 4 +- .../CitizenSpecificRepositoryImpl.java | 10 +-- .../citizen/service/CitizenServiceImpl.java | 67 +++++++++---------- .../controller/CitizenControllerTest.java | 2 +- .../citizen/faker/CitizenConsentDTOFaker.java | 4 +- .../citizen/faker/CitizenConsentFaker.java | 7 +- .../CitizenConsentStateUpdateDTOFaker.java | 2 +- .../CitizenSpecificRepositoryImplTest.java | 16 ++--- .../citizen/service/CitizenServiceTest.java | 66 ++++++++++++++---- 17 files changed, 118 insertions(+), 91 deletions(-) diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/controller/CitizenControllerImpl.java b/src/main/java/it/gov/pagopa/onboarding/citizen/controller/CitizenControllerImpl.java index 85b5156..dda2e0d 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/controller/CitizenControllerImpl.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/controller/CitizenControllerImpl.java @@ -28,7 +28,7 @@ public Mono> saveCitizenConsent(@Valid Citizen @Override public Mono> stateUpdate(@Valid CitizenConsentStateUpdateDTO citizenConsentStateUpdateDTO) { return citizenService.updateChannelState( - citizenConsentStateUpdateDTO.getHashedFiscalCode(), + citizenConsentStateUpdateDTO.getFiscalCode(), citizenConsentStateUpdateDTO.getTppId(), citizenConsentStateUpdateDTO.getTppState()) .map(ResponseEntity::ok); diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/dto/CitizenConsentDTO.java b/src/main/java/it/gov/pagopa/onboarding/citizen/dto/CitizenConsentDTO.java index 931a4e3..e1d1769 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/dto/CitizenConsentDTO.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/dto/CitizenConsentDTO.java @@ -15,7 +15,7 @@ @NoArgsConstructor public class CitizenConsentDTO { @JsonAlias("fiscalCode") - private String hashedFiscalCode; + private String fiscalCode; private Map consents; @Data @@ -23,9 +23,7 @@ public class CitizenConsentDTO { @NoArgsConstructor @AllArgsConstructor public static class ConsentDTO { - private Boolean tc; private Boolean tppState; - private LocalDateTime creationDate; - private LocalDateTime lastUpdateDate; + private LocalDateTime tcDate; } } diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/dto/CitizenConsentStateUpdateDTO.java b/src/main/java/it/gov/pagopa/onboarding/citizen/dto/CitizenConsentStateUpdateDTO.java index 973eb2a..edb0f7a 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/dto/CitizenConsentStateUpdateDTO.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/dto/CitizenConsentStateUpdateDTO.java @@ -10,7 +10,7 @@ @NoArgsConstructor @AllArgsConstructor public class CitizenConsentStateUpdateDTO { - private String hashedFiscalCode; + private String fiscalCode; private String tppId; private Boolean tppState; } diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/dto/mapper/CitizenConsentObjectToDTOMapper.java b/src/main/java/it/gov/pagopa/onboarding/citizen/dto/mapper/CitizenConsentObjectToDTOMapper.java index f663d34..9ba6a1a 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/dto/mapper/CitizenConsentObjectToDTOMapper.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/dto/mapper/CitizenConsentObjectToDTOMapper.java @@ -15,14 +15,12 @@ public CitizenConsentDTO map(CitizenConsent citizenConsent) { Map consentsDTO = new HashMap<>(); citizenConsent.getConsents().forEach((tppId, consentDetails) -> consentsDTO.put(tppId, ConsentDTO.builder() - .tc(consentDetails.getTc()) .tppState(consentDetails.getTppState()) - .creationDate(consentDetails.getCreationDate()) - .lastUpdateDate(consentDetails.getLastUpdateDate()) + .tcDate(consentDetails.getTcDate()) .build())); return CitizenConsentDTO.builder() - .hashedFiscalCode(citizenConsent.getHashedFiscalCode()) + .fiscalCode(citizenConsent.getFiscalCode()) .consents(consentsDTO) .build(); } diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/model/CitizenConsent.java b/src/main/java/it/gov/pagopa/onboarding/citizen/model/CitizenConsent.java index 5b37004..d9862d6 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/model/CitizenConsent.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/model/CitizenConsent.java @@ -14,7 +14,7 @@ public class CitizenConsent { private String id; - private String hashedFiscalCode; + private String fiscalCode; private Map consents; } diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/model/ConsentDetails.java b/src/main/java/it/gov/pagopa/onboarding/citizen/model/ConsentDetails.java index 27aacf0..bc91264 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/model/ConsentDetails.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/model/ConsentDetails.java @@ -10,9 +10,6 @@ @NoArgsConstructor @SuperBuilder public class ConsentDetails { - private Boolean tc; private Boolean tppState; - private LocalDateTime creationDate; - private LocalDateTime lastTcUpdateDate; - private LocalDateTime lastUpdateDate; + private LocalDateTime tcDate; } diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/model/mapper/CitizenConsentDTOToObjectMapper.java b/src/main/java/it/gov/pagopa/onboarding/citizen/model/mapper/CitizenConsentDTOToObjectMapper.java index 4c3aef2..58b9bff 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/model/mapper/CitizenConsentDTOToObjectMapper.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/model/mapper/CitizenConsentDTOToObjectMapper.java @@ -15,14 +15,12 @@ public CitizenConsent map(CitizenConsentDTO citizenConsentDTO) { Map consents = new HashMap<>(); citizenConsentDTO.getConsents().forEach((tppId, consentDTO) -> consents.put(tppId, ConsentDetails.builder() - .tc(consentDTO.getTc()) .tppState(consentDTO.getTppState()) - .creationDate(consentDTO.getCreationDate()) - .lastTcUpdateDate(consentDTO.getLastUpdateDate()) + .tcDate(consentDTO.getTcDate()) .build())); return CitizenConsent.builder() - .hashedFiscalCode(citizenConsentDTO.getHashedFiscalCode()) + .fiscalCode(citizenConsentDTO.getFiscalCode()) .consents(consents) .build(); } diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenRepository.java b/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenRepository.java index fb58a14..9d8b3b1 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenRepository.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenRepository.java @@ -6,7 +6,7 @@ public interface CitizenRepository extends ReactiveMongoRepository, CitizenSpecificRepository { - Flux findByHashedFiscalCode(String hashedFiscalCode); + Flux findByFiscalCode(String fiscalCode); //Flux findByHashedFiscalCodeAndTppStateTrue(String hashedFiscalCode); diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepository.java b/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepository.java index 43932e7..84d3e62 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepository.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepository.java @@ -5,6 +5,6 @@ import reactor.core.publisher.Mono; public interface CitizenSpecificRepository { - Flux findByHashedFiscalCodeAndTppStateTrue(String hashedFiscalCode); - Mono findByHashedFiscalCodeAndTppId(String hashedFiscalCode, String tppId); + Flux findByFiscalCodeAndTppStateTrue(String fiscalCode); + Mono findByFiscalCodeAndTppId(String fiscalCode, String tppId); } diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImpl.java b/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImpl.java index 81db2b4..5bff272 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImpl.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImpl.java @@ -21,9 +21,9 @@ public CitizenSpecificRepositoryImpl(ReactiveMongoTemplate mongoTemplate) { this.mongoTemplate = mongoTemplate; } - public Flux findByHashedFiscalCodeAndTppStateTrue(String hashedFiscalCode) { + public Flux findByFiscalCodeAndTppStateTrue(String fiscalCode) { Aggregation aggregation = Aggregation.newAggregation( - Aggregation.match(Criteria.where("hashedFiscalCode").is(hashedFiscalCode)), + Aggregation.match(Criteria.where("fiscalCode").is(fiscalCode)), Aggregation.unwind("consents"), Aggregation.match(Criteria.where("consents.tppState").is(true)) ); @@ -38,19 +38,19 @@ public Flux findByHashedFiscalCodeAndTppStateTrue(String hashedF return Flux.just(CitizenConsent.builder() .id(result.getId()) - .hashedFiscalCode(result.getHashedFiscalCode()) + .fiscalCode(result.getFiscalCode()) .consents(validConsents) .build()); }); } - public Mono findByHashedFiscalCodeAndTppId(String hashedFiscalCode, String tppId) { + public Mono findByFiscalCodeAndTppId(String fiscalCode, String tppId) { if (tppId == null) { return Mono.empty(); } Aggregation aggregation = Aggregation.newAggregation( - Aggregation.match(Criteria.where("hashedFiscalCode").is(hashedFiscalCode)), + Aggregation.match(Criteria.where("fiscalCode").is(fiscalCode)), Aggregation.match(Criteria.where("consents." + tppId).exists(true)) ); diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceImpl.java b/src/main/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceImpl.java index 16fbafa..19d0aa9 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceImpl.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceImpl.java @@ -40,13 +40,9 @@ public CitizenServiceImpl(CitizenRepository citizenRepository, CitizenConsentObj @Override public Mono createCitizenConsent(CitizenConsentDTO citizenConsentDTO) { CitizenConsent citizenConsent = mapperToObject.map(citizenConsentDTO); - String hashedFiscalCode = Utils.createSHA256(citizenConsent.getHashedFiscalCode()); - citizenConsent.setHashedFiscalCode(hashedFiscalCode); + String fiscalCode = citizenConsent.getFiscalCode(); - citizenConsent.getConsents().forEach((tppId, consentDetails) -> { - consentDetails.setCreationDate(LocalDateTime.now()); - consentDetails.setLastUpdateDate(LocalDateTime.now()); - }); + citizenConsent.getConsents().forEach((tppId, consentDetails) -> consentDetails.setTcDate(LocalDateTime.now())); log.info("[EMD-CITIZEN][CREATE] Received consent: {}", inputSanify(citizenConsent.toString())); @@ -55,7 +51,7 @@ public Mono createCitizenConsent(CitizenConsentDTO citizenCon return Mono.error(exceptionMap.throwException(ExceptionName.TPP_NOT_FOUND, "TPP does not exist or is not active")); } - return citizenRepository.findByHashedFiscalCodeAndTppId(hashedFiscalCode, tppId) + return citizenRepository.findByFiscalCodeAndTppId(fiscalCode, tppId) .flatMap(existingConsent -> { log.info("[EMD][CREATE-CITIZEN-CONSENT] Citizen consent already exists"); return Mono.just(mapperToDTO.map(existingConsent)); @@ -75,32 +71,37 @@ public Mono createCitizenConsent(CitizenConsentDTO citizenCon @Override public Mono updateChannelState(String fiscalCode, String tppId, boolean tppState) { - String hashedFiscalCode = Utils.createSHA256(fiscalCode); - log.info("[EMD][[CITIZEN][UPDATE-CHANNEL-STATE] Received hashedFiscalCode: {} and tppId: {} with state: {}" - , hashedFiscalCode, inputSanify(tppId), tppState); - return citizenRepository.findByHashedFiscalCodeAndTppId(hashedFiscalCode, tppId) - .switchIfEmpty(Mono.error(exceptionMap.throwException - (ExceptionName.CITIZEN_NOT_ONBOARDED, "Citizen consent not founded during update state process"))) - .flatMap(citizenConsent -> { - ConsentDetails consentDetails = citizenConsent.getConsents().get(tppId); - if (consentDetails != null) { - consentDetails.setTppState(tppState); - consentDetails.setLastUpdateDate(LocalDateTime.now()); - } else { - return Mono.error(exceptionMap.throwException - (ExceptionName.CITIZEN_NOT_ONBOARDED, "ConsentDetails is null for this tppId")); + log.info("[EMD][CITIZEN][UPDATE-CHANNEL-STATE] Received hashedFiscalCode: {} and tppId: {} with state: {}", + Utils.createSHA256(fiscalCode), inputSanify(tppId), tppState); + + return tppConnector.get(tppId) + .switchIfEmpty(Mono.error(exceptionMap.throwException(ExceptionName.TPP_NOT_FOUND, "TPP does not exist or is not active"))) + .flatMap(tppResponse -> { + if (tppResponse == null || !Boolean.TRUE.equals(tppResponse.getState())) { + return Mono.error(exceptionMap.throwException(ExceptionName.TPP_NOT_FOUND, "TPP does not exist or is not active")); } - return citizenRepository.save(citizenConsent); - }) - .map(mapperToDTO::map) - .doOnSuccess(savedConsent -> log.info("[EMD][[CITIZEN][UPDATE-CHANNEL-STATE] Updated state")); + return citizenRepository.findByFiscalCodeAndTppId(fiscalCode, tppId) + .switchIfEmpty(Mono.error(exceptionMap.throwException + (ExceptionName.CITIZEN_NOT_ONBOARDED, "Citizen consent not founded during update state process"))) + .flatMap(citizenConsent -> { + ConsentDetails consentDetails = citizenConsent.getConsents().get(tppId); + if (consentDetails != null) { + consentDetails.setTppState(tppState); + } else { + return Mono.error(exceptionMap.throwException + (ExceptionName.CITIZEN_NOT_ONBOARDED, "ConsentDetails is null for this tppId")); + } + return citizenRepository.save(citizenConsent); + }) + .map(mapperToDTO::map) + .doOnSuccess(savedConsent -> log.info("[EMD][CITIZEN][UPDATE-CHANNEL-STATE] Updated state")); + }); } @Override public Mono getConsentStatus(String fiscalCode, String tppId) { - String hashedFiscalCode = Utils.createSHA256(fiscalCode); - log.info("[EMD-CITIZEN][GET-CONSENT-STATUS] Received hashedFiscalCode: {} and tppId: {}", hashedFiscalCode, inputSanify(tppId)); - return citizenRepository.findByHashedFiscalCodeAndTppId(hashedFiscalCode, tppId) + log.info("[EMD-CITIZEN][GET-CONSENT-STATUS] Received hashedFiscalCode: {} and tppId: {}", Utils.createSHA256(fiscalCode), inputSanify(tppId)); + return citizenRepository.findByFiscalCodeAndTppId(fiscalCode, tppId) .switchIfEmpty(Mono.error(exceptionMap.throwException (ExceptionName.CITIZEN_NOT_ONBOARDED, "Citizen consent not founded during get process "))) .map(mapperToDTO::map) @@ -110,9 +111,8 @@ public Mono getConsentStatus(String fiscalCode, String tppId) @Override public Mono> getListEnabledConsents(String fiscalCode) { - String hashedFiscalCode = Utils.createSHA256(fiscalCode); - log.info("[EMD-CITIZEN][FIND-CITIZEN-CONSENTS-ENABLED] Received hashedFiscalCode: {}", hashedFiscalCode); - return citizenRepository.findByHashedFiscalCodeAndTppStateTrue(hashedFiscalCode) + log.info("[EMD-CITIZEN][FIND-CITIZEN-CONSENTS-ENABLED] Received hashedFiscalCode: {}", Utils.createSHA256(fiscalCode)); + return citizenRepository.findByFiscalCodeAndTppStateTrue(fiscalCode) .collectList() .map(consentList -> consentList.stream() .map(mapperToDTO::map) @@ -124,9 +124,8 @@ public Mono> getListEnabledConsents(String fiscalCode) { @Override public Mono> getListAllConsents(String fiscalCode) { - String hashedFiscalCode = Utils.createSHA256(fiscalCode); - log.info("[EMD-CITIZEN][FIND-ALL-CITIZEN-CONSENTS] Received hashedFiscalCode: {}", (hashedFiscalCode)); - return citizenRepository.findByHashedFiscalCode(hashedFiscalCode) + log.info("[EMD-CITIZEN][FIND-ALL-CITIZEN-CONSENTS] Received hashedFiscalCode: {}", (Utils.createSHA256(fiscalCode))); + return citizenRepository.findByFiscalCode(fiscalCode) .collectList() .map(consentList -> consentList.stream() .map(mapperToDTO::map) diff --git a/src/test/java/it/gov/pagopa/onboarding/citizen/controller/CitizenControllerTest.java b/src/test/java/it/gov/pagopa/onboarding/citizen/controller/CitizenControllerTest.java index d955423..d8f9c1a 100644 --- a/src/test/java/it/gov/pagopa/onboarding/citizen/controller/CitizenControllerTest.java +++ b/src/test/java/it/gov/pagopa/onboarding/citizen/controller/CitizenControllerTest.java @@ -59,7 +59,7 @@ void stateUpdate_Ok() { CitizenConsentDTO expectedResponseDTO = CitizenConsentDTOFaker.mockInstance(true); Mockito.when(citizenService.updateChannelState( - citizenConsentStateUpdateDTO.getHashedFiscalCode(), + citizenConsentStateUpdateDTO.getFiscalCode(), citizenConsentStateUpdateDTO.getTppId(), citizenConsentStateUpdateDTO.getTppState())) .thenReturn(Mono.just(expectedResponseDTO)); diff --git a/src/test/java/it/gov/pagopa/onboarding/citizen/faker/CitizenConsentDTOFaker.java b/src/test/java/it/gov/pagopa/onboarding/citizen/faker/CitizenConsentDTOFaker.java index a135012..24bd42e 100644 --- a/src/test/java/it/gov/pagopa/onboarding/citizen/faker/CitizenConsentDTOFaker.java +++ b/src/test/java/it/gov/pagopa/onboarding/citizen/faker/CitizenConsentDTOFaker.java @@ -12,10 +12,10 @@ private CitizenConsentDTOFaker() {} public static CitizenConsentDTO mockInstance(Boolean bias) { Map consents = new HashMap<>(); - consents.put("tppId", new CitizenConsentDTO.ConsentDTO(bias, bias, LocalDateTime.now(), LocalDateTime.now())); + consents.put("tppId", new CitizenConsentDTO.ConsentDTO(bias, LocalDateTime.now())); return CitizenConsentDTO.builder() - .hashedFiscalCode("hashedFiscalCode") + .fiscalCode("fiscalCode") .consents(consents) .build(); } diff --git a/src/test/java/it/gov/pagopa/onboarding/citizen/faker/CitizenConsentFaker.java b/src/test/java/it/gov/pagopa/onboarding/citizen/faker/CitizenConsentFaker.java index fec5c44..03a2755 100644 --- a/src/test/java/it/gov/pagopa/onboarding/citizen/faker/CitizenConsentFaker.java +++ b/src/test/java/it/gov/pagopa/onboarding/citizen/faker/CitizenConsentFaker.java @@ -15,17 +15,14 @@ public static CitizenConsent mockInstance(Boolean bias) { Map consents = new HashMap<>(); ConsentDetails consentDetails = ConsentDetails.builder() - .tc(bias) .tppState(bias) - .creationDate(LocalDateTime.now()) - .lastTcUpdateDate(LocalDateTime.now()) - .lastUpdateDate(LocalDateTime.now()) + .tcDate(LocalDateTime.now()) .build(); consents.put("tppId", consentDetails); return CitizenConsent.builder() - .hashedFiscalCode("hashedFiscalCode") + .fiscalCode("fiscalCode") .consents(consents) .build(); } diff --git a/src/test/java/it/gov/pagopa/onboarding/citizen/faker/CitizenConsentStateUpdateDTOFaker.java b/src/test/java/it/gov/pagopa/onboarding/citizen/faker/CitizenConsentStateUpdateDTOFaker.java index 6685dcb..31464f6 100644 --- a/src/test/java/it/gov/pagopa/onboarding/citizen/faker/CitizenConsentStateUpdateDTOFaker.java +++ b/src/test/java/it/gov/pagopa/onboarding/citizen/faker/CitizenConsentStateUpdateDTOFaker.java @@ -8,7 +8,7 @@ private CitizenConsentStateUpdateDTOFaker() {} public static CitizenConsentStateUpdateDTO mockInstance(Boolean tppState) { return CitizenConsentStateUpdateDTO.builder() - .hashedFiscalCode("hashedFiscalCode") + .fiscalCode("hashedFiscalCode") .tppId("tppId") .tppState(tppState) .build(); diff --git a/src/test/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImplTest.java b/src/test/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImplTest.java index f3ca0cd..e09fdc8 100644 --- a/src/test/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImplTest.java +++ b/src/test/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImplTest.java @@ -30,11 +30,11 @@ public void setUp() { } @Test - void testFindByHashedFiscalCodeAndTppStateTrue() { + void testFindByFiscalCodeAndTppStateTrue() { String hashedFiscalCode = "hashedCode"; CitizenConsent citizenConsent = new CitizenConsent(); citizenConsent.setId("1"); - citizenConsent.setHashedFiscalCode(hashedFiscalCode); + citizenConsent.setFiscalCode(hashedFiscalCode); Map consents = new HashMap<>(); ConsentDetails consentDetails = new ConsentDetails(); @@ -45,20 +45,20 @@ void testFindByHashedFiscalCodeAndTppStateTrue() { when(mongoTemplate.aggregate(Mockito.any(), Mockito.eq(CitizenConsent.class), Mockito.eq(CitizenConsent.class))) .thenReturn(Flux.just(citizenConsent)); - Flux result = repository.findByHashedFiscalCodeAndTppStateTrue(hashedFiscalCode); + Flux result = repository.findByFiscalCodeAndTppStateTrue(hashedFiscalCode); Assertions.assertEquals(1, result.count().block()); - Assertions.assertEquals(hashedFiscalCode, result.blockFirst().getHashedFiscalCode()); + Assertions.assertEquals(hashedFiscalCode, result.blockFirst().getFiscalCode()); Mockito.verify(mongoTemplate).aggregate(Mockito.any(), Mockito.eq(CitizenConsent.class), Mockito.eq(CitizenConsent.class)); } @Test - void testFindByHashedFiscalCodeAndTppId() { + void testFindByFiscalCodeAndTppId() { String hashedFiscalCode = "hashedCode"; String tppId = "tpp1"; CitizenConsent citizenConsent = new CitizenConsent(); citizenConsent.setId("1"); - citizenConsent.setHashedFiscalCode(hashedFiscalCode); + citizenConsent.setFiscalCode(hashedFiscalCode); Map consents = new HashMap<>(); ConsentDetails consentDetails = new ConsentDetails(); @@ -69,9 +69,9 @@ void testFindByHashedFiscalCodeAndTppId() { when(mongoTemplate.aggregate(Mockito.any(), Mockito.eq(CitizenConsent.class), Mockito.eq(CitizenConsent.class))) .thenReturn(Flux.just(citizenConsent)); - Mono result = repository.findByHashedFiscalCodeAndTppId(hashedFiscalCode, tppId); + Mono result = repository.findByFiscalCodeAndTppId(hashedFiscalCode, tppId); - Assertions.assertEquals(hashedFiscalCode, result.block().getHashedFiscalCode()); + Assertions.assertEquals(hashedFiscalCode, result.block().getFiscalCode()); Mockito.verify(mongoTemplate).aggregate(Mockito.any(), Mockito.eq(CitizenConsent.class), Mockito.eq(CitizenConsent.class)); } diff --git a/src/test/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceTest.java b/src/test/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceTest.java index 0f6cf85..c2f8ffd 100644 --- a/src/test/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceTest.java +++ b/src/test/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceTest.java @@ -65,8 +65,7 @@ class CitizenServiceTest { void createCitizenConsent_Ok() { CitizenConsentDTO citizenConsentDTO = dtoMapper.map(CITIZEN_CONSENT); - TppDTO mockTppDTO = new TppDTO(); - mockTppDTO.setTppId(CITIZEN_CONSENT_DTO.getConsents().keySet().stream().findFirst().orElse(null)); + TppDTO mockTppDTO = TppDTOFaker.mockInstance(); mockTppDTO.setState(true); Mockito.when(tppConnector.get(anyString())) @@ -78,7 +77,7 @@ void createCitizenConsent_Ok() { Mockito.when(citizenRepository.findById(anyString())) .thenReturn(Mono.empty()); - Mockito.when(citizenRepository.findByHashedFiscalCodeAndTppId(anyString(), anyString())) + Mockito.when(citizenRepository.findByFiscalCodeAndTppId(anyString(), anyString())) .thenReturn(Mono.empty()); CitizenConsentDTO response = citizenService.createCitizenConsent(CITIZEN_CONSENT_DTO).block(); @@ -102,7 +101,7 @@ void createCitizenConsent_AlreadyExists() { Mockito.when(citizenRepository.findById(anyString())) .thenReturn(Mono.just(CITIZEN_CONSENT)); - Mockito.when(citizenRepository.findByHashedFiscalCodeAndTppId(anyString(), anyString())) + Mockito.when(citizenRepository.findByFiscalCodeAndTppId(anyString(), anyString())) .thenReturn(Mono.just(CITIZEN_CONSENT)); CitizenConsentDTO response = citizenService.createCitizenConsent(CITIZEN_CONSENT_DTO).block(); @@ -117,7 +116,7 @@ void createCitizenConsent_Ko_TppNull() { citizenConsentDTO.getConsents().clear(); Mockito.when(tppConnector.get(anyString())).thenReturn(Mono.empty()); - Mockito.when(citizenRepository.findByHashedFiscalCodeAndTppId(anyString(), anyString())) + Mockito.when(citizenRepository.findByFiscalCodeAndTppId(anyString(), anyString())) .thenReturn(Mono.empty()); ClientExceptionWithBody exception = assertThrows(ClientExceptionWithBody.class, @@ -133,7 +132,7 @@ void createCitizenConsent_Ko_TppInactive() { mockTppDTO.setState(false); Mockito.when(tppConnector.get(anyString())).thenReturn(Mono.just(mockTppDTO)); - Mockito.when(citizenRepository.findByHashedFiscalCodeAndTppId(anyString(), anyString())) + Mockito.when(citizenRepository.findByFiscalCodeAndTppId(anyString(), anyString())) .thenReturn(Mono.empty()); CitizenConsentDTO citizenConsentDTO = dtoMapper.map(CITIZEN_CONSENT); @@ -160,8 +159,13 @@ void createCitizenConsent_Ko_EmdEncryptError() { @Test void updateChannelState_Ok() { + TppDTO mockTppDTO = TppDTOFaker.mockInstance(); + mockTppDTO.setState(true); + + Mockito.when(tppConnector.get(anyString())) + .thenReturn(Mono.just(mockTppDTO)); - Mockito.when(citizenRepository.findByHashedFiscalCodeAndTppId(HASHED_FISCAL_CODE, TPP_ID)) + Mockito.when(citizenRepository.findByFiscalCodeAndTppId(HASHED_FISCAL_CODE, TPP_ID)) .thenReturn(Mono.just(CITIZEN_CONSENT)); Mockito.when(citizenRepository.save(Mockito.any())) @@ -177,8 +181,13 @@ void updateChannelState_Ok() { @Test void updateChannelState_Ko_CitizenNotOnboarded() { + TppDTO mockTppDTO = TppDTOFaker.mockInstance(); + mockTppDTO.setState(true); + + Mockito.when(tppConnector.get(anyString())) + .thenReturn(Mono.just(mockTppDTO)); - Mockito.when(citizenRepository.findByHashedFiscalCodeAndTppId(HASHED_FISCAL_CODE, TPP_ID)) + Mockito.when(citizenRepository.findByFiscalCodeAndTppId(HASHED_FISCAL_CODE, TPP_ID)) .thenReturn(Mono.empty()); Executable executable = () -> citizenService.updateChannelState(FISCAL_CODE, TPP_ID, true).block(); @@ -189,10 +198,17 @@ void updateChannelState_Ko_CitizenNotOnboarded() { @Test void updateChannelState_Ok_ConsentDetailsIsNull() { + + TppDTO mockTppDTO = TppDTOFaker.mockInstance(); + mockTppDTO.setState(true); + CitizenConsent citizenConsentWithConsentDetailNull = CitizenConsentFaker.mockInstance(true); citizenConsentWithConsentDetailNull.getConsents().put(TPP_ID, null); - Mockito.when(citizenRepository.findByHashedFiscalCodeAndTppId(HASHED_FISCAL_CODE, TPP_ID)) + Mockito.when(tppConnector.get(anyString())) + .thenReturn(Mono.just(mockTppDTO)); + + Mockito.when(citizenRepository.findByFiscalCodeAndTppId(HASHED_FISCAL_CODE, TPP_ID)) .thenReturn(Mono.just(citizenConsentWithConsentDetailNull)); Mockito.when(citizenRepository.save(Mockito.any())) @@ -204,12 +220,36 @@ void updateChannelState_Ok_ConsentDetailsIsNull() { assertEquals("ConsentDetails is null for this tppId", exception.getMessage()); } + @Test + void updateChannelState_Ko_TppResponseIsNull() { + + Mockito.when(tppConnector.get(anyString())).thenReturn(Mono.empty()); + + Executable executable = () -> citizenService.updateChannelState(FISCAL_CODE, TPP_ID, TPP_STATE).block(); + ClientExceptionWithBody exception = assertThrows(ClientExceptionWithBody.class, executable); + + assertEquals("TPP does not exist or is not active", exception.getMessage()); + } + + @Test + void updateChannelState_Ko_TppInactive() { + + TppDTO mockTppDTO = TppDTOFaker.mockInstance(); + mockTppDTO.setState(false); + + Mockito.when(tppConnector.get(anyString())).thenReturn(Mono.just(mockTppDTO)); + + Executable executable = () -> citizenService.updateChannelState(FISCAL_CODE, TPP_ID, TPP_STATE).block(); + ClientExceptionWithBody exception = assertThrows(ClientExceptionWithBody.class, executable); + + assertEquals("TPP does not exist or is not active", exception.getMessage()); + } @Test void getConsentStatus_Ok() { - Mockito.when(citizenRepository.findByHashedFiscalCodeAndTppId(HASHED_FISCAL_CODE, TPP_ID)) + Mockito.when(citizenRepository.findByFiscalCodeAndTppId(HASHED_FISCAL_CODE, TPP_ID)) .thenReturn(Mono.just(CITIZEN_CONSENT)); CitizenConsentDTO response = citizenService.getConsentStatus(FISCAL_CODE, TPP_ID).block(); @@ -219,7 +259,7 @@ void getConsentStatus_Ok() { @Test void getConsentStatus_Ko_CitizenNotOnboarded() { - Mockito.when(citizenRepository.findByHashedFiscalCodeAndTppId(HASHED_FISCAL_CODE, TPP_ID)) + Mockito.when(citizenRepository.findByFiscalCodeAndTppId(HASHED_FISCAL_CODE, TPP_ID)) .thenReturn(Mono.empty()); Executable executable = () -> citizenService.getConsentStatus(FISCAL_CODE, TPP_ID).block(); @@ -232,7 +272,7 @@ void getConsentStatus_Ko_CitizenNotOnboarded() { void getListEnabledConsents_Ok() { - Mockito.when(citizenRepository.findByHashedFiscalCodeAndTppStateTrue(HASHED_FISCAL_CODE)) + Mockito.when(citizenRepository.findByFiscalCodeAndTppStateTrue(HASHED_FISCAL_CODE)) .thenReturn(Flux.just(CITIZEN_CONSENT)); List response = citizenService.getListEnabledConsents(FISCAL_CODE).block(); @@ -242,7 +282,7 @@ void getListEnabledConsents_Ok() { @Test void getListAllConsents_Ok() { - Mockito.when(citizenRepository.findByHashedFiscalCode(HASHED_FISCAL_CODE)) + Mockito.when(citizenRepository.findByFiscalCode(HASHED_FISCAL_CODE)) .thenReturn(Flux.just(CITIZEN_CONSENT)); List response = citizenService.getListAllConsents(FISCAL_CODE).block(); From 15fef520ff9295d71ffc2c2669137287eb60250a Mon Sep 17 00:00:00 2001 From: DanieleRanaldo Date: Tue, 5 Nov 2024 16:27:39 +0100 Subject: [PATCH 03/20] added rest-client --- src/main/resources/application.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index ad8085a..74fb1cc 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -38,3 +38,6 @@ management: exposure.include: "*" web: exposure.include: info, health +rest-client: + tpp: + baseUrl: ${EMD_TPP:http://emd-tpp} \ No newline at end of file From 82f31e52a3c6505c0d798dc4797faf888492f712 Mon Sep 17 00:00:00 2001 From: DanieleRanaldo Date: Thu, 7 Nov 2024 10:28:41 +0100 Subject: [PATCH 04/20] modified query, service, controller, added validation class --- .../citizen/constants/CitizenConstants.java | 2 + .../citizen/controller/CitizenController.java | 4 +- .../controller/CitizenControllerImpl.java | 9 ++- .../citizen/repository/CitizenRepository.java | 8 +- .../repository/CitizenSpecificRepository.java | 5 +- .../CitizenSpecificRepositoryImpl.java | 26 +++---- .../citizen/service/CitizenService.java | 4 +- .../citizen/service/CitizenServiceImpl.java | 54 +++++--------- .../CitizenConsentValidationService.java | 15 ++++ .../CitizenConsentValidationServiceImpl.java | 73 +++++++++++++++++++ 10 files changed, 137 insertions(+), 63 deletions(-) create mode 100644 src/main/java/it/gov/pagopa/onboarding/citizen/validation/CitizenConsentValidationService.java create mode 100644 src/main/java/it/gov/pagopa/onboarding/citizen/validation/CitizenConsentValidationServiceImpl.java diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/constants/CitizenConstants.java b/src/main/java/it/gov/pagopa/onboarding/citizen/constants/CitizenConstants.java index 0c80794..647e90f 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/constants/CitizenConstants.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/constants/CitizenConstants.java @@ -14,6 +14,8 @@ public static final class ExceptionMessage { public static final String CITIZEN_NOT_ONBOARDED = "CITIZEN_NOT_ONBOARDED"; public static final String GENERIC_ERROR = "GENERIC_ERROR"; + public static final String TPP_NOT_FOUND = "TPP does not exist or is not active"; + private ExceptionMessage() {} } diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/controller/CitizenController.java b/src/main/java/it/gov/pagopa/onboarding/citizen/controller/CitizenController.java index 51a320f..c64a004 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/controller/CitizenController.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/controller/CitizenController.java @@ -46,7 +46,7 @@ public interface CitizenController { * @return a list of channels with enabled consents */ @GetMapping("/list/{fiscalCode}/enabled") - Mono>> getCitizenConsentsEnabled(@PathVariable @Pattern(regexp = FISCAL_CODE_STRUCTURE_REGEX, message = "Invalid fiscal code format") String fiscalCode); + Mono>> getTppEnabledList(@PathVariable @Pattern(regexp = FISCAL_CODE_STRUCTURE_REGEX, message = "Invalid fiscal code format") String fiscalCode); /** * List all channels and their consent status for a specific citizen. @@ -55,6 +55,6 @@ public interface CitizenController { * @return a list of all channels with their consent statuses */ @GetMapping("/list/{fiscalCode}") - Mono>> getCitizenConsents(@PathVariable @Pattern(regexp = FISCAL_CODE_STRUCTURE_REGEX, message = "Invalid fiscal code format") String fiscalCode); + Mono> get(@PathVariable @Pattern(regexp = FISCAL_CODE_STRUCTURE_REGEX, message = "Invalid fiscal code format") String fiscalCode); } diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/controller/CitizenControllerImpl.java b/src/main/java/it/gov/pagopa/onboarding/citizen/controller/CitizenControllerImpl.java index dda2e0d..26b3cb5 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/controller/CitizenControllerImpl.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/controller/CitizenControllerImpl.java @@ -41,14 +41,15 @@ public Mono> getConsentStatus(String fiscalCod } @Override - public Mono>> getCitizenConsentsEnabled(String fiscalCode) { - return citizenService.getListEnabledConsents(fiscalCode) + public Mono>> getTppEnabledList(String fiscalCode) { + return citizenService.getTppEnabledList(fiscalCode) .map(ResponseEntity::ok); } @Override - public Mono>> getCitizenConsents(String fiscalCode) { - return citizenService.getListAllConsents(fiscalCode) + public Mono> get(String fiscalCode) { + return citizenService.get(fiscalCode) .map(ResponseEntity::ok); } + } diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenRepository.java b/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenRepository.java index 9d8b3b1..9263ea7 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenRepository.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenRepository.java @@ -2,14 +2,10 @@ import it.gov.pagopa.onboarding.citizen.model.CitizenConsent; import org.springframework.data.mongodb.repository.ReactiveMongoRepository; -import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; public interface CitizenRepository extends ReactiveMongoRepository, CitizenSpecificRepository { - Flux findByFiscalCode(String fiscalCode); - - //Flux findByHashedFiscalCodeAndTppStateTrue(String hashedFiscalCode); - - //Mono findByHashedFiscalCodeAndTppId(String hashedFiscalCode, String tppId); + Mono findByFiscalCode(String fiscalCode); } diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepository.java b/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepository.java index 84d3e62..8cd9c4a 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepository.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepository.java @@ -1,10 +1,11 @@ package it.gov.pagopa.onboarding.citizen.repository; import it.gov.pagopa.onboarding.citizen.model.CitizenConsent; -import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import java.util.List; + public interface CitizenSpecificRepository { - Flux findByFiscalCodeAndTppStateTrue(String fiscalCode); + Mono> findByFiscalCodeAndTppStateTrue(String fiscalCode); Mono findByFiscalCodeAndTppId(String fiscalCode, String tppId); } diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImpl.java b/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImpl.java index 5bff272..1606b68 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImpl.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImpl.java @@ -1,16 +1,14 @@ package it.gov.pagopa.onboarding.citizen.repository; import it.gov.pagopa.onboarding.citizen.model.CitizenConsent; -import it.gov.pagopa.onboarding.citizen.model.ConsentDetails; import org.springframework.data.mongodb.core.ReactiveMongoTemplate; import org.springframework.data.mongodb.core.aggregation.Aggregation; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.stereotype.Repository; -import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import java.util.List; import java.util.Map; -import java.util.stream.Collectors; @Repository public class CitizenSpecificRepositoryImpl implements CitizenSpecificRepository { @@ -21,7 +19,7 @@ public CitizenSpecificRepositoryImpl(ReactiveMongoTemplate mongoTemplate) { this.mongoTemplate = mongoTemplate; } - public Flux findByFiscalCodeAndTppStateTrue(String fiscalCode) { + public Mono> findByFiscalCodeAndTppStateTrue(String fiscalCode) { Aggregation aggregation = Aggregation.newAggregation( Aggregation.match(Criteria.where("fiscalCode").is(fiscalCode)), Aggregation.unwind("consents"), @@ -30,18 +28,20 @@ public Flux findByFiscalCodeAndTppStateTrue(String fiscalCode) { return mongoTemplate.aggregate(aggregation, CitizenConsent.class, CitizenConsent.class) .flatMap(result -> { - Map validConsents = result.getConsents() + + List validConsentIds = result.getConsents() .entrySet() .stream() .filter(entry -> entry.getValue().getTppState()) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - - return Flux.just(CitizenConsent.builder() - .id(result.getId()) - .fiscalCode(result.getFiscalCode()) - .consents(validConsents) - .build()); - }); + .map(Map.Entry::getKey) + .toList(); + + return Mono.just(validConsentIds); + }) + .collectList() + .map(lists -> lists.stream() + .flatMap(List::stream) + .toList()); } public Mono findByFiscalCodeAndTppId(String fiscalCode, String tppId) { diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/service/CitizenService.java b/src/main/java/it/gov/pagopa/onboarding/citizen/service/CitizenService.java index ae98fcd..5fc865c 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/service/CitizenService.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/service/CitizenService.java @@ -10,6 +10,6 @@ public interface CitizenService { Mono createCitizenConsent(CitizenConsentDTO citizenConsent); Mono updateChannelState(String fiscalCode, String tppId, boolean tppState); Mono getConsentStatus(String fiscalCode, String tppId); - Mono> getListEnabledConsents(String fiscalCode); - Mono> getListAllConsents(String fiscalCode); + Mono> getTppEnabledList(String fiscalCode); + Mono get(String fiscalCode); } diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceImpl.java b/src/main/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceImpl.java index 19d0aa9..2a257ca 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceImpl.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceImpl.java @@ -3,6 +3,7 @@ import it.gov.pagopa.common.utils.Utils; import it.gov.pagopa.onboarding.citizen.configuration.ExceptionMap; import it.gov.pagopa.onboarding.citizen.connector.tpp.TppConnectorImpl; +import it.gov.pagopa.onboarding.citizen.constants.CitizenConstants.ExceptionMessage; import it.gov.pagopa.onboarding.citizen.constants.CitizenConstants.ExceptionName; import it.gov.pagopa.onboarding.citizen.dto.CitizenConsentDTO; import it.gov.pagopa.onboarding.citizen.dto.mapper.CitizenConsentObjectToDTOMapper; @@ -10,11 +11,13 @@ import it.gov.pagopa.onboarding.citizen.model.ConsentDetails; import it.gov.pagopa.onboarding.citizen.model.mapper.CitizenConsentDTOToObjectMapper; import it.gov.pagopa.onboarding.citizen.repository.CitizenRepository; +import it.gov.pagopa.onboarding.citizen.validation.CitizenConsentValidationServiceImpl; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import reactor.core.publisher.Mono; import java.time.LocalDateTime; +import java.util.Collections; import java.util.List; import static it.gov.pagopa.common.utils.Utils.inputSanify; @@ -28,17 +31,20 @@ public class CitizenServiceImpl implements CitizenService { private final CitizenConsentDTOToObjectMapper mapperToObject; private final ExceptionMap exceptionMap; private final TppConnectorImpl tppConnector; + private final CitizenConsentValidationServiceImpl validationService; - public CitizenServiceImpl(CitizenRepository citizenRepository, CitizenConsentObjectToDTOMapper mapperToDTO, CitizenConsentDTOToObjectMapper mapperToObject, ExceptionMap exceptionMap, TppConnectorImpl tppConnector) { + public CitizenServiceImpl(CitizenRepository citizenRepository, CitizenConsentObjectToDTOMapper mapperToDTO, CitizenConsentDTOToObjectMapper mapperToObject, ExceptionMap exceptionMap, TppConnectorImpl tppConnector, CitizenConsentValidationServiceImpl validationService) { this.citizenRepository = citizenRepository; this.mapperToDTO = mapperToDTO; this.mapperToObject = mapperToObject; this.exceptionMap = exceptionMap; this.tppConnector = tppConnector; + this.validationService = validationService; } @Override public Mono createCitizenConsent(CitizenConsentDTO citizenConsentDTO) { + CitizenConsent citizenConsent = mapperToObject.map(citizenConsentDTO); String fiscalCode = citizenConsent.getFiscalCode(); @@ -48,37 +54,25 @@ public Mono createCitizenConsent(CitizenConsentDTO citizenCon String tppId = citizenConsent.getConsents().keySet().stream().findFirst().orElse(null); if (tppId == null) { - return Mono.error(exceptionMap.throwException(ExceptionName.TPP_NOT_FOUND, "TPP does not exist or is not active")); + return Mono.error(exceptionMap.throwException(ExceptionName.TPP_NOT_FOUND, ExceptionMessage.TPP_NOT_FOUND)); } - return citizenRepository.findByFiscalCodeAndTppId(fiscalCode, tppId) - .flatMap(existingConsent -> { - log.info("[EMD][CREATE-CITIZEN-CONSENT] Citizen consent already exists"); - return Mono.just(mapperToDTO.map(existingConsent)); - }) - .switchIfEmpty(tppConnector.get(tppId) - .flatMap(tppResponse -> { - if (tppResponse != null && Boolean.TRUE.equals(tppResponse.getState())) { - return citizenRepository.save(citizenConsent) - .doOnSuccess(savedConsent -> log.info("[EMD][CREATE-CITIZEN-CONSENT] Created new citizen consent")) - .flatMap(savedConsent -> Mono.just(mapperToDTO.map(savedConsent))); - } else { - return Mono.error(exceptionMap.throwException(ExceptionName.TPP_NOT_FOUND, "TPP does not exist or is not active")); - } - }) - ); + return citizenRepository.findByFiscalCode(fiscalCode) + .flatMap(existingConsent -> validationService.handleExistingConsent(existingConsent, tppId, citizenConsent)) + .switchIfEmpty(validationService.validateTppAndSaveConsent(fiscalCode, tppId, citizenConsent)); } + @Override public Mono updateChannelState(String fiscalCode, String tppId, boolean tppState) { log.info("[EMD][CITIZEN][UPDATE-CHANNEL-STATE] Received hashedFiscalCode: {} and tppId: {} with state: {}", Utils.createSHA256(fiscalCode), inputSanify(tppId), tppState); return tppConnector.get(tppId) - .switchIfEmpty(Mono.error(exceptionMap.throwException(ExceptionName.TPP_NOT_FOUND, "TPP does not exist or is not active"))) + .switchIfEmpty(Mono.error(exceptionMap.throwException(ExceptionName.TPP_NOT_FOUND, ExceptionMessage.TPP_NOT_FOUND))) .flatMap(tppResponse -> { if (tppResponse == null || !Boolean.TRUE.equals(tppResponse.getState())) { - return Mono.error(exceptionMap.throwException(ExceptionName.TPP_NOT_FOUND, "TPP does not exist or is not active")); + return Mono.error(exceptionMap.throwException(ExceptionName.TPP_NOT_FOUND, ExceptionMessage.TPP_NOT_FOUND)); } return citizenRepository.findByFiscalCodeAndTppId(fiscalCode, tppId) .switchIfEmpty(Mono.error(exceptionMap.throwException @@ -110,27 +104,19 @@ public Mono getConsentStatus(String fiscalCode, String tppId) } @Override - public Mono> getListEnabledConsents(String fiscalCode) { + public Mono> getTppEnabledList(String fiscalCode) { log.info("[EMD-CITIZEN][FIND-CITIZEN-CONSENTS-ENABLED] Received hashedFiscalCode: {}", Utils.createSHA256(fiscalCode)); - return citizenRepository.findByFiscalCodeAndTppStateTrue(fiscalCode) - .collectList() - .map(consentList -> consentList.stream() - .map(mapperToDTO::map) - .toList() - ) - .doOnSuccess(consentList -> log.info("EMD][CITIZEN][FIND-CITIZEN-CONSENTS-ENABLED] Consents founded: {}", (consentList.size()))); + return citizenRepository.findByFiscalCodeAndTppStateTrue(fiscalCode) + .switchIfEmpty(Mono.just(Collections.emptyList())) + .doOnSuccess(tppIdList -> log.info("EMD][CITIZEN][FIND-CITIZEN-CONSENTS-ENABLED] Consents found: {}", (tppIdList.size()))); } @Override - public Mono> getListAllConsents(String fiscalCode) { + public Mono get(String fiscalCode) { log.info("[EMD-CITIZEN][FIND-ALL-CITIZEN-CONSENTS] Received hashedFiscalCode: {}", (Utils.createSHA256(fiscalCode))); return citizenRepository.findByFiscalCode(fiscalCode) - .collectList() - .map(consentList -> consentList.stream() - .map(mapperToDTO::map) - .toList() - ) + .map(mapperToDTO::map) .doOnSuccess(consentList -> log.info("[EMD-CITIZEN][FIND-ALL-CITIZEN-CONSENTS] Consents found:: {}", consentList)); } } diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/validation/CitizenConsentValidationService.java b/src/main/java/it/gov/pagopa/onboarding/citizen/validation/CitizenConsentValidationService.java new file mode 100644 index 0000000..46830e9 --- /dev/null +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/validation/CitizenConsentValidationService.java @@ -0,0 +1,15 @@ +package it.gov.pagopa.onboarding.citizen.validation; + +import it.gov.pagopa.onboarding.citizen.dto.CitizenConsentDTO; +import it.gov.pagopa.onboarding.citizen.dto.TppDTO; +import it.gov.pagopa.onboarding.citizen.model.CitizenConsent; +import reactor.core.publisher.Mono; + +public interface CitizenConsentValidationService { + + Mono handleExistingConsent(CitizenConsent existingConsent, String tppId, CitizenConsent citizenConsent); + + Mono validateTppAndSaveConsent(String fiscalCode, String tppId, CitizenConsent citizenConsent); + + boolean isTppValid(TppDTO tppResponse); +} diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/validation/CitizenConsentValidationServiceImpl.java b/src/main/java/it/gov/pagopa/onboarding/citizen/validation/CitizenConsentValidationServiceImpl.java new file mode 100644 index 0000000..0804fcc --- /dev/null +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/validation/CitizenConsentValidationServiceImpl.java @@ -0,0 +1,73 @@ +package it.gov.pagopa.onboarding.citizen.validation; + +import it.gov.pagopa.onboarding.citizen.configuration.ExceptionMap; +import it.gov.pagopa.onboarding.citizen.connector.tpp.TppConnectorImpl; +import it.gov.pagopa.onboarding.citizen.constants.CitizenConstants.ExceptionName; +import it.gov.pagopa.onboarding.citizen.dto.CitizenConsentDTO; +import it.gov.pagopa.onboarding.citizen.dto.TppDTO; +import it.gov.pagopa.onboarding.citizen.dto.mapper.CitizenConsentObjectToDTOMapper; +import it.gov.pagopa.onboarding.citizen.model.CitizenConsent; +import it.gov.pagopa.onboarding.citizen.repository.CitizenRepository; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Mono; + +@Service +@Slf4j +public class CitizenConsentValidationServiceImpl implements CitizenConsentValidationService { + + private final CitizenRepository citizenRepository; + private final TppConnectorImpl tppConnector; + private final CitizenConsentObjectToDTOMapper mapperToDTO; + private final ExceptionMap exceptionMap; + + public CitizenConsentValidationServiceImpl(CitizenRepository citizenRepository, TppConnectorImpl tppConnector, + CitizenConsentObjectToDTOMapper mapperToDTO, ExceptionMap exceptionMap) { + this.citizenRepository = citizenRepository; + this.tppConnector = tppConnector; + this.mapperToDTO = mapperToDTO; + this.exceptionMap = exceptionMap; + } + + @Override + public Mono handleExistingConsent(CitizenConsent existingConsent, String tppId, CitizenConsent citizenConsent) { + if (existingConsent.getConsents().containsKey(tppId)) { + return Mono.just(mapperToDTO.map(existingConsent)); + } else { + return validateTppAndUpdateConsent(existingConsent, tppId, citizenConsent); + } + } + + @Override + public Mono validateTppAndSaveConsent(String fiscalCode, String tppId, CitizenConsent citizenConsent) { + return tppConnector.get(tppId) + .flatMap(tppResponse -> { + if (isTppValid(tppResponse)) { + return citizenRepository.save(citizenConsent) + .doOnSuccess(savedConsent -> log.info("[EMD][CREATE-CITIZEN-CONSENT] Created new citizen consent for fiscal code: {}", fiscalCode)) + .flatMap(savedConsent -> Mono.just(mapperToDTO.map(savedConsent))); + } else { + return Mono.error(exceptionMap.throwException(ExceptionName.TPP_NOT_FOUND, "TPP does not exist or is not active")); + } + }); + } + + @Override + public boolean isTppValid(TppDTO tppResponse) { + return tppResponse != null && Boolean.TRUE.equals(tppResponse.getState()); + } + + private Mono validateTppAndUpdateConsent(CitizenConsent existingConsent, String tppId, CitizenConsent citizenConsent) { + return tppConnector.get(tppId) + .flatMap(tppResponse -> { + if (isTppValid(tppResponse)) { + existingConsent.getConsents().put(tppId, citizenConsent.getConsents().get(tppId)); + return citizenRepository.save(existingConsent) + .doOnSuccess(savedConsent -> log.info("[EMD][CREATE-CITIZEN-CONSENT] Updated citizen consent for TPP: {}", tppId)) + .flatMap(savedConsent -> Mono.just(mapperToDTO.map(savedConsent))); + } else { + return Mono.error(exceptionMap.throwException(ExceptionName.TPP_NOT_FOUND, "TPP does not exist or is not active")); + } + }); + } +} From dd78a55d3d85a67ae19cb292efaa352b84cf31c0 Mon Sep 17 00:00:00 2001 From: Vitolo-Andrea Date: Thu, 7 Nov 2024 15:08:27 +0100 Subject: [PATCH 05/20] Repository fix --- .../CitizenSpecificRepositoryImpl.java | 34 +++++++++---------- .../controller/CitizenControllerTest.java | 26 +++++++------- .../CitizenSpecificRepositoryImplTest.java | 25 ++++++-------- .../citizen/service/CitizenServiceTest.java | 10 +++--- 4 files changed, 46 insertions(+), 49 deletions(-) diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImpl.java b/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImpl.java index 1606b68..2cb2692 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImpl.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImpl.java @@ -1,14 +1,20 @@ package it.gov.pagopa.onboarding.citizen.repository; import it.gov.pagopa.onboarding.citizen.model.CitizenConsent; +import lombok.Data; import org.springframework.data.mongodb.core.ReactiveMongoTemplate; import org.springframework.data.mongodb.core.aggregation.Aggregation; +import org.springframework.data.mongodb.core.aggregation.AggregationOperation; +import org.springframework.data.mongodb.core.aggregation.AggregationResults; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.stereotype.Repository; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; @Repository public class CitizenSpecificRepositoryImpl implements CitizenSpecificRepository { @@ -22,28 +28,16 @@ public CitizenSpecificRepositoryImpl(ReactiveMongoTemplate mongoTemplate) { public Mono> findByFiscalCodeAndTppStateTrue(String fiscalCode) { Aggregation aggregation = Aggregation.newAggregation( Aggregation.match(Criteria.where("fiscalCode").is(fiscalCode)), - Aggregation.unwind("consents"), - Aggregation.match(Criteria.where("consents.tppState").is(true)) + Aggregation.project("consents").asArray("consentsArray"), + Aggregation.match(Criteria.where("consentsArray.v.tppState").is(true)) ); - return mongoTemplate.aggregate(aggregation, CitizenConsent.class, CitizenConsent.class) - .flatMap(result -> { - - List validConsentIds = result.getConsents() - .entrySet() - .stream() - .filter(entry -> entry.getValue().getTppState()) - .map(Map.Entry::getKey) - .toList(); - - return Mono.just(validConsentIds); - }) + return mongoTemplate.aggregate(aggregation, "citizen_consents", ConsentKeyWrapper.class) .collectList() - .map(lists -> lists.stream() - .flatMap(List::stream) - .toList()); + .map(results -> results.stream() + .map(ConsentKeyWrapper::getKey) + .collect(Collectors.toList())); } - public Mono findByFiscalCodeAndTppId(String fiscalCode, String tppId) { if (tppId == null) { return Mono.empty(); @@ -57,5 +51,9 @@ public Mono findByFiscalCodeAndTppId(String fiscalCode, String t return mongoTemplate.aggregate(aggregation, CitizenConsent.class, CitizenConsent.class) .next(); } + @Data + public static class ConsentKeyWrapper { + private String key; + } } diff --git a/src/test/java/it/gov/pagopa/onboarding/citizen/controller/CitizenControllerTest.java b/src/test/java/it/gov/pagopa/onboarding/citizen/controller/CitizenControllerTest.java index d8f9c1a..93583e0 100644 --- a/src/test/java/it/gov/pagopa/onboarding/citizen/controller/CitizenControllerTest.java +++ b/src/test/java/it/gov/pagopa/onboarding/citizen/controller/CitizenControllerTest.java @@ -11,6 +11,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.MediaType; import org.springframework.test.web.reactive.server.WebTestClient; import reactor.core.publisher.Mono; @@ -100,39 +101,40 @@ void getConsentStatus_Ok() { @Test void getCitizenConsentsEnabled_Ok() { - List citizenConsentDTOList = List.of(CitizenConsentDTOFaker.mockInstance(true)); + List tppIdList = List.of("tpp1","tpp2"); - Mockito.when(citizenService.getListEnabledConsents(FISCAL_CODE)) - .thenReturn(Mono.just(citizenConsentDTOList)); + Mockito.when(citizenService.getTppEnabledList(FISCAL_CODE)) + .thenReturn(Mono.just(tppIdList)); webClient.get() .uri("/emd/citizen/list/{fiscalCode}/enabled", FISCAL_CODE) .exchange() .expectStatus().isOk() - .expectBodyList(CitizenConsentDTO.class) + .expectBody(new ParameterizedTypeReference>() { + }) .consumeWith(response -> { - List resultResponse = response.getResponseBody(); + List resultResponse = response.getResponseBody(); Assertions.assertNotNull(resultResponse); - Assertions.assertEquals(citizenConsentDTOList.size(), resultResponse.size()); + Assertions.assertEquals(tppIdList.size(), resultResponse.size()); }); } @Test void getCitizenConsents_Ok() { - List citizenConsentDTOList = List.of(CitizenConsentDTOFaker.mockInstance(true)); + CitizenConsentDTO citizenConsentDTO = CitizenConsentDTOFaker.mockInstance(true); - Mockito.when(citizenService.getListAllConsents(FISCAL_CODE)) - .thenReturn(Mono.just(citizenConsentDTOList)); + Mockito.when(citizenService.get(FISCAL_CODE)) + .thenReturn(Mono.just(citizenConsentDTO)); webClient.get() .uri("/emd/citizen/list/{fiscalCode}", FISCAL_CODE) .exchange() .expectStatus().isOk() - .expectBodyList(CitizenConsentDTO.class) + .expectBody(CitizenConsentDTO.class) .consumeWith(response -> { - List resultResponse = response.getResponseBody(); + CitizenConsentDTO resultResponse = response.getResponseBody(); Assertions.assertNotNull(resultResponse); - Assertions.assertEquals(citizenConsentDTOList.size(), resultResponse.size()); + Assertions.assertEquals(citizenConsentDTO, resultResponse); }); } } diff --git a/src/test/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImplTest.java b/src/test/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImplTest.java index e09fdc8..7df1a6a 100644 --- a/src/test/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImplTest.java +++ b/src/test/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImplTest.java @@ -13,8 +13,11 @@ import reactor.core.publisher.Mono; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Objects; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) @@ -31,25 +34,19 @@ public void setUp() { @Test void testFindByFiscalCodeAndTppStateTrue() { + CitizenSpecificRepositoryImpl.ConsentKeyWrapper key = new CitizenSpecificRepositoryImpl.ConsentKeyWrapper(); + key.setKey("tppId"); String hashedFiscalCode = "hashedCode"; - CitizenConsent citizenConsent = new CitizenConsent(); - citizenConsent.setId("1"); - citizenConsent.setFiscalCode(hashedFiscalCode); - Map consents = new HashMap<>(); - ConsentDetails consentDetails = new ConsentDetails(); - consentDetails.setTppState(true); - consents.put("tpp1", consentDetails); - citizenConsent.setConsents(consents); + when(mongoTemplate.aggregate(Mockito.any(), anyString(), Mockito.eq(CitizenSpecificRepositoryImpl.ConsentKeyWrapper.class))) + .thenReturn(Flux.just(key)); - when(mongoTemplate.aggregate(Mockito.any(), Mockito.eq(CitizenConsent.class), Mockito.eq(CitizenConsent.class))) - .thenReturn(Flux.just(citizenConsent)); + List result = repository.findByFiscalCodeAndTppStateTrue(hashedFiscalCode).block(); - Flux result = repository.findByFiscalCodeAndTppStateTrue(hashedFiscalCode); + Assertions.assertNotNull(result); + Assertions.assertEquals(1,result.size()); + Assertions.assertEquals("tppId", result.get(0)); - Assertions.assertEquals(1, result.count().block()); - Assertions.assertEquals(hashedFiscalCode, result.blockFirst().getFiscalCode()); - Mockito.verify(mongoTemplate).aggregate(Mockito.any(), Mockito.eq(CitizenConsent.class), Mockito.eq(CitizenConsent.class)); } @Test diff --git a/src/test/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceTest.java b/src/test/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceTest.java index c2f8ffd..3cf1b30 100644 --- a/src/test/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceTest.java +++ b/src/test/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceTest.java @@ -273,9 +273,9 @@ void getListEnabledConsents_Ok() { Mockito.when(citizenRepository.findByFiscalCodeAndTppStateTrue(HASHED_FISCAL_CODE)) - .thenReturn(Flux.just(CITIZEN_CONSENT)); + .thenReturn(Mono.just(List.of("TPPID"))); - List response = citizenService.getListEnabledConsents(FISCAL_CODE).block(); + List response = citizenService.getTppEnabledList(FISCAL_CODE).block(); assertNotNull(response); assertEquals(1, response.size()); } @@ -283,10 +283,10 @@ void getListEnabledConsents_Ok() { @Test void getListAllConsents_Ok() { Mockito.when(citizenRepository.findByFiscalCode(HASHED_FISCAL_CODE)) - .thenReturn(Flux.just(CITIZEN_CONSENT)); + .thenReturn(Mono.just(CITIZEN_CONSENT)); - List response = citizenService.getListAllConsents(FISCAL_CODE).block(); + CitizenConsentDTO response = citizenService.get(FISCAL_CODE).block(); assertNotNull(response); - assertEquals(1, response.size()); + assertEquals(CITIZEN_CONSENT, response); } } From 6b6567ca7c9ac05d7a5da31b9a52f0cd8ed27dfd Mon Sep 17 00:00:00 2001 From: Vitolo-Andrea Date: Thu, 7 Nov 2024 15:25:24 +0100 Subject: [PATCH 06/20] Repository fix --- .../citizen/repository/CitizenSpecificRepositoryImpl.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImpl.java b/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImpl.java index 2cb2692..6664028 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImpl.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImpl.java @@ -29,7 +29,8 @@ public Mono> findByFiscalCodeAndTppStateTrue(String fiscalCode) { Aggregation aggregation = Aggregation.newAggregation( Aggregation.match(Criteria.where("fiscalCode").is(fiscalCode)), Aggregation.project("consents").asArray("consentsArray"), - Aggregation.match(Criteria.where("consentsArray.v.tppState").is(true)) + Aggregation.match(Criteria.where("consentsArray.v.tppState").is(true)), + Aggregation.project("consentsArray.k").and("key") ); return mongoTemplate.aggregate(aggregation, "citizen_consents", ConsentKeyWrapper.class) From 7981baf44a006f707e1a086c92db2598b2e3ad8d Mon Sep 17 00:00:00 2001 From: Vitolo-Andrea Date: Thu, 7 Nov 2024 15:36:13 +0100 Subject: [PATCH 07/20] Repository fix --- .../citizen/repository/CitizenSpecificRepositoryImpl.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImpl.java b/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImpl.java index 6664028..31ee5dd 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImpl.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImpl.java @@ -30,13 +30,13 @@ public Mono> findByFiscalCodeAndTppStateTrue(String fiscalCode) { Aggregation.match(Criteria.where("fiscalCode").is(fiscalCode)), Aggregation.project("consents").asArray("consentsArray"), Aggregation.match(Criteria.where("consentsArray.v.tppState").is(true)), - Aggregation.project("consentsArray.k").and("key") + Aggregation.project("consentsArray.k") ); return mongoTemplate.aggregate(aggregation, "citizen_consents", ConsentKeyWrapper.class) .collectList() .map(results -> results.stream() - .map(ConsentKeyWrapper::getKey) + .map(ConsentKeyWrapper::getK) .collect(Collectors.toList())); } public Mono findByFiscalCodeAndTppId(String fiscalCode, String tppId) { @@ -54,7 +54,7 @@ public Mono findByFiscalCodeAndTppId(String fiscalCode, String t } @Data public static class ConsentKeyWrapper { - private String key; + private String k; } } From 1aa4cf022ef5347b042d44319e639c0df2a44240 Mon Sep 17 00:00:00 2001 From: Vitolo-Andrea Date: Thu, 7 Nov 2024 15:39:30 +0100 Subject: [PATCH 08/20] Repository fix --- .../citizen/repository/CitizenSpecificRepositoryImplTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImplTest.java b/src/test/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImplTest.java index 7df1a6a..8ffb254 100644 --- a/src/test/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImplTest.java +++ b/src/test/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImplTest.java @@ -35,7 +35,7 @@ public void setUp() { @Test void testFindByFiscalCodeAndTppStateTrue() { CitizenSpecificRepositoryImpl.ConsentKeyWrapper key = new CitizenSpecificRepositoryImpl.ConsentKeyWrapper(); - key.setKey("tppId"); + key.setK("tppId"); String hashedFiscalCode = "hashedCode"; when(mongoTemplate.aggregate(Mockito.any(), anyString(), Mockito.eq(CitizenSpecificRepositoryImpl.ConsentKeyWrapper.class))) From 5d9ce5ac4971579167c55962a7a54d5b590fac99 Mon Sep 17 00:00:00 2001 From: Vitolo-Andrea Date: Thu, 7 Nov 2024 16:19:14 +0100 Subject: [PATCH 09/20] Repository fix --- .../repository/CitizenSpecificRepository.java | 3 +-- .../CitizenSpecificRepositoryImpl.java | 21 ------------------- .../citizen/service/CitizenServiceImpl.java | 10 ++++++--- .../CitizenSpecificRepositoryImplTest.java | 20 ------------------ 4 files changed, 8 insertions(+), 46 deletions(-) diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepository.java b/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepository.java index 8cd9c4a..2fccd8f 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepository.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepository.java @@ -3,9 +3,8 @@ import it.gov.pagopa.onboarding.citizen.model.CitizenConsent; import reactor.core.publisher.Mono; -import java.util.List; public interface CitizenSpecificRepository { - Mono> findByFiscalCodeAndTppStateTrue(String fiscalCode); + Mono findByFiscalCodeAndTppId(String fiscalCode, String tppId); } diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImpl.java b/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImpl.java index 31ee5dd..8d57830 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImpl.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImpl.java @@ -4,18 +4,10 @@ import lombok.Data; import org.springframework.data.mongodb.core.ReactiveMongoTemplate; import org.springframework.data.mongodb.core.aggregation.Aggregation; -import org.springframework.data.mongodb.core.aggregation.AggregationOperation; -import org.springframework.data.mongodb.core.aggregation.AggregationResults; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.stereotype.Repository; -import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - @Repository public class CitizenSpecificRepositoryImpl implements CitizenSpecificRepository { @@ -25,20 +17,7 @@ public CitizenSpecificRepositoryImpl(ReactiveMongoTemplate mongoTemplate) { this.mongoTemplate = mongoTemplate; } - public Mono> findByFiscalCodeAndTppStateTrue(String fiscalCode) { - Aggregation aggregation = Aggregation.newAggregation( - Aggregation.match(Criteria.where("fiscalCode").is(fiscalCode)), - Aggregation.project("consents").asArray("consentsArray"), - Aggregation.match(Criteria.where("consentsArray.v.tppState").is(true)), - Aggregation.project("consentsArray.k") - ); - return mongoTemplate.aggregate(aggregation, "citizen_consents", ConsentKeyWrapper.class) - .collectList() - .map(results -> results.stream() - .map(ConsentKeyWrapper::getK) - .collect(Collectors.toList())); - } public Mono findByFiscalCodeAndTppId(String fiscalCode, String tppId) { if (tppId == null) { return Mono.empty(); diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceImpl.java b/src/main/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceImpl.java index 2a257ca..b5ed458 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceImpl.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceImpl.java @@ -17,8 +17,8 @@ import reactor.core.publisher.Mono; import java.time.LocalDateTime; -import java.util.Collections; import java.util.List; +import java.util.Map; import static it.gov.pagopa.common.utils.Utils.inputSanify; @@ -107,8 +107,12 @@ public Mono getConsentStatus(String fiscalCode, String tppId) public Mono> getTppEnabledList(String fiscalCode) { log.info("[EMD-CITIZEN][FIND-CITIZEN-CONSENTS-ENABLED] Received hashedFiscalCode: {}", Utils.createSHA256(fiscalCode)); - return citizenRepository.findByFiscalCodeAndTppStateTrue(fiscalCode) - .switchIfEmpty(Mono.just(Collections.emptyList())) + return citizenRepository.findByFiscalCode(fiscalCode) + .switchIfEmpty(Mono.empty()) + .map(citizenConsent -> citizenConsent.getConsents().entrySet().stream() + .filter(tpp -> tpp.getValue().getTppState()) + .map(Map.Entry::getKey) + .toList()) .doOnSuccess(tppIdList -> log.info("EMD][CITIZEN][FIND-CITIZEN-CONSENTS-ENABLED] Consents found: {}", (tppIdList.size()))); } diff --git a/src/test/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImplTest.java b/src/test/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImplTest.java index 8ffb254..6d76be9 100644 --- a/src/test/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImplTest.java +++ b/src/test/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImplTest.java @@ -13,11 +13,8 @@ import reactor.core.publisher.Mono; import java.util.HashMap; -import java.util.List; import java.util.Map; -import java.util.Objects; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) @@ -32,23 +29,6 @@ public void setUp() { repository = new CitizenSpecificRepositoryImpl(mongoTemplate); } - @Test - void testFindByFiscalCodeAndTppStateTrue() { - CitizenSpecificRepositoryImpl.ConsentKeyWrapper key = new CitizenSpecificRepositoryImpl.ConsentKeyWrapper(); - key.setK("tppId"); - String hashedFiscalCode = "hashedCode"; - - when(mongoTemplate.aggregate(Mockito.any(), anyString(), Mockito.eq(CitizenSpecificRepositoryImpl.ConsentKeyWrapper.class))) - .thenReturn(Flux.just(key)); - - List result = repository.findByFiscalCodeAndTppStateTrue(hashedFiscalCode).block(); - - Assertions.assertNotNull(result); - Assertions.assertEquals(1,result.size()); - Assertions.assertEquals("tppId", result.get(0)); - - } - @Test void testFindByFiscalCodeAndTppId() { String hashedFiscalCode = "hashedCode"; From bd8c12bbddf46e5d6eb7197a14bc5a0e4b3f8a29 Mon Sep 17 00:00:00 2001 From: Vitolo-Andrea Date: Thu, 7 Nov 2024 16:26:05 +0100 Subject: [PATCH 10/20] Repository fix --- .../onboarding/citizen/service/CitizenServiceTest.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/test/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceTest.java b/src/test/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceTest.java index 3cf1b30..82d0eec 100644 --- a/src/test/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceTest.java +++ b/src/test/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceTest.java @@ -268,17 +268,7 @@ void getConsentStatus_Ko_CitizenNotOnboarded() { assertEquals("Citizen consent not founded during get process ", exception.getMessage()); } - @Test - void getListEnabledConsents_Ok() { - - - Mockito.when(citizenRepository.findByFiscalCodeAndTppStateTrue(HASHED_FISCAL_CODE)) - .thenReturn(Mono.just(List.of("TPPID"))); - List response = citizenService.getTppEnabledList(FISCAL_CODE).block(); - assertNotNull(response); - assertEquals(1, response.size()); - } @Test void getListAllConsents_Ok() { From 8e273de0140b9ddc7ce8ece4ef4ee01c1b7e99c4 Mon Sep 17 00:00:00 2001 From: Vitolo-Andrea Date: Fri, 8 Nov 2024 09:50:46 +0100 Subject: [PATCH 11/20] Bloom filter added --- .../citizen/controller/CitizenController.java | 4 ++ .../controller/CitizenControllerImpl.java | 19 +++++++- .../citizen/repository/CitizenRepository.java | 5 ++ .../citizen/service/BloomFilterService.java | 6 +++ .../service/BloomFilterServiceImpl.java | 48 +++++++++++++++++++ .../citizen/service/CitizenService.java | 2 +- .../citizen/service/CitizenServiceImpl.java | 2 +- .../controller/CitizenControllerTest.java | 39 ++++++++++++++- .../service/BloomFilterServiceTest.java | 47 ++++++++++++++++++ .../citizen/service/CitizenServiceTest.java | 13 ++--- 10 files changed, 172 insertions(+), 13 deletions(-) create mode 100644 src/main/java/it/gov/pagopa/onboarding/citizen/service/BloomFilterService.java create mode 100644 src/main/java/it/gov/pagopa/onboarding/citizen/service/BloomFilterServiceImpl.java create mode 100644 src/test/java/it/gov/pagopa/onboarding/citizen/service/BloomFilterServiceTest.java diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/controller/CitizenController.java b/src/main/java/it/gov/pagopa/onboarding/citizen/controller/CitizenController.java index c64a004..34c7d80 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/controller/CitizenController.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/controller/CitizenController.java @@ -57,4 +57,8 @@ public interface CitizenController { @GetMapping("/list/{fiscalCode}") Mono> get(@PathVariable @Pattern(regexp = FISCAL_CODE_STRUCTURE_REGEX, message = "Invalid fiscal code format") String fiscalCode); + @GetMapping("/filter/{fiscalCode}") + Mono> getAllFiscalCode(@PathVariable @Pattern(regexp = FISCAL_CODE_STRUCTURE_REGEX, message = "Invalid fiscal code format") String fiscalCode); + + } diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/controller/CitizenControllerImpl.java b/src/main/java/it/gov/pagopa/onboarding/citizen/controller/CitizenControllerImpl.java index 26b3cb5..18c3d33 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/controller/CitizenControllerImpl.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/controller/CitizenControllerImpl.java @@ -2,8 +2,10 @@ import it.gov.pagopa.onboarding.citizen.dto.CitizenConsentDTO; import it.gov.pagopa.onboarding.citizen.dto.CitizenConsentStateUpdateDTO; +import it.gov.pagopa.onboarding.citizen.service.BloomFilterServiceImpl; import it.gov.pagopa.onboarding.citizen.service.CitizenServiceImpl; import jakarta.validation.Valid; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Mono; @@ -13,9 +15,12 @@ @RestController public class CitizenControllerImpl implements CitizenController { + private final BloomFilterServiceImpl bloomFilterService; + private final CitizenServiceImpl citizenService; - public CitizenControllerImpl(CitizenServiceImpl citizenService) { + public CitizenControllerImpl(BloomFilterServiceImpl bloomFilterService, CitizenServiceImpl citizenService) { + this.bloomFilterService = bloomFilterService; this.citizenService = citizenService; } @@ -27,7 +32,7 @@ public Mono> saveCitizenConsent(@Valid Citizen @Override public Mono> stateUpdate(@Valid CitizenConsentStateUpdateDTO citizenConsentStateUpdateDTO) { - return citizenService.updateChannelState( + return citizenService.updateTppState( citizenConsentStateUpdateDTO.getFiscalCode(), citizenConsentStateUpdateDTO.getTppId(), citizenConsentStateUpdateDTO.getTppState()) @@ -52,4 +57,14 @@ public Mono> get(String fiscalCode) { .map(ResponseEntity::ok); } + @Override + public Mono> getAllFiscalCode(String fiscalCode) { + return Mono.fromCallable(() -> + bloomFilterService.mightContain(fiscalCode) ? + ResponseEntity.ok("OK") : + ResponseEntity.status(HttpStatus.ACCEPTED).body("NO CHANNELS ENABLED") + ); + + } + } diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenRepository.java b/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenRepository.java index 9263ea7..a58e3ec 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenRepository.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenRepository.java @@ -1,11 +1,16 @@ package it.gov.pagopa.onboarding.citizen.repository; import it.gov.pagopa.onboarding.citizen.model.CitizenConsent; +import org.springframework.data.mongodb.repository.Query; import org.springframework.data.mongodb.repository.ReactiveMongoRepository; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; public interface CitizenRepository extends ReactiveMongoRepository, CitizenSpecificRepository { Mono findByFiscalCode(String fiscalCode); + @Query(value = "{}", fields = "{ 'fiscalCode' : 1 }") + Flux findAllFiscalCodes(); + } diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/service/BloomFilterService.java b/src/main/java/it/gov/pagopa/onboarding/citizen/service/BloomFilterService.java new file mode 100644 index 0000000..bce36f0 --- /dev/null +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/service/BloomFilterService.java @@ -0,0 +1,6 @@ +package it.gov.pagopa.onboarding.citizen.service; + +public interface BloomFilterService { + + boolean mightContain(String hashedFiscalCode); +} diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/service/BloomFilterServiceImpl.java b/src/main/java/it/gov/pagopa/onboarding/citizen/service/BloomFilterServiceImpl.java new file mode 100644 index 0000000..3d7c905 --- /dev/null +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/service/BloomFilterServiceImpl.java @@ -0,0 +1,48 @@ +package it.gov.pagopa.onboarding.citizen.service; + + +import com.azure.cosmos.implementation.guava25.hash.BloomFilter; +import com.azure.cosmos.implementation.guava25.hash.Funnels; +import it.gov.pagopa.onboarding.citizen.repository.CitizenRepository; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import java.nio.charset.StandardCharsets; + +@Service +@Slf4j +public class BloomFilterServiceImpl implements BloomFilterService{ + + private final CitizenRepository citizenRepository; + + private BloomFilter bloomFilter; + + public BloomFilterServiceImpl(CitizenRepository citizenRepository) { + this.citizenRepository = citizenRepository; + } + + + @PostConstruct + public void initializeBloomFilter() { + bloomFilter = BloomFilter.create(Funnels.stringFunnel(StandardCharsets.UTF_8), 1000000, 0.01); + + citizenRepository.findAllFiscalCodes() + .doOnNext(bloomFilter::put) + .doOnComplete(() -> log.info("Bloom filter initialized")) + .subscribe(); + } + @Override + + public boolean mightContain(String hashedFiscalCode) { + return bloomFilter.mightContain(hashedFiscalCode); + } + + + @Scheduled(fixedRate = 3600000) + public void update() { + this.initializeBloomFilter(); + } + +} diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/service/CitizenService.java b/src/main/java/it/gov/pagopa/onboarding/citizen/service/CitizenService.java index 5fc865c..f33a6a6 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/service/CitizenService.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/service/CitizenService.java @@ -8,7 +8,7 @@ public interface CitizenService { Mono createCitizenConsent(CitizenConsentDTO citizenConsent); - Mono updateChannelState(String fiscalCode, String tppId, boolean tppState); + Mono updateTppState(String fiscalCode, String tppId, boolean tppState); Mono getConsentStatus(String fiscalCode, String tppId); Mono> getTppEnabledList(String fiscalCode); Mono get(String fiscalCode); diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceImpl.java b/src/main/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceImpl.java index b5ed458..6c07b08 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceImpl.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceImpl.java @@ -64,7 +64,7 @@ public Mono createCitizenConsent(CitizenConsentDTO citizenCon @Override - public Mono updateChannelState(String fiscalCode, String tppId, boolean tppState) { + public Mono updateTppState(String fiscalCode, String tppId, boolean tppState) { log.info("[EMD][CITIZEN][UPDATE-CHANNEL-STATE] Received hashedFiscalCode: {} and tppId: {} with state: {}", Utils.createSHA256(fiscalCode), inputSanify(tppId), tppState); diff --git a/src/test/java/it/gov/pagopa/onboarding/citizen/controller/CitizenControllerTest.java b/src/test/java/it/gov/pagopa/onboarding/citizen/controller/CitizenControllerTest.java index 93583e0..dfb685a 100644 --- a/src/test/java/it/gov/pagopa/onboarding/citizen/controller/CitizenControllerTest.java +++ b/src/test/java/it/gov/pagopa/onboarding/citizen/controller/CitizenControllerTest.java @@ -4,6 +4,7 @@ import it.gov.pagopa.onboarding.citizen.dto.CitizenConsentStateUpdateDTO; import it.gov.pagopa.onboarding.citizen.faker.CitizenConsentDTOFaker; import it.gov.pagopa.onboarding.citizen.faker.CitizenConsentStateUpdateDTOFaker; +import it.gov.pagopa.onboarding.citizen.service.BloomFilterServiceImpl; import it.gov.pagopa.onboarding.citizen.service.CitizenServiceImpl; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -24,6 +25,9 @@ class CitizenControllerTest { @MockBean private CitizenServiceImpl citizenService; + @MockBean + private BloomFilterServiceImpl bloomFilterService; + @Autowired private WebTestClient webClient; @@ -59,7 +63,7 @@ void stateUpdate_Ok() { CitizenConsentDTO expectedResponseDTO = CitizenConsentDTOFaker.mockInstance(true); - Mockito.when(citizenService.updateChannelState( + Mockito.when(citizenService.updateTppState( citizenConsentStateUpdateDTO.getFiscalCode(), citizenConsentStateUpdateDTO.getTppId(), citizenConsentStateUpdateDTO.getTppState())) @@ -137,4 +141,37 @@ void getCitizenConsents_Ok() { Assertions.assertEquals(citizenConsentDTO, resultResponse); }); } + + @Test + void getAllFiscalCode_Ok() { + Mockito.when(bloomFilterService.mightContain(FISCAL_CODE)) + .thenReturn(true); + + webClient.get() + .uri("/emd/citizen/filter/{fiscalCode}", FISCAL_CODE) + .exchange() + .expectStatus().isOk() + .expectBody(String.class) + .consumeWith(response -> { + String resultResponse = response.getResponseBody(); + Assertions.assertNotNull(resultResponse); + Assertions.assertEquals("OK", resultResponse); + }); + } + @Test + void getAllFiscalCode_NoChannelsEnabled() { + Mockito.when(bloomFilterService.mightContain(FISCAL_CODE)) + .thenReturn(false); + + webClient.get() + .uri("/emd/citizen/filter/{fiscalCode}", FISCAL_CODE) + .exchange() + .expectStatus().isAccepted() + .expectBody(String.class) + .consumeWith(response -> { + String resultResponse = response.getResponseBody(); + Assertions.assertNotNull(resultResponse); + Assertions.assertEquals("NO CHANNELS ENABLED", resultResponse); + }); + } } diff --git a/src/test/java/it/gov/pagopa/onboarding/citizen/service/BloomFilterServiceTest.java b/src/test/java/it/gov/pagopa/onboarding/citizen/service/BloomFilterServiceTest.java new file mode 100644 index 0000000..1bd3533 --- /dev/null +++ b/src/test/java/it/gov/pagopa/onboarding/citizen/service/BloomFilterServiceTest.java @@ -0,0 +1,47 @@ +package it.gov.pagopa.onboarding.citizen.service; + +import it.gov.pagopa.onboarding.citizen.repository.CitizenRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import reactor.core.publisher.Flux; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class BloomFilterServiceTest { + + @Mock + private CitizenRepository citizenRepository; + + @InjectMocks + private BloomFilterServiceImpl bloomFilterService; + + @BeforeEach + void setUp() { + + when(citizenRepository.findAllFiscalCodes()).thenReturn(Flux.just("fiscalCode1", "fiscalCode2")); + bloomFilterService.initializeBloomFilter(); + } + + @Test + void testMightContain() { + assertTrue(bloomFilterService.mightContain("fiscalCode1")); + assertTrue(bloomFilterService.mightContain("fiscalCode2")); + assertFalse(bloomFilterService.mightContain("nonExistentFiscalCode")); + } + + @Test + void testUpdate() { + when(citizenRepository.findAllFiscalCodes()).thenReturn(Flux.just("fiscalCode1")); + bloomFilterService.update(); + assertTrue(bloomFilterService.mightContain("fiscalCode1")); + assertFalse(bloomFilterService.mightContain("fiscalCode2")); + assertFalse(bloomFilterService.mightContain("nonexistentHashedFiscalCode")); + } +} diff --git a/src/test/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceTest.java b/src/test/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceTest.java index 82d0eec..c9f0377 100644 --- a/src/test/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceTest.java +++ b/src/test/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceTest.java @@ -24,11 +24,8 @@ import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; -import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import java.util.List; - import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; @@ -171,7 +168,7 @@ void updateChannelState_Ok() { Mockito.when(citizenRepository.save(Mockito.any())) .thenReturn(Mono.just(CITIZEN_CONSENT)); - CitizenConsentDTO response = citizenService.updateChannelState(FISCAL_CODE, TPP_ID, TPP_STATE).block(); + CitizenConsentDTO response = citizenService.updateTppState(FISCAL_CODE, TPP_ID, TPP_STATE).block(); assertNotNull(response); @@ -190,7 +187,7 @@ void updateChannelState_Ko_CitizenNotOnboarded() { Mockito.when(citizenRepository.findByFiscalCodeAndTppId(HASHED_FISCAL_CODE, TPP_ID)) .thenReturn(Mono.empty()); - Executable executable = () -> citizenService.updateChannelState(FISCAL_CODE, TPP_ID, true).block(); + Executable executable = () -> citizenService.updateTppState(FISCAL_CODE, TPP_ID, true).block(); ClientExceptionWithBody exception = assertThrows(ClientExceptionWithBody.class, executable); assertEquals("Citizen consent not founded during update state process", exception.getMessage()); @@ -214,7 +211,7 @@ void updateChannelState_Ok_ConsentDetailsIsNull() { Mockito.when(citizenRepository.save(Mockito.any())) .thenReturn(Mono.just(citizenConsentWithConsentDetailNull)); - Executable executable = () -> citizenService.updateChannelState(FISCAL_CODE, TPP_ID, TPP_STATE).block(); + Executable executable = () -> citizenService.updateTppState(FISCAL_CODE, TPP_ID, TPP_STATE).block(); ClientExceptionWithBody exception = assertThrows(ClientExceptionWithBody.class, executable); assertEquals("ConsentDetails is null for this tppId", exception.getMessage()); @@ -225,7 +222,7 @@ void updateChannelState_Ko_TppResponseIsNull() { Mockito.when(tppConnector.get(anyString())).thenReturn(Mono.empty()); - Executable executable = () -> citizenService.updateChannelState(FISCAL_CODE, TPP_ID, TPP_STATE).block(); + Executable executable = () -> citizenService.updateTppState(FISCAL_CODE, TPP_ID, TPP_STATE).block(); ClientExceptionWithBody exception = assertThrows(ClientExceptionWithBody.class, executable); assertEquals("TPP does not exist or is not active", exception.getMessage()); @@ -239,7 +236,7 @@ void updateChannelState_Ko_TppInactive() { Mockito.when(tppConnector.get(anyString())).thenReturn(Mono.just(mockTppDTO)); - Executable executable = () -> citizenService.updateChannelState(FISCAL_CODE, TPP_ID, TPP_STATE).block(); + Executable executable = () -> citizenService.updateTppState(FISCAL_CODE, TPP_ID, TPP_STATE).block(); ClientExceptionWithBody exception = assertThrows(ClientExceptionWithBody.class, executable); assertEquals("TPP does not exist or is not active", exception.getMessage()); From 72c041457b4377327b4537225836ce559f065b3d Mon Sep 17 00:00:00 2001 From: Vitolo-Andrea Date: Fri, 8 Nov 2024 10:01:46 +0100 Subject: [PATCH 12/20] Bloom filter fix --- .../citizen/service/BloomFilterServiceImpl.java | 3 +++ .../CitizenConsentValidationServiceImpl.java | 13 +++++++++++-- .../citizen/service/BloomFilterServiceTest.java | 6 ++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/service/BloomFilterServiceImpl.java b/src/main/java/it/gov/pagopa/onboarding/citizen/service/BloomFilterServiceImpl.java index 3d7c905..1d0b488 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/service/BloomFilterServiceImpl.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/service/BloomFilterServiceImpl.java @@ -45,4 +45,7 @@ public void update() { this.initializeBloomFilter(); } + public void add(String fiscalCode){ + bloomFilter.put(fiscalCode); + } } diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/validation/CitizenConsentValidationServiceImpl.java b/src/main/java/it/gov/pagopa/onboarding/citizen/validation/CitizenConsentValidationServiceImpl.java index 0804fcc..8d04bf2 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/validation/CitizenConsentValidationServiceImpl.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/validation/CitizenConsentValidationServiceImpl.java @@ -1,5 +1,6 @@ package it.gov.pagopa.onboarding.citizen.validation; +import it.gov.pagopa.common.utils.Utils; import it.gov.pagopa.onboarding.citizen.configuration.ExceptionMap; import it.gov.pagopa.onboarding.citizen.connector.tpp.TppConnectorImpl; import it.gov.pagopa.onboarding.citizen.constants.CitizenConstants.ExceptionName; @@ -8,6 +9,7 @@ import it.gov.pagopa.onboarding.citizen.dto.mapper.CitizenConsentObjectToDTOMapper; import it.gov.pagopa.onboarding.citizen.model.CitizenConsent; import it.gov.pagopa.onboarding.citizen.repository.CitizenRepository; +import it.gov.pagopa.onboarding.citizen.service.BloomFilterServiceImpl; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import reactor.core.publisher.Mono; @@ -17,13 +19,17 @@ public class CitizenConsentValidationServiceImpl implements CitizenConsentValidationService { private final CitizenRepository citizenRepository; + + private final BloomFilterServiceImpl bloomFilterService; + private final TppConnectorImpl tppConnector; private final CitizenConsentObjectToDTOMapper mapperToDTO; private final ExceptionMap exceptionMap; - public CitizenConsentValidationServiceImpl(CitizenRepository citizenRepository, TppConnectorImpl tppConnector, + public CitizenConsentValidationServiceImpl(CitizenRepository citizenRepository, BloomFilterServiceImpl bloomFilterService, TppConnectorImpl tppConnector, CitizenConsentObjectToDTOMapper mapperToDTO, ExceptionMap exceptionMap) { this.citizenRepository = citizenRepository; + this.bloomFilterService = bloomFilterService; this.tppConnector = tppConnector; this.mapperToDTO = mapperToDTO; this.exceptionMap = exceptionMap; @@ -44,7 +50,10 @@ public Mono validateTppAndSaveConsent(String fiscalCode, Stri .flatMap(tppResponse -> { if (isTppValid(tppResponse)) { return citizenRepository.save(citizenConsent) - .doOnSuccess(savedConsent -> log.info("[EMD][CREATE-CITIZEN-CONSENT] Created new citizen consent for fiscal code: {}", fiscalCode)) + .doOnSuccess(savedConsent -> { + log.info("[EMD][CREATE-CITIZEN-CONSENT] Created new citizen consent for fiscal code: {}", Utils.createSHA256(fiscalCode)); + bloomFilterService.add(fiscalCode); + }) .flatMap(savedConsent -> Mono.just(mapperToDTO.map(savedConsent))); } else { return Mono.error(exceptionMap.throwException(ExceptionName.TPP_NOT_FOUND, "TPP does not exist or is not active")); diff --git a/src/test/java/it/gov/pagopa/onboarding/citizen/service/BloomFilterServiceTest.java b/src/test/java/it/gov/pagopa/onboarding/citizen/service/BloomFilterServiceTest.java index 1bd3533..7546655 100644 --- a/src/test/java/it/gov/pagopa/onboarding/citizen/service/BloomFilterServiceTest.java +++ b/src/test/java/it/gov/pagopa/onboarding/citizen/service/BloomFilterServiceTest.java @@ -44,4 +44,10 @@ void testUpdate() { assertFalse(bloomFilterService.mightContain("fiscalCode2")); assertFalse(bloomFilterService.mightContain("nonexistentHashedFiscalCode")); } + + @Test + void testAdd() { + bloomFilterService.add("fiscalCode3"); + assertTrue(bloomFilterService.mightContain("fiscalCode3")); + } } From 30ffed52c8e36dcef54e8d03c18b0a525a4bd69b Mon Sep 17 00:00:00 2001 From: Vitolo-Andrea Date: Mon, 11 Nov 2024 14:26:27 +0100 Subject: [PATCH 13/20] Bloom filter fix --- .../citizen/service/BloomFilterServiceImpl.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/service/BloomFilterServiceImpl.java b/src/main/java/it/gov/pagopa/onboarding/citizen/service/BloomFilterServiceImpl.java index 1d0b488..29bbfe0 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/service/BloomFilterServiceImpl.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/service/BloomFilterServiceImpl.java @@ -29,14 +29,18 @@ public void initializeBloomFilter() { bloomFilter = BloomFilter.create(Funnels.stringFunnel(StandardCharsets.UTF_8), 1000000, 0.01); citizenRepository.findAllFiscalCodes() - .doOnNext(bloomFilter::put) + .doOnNext(string -> { + log.info("{} added to filter",string); + bloomFilter.put(string); + }) .doOnComplete(() -> log.info("Bloom filter initialized")) .subscribe(); + } @Override - public boolean mightContain(String hashedFiscalCode) { - return bloomFilter.mightContain(hashedFiscalCode); + public boolean mightContain(String fiscalCode) { + return bloomFilter.mightContain(fiscalCode); } From 55e4ef0597fc207ede734286e5cde2fafbcf04ee Mon Sep 17 00:00:00 2001 From: Vitolo-Andrea Date: Mon, 11 Nov 2024 14:32:56 +0100 Subject: [PATCH 14/20] Bloom filter fix --- .../pagopa/onboarding/citizen/repository/CitizenRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenRepository.java b/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenRepository.java index a58e3ec..36b4577 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenRepository.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenRepository.java @@ -10,7 +10,7 @@ public interface CitizenRepository extends ReactiveMongoRepository findByFiscalCode(String fiscalCode); - @Query(value = "{}", fields = "{ 'fiscalCode' : 1 }") + @Query(value = "{}", fields = "{ 'fiscalCode' : 1, '_id' : 0 }") Flux findAllFiscalCodes(); } From 975684cebcf8463ace5e11d467536806e84528f1 Mon Sep 17 00:00:00 2001 From: Vitolo-Andrea Date: Mon, 11 Nov 2024 14:51:47 +0100 Subject: [PATCH 15/20] Bloom filter fix --- .../onboarding/citizen/repository/CitizenRepository.java | 4 ---- .../citizen/service/BloomFilterServiceImpl.java | 9 +++------ 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenRepository.java b/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenRepository.java index 36b4577..92f80a4 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenRepository.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenRepository.java @@ -1,16 +1,12 @@ package it.gov.pagopa.onboarding.citizen.repository; import it.gov.pagopa.onboarding.citizen.model.CitizenConsent; -import org.springframework.data.mongodb.repository.Query; import org.springframework.data.mongodb.repository.ReactiveMongoRepository; -import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; public interface CitizenRepository extends ReactiveMongoRepository, CitizenSpecificRepository { Mono findByFiscalCode(String fiscalCode); - @Query(value = "{}", fields = "{ 'fiscalCode' : 1, '_id' : 0 }") - Flux findAllFiscalCodes(); } diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/service/BloomFilterServiceImpl.java b/src/main/java/it/gov/pagopa/onboarding/citizen/service/BloomFilterServiceImpl.java index 29bbfe0..efef5f1 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/service/BloomFilterServiceImpl.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/service/BloomFilterServiceImpl.java @@ -28,14 +28,11 @@ public BloomFilterServiceImpl(CitizenRepository citizenRepository) { public void initializeBloomFilter() { bloomFilter = BloomFilter.create(Funnels.stringFunnel(StandardCharsets.UTF_8), 1000000, 0.01); - citizenRepository.findAllFiscalCodes() - .doOnNext(string -> { - log.info("{} added to filter",string); - bloomFilter.put(string); - }) + citizenRepository.findAll() + .doOnNext(citizenConsent -> + bloomFilter.put(citizenConsent.getFiscalCode())) .doOnComplete(() -> log.info("Bloom filter initialized")) .subscribe(); - } @Override From 2ba67b86227e9f2be3aa417f9f99e4ae7ad5eabf Mon Sep 17 00:00:00 2001 From: Vitolo-Andrea Date: Mon, 11 Nov 2024 15:08:25 +0100 Subject: [PATCH 16/20] Bloom filter fix --- .../citizen/service/BloomFilterServiceTest.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/test/java/it/gov/pagopa/onboarding/citizen/service/BloomFilterServiceTest.java b/src/test/java/it/gov/pagopa/onboarding/citizen/service/BloomFilterServiceTest.java index 7546655..9516cb4 100644 --- a/src/test/java/it/gov/pagopa/onboarding/citizen/service/BloomFilterServiceTest.java +++ b/src/test/java/it/gov/pagopa/onboarding/citizen/service/BloomFilterServiceTest.java @@ -1,5 +1,6 @@ package it.gov.pagopa.onboarding.citizen.service; +import it.gov.pagopa.onboarding.citizen.faker.CitizenConsentFaker; import it.gov.pagopa.onboarding.citizen.repository.CitizenRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -25,23 +26,21 @@ class BloomFilterServiceTest { @BeforeEach void setUp() { - when(citizenRepository.findAllFiscalCodes()).thenReturn(Flux.just("fiscalCode1", "fiscalCode2")); + when(citizenRepository.findAll()).thenReturn(Flux.just(CitizenConsentFaker.mockInstance(true))); bloomFilterService.initializeBloomFilter(); } @Test void testMightContain() { - assertTrue(bloomFilterService.mightContain("fiscalCode1")); - assertTrue(bloomFilterService.mightContain("fiscalCode2")); + assertTrue(bloomFilterService.mightContain("fiscalCode")); assertFalse(bloomFilterService.mightContain("nonExistentFiscalCode")); } @Test void testUpdate() { - when(citizenRepository.findAllFiscalCodes()).thenReturn(Flux.just("fiscalCode1")); + when(citizenRepository.findAll()).thenReturn(Flux.just(CitizenConsentFaker.mockInstance(true))); bloomFilterService.update(); - assertTrue(bloomFilterService.mightContain("fiscalCode1")); - assertFalse(bloomFilterService.mightContain("fiscalCode2")); + assertTrue(bloomFilterService.mightContain("fiscalCode")); assertFalse(bloomFilterService.mightContain("nonexistentHashedFiscalCode")); } From 7621c95fc75f30edb585cb1edcfe56dd2d13b7bb Mon Sep 17 00:00:00 2001 From: stefanodel Date: Tue, 12 Nov 2024 14:52:15 +0100 Subject: [PATCH 17/20] feat: add spectral and swagger --- .spectral.yaml | 19 + src/main/resources/META-INF/openapi.yaml | 1306 ++++++++++++++++++++++ 2 files changed, 1325 insertions(+) create mode 100644 .spectral.yaml create mode 100644 src/main/resources/META-INF/openapi.yaml diff --git a/.spectral.yaml b/.spectral.yaml new file mode 100644 index 0000000..b950826 --- /dev/null +++ b/.spectral.yaml @@ -0,0 +1,19 @@ +extends: + - "spectral:oas" + - "spectral:asyncapi" + - "https://unpkg.com/@stoplight/spectral-owasp-ruleset/dist/ruleset.mjs" +overrides: + - files: + - "src/main/resources/META-INF/openapi.yaml#/paths/~1token/post/security" + rules: + owasp:api2:2023-write-restricted: "off" + - files: + - "src/main/resources/META-INF/openapi.yaml#/paths/~1.well-known~1jwks.json/get/security" + - "src/main/resources/META-INF/openapi.yaml#/paths/~1.well-known~1openid-configuration/get/security" + rules: + owasp:api2:2023-read-restricted: "off" + - files: + - "src/main/resources/META-INF/openapi.yaml" + rules: + owasp:api3:2023-no-additionalProperties: "off" + owasp:api3:2023-constrained-additionalProperties: "off" \ No newline at end of file diff --git a/src/main/resources/META-INF/openapi.yaml b/src/main/resources/META-INF/openapi.yaml new file mode 100644 index 0000000..06c9136 --- /dev/null +++ b/src/main/resources/META-INF/openapi.yaml @@ -0,0 +1,1306 @@ +openapi: 3.0.3 +info: + title: EMD CITIZEN CONSENT API + version: '1.0' + description: |- + EMD CITIZEN CONSENT + contact: + name: Stefano D'Elia + email: stefano11.delia@emeal.nttdata.com + +servers: + - description: Development Test + url: https://api-io.dev.cstar.pagopa.it/emd/citizen + x-internal: true + - description: User Acceptance Test + url: https://api-io.uat.cstar.pagopa.it/emd/citizen + x-internal: true + +security: + - bearerAuth: [ ] + +tags: + - name: Citizen consent + description: 'Citizen consent operation' +paths: + '/': + post: + tags: + - Citizen consent + summary: >- + ENG: Save citizen consent information - IT: Salvataggio dei consensi del cittadino + operationId: saveCitizenConsent + description: Save citizen consents + parameters: + - name: Accept-Language + in: header + description: 'ENG: Language - IT: Lingua' + schema: + type: string + pattern: "^[ -~]{2,5}$" + minLength: 2 + maxLength: 5 + example: it-IT + default: it-IT + required: true + requestBody: + description: 'ENG: Citizen consent details - IT: Dettagli consensi del cittadino' + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CitizenConsentRequestDTO' + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/CitizenConsentResponseDTO' + examples: + example1: + $ref: '#/components/examples/example1' + headers: + Access-Control-Allow-Origin: + description: Indicates whether the response can be shared with requesting code from the given origin + required: false + schema: + $ref: '#/components/schemas/AccessControlAllowOrigin' + RateLimit-Limit: + description: The number of allowed requests in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitLimit' + RateLimit-Reset: + description: The number of seconds left in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitReset' + Retry-After: + description: The number of seconds to wait before allowing a follow-up request + required: false + schema: + $ref: '#/components/schemas/RetryAfter' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/CitizenConsentErrorDTO' + example: + code: CITIZEN_CONSENT_BAD_REQUEST + message: Something went wrong handling the request + headers: + Access-Control-Allow-Origin: + description: Indicates whether the response can be shared with requesting code from the given origin + required: false + schema: + $ref: '#/components/schemas/AccessControlAllowOrigin' + RateLimit-Limit: + description: The number of allowed requests in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitLimit' + RateLimit-Reset: + description: The number of seconds left in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitReset' + Retry-After: + description: The number of seconds to wait before allowing a follow-up request + required: false + schema: + $ref: '#/components/schemas/RetryAfter' + '401': + description: Authentication failed + content: + application/json: + schema: + $ref: '#/components/schemas/CitizenConsentErrorDTO' + example: + code: CITIZEN_CONSENT_AUTHENTICATION_FAILED + message: Something went wrong with authentication + headers: + Access-Control-Allow-Origin: + description: Indicates whether the response can be shared with requesting code from the given origin + required: false + schema: + $ref: '#/components/schemas/AccessControlAllowOrigin' + RateLimit-Limit: + description: The number of allowed requests in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitLimit' + RateLimit-Reset: + description: The number of seconds left in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitReset' + Retry-After: + description: The number of seconds to wait before allowing a follow-up request + required: false + schema: + $ref: '#/components/schemas/RetryAfter' + '404': + description: The citizen consent was not found + content: + application/json: + schema: + $ref: '#/components/schemas/CitizenConsentErrorDTO' + example: + code: CITIZEN_CONSENT_NOT_ONBOARDED + message: Citizen consent not inserted + headers: + Access-Control-Allow-Origin: + description: Indicates whether the response can be shared with requesting code from the given origin + required: false + schema: + $ref: '#/components/schemas/AccessControlAllowOrigin' + RateLimit-Limit: + description: The number of allowed requests in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitLimit' + RateLimit-Reset: + description: The number of seconds left in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitReset' + Retry-After: + description: The number of seconds to wait before allowing a follow-up request + required: false + schema: + $ref: '#/components/schemas/RetryAfter' + '429': + description: Too many Request + content: + application/json: + schema: + $ref: '#/components/schemas/CitizenConsentErrorDTO' + example: + code: CITIZEN_CONSENT_TOO_MANY_REQUESTS + message: Too many requests + headers: + Access-Control-Allow-Origin: + description: Indicates whether the response can be shared with requesting code from the given origin + required: false + schema: + $ref: '#/components/schemas/AccessControlAllowOrigin' + RateLimit-Limit: + description: The number of allowed requests in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitLimit' + RateLimit-Reset: + description: The number of seconds left in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitReset' + Retry-After: + description: The number of seconds to wait before allowing a follow-up request + required: false + schema: + $ref: '#/components/schemas/RetryAfter' + '500': + description: Server ERROR + content: + application/json: + schema: + $ref: '#/components/schemas/CitizenConsentErrorDTO' + example: + code: CITIZEN_CONSENT_GENERIC_ERROR + message: Application error + headers: + Access-Control-Allow-Origin: + description: Indicates whether the response can be shared with requesting code from the given origin + required: false + schema: + $ref: '#/components/schemas/AccessControlAllowOrigin' + RateLimit-Limit: + description: The number of allowed requests in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitLimit' + RateLimit-Reset: + description: The number of seconds left in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitReset' + Retry-After: + description: The number of seconds to wait before allowing a follow-up request + required: false + schema: + $ref: '#/components/schemas/RetryAfter' + '/stateUpdate': + put: + tags: + - Citizen consent + summary: >- + ENG: Update citizen consent state - IT: Aggiornamento dello stato dei consensi del cittadino + operationId: updateState + description: Update citizen consents + parameters: + - name: Accept-Language + in: header + description: 'ENG: Language - IT: Lingua' + schema: + type: string + pattern: "^[ -~]{2,5}$" + minLength: 2 + maxLength: 5 + example: it-IT + default: it-IT + required: true + requestBody: + description: 'ENG: Citizen consent details - IT: Dettagli consensi del cittadino' + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CitizenConsentStateUpdateDTO' + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/CitizenConsentResponseDTO' + examples: + example2: + $ref: '#/components/examples/example2' + headers: + Access-Control-Allow-Origin: + description: Indicates whether the response can be shared with requesting code from the given origin + required: false + schema: + $ref: '#/components/schemas/AccessControlAllowOrigin' + RateLimit-Limit: + description: The number of allowed requests in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitLimit' + RateLimit-Reset: + description: The number of seconds left in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitReset' + Retry-After: + description: The number of seconds to wait before allowing a follow-up request + required: false + schema: + $ref: '#/components/schemas/RetryAfter' + + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/CitizenConsentErrorDTO' + example: + code: CITIZEN_CONSENT_BAD_REQUEST + message: Something went wrong handling the request + headers: + Access-Control-Allow-Origin: + description: Indicates whether the response can be shared with requesting code from the given origin + required: false + schema: + $ref: '#/components/schemas/AccessControlAllowOrigin' + RateLimit-Limit: + description: The number of allowed requests in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitLimit' + RateLimit-Reset: + description: The number of seconds left in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitReset' + Retry-After: + description: The number of seconds to wait before allowing a follow-up request + required: false + schema: + $ref: '#/components/schemas/RetryAfter' + '401': + description: Authentication failed + content: + application/json: + schema: + $ref: '#/components/schemas/CitizenConsentErrorDTO' + example: + code: CITIZEN_CONSENT_AUTHENTICATION_FAILED + message: Something went wrong with authentication + headers: + Access-Control-Allow-Origin: + description: Indicates whether the response can be shared with requesting code from the given origin + required: false + schema: + $ref: '#/components/schemas/AccessControlAllowOrigin' + RateLimit-Limit: + description: The number of allowed requests in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitLimit' + RateLimit-Reset: + description: The number of seconds left in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitReset' + Retry-After: + description: The number of seconds to wait before allowing a follow-up request + required: false + schema: + $ref: '#/components/schemas/RetryAfter' + '404': + description: The citizen consent was not found + content: + application/json: + schema: + $ref: '#/components/schemas/CitizenConsentErrorDTO' + example: + code: CITIZEN_CONSENT_NOT_ONBOARDED + message: Citizen consent not inserted + headers: + Access-Control-Allow-Origin: + description: Indicates whether the response can be shared with requesting code from the given origin + required: false + schema: + $ref: '#/components/schemas/AccessControlAllowOrigin' + RateLimit-Limit: + description: The number of allowed requests in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitLimit' + RateLimit-Reset: + description: The number of seconds left in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitReset' + Retry-After: + description: The number of seconds to wait before allowing a follow-up request + required: false + schema: + $ref: '#/components/schemas/RetryAfter' + '429': + description: Too many Request + content: + application/json: + schema: + $ref: '#/components/schemas/CitizenConsentErrorDTO' + example: + code: CITIZEN_CONSENT_TOO_MANY_REQUESTS + message: Too many requests + headers: + Access-Control-Allow-Origin: + description: Indicates whether the response can be shared with requesting code from the given origin + required: false + schema: + $ref: '#/components/schemas/AccessControlAllowOrigin' + RateLimit-Limit: + description: The number of allowed requests in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitLimit' + RateLimit-Reset: + description: The number of seconds left in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitReset' + Retry-After: + description: The number of seconds to wait before allowing a follow-up request + required: false + schema: + $ref: '#/components/schemas/RetryAfter' + '500': + description: Server ERROR + content: + application/json: + schema: + $ref: '#/components/schemas/CitizenConsentErrorDTO' + example: + code: CITIZEN_CONSENT_GENERIC_ERROR + message: Application error + headers: + Access-Control-Allow-Origin: + description: Indicates whether the response can be shared with requesting code from the given origin + required: false + schema: + $ref: '#/components/schemas/AccessControlAllowOrigin' + RateLimit-Limit: + description: The number of allowed requests in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitLimit' + RateLimit-Reset: + description: The number of seconds left in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitReset' + Retry-After: + description: The number of seconds to wait before allowing a follow-up request + required: false + schema: + $ref: '#/components/schemas/RetryAfter' + '/{fiscalCode}/{tppId}': + get: + tags: + - Citizen consent + summary: >- + ENG: Returns the Citizen consent detalil from fiscalCode and tppId associated - IT: Ritorna il dettaglio dei consensi del cittadino tramite fiscalCode e tppId + operationId: getConsentStatus + description: Get citizen consent from fiscalCode and tppId + parameters: + - name: Accept-Language + in: header + description: 'ENG: Language - IT: Lingua' + schema: + type: string + pattern: "^[ -~]{2,5}$" + minLength: 2 + maxLength: 5 + example: it-IT + default: it-IT + required: true + - name: fiscalCode + in: path + description: 'ENG: Fiscal code of the citizen - IT: codice fiscale del cittadino' + required: true + schema: + type: string + description: "Fiscal Code or P.IVA of the citizen" + pattern: "^[A-Za-z0-9]{11,16}$" + minLength: 11 + maxLength: 16 + example: "RSSMRO92S18L048H" + - name: tppId + in: path + description: 'ENG: Unique ID that identify TPP on PagoPA systems - IT: Identificativo univoco della TPP sui sistemi PagoPA' + required: true + schema: + type: string + description: "Unique ID that identify TPP on PagoPA systems" + pattern: "^[ -~]{1,50}$" + minLength: 1 + maxLength: 50 + example: "0e3bee29-8753-447c-b0da-1f7965558ec2_1706867960900" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/CitizenConsentResponseDTO' + examples: + example3: + $ref: '#/components/examples/example3' + headers: + Access-Control-Allow-Origin: + description: Indicates whether the response can be shared with requesting code from the given origin + required: false + schema: + $ref: '#/components/schemas/AccessControlAllowOrigin' + RateLimit-Limit: + description: The number of allowed requests in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitLimit' + RateLimit-Reset: + description: The number of seconds left in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitReset' + Retry-After: + description: The number of seconds to wait before allowing a follow-up request + required: false + schema: + $ref: '#/components/schemas/RetryAfter' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/CitizenConsentErrorDTO' + example: + code: CITIZEN_CONSENT_BAD_REQUEST + message: Something went wrong handling the request + headers: + Access-Control-Allow-Origin: + description: Indicates whether the response can be shared with requesting code from the given origin + required: false + schema: + $ref: '#/components/schemas/AccessControlAllowOrigin' + RateLimit-Limit: + description: The number of allowed requests in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitLimit' + RateLimit-Reset: + description: The number of seconds left in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitReset' + Retry-After: + description: The number of seconds to wait before allowing a follow-up request + required: false + schema: + $ref: '#/components/schemas/RetryAfter' + '401': + description: Authentication failed + content: + application/json: + schema: + $ref: '#/components/schemas/CitizenConsentErrorDTO' + example: + code: CITIZEN_CONSENT_AUTHENTICATION_FAILED + message: Something went wrong with authentication + headers: + Access-Control-Allow-Origin: + description: Indicates whether the response can be shared with requesting code from the given origin + required: false + schema: + $ref: '#/components/schemas/AccessControlAllowOrigin' + RateLimit-Limit: + description: The number of allowed requests in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitLimit' + RateLimit-Reset: + description: The number of seconds left in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitReset' + Retry-After: + description: The number of seconds to wait before allowing a follow-up request + required: false + schema: + $ref: '#/components/schemas/RetryAfter' + '404': + description: The citizen consent was not found + content: + application/json: + schema: + $ref: '#/components/schemas/CitizenConsentErrorDTO' + example: + code: CITIZEN_CONSENT_NOT_ONBOARDED + message: Citizen consent not inserted + headers: + Access-Control-Allow-Origin: + description: Indicates whether the response can be shared with requesting code from the given origin + required: false + schema: + $ref: '#/components/schemas/AccessControlAllowOrigin' + RateLimit-Limit: + description: The number of allowed requests in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitLimit' + RateLimit-Reset: + description: The number of seconds left in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitReset' + Retry-After: + description: The number of seconds to wait before allowing a follow-up request + required: false + schema: + $ref: '#/components/schemas/RetryAfter' + '429': + description: Too many Request + content: + application/json: + schema: + $ref: '#/components/schemas/CitizenConsentErrorDTO' + example: + code: CITIZEN_CONSENT_TOO_MANY_REQUESTS + message: Too many requests + headers: + Access-Control-Allow-Origin: + description: Indicates whether the response can be shared with requesting code from the given origin + required: false + schema: + $ref: '#/components/schemas/AccessControlAllowOrigin' + RateLimit-Limit: + description: The number of allowed requests in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitLimit' + RateLimit-Reset: + description: The number of seconds left in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitReset' + Retry-After: + description: The number of seconds to wait before allowing a follow-up request + required: false + schema: + $ref: '#/components/schemas/RetryAfter' + '500': + description: Server ERROR + content: + application/json: + schema: + $ref: '#/components/schemas/CitizenConsentErrorDTO' + example: + code: CITIZEN_CONSENT_GENERIC_ERROR + message: Application error + headers: + Access-Control-Allow-Origin: + description: Indicates whether the response can be shared with requesting code from the given origin + required: false + schema: + $ref: '#/components/schemas/AccessControlAllowOrigin' + RateLimit-Limit: + description: The number of allowed requests in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitLimit' + RateLimit-Reset: + description: The number of seconds left in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitReset' + Retry-After: + description: The number of seconds to wait before allowing a follow-up request + required: false + schema: + $ref: '#/components/schemas/RetryAfter' + + '/list/{fiscalCode}': + get: + tags: + - Citizen consent + summary: >- + ENG: Returns the list of tpp associated with a specific citizen - IT: Restituisce la lista dei canali associati ad un determinato cittadino + operationId: get + description: Get citizen consents list from fiscalCode + parameters: + - name: Accept-Language + in: header + description: 'ENG: Language - IT: Lingua' + schema: + type: string + pattern: "^[ -~]{2,5}$" + minLength: 2 + maxLength: 5 + example: it-IT + default: it-IT + required: true + - name: fiscalCode + in: path + description: 'ENG: Fiscal code of the citizen - IT: codice fiscale del cittadino' + required: true + schema: + type: string + description: "Fiscal Code or P.IVA of the citizen" + pattern: "^[A-Za-z0-9]{11,16}$" + minLength: 11 + maxLength: 16 + example: "RSSMRO92S18L048H" + responses: + '200': + description: "A list of citizen consents" + content: + application/json: + schema: + $ref: '#/components/schemas/CitizenConsentResponseDTO' + examples: + example4: + $ref: '#/components/examples/example4' + headers: + Access-Control-Allow-Origin: + description: Indicates whether the response can be shared with requesting code from the given origin + required: false + schema: + $ref: '#/components/schemas/AccessControlAllowOrigin' + RateLimit-Limit: + description: The number of allowed requests in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitLimit' + RateLimit-Reset: + description: The number of seconds left in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitReset' + Retry-After: + description: The number of seconds to wait before allowing a follow-up request + required: false + schema: + $ref: '#/components/schemas/RetryAfter' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/CitizenConsentErrorDTO' + example: + code: CITIZEN_CONSENT_BAD_REQUEST + message: Something went wrong handling the request + headers: + Access-Control-Allow-Origin: + description: Indicates whether the response can be shared with requesting code from the given origin + required: false + schema: + $ref: '#/components/schemas/AccessControlAllowOrigin' + RateLimit-Limit: + description: The number of allowed requests in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitLimit' + RateLimit-Reset: + description: The number of seconds left in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitReset' + Retry-After: + description: The number of seconds to wait before allowing a follow-up request + required: false + schema: + $ref: '#/components/schemas/RetryAfter' + '401': + description: Authentication failed + content: + application/json: + schema: + $ref: '#/components/schemas/CitizenConsentErrorDTO' + example: + code: CITIZEN_CONSENT_AUTHENTICATION_FAILED + message: Something went wrong with authentication + headers: + Access-Control-Allow-Origin: + description: Indicates whether the response can be shared with requesting code from the given origin + required: false + schema: + $ref: '#/components/schemas/AccessControlAllowOrigin' + RateLimit-Limit: + description: The number of allowed requests in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitLimit' + RateLimit-Reset: + description: The number of seconds left in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitReset' + Retry-After: + description: The number of seconds to wait before allowing a follow-up request + required: false + schema: + $ref: '#/components/schemas/RetryAfter' + '404': + description: The citizen consent was not found + content: + application/json: + schema: + $ref: '#/components/schemas/CitizenConsentErrorDTO' + example: + code: CITIZEN_CONSENT_NOT_ONBOARDED + message: Citizen consent not inserted + headers: + Access-Control-Allow-Origin: + description: Indicates whether the response can be shared with requesting code from the given origin + required: false + schema: + $ref: '#/components/schemas/AccessControlAllowOrigin' + RateLimit-Limit: + description: The number of allowed requests in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitLimit' + RateLimit-Reset: + description: The number of seconds left in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitReset' + Retry-After: + description: The number of seconds to wait before allowing a follow-up request + required: false + schema: + $ref: '#/components/schemas/RetryAfter' + '429': + description: Too many Request + content: + application/json: + schema: + $ref: '#/components/schemas/CitizenConsentErrorDTO' + example: + code: CITIZEN_CONSENT_TOO_MANY_REQUESTS + message: Too many requests + headers: + Access-Control-Allow-Origin: + description: Indicates whether the response can be shared with requesting code from the given origin + required: false + schema: + $ref: '#/components/schemas/AccessControlAllowOrigin' + RateLimit-Limit: + description: The number of allowed requests in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitLimit' + RateLimit-Reset: + description: The number of seconds left in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitReset' + Retry-After: + description: The number of seconds to wait before allowing a follow-up request + required: false + schema: + $ref: '#/components/schemas/RetryAfter' + '500': + description: Server ERROR + content: + application/json: + schema: + $ref: '#/components/schemas/CitizenConsentErrorDTO' + example: + code: CITIZEN_CONSENT_GENERIC_ERROR + message: Application error + headers: + Access-Control-Allow-Origin: + description: Indicates whether the response can be shared with requesting code from the given origin + required: false + schema: + $ref: '#/components/schemas/AccessControlAllowOrigin' + RateLimit-Limit: + description: The number of allowed requests in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitLimit' + RateLimit-Reset: + description: The number of seconds left in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitReset' + Retry-After: + description: The number of seconds to wait before allowing a follow-up request + required: false + schema: + $ref: '#/components/schemas/RetryAfter' + + '/list/{fiscalCode}/enabled': + get: + tags: + - Citizen consent + summary: >- + ENG: Returns the list of tpp enabled associated with a specific citizen - IT: Restituisce la lista dei canali abilitati associati ad un determinato cittadino + operationId: getTppEnabledList + description: Get citizen consents tppId enabled list from fiscalCode + parameters: + - name: Accept-Language + in: header + description: 'ENG: Language - IT: Lingua' + schema: + type: string + pattern: "^[ -~]{2,5}$" + minLength: 2 + maxLength: 5 + example: it-IT + default: it-IT + required: true + - name: fiscalCode + in: path + description: 'ENG: Fiscal code of the citizen - IT: codice fiscale del cittadino' + required: true + schema: + type: string + description: "Fiscal Code or P.IVA of the citizen" + pattern: "^[A-Za-z0-9]{11,16}$" + minLength: 11 + maxLength: 16 + example: "RSSMRO92S18L048H" + responses: + '200': + description: "Lista di tppId abilitati per il codice fiscale fornito" + content: + application/json: + schema: + type: array + items: + type: string + description: "Identificativo univoco di un TPP (tppId)" + pattern: "^[ -~]{1,50}$" + minLength: 1 + maxLength: 50 + maxItems: 100 + example: + - "0e3bee29-8753-447c-b0da-1f7965558ec2_1706867960900" + - "bcdef234-5678-90ab-cdef-abcdef012345_1706869005678" + - "cafe1234-babe-5678-cafe-123456789abc_1706872007890" + headers: + Access-Control-Allow-Origin: + description: Indicates whether the response can be shared with requesting code from the given origin + required: false + schema: + $ref: '#/components/schemas/AccessControlAllowOrigin' + RateLimit-Limit: + description: The number of allowed requests in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitLimit' + RateLimit-Reset: + description: The number of seconds left in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitReset' + Retry-After: + description: The number of seconds to wait before allowing a follow-up request + required: false + schema: + $ref: '#/components/schemas/RetryAfter' + + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/CitizenConsentErrorDTO' + example: + code: CITIZEN_CONSENT_BAD_REQUEST + message: Something went wrong handling the request + headers: + Access-Control-Allow-Origin: + description: Indicates whether the response can be shared with requesting code from the given origin + required: false + schema: + $ref: '#/components/schemas/AccessControlAllowOrigin' + RateLimit-Limit: + description: The number of allowed requests in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitLimit' + RateLimit-Reset: + description: The number of seconds left in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitReset' + Retry-After: + description: The number of seconds to wait before allowing a follow-up request + required: false + schema: + $ref: '#/components/schemas/RetryAfter' + '401': + description: Authentication failed + content: + application/json: + schema: + $ref: '#/components/schemas/CitizenConsentErrorDTO' + example: + code: CITIZEN_CONSENT_AUTHENTICATION_FAILED + message: Something went wrong with authentication + headers: + Access-Control-Allow-Origin: + description: Indicates whether the response can be shared with requesting code from the given origin + required: false + schema: + $ref: '#/components/schemas/AccessControlAllowOrigin' + RateLimit-Limit: + description: The number of allowed requests in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitLimit' + RateLimit-Reset: + description: The number of seconds left in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitReset' + Retry-After: + description: The number of seconds to wait before allowing a follow-up request + required: false + schema: + $ref: '#/components/schemas/RetryAfter' + '404': + description: The citizen consent was not found + content: + application/json: + schema: + $ref: '#/components/schemas/CitizenConsentErrorDTO' + example: + code: CITIZEN_CONSENT_NOT_ONBOARDED + message: Citizen consent not inserted + headers: + Access-Control-Allow-Origin: + description: Indicates whether the response can be shared with requesting code from the given origin + required: false + schema: + $ref: '#/components/schemas/AccessControlAllowOrigin' + RateLimit-Limit: + description: The number of allowed requests in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitLimit' + RateLimit-Reset: + description: The number of seconds left in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitReset' + Retry-After: + description: The number of seconds to wait before allowing a follow-up request + required: false + schema: + $ref: '#/components/schemas/RetryAfter' + '429': + description: Too many Request + content: + application/json: + schema: + $ref: '#/components/schemas/CitizenConsentErrorDTO' + example: + code: CITIZEN_CONSENT_TOO_MANY_REQUESTS + message: Too many requests + headers: + Access-Control-Allow-Origin: + description: Indicates whether the response can be shared with requesting code from the given origin + required: false + schema: + $ref: '#/components/schemas/AccessControlAllowOrigin' + RateLimit-Limit: + description: The number of allowed requests in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitLimit' + RateLimit-Reset: + description: The number of seconds left in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitReset' + Retry-After: + description: The number of seconds to wait before allowing a follow-up request + required: false + schema: + $ref: '#/components/schemas/RetryAfter' + '500': + description: Server ERROR + content: + application/json: + schema: + $ref: '#/components/schemas/CitizenConsentErrorDTO' + example: + code: CITIZEN_CONSENT_GENERIC_ERROR + message: Application error + headers: + Access-Control-Allow-Origin: + description: Indicates whether the response can be shared with requesting code from the given origin + required: false + schema: + $ref: '#/components/schemas/AccessControlAllowOrigin' + RateLimit-Limit: + description: The number of allowed requests in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitLimit' + RateLimit-Reset: + description: The number of seconds left in the current period + required: false + schema: + $ref: '#/components/schemas/RateLimitReset' + Retry-After: + description: The number of seconds to wait before allowing a follow-up request + required: false + schema: + $ref: '#/components/schemas/RetryAfter' + +components: + schemas: + + AccessControlAllowOrigin: + description: Indicates whether the response can be shared with requesting code from the given origin + type: string + pattern: "^[ -~]{1,2048}$" + minLength: 1 + maxLength: 2048 + + RateLimitLimit: + description: The number of allowed requests in the current period + type: integer + format: int32 + minimum: 1 + maximum: 240 + + RateLimitReset: + description: The number of seconds left in the current period + type: integer + format: int32 + minimum: 1 + maximum: 60 + + RetryAfter: + description: The number of seconds to wait before allowing a follow-up request + type: integer + format: int32 + minimum: 1 + maximum: 240 + + CitizenConsentRequestDTO: + type: object + description: "Schema di richiesta per aggiornare i consensi" + properties: + fiscalCode: + type: string + example: "RSSMRO92S18L048H" + description: "Codice fiscale dell'utente" + pattern: "^[A-Za-z0-9]{11,16}$" + minLength: 11 + maxLength: 16 + consents: + type: object + description: "Oggetto contenente consensi con chiavi dinamiche basate su tppId (UUID concatenati con timestamp)" + additionalProperties: + type: object + properties: + tppState: + type: boolean + description: "Stato del consenso TPP" + example: true + required: + - tppState + example: + "0e3bee29-8753-447c-b0da-1f7965558ec2_1706867960900": + tppState: true + "a12cd3e4-5678-90ab-cdef-1234567890ab_1706868990123": + tppState: true + + required: + - fiscalCode + - consents + + + + CitizenConsentResponseDTO: + type: object + description: "Schema di risposta che contiene lo stato e la data dei consensi" + properties: + fiscalCode: + type: string + example: "RSSMRO92S18L048H" + description: "Codice fiscale del cittadino" + pattern: "^[A-Za-z0-9]{11,16}$" + minLength: 11 + maxLength: 16 + consents: + type: object + description: "Oggetto contenente consensi con chiavi dinamiche basate su tppId (UUID concatenati con timestamp)" + additionalProperties: + type: object + properties: + tppState: + type: boolean + description: "Stato del consenso TPP" + example: true + tcDate: + type: string + format: date-time + description: "Data e ora dell'ultimo aggiornamento del consenso" + example: "2024-11-01T11:25:40.695Z" + maxLength: 30 + pattern: "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$" + example: + "0e3bee29-8753-447c-b0da-1f7965558ec2_1706867960900": + tppState: true + tcDate: "2024-11-01T11:25:40.695Z" + "a12cd3e4-5678-90ab-cdef-1234567890ab_1706868990123": + tppState: true + tcDate: "2024-11-05T11:25:40.695Z" + required: + - fiscalCode + - consents + + + CitizenConsentStateUpdateDTO: + type: object + description: "Schema che permette la modifica dei consensi del cittadino" + properties: + fiscalCode: + type: string + example: "RSSMRO92S18L048H" + description: "Codice fiscale del cittadino" + pattern: "^[A-Za-z0-9]{11,16}$" + minLength: 11 + maxLength: 16 + tppId: + type: string + example: "0e3bee29-8753-447c-b0da-1f7965558ec2_1706867960900" + description: "ENG: Unique ID that identify TPP on PagoPA systems - IT: Identificativo univoco della TPP sui sistemi PagoPA" + pattern: "^[ -~]{1,50}$" + minLength: 1 + maxLength: 50 + tppState: + type: boolean + example: false + description: "Stato del consenso TPP" + + CitizenConsentErrorDTO: + type: object + required: + - code + - message + properties: + code: + type: string + enum: + - CITIZEN_CONSENT_BAD_REQUEST + - CITIZEN_CONSENT_NOT_ONBOARDED + - CITIZEN_CONSENT_TOO_MANY_REQUESTS + - CITIZEN_CONSENT_GENERIC_ERROR + - CITIZEN_CONSENT_AUTHENTICATION_FAILED + description: |- + "ENG: Error code: CITIZEN_CONSENT_BAD_REQUEST: Something went wrong handling the request, + CITIZEN_CONSENT_NOT_ONBOARDED: Citizen consent not inserted, + CITIZEN_CONSENT_TOO_MANY_REQUESTS: Too many requests, + CITIZEN_CONSENT_GENERIC_ERROR: Application Error, + CITIZEN_CONSENT_AUTHENTICATION_FAILED: Something went wrong with authentication - + IT: Codice di errore: + CITIZEN_CONSENT_BAD_REQUEST: Qualcosa è andato storto durante + l'invio della richiesta, + CITIZEN_CONSENT_NOT_ONBOARDED: Consensi del cittadino non inseriti, + CITIZEN_CONSENT_TOO_MANY_REQUESTS: Troppe richieste, + CITIZEN_CONSENT_GENERIC_ERROR: Errore generico, + CITIZEN_CONSENT_AUTHENTICATION_FAILED: Qualcosa è andato storto con l'autenticazione" + message: + type: string + description: 'ENG: Error message - IT: Messaggio di errore' + maxLength: 250 + pattern: "^[\\w\\s.,!?'\"-]+$" + + examples: + example1: + summary: "Esempio con più consensi" + value: + fiscalCode: "RSSMRO92S18L048H" + consents: + "0e3bee29-8753-447c-b0da-1f7965558ec2_1706867960900": + tppState: true + tcDate: "2024-11-01T11:25:40.695Z" + "a12cd3e4-5678-90ab-cdef-1234567890ab_1706868990123": + tppState: true + tcDate: "2024-11-05T11:25:40.695Z" + example2: + summary: "Esempio con un solo consenso disattivo" + value: + fiscalCode: "RSSMRO92S18L048H" + consents: + "0e3bee29-8753-447c-b0da-1f7965558ec2_1706867960900": + tppState: false + tcDate: "2024-11-06T11:25:40.695Z" + example3: + summary: "Esempio con un solo consenso attivo" + value: + fiscalCode: "RSSMRO92S18L048H" + consents: + "0e3bee29-8753-447c-b0da-1f7965558ec2_1706867960900": + tppState: true + tcDate: "2024-11-06T11:25:40.695Z" + example4: + summary: "Esempio con un consenso attivo e uno non attivo" + value: + fiscalCode: "RSSMRO92S18L048H" + consents: + "0e3bee29-8753-447c-b0da-1f7965558ec2_1706867960900": + tppState: true + tcDate: "2024-11-01T11:25:40.695Z" + "a12cd3e4-5678-90ab-cdef-1234567890ab_1706868990123": + tppState: false + tcDate: "2024-11-05T11:25:40.695Z" + securitySchemes: + bearerAuth: + type: http + scheme: bearer From 0188a17067bf57de5ea745a4e62315830be5532e Mon Sep 17 00:00:00 2001 From: DanieleRanaldo Date: Tue, 12 Nov 2024 16:08:51 +0100 Subject: [PATCH 18/20] added test, validation, dependencies --- pom.xml | 10 + .../citizen/dto/CitizenConsentDTO.java | 7 + .../citizen/service/CitizenServiceImpl.java | 4 +- .../connector/tpp/TppConnectorImplTest.java | 58 ++++++ .../controller/CitizenControllerTest.java | 13 +- .../citizen/faker/CitizenConsentDTOFaker.java | 2 +- .../CitizenSpecificRepositoryImplTest.java | 59 +++++- .../citizen/service/CitizenServiceTest.java | 189 +++++++++++------- ...tizenConsentValidationServiceImplTest.java | 146 ++++++++++++++ 9 files changed, 401 insertions(+), 87 deletions(-) create mode 100644 src/test/java/it/gov/pagopa/onboarding/citizen/connector/tpp/TppConnectorImplTest.java create mode 100644 src/test/java/it/gov/pagopa/onboarding/citizen/validation/CitizenConsentValidationServiceImplTest.java diff --git a/pom.xml b/pom.xml index 45deb9c..a306984 100644 --- a/pom.xml +++ b/pom.xml @@ -78,6 +78,16 @@ reactor-test test + + com.squareup.okhttp3 + okhttp + test + + + com.squareup.okhttp3 + mockwebserver + test + diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/dto/CitizenConsentDTO.java b/src/main/java/it/gov/pagopa/onboarding/citizen/dto/CitizenConsentDTO.java index e1d1769..3ed1f7a 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/dto/CitizenConsentDTO.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/dto/CitizenConsentDTO.java @@ -1,6 +1,8 @@ package it.gov.pagopa.onboarding.citizen.dto; import com.fasterxml.jackson.annotation.JsonAlias; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -9,12 +11,17 @@ import java.time.LocalDateTime; import java.util.Map; +import static it.gov.pagopa.onboarding.citizen.constants.CitizenConstants.ValidationRegex.FISCAL_CODE_STRUCTURE_REGEX; + @Data @SuperBuilder @AllArgsConstructor @NoArgsConstructor public class CitizenConsentDTO { @JsonAlias("fiscalCode") + @NotBlank(message = "Fiscal Code must not be blank") + @Pattern(regexp = FISCAL_CODE_STRUCTURE_REGEX, + message = "Fiscal Code must be 11 digits or up to 16 alphanumeric characters") private String fiscalCode; private Map consents; diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceImpl.java b/src/main/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceImpl.java index 6c07b08..e997977 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceImpl.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceImpl.java @@ -62,7 +62,6 @@ public Mono createCitizenConsent(CitizenConsentDTO citizenCon .switchIfEmpty(validationService.validateTppAndSaveConsent(fiscalCode, tppId, citizenConsent)); } - @Override public Mono updateTppState(String fiscalCode, String tppId, boolean tppState) { log.info("[EMD][CITIZEN][UPDATE-CHANNEL-STATE] Received hashedFiscalCode: {} and tppId: {} with state: {}", @@ -71,9 +70,10 @@ public Mono updateTppState(String fiscalCode, String tppId, b return tppConnector.get(tppId) .switchIfEmpty(Mono.error(exceptionMap.throwException(ExceptionName.TPP_NOT_FOUND, ExceptionMessage.TPP_NOT_FOUND))) .flatMap(tppResponse -> { - if (tppResponse == null || !Boolean.TRUE.equals(tppResponse.getState())) { + if (!validationService.isTppValid(tppResponse)) { return Mono.error(exceptionMap.throwException(ExceptionName.TPP_NOT_FOUND, ExceptionMessage.TPP_NOT_FOUND)); } + return citizenRepository.findByFiscalCodeAndTppId(fiscalCode, tppId) .switchIfEmpty(Mono.error(exceptionMap.throwException (ExceptionName.CITIZEN_NOT_ONBOARDED, "Citizen consent not founded during update state process"))) diff --git a/src/test/java/it/gov/pagopa/onboarding/citizen/connector/tpp/TppConnectorImplTest.java b/src/test/java/it/gov/pagopa/onboarding/citizen/connector/tpp/TppConnectorImplTest.java new file mode 100644 index 0000000..ec0594e --- /dev/null +++ b/src/test/java/it/gov/pagopa/onboarding/citizen/connector/tpp/TppConnectorImplTest.java @@ -0,0 +1,58 @@ +package it.gov.pagopa.onboarding.citizen.connector.tpp; + +import it.gov.pagopa.onboarding.citizen.dto.TppDTO; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; + +import static it.gov.pagopa.onboarding.citizen.enums.AuthenticationType.OAUTH2; +import static org.assertj.core.api.Assertions.assertThat; + +class TppConnectorImplTest { + + private MockWebServer mockWebServer; + private TppConnectorImpl tppConnector; + + @BeforeEach + void setUp() throws Exception { + mockWebServer = new MockWebServer(); + mockWebServer.start(); + + WebClient.Builder webClientBuilder = WebClient.builder(); + + tppConnector = new TppConnectorImpl(webClientBuilder, mockWebServer.url("/").toString()); + } + + @AfterEach + void tearDown() throws Exception { + mockWebServer.shutdown(); + } + + @Test + void testGetTppInfoOk() { + + mockWebServer.enqueue(new MockResponse() + .setResponseCode(200) + .setBody("{\"tppId\":\"TPP_OK_1\",\"entityId\":\"ENTITY_OK_1\",\"businessName\":\"Test Business\",\"messageUrl\":\"https://example.com/message\",\"authenticationUrl\":\"https://example.com/auth\",\"authenticationType\":\"OAUTH2\",\"contact\":{\"name\":\"John Doe\",\"number\":\"+1234567890\",\"email\":\"contact@example.com\"},\"state\":true}") + .addHeader("Content-Type", "application/json")); + + Mono resultMono = tppConnector.get("TPP_OK_1"); + TppDTO tppDTO = resultMono.block(); + + assertThat(tppDTO).isNotNull(); + assertThat(tppDTO.getTppId()).isEqualTo("TPP_OK_1"); + assertThat(tppDTO.getEntityId()).isEqualTo("ENTITY_OK_1"); + assertThat(tppDTO.getBusinessName()).isEqualTo("Test Business"); + assertThat(tppDTO.getMessageUrl()).isEqualTo("https://example.com/message"); + assertThat(tppDTO.getAuthenticationUrl()).isEqualTo("https://example.com/auth"); + assertThat(tppDTO.getAuthenticationType()).isEqualTo(OAUTH2); + assertThat(tppDTO.getContact().getName()).isEqualTo("John Doe"); + assertThat(tppDTO.getContact().getNumber()).isEqualTo("+1234567890"); + assertThat(tppDTO.getContact().getEmail()).isEqualTo("contact@example.com"); + assertThat(tppDTO.getState()).isTrue(); + } +} diff --git a/src/test/java/it/gov/pagopa/onboarding/citizen/controller/CitizenControllerTest.java b/src/test/java/it/gov/pagopa/onboarding/citizen/controller/CitizenControllerTest.java index dfb685a..f42d41b 100644 --- a/src/test/java/it/gov/pagopa/onboarding/citizen/controller/CitizenControllerTest.java +++ b/src/test/java/it/gov/pagopa/onboarding/citizen/controller/CitizenControllerTest.java @@ -104,27 +104,26 @@ void getConsentStatus_Ok() { } @Test - void getCitizenConsentsEnabled_Ok() { - List tppIdList = List.of("tpp1","tpp2"); + void getTppEnabledList_Ok() { + List tppEnabledList = List.of("TPP1", "TPP2"); Mockito.when(citizenService.getTppEnabledList(FISCAL_CODE)) - .thenReturn(Mono.just(tppIdList)); + .thenReturn(Mono.just(tppEnabledList)); webClient.get() .uri("/emd/citizen/list/{fiscalCode}/enabled", FISCAL_CODE) .exchange() .expectStatus().isOk() - .expectBody(new ParameterizedTypeReference>() { - }) + .expectBody(new ParameterizedTypeReference>() {}) .consumeWith(response -> { List resultResponse = response.getResponseBody(); Assertions.assertNotNull(resultResponse); - Assertions.assertEquals(tppIdList.size(), resultResponse.size()); + Assertions.assertEquals(tppEnabledList.size(), resultResponse.size()); }); } @Test - void getCitizenConsents_Ok() { + void get_Ok() { CitizenConsentDTO citizenConsentDTO = CitizenConsentDTOFaker.mockInstance(true); Mockito.when(citizenService.get(FISCAL_CODE)) diff --git a/src/test/java/it/gov/pagopa/onboarding/citizen/faker/CitizenConsentDTOFaker.java b/src/test/java/it/gov/pagopa/onboarding/citizen/faker/CitizenConsentDTOFaker.java index 24bd42e..d6a1442 100644 --- a/src/test/java/it/gov/pagopa/onboarding/citizen/faker/CitizenConsentDTOFaker.java +++ b/src/test/java/it/gov/pagopa/onboarding/citizen/faker/CitizenConsentDTOFaker.java @@ -15,7 +15,7 @@ public static CitizenConsentDTO mockInstance(Boolean bias) { consents.put("tppId", new CitizenConsentDTO.ConsentDTO(bias, LocalDateTime.now())); return CitizenConsentDTO.builder() - .fiscalCode("fiscalCode") + .fiscalCode("MLXHZZ43A70H203T") .consents(consents) .build(); } diff --git a/src/test/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImplTest.java b/src/test/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImplTest.java index 6d76be9..0acc9cf 100644 --- a/src/test/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImplTest.java +++ b/src/test/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImplTest.java @@ -14,6 +14,7 @@ import java.util.HashMap; import java.util.Map; +import java.util.Objects; import static org.mockito.Mockito.when; @@ -48,8 +49,62 @@ void testFindByFiscalCodeAndTppId() { Mono result = repository.findByFiscalCodeAndTppId(hashedFiscalCode, tppId); - Assertions.assertEquals(hashedFiscalCode, result.block().getFiscalCode()); + Assertions.assertEquals(hashedFiscalCode, Objects.requireNonNull(result.block()).getFiscalCode()); Mockito.verify(mongoTemplate).aggregate(Mockito.any(), Mockito.eq(CitizenConsent.class), Mockito.eq(CitizenConsent.class)); } -} \ No newline at end of file + @Test + void testFindByFiscalCodeAndTppId_TppIdNull() { + String hashedFiscalCode = "hashedCode"; + String tppId = null; + Mono result = repository.findByFiscalCodeAndTppId(hashedFiscalCode, tppId); + + Assertions.assertNotEquals(Boolean.TRUE, result.hasElement().block()); + } + + @Test + void testFindByFiscalCodeAndTppId_EmptyResult() { + String hashedFiscalCode = "hashedCode"; + String tppId = "tpp1"; + + when(mongoTemplate.aggregate(Mockito.any(), Mockito.eq(CitizenConsent.class), Mockito.eq(CitizenConsent.class))) + .thenReturn(Flux.empty()); + + Mono result = repository.findByFiscalCodeAndTppId(hashedFiscalCode, tppId); + + Assertions.assertNull(result.block()); + } + + @Test + void testConsentKeyWrapperGetterSetter() { + CitizenSpecificRepositoryImpl.ConsentKeyWrapper consentKeyWrapper = new CitizenSpecificRepositoryImpl.ConsentKeyWrapper(); + + consentKeyWrapper.setK("testKey"); + + Assertions.assertEquals("testKey", consentKeyWrapper.getK()); + } + + @Test + void testConsentKeyWrapperToString() { + + CitizenSpecificRepositoryImpl.ConsentKeyWrapper consentKeyWrapper = new CitizenSpecificRepositoryImpl.ConsentKeyWrapper(); + consentKeyWrapper.setK("testKey"); + + Assertions.assertEquals("CitizenSpecificRepositoryImpl.ConsentKeyWrapper(k=testKey)", consentKeyWrapper.toString()); + } + + @Test + void testConsentKeyWrapperEqualsAndHashCode() { + + CitizenSpecificRepositoryImpl.ConsentKeyWrapper consentKeyWrapper1 = new CitizenSpecificRepositoryImpl.ConsentKeyWrapper(); + CitizenSpecificRepositoryImpl.ConsentKeyWrapper consentKeyWrapper2 = new CitizenSpecificRepositoryImpl.ConsentKeyWrapper(); + consentKeyWrapper1.setK("testKey"); + consentKeyWrapper2.setK("testKey"); + + Assertions.assertEquals(consentKeyWrapper1, consentKeyWrapper2); + Assertions.assertEquals(consentKeyWrapper1.hashCode(), consentKeyWrapper2.hashCode()); + + consentKeyWrapper2.setK("differentKey"); + Assertions.assertNotEquals(consentKeyWrapper1, consentKeyWrapper2); + } +} diff --git a/src/test/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceTest.java b/src/test/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceTest.java index c9f0377..4dac064 100644 --- a/src/test/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceTest.java +++ b/src/test/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceTest.java @@ -1,8 +1,6 @@ package it.gov.pagopa.onboarding.citizen.service; -import it.gov.pagopa.common.utils.Utils; import it.gov.pagopa.common.web.exception.ClientExceptionWithBody; -import it.gov.pagopa.common.web.exception.EmdEncryptionException; import it.gov.pagopa.onboarding.citizen.configuration.ExceptionMap; import it.gov.pagopa.onboarding.citizen.connector.tpp.TppConnectorImpl; import it.gov.pagopa.onboarding.citizen.dto.CitizenConsentDTO; @@ -12,27 +10,37 @@ import it.gov.pagopa.onboarding.citizen.faker.CitizenConsentFaker; import it.gov.pagopa.onboarding.citizen.faker.TppDTOFaker; import it.gov.pagopa.onboarding.citizen.model.CitizenConsent; +import it.gov.pagopa.onboarding.citizen.model.ConsentDetails; import it.gov.pagopa.onboarding.citizen.model.mapper.CitizenConsentDTOToObjectMapper; import it.gov.pagopa.onboarding.citizen.repository.CitizenRepository; +import it.gov.pagopa.onboarding.citizen.validation.CitizenConsentValidationServiceImpl; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.function.Executable; -import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpStatus; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; @ExtendWith({SpringExtension.class, MockitoExtension.class}) @ContextConfiguration(classes = { CitizenServiceImpl.class, + CitizenConsentValidationServiceImpl.class, CitizenConsentObjectToDTOMapper.class, CitizenConsentDTOToObjectMapper.class, ExceptionMap.class @@ -42,6 +50,9 @@ class CitizenServiceTest { @Autowired CitizenServiceImpl citizenService; + @MockBean + CitizenConsentValidationServiceImpl validationService; + @MockBean CitizenRepository citizenRepository; @@ -52,122 +63,115 @@ class CitizenServiceTest { CitizenConsentObjectToDTOMapper dtoMapper; private static final String FISCAL_CODE = "fiscalCode"; - private static final String HASHED_FISCAL_CODE = Utils.createSHA256(FISCAL_CODE); private static final String TPP_ID = "tppId"; private static final boolean TPP_STATE = true; private static final CitizenConsent CITIZEN_CONSENT = CitizenConsentFaker.mockInstance(true); private static final CitizenConsentDTO CITIZEN_CONSENT_DTO = CitizenConsentDTOFaker.mockInstance(true); + private static final TppDTO TPP_DTO = TppDTOFaker.mockInstance(); @Test void createCitizenConsent_Ok() { - CitizenConsentDTO citizenConsentDTO = dtoMapper.map(CITIZEN_CONSENT); - - TppDTO mockTppDTO = TppDTOFaker.mockInstance(); - mockTppDTO.setState(true); - - Mockito.when(tppConnector.get(anyString())) - .thenReturn(Mono.just(mockTppDTO)); - Mockito.when(citizenRepository.save(Mockito.any())) - .thenReturn(Mono.just(CITIZEN_CONSENT)); - - Mockito.when(citizenRepository.findById(anyString())) - .thenReturn(Mono.empty()); + CitizenConsentDTO expectedConsentDTO = dtoMapper.map(CITIZEN_CONSENT); + TppDTO activeTppDTO = TPP_DTO; + activeTppDTO.setState(true); - Mockito.when(citizenRepository.findByFiscalCodeAndTppId(anyString(), anyString())) - .thenReturn(Mono.empty()); + when(tppConnector.get(anyString())).thenReturn(Mono.just(activeTppDTO)); + when(citizenRepository.save(Mockito.any())).thenReturn(Mono.just(CITIZEN_CONSENT)); + when(citizenRepository.findByFiscalCode(anyString())).thenReturn(Mono.empty()); + when(validationService.validateTppAndSaveConsent(anyString(), anyString(), any(CitizenConsent.class))) + .thenReturn(Mono.just(expectedConsentDTO)); CitizenConsentDTO response = citizenService.createCitizenConsent(CITIZEN_CONSENT_DTO).block(); assertNotNull(response); - assertEquals(citizenConsentDTO, response); + assertEquals(expectedConsentDTO, response); } @Test void createCitizenConsent_AlreadyExists() { - CitizenConsentDTO citizenConsentDTO = dtoMapper.map(CITIZEN_CONSENT); - TppDTO mockTppDTO = TppDTOFaker.mockInstance(); - mockTppDTO.setState(true); + CitizenConsentDTO expectedConsentDTO = dtoMapper.map(CITIZEN_CONSENT); + TppDTO activeTppDTO = TPP_DTO; + activeTppDTO.setState(true); - Mockito.when(tppConnector.get(anyString())) - .thenReturn(Mono.just(mockTppDTO)); + when(tppConnector.get(anyString())).thenReturn(Mono.just(activeTppDTO)); - Mockito.when(citizenRepository.save(Mockito.any())) - .thenReturn(Mono.empty()); + when(citizenRepository.save(Mockito.any())).thenReturn(Mono.just(CITIZEN_CONSENT)); + when(citizenRepository.findByFiscalCode(anyString())).thenReturn(Mono.just(CITIZEN_CONSENT)); + when(citizenRepository.findByFiscalCodeAndTppId(anyString(), anyString())).thenReturn(Mono.just(CITIZEN_CONSENT)); - Mockito.when(citizenRepository.findById(anyString())) - .thenReturn(Mono.just(CITIZEN_CONSENT)); + when(validationService.handleExistingConsent(any(), anyString(), any())) + .thenReturn(Mono.just(expectedConsentDTO)); - Mockito.when(citizenRepository.findByFiscalCodeAndTppId(anyString(), anyString())) - .thenReturn(Mono.just(CITIZEN_CONSENT)); + when(validationService.validateTppAndSaveConsent(anyString(), anyString(), any(CitizenConsent.class))) + .thenReturn(Mono.just(expectedConsentDTO)); CitizenConsentDTO response = citizenService.createCitizenConsent(CITIZEN_CONSENT_DTO).block(); + assertNotNull(response); - assertEquals(citizenConsentDTO, response); + assertEquals(expectedConsentDTO, response); } @Test - void createCitizenConsent_Ko_TppNull() { + void createCitizenConsent_Ko_TppNotProvided() { - CitizenConsentDTO citizenConsentDTO = CitizenConsentDTOFaker.mockInstance(true); - citizenConsentDTO.getConsents().clear(); + CitizenConsentDTO incompleteConsentDTO = CitizenConsentDTOFaker.mockInstance(true); + incompleteConsentDTO.getConsents().clear(); - Mockito.when(tppConnector.get(anyString())).thenReturn(Mono.empty()); - Mockito.when(citizenRepository.findByFiscalCodeAndTppId(anyString(), anyString())) - .thenReturn(Mono.empty()); + when(tppConnector.get(anyString())).thenReturn(Mono.empty()); + when(citizenRepository.findByFiscalCodeAndTppId(anyString(), anyString())).thenReturn(Mono.empty()); - ClientExceptionWithBody exception = assertThrows(ClientExceptionWithBody.class, - () -> citizenService.createCitizenConsent(citizenConsentDTO).block()); + when(validationService.validateTppAndSaveConsent(anyString(), anyString(), any(CitizenConsent.class))) + .thenReturn(Mono.error(new ClientExceptionWithBody(HttpStatus.BAD_REQUEST, "TPP_NOT_FOUND", "TPP does not exist or is not active"))); - assertEquals("TPP does not exist or is not active", exception.getMessage()); + StepVerifier.create(citizenService.createCitizenConsent(incompleteConsentDTO)) + .expectErrorMatches(throwable -> throwable instanceof ClientExceptionWithBody && + "TPP does not exist or is not active".equals(throwable.getMessage()) && + "TPP_NOT_FOUND".equals(((ClientExceptionWithBody) throwable).getCode())) + .verify(); } @Test void createCitizenConsent_Ko_TppInactive() { - TppDTO mockTppDTO = TppDTOFaker.mockInstance(); - mockTppDTO.setState(false); + TppDTO inactiveTppDTO = TPP_DTO; + inactiveTppDTO.setState(false); - Mockito.when(tppConnector.get(anyString())).thenReturn(Mono.just(mockTppDTO)); - Mockito.when(citizenRepository.findByFiscalCodeAndTppId(anyString(), anyString())) - .thenReturn(Mono.empty()); + when(tppConnector.get(anyString())).thenReturn(Mono.just(inactiveTppDTO)); + when(citizenRepository.findByFiscalCodeAndTppId(anyString(), anyString())).thenReturn(Mono.empty()); + when(citizenRepository.findByFiscalCode(anyString())).thenReturn(Mono.empty()); + + when(validationService.validateTppAndSaveConsent(anyString(), anyString(), any(CitizenConsent.class))) + .thenReturn(Mono.error(new ClientExceptionWithBody(HttpStatus.BAD_REQUEST, "TPP_NOT_FOUND", "TPP does not exist or is not active"))); CitizenConsentDTO citizenConsentDTO = dtoMapper.map(CITIZEN_CONSENT); - ClientExceptionWithBody exception = assertThrows(ClientExceptionWithBody.class, - () -> citizenService.createCitizenConsent(citizenConsentDTO).block()); + StepVerifier.create(citizenService.createCitizenConsent(citizenConsentDTO)) + .expectErrorMatches(throwable -> throwable instanceof ClientExceptionWithBody && + "TPP does not exist or is not active".equals(throwable.getMessage()) && + "TPP_NOT_FOUND".equals(((ClientExceptionWithBody) throwable).getCode())) + .verify(); - assertEquals("TPP does not exist or is not active", exception.getMessage()); Mockito.verify(citizenRepository, Mockito.never()).save(Mockito.any()); } - @Test - void createCitizenConsent_Ko_EmdEncryptError() { - CitizenConsentDTO citizenConsentDTO = CitizenConsentDTOFaker.mockInstance(true); - - try (MockedStatic mockedStatic = Mockito.mockStatic(Utils.class)) { - mockedStatic.when(() -> Utils.createSHA256(any())) - .thenThrow(EmdEncryptionException.class); - - assertThrows(EmdEncryptionException.class, () -> citizenService.createCitizenConsent(citizenConsentDTO)); - } - } - @Test void updateChannelState_Ok() { TppDTO mockTppDTO = TppDTOFaker.mockInstance(); mockTppDTO.setState(true); - Mockito.when(tppConnector.get(anyString())) + when(tppConnector.get(anyString())) .thenReturn(Mono.just(mockTppDTO)); - Mockito.when(citizenRepository.findByFiscalCodeAndTppId(HASHED_FISCAL_CODE, TPP_ID)) + when(citizenRepository.findByFiscalCodeAndTppId(FISCAL_CODE, TPP_ID)) .thenReturn(Mono.just(CITIZEN_CONSENT)); - Mockito.when(citizenRepository.save(Mockito.any())) + when(citizenRepository.save(any())) .thenReturn(Mono.just(CITIZEN_CONSENT)); + when(validationService.isTppValid(mockTppDTO)).thenReturn(true); + CitizenConsentDTO response = citizenService.updateTppState(FISCAL_CODE, TPP_ID, TPP_STATE).block(); assertNotNull(response); @@ -181,12 +185,14 @@ void updateChannelState_Ko_CitizenNotOnboarded() { TppDTO mockTppDTO = TppDTOFaker.mockInstance(); mockTppDTO.setState(true); - Mockito.when(tppConnector.get(anyString())) + when(tppConnector.get(anyString())) .thenReturn(Mono.just(mockTppDTO)); - Mockito.when(citizenRepository.findByFiscalCodeAndTppId(HASHED_FISCAL_CODE, TPP_ID)) + when(citizenRepository.findByFiscalCodeAndTppId(FISCAL_CODE, TPP_ID)) .thenReturn(Mono.empty()); + when(validationService.isTppValid(mockTppDTO)).thenReturn(true); + Executable executable = () -> citizenService.updateTppState(FISCAL_CODE, TPP_ID, true).block(); ClientExceptionWithBody exception = assertThrows(ClientExceptionWithBody.class, executable); @@ -202,15 +208,17 @@ void updateChannelState_Ok_ConsentDetailsIsNull() { CitizenConsent citizenConsentWithConsentDetailNull = CitizenConsentFaker.mockInstance(true); citizenConsentWithConsentDetailNull.getConsents().put(TPP_ID, null); - Mockito.when(tppConnector.get(anyString())) + when(tppConnector.get(anyString())) .thenReturn(Mono.just(mockTppDTO)); - Mockito.when(citizenRepository.findByFiscalCodeAndTppId(HASHED_FISCAL_CODE, TPP_ID)) + when(citizenRepository.findByFiscalCodeAndTppId(FISCAL_CODE, TPP_ID)) .thenReturn(Mono.just(citizenConsentWithConsentDetailNull)); - Mockito.when(citizenRepository.save(Mockito.any())) + when(citizenRepository.save(any())) .thenReturn(Mono.just(citizenConsentWithConsentDetailNull)); + when(validationService.isTppValid(mockTppDTO)).thenReturn(true); + Executable executable = () -> citizenService.updateTppState(FISCAL_CODE, TPP_ID, TPP_STATE).block(); ClientExceptionWithBody exception = assertThrows(ClientExceptionWithBody.class, executable); @@ -220,7 +228,7 @@ void updateChannelState_Ok_ConsentDetailsIsNull() { @Test void updateChannelState_Ko_TppResponseIsNull() { - Mockito.when(tppConnector.get(anyString())).thenReturn(Mono.empty()); + when(tppConnector.get(anyString())).thenReturn(Mono.empty()); Executable executable = () -> citizenService.updateTppState(FISCAL_CODE, TPP_ID, TPP_STATE).block(); ClientExceptionWithBody exception = assertThrows(ClientExceptionWithBody.class, executable); @@ -234,7 +242,7 @@ void updateChannelState_Ko_TppInactive() { TppDTO mockTppDTO = TppDTOFaker.mockInstance(); mockTppDTO.setState(false); - Mockito.when(tppConnector.get(anyString())).thenReturn(Mono.just(mockTppDTO)); + when(tppConnector.get(anyString())).thenReturn(Mono.just(mockTppDTO)); Executable executable = () -> citizenService.updateTppState(FISCAL_CODE, TPP_ID, TPP_STATE).block(); ClientExceptionWithBody exception = assertThrows(ClientExceptionWithBody.class, executable); @@ -246,7 +254,7 @@ void updateChannelState_Ko_TppInactive() { void getConsentStatus_Ok() { - Mockito.when(citizenRepository.findByFiscalCodeAndTppId(HASHED_FISCAL_CODE, TPP_ID)) + when(citizenRepository.findByFiscalCodeAndTppId(FISCAL_CODE, TPP_ID)) .thenReturn(Mono.just(CITIZEN_CONSENT)); CitizenConsentDTO response = citizenService.getConsentStatus(FISCAL_CODE, TPP_ID).block(); @@ -256,7 +264,7 @@ void getConsentStatus_Ok() { @Test void getConsentStatus_Ko_CitizenNotOnboarded() { - Mockito.when(citizenRepository.findByFiscalCodeAndTppId(HASHED_FISCAL_CODE, TPP_ID)) + when(citizenRepository.findByFiscalCodeAndTppId(FISCAL_CODE, TPP_ID)) .thenReturn(Mono.empty()); Executable executable = () -> citizenService.getConsentStatus(FISCAL_CODE, TPP_ID).block(); @@ -265,15 +273,46 @@ void getConsentStatus_Ko_CitizenNotOnboarded() { assertEquals("Citizen consent not founded during get process ", exception.getMessage()); } + @Test + void testGetTppEnabledList_Success() { + + Map consents = new HashMap<>(); + + consents.put("Tpp1", ConsentDetails.builder() + .tppState(true) + .tcDate(LocalDateTime.now()) + .build()); + + consents.put("Tpp2", ConsentDetails.builder() + .tppState(false) + .tcDate(LocalDateTime.now()) + .build()); + + CitizenConsent citizenConsent = CitizenConsent.builder() + .fiscalCode(FISCAL_CODE) + .consents(consents) + .build(); + + when(citizenRepository.findByFiscalCode(FISCAL_CODE)).thenReturn(Mono.just(citizenConsent)); + Mono> result = citizenService.getTppEnabledList(FISCAL_CODE); + + StepVerifier.create(result) + .expectNext(List.of("Tpp1")) + .verifyComplete(); + } @Test - void getListAllConsents_Ok() { - Mockito.when(citizenRepository.findByFiscalCode(HASHED_FISCAL_CODE)) + void get_Ok() { + + when(citizenRepository.findByFiscalCode(FISCAL_CODE)) .thenReturn(Mono.just(CITIZEN_CONSENT)); CitizenConsentDTO response = citizenService.get(FISCAL_CODE).block(); + assertNotNull(response); - assertEquals(CITIZEN_CONSENT, response); + + assertEquals(CITIZEN_CONSENT.getFiscalCode(), response.getFiscalCode()); } + } diff --git a/src/test/java/it/gov/pagopa/onboarding/citizen/validation/CitizenConsentValidationServiceImplTest.java b/src/test/java/it/gov/pagopa/onboarding/citizen/validation/CitizenConsentValidationServiceImplTest.java new file mode 100644 index 0000000..c1985fd --- /dev/null +++ b/src/test/java/it/gov/pagopa/onboarding/citizen/validation/CitizenConsentValidationServiceImplTest.java @@ -0,0 +1,146 @@ +package it.gov.pagopa.onboarding.citizen.validation; + +import it.gov.pagopa.common.web.exception.ClientExceptionWithBody; +import it.gov.pagopa.onboarding.citizen.connector.tpp.TppConnectorImpl; +import it.gov.pagopa.onboarding.citizen.dto.CitizenConsentDTO; +import it.gov.pagopa.onboarding.citizen.dto.TppDTO; +import it.gov.pagopa.onboarding.citizen.dto.mapper.CitizenConsentObjectToDTOMapper; +import it.gov.pagopa.onboarding.citizen.faker.CitizenConsentDTOFaker; +import it.gov.pagopa.onboarding.citizen.faker.CitizenConsentFaker; +import it.gov.pagopa.onboarding.citizen.faker.TppDTOFaker; +import it.gov.pagopa.onboarding.citizen.model.CitizenConsent; +import it.gov.pagopa.onboarding.citizen.repository.CitizenRepository; +import it.gov.pagopa.onboarding.citizen.service.BloomFilterServiceImpl; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +@SpringBootTest +class CitizenConsentValidationServiceImplTest { + + @MockBean + BloomFilterServiceImpl bloomFilterService; + + @MockBean + CitizenRepository citizenRepository; + + @MockBean + TppConnectorImpl tppConnector; + + @MockBean + CitizenConsentObjectToDTOMapper dtoMapper; + + @Autowired + CitizenConsentValidationServiceImpl validationService; + + private static final CitizenConsent CITIZEN_CONSENT = CitizenConsentFaker.mockInstance(true); + private static final CitizenConsentDTO CITIZEN_CONSENT_DTO = CitizenConsentDTOFaker.mockInstance(true); + private static final TppDTO TPP_DTO = TppDTOFaker.mockInstance(); + + @Test + void handleExistingConsent_ConsentAlreadyExists() { + CitizenConsent existingConsent = CITIZEN_CONSENT; + String tppId = "existingTppId"; + existingConsent.getConsents().put(tppId, CITIZEN_CONSENT.getConsents().get(tppId)); + + when(dtoMapper.map(existingConsent)).thenReturn(CITIZEN_CONSENT_DTO); + + when(citizenRepository.findAll()).thenReturn(Flux.just(CitizenConsentFaker.mockInstance(true))); + + doNothing().when(bloomFilterService).add(anyString()); + + CitizenConsentDTO result = validationService.handleExistingConsent(existingConsent, tppId, CITIZEN_CONSENT).block(); + + assertNotNull(result); + assertEquals(CITIZEN_CONSENT_DTO, result); + } + + @Test + void handleExistingConsent_NewConsentForTpp() { + CitizenConsent existingConsent = CITIZEN_CONSENT; + TppDTO activeTppDTO = TPP_DTO; + activeTppDTO.setState(true); + + when(tppConnector.get(anyString())).thenReturn(Mono.just(activeTppDTO)); + when(citizenRepository.save(existingConsent)).thenReturn(Mono.just(existingConsent)); + when(dtoMapper.map(existingConsent)).thenReturn(CITIZEN_CONSENT_DTO); + + when(citizenRepository.findAll()).thenReturn(Flux.just(CitizenConsentFaker.mockInstance(true))); + + doNothing().when(bloomFilterService).add(anyString()); + + CitizenConsentDTO result = validationService.handleExistingConsent(existingConsent, activeTppDTO.getTppId(), CITIZEN_CONSENT).block(); + + assertNotNull(result); + assertEquals(CITIZEN_CONSENT_DTO, result); + } + + @Test + void validateTppAndSaveConsent_TppValidAndActive() { + String fiscalCode = CITIZEN_CONSENT.getFiscalCode(); + TppDTO activeTppDTO = TPP_DTO; + activeTppDTO.setState(true); + + + when(tppConnector.get(anyString())).thenReturn(Mono.just(activeTppDTO)); + when(citizenRepository.save(CITIZEN_CONSENT)).thenReturn(Mono.just(CITIZEN_CONSENT)); + when(dtoMapper.map(CITIZEN_CONSENT)).thenReturn(CITIZEN_CONSENT_DTO); + + when(citizenRepository.findAll()).thenReturn(Flux.just(CitizenConsentFaker.mockInstance(true))); + + doNothing().when(bloomFilterService).add(anyString()); + + CitizenConsentDTO result = validationService.validateTppAndSaveConsent(fiscalCode, activeTppDTO.getTppId(), CITIZEN_CONSENT).block(); + + assertNotNull(result); + assertEquals(CITIZEN_CONSENT_DTO, result); + verify(bloomFilterService, times(1)).add(fiscalCode); + } + + @Test + void validateTppAndSaveConsent_TppInvalid() { + String fiscalCode = CITIZEN_CONSENT.getFiscalCode(); + String tppId = "inactiveTppId"; + + TppDTO inactiveTppDTO = TPP_DTO; + inactiveTppDTO.setState(false); + + when(tppConnector.get(anyString())).thenReturn(Mono.just(inactiveTppDTO)); + + StepVerifier.create(validationService.validateTppAndSaveConsent(fiscalCode, tppId, CITIZEN_CONSENT)) + .expectErrorMatches(throwable -> throwable instanceof ClientExceptionWithBody && + "TPP does not exist or is not active".equals(throwable.getMessage()) && + "TPP_NOT_FOUND".equals(((ClientExceptionWithBody) throwable).getCode())) + .verify(); + + verify(citizenRepository, never()).save(any()); + verify(bloomFilterService, never()).add(fiscalCode); + } + + @Test + void isTppValid_TrueWhenActive() { + TppDTO activeTpp = TPP_DTO; + activeTpp.setState(true); + assertTrue(validationService.isTppValid(activeTpp)); + } + + @Test + void isTppValid_FalseWhenInactive() { + TppDTO inactiveTpp = TPP_DTO; + inactiveTpp.setState(false); + assertFalse(validationService.isTppValid(inactiveTpp)); + } + + @Test + void isTppValid_FalseWhenNull() { + assertFalse(validationService.isTppValid(null)); + } +} From 961d4d1d6f14a14f797926619e902652079e4083 Mon Sep 17 00:00:00 2001 From: Vitolo-Andrea Date: Wed, 13 Nov 2024 09:45:02 +0100 Subject: [PATCH 19/20] Tpp Connector Error Fix --- .../citizen/service/CitizenServiceImpl.java | 33 +++++++------------ .../CitizenConsentValidationService.java | 2 -- .../CitizenConsentValidationServiceImpl.java | 20 ++++------- .../citizen/service/CitizenServiceTest.java | 5 ++- ...tizenConsentValidationServiceImplTest.java | 32 +++++++++--------- 5 files changed, 37 insertions(+), 55 deletions(-) diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceImpl.java b/src/main/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceImpl.java index e997977..c0c45a5 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceImpl.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceImpl.java @@ -68,28 +68,17 @@ public Mono updateTppState(String fiscalCode, String tppId, b Utils.createSHA256(fiscalCode), inputSanify(tppId), tppState); return tppConnector.get(tppId) - .switchIfEmpty(Mono.error(exceptionMap.throwException(ExceptionName.TPP_NOT_FOUND, ExceptionMessage.TPP_NOT_FOUND))) - .flatMap(tppResponse -> { - if (!validationService.isTppValid(tppResponse)) { - return Mono.error(exceptionMap.throwException(ExceptionName.TPP_NOT_FOUND, ExceptionMessage.TPP_NOT_FOUND)); - } - - return citizenRepository.findByFiscalCodeAndTppId(fiscalCode, tppId) - .switchIfEmpty(Mono.error(exceptionMap.throwException - (ExceptionName.CITIZEN_NOT_ONBOARDED, "Citizen consent not founded during update state process"))) - .flatMap(citizenConsent -> { - ConsentDetails consentDetails = citizenConsent.getConsents().get(tppId); - if (consentDetails != null) { - consentDetails.setTppState(tppState); - } else { - return Mono.error(exceptionMap.throwException - (ExceptionName.CITIZEN_NOT_ONBOARDED, "ConsentDetails is null for this tppId")); - } - return citizenRepository.save(citizenConsent); - }) - .map(mapperToDTO::map) - .doOnSuccess(savedConsent -> log.info("[EMD][CITIZEN][UPDATE-CHANNEL-STATE] Updated state")); - }); + .onErrorMap(error ->exceptionMap.throwException(ExceptionName.TPP_NOT_FOUND, ExceptionMessage.TPP_NOT_FOUND)) + .flatMap(tppResponse -> citizenRepository.findByFiscalCodeAndTppId(fiscalCode, tppId) + .switchIfEmpty(Mono.error(exceptionMap.throwException + (ExceptionName.CITIZEN_NOT_ONBOARDED, "Citizen consent not founded during update state process"))) + .flatMap(citizenConsent -> { + ConsentDetails consentDetails = citizenConsent.getConsents().get(tppId); + consentDetails.setTppState(tppState); + return citizenRepository.save(citizenConsent); + }) + .map(mapperToDTO::map) + .doOnSuccess(savedConsent -> log.info("[EMD][CITIZEN][UPDATE-CHANNEL-STATE] Updated state"))); } @Override diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/validation/CitizenConsentValidationService.java b/src/main/java/it/gov/pagopa/onboarding/citizen/validation/CitizenConsentValidationService.java index 46830e9..8cbabed 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/validation/CitizenConsentValidationService.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/validation/CitizenConsentValidationService.java @@ -1,7 +1,6 @@ package it.gov.pagopa.onboarding.citizen.validation; import it.gov.pagopa.onboarding.citizen.dto.CitizenConsentDTO; -import it.gov.pagopa.onboarding.citizen.dto.TppDTO; import it.gov.pagopa.onboarding.citizen.model.CitizenConsent; import reactor.core.publisher.Mono; @@ -11,5 +10,4 @@ public interface CitizenConsentValidationService { Mono validateTppAndSaveConsent(String fiscalCode, String tppId, CitizenConsent citizenConsent); - boolean isTppValid(TppDTO tppResponse); } diff --git a/src/main/java/it/gov/pagopa/onboarding/citizen/validation/CitizenConsentValidationServiceImpl.java b/src/main/java/it/gov/pagopa/onboarding/citizen/validation/CitizenConsentValidationServiceImpl.java index 8d04bf2..d2e8fd5 100644 --- a/src/main/java/it/gov/pagopa/onboarding/citizen/validation/CitizenConsentValidationServiceImpl.java +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/validation/CitizenConsentValidationServiceImpl.java @@ -3,9 +3,9 @@ import it.gov.pagopa.common.utils.Utils; import it.gov.pagopa.onboarding.citizen.configuration.ExceptionMap; import it.gov.pagopa.onboarding.citizen.connector.tpp.TppConnectorImpl; +import it.gov.pagopa.onboarding.citizen.constants.CitizenConstants; import it.gov.pagopa.onboarding.citizen.constants.CitizenConstants.ExceptionName; import it.gov.pagopa.onboarding.citizen.dto.CitizenConsentDTO; -import it.gov.pagopa.onboarding.citizen.dto.TppDTO; import it.gov.pagopa.onboarding.citizen.dto.mapper.CitizenConsentObjectToDTOMapper; import it.gov.pagopa.onboarding.citizen.model.CitizenConsent; import it.gov.pagopa.onboarding.citizen.repository.CitizenRepository; @@ -47,36 +47,30 @@ public Mono handleExistingConsent(CitizenConsent existingCons @Override public Mono validateTppAndSaveConsent(String fiscalCode, String tppId, CitizenConsent citizenConsent) { return tppConnector.get(tppId) + .onErrorMap(error -> exceptionMap.throwException(ExceptionName.TPP_NOT_FOUND, CitizenConstants.ExceptionMessage.TPP_NOT_FOUND)) .flatMap(tppResponse -> { - if (isTppValid(tppResponse)) { + if (Boolean.TRUE.equals(citizenConsent.getConsents().get(tppId).getTppState())) { return citizenRepository.save(citizenConsent) .doOnSuccess(savedConsent -> { - log.info("[EMD][CREATE-CITIZEN-CONSENT] Created new citizen consent for fiscal code: {}", Utils.createSHA256(fiscalCode)); + log.info("[EMD][CREATE-CITIZEN-CONSENT] Created new citizen consent for fiscal code: {}", Utils.createSHA256(fiscalCode)); bloomFilterService.add(fiscalCode); }) - .flatMap(savedConsent -> Mono.just(mapperToDTO.map(savedConsent))); + .map(mapperToDTO::map); } else { - return Mono.error(exceptionMap.throwException(ExceptionName.TPP_NOT_FOUND, "TPP does not exist or is not active")); + return Mono.error(exceptionMap.throwException(ExceptionName.TPP_NOT_FOUND, "TPP is not active or is invalid")); } }); } - @Override - public boolean isTppValid(TppDTO tppResponse) { - return tppResponse != null && Boolean.TRUE.equals(tppResponse.getState()); - } private Mono validateTppAndUpdateConsent(CitizenConsent existingConsent, String tppId, CitizenConsent citizenConsent) { return tppConnector.get(tppId) + .onErrorMap(error -> exceptionMap.throwException(ExceptionName.TPP_NOT_FOUND, CitizenConstants.ExceptionMessage.TPP_NOT_FOUND)) .flatMap(tppResponse -> { - if (isTppValid(tppResponse)) { existingConsent.getConsents().put(tppId, citizenConsent.getConsents().get(tppId)); return citizenRepository.save(existingConsent) .doOnSuccess(savedConsent -> log.info("[EMD][CREATE-CITIZEN-CONSENT] Updated citizen consent for TPP: {}", tppId)) .flatMap(savedConsent -> Mono.just(mapperToDTO.map(savedConsent))); - } else { - return Mono.error(exceptionMap.throwException(ExceptionName.TPP_NOT_FOUND, "TPP does not exist or is not active")); - } }); } } diff --git a/src/test/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceTest.java b/src/test/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceTest.java index 4dac064..21ffa7a 100644 --- a/src/test/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceTest.java +++ b/src/test/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceTest.java @@ -170,7 +170,6 @@ void updateChannelState_Ok() { when(citizenRepository.save(any())) .thenReturn(Mono.just(CITIZEN_CONSENT)); - when(validationService.isTppValid(mockTppDTO)).thenReturn(true); CitizenConsentDTO response = citizenService.updateTppState(FISCAL_CODE, TPP_ID, TPP_STATE).block(); @@ -191,7 +190,7 @@ void updateChannelState_Ko_CitizenNotOnboarded() { when(citizenRepository.findByFiscalCodeAndTppId(FISCAL_CODE, TPP_ID)) .thenReturn(Mono.empty()); - when(validationService.isTppValid(mockTppDTO)).thenReturn(true); + Executable executable = () -> citizenService.updateTppState(FISCAL_CODE, TPP_ID, true).block(); ClientExceptionWithBody exception = assertThrows(ClientExceptionWithBody.class, executable); @@ -217,7 +216,7 @@ void updateChannelState_Ok_ConsentDetailsIsNull() { when(citizenRepository.save(any())) .thenReturn(Mono.just(citizenConsentWithConsentDetailNull)); - when(validationService.isTppValid(mockTppDTO)).thenReturn(true); + Executable executable = () -> citizenService.updateTppState(FISCAL_CODE, TPP_ID, TPP_STATE).block(); ClientExceptionWithBody exception = assertThrows(ClientExceptionWithBody.class, executable); diff --git a/src/test/java/it/gov/pagopa/onboarding/citizen/validation/CitizenConsentValidationServiceImplTest.java b/src/test/java/it/gov/pagopa/onboarding/citizen/validation/CitizenConsentValidationServiceImplTest.java index c1985fd..e4b2af1 100644 --- a/src/test/java/it/gov/pagopa/onboarding/citizen/validation/CitizenConsentValidationServiceImplTest.java +++ b/src/test/java/it/gov/pagopa/onboarding/citizen/validation/CitizenConsentValidationServiceImplTest.java @@ -117,7 +117,7 @@ void validateTppAndSaveConsent_TppInvalid() { StepVerifier.create(validationService.validateTppAndSaveConsent(fiscalCode, tppId, CITIZEN_CONSENT)) .expectErrorMatches(throwable -> throwable instanceof ClientExceptionWithBody && - "TPP does not exist or is not active".equals(throwable.getMessage()) && + "TPP is not active or is invalid".equals(throwable.getMessage()) && "TPP_NOT_FOUND".equals(((ClientExceptionWithBody) throwable).getCode())) .verify(); @@ -126,21 +126,23 @@ void validateTppAndSaveConsent_TppInvalid() { } @Test - void isTppValid_TrueWhenActive() { - TppDTO activeTpp = TPP_DTO; - activeTpp.setState(true); - assertTrue(validationService.isTppValid(activeTpp)); - } + void validateTppAndSaveConsent_TppNotFound() { + String fiscalCode = CITIZEN_CONSENT.getFiscalCode(); + String tppId = "inactiveTppId"; - @Test - void isTppValid_FalseWhenInactive() { - TppDTO inactiveTpp = TPP_DTO; - inactiveTpp.setState(false); - assertFalse(validationService.isTppValid(inactiveTpp)); - } + TppDTO inactiveTppDTO = TPP_DTO; + inactiveTppDTO.setState(false); - @Test - void isTppValid_FalseWhenNull() { - assertFalse(validationService.isTppValid(null)); + when(tppConnector.get(anyString())).thenReturn(Mono.just(inactiveTppDTO)); + + StepVerifier.create(validationService.validateTppAndSaveConsent(fiscalCode, tppId, CITIZEN_CONSENT)) + .expectErrorMatches(throwable -> throwable instanceof ClientExceptionWithBody && + "TPP does not exist or is not active".equals(throwable.getMessage()) && + "TPP_NOT_FOUND".equals(((ClientExceptionWithBody) throwable).getCode())) + .verify(); + + verify(citizenRepository, never()).save(any()); + verify(bloomFilterService, never()).add(fiscalCode); } + } From 8792ca4e1e3a02be85c33ffb17f494ab281ff000 Mon Sep 17 00:00:00 2001 From: DanieleRanaldo Date: Wed, 13 Nov 2024 16:08:06 +0100 Subject: [PATCH 20/20] added and updated tests --- .../citizen/service/CitizenServiceTest.java | 51 -------------- ...tizenConsentValidationServiceImplTest.java | 67 +++++++++++++++++-- 2 files changed, 60 insertions(+), 58 deletions(-) diff --git a/src/test/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceTest.java b/src/test/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceTest.java index 21ffa7a..34961c8 100644 --- a/src/test/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceTest.java +++ b/src/test/java/it/gov/pagopa/onboarding/citizen/service/CitizenServiceTest.java @@ -198,57 +198,6 @@ void updateChannelState_Ko_CitizenNotOnboarded() { assertEquals("Citizen consent not founded during update state process", exception.getMessage()); } - @Test - void updateChannelState_Ok_ConsentDetailsIsNull() { - - TppDTO mockTppDTO = TppDTOFaker.mockInstance(); - mockTppDTO.setState(true); - - CitizenConsent citizenConsentWithConsentDetailNull = CitizenConsentFaker.mockInstance(true); - citizenConsentWithConsentDetailNull.getConsents().put(TPP_ID, null); - - when(tppConnector.get(anyString())) - .thenReturn(Mono.just(mockTppDTO)); - - when(citizenRepository.findByFiscalCodeAndTppId(FISCAL_CODE, TPP_ID)) - .thenReturn(Mono.just(citizenConsentWithConsentDetailNull)); - - when(citizenRepository.save(any())) - .thenReturn(Mono.just(citizenConsentWithConsentDetailNull)); - - - - Executable executable = () -> citizenService.updateTppState(FISCAL_CODE, TPP_ID, TPP_STATE).block(); - ClientExceptionWithBody exception = assertThrows(ClientExceptionWithBody.class, executable); - - assertEquals("ConsentDetails is null for this tppId", exception.getMessage()); - } - - @Test - void updateChannelState_Ko_TppResponseIsNull() { - - when(tppConnector.get(anyString())).thenReturn(Mono.empty()); - - Executable executable = () -> citizenService.updateTppState(FISCAL_CODE, TPP_ID, TPP_STATE).block(); - ClientExceptionWithBody exception = assertThrows(ClientExceptionWithBody.class, executable); - - assertEquals("TPP does not exist or is not active", exception.getMessage()); - } - - @Test - void updateChannelState_Ko_TppInactive() { - - TppDTO mockTppDTO = TppDTOFaker.mockInstance(); - mockTppDTO.setState(false); - - when(tppConnector.get(anyString())).thenReturn(Mono.just(mockTppDTO)); - - Executable executable = () -> citizenService.updateTppState(FISCAL_CODE, TPP_ID, TPP_STATE).block(); - ClientExceptionWithBody exception = assertThrows(ClientExceptionWithBody.class, executable); - - assertEquals("TPP does not exist or is not active", exception.getMessage()); - } - @Test void getConsentStatus_Ok() { diff --git a/src/test/java/it/gov/pagopa/onboarding/citizen/validation/CitizenConsentValidationServiceImplTest.java b/src/test/java/it/gov/pagopa/onboarding/citizen/validation/CitizenConsentValidationServiceImplTest.java index e4b2af1..48165b7 100644 --- a/src/test/java/it/gov/pagopa/onboarding/citizen/validation/CitizenConsentValidationServiceImplTest.java +++ b/src/test/java/it/gov/pagopa/onboarding/citizen/validation/CitizenConsentValidationServiceImplTest.java @@ -9,6 +9,7 @@ import it.gov.pagopa.onboarding.citizen.faker.CitizenConsentFaker; import it.gov.pagopa.onboarding.citizen.faker.TppDTOFaker; import it.gov.pagopa.onboarding.citizen.model.CitizenConsent; +import it.gov.pagopa.onboarding.citizen.model.ConsentDetails; import it.gov.pagopa.onboarding.citizen.repository.CitizenRepository; import it.gov.pagopa.onboarding.citizen.service.BloomFilterServiceImpl; import org.junit.jupiter.api.Test; @@ -47,6 +48,7 @@ class CitizenConsentValidationServiceImplTest { @Test void handleExistingConsent_ConsentAlreadyExists() { + CitizenConsent existingConsent = CITIZEN_CONSENT; String tppId = "existingTppId"; existingConsent.getConsents().put(tppId, CITIZEN_CONSENT.getConsents().get(tppId)); @@ -65,6 +67,7 @@ void handleExistingConsent_ConsentAlreadyExists() { @Test void handleExistingConsent_NewConsentForTpp() { + CitizenConsent existingConsent = CITIZEN_CONSENT; TppDTO activeTppDTO = TPP_DTO; activeTppDTO.setState(true); @@ -85,10 +88,16 @@ void handleExistingConsent_NewConsentForTpp() { @Test void validateTppAndSaveConsent_TppValidAndActive() { - String fiscalCode = CITIZEN_CONSENT.getFiscalCode(); + + CitizenConsent citizenConsentWithValidConsent = CITIZEN_CONSENT; + String fiscalCode = citizenConsentWithValidConsent.getFiscalCode(); TppDTO activeTppDTO = TPP_DTO; activeTppDTO.setState(true); + ConsentDetails consentDetails = new ConsentDetails(); + consentDetails.setTppState(true); + + citizenConsentWithValidConsent.getConsents().put(activeTppDTO.getTppId(), consentDetails); when(tppConnector.get(anyString())).thenReturn(Mono.just(activeTppDTO)); when(citizenRepository.save(CITIZEN_CONSENT)).thenReturn(Mono.just(CITIZEN_CONSENT)); @@ -107,15 +116,21 @@ void validateTppAndSaveConsent_TppValidAndActive() { @Test void validateTppAndSaveConsent_TppInvalid() { - String fiscalCode = CITIZEN_CONSENT.getFiscalCode(); - String tppId = "inactiveTppId"; + + CitizenConsent citizenConsentWithInvalidConsent = CITIZEN_CONSENT; + String fiscalCode = citizenConsentWithInvalidConsent.getFiscalCode(); TppDTO inactiveTppDTO = TPP_DTO; inactiveTppDTO.setState(false); + ConsentDetails consentDetails = new ConsentDetails(); + consentDetails.setTppState(false); + + citizenConsentWithInvalidConsent.getConsents().put(inactiveTppDTO.getTppId(), consentDetails); + when(tppConnector.get(anyString())).thenReturn(Mono.just(inactiveTppDTO)); - StepVerifier.create(validationService.validateTppAndSaveConsent(fiscalCode, tppId, CITIZEN_CONSENT)) + StepVerifier.create(validationService.validateTppAndSaveConsent(fiscalCode, inactiveTppDTO.getTppId(), CITIZEN_CONSENT)) .expectErrorMatches(throwable -> throwable instanceof ClientExceptionWithBody && "TPP is not active or is invalid".equals(throwable.getMessage()) && "TPP_NOT_FOUND".equals(((ClientExceptionWithBody) throwable).getCode())) @@ -127,17 +142,39 @@ void validateTppAndSaveConsent_TppInvalid() { @Test void validateTppAndSaveConsent_TppNotFound() { + String fiscalCode = CITIZEN_CONSENT.getFiscalCode(); - String tppId = "inactiveTppId"; + String tppId = "nonExistentTppId"; + when(tppConnector.get(tppId)).thenReturn(Mono.error(new RuntimeException("TPP not found"))); + + StepVerifier.create(validationService.validateTppAndSaveConsent(fiscalCode, tppId, CITIZEN_CONSENT)) + .expectErrorMatches(throwable -> throwable instanceof ClientExceptionWithBody && + "TPP does not exist or is not active".equals(throwable.getMessage()) && + "TPP_NOT_FOUND".equals(((ClientExceptionWithBody) throwable).getCode())) + .verify(); + + verify(citizenRepository, never()).save(any()); + verify(bloomFilterService, never()).add(fiscalCode); + } + + @Test + void validateTppAndSaveConsent_TppInactive() { + + String fiscalCode = CITIZEN_CONSENT.getFiscalCode(); TppDTO inactiveTppDTO = TPP_DTO; inactiveTppDTO.setState(false); + ConsentDetails consentDetails = new ConsentDetails(); + consentDetails.setTppState(false); + + CITIZEN_CONSENT.getConsents().put(inactiveTppDTO.getTppId(), consentDetails); + when(tppConnector.get(anyString())).thenReturn(Mono.just(inactiveTppDTO)); - StepVerifier.create(validationService.validateTppAndSaveConsent(fiscalCode, tppId, CITIZEN_CONSENT)) + StepVerifier.create(validationService.validateTppAndSaveConsent(fiscalCode, inactiveTppDTO.getTppId(), CITIZEN_CONSENT)) .expectErrorMatches(throwable -> throwable instanceof ClientExceptionWithBody && - "TPP does not exist or is not active".equals(throwable.getMessage()) && + "TPP is not active or is invalid".equals(throwable.getMessage()) && "TPP_NOT_FOUND".equals(((ClientExceptionWithBody) throwable).getCode())) .verify(); @@ -145,4 +182,20 @@ void validateTppAndSaveConsent_TppNotFound() { verify(bloomFilterService, never()).add(fiscalCode); } + @Test + void handleExistingConsent_TppNotFound() { + + String tppId = "nonExistentTppId"; + + when(tppConnector.get(anyString())).thenReturn(Mono.error(new RuntimeException("TPP not found"))); + + StepVerifier.create(validationService.handleExistingConsent(CITIZEN_CONSENT, tppId, CITIZEN_CONSENT)) + .expectErrorMatches(throwable -> throwable instanceof ClientExceptionWithBody && + "TPP does not exist or is not active".equals(throwable.getMessage()) && + "TPP_NOT_FOUND".equals(((ClientExceptionWithBody) throwable).getCode())) + .verify(); + + verify(citizenRepository, never()).save(any()); + verify(bloomFilterService, never()).add(anyString()); + } }