diff --git a/build.gradle.kts b/build.gradle.kts index 747ed56..1207ee8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -87,6 +87,7 @@ dependencies { testImplementation("org.mockito.kotlin:mockito-kotlin:4.0.0") testImplementation("com.squareup.okhttp3:mockwebserver:$mockWebServerVersion") testImplementation("com.squareup.okhttp3:okhttp:$mockWebServerVersion") + testImplementation("it.pagopa:pagopa-ecommerce-commons:$ecommerceCommonsVersion:tests") } configurations { @@ -117,7 +118,10 @@ tasks.create("applySemanticVersionPlugin") { apply(plugin = "com.dipien.semantic-version") } -tasks.withType(JavaCompile::class.java).configureEach { options.encoding = "UTF-8" } +tasks.withType(JavaCompile::class.java).configureEach { + options.encoding = "UTF-8" + options.compilerArgs.add("--enable-preview") +} tasks.withType(Javadoc::class.java).configureEach { options.encoding = "UTF-8" } @@ -191,6 +195,7 @@ tasks.named("jar") { enabled = false } tasks.test { useJUnitPlatform() finalizedBy(tasks.jacocoTestReport) // report is always generated after tests run + jvmArgs(listOf("--enable-preview")) } tasks.jacocoTestReport { diff --git a/src/main/kotlin/it/pagopa/ecommerce/helpdesk/controllers/EcommerceController.kt b/src/main/kotlin/it/pagopa/ecommerce/helpdesk/controllers/EcommerceController.kt index d8eed35..2076782 100644 --- a/src/main/kotlin/it/pagopa/ecommerce/helpdesk/controllers/EcommerceController.kt +++ b/src/main/kotlin/it/pagopa/ecommerce/helpdesk/controllers/EcommerceController.kt @@ -22,8 +22,14 @@ class EcommerceController(@Autowired val ecommerceService: EcommerceService) : E exchange: ServerWebExchange ): Mono> { logger.info("[HelpDesk controller] ecommerceSearchTransaction") - return ecommerceService - .searchTransaction(pageNumber, pageSize, ecommerceSearchTransactionRequestDto) + return ecommerceSearchTransactionRequestDto + .flatMap { + ecommerceService.searchTransaction( + pageNumber = pageNumber, + pageSize = pageSize, + ecommerceSearchTransactionRequestDto = it + ) + } .map { ResponseEntity.ok(it) } } } diff --git a/src/main/kotlin/it/pagopa/ecommerce/helpdesk/controllers/HelpdeskController.kt b/src/main/kotlin/it/pagopa/ecommerce/helpdesk/controllers/HelpdeskController.kt index 08e053b..3ac8b2f 100644 --- a/src/main/kotlin/it/pagopa/ecommerce/helpdesk/controllers/HelpdeskController.kt +++ b/src/main/kotlin/it/pagopa/ecommerce/helpdesk/controllers/HelpdeskController.kt @@ -3,7 +3,10 @@ package it.pagopa.ecommerce.helpdesk.controllers import it.pagopa.ecommerce.helpdesk.services.EcommerceService import it.pagopa.ecommerce.helpdesk.services.PmService import it.pagopa.generated.ecommerce.helpdesk.api.HelpdeskApi -import it.pagopa.generated.ecommerce.helpdesk.model.* +import it.pagopa.generated.ecommerce.helpdesk.model.EcommerceSearchTransactionRequestDto +import it.pagopa.generated.ecommerce.helpdesk.model.HelpDeskSearchTransactionRequestDto +import it.pagopa.generated.ecommerce.helpdesk.model.PmSearchTransactionRequestDto +import it.pagopa.generated.ecommerce.helpdesk.model.SearchTransactionResponseDto import org.springframework.beans.factory.annotation.Autowired import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.RestController @@ -29,7 +32,7 @@ class HelpdeskController( .searchTransaction( pageNumber = pageNumber, pageSize = pageSize, - ecommerceSearchTransactionRequestDto = Mono.just(it) + ecommerceSearchTransactionRequestDto = it ) .map { response -> ResponseEntity.ok(response) } is PmSearchTransactionRequestDto -> @@ -37,7 +40,7 @@ class HelpdeskController( .searchTransaction( pageNumber = pageNumber, pageSize = pageSize, - pmSearchTransactionRequestDto = Mono.just(it) + pmSearchTransactionRequestDto = it ) .map { response -> ResponseEntity.ok(response) } else -> Mono.error(RuntimeException("Unknown search criteria")) diff --git a/src/main/kotlin/it/pagopa/ecommerce/helpdesk/controllers/PmController.kt b/src/main/kotlin/it/pagopa/ecommerce/helpdesk/controllers/PmController.kt index c0ff26b..d6b4892 100644 --- a/src/main/kotlin/it/pagopa/ecommerce/helpdesk/controllers/PmController.kt +++ b/src/main/kotlin/it/pagopa/ecommerce/helpdesk/controllers/PmController.kt @@ -22,8 +22,14 @@ class PmController(@Autowired val pmService: PmService) : PmApi { exchange: ServerWebExchange ): Mono> { logger.info("[HelpDesk controller] pmSearchTransaction") - return pmService - .searchTransaction(pageSize, pageNumber, pmSearchTransactionRequestDto) + return pmSearchTransactionRequestDto + .flatMap { + pmService.searchTransaction( + pageSize = pageSize, + pageNumber = pageNumber, + pmSearchTransactionRequestDto = it + ) + } .map { ResponseEntity.ok(it) } } } diff --git a/src/main/kotlin/it/pagopa/ecommerce/helpdesk/exceptionhandler/ExceptionHandler.kt b/src/main/kotlin/it/pagopa/ecommerce/helpdesk/exceptionhandler/ExceptionHandler.kt new file mode 100644 index 0000000..20d9e73 --- /dev/null +++ b/src/main/kotlin/it/pagopa/ecommerce/helpdesk/exceptionhandler/ExceptionHandler.kt @@ -0,0 +1,78 @@ +package it.pagopa.ecommerce.helpdesk.exceptionhandler + +import it.pagopa.ecommerce.helpdesk.exceptions.ApiError +import it.pagopa.ecommerce.helpdesk.exceptions.RestApiException +import it.pagopa.generated.ecommerce.helpdesk.model.ProblemJsonDto +import jakarta.xml.bind.ValidationException +import java.util.* +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.http.converter.HttpMessageNotReadableException +import org.springframework.web.bind.MethodArgumentNotValidException +import org.springframework.web.bind.annotation.ExceptionHandler +import org.springframework.web.bind.annotation.RestControllerAdvice +import org.springframework.web.bind.support.WebExchangeBindException +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException +import org.springframework.web.server.ServerWebInputException + +/** + * Exception handler used to output a custom message in case an incoming request is invalid or an + * api encounter an error and throw an RestApiException + */ +@RestControllerAdvice +class ExceptionHandler { + + val logger: Logger = LoggerFactory.getLogger(javaClass) + + /** RestApiException exception handler */ + @ExceptionHandler(RestApiException::class) + fun handleException(e: RestApiException): ResponseEntity { + logger.error("Exception processing request", e) + return ResponseEntity.status(e.httpStatus) + .body( + ProblemJsonDto().status(e.httpStatus.value()).title(e.title).detail(e.description) + ) + } + + /** ApiError exception handler */ + @ExceptionHandler(ApiError::class) + fun handleException(e: ApiError): ResponseEntity { + return handleException(e.toRestException()) + } + + /** Validation request exception handler */ + @ExceptionHandler( + MethodArgumentNotValidException::class, + MethodArgumentTypeMismatchException::class, + ServerWebInputException::class, + ValidationException::class, + HttpMessageNotReadableException::class, + WebExchangeBindException::class + ) + fun handleRequestValidationException(e: Exception): ResponseEntity { + + logger.error("Input request is not valid", e) + return ResponseEntity.badRequest() + .body( + ProblemJsonDto() + .status(HttpStatus.BAD_REQUEST.value()) + .title("Bad request") + .detail(e.localizedMessage) + ) + } + + /** Handler for generic exception */ + @ExceptionHandler(Exception::class) + fun handleGenericException(e: Exception): ResponseEntity { + logger.error("Exception processing the request", e) + return ResponseEntity.internalServerError() + .body( + ProblemJsonDto() + .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) + .title("Error processing the request") + .detail(e.localizedMessage) + ) + } +} diff --git a/src/main/kotlin/it/pagopa/ecommerce/helpdesk/exceptions/ApiError.kt b/src/main/kotlin/it/pagopa/ecommerce/helpdesk/exceptions/ApiError.kt new file mode 100644 index 0000000..975eab4 --- /dev/null +++ b/src/main/kotlin/it/pagopa/ecommerce/helpdesk/exceptions/ApiError.kt @@ -0,0 +1,10 @@ +package it.pagopa.ecommerce.helpdesk.exceptions + +/** + * Class that bridges business-related exception to `RestException`. Business-related exceptions + * should extend this class. + */ +abstract class ApiError(message: String?) : RuntimeException(message) { + /** Convert this ApiError to RestApiException */ + abstract fun toRestException(): RestApiException +} diff --git a/src/main/kotlin/it/pagopa/ecommerce/helpdesk/exceptions/NoResultFoundException.kt b/src/main/kotlin/it/pagopa/ecommerce/helpdesk/exceptions/NoResultFoundException.kt new file mode 100644 index 0000000..4963fc0 --- /dev/null +++ b/src/main/kotlin/it/pagopa/ecommerce/helpdesk/exceptions/NoResultFoundException.kt @@ -0,0 +1,14 @@ +package it.pagopa.ecommerce.helpdesk.exceptions + +import org.springframework.http.HttpStatus + +class NoResultFoundException(private val searchCriteriaType: String) : + ApiError("No result found for criteria: $searchCriteriaType") { + + override fun toRestException() = + RestApiException( + httpStatus = HttpStatus.NOT_FOUND, + description = "No result can be found searching for criteria $searchCriteriaType", + title = "No result found" + ) +} diff --git a/src/main/kotlin/it/pagopa/ecommerce/helpdesk/exceptions/RestApiException.kt b/src/main/kotlin/it/pagopa/ecommerce/helpdesk/exceptions/RestApiException.kt new file mode 100644 index 0000000..d676292 --- /dev/null +++ b/src/main/kotlin/it/pagopa/ecommerce/helpdesk/exceptions/RestApiException.kt @@ -0,0 +1,7 @@ +package it.pagopa.ecommerce.helpdesk.exceptions + +import org.springframework.http.HttpStatus + +/** Rest api exception, used to return an error specific HttpStatus and reason code to the caller */ +class RestApiException(val httpStatus: HttpStatus, val title: String, val description: String) : + RuntimeException(title) diff --git a/src/main/kotlin/it/pagopa/ecommerce/helpdesk/services/EcommerceService.kt b/src/main/kotlin/it/pagopa/ecommerce/helpdesk/services/EcommerceService.kt index 5c66129..cfa7f8b 100644 --- a/src/main/kotlin/it/pagopa/ecommerce/helpdesk/services/EcommerceService.kt +++ b/src/main/kotlin/it/pagopa/ecommerce/helpdesk/services/EcommerceService.kt @@ -1,6 +1,5 @@ package it.pagopa.ecommerce.helpdesk.services -import it.pagopa.ecommerce.commons.documents.v1.TransactionActivatedEvent import it.pagopa.generated.ecommerce.helpdesk.model.* import java.time.OffsetDateTime import org.slf4j.LoggerFactory @@ -15,62 +14,56 @@ class EcommerceService { fun searchTransaction( pageNumber: Int, pageSize: Int, - ecommerceSearchTransactionRequestDto: Mono + ecommerceSearchTransactionRequestDto: EcommerceSearchTransactionRequestDto ): Mono { logger.info("[helpDesk ecommerce service] searchTransaction method") - return ecommerceSearchTransactionRequestDto - .doOnNext { - logger.info("Search type: ${it.type}") - val transactionEvent = TransactionActivatedEvent() - println(transactionEvent) - } - .map { - SearchTransactionResponseDto() - .page(PageInfoDto().current(0).results(3).total(1)) - .transactions( - listOf( - TransactionResultDto() - .product(ProductDto.ECOMMERCE) - .userInfo( - UserInfoDto() - .userFiscalCode("userFiscalCode") - .notificationEmail("notificationEmail") - .surname("surname") - .name("name") - .username("username") - .authenticationType("auth type") - ) - .transactionInfo( - TransactionInfoDto() - .amount(100) - .fee(100) - .creationDate(OffsetDateTime.now()) - .status("status") - .statusDetails("status details") - .grandTotal(200) - .rrn("rrn") - .authotizationCode("authCode") - .paymentMethodName("paymentMethodName") - .brand("brand") - ) - .paymentDetailInfo( - PaymentDetailInfoDto() - .iuv("IUV") - .rptIds(listOf("rptId1", "rptId2")) - .idTransaction("paymentContextCode") - .paymentToken("paymentToken") - .creditorInstitution("creditor institution") - .paFiscalCode("77777777777") - ) - .paymentInfo(PaymentInfoDto().origin("origin").subject("subject")) - .pspInfo( - PspInfoDto() - .pspId("pspId") - .businessName("business name") - .idChannel("id channel") - ) - ) + return Mono.just( + SearchTransactionResponseDto() + .page(PageInfoDto().current(0).results(3).total(1)) + .transactions( + listOf( + TransactionResultDto() + .product(ProductDto.ECOMMERCE) + .userInfo( + UserInfoDto() + .userFiscalCode("userFiscalCode") + .notificationEmail("notificationEmail") + .surname("surname") + .name("name") + .username("username") + .authenticationType("auth type") + ) + .transactionInfo( + TransactionInfoDto() + .amount(100) + .fee(100) + .creationDate(OffsetDateTime.now()) + .status("status") + .statusDetails("status details") + .grandTotal(200) + .rrn("rrn") + .authotizationCode("authCode") + .paymentMethodName("paymentMethodName") + .brand("brand") + ) + .paymentDetailInfo( + PaymentDetailInfoDto() + .iuv("IUV") + .rptIds(listOf("rptId1", "rptId2")) + .idTransaction("paymentContextCode") + .paymentToken("paymentToken") + .creditorInstitution("creditor institution") + .paFiscalCode("77777777777") + ) + .paymentInfo(PaymentInfoDto().origin("origin").subject("subject")) + .pspInfo( + PspInfoDto() + .pspId("pspId") + .businessName("business name") + .idChannel("id channel") + ) ) - } + ) + ) } } diff --git a/src/main/kotlin/it/pagopa/ecommerce/helpdesk/services/PmService.kt b/src/main/kotlin/it/pagopa/ecommerce/helpdesk/services/PmService.kt index 7989bd3..b41ba3f 100644 --- a/src/main/kotlin/it/pagopa/ecommerce/helpdesk/services/PmService.kt +++ b/src/main/kotlin/it/pagopa/ecommerce/helpdesk/services/PmService.kt @@ -17,27 +17,23 @@ class PmService(@Autowired val connectionFactory: ConnectionFactory) { fun searchTransaction( pageNumber: Int, pageSize: Int, - pmSearchTransactionRequestDto: Mono + pmSearchTransactionRequestDto: PmSearchTransactionRequestDto ): Mono { logger.info("[helpDesk pm service] searchTransaction method") - return pmSearchTransactionRequestDto - .doOnNext { logger.info("Search type: ${it.type}") } - .flatMapMany { - Flux.usingWhen( - connectionFactory.create(), - { connection -> - Flux.from( - connection - .createStatement("SELECT 'Hello, Oracle' FROM sys.dual") - .execute() - ) - .flatMap { result -> result.map { row -> row[0, String::class.java] } } - .doOnNext { logger.info("Read from DB: $it") } - }, - { it.close() } - ) - } + return Flux.usingWhen( + connectionFactory.create(), + { connection -> + Flux.from( + connection + .createStatement("SELECT 'Hello, Oracle' FROM sys.dual") + .execute() + ) + .flatMap { result -> result.map { row -> row[0, String::class.java] } } + .doOnNext { logger.info("Read from DB: $it") } + }, + { it.close() } + ) .collectList() .map { SearchTransactionResponseDto() diff --git a/src/test/kotlin/it/pagopa/ecommerce/helpdesk/HelpdeskTestUtils.kt b/src/test/kotlin/it/pagopa/ecommerce/helpdesk/HelpdeskTestUtils.kt new file mode 100644 index 0000000..af50462 --- /dev/null +++ b/src/test/kotlin/it/pagopa/ecommerce/helpdesk/HelpdeskTestUtils.kt @@ -0,0 +1,35 @@ +package it.pagopa.ecommerce.helpdesk + +import it.pagopa.ecommerce.commons.v1.TransactionTestUtils +import it.pagopa.generated.ecommerce.helpdesk.model.* +import org.springframework.http.HttpStatus + +object HelpdeskTestUtils { + + fun buildProblemJson( + httpStatus: HttpStatus, + title: String, + description: String + ): ProblemJsonDto = ProblemJsonDto().status(httpStatus.value()).detail(description).title(title) + + fun buildSearchRequestByRptId(): SearchTransactionRequestRptIdDto = + SearchTransactionRequestRptIdDto().rptId(TransactionTestUtils.RPT_ID).type("RPT_ID") + + fun buildSearchRequestByTransactionId(): SearchTransactionRequestTransactionIdDto = + SearchTransactionRequestTransactionIdDto() + .transactionId(TransactionTestUtils.TRANSACTION_ID) + .type("TRANSACTION_ID") + + fun buildSearchRequestByPaymentToken(): SearchTransactionRequestPaymentTokenDto = + SearchTransactionRequestPaymentTokenDto() + .paymentToken(TransactionTestUtils.PAYMENT_TOKEN) + .type("PAYMENT_TOKEN") + + fun buildSearchRequestByFiscalCode(): SearchTransactionRequestFiscalCodeDto = + SearchTransactionRequestFiscalCodeDto() + .userFiscalCode("AAABBB99A01A000A") + .type("USER_FISCAL_CODE") + + fun buildSearchRequestByUserMail(): SearchTransactionRequestEmailDto = + SearchTransactionRequestEmailDto().userEmail("test@test.it").type("USER_EMAIL") +} diff --git a/src/test/kotlin/it/pagopa/ecommerce/helpdesk/configurations/OracleConfigurationTest.kt b/src/test/kotlin/it/pagopa/ecommerce/helpdesk/configurations/OracleConfigurationTest.kt new file mode 100644 index 0000000..e73b54f --- /dev/null +++ b/src/test/kotlin/it/pagopa/ecommerce/helpdesk/configurations/OracleConfigurationTest.kt @@ -0,0 +1,22 @@ +package it.pagopa.ecommerce.helpdesk.configurations + +import org.junit.jupiter.api.Assertions.assertDoesNotThrow +import org.junit.jupiter.api.Test + +class OracleConfigurationTest { + + private val oracleConfiguration = OracleConfiguration() + + @Test + fun `should build connection factory successfully`() { + assertDoesNotThrow { + oracleConfiguration.getPMConnectionFactory( + dbHost = "127.0.0.1", + dbPort = 1521, + databaseName = "database", + username = "username", + password = "password" + ) + } + } +} diff --git a/src/test/kotlin/it/pagopa/ecommerce/helpdesk/controllers/EcommerceControllerTest.kt b/src/test/kotlin/it/pagopa/ecommerce/helpdesk/controllers/EcommerceControllerTest.kt new file mode 100644 index 0000000..21ff63e --- /dev/null +++ b/src/test/kotlin/it/pagopa/ecommerce/helpdesk/controllers/EcommerceControllerTest.kt @@ -0,0 +1,231 @@ +package it.pagopa.ecommerce.helpdesk.controllers + +import it.pagopa.ecommerce.helpdesk.HelpdeskTestUtils +import it.pagopa.ecommerce.helpdesk.exceptions.NoResultFoundException +import it.pagopa.ecommerce.helpdesk.services.EcommerceService +import it.pagopa.generated.ecommerce.helpdesk.model.* +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Test +import org.mockito.kotlin.argThat +import org.mockito.kotlin.eq +import org.mockito.kotlin.given +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.http.HttpStatus +import org.springframework.http.MediaType +import org.springframework.test.web.reactive.server.WebTestClient +import org.springframework.test.web.reactive.server.expectBody +import reactor.core.publisher.Mono + +@OptIn(ExperimentalCoroutinesApi::class) +@WebFluxTest(EcommerceController::class) +class EcommerceControllerTest { + + @Autowired lateinit var webClient: WebTestClient + + @MockBean lateinit var ecommerceService: EcommerceService + + @Test + fun `post search transaction succeeded searching by payment token`() = runTest { + val pageNumber = 1 + val pageSize = 15 + val request = HelpdeskTestUtils.buildSearchRequestByPaymentToken() + given( + ecommerceService.searchTransaction( + pageNumber = eq(pageNumber), + pageSize = eq(pageSize), + ecommerceSearchTransactionRequestDto = + argThat { + this is SearchTransactionRequestPaymentTokenDto && + this.paymentToken == request.paymentToken + } + ) + ) + .willReturn(Mono.just(SearchTransactionResponseDto())) + webClient + .post() + .uri { uriBuilder -> + uriBuilder + .path("/ecommerce/searchTransaction") + .queryParam("pageNumber", "{pageNumber}") + .queryParam("pageSize", "{pageSize}") + .build(pageNumber, pageSize) + } + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(request) + .exchange() + .expectStatus() + .isOk + } + + @Test + fun `post search transaction succeeded searching by rpt id`() = runTest { + val pageNumber = 1 + val pageSize = 15 + val request = HelpdeskTestUtils.buildSearchRequestByRptId() + given( + ecommerceService.searchTransaction( + pageNumber = eq(pageNumber), + pageSize = eq(pageSize), + ecommerceSearchTransactionRequestDto = + argThat { + this is SearchTransactionRequestRptIdDto && this.rptId == request.rptId + } + ) + ) + .willReturn(Mono.just(SearchTransactionResponseDto())) + webClient + .post() + .uri { uriBuilder -> + uriBuilder + .path("/ecommerce/searchTransaction") + .queryParam("pageNumber", "{pageNumber}") + .queryParam("pageSize", "{pageSize}") + .build(pageNumber, pageSize) + } + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(request) + .exchange() + .expectStatus() + .isOk + } + + @Test + fun `post search transaction succeeded searching by transaction id`() = runTest { + val pageNumber = 1 + val pageSize = 15 + val request = HelpdeskTestUtils.buildSearchRequestByTransactionId() + given( + ecommerceService.searchTransaction( + pageNumber = eq(pageNumber), + pageSize = eq(pageSize), + ecommerceSearchTransactionRequestDto = + argThat { + this is SearchTransactionRequestTransactionIdDto && + this.transactionId == request.transactionId + } + ) + ) + .willReturn(Mono.just(SearchTransactionResponseDto())) + webClient + .post() + .uri { uriBuilder -> + uriBuilder + .path("/ecommerce/searchTransaction") + .queryParam("pageNumber", "{pageNumber}") + .queryParam("pageSize", "{pageSize}") + .build(pageNumber, pageSize) + } + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(request) + .exchange() + .expectStatus() + .isOk + } + + @Test + fun `post search transaction should return 404 for no transaction found`() = runTest { + val pageNumber = 1 + val pageSize = 15 + val request = HelpdeskTestUtils.buildSearchRequestByTransactionId() + val expected = + HelpdeskTestUtils.buildProblemJson( + httpStatus = HttpStatus.NOT_FOUND, + title = "No result found", + description = "No result can be found searching for criteria ${request.type}" + ) + given( + ecommerceService.searchTransaction( + pageNumber = eq(pageNumber), + pageSize = eq(pageSize), + ecommerceSearchTransactionRequestDto = + argThat { + this is SearchTransactionRequestTransactionIdDto && + this.transactionId == request.transactionId + } + ) + ) + .willReturn(Mono.error(NoResultFoundException(request.type))) + webClient + .post() + .uri { uriBuilder -> + uriBuilder + .path("/ecommerce/searchTransaction") + .queryParam("pageNumber", "{pageNumber}") + .queryParam("pageSize", "{pageSize}") + .build(pageNumber, pageSize) + } + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(request) + .exchange() + .expectStatus() + .isNotFound + .expectBody() + .isEqualTo(expected) + } + + @Test + fun `post search transaction should return 400 for bad request`() = runTest { + val pageNumber = 1 + val pageSize = 15 + + webClient + .post() + .uri { uriBuilder -> + uriBuilder + .path("/ecommerce/searchTransaction") + .queryParam("pageNumber", "{pageNumber}") + .queryParam("pageSize", "{pageSize}") + .build(pageNumber, pageSize) + } + .contentType(MediaType.APPLICATION_JSON) + .bodyValue("{}") + .exchange() + .expectStatus() + .isBadRequest + } + + @Test + fun `post search transaction should return 500 for unhandled error processing request`() = + runTest { + val pageNumber = 1 + val pageSize = 15 + val request = HelpdeskTestUtils.buildSearchRequestByTransactionId() + val expected = + HelpdeskTestUtils.buildProblemJson( + httpStatus = HttpStatus.INTERNAL_SERVER_ERROR, + title = "Error processing the request", + description = "Unhandled error" + ) + given( + ecommerceService.searchTransaction( + pageNumber = eq(pageNumber), + pageSize = eq(pageSize), + ecommerceSearchTransactionRequestDto = + argThat { + this is SearchTransactionRequestTransactionIdDto && + this.transactionId == request.transactionId + } + ) + ) + .willReturn(Mono.error(RuntimeException("Unhandled error"))) + webClient + .post() + .uri { uriBuilder -> + uriBuilder + .path("/ecommerce/searchTransaction") + .queryParam("pageNumber", "{pageNumber}") + .queryParam("pageSize", "{pageSize}") + .build(pageNumber, pageSize) + } + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(request) + .exchange() + .expectStatus() + .isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR) + .expectBody() + .isEqualTo(expected) + } +} diff --git a/src/test/kotlin/it/pagopa/ecommerce/helpdesk/controllers/HelpdeskControllerTest.kt b/src/test/kotlin/it/pagopa/ecommerce/helpdesk/controllers/HelpdeskControllerTest.kt new file mode 100644 index 0000000..c81e8b0 --- /dev/null +++ b/src/test/kotlin/it/pagopa/ecommerce/helpdesk/controllers/HelpdeskControllerTest.kt @@ -0,0 +1,300 @@ +package it.pagopa.ecommerce.helpdesk.controllers + +import it.pagopa.ecommerce.helpdesk.HelpdeskTestUtils +import it.pagopa.ecommerce.helpdesk.exceptions.NoResultFoundException +import it.pagopa.ecommerce.helpdesk.services.EcommerceService +import it.pagopa.ecommerce.helpdesk.services.PmService +import it.pagopa.generated.ecommerce.helpdesk.model.* +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import org.mockito.kotlin.argThat +import org.mockito.kotlin.eq +import org.mockito.kotlin.given +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.http.HttpStatus +import org.springframework.http.MediaType +import org.springframework.test.web.reactive.server.WebTestClient +import org.springframework.test.web.reactive.server.expectBody +import reactor.core.publisher.Mono + +@OptIn(ExperimentalCoroutinesApi::class) +@WebFluxTest(HelpdeskController::class) +class HelpdeskControllerTest { + @Autowired lateinit var webClient: WebTestClient + + @MockBean lateinit var ecommerceService: EcommerceService + + @MockBean lateinit var pmService: PmService + + @Test + fun `post search transaction succeeded searching by payment token`() = runTest { + val pageNumber = 1 + val pageSize = 15 + val request = HelpdeskTestUtils.buildSearchRequestByPaymentToken() + given( + ecommerceService.searchTransaction( + pageNumber = eq(pageNumber), + pageSize = eq(pageSize), + ecommerceSearchTransactionRequestDto = + argThat { + this is SearchTransactionRequestPaymentTokenDto && + this.paymentToken == request.paymentToken + } + ) + ) + .willReturn(Mono.just(SearchTransactionResponseDto())) + webClient + .post() + .uri { uriBuilder -> + uriBuilder + .path("/helpdesk/searchTransaction") + .queryParam("pageNumber", "{pageNumber}") + .queryParam("pageSize", "{pageSize}") + .build(pageNumber, pageSize) + } + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(request) + .exchange() + .expectStatus() + .isOk + } + + @Test + fun `post search transaction succeeded searching by rpt id`() = runTest { + val pageNumber = 1 + val pageSize = 15 + val request = HelpdeskTestUtils.buildSearchRequestByRptId() + given( + ecommerceService.searchTransaction( + pageNumber = eq(pageNumber), + pageSize = eq(pageSize), + ecommerceSearchTransactionRequestDto = + argThat { + this is SearchTransactionRequestRptIdDto && this.rptId == request.rptId + } + ) + ) + .willReturn(Mono.just(SearchTransactionResponseDto())) + webClient + .post() + .uri { uriBuilder -> + uriBuilder + .path("/helpdesk/searchTransaction") + .queryParam("pageNumber", "{pageNumber}") + .queryParam("pageSize", "{pageSize}") + .build(pageNumber, pageSize) + } + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(request) + .exchange() + .expectStatus() + .isOk + } + + @Test + fun `post search transaction succeeded searching by transaction id`() = runTest { + val pageNumber = 1 + val pageSize = 15 + val request = HelpdeskTestUtils.buildSearchRequestByTransactionId() + given( + ecommerceService.searchTransaction( + pageNumber = eq(pageNumber), + pageSize = eq(pageSize), + ecommerceSearchTransactionRequestDto = + argThat { + this is SearchTransactionRequestTransactionIdDto && + this.transactionId == request.transactionId + } + ) + ) + .willReturn(Mono.just(SearchTransactionResponseDto())) + webClient + .post() + .uri { uriBuilder -> + uriBuilder + .path("/helpdesk/searchTransaction") + .queryParam("pageNumber", "{pageNumber}") + .queryParam("pageSize", "{pageSize}") + .build(pageNumber, pageSize) + } + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(request) + .exchange() + .expectStatus() + .isOk + } + + @Test + fun `post search transaction succeeded searching by user email`() = runTest { + val pageNumber = 1 + val pageSize = 15 + val request = HelpdeskTestUtils.buildSearchRequestByUserMail() + given( + pmService.searchTransaction( + pageNumber = eq(pageNumber), + pageSize = eq(pageSize), + pmSearchTransactionRequestDto = + argThat { + this is SearchTransactionRequestEmailDto && + this.userEmail == request.userEmail + } + ) + ) + .willReturn(Mono.just(SearchTransactionResponseDto())) + webClient + .post() + .uri { uriBuilder -> + uriBuilder + .path("/helpdesk/searchTransaction") + .queryParam("pageNumber", "{pageNumber}") + .queryParam("pageSize", "{pageSize}") + .build(pageNumber, pageSize) + } + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(request) + .exchange() + .expectStatus() + .isOk + } + + @Test + fun `post search transaction succeeded searching by user fiscal code`() = runTest { + val pageNumber = 1 + val pageSize = 15 + val request = HelpdeskTestUtils.buildSearchRequestByFiscalCode() + given( + pmService.searchTransaction( + pageNumber = eq(pageNumber), + pageSize = eq(pageSize), + pmSearchTransactionRequestDto = + argThat { + this is SearchTransactionRequestFiscalCodeDto && + this.userFiscalCode == request.userFiscalCode + } + ) + ) + .willReturn(Mono.just(SearchTransactionResponseDto())) + webClient + .post() + .uri { uriBuilder -> + uriBuilder + .path("/helpdesk/searchTransaction") + .queryParam("pageNumber", "{pageNumber}") + .queryParam("pageSize", "{pageSize}") + .build(pageNumber, pageSize) + } + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(request) + .exchange() + .expectStatus() + .isOk + } + + @Test + fun `post search transaction should return 404 for no transaction found`() = runTest { + val pageNumber = 1 + val pageSize = 15 + val request = HelpdeskTestUtils.buildSearchRequestByTransactionId() + val expected = + HelpdeskTestUtils.buildProblemJson( + httpStatus = HttpStatus.NOT_FOUND, + title = "No result found", + description = "No result can be found searching for criteria ${request.type}" + ) + given( + ecommerceService.searchTransaction( + pageNumber = eq(pageNumber), + pageSize = eq(pageSize), + ecommerceSearchTransactionRequestDto = + argThat { + this is SearchTransactionRequestTransactionIdDto && + this.transactionId == request.transactionId + } + ) + ) + .willReturn(Mono.error(NoResultFoundException(request.type))) + webClient + .post() + .uri { uriBuilder -> + uriBuilder + .path("/helpdesk/searchTransaction") + .queryParam("pageNumber", "{pageNumber}") + .queryParam("pageSize", "{pageSize}") + .build(pageNumber, pageSize) + } + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(request) + .exchange() + .expectStatus() + .isNotFound + .expectBody() + .isEqualTo(expected) + } + + @Test + fun `post search transaction should return 400 for bad request`() = runTest { + val pageNumber = 1 + val pageSize = 15 + + webClient + .post() + .uri { uriBuilder -> + uriBuilder + .path("/helpdesk/searchTransaction") + .queryParam("pageNumber", "{pageNumber}") + .queryParam("pageSize", "{pageSize}") + .build(pageNumber, pageSize) + } + .contentType(MediaType.APPLICATION_JSON) + .bodyValue("{}") + .exchange() + .expectStatus() + .isBadRequest + } + + @Test + fun `post search transaction should return 500 for unhandled error processing request`() = + runTest { + val pageNumber = 1 + val pageSize = 15 + val request = HelpdeskTestUtils.buildSearchRequestByTransactionId() + val expected = + HelpdeskTestUtils.buildProblemJson( + httpStatus = HttpStatus.INTERNAL_SERVER_ERROR, + title = "Error processing the request", + description = "Unhandled error" + ) + given( + ecommerceService.searchTransaction( + pageNumber = eq(pageNumber), + pageSize = eq(pageSize), + ecommerceSearchTransactionRequestDto = + argThat { + this is SearchTransactionRequestTransactionIdDto && + this.transactionId == request.transactionId + } + ) + ) + .willReturn(Mono.error(RuntimeException("Unhandled error"))) + webClient + .post() + .uri { uriBuilder -> + uriBuilder + .path("/helpdesk/searchTransaction") + .queryParam("pageNumber", "{pageNumber}") + .queryParam("pageSize", "{pageSize}") + .build(pageNumber, pageSize) + } + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(request) + .exchange() + .expectStatus() + .isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR) + .expectBody() + .isEqualTo(expected) + } +} diff --git a/src/test/kotlin/it/pagopa/ecommerce/helpdesk/controllers/PmControllerTest.kt b/src/test/kotlin/it/pagopa/ecommerce/helpdesk/controllers/PmControllerTest.kt new file mode 100644 index 0000000..2496642 --- /dev/null +++ b/src/test/kotlin/it/pagopa/ecommerce/helpdesk/controllers/PmControllerTest.kt @@ -0,0 +1,199 @@ +package it.pagopa.ecommerce.helpdesk.controllers + +import it.pagopa.ecommerce.helpdesk.HelpdeskTestUtils +import it.pagopa.ecommerce.helpdesk.exceptions.NoResultFoundException +import it.pagopa.ecommerce.helpdesk.services.PmService +import it.pagopa.generated.ecommerce.helpdesk.model.* +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import org.mockito.kotlin.argThat +import org.mockito.kotlin.eq +import org.mockito.kotlin.given +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.http.HttpStatus +import org.springframework.http.MediaType +import org.springframework.test.web.reactive.server.WebTestClient +import org.springframework.test.web.reactive.server.expectBody +import reactor.core.publisher.Mono + +@OptIn(ExperimentalCoroutinesApi::class) +@WebFluxTest(PmController::class) +class PmControllerTest { + @Autowired lateinit var webClient: WebTestClient + + @MockBean lateinit var pmService: PmService + + @Test + fun `post search transaction succeeded searching by user email`() = runTest { + val pageNumber = 1 + val pageSize = 15 + val request = HelpdeskTestUtils.buildSearchRequestByUserMail() + given( + pmService.searchTransaction( + pageNumber = eq(pageNumber), + pageSize = eq(pageSize), + pmSearchTransactionRequestDto = + argThat { + this is SearchTransactionRequestEmailDto && + this.userEmail == request.userEmail + } + ) + ) + .willReturn(Mono.just(SearchTransactionResponseDto())) + webClient + .post() + .uri { uriBuilder -> + uriBuilder + .path("/pm/searchTransaction") + .queryParam("pageNumber", "{pageNumber}") + .queryParam("pageSize", "{pageSize}") + .build(pageNumber, pageSize) + } + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(request) + .exchange() + .expectStatus() + .isOk + } + + @Test + fun `post search transaction succeeded searching by user fiscal code`() = runTest { + val pageNumber = 1 + val pageSize = 15 + val request = HelpdeskTestUtils.buildSearchRequestByFiscalCode() + given( + pmService.searchTransaction( + pageNumber = eq(pageNumber), + pageSize = eq(pageSize), + pmSearchTransactionRequestDto = + argThat { + this is SearchTransactionRequestFiscalCodeDto && + this.userFiscalCode == request.userFiscalCode + } + ) + ) + .willReturn(Mono.just(SearchTransactionResponseDto())) + webClient + .post() + .uri { uriBuilder -> + uriBuilder + .path("/pm/searchTransaction") + .queryParam("pageNumber", "{pageNumber}") + .queryParam("pageSize", "{pageSize}") + .build(pageNumber, pageSize) + } + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(request) + .exchange() + .expectStatus() + .isOk + } + + @Test + fun `post search transaction should return 404 for no transaction found`() = runTest { + val pageNumber = 1 + val pageSize = 15 + val request = HelpdeskTestUtils.buildSearchRequestByUserMail() + val expected = + HelpdeskTestUtils.buildProblemJson( + httpStatus = HttpStatus.NOT_FOUND, + title = "No result found", + description = "No result can be found searching for criteria ${request.type}" + ) + given( + pmService.searchTransaction( + pageNumber = eq(pageNumber), + pageSize = eq(pageSize), + pmSearchTransactionRequestDto = + argThat { + this is SearchTransactionRequestEmailDto && + this.userEmail == request.userEmail + } + ) + ) + .willReturn(Mono.error(NoResultFoundException(request.type))) + webClient + .post() + .uri { uriBuilder -> + uriBuilder + .path("/pm/searchTransaction") + .queryParam("pageNumber", "{pageNumber}") + .queryParam("pageSize", "{pageSize}") + .build(pageNumber, pageSize) + } + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(request) + .exchange() + .expectStatus() + .isNotFound + .expectBody() + .isEqualTo(expected) + } + + @Test + fun `post search transaction should return 400 for bad request`() = runTest { + val pageNumber = 1 + val pageSize = 15 + + webClient + .post() + .uri { uriBuilder -> + uriBuilder + .path("/pm/searchTransaction") + .queryParam("pageNumber", "{pageNumber}") + .queryParam("pageSize", "{pageSize}") + .build(pageNumber, pageSize) + } + .contentType(MediaType.APPLICATION_JSON) + .bodyValue("{}") + .exchange() + .expectStatus() + .isBadRequest + } + + @Test + fun `post search transaction should return 500 for unhandled error processing request`() = + runTest { + val pageNumber = 1 + val pageSize = 15 + val request = HelpdeskTestUtils.buildSearchRequestByUserMail() + val expected = + HelpdeskTestUtils.buildProblemJson( + httpStatus = HttpStatus.INTERNAL_SERVER_ERROR, + title = "Error processing the request", + description = "Unhandled error" + ) + given( + pmService.searchTransaction( + pageNumber = eq(pageNumber), + pageSize = eq(pageSize), + pmSearchTransactionRequestDto = + argThat { + this is SearchTransactionRequestEmailDto && + this.userEmail == request.userEmail + } + ) + ) + .willReturn(Mono.error(RuntimeException("Unhandled error"))) + webClient + .post() + .uri { uriBuilder -> + uriBuilder + .path("/pm/searchTransaction") + .queryParam("pageNumber", "{pageNumber}") + .queryParam("pageSize", "{pageSize}") + .build(pageNumber, pageSize) + } + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(request) + .exchange() + .expectStatus() + .isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR) + .expectBody() + .isEqualTo(expected) + } +} diff --git a/src/test/kotlin/it/pagopa/ecommerce/helpdesk/exceptionhandler/ExceptionHandlerTest.kt b/src/test/kotlin/it/pagopa/ecommerce/helpdesk/exceptionhandler/ExceptionHandlerTest.kt new file mode 100644 index 0000000..15a555c --- /dev/null +++ b/src/test/kotlin/it/pagopa/ecommerce/helpdesk/exceptionhandler/ExceptionHandlerTest.kt @@ -0,0 +1,81 @@ +package it.pagopa.ecommerce.helpdesk.exceptionhandler + +import it.pagopa.ecommerce.helpdesk.HelpdeskTestUtils +import it.pagopa.ecommerce.helpdesk.exceptions.NoResultFoundException +import it.pagopa.ecommerce.helpdesk.exceptions.RestApiException +import jakarta.xml.bind.ValidationException +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.springframework.http.HttpStatus + +class ExceptionHandlerTest { + + private val exceptionHandler = ExceptionHandler() + + @Test + fun `Should handle RestApiException`() { + val response = + exceptionHandler.handleException( + RestApiException( + httpStatus = HttpStatus.UNAUTHORIZED, + title = "title", + description = "description" + ) + ) + assertEquals( + HelpdeskTestUtils.buildProblemJson( + httpStatus = HttpStatus.UNAUTHORIZED, + title = "title", + description = "description" + ), + response.body + ) + assertEquals(HttpStatus.UNAUTHORIZED, response.statusCode) + } + + @Test + fun `Should handle ApiError`() { + val searchCriteria = "searchCriteria" + val exception = NoResultFoundException(searchCriteria) + val response = exceptionHandler.handleException(exception) + assertEquals( + HelpdeskTestUtils.buildProblemJson( + httpStatus = HttpStatus.NOT_FOUND, + title = "No result found", + description = "No result can be found searching for criteria $searchCriteria" + ), + response.body + ) + assertEquals(HttpStatus.NOT_FOUND, response.statusCode) + } + + @Test + fun `Should handle ValidationExceptions`() { + val exception = ValidationException("Invalid request") + val response = exceptionHandler.handleRequestValidationException(exception) + assertEquals( + HelpdeskTestUtils.buildProblemJson( + httpStatus = HttpStatus.BAD_REQUEST, + title = "Bad request", + description = "Invalid request" + ), + response.body + ) + assertEquals(HttpStatus.BAD_REQUEST, response.statusCode) + } + + @Test + fun `Should handle generic exception`() { + val exception = NullPointerException("Nullpointer exception") + val response = exceptionHandler.handleGenericException(exception) + assertEquals( + HelpdeskTestUtils.buildProblemJson( + httpStatus = HttpStatus.INTERNAL_SERVER_ERROR, + title = "Error processing the request", + description = "Nullpointer exception" + ), + response.body + ) + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.statusCode) + } +}