Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: P4PU-145 create feign client for biz-events service #5

Merged
merged 12 commits into from
Jun 4, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ plugins {
jacoco
id("org.sonarqube") version "5.0.0.4638"
id("com.github.ben-manes.versions") version "0.51.0"
id ("org.openapi.generator") version "7.5.0"
id("org.openapi.generator") version "7.5.0"
}

group = "it.gov.pagopa"
Expand All @@ -28,6 +28,7 @@ repositories {
val springdocOpenApiVersion = "2.5.0"
val janinoVersion = "3.1.12"
val openApiToolsVersion = "0.2.6"
val wiremockVersion = "3.5.4"

dependencies {
implementation("org.springframework.boot:spring-boot-starter")
Expand All @@ -36,6 +37,7 @@ dependencies {
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:$springdocOpenApiVersion")
implementation("org.codehaus.janino:janino:$janinoVersion")
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
implementation ("org.springframework.cloud:spring-cloud-starter-openfeign")
implementation("org.openapitools:jackson-databind-nullable:$openApiToolsVersion")
compileOnly("org.projectlombok:lombok")
annotationProcessor("org.projectlombok:lombok")
Expand All @@ -45,7 +47,7 @@ dependencies {
testImplementation("org.junit.jupiter:junit-jupiter-api")
testImplementation("org.junit.jupiter:junit-jupiter-engine")
testImplementation("org.mockito:mockito-core")

testImplementation ("org.wiremock:wiremock-standalone:$wiremockVersion")
}

tasks.withType<Test> {
Expand All @@ -60,6 +62,12 @@ tasks.jacocoTestReport {
}
}

dependencyManagement {
imports {
mavenBom("org.springframework.cloud:spring-cloud-dependencies:2023.0.1")
}
}

val projectInfo = mapOf(
"artifactId" to project.name,
"version" to project.version
Expand All @@ -72,6 +80,7 @@ tasks {
}
}
}

configure<SourceSetContainer> {
named("main") {
java.srcDir("$projectDir/build/generated/src/main/java")
Expand Down
3 changes: 3 additions & 0 deletions helm/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ microservice-chart:
APPLICATIONINSIGHTS_PREVIEW_PROFILER_ENABLED: "false"
ENABLE_AUDIT_APPENDER: "TRUE"

externalConfigMapValues:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why externalConfigMapValues? do we have a config map defined in the infrastructure code?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rest-client:
BIZ_EVENTS_BASE_URL: bizevents-base-url

envSecret:
APPLICATIONINSIGHTS_CONNECTION_STRING: appinsights-connection-string
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/it/gov/pagopa/arc/config/FeignConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package it.gov.pagopa.arc.config;

import it.gov.pagopa.arc.connector.bizevents.BizEventsRestClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableFeignClients(clients = {BizEventsRestClient.class})
public class FeignConfig {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package it.gov.pagopa.arc.connector.bizevents;

import it.gov.pagopa.arc.connector.bizevents.dto.BizEventsTransactionsListDTO;

public interface BizEventsConnector {
BizEventsTransactionsListDTO getTransactionsList(String fiscalCode, String continuationToken, int size);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package it.gov.pagopa.arc.connector.bizevents;

import feign.FeignException;
import it.gov.pagopa.arc.connector.bizevents.dto.BizEventsTransactionsListDTO;
import it.gov.pagopa.arc.exception.custom.BizEventsInvocationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;

import java.util.ArrayList;

@Service
@Slf4j
public class BizEventsConnectorImpl implements BizEventsConnector {
private final String apikey;
private final BizEventsRestClient bizEventsRestClient;

public BizEventsConnectorImpl(@Value("${rest-client.biz-events.api-key}") String apikey,
BizEventsRestClient bizEventsRestClient) {
this.apikey = apikey;
this.bizEventsRestClient = bizEventsRestClient;
}

@Override
public BizEventsTransactionsListDTO getTransactionsList(String fiscalCode, String continuationToken, int size) {
BizEventsTransactionsListDTO bizEventsTransactionsListDTO;
try {
bizEventsTransactionsListDTO = bizEventsRestClient.transactionsList(apikey, fiscalCode, continuationToken, size);
}catch (FeignException e) {
if (e.status() == HttpStatus.NOT_FOUND.value()){
bizEventsTransactionsListDTO =
BizEventsTransactionsListDTO
.builder()
.transactions(new ArrayList<>())
.build();

log.info("A {} occurred handling request getTransactionsList from biz-Events: HttpStatus {} - {}",
e.getClass(),
e.status(),
e.getMessage());
}else {
throw new BizEventsInvocationException("An error occurred handling request from biz-Events");
}
}
return bizEventsTransactionsListDTO;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package it.gov.pagopa.arc.connector.bizevents;

import it.gov.pagopa.arc.connector.bizevents.dto.BizEventsTransactionsListDTO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;

@FeignClient(
name = "biz-events",
url = "${rest-client.biz-events.baseUrl}")
public interface BizEventsRestClient {
@GetMapping(
value = "/transactions",
produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
@ResponseStatus(HttpStatus.OK)
BizEventsTransactionsListDTO transactionsList(
@RequestHeader("x-api-key") String apikey,
@RequestHeader("x-fiscal-code") String fiscalCode,
@RequestHeader("x-continuation-token") String continuationToken,
@RequestParam("size") int size
);
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package it.gov.pagopa.arc.connector.bizevents.dto;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class BizEventsTransactionDTO {
private String transactionId;
private String payeeName;
private String payeeTaxCode;
private String amount;
private String transactionDate;
private Boolean isCart;
private Boolean isPayer;
private Boolean isDebtor;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package it.gov.pagopa.arc.connector.bizevents.dto;

import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@Data
@AllArgsConstructor
@Builder
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class BizEventsTransactionsListDTO {
private List<BizEventsTransactionDTO> transactions;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package it.gov.pagopa.arc.exception;

import it.gov.pagopa.arc.exception.custom.BizEventsInvocationException;
import it.gov.pagopa.arc.model.generated.ErrorDTO;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
@Slf4j
@Order(Ordered.HIGHEST_PRECEDENCE)
public class ArcExceptionHandler {

@ExceptionHandler(BizEventsInvocationException.class)
public ResponseEntity<ErrorDTO> handleBizEventsInvocationException(RuntimeException ex, HttpServletRequest request){
return handleArcErrorException(ex, request, HttpStatus.INTERNAL_SERVER_ERROR, ErrorDTO.ErrorEnum.GENERIC_ERROR);
}

private static ResponseEntity<ErrorDTO> handleArcErrorException(RuntimeException ex, HttpServletRequest request, HttpStatus httpStatus, ErrorDTO.ErrorEnum errorEnum) {
String message = ex.getMessage();
log.info("A {} occurred handling request {}: HttpStatus {} - {}",
ex.getClass(),
getRequestDetails(request),
httpStatus.value(),
message);

return ResponseEntity
.status(httpStatus)
.body(new ErrorDTO(errorEnum, message));
}

private static String getRequestDetails(HttpServletRequest request) {
return "%s %s".formatted(request.getMethod(), request.getRequestURI());
}
}
13 changes: 0 additions & 13 deletions src/main/java/it/gov/pagopa/arc/exception/ExceptionHandler.java

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package it.gov.pagopa.arc.exception.custom;

import lombok.Getter;

@Getter
public class BizEventsInvocationException extends RuntimeException{
public BizEventsInvocationException(String message){super(message);}
}
5 changes: 4 additions & 1 deletion src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,7 @@ management:
web:
exposure.include: info, health

apiKey.biz-events: \${BIZ_EVENTS_SERVICE_API_KEY:}
rest-client:
biz-events:
baseUrl: \${BIZ_EVENTS_BASE_URL:}
api-key: \${BIZ_EVENTS_SERVICE_API_KEY:}
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package it.gov.pagopa.arc.connector.bizevents;

import ch.qos.logback.classic.LoggerContext;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
import it.gov.pagopa.arc.config.FeignConfig;
import it.gov.pagopa.arc.connector.bizevents.dto.BizEventsTransactionsListDTO;
import it.gov.pagopa.arc.exception.custom.BizEventsInvocationException;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.openfeign.FeignAutoConfiguration;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.support.TestPropertySourceUtils;
import utils.MemoryAppender;

import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
@ContextConfiguration(
initializers = BizEventsConnectorImplTest.WireMockInitializer.class,
classes = {
BizEventsConnectorImpl.class,
FeignConfig.class,
BizEventsRestClient.class,
FeignAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class
})
@TestPropertySource(
properties = {
"rest-client.biz-events.api-key=x_api_key0",
})
class BizEventsConnectorImplTest {

@Autowired
private BizEventsConnector bizEventsConnector;
private MemoryAppender memoryAppender;

@BeforeEach
void setUp() {
ch.qos.logback.classic.Logger logger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger("it.gov.pagopa.arc.connector.bizevents.BizEventsConnectorImpl");
memoryAppender = new MemoryAppender();
memoryAppender.setContext((LoggerContext) LoggerFactory.getILoggerFactory());
logger.setLevel(ch.qos.logback.classic.Level.INFO);
logger.addAppender(memoryAppender);
memoryAppender.start();
}

@Test
void givenHeaderAndParameterWhenCallBizEventsConnectorThenReturnTransactionList() {
//given
//when
BizEventsTransactionsListDTO bizEventsTransactionsListDTO = bizEventsConnector.getTransactionsList("DUMMY_FISCAL_CODE", "TOKEN", 1);

//then
assertEquals(1, bizEventsTransactionsListDTO.getTransactions().size());
assertEquals("1", bizEventsTransactionsListDTO.getTransactions().get(0).getTransactionId());
assertEquals("Comune di Milano", bizEventsTransactionsListDTO.getTransactions().get(0).getPayeeName());
assertEquals("MI_XXX", bizEventsTransactionsListDTO.getTransactions().get(0).getPayeeTaxCode());
assertEquals("180,00", bizEventsTransactionsListDTO.getTransactions().get(0).getAmount());
assertEquals("2024-03-27T13:07:25Z", bizEventsTransactionsListDTO.getTransactions().get(0).getTransactionDate());
assertFalse(bizEventsTransactionsListDTO.getTransactions().get(0).getIsCart());
assertTrue(bizEventsTransactionsListDTO.getTransactions().get(0).getIsPayer());
assertTrue(bizEventsTransactionsListDTO.getTransactions().get(0).getIsDebtor());

}

@Test
void givenHeaderAndParameterWhenNotFoundThenReturnEmptyTransactionList() {
//given
//when
BizEventsTransactionsListDTO bizEventsTransactionsListDTO = bizEventsConnector.getTransactionsList("DUMMY_FISCAL_CODE_NOT_FOUND", "TOKEN", 2);
//then
Assertions.assertEquals(0,bizEventsTransactionsListDTO.getTransactions().size());
Assertions.assertTrue(memoryAppender.getLoggedEvents().get(0).getFormattedMessage()
.contains(("A class feign.FeignException$NotFound occurred handling request getTransactionsList from biz-Events: HttpStatus 404"))
);
}
@Test
void givenHeaderAndParameterWhenErrorThenThrowBizEventsInvocationException() {
//When
//Then
Assertions.assertThrows(BizEventsInvocationException.class,
()-> bizEventsConnector.getTransactionsList("DUMMY_FISCAL_CODE_ERROR", "TOKEN", 2));

}

public static class WireMockInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
WireMockServer wireMockServer = new WireMockServer(new WireMockConfiguration().usingFilesUnderClasspath("src/test/resources/stub"));
wireMockServer.start();

applicationContext.getBeanFactory().registerSingleton("wireMockServer", wireMockServer);
applicationContext.addApplicationListener(
applicationEvent -> {
if (applicationEvent instanceof ContextClosedEvent) {
wireMockServer.stop();
}
});

TestPropertySourceUtils.addInlinedPropertiesToEnvironment(
applicationContext,
String.format(
"rest-client.biz-events.baseUrl=http://%s:%d/bizEventsMock",
wireMockServer.getOptions().bindAddress(), wireMockServer.port()));
}
}
}
Loading