From 9f7cd250c88ce323fd44e800053e041452e016e9 Mon Sep 17 00:00:00 2001 From: Luca Consalvi <117908483+lucaconsalvi@users.noreply.github.com> Date: Fri, 29 Nov 2024 09:57:45 +0100 Subject: [PATCH] feat: [SRTP-81] setup controller and model generation (#20) --- build.gradle | 23 ++++-- openapi/activation.openapi.yaml | 45 ++++++++--- .../activator/RtpActivatorApplication.java | 5 +- .../ActivationPropertiesConfig.java | 17 ++++ .../ActivationAPIControllerImpl.java | 35 ++++++-- .../pagopa/rtp/activator/domain/Payer.java | 6 ++ .../pagopa/rtp/activator/domain/PayerID.java | 21 +++++ .../rtp/activator/domain/PayerRepository.java | 13 +++ .../domain/errors/PayerAlreadyExists.java | 5 ++ .../activator/repository/ActivationDB.java | 13 +++ .../repository/ActivationDBRepository.java | 33 ++++++++ .../repository/ActivationEntity.java | 25 ++++++ .../repository/ActivationMapper.java | 28 +++++++ .../service/ActivationPayerService.java | 8 ++ .../service/ActivationPayerServiceImpl.java | 32 ++++++++ src/main/resources/application.properties | 5 ++ src/main/resources/custom.security | 2 +- src/main/terraform/container_app.tf | 5 ++ src/main/terraform/data.tf | 2 + .../env/cstar-d-weu-rtp/terraform.tfvars | 15 ++-- .../env/cstar-p-weu-rtp/terraform.tfvars | 13 +-- .../env/cstar-u-weu-rtp/terraform.tfvars | 15 ++-- src/main/terraform/variables.tf | 2 +- .../RtpActivatorApplicationTest.java | 17 ++++ .../RtpActivatorApplicationTests.java | 13 --- .../ActivationPropertiesConfigTest.java | 26 ++++++ .../ActivationAPIControllerImplTest.java | 80 +++++++++++++++---- .../rtp/activator/domain/PayerIDTest.java | 22 +++++ .../repository/ActivationMapperTest.java | 47 +++++++++++ .../ActivationPayerServiceImplTest.java | 63 +++++++++++++++ .../gov/pagopa/rtp/activator/utils/Users.java | 2 +- src/test/resources/application.properties | 5 ++ 32 files changed, 570 insertions(+), 73 deletions(-) create mode 100644 src/main/java/it/gov/pagopa/rtp/activator/configuration/ActivationPropertiesConfig.java create mode 100644 src/main/java/it/gov/pagopa/rtp/activator/domain/Payer.java create mode 100644 src/main/java/it/gov/pagopa/rtp/activator/domain/PayerID.java create mode 100644 src/main/java/it/gov/pagopa/rtp/activator/domain/PayerRepository.java create mode 100644 src/main/java/it/gov/pagopa/rtp/activator/domain/errors/PayerAlreadyExists.java create mode 100644 src/main/java/it/gov/pagopa/rtp/activator/repository/ActivationDB.java create mode 100644 src/main/java/it/gov/pagopa/rtp/activator/repository/ActivationDBRepository.java create mode 100644 src/main/java/it/gov/pagopa/rtp/activator/repository/ActivationEntity.java create mode 100644 src/main/java/it/gov/pagopa/rtp/activator/repository/ActivationMapper.java create mode 100644 src/main/java/it/gov/pagopa/rtp/activator/service/ActivationPayerService.java create mode 100644 src/main/java/it/gov/pagopa/rtp/activator/service/ActivationPayerServiceImpl.java create mode 100644 src/test/java/it/gov/pagopa/rtp/activator/RtpActivatorApplicationTest.java delete mode 100644 src/test/java/it/gov/pagopa/rtp/activator/RtpActivatorApplicationTests.java create mode 100644 src/test/java/it/gov/pagopa/rtp/activator/configuration/ActivationPropertiesConfigTest.java create mode 100644 src/test/java/it/gov/pagopa/rtp/activator/domain/PayerIDTest.java create mode 100644 src/test/java/it/gov/pagopa/rtp/activator/repository/ActivationMapperTest.java create mode 100644 src/test/java/it/gov/pagopa/rtp/activator/service/ActivationPayerServiceImplTest.java diff --git a/build.gradle b/build.gradle index aac0f38..7748935 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ plugins { id 'org.springframework.boot' version '3.3.5' id 'io.spring.dependency-management' version '1.1.6' id 'org.graalvm.buildtools.native' version '0.10.3' - id("org.openapi.generator") version "7.5.0" + id("org.openapi.generator") version "7.10.0" id "org.sonarqube" version "6.0.0.5145" id 'jacoco' } @@ -29,16 +29,14 @@ ext { dependencies { implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'org.springframework.boot:spring-boot-starter-webflux' + implementation 'org.springframework.boot:spring-boot-starter-data-mongodb-reactive' - // spring security + oauth2 resource server +// spring security + oauth2 resource server implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server' implementation 'org.springframework.security:spring-security-oauth2-jose' - implementation("org.springframework.boot:spring-boot-starter-actuator") - // implementation 'com.azure.spring:spring-cloud-azure-starter-actuator' -// implementation 'com.azure.spring:spring-cloud-azure-starter-data-cosmos' implementation("io.swagger.core.v3:swagger-annotations:2.2.8") implementation("org.openapitools:jackson-databind-nullable:0.2.6") implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310") @@ -53,6 +51,8 @@ dependencies { testImplementation 'org.springframework.security:spring-security-test' testImplementation 'io.projectreactor:reactor-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + annotationProcessor("org.projectlombok:lombok") + compileOnly("org.projectlombok:lombok") } dependencyManagement { @@ -107,6 +107,7 @@ openApiGenerate { ]) } + graalvmNative { binaries { main { @@ -116,6 +117,17 @@ graalvmNative { } } +bootRun { + jvmArgs = [ + '-Djava.security.properties=src/main/resources/custom.security' + ] +} + +tasks.withType(JavaExec) { + jvmArgs += '-Djava.security.properties=src/main/resources/custom.security' +} + + test { finalizedBy jacocoTestReport // report is always generated after tests run } @@ -134,3 +146,4 @@ sonarqube { property 'sonar.projectKey', 'pagopa_rtp-activator' } } + diff --git a/openapi/activation.openapi.yaml b/openapi/activation.openapi.yaml index 403e49e..ec3be9c 100644 --- a/openapi/activation.openapi.yaml +++ b/openapi/activation.openapi.yaml @@ -450,6 +450,14 @@ components: # -------------------------------------------------------------------------- # Domain specific basic types. # -------------------------------------------------------------------------- + Bic: + description: Bank Identification Code. + type: string + pattern: "^[A-Z0-9]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3}){0,1}$" + minLength: 8 + maxLength: 11 + example: "UNCRITMM" + EffectiveActivationDate: description: | Effective activation date (B035). @@ -461,7 +469,7 @@ components: FiscalCode: description: | - Fiscal (or tax) code. + Fiscal code. It is used as identifier of the Payer (P009) and of the Payee (E005). type: string @@ -470,17 +478,28 @@ components: maxLength: 16 example: "RSSMRA85T10A562S" + Lei: + description: | + Legal Entity Identifier is a code allocated to a party as described in + ISO 17442 "Financial Services - Legal Entity Identifier (LEI)". + type: string + pattern: "^[A-Z0-9]{18}[0-9]{2}$" + minLength: 20 + maxLength: 20 + example: "984500A9EB6B07AC2G71" + PartyId: description: | Unique and unambiguous identification of a party. - - It is used as identifier of the Payer’s RTP Service Provider (N001) and - for Payee’s RTP Service Provider (N002). + + It is used as identifier of the Payer's RTP Service Provider (N001) and + for Payee's RTP Service Provider (N002). + + A Service Provider is identified by its BIC (Bank Identification Code) + if it is a PSP, otherwise it is identified by its LEI (Legal Entity + Identifier). type: string - pattern: "^[ -~]{1,35}$" - minLength: 1 - maxLength: 35 - example: "12345678901" + pattern: "(^([A-Z0-9]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3}){0,1})$)|(^[A-Z0-9]{18}[0-9]{2}$)" # -------------------------------------------------------------------------- # Complex types for paging. @@ -566,7 +585,7 @@ components: effectiveActivationDate: "2024-10-30T16:39:34+01:00" payer: fiscalCode: "RSSMRA85T10A562S" - rtpSpId: "10987654321" + rtpSpId: "984500A9EB6B07AC2G71" Activations: description: List of RTP activations. @@ -580,7 +599,7 @@ components: effectiveActivationDate: "2024-10-30T16:39:34+01:00" payer: fiscalCode: "RSSMRA85T10A562S" - rtpSpId: "10987654321" + rtpSpId: "984500A9EB6B07AC2G71" ActivationReq: description: | @@ -595,7 +614,7 @@ components: example: payer: fiscalCode: "RSSMRA85T10A562S" - rtpSpId: "10987654321" + rtpSpId: "984500A9EB6B07AC2G71" PageOfActivations: description: Page of RTP activations. @@ -615,7 +634,7 @@ components: effectiveActivationDate: "2024-10-30T16:39:34+01:00" payer: fiscalCode: "RSSMRA85T10A562S" - rtpSpId: "10987654321" + rtpSpId: "984500A9EB6B07AC2G71" page: totalElements: 2 totalPages: 2 @@ -637,7 +656,7 @@ components: - rtpSpId example: fiscalCode: "RSSMRA85T10A562S" - rtpSpId: "10987654321" + rtpSpId: "984500A9EB6B07AC2G71" # ============================================================================ # Request bodies. diff --git a/src/main/java/it/gov/pagopa/rtp/activator/RtpActivatorApplication.java b/src/main/java/it/gov/pagopa/rtp/activator/RtpActivatorApplication.java index 46fd9e2..15de606 100644 --- a/src/main/java/it/gov/pagopa/rtp/activator/RtpActivatorApplication.java +++ b/src/main/java/it/gov/pagopa/rtp/activator/RtpActivatorApplication.java @@ -2,9 +2,12 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; + import reactor.core.publisher.Hooks; @SpringBootApplication +@ConfigurationPropertiesScan() public class RtpActivatorApplication { public static void main(String[] args) { @@ -12,4 +15,4 @@ public static void main(String[] args) { SpringApplication.run(RtpActivatorApplication.class, args); } -} +} \ No newline at end of file diff --git a/src/main/java/it/gov/pagopa/rtp/activator/configuration/ActivationPropertiesConfig.java b/src/main/java/it/gov/pagopa/rtp/activator/configuration/ActivationPropertiesConfig.java new file mode 100644 index 0000000..cfdd312 --- /dev/null +++ b/src/main/java/it/gov/pagopa/rtp/activator/configuration/ActivationPropertiesConfig.java @@ -0,0 +1,17 @@ +package it.gov.pagopa.rtp.activator.configuration; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +import lombok.Getter; +import lombok.Setter; + +@ConfigurationProperties(prefix = "activation") +@Getter +@Setter +public class ActivationPropertiesConfig{ + private String baseUrl; + + public ActivationPropertiesConfig(String baseUrl){ + this.baseUrl = baseUrl; + } +} diff --git a/src/main/java/it/gov/pagopa/rtp/activator/controller/ActivationAPIControllerImpl.java b/src/main/java/it/gov/pagopa/rtp/activator/controller/ActivationAPIControllerImpl.java index c14d83a..0b6ebb7 100644 --- a/src/main/java/it/gov/pagopa/rtp/activator/controller/ActivationAPIControllerImpl.java +++ b/src/main/java/it/gov/pagopa/rtp/activator/controller/ActivationAPIControllerImpl.java @@ -1,16 +1,22 @@ package it.gov.pagopa.rtp.activator.controller; -import it.gov.pagopa.rtp.activator.controller.generated.CreateApi; -import it.gov.pagopa.rtp.activator.model.generated.ActivationReqDto; +import java.util.UUID; + import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.server.ServerWebExchange; + +import it.gov.pagopa.rtp.activator.configuration.ActivationPropertiesConfig; +import it.gov.pagopa.rtp.activator.controller.generated.CreateApi; +import it.gov.pagopa.rtp.activator.domain.errors.PayerAlreadyExists; +import it.gov.pagopa.rtp.activator.model.generated.ActivationReqDto; +import it.gov.pagopa.rtp.activator.service.ActivationPayerService; import reactor.core.publisher.Mono; +import org.springframework.security.access.prepost.PreAuthorize; + import java.net.URI; -import java.util.UUID; import static it.gov.pagopa.rtp.activator.utils.Authorizations.verifySubjectRequest; @@ -18,15 +24,30 @@ @Validated public class ActivationAPIControllerImpl implements CreateApi { + private final ActivationPayerService activationPayerService; + + private final ActivationPropertiesConfig activationPropertiesConfig; + + public ActivationAPIControllerImpl(ActivationPayerService activationPayerService, + ActivationPropertiesConfig activationPropertiesConfig) { + this.activationPayerService = activationPayerService; + this.activationPropertiesConfig = activationPropertiesConfig; + } + @Override @PreAuthorize("hasRole('write_rtp_activations')") public Mono> activate( UUID requestId, String version, Mono activationReqDto, - ServerWebExchange exchange - ) { + ServerWebExchange exchange) { + return verifySubjectRequest(activationReqDto, it -> it.getPayer().getRtpSpId()) - .map(request -> ResponseEntity.created(URI.create("http://localhost")).build()); + .flatMap(t -> activationPayerService.activatePayer(t.getPayer().getRtpSpId(), + t.getPayer().getFiscalCode())) + .>map(payer -> ResponseEntity + .created(URI.create(activationPropertiesConfig.getBaseUrl() + payer.payerID().toString())) + .build()) + .onErrorReturn(PayerAlreadyExists.class, ResponseEntity.status(409).build()); } } diff --git a/src/main/java/it/gov/pagopa/rtp/activator/domain/Payer.java b/src/main/java/it/gov/pagopa/rtp/activator/domain/Payer.java new file mode 100644 index 0000000..6bd8738 --- /dev/null +++ b/src/main/java/it/gov/pagopa/rtp/activator/domain/Payer.java @@ -0,0 +1,6 @@ +package it.gov.pagopa.rtp.activator.domain; + +import java.time.Instant; + +public record Payer(PayerID payerID, String rtpSpId, String fiscalCode, Instant effectiveActivationDate) { +} diff --git a/src/main/java/it/gov/pagopa/rtp/activator/domain/PayerID.java b/src/main/java/it/gov/pagopa/rtp/activator/domain/PayerID.java new file mode 100644 index 0000000..1467cf8 --- /dev/null +++ b/src/main/java/it/gov/pagopa/rtp/activator/domain/PayerID.java @@ -0,0 +1,21 @@ +package it.gov.pagopa.rtp.activator.domain; + +import java.util.UUID; + +import lombok.Getter; + +@Getter +public class PayerID { + + private final UUID id; + + public PayerID(UUID uuid) { + this.id = uuid; + } + + public static PayerID createNew() { + UUID uuid = UUID.randomUUID(); + return new PayerID(uuid); + } + +} diff --git a/src/main/java/it/gov/pagopa/rtp/activator/domain/PayerRepository.java b/src/main/java/it/gov/pagopa/rtp/activator/domain/PayerRepository.java new file mode 100644 index 0000000..311d54e --- /dev/null +++ b/src/main/java/it/gov/pagopa/rtp/activator/domain/PayerRepository.java @@ -0,0 +1,13 @@ +package it.gov.pagopa.rtp.activator.domain; + + +import reactor.core.publisher.Mono; + +public interface PayerRepository { + + // Used to check if a specific payer is already registered. + Mono findByFiscalCode(String fiscalCode); + + Mono save(Payer payer); + +} \ No newline at end of file diff --git a/src/main/java/it/gov/pagopa/rtp/activator/domain/errors/PayerAlreadyExists.java b/src/main/java/it/gov/pagopa/rtp/activator/domain/errors/PayerAlreadyExists.java new file mode 100644 index 0000000..ac7fab3 --- /dev/null +++ b/src/main/java/it/gov/pagopa/rtp/activator/domain/errors/PayerAlreadyExists.java @@ -0,0 +1,5 @@ +package it.gov.pagopa.rtp.activator.domain.errors; + +public class PayerAlreadyExists extends Throwable{ + // You can insert here payer info. +} diff --git a/src/main/java/it/gov/pagopa/rtp/activator/repository/ActivationDB.java b/src/main/java/it/gov/pagopa/rtp/activator/repository/ActivationDB.java new file mode 100644 index 0000000..8a62c2d --- /dev/null +++ b/src/main/java/it/gov/pagopa/rtp/activator/repository/ActivationDB.java @@ -0,0 +1,13 @@ +package it.gov.pagopa.rtp.activator.repository; + + + +import org.springframework.data.mongodb.repository.ReactiveMongoRepository; +import org.springframework.stereotype.Repository; + +import reactor.core.publisher.Mono; + +@Repository +public interface ActivationDB extends ReactiveMongoRepository { + Mono findByFiscalCode(String fiscalCode); +} diff --git a/src/main/java/it/gov/pagopa/rtp/activator/repository/ActivationDBRepository.java b/src/main/java/it/gov/pagopa/rtp/activator/repository/ActivationDBRepository.java new file mode 100644 index 0000000..025a19b --- /dev/null +++ b/src/main/java/it/gov/pagopa/rtp/activator/repository/ActivationDBRepository.java @@ -0,0 +1,33 @@ +package it.gov.pagopa.rtp.activator.repository; + + +import org.springframework.stereotype.Repository; + +import it.gov.pagopa.rtp.activator.domain.Payer; +import it.gov.pagopa.rtp.activator.domain.PayerRepository; +import reactor.core.publisher.Mono; + +@Repository +public class ActivationDBRepository implements PayerRepository { + + private final ActivationDB activationDB; + private final ActivationMapper activationMapper; + + public ActivationDBRepository(ActivationDB activationDB, + ActivationMapper activationMapper) { + this.activationDB = activationDB; + this.activationMapper = activationMapper; + } + + @Override + public Mono findByFiscalCode(String fiscalCode) { + return activationDB.findByFiscalCode(fiscalCode) + .map(activationMapper::toDomain); + } + + @Override + public Mono save(Payer payer) { + return activationDB.save(activationMapper.toDbEntity(payer)).map(activationMapper::toDomain); + } + +} diff --git a/src/main/java/it/gov/pagopa/rtp/activator/repository/ActivationEntity.java b/src/main/java/it/gov/pagopa/rtp/activator/repository/ActivationEntity.java new file mode 100644 index 0000000..66df5c0 --- /dev/null +++ b/src/main/java/it/gov/pagopa/rtp/activator/repository/ActivationEntity.java @@ -0,0 +1,25 @@ +package it.gov.pagopa.rtp.activator.repository; + +import java.time.Instant; + +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Document("activations") +public class ActivationEntity { + @Id + private String id; + private String rtpSpId; + private Instant effectiveActivationDate; + + private String fiscalCode; +} diff --git a/src/main/java/it/gov/pagopa/rtp/activator/repository/ActivationMapper.java b/src/main/java/it/gov/pagopa/rtp/activator/repository/ActivationMapper.java new file mode 100644 index 0000000..e8cc816 --- /dev/null +++ b/src/main/java/it/gov/pagopa/rtp/activator/repository/ActivationMapper.java @@ -0,0 +1,28 @@ +package it.gov.pagopa.rtp.activator.repository; + +import java.util.UUID; + +import org.springframework.stereotype.Component; + +import it.gov.pagopa.rtp.activator.domain.Payer; +import it.gov.pagopa.rtp.activator.domain.PayerID; + +@Component +public class ActivationMapper { + + public Payer toDomain(ActivationEntity activationEntity) { + PayerID payerID = new PayerID( + UUID.fromString(activationEntity.getId())); + return new Payer(payerID, + activationEntity.getRtpSpId(), activationEntity.getFiscalCode(), + activationEntity.getEffectiveActivationDate()); + } + + public ActivationEntity toDbEntity(Payer payer) { + return ActivationEntity.builder().id(payer.payerID().getId().toString()) + .fiscalCode(payer.fiscalCode()) + .rtpSpId(payer.rtpSpId()) + .effectiveActivationDate(payer.effectiveActivationDate()) + .build(); + } +} diff --git a/src/main/java/it/gov/pagopa/rtp/activator/service/ActivationPayerService.java b/src/main/java/it/gov/pagopa/rtp/activator/service/ActivationPayerService.java new file mode 100644 index 0000000..3689f8a --- /dev/null +++ b/src/main/java/it/gov/pagopa/rtp/activator/service/ActivationPayerService.java @@ -0,0 +1,8 @@ +package it.gov.pagopa.rtp.activator.service; + +import it.gov.pagopa.rtp.activator.domain.Payer; +import reactor.core.publisher.Mono; + +public interface ActivationPayerService { + Mono activatePayer(String payer, String fiscalCode); +} \ No newline at end of file diff --git a/src/main/java/it/gov/pagopa/rtp/activator/service/ActivationPayerServiceImpl.java b/src/main/java/it/gov/pagopa/rtp/activator/service/ActivationPayerServiceImpl.java new file mode 100644 index 0000000..de921e6 --- /dev/null +++ b/src/main/java/it/gov/pagopa/rtp/activator/service/ActivationPayerServiceImpl.java @@ -0,0 +1,32 @@ +package it.gov.pagopa.rtp.activator.service; + +import java.time.Instant; + +import org.springframework.stereotype.Service; + +import it.gov.pagopa.rtp.activator.domain.Payer; +import it.gov.pagopa.rtp.activator.domain.PayerID; +import it.gov.pagopa.rtp.activator.domain.errors.PayerAlreadyExists; +import it.gov.pagopa.rtp.activator.repository.ActivationDBRepository; +import reactor.core.publisher.Mono; + +@Service +public class ActivationPayerServiceImpl implements ActivationPayerService { + + private final ActivationDBRepository activationDBRepository; + + public ActivationPayerServiceImpl(ActivationDBRepository activationDBRepository) { + this.activationDBRepository = activationDBRepository; + } + + @Override + public Mono activatePayer(String rtpSpId, String fiscalCode) { + + PayerID payerID = PayerID.createNew(); + Payer payer = new Payer(payerID, rtpSpId, fiscalCode, Instant.now()); + + return activationDBRepository.findByFiscalCode(fiscalCode) + .flatMap(existingEntity -> Mono.error(new PayerAlreadyExists())) + .switchIfEmpty(Mono.defer(() -> activationDBRepository.save(payer))); + } +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index bc924fc..ccf4b8f 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -8,3 +8,8 @@ management.endpoints.web.exposure.include=health management.endpoint.health.enabled=true management.endpoint.health.probes.enabled=true +#repository +management.health.mongo.enabled=false +spring.data.mongodb.uri=${COSMOS_ACCOUNT_RTP_CONNECTION_STRING:} +spring.data.mongodb.database=${DB_NAME:rtp} +activation.base-url=${BASE_URL:http://localhost} diff --git a/src/main/resources/custom.security b/src/main/resources/custom.security index 9861c93..d9fc07f 100644 --- a/src/main/resources/custom.security +++ b/src/main/resources/custom.security @@ -1 +1 @@ -jdk.jar.disabledAlgorithms=MD2, MD5, RSA, DSA \ No newline at end of file +jdk.jar.disabledAlgorithms=MD2, MD5, RSA, DSA diff --git a/src/main/terraform/container_app.tf b/src/main/terraform/container_app.tf index bc4e510..85e48bb 100644 --- a/src/main/terraform/container_app.tf +++ b/src/main/terraform/container_app.tf @@ -47,6 +47,11 @@ resource "azurerm_container_app" "rtp-activator" { secret_name = "identity-client-id" } + env { + name = "AZURE_CLIENT_ID" + secret_name = "identity-client-id" + } + dynamic "env" { for_each = var.rtp_environment_configs content { diff --git a/src/main/terraform/data.tf b/src/main/terraform/data.tf index 60f0888..4c539c3 100644 --- a/src/main/terraform/data.tf +++ b/src/main/terraform/data.tf @@ -14,6 +14,7 @@ data "azurerm_user_assigned_identity" "rtp-activator" { resource_group_name = var.id_resource_group_name } + # ------------------------------------------------------------------------------ # General purpose key vault used to protect secrets. # ------------------------------------------------------------------------------ @@ -21,3 +22,4 @@ data "azurerm_key_vault" "rtp-kv" { name = local.rtp_kv_name resource_group_name = local.rtp_kv_resource_group_name } + diff --git a/src/main/terraform/env/cstar-d-weu-rtp/terraform.tfvars b/src/main/terraform/env/cstar-d-weu-rtp/terraform.tfvars index b3c4910..be52f33 100644 --- a/src/main/terraform/env/cstar-d-weu-rtp/terraform.tfvars +++ b/src/main/terraform/env/cstar-d-weu-rtp/terraform.tfvars @@ -41,10 +41,15 @@ rtp_activator_max_replicas = 5 rtp_activator_min_replicas = 1 rtp_activator_base_url = "https://mil-d-apim.azure-api.net/rtp-activator" -rtp_environment_configs = { - OTEL_TRACES_SAMPLER: "always_on" -} rtp_environment_secrets = { - APPLICATIONINSIGHTS_CONNECTION_STRING: "appinsights-connection-string" -} \ No newline at end of file + COSMOS_ACCOUNT_RTP_CONNECTION_STRING : "cosmosdb-account-rtp-connection-string" + APPLICATIONINSIGHTS_CONNECTION_STRING : "appinsights-connection-string" +} + + +rtp_environment_configs = { + DB_NAME : "rtp" + BASE_URL : "https://" + OTEL_TRACES_SAMPLER : "always_on" +} diff --git a/src/main/terraform/env/cstar-p-weu-rtp/terraform.tfvars b/src/main/terraform/env/cstar-p-weu-rtp/terraform.tfvars index 1e8f10d..bb49bd8 100644 --- a/src/main/terraform/env/cstar-p-weu-rtp/terraform.tfvars +++ b/src/main/terraform/env/cstar-p-weu-rtp/terraform.tfvars @@ -41,10 +41,13 @@ rtp_activator_max_replicas = 5 rtp_activator_min_replicas = 1 rtp_activator_base_url = "https://mil-d-apim.azure-api.net/rtp_activator" -rtp_environment_configs = { - OTEL_TRACES_SAMPLER: "always_on" +rtp_environment_secrets = { + COSMOS_ACCOUNT_RTP_CONNECTION_STRING : "cosmosdb-account-rtp-connection-string" + APPLICATIONINSIGHTS_CONNECTION_STRING : "appinsights-connection-string" } -rtp_environment_secrets = { - APPLICATIONINSIGHTS_CONNECTION_STRING: "appinsights-connection-string" -} \ No newline at end of file +rtp_environment_configs = { + DB_NAME : "rtp" + BASE_URL : "https://" + OTEL_TRACES_SAMPLER : "always_on" +} diff --git a/src/main/terraform/env/cstar-u-weu-rtp/terraform.tfvars b/src/main/terraform/env/cstar-u-weu-rtp/terraform.tfvars index 3bcbe92..7efa5a3 100644 --- a/src/main/terraform/env/cstar-u-weu-rtp/terraform.tfvars +++ b/src/main/terraform/env/cstar-u-weu-rtp/terraform.tfvars @@ -41,10 +41,15 @@ rtp_activator_max_replicas = 5 rtp_activator_min_replicas = 1 rtp_activator_base_url = "https://mil-d-apim.azure-api.net/rtp-activator" -rtp_environment_configs = { - OTEL_TRACES_SAMPLER: "always_on" -} rtp_environment_secrets = { - APPLICATIONINSIGHTS_CONNECTION_STRING: "appinsights-connection-string" -} \ No newline at end of file + COSMOS_ACCOUNT_RTP_CONNECTION_STRING : "cosmosdb-account-rtp-connection-string" + APPLICATIONINSIGHTS_CONNECTION_STRING : "appinsights-connection-string" +} + + +rtp_environment_configs = { + DB_NAME : "rtp" + BASE_URL : "https://" + OTEL_TRACES_SAMPLER : "always_on" +} diff --git a/src/main/terraform/variables.tf b/src/main/terraform/variables.tf index 0a8289f..98aab26 100644 --- a/src/main/terraform/variables.tf +++ b/src/main/terraform/variables.tf @@ -116,4 +116,4 @@ variable "rtp_environment_configs" { variable "rtp_environment_secrets" { type = map(any) default = {} -} \ No newline at end of file +} diff --git a/src/test/java/it/gov/pagopa/rtp/activator/RtpActivatorApplicationTest.java b/src/test/java/it/gov/pagopa/rtp/activator/RtpActivatorApplicationTest.java new file mode 100644 index 0000000..b363f5e --- /dev/null +++ b/src/test/java/it/gov/pagopa/rtp/activator/RtpActivatorApplicationTest.java @@ -0,0 +1,17 @@ +package it.gov.pagopa.rtp.activator; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +@ExtendWith(SpringExtension.class) +class RtpActivatorApplicationTests { + + @Test + void contextLoads() { + assertNotNull(new RtpActivatorApplication()); + } + +} \ No newline at end of file diff --git a/src/test/java/it/gov/pagopa/rtp/activator/RtpActivatorApplicationTests.java b/src/test/java/it/gov/pagopa/rtp/activator/RtpActivatorApplicationTests.java deleted file mode 100644 index 3d0b211..0000000 --- a/src/test/java/it/gov/pagopa/rtp/activator/RtpActivatorApplicationTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package it.gov.pagopa.rtp.activator; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class RtpActivatorApplicationTests { - - @Test - void contextLoads() { - } - -} diff --git a/src/test/java/it/gov/pagopa/rtp/activator/configuration/ActivationPropertiesConfigTest.java b/src/test/java/it/gov/pagopa/rtp/activator/configuration/ActivationPropertiesConfigTest.java new file mode 100644 index 0000000..76ab056 --- /dev/null +++ b/src/test/java/it/gov/pagopa/rtp/activator/configuration/ActivationPropertiesConfigTest.java @@ -0,0 +1,26 @@ +package it.gov.pagopa.rtp.activator.configuration; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@ExtendWith(SpringExtension.class) +@EnableConfigurationProperties(value = ActivationPropertiesConfig.class) +@TestPropertySource("classpath:application.properties") +class ActivationPropertiesConfigTest { + + @Autowired + private ActivationPropertiesConfig activationPropertiesConfig; + + @Test + void testPropertiesLoaded() { + assertNotNull(activationPropertiesConfig); + assertEquals("https://example.com", activationPropertiesConfig.getBaseUrl()); + } +} \ No newline at end of file diff --git a/src/test/java/it/gov/pagopa/rtp/activator/controller/ActivationAPIControllerImplTest.java b/src/test/java/it/gov/pagopa/rtp/activator/controller/ActivationAPIControllerImplTest.java index 46178e9..33d6c15 100644 --- a/src/test/java/it/gov/pagopa/rtp/activator/controller/ActivationAPIControllerImplTest.java +++ b/src/test/java/it/gov/pagopa/rtp/activator/controller/ActivationAPIControllerImplTest.java @@ -1,41 +1,67 @@ package it.gov.pagopa.rtp.activator.controller; +import it.gov.pagopa.rtp.activator.configuration.ActivationPropertiesConfig; import it.gov.pagopa.rtp.activator.configuration.SecurityConfig; +import it.gov.pagopa.rtp.activator.domain.Payer; +import it.gov.pagopa.rtp.activator.domain.PayerID; +import it.gov.pagopa.rtp.activator.domain.errors.PayerAlreadyExists; import it.gov.pagopa.rtp.activator.model.generated.ActivationReqDto; import it.gov.pagopa.rtp.activator.model.generated.PayerDto; +import it.gov.pagopa.rtp.activator.repository.ActivationDBRepository; +import it.gov.pagopa.rtp.activator.service.ActivationPayerService; + import it.gov.pagopa.rtp.activator.utils.Users; +import reactor.core.publisher.Mono; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; + 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.context.ApplicationContext; import org.springframework.context.annotation.Import; -import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; + import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.aot.DisabledInAotMode; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.reactive.server.WebTestClient; +import java.time.Instant; import java.util.UUID; import static it.gov.pagopa.rtp.activator.utils.Users.SERVICE_PROVIDER_ID; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.springSecurity; - @ExtendWith(SpringExtension.class) -@WebFluxTest(controllers = {ActivationAPIControllerImpl.class}) -@Import(SecurityConfig.class) +@WebFluxTest(controllers = { ActivationAPIControllerImpl.class }) +@Import({ SecurityConfig.class }) +@DisabledInAotMode class ActivationAPIControllerImplTest { - @Autowired - ApplicationContext context; - WebTestClient web; + @MockBean + private ActivationDBRepository activationDBRepository; + + @MockBean + private ActivationPayerService activationPayerService; + + @MockBean + private ActivationPropertiesConfig activationPropertiesConfig; + + private WebTestClient webTestClient; + + @Autowired + private ApplicationContext context; @BeforeEach - public void setup() { - web = WebTestClient - .bindToApplicationContext(this.context) + void setup() { + webTestClient = WebTestClient + .bindToApplicationContext(context) .apply(springSecurity()) .configureClient() .build(); @@ -43,21 +69,43 @@ public void setup() { @Test @Users.RtpWriter - void shouldCreateNewActivation() { - web.post() + void testActivatePayerSuccessful() { + Payer payer = new Payer(PayerID.createNew(), "RTP_SP_ID", "FISCAL_CODE", Instant.now()); + + when(activationPayerService.activatePayer(any(String.class), any(String.class))) + .thenReturn(Mono.just(payer)); + + when(activationPropertiesConfig.getBaseUrl()).thenReturn("http://localhost:8080/"); + + webTestClient.post() + .uri("/activations") + .header("RequestId", UUID.randomUUID().toString()) + .header("Version", "v1") + .bodyValue(generateActivationRequest()) + .exchange() + .expectStatus().isCreated().expectHeader() + .location("http://localhost:8080/" + payer.payerID().toString()); + } + + @Test + @Users.RtpWriter + void testActivatePayerAlreadyExists() { + when(activationPayerService.activatePayer(any(String.class), + any(String.class))) + .thenReturn(Mono.error(new PayerAlreadyExists())); + webTestClient.post() .uri("/activations") .header("RequestId", UUID.randomUUID().toString()) .header("Version", "v1") .bodyValue(generateActivationRequest()) .exchange() - .expectStatus().isEqualTo(HttpStatus.CREATED) - .expectHeader().exists(HttpHeaders.LOCATION); + .expectStatus().isEqualTo(409); } @Test @WithMockUser(value = "another", roles = Users.ACTIVATION_WRITE_ROLE) void authorizedUserShouldNotActivateForAnotherServiceProvider() { - web.post() + webTestClient.post() .uri("/activations") .header("RequestId", UUID.randomUUID().toString()) .header("Version", "v1") @@ -69,7 +117,7 @@ void authorizedUserShouldNotActivateForAnotherServiceProvider() { @Test @WithMockUser void userWithoutEnoughPermissionShouldNotCreateNewActivation() { - web.post() + webTestClient.post() .uri("/activations") .header("RequestId", UUID.randomUUID().toString()) .header("Version", "v1") diff --git a/src/test/java/it/gov/pagopa/rtp/activator/domain/PayerIDTest.java b/src/test/java/it/gov/pagopa/rtp/activator/domain/PayerIDTest.java new file mode 100644 index 0000000..c0dfc9f --- /dev/null +++ b/src/test/java/it/gov/pagopa/rtp/activator/domain/PayerIDTest.java @@ -0,0 +1,22 @@ +package it.gov.pagopa.rtp.activator.domain; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; +import java.util.UUID; + +class PayerIDTest { + + @Test + void testCreateNew() { + PayerID payerID = PayerID.createNew(); + assertNotNull(payerID); + assertNotNull(payerID.getId()); + } + + @Test + void testConstructor() { + UUID uuid = UUID.randomUUID(); + PayerID payerID = new PayerID(uuid); + assertNotNull(payerID); + assertEquals(uuid, payerID.getId()); + } +} diff --git a/src/test/java/it/gov/pagopa/rtp/activator/repository/ActivationMapperTest.java b/src/test/java/it/gov/pagopa/rtp/activator/repository/ActivationMapperTest.java new file mode 100644 index 0000000..a267771 --- /dev/null +++ b/src/test/java/it/gov/pagopa/rtp/activator/repository/ActivationMapperTest.java @@ -0,0 +1,47 @@ +package it.gov.pagopa.rtp.activator.repository; + +import org.junit.jupiter.api.Test; + +import it.gov.pagopa.rtp.activator.domain.Payer; +import it.gov.pagopa.rtp.activator.domain.PayerID; + +import static org.junit.jupiter.api.Assertions.*; + +import java.time.Instant; +import java.util.UUID; + +class ActivationMapperTest { + + private ActivationMapper mapper = new ActivationMapper(); + + @Test + void testToDomain() { + ActivationEntity activationEntity = new ActivationEntity(); + activationEntity.setId(UUID.randomUUID().toString()); + activationEntity.setRtpSpId("RTP_SP_ID"); + activationEntity.setFiscalCode("FISCAL_CODE"); + activationEntity.setEffectiveActivationDate(Instant.ofEpochSecond(1732517304)); + + Payer payer = mapper.toDomain(activationEntity); + + assertNotNull(payer); + assertEquals(activationEntity.getId(), payer.payerID().getId().toString()); + assertEquals(activationEntity.getRtpSpId(), payer.rtpSpId()); + assertEquals(activationEntity.getFiscalCode(), payer.fiscalCode()); + assertEquals(activationEntity.getEffectiveActivationDate(), payer.effectiveActivationDate()); + } + + @Test + void testToDbEntity() { + PayerID payerID = new PayerID(UUID.randomUUID()); + Payer payer = new Payer(payerID, "RTP_SP_ID", "FISCAL_CODE", Instant.ofEpochSecond(1732517304)); + + ActivationEntity activationEntity = mapper.toDbEntity(payer); + + assertNotNull(activationEntity); + assertEquals(payer.payerID().getId().toString(), activationEntity.getId()); + assertEquals(payer.rtpSpId(), activationEntity.getRtpSpId()); + assertEquals(payer.fiscalCode(), activationEntity.getFiscalCode()); + assertEquals(payer.effectiveActivationDate(), activationEntity.getEffectiveActivationDate()); + } +} diff --git a/src/test/java/it/gov/pagopa/rtp/activator/service/ActivationPayerServiceImplTest.java b/src/test/java/it/gov/pagopa/rtp/activator/service/ActivationPayerServiceImplTest.java new file mode 100644 index 0000000..40a85fa --- /dev/null +++ b/src/test/java/it/gov/pagopa/rtp/activator/service/ActivationPayerServiceImplTest.java @@ -0,0 +1,63 @@ +package it.gov.pagopa.rtp.activator.service; + +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 it.gov.pagopa.rtp.activator.domain.Payer; +import it.gov.pagopa.rtp.activator.domain.PayerID; +import it.gov.pagopa.rtp.activator.domain.errors.PayerAlreadyExists; +import it.gov.pagopa.rtp.activator.repository.ActivationDBRepository; + +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import java.time.Instant; + + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class ActivationPayerServiceImplTest { + + @Mock + private ActivationDBRepository activationDBRepository; + + @InjectMocks + private ActivationPayerServiceImpl activationPayerService; + + private Payer payer; + + @BeforeEach + void setUp() { + payer = new Payer(PayerID.createNew(), "RTP_SP_ID", "FISCAL_CODE", Instant.now()); + + } + + @Test + void testActivatePayerSuccessful() { + when(activationDBRepository.findByFiscalCode("FISCAL_CODE")).thenReturn(Mono.empty()); + when(activationDBRepository.save(any())).thenReturn(Mono.just(payer)); + + Mono result = activationPayerService.activatePayer("RTP_SP_ID", "FISCAL_CODE"); + + StepVerifier.create(result) + .expectNext(payer) + .verifyComplete(); + } + + @Test + void testActivatePayerAlreadyExists() { + when(activationDBRepository.findByFiscalCode("FISCAL_CODE")).thenReturn(Mono.just(payer)); + + Mono result = activationPayerService.activatePayer("RTP_SP_ID", "FISCAL_CODE"); + + StepVerifier.create(result) + .expectError(PayerAlreadyExists.class) + .verify(); + } +} diff --git a/src/test/java/it/gov/pagopa/rtp/activator/utils/Users.java b/src/test/java/it/gov/pagopa/rtp/activator/utils/Users.java index 3ef1898..7e8596f 100644 --- a/src/test/java/it/gov/pagopa/rtp/activator/utils/Users.java +++ b/src/test/java/it/gov/pagopa/rtp/activator/utils/Users.java @@ -7,7 +7,7 @@ public class Users { - public static final String SERVICE_PROVIDER_ID = "1234"; + public static final String SERVICE_PROVIDER_ID = "984500A9EB6B07AC2G71"; public static final String ACTIVATION_WRITE_ROLE = "write_rtp_activations"; public static final String ACTIVATION_READ_ROLE = "read_rtp_activations"; diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties index cf39e7e..9b31274 100644 --- a/src/test/resources/application.properties +++ b/src/test/resources/application.properties @@ -1 +1,6 @@ logging.level.org.springframework.security=DEBUG +activation.baseUrl=https://example.com + +azure.cosmos.uri=https://example.com/db/endpoint +azure.cosmos.key=your-primary-key +azure.cosmos.database=rtp \ No newline at end of file