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/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/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..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 @@ -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() {} } @@ -12,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() {} } @@ -19,9 +23,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..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 @@ -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>> 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. @@ -51,6 +55,10 @@ public interface CitizenController { * @return a list of all channels with their consent statuses */ @GetMapping("/list/{fiscalCode}") - Mono>> getCitizenConsents(@PathVariable String 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 668325d..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 @@ -1,8 +1,11 @@ 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.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; @@ -12,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; } @@ -25,11 +31,11 @@ public Mono> saveCitizenConsent(@Valid Citizen } @Override - public Mono> stateUpdate(@Valid CitizenConsentDTO citizenConsentDTO) { - return citizenService.updateChannelState( - citizenConsentDTO.getHashedFiscalCode(), //at this stage the fiscalCode has not yet been hashed - citizenConsentDTO.getTppId(), - citizenConsentDTO.getTppState()) + public Mono> stateUpdate(@Valid CitizenConsentStateUpdateDTO citizenConsentStateUpdateDTO) { + return citizenService.updateTppState( + citizenConsentStateUpdateDTO.getFiscalCode(), + citizenConsentStateUpdateDTO.getTppId(), + citizenConsentStateUpdateDTO.getTppState()) .map(ResponseEntity::ok); } @@ -40,14 +46,25 @@ 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); } + + @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/dto/CitizenConsentDTO.java b/src/main/java/it/gov/pagopa/onboarding/citizen/dto/CitizenConsentDTO.java index c35fec5..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,12 +1,17 @@ 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; import lombok.experimental.SuperBuilder; import java.time.LocalDateTime; +import java.util.Map; + +import static it.gov.pagopa.onboarding.citizen.constants.CitizenConstants.ValidationRegex.FISCAL_CODE_STRUCTURE_REGEX; @Data @SuperBuilder @@ -14,9 +19,18 @@ @NoArgsConstructor public class CitizenConsentDTO { @JsonAlias("fiscalCode") - private String hashedFiscalCode; - private String tppId; - private Boolean tppState; - private LocalDateTime creationDate; - private LocalDateTime lastUpdateDate; + @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; + + @Data + @SuperBuilder + @NoArgsConstructor + @AllArgsConstructor + public static class ConsentDTO { + private Boolean tppState; + 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 new file mode 100644 index 0000000..edb0f7a --- /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 fiscalCode; + 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..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 @@ -1,20 +1,27 @@ 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() + .tppState(consentDetails.getTppState()) + .tcDate(consentDetails.getTcDate()) + .build())); + return CitizenConsentDTO.builder() - .hashedFiscalCode(citizenConsent.getHashedFiscalCode()) - .tppState(citizenConsent.getTppState()) - .tppId(citizenConsent.getTppId()) - .creationDate(citizenConsent.getCreationDate()) - .lastUpdateDate(citizenConsent.getLastUpdateDate()) + .fiscalCode(citizenConsent.getFiscalCode()) + .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..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 @@ -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 @@ -14,10 +14,8 @@ public class CitizenConsent { private String id; - private String hashedFiscalCode; - private String tppId; - private Boolean tppState; - private LocalDateTime creationDate; - private LocalDateTime lastUpdateDate; + 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 new file mode 100644 index 0000000..bc91264 --- /dev/null +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/model/ConsentDetails.java @@ -0,0 +1,15 @@ +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 tppState; + 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 96caf6a..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 @@ -1,18 +1,27 @@ 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() + .tppState(consentDTO.getTppState()) + .tcDate(consentDTO.getTcDate()) + .build())); + return CitizenConsent.builder() - .tppState(true) - .tppId(citizenConsentDTO.getTppId()) - .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 103dc2f..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 @@ -2,15 +2,11 @@ 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); + Mono findByFiscalCode(String fiscalCode); - Flux findByHashedFiscalCodeAndTppStateTrue(String hashedFiscalCode); - - 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..2fccd8f --- /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.Mono; + + +public interface CitizenSpecificRepository { + + 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 new file mode 100644 index 0000000..8d57830 --- /dev/null +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImpl.java @@ -0,0 +1,39 @@ +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.query.Criteria; +import org.springframework.stereotype.Repository; +import reactor.core.publisher.Mono; + +@Repository +public class CitizenSpecificRepositoryImpl implements CitizenSpecificRepository { + + private final ReactiveMongoTemplate mongoTemplate; + + public CitizenSpecificRepositoryImpl(ReactiveMongoTemplate mongoTemplate) { + this.mongoTemplate = mongoTemplate; + } + + + public Mono findByFiscalCodeAndTppId(String fiscalCode, String tppId) { + if (tppId == null) { + return Mono.empty(); + } + + Aggregation aggregation = Aggregation.newAggregation( + Aggregation.match(Criteria.where("fiscalCode").is(fiscalCode)), + Aggregation.match(Criteria.where("consents." + tppId).exists(true)) + ); + + return mongoTemplate.aggregate(aggregation, CitizenConsent.class, CitizenConsent.class) + .next(); + } + @Data + public static class ConsentKeyWrapper { + private String k; + } + +} 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..efef5f1 --- /dev/null +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/service/BloomFilterServiceImpl.java @@ -0,0 +1,52 @@ +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.findAll() + .doOnNext(citizenConsent -> + bloomFilter.put(citizenConsent.getFiscalCode())) + .doOnComplete(() -> log.info("Bloom filter initialized")) + .subscribe(); + } + @Override + + public boolean mightContain(String fiscalCode) { + return bloomFilter.mightContain(fiscalCode); + } + + + @Scheduled(fixedRate = 3600000) + public void update() { + this.initializeBloomFilter(); + } + + public void add(String fiscalCode){ + bloomFilter.put(fiscalCode); + } +} 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..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,8 +8,8 @@ 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> 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 ddcc279..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 @@ -2,18 +2,23 @@ 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; 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 lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import reactor.core.publisher.Mono; import java.time.LocalDateTime; import java.util.List; +import java.util.Map; import static it.gov.pagopa.common.utils.Utils.inputSanify; @@ -25,86 +30,86 @@ public class CitizenServiceImpl implements CitizenService { private final CitizenConsentObjectToDTOMapper mapperToDTO; 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) { + 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 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()) - .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))) - ); + String fiscalCode = citizenConsent.getFiscalCode(); + + citizenConsent.getConsents().forEach((tppId, consentDetails) -> consentDetails.setTcDate(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, ExceptionMessage.TPP_NOT_FOUND)); + } + + 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) { - 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 -> { - citizenConsent.setTppState(tppState); - citizenConsent.setLastUpdateDate(LocalDateTime.now()); - return citizenRepository.save(citizenConsent); - }) - .map(mapperToDTO::map) - .doOnSuccess(savedConsent -> log.info("[EMD][[CITIZEN][UPDATE-CHANNEL-STATE] Updated state")); + 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); + + return tppConnector.get(tppId) + .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 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 "))) + (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); - 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()))); + public Mono> getTppEnabledList(String fiscalCode) { + log.info("[EMD-CITIZEN][FIND-CITIZEN-CONSENTS-ENABLED] Received hashedFiscalCode: {}", Utils.createSHA256(fiscalCode)); + 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()))); } @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) - .collectList() - .map(consentList -> consentList.stream() - .map(mapperToDTO::map) - .toList() - ) - .doOnSuccess(consentList -> log.info("[EMD-CITIZEN][FIND-ALL-CITIZEN-CONSENTS] Consents found:: {}",consentList)); + public Mono get(String fiscalCode) { + log.info("[EMD-CITIZEN][FIND-ALL-CITIZEN-CONSENTS] Received hashedFiscalCode: {}", (Utils.createSHA256(fiscalCode))); + return citizenRepository.findByFiscalCode(fiscalCode) + .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..8cbabed --- /dev/null +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/validation/CitizenConsentValidationService.java @@ -0,0 +1,13 @@ +package it.gov.pagopa.onboarding.citizen.validation; + +import it.gov.pagopa.onboarding.citizen.dto.CitizenConsentDTO; +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); + +} 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..d2e8fd5 --- /dev/null +++ b/src/main/java/it/gov/pagopa/onboarding/citizen/validation/CitizenConsentValidationServiceImpl.java @@ -0,0 +1,76 @@ +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; +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.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; + +@Service +@Slf4j +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, BloomFilterServiceImpl bloomFilterService, TppConnectorImpl tppConnector, + CitizenConsentObjectToDTOMapper mapperToDTO, ExceptionMap exceptionMap) { + this.citizenRepository = citizenRepository; + this.bloomFilterService = bloomFilterService; + 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) + .onErrorMap(error -> exceptionMap.throwException(ExceptionName.TPP_NOT_FOUND, CitizenConstants.ExceptionMessage.TPP_NOT_FOUND)) + .flatMap(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)); + bloomFilterService.add(fiscalCode); + }) + .map(mapperToDTO::map); + } else { + return Mono.error(exceptionMap.throwException(ExceptionName.TPP_NOT_FOUND, "TPP is not active or is invalid")); + } + }); + } + + + 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 -> { + 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))); + }); + } +} 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 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 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 f49c259..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 @@ -1,7 +1,10 @@ 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.BloomFilterServiceImpl; import it.gov.pagopa.onboarding.citizen.service.CitizenServiceImpl; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -9,6 +12,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; @@ -21,12 +25,15 @@ class CitizenControllerTest { @MockBean private CitizenServiceImpl citizenService; + @MockBean + private BloomFilterServiceImpl bloomFilterService; + @Autowired private WebTestClient webClient; - private static final String FISCAL_CODE = "fiscalCode"; + private static final String FISCAL_CODE = "MLXHZZ43A70H203T"; private static final String TPP_ID = "tppId"; @@ -52,25 +59,27 @@ void saveCitizenConsent_Ok() { @Test void stateUpdate_Ok() { - CitizenConsentDTO citizenConsentDTO = CitizenConsentDTOFaker.mockInstance(true); + CitizenConsentStateUpdateDTO citizenConsentStateUpdateDTO = CitizenConsentStateUpdateDTOFaker.mockInstance(true); - Mockito.when(citizenService.updateChannelState( - citizenConsentDTO.getHashedFiscalCode(), - citizenConsentDTO.getTppId(), - citizenConsentDTO.getTppState())) - .thenReturn(Mono.just(citizenConsentDTO)); + CitizenConsentDTO expectedResponseDTO = CitizenConsentDTOFaker.mockInstance(true); + + Mockito.when(citizenService.updateTppState( + citizenConsentStateUpdateDTO.getFiscalCode(), + 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); }); } @@ -95,40 +104,73 @@ void getConsentStatus_Ok() { } @Test - void getCitizenConsentsEnabled_Ok() { - List citizenConsentDTOList = List.of(CitizenConsentDTOFaker.mockInstance(true)); + void getTppEnabledList_Ok() { + List tppEnabledList = List.of("TPP1", "TPP2"); - Mockito.when(citizenService.getListEnabledConsents(FISCAL_CODE)) - .thenReturn(Mono.just(citizenConsentDTOList)); + Mockito.when(citizenService.getTppEnabledList(FISCAL_CODE)) + .thenReturn(Mono.just(tppEnabledList)); 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(tppEnabledList.size(), resultResponse.size()); }); } @Test - void getCitizenConsents_Ok() { - List citizenConsentDTOList = List.of(CitizenConsentDTOFaker.mockInstance(true)); + void get_Ok() { + 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 -> { + CitizenConsentDTO resultResponse = response.getResponseBody(); + Assertions.assertNotNull(resultResponse); + 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 -> { - List resultResponse = response.getResponseBody(); + String resultResponse = response.getResponseBody(); Assertions.assertNotNull(resultResponse); - Assertions.assertEquals(citizenConsentDTOList.size(), resultResponse.size()); + Assertions.assertEquals("NO CHANNELS ENABLED", 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..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 @@ -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, LocalDateTime.now())); + return CitizenConsentDTO.builder() - .tppId("tppId") - .tppState(bias) - .hashedFiscalCode("hashedFiscalCode") - .creationDate(LocalDateTime.now()) - .lastUpdateDate(LocalDateTime.now()) + .fiscalCode("MLXHZZ43A70H203T") + .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..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 @@ -1,20 +1,29 @@ 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() .tppState(bias) - .hashedFiscalCode("hashedFiscalCode") - .creationDate(LocalDateTime.now()) - .lastUpdateDate(LocalDateTime.now()) + .tcDate(LocalDateTime.now()) + .build(); + + consents.put("tppId", consentDetails); + + return CitizenConsent.builder() + .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 new file mode 100644 index 0000000..31464f6 --- /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() + .fiscalCode("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..0acc9cf --- /dev/null +++ b/src/test/java/it/gov/pagopa/onboarding/citizen/repository/CitizenSpecificRepositoryImplTest.java @@ -0,0 +1,110 @@ +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 java.util.Objects; + +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 testFindByFiscalCodeAndTppId() { + String hashedFiscalCode = "hashedCode"; + String tppId = "tpp1"; + CitizenConsent citizenConsent = new CitizenConsent(); + citizenConsent.setId("1"); + citizenConsent.setFiscalCode(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.findByFiscalCodeAndTppId(hashedFiscalCode, tppId); + + Assertions.assertEquals(hashedFiscalCode, Objects.requireNonNull(result.block()).getFiscalCode()); + Mockito.verify(mongoTemplate).aggregate(Mockito.any(), Mockito.eq(CitizenConsent.class), Mockito.eq(CitizenConsent.class)); + } + + @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/BloomFilterServiceTest.java b/src/test/java/it/gov/pagopa/onboarding/citizen/service/BloomFilterServiceTest.java new file mode 100644 index 0000000..9516cb4 --- /dev/null +++ b/src/test/java/it/gov/pagopa/onboarding/citizen/service/BloomFilterServiceTest.java @@ -0,0 +1,52 @@ +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; +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.findAll()).thenReturn(Flux.just(CitizenConsentFaker.mockInstance(true))); + bloomFilterService.initializeBloomFilter(); + } + + @Test + void testMightContain() { + assertTrue(bloomFilterService.mightContain("fiscalCode")); + assertFalse(bloomFilterService.mightContain("nonExistentFiscalCode")); + } + + @Test + void testUpdate() { + when(citizenRepository.findAll()).thenReturn(Flux.just(CitizenConsentFaker.mockInstance(true))); + bloomFilterService.update(); + assertTrue(bloomFilterService.mightContain("fiscalCode")); + assertFalse(bloomFilterService.mightContain("nonexistentHashedFiscalCode")); + } + + @Test + void testAdd() { + bloomFilterService.add("fiscalCode3"); + assertTrue(bloomFilterService.mightContain("fiscalCode3")); + } +} 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..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 @@ -1,38 +1,46 @@ 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.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.common.web.exception.EmdEncryptionException; -import it.gov.pagopa.onboarding.citizen.configuration.ExceptionMap; 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.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.Flux; 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,86 +50,149 @@ class CitizenServiceTest { @Autowired CitizenServiceImpl citizenService; + @MockBean + CitizenConsentValidationServiceImpl validationService; + @MockBean CitizenRepository citizenRepository; + @MockBean + TppConnectorImpl tppConnector; + @Autowired 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); - - Mockito.when(citizenRepository.save(Mockito.any())) - .thenReturn(Mono.just(CITIZEN_CONSENT)); + CitizenConsentDTO expectedConsentDTO = dtoMapper.map(CITIZEN_CONSENT); + TppDTO activeTppDTO = TPP_DTO; + activeTppDTO.setState(true); - Mockito.when(citizenRepository.findById(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); + CitizenConsentDTO expectedConsentDTO = dtoMapper.map(CITIZEN_CONSENT); + TppDTO activeTppDTO = TPP_DTO; + activeTppDTO.setState(true); - Mockito.when(citizenRepository.save(Mockito.any())) - .thenReturn(Mono.empty()); + when(tppConnector.get(anyString())).thenReturn(Mono.just(activeTppDTO)); - Mockito.when(citizenRepository.findById(anyString())) - .thenReturn(Mono.just(CITIZEN_CONSENT)); + 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)); + + when(validationService.handleExistingConsent(any(), anyString(), any())) + .thenReturn(Mono.just(expectedConsentDTO)); + + when(validationService.validateTppAndSaveConsent(anyString(), anyString(), any(CitizenConsent.class))) + .thenReturn(Mono.just(expectedConsentDTO)); CitizenConsentDTO response = citizenService.createCitizenConsent(CITIZEN_CONSENT_DTO).block(); + assertNotNull(response); + assertEquals(expectedConsentDTO, response); + } + + @Test + void createCitizenConsent_Ko_TppNotProvided() { + + CitizenConsentDTO incompleteConsentDTO = CitizenConsentDTOFaker.mockInstance(true); + incompleteConsentDTO.getConsents().clear(); + + when(tppConnector.get(anyString())).thenReturn(Mono.empty()); + when(citizenRepository.findByFiscalCodeAndTppId(anyString(), 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"))); - assertEquals(citizenConsentDTO, response); + 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_EmdEncryptError() { - CitizenConsentDTO citizenConsentDTO = CitizenConsentDTOFaker.mockInstance(true); + void createCitizenConsent_Ko_TppInactive() { - try (MockedStatic mockedStatic = Mockito.mockStatic(Utils.class)) { - mockedStatic.when(() -> Utils.createSHA256(any())) - .thenThrow(EmdEncryptionException.class); + TppDTO inactiveTppDTO = TPP_DTO; + inactiveTppDTO.setState(false); - assertThrows(EmdEncryptionException.class, () -> citizenService.createCitizenConsent(citizenConsentDTO)); - } + 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); + + 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(); + + Mockito.verify(citizenRepository, Mockito.never()).save(Mockito.any()); } @Test void updateChannelState_Ok() { + TppDTO mockTppDTO = TppDTOFaker.mockInstance(); + mockTppDTO.setState(true); + + when(tppConnector.get(anyString())) + .thenReturn(Mono.just(mockTppDTO)); - Mockito.when(citizenRepository.findByHashedFiscalCodeAndTppId(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)); - CitizenConsentDTO response = citizenService.updateChannelState(FISCAL_CODE, TPP_ID, TPP_STATE).block(); + + CitizenConsentDTO response = citizenService.updateTppState(FISCAL_CODE, TPP_ID, TPP_STATE).block(); + assertNotNull(response); - assertEquals(TPP_STATE, response.getTppState()); + + assertEquals(TPP_STATE, response.getConsents().get(TPP_ID).getTppState()); } @Test void updateChannelState_Ko_CitizenNotOnboarded() { + TppDTO mockTppDTO = TppDTOFaker.mockInstance(); + mockTppDTO.setState(true); + + when(tppConnector.get(anyString())) + .thenReturn(Mono.just(mockTppDTO)); - Mockito.when(citizenRepository.findByHashedFiscalCodeAndTppId(HASHED_FISCAL_CODE, TPP_ID)) + when(citizenRepository.findByFiscalCodeAndTppId(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()); @@ -131,7 +202,7 @@ void updateChannelState_Ko_CitizenNotOnboarded() { void getConsentStatus_Ok() { - Mockito.when(citizenRepository.findByHashedFiscalCodeAndTppId(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(); @@ -141,7 +212,7 @@ void getConsentStatus_Ok() { @Test void getConsentStatus_Ko_CitizenNotOnboarded() { - Mockito.when(citizenRepository.findByHashedFiscalCodeAndTppId(HASHED_FISCAL_CODE, TPP_ID)) + when(citizenRepository.findByFiscalCodeAndTppId(FISCAL_CODE, TPP_ID)) .thenReturn(Mono.empty()); Executable executable = () -> citizenService.getConsentStatus(FISCAL_CODE, TPP_ID).block(); @@ -151,24 +222,45 @@ void getConsentStatus_Ko_CitizenNotOnboarded() { } @Test - void getListEnabledConsents_Ok() { + void testGetTppEnabledList_Success() { + Map consents = new HashMap<>(); - Mockito.when(citizenRepository.findByHashedFiscalCodeAndTppStateTrue(HASHED_FISCAL_CODE)) - .thenReturn(Flux.just(CITIZEN_CONSENT)); + consents.put("Tpp1", ConsentDetails.builder() + .tppState(true) + .tcDate(LocalDateTime.now()) + .build()); - List response = citizenService.getListEnabledConsents(FISCAL_CODE).block(); - assertNotNull(response); - assertEquals(1, response.size()); + 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.findByHashedFiscalCode(HASHED_FISCAL_CODE)) - .thenReturn(Flux.just(CITIZEN_CONSENT)); + void get_Ok() { + + when(citizenRepository.findByFiscalCode(FISCAL_CODE)) + .thenReturn(Mono.just(CITIZEN_CONSENT)); + + CitizenConsentDTO response = citizenService.get(FISCAL_CODE).block(); - List response = citizenService.getListAllConsents(FISCAL_CODE).block(); assertNotNull(response); - assertEquals(1, response.size()); + + 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..48165b7 --- /dev/null +++ b/src/test/java/it/gov/pagopa/onboarding/citizen/validation/CitizenConsentValidationServiceImplTest.java @@ -0,0 +1,201 @@ +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.model.ConsentDetails; +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() { + + 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)); + 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() { + + 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, 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())) + .verify(); + + verify(citizenRepository, never()).save(any()); + verify(bloomFilterService, never()).add(fiscalCode); + } + + @Test + void validateTppAndSaveConsent_TppNotFound() { + + String fiscalCode = CITIZEN_CONSENT.getFiscalCode(); + 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, 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())) + .verify(); + + verify(citizenRepository, never()).save(any()); + 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()); + } +}