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());
+ }
+}