diff --git a/apps/onboarding-ms/src/main/docs/openapi.json b/apps/onboarding-ms/src/main/docs/openapi.json index c2fd6e87d..dcdb075a9 100644 --- a/apps/onboarding-ms/src/main/docs/openapi.json +++ b/apps/onboarding-ms/src/main/docs/openapi.json @@ -15,6 +15,8 @@ "name" : "Aggregates Controller" }, { "name" : "Document Controller" + }, { + "name" : "Institution Controller" }, { "name" : "Onboarding" }, { @@ -281,6 +283,47 @@ } ] } }, + "/v1/institutions" : { + "post" : { + "tags" : [ "Institution Controller" ], + "summary" : "Retrieve list of institutions given ids in input.", + "description" : "Retrieve list of institutions given ids in input. If list in input is nullor empty, a bad request exception is thrown", + "operationId" : "getInstitutions", + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/GetInstitutionRequest" + } + } + } + }, + "responses" : { + "200" : { + "description" : "OK", + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/InstitutionResponse" + } + } + } + } + }, + "401" : { + "description" : "Not Authorized" + }, + "403" : { + "description" : "Not Allowed" + } + }, + "security" : [ { + "SecurityScheme" : [ ] + } ] + } + }, "/v1/onboarding" : { "get" : { "tags" : [ "Onboarding Controller" ], @@ -2154,6 +2197,19 @@ } } }, + "GetInstitutionRequest" : { + "required" : [ "institutionIds" ], + "type" : "object", + "properties" : { + "institutionIds" : { + "minItems" : 1, + "type" : "array", + "items" : { + "type" : "string" + } + } + } + }, "InstitutionBaseRequest" : { "required" : [ "institutionType", "origin", "originId", "digitalAddress" ], "type" : "object", diff --git a/apps/onboarding-ms/src/main/docs/openapi.yaml b/apps/onboarding-ms/src/main/docs/openapi.yaml index 5bb45bdfd..62677a55d 100644 --- a/apps/onboarding-ms/src/main/docs/openapi.yaml +++ b/apps/onboarding-ms/src/main/docs/openapi.yaml @@ -11,6 +11,7 @@ servers: tags: - name: Aggregates Controller - name: Document Controller +- name: Institution Controller - name: Onboarding - name: Onboarding Controller - name: billing-portal @@ -205,6 +206,34 @@ paths: description: Not Allowed security: - SecurityScheme: [] + /v1/institutions: + post: + tags: + - Institution Controller + summary: Retrieve list of institutions given ids in input. + description: "Retrieve list of institutions given ids in input. If list in input\ + \ is nullor empty, a bad request exception is thrown" + operationId: getInstitutions + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/GetInstitutionRequest" + responses: + "200": + description: OK + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/InstitutionResponse" + "401": + description: Not Authorized + "403": + description: Not Allowed + security: + - SecurityScheme: [] /v1/onboarding: get: tags: @@ -1557,6 +1586,16 @@ components: type: string desc: type: string + GetInstitutionRequest: + required: + - institutionIds + type: object + properties: + institutionIds: + minItems: 1 + type: array + items: + type: string InstitutionBaseRequest: required: - institutionType diff --git a/apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/controller/InstitutionController.java b/apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/controller/InstitutionController.java new file mode 100644 index 000000000..3897cbd35 --- /dev/null +++ b/apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/controller/InstitutionController.java @@ -0,0 +1,43 @@ +package it.pagopa.selfcare.onboarding.controller; + +import io.quarkus.security.Authenticated; +import io.smallrye.mutiny.Multi; +import it.pagopa.selfcare.onboarding.controller.request.GetInstitutionRequest; +import it.pagopa.selfcare.onboarding.controller.response.InstitutionResponse; +import it.pagopa.selfcare.onboarding.service.InstitutionService; +import jakarta.validation.Valid; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.microprofile.openapi.annotations.Operation; +import org.eclipse.microprofile.openapi.annotations.tags.Tag; + +@Authenticated +@Path("/v1/institutions") +@Tag(name = "Institution Controller") +@AllArgsConstructor +@Slf4j +public class InstitutionController { + + private final InstitutionService institutionService; + + @Operation( + summary = "Retrieve list of institutions given ids in input.", + description = "Retrieve list of institutions given ids in input. If list in input is null" + + "or empty, a bad request exception is thrown" + ) + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Multi getInstitutions(@Valid GetInstitutionRequest institutionRequest) { + final List institutionIds = institutionRequest.getInstitutionIds(); + return institutionService.getInstitutions(institutionIds); + } + + +} diff --git a/apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/controller/request/GetInstitutionRequest.java b/apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/controller/request/GetInstitutionRequest.java new file mode 100644 index 000000000..a1a454581 --- /dev/null +++ b/apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/controller/request/GetInstitutionRequest.java @@ -0,0 +1,13 @@ +package it.pagopa.selfcare.onboarding.controller.request; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +@Data +public class GetInstitutionRequest { + @NotNull @NotEmpty + private List institutionIds; +} diff --git a/apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/service/InstitutionService.java b/apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/service/InstitutionService.java new file mode 100644 index 000000000..9dd4d8b2e --- /dev/null +++ b/apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/service/InstitutionService.java @@ -0,0 +1,10 @@ +package it.pagopa.selfcare.onboarding.service; + +import io.smallrye.mutiny.Multi; +import it.pagopa.selfcare.onboarding.controller.response.InstitutionResponse; +import java.util.List; + +public interface InstitutionService { + + Multi getInstitutions(List institutionIds); +} diff --git a/apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/service/InstitutionServiceDefault.java b/apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/service/InstitutionServiceDefault.java new file mode 100644 index 000000000..96ea24138 --- /dev/null +++ b/apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/service/InstitutionServiceDefault.java @@ -0,0 +1,43 @@ +package it.pagopa.selfcare.onboarding.service; + +import static it.pagopa.selfcare.onboarding.util.QueryUtils.FieldNames.INSTITUTION_ID; + +import io.smallrye.mutiny.Multi; +import it.pagopa.selfcare.onboarding.controller.response.InstitutionResponse; +import it.pagopa.selfcare.onboarding.entity.*; +import it.pagopa.selfcare.onboarding.util.QueryUtils; +import jakarta.enterprise.context.ApplicationScoped; +import java.util.*; + +import org.bson.Document; +import org.jboss.logging.Logger; + +@ApplicationScoped +public class InstitutionServiceDefault implements InstitutionService { + + private static final Logger LOG = Logger.getLogger(InstitutionServiceDefault.class); + + @Override + public Multi getInstitutions(List institutionIds) { + if (Objects.isNull(institutionIds) || institutionIds.isEmpty()) { + LOG.error("The parameter institutionIds cannot be null or empty"); + return Multi.createFrom().empty(); + } + Map queryParameterMap = new HashMap<>(); + queryParameterMap.put(INSTITUTION_ID, institutionIds); + Document query = QueryUtils.buildQuery(queryParameterMap); + return getDistinctOnboardings(query).onItem().transform(onboarding -> { + InstitutionResponse institutionResponse = new InstitutionResponse(); + institutionResponse.setId(onboarding.getInstitution().getId()); + institutionResponse.setInstitutionType(onboarding.getInstitution().getInstitutionType().name()); + institutionResponse.setDescription(onboarding.getInstitution().getDescription()); + return institutionResponse; + }); + } + + public Multi getDistinctOnboardings(Document query) { + Set seenInstitutionIds = new HashSet<>(); + return Onboarding.find(query).stream().map(Onboarding.class::cast) + .filter(onboarding -> seenInstitutionIds.add(onboarding.getInstitution().getId())); + } +} diff --git a/apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/util/QueryUtils.java b/apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/util/QueryUtils.java index 9f82e8953..7be88ab82 100644 --- a/apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/util/QueryUtils.java +++ b/apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/util/QueryUtils.java @@ -1,5 +1,7 @@ package it.pagopa.selfcare.onboarding.util; +import static it.pagopa.selfcare.onboarding.util.QueryUtils.FieldNames.*; + import com.mongodb.MongoClientSettings; import com.mongodb.client.model.Filters; import com.mongodb.client.model.Sorts; @@ -7,6 +9,10 @@ import it.pagopa.selfcare.onboarding.common.OnboardingStatus; import it.pagopa.selfcare.onboarding.entity.Onboarding; import it.pagopa.selfcare.onboarding.model.OnboardingGetFilters; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; import org.apache.commons.lang3.StringUtils; import org.bson.BsonDocument; import org.bson.BsonDocumentReader; @@ -15,19 +21,12 @@ import org.bson.codecs.DocumentCodec; import org.bson.conversions.Bson; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.*; - -import static it.pagopa.selfcare.onboarding.util.QueryUtils.FieldNames.*; - public class QueryUtils { private QueryUtils() { } - static class FieldNames { + public static class FieldNames { private FieldNames() { } diff --git a/apps/onboarding-ms/src/test/java/it/pagopa/selfcare/onboarding/controller/InstitutionControllerTest.java b/apps/onboarding-ms/src/test/java/it/pagopa/selfcare/onboarding/controller/InstitutionControllerTest.java new file mode 100644 index 000000000..da5849648 --- /dev/null +++ b/apps/onboarding-ms/src/test/java/it/pagopa/selfcare/onboarding/controller/InstitutionControllerTest.java @@ -0,0 +1,95 @@ +package it.pagopa.selfcare.onboarding.controller; + +import static io.restassured.RestAssured.given; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import io.quarkus.test.InjectMock; +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.common.http.TestHTTPEndpoint; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.mongodb.MongoTestResource; +import io.quarkus.test.security.TestSecurity; +import io.restassured.http.ContentType; +import io.restassured.response.Response; +import io.smallrye.mutiny.Multi; +import io.smallrye.mutiny.Uni; +import it.pagopa.selfcare.onboarding.common.InstitutionType; +import it.pagopa.selfcare.onboarding.common.OnboardingStatus; +import it.pagopa.selfcare.onboarding.common.Origin; +import it.pagopa.selfcare.onboarding.common.WorkflowType; +import it.pagopa.selfcare.onboarding.constants.CustomError; +import it.pagopa.selfcare.onboarding.controller.request.*; +import it.pagopa.selfcare.onboarding.controller.response.InstitutionResponse; +import it.pagopa.selfcare.onboarding.controller.response.OnboardingGet; +import it.pagopa.selfcare.onboarding.controller.response.OnboardingGetResponse; +import it.pagopa.selfcare.onboarding.controller.response.OnboardingResponse; +import it.pagopa.selfcare.onboarding.entity.Billing; +import it.pagopa.selfcare.onboarding.entity.CheckManagerResponse; +import it.pagopa.selfcare.onboarding.entity.Onboarding; +import it.pagopa.selfcare.onboarding.entity.OnboardingAggregationImportRequest; +import it.pagopa.selfcare.onboarding.exception.InvalidRequestException; +import it.pagopa.selfcare.onboarding.model.OnboardingGetFilters; +import it.pagopa.selfcare.onboarding.model.RecipientCodeStatus; +import it.pagopa.selfcare.onboarding.service.InstitutionService; +import it.pagopa.selfcare.onboarding.service.OnboardingService; +import java.io.File; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; + +@QuarkusTest +@TestHTTPEndpoint(InstitutionController.class) +@QuarkusTestResource(MongoTestResource.class) +class InstitutionControllerTest { + + @InjectMock InstitutionService institutionService; + + @Test + @TestSecurity(user = "userJwt") + void getInstitutions_withEmptyList() { + + given() + .when() + .body(new GetInstitutionRequest()) + .contentType(ContentType.JSON) + .post() + .then() + .statusCode(400); + } + + @Test + @TestSecurity(user = "userJwt") + void getInstitutions() { + final String institutionId = "institutionId"; + InstitutionResponse response = new InstitutionResponse(); + response.setId(institutionId); + response.setInstitutionType(InstitutionType.GPU.name()); + + GetInstitutionRequest request = new GetInstitutionRequest(); + request.setInstitutionIds(List.of(institutionId)); + + Mockito.when(institutionService.getInstitutions(any())) + .thenReturn(Multi.createFrom().item(response)); + + given().when().body(request).contentType(ContentType.JSON).post().then().statusCode(200); + } +} \ No newline at end of file diff --git a/apps/onboarding-ms/src/test/java/it/pagopa/selfcare/onboarding/service/InstitutionServiceDefaultTest.java b/apps/onboarding-ms/src/test/java/it/pagopa/selfcare/onboarding/service/InstitutionServiceDefaultTest.java new file mode 100644 index 000000000..cada01539 --- /dev/null +++ b/apps/onboarding-ms/src/test/java/it/pagopa/selfcare/onboarding/service/InstitutionServiceDefaultTest.java @@ -0,0 +1,74 @@ +package it.pagopa.selfcare.onboarding.service; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +import io.quarkus.mongodb.panache.reactive.ReactivePanacheQuery; +import io.quarkus.panache.mock.PanacheMock; +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.mongodb.MongoTestResource; +import io.quarkus.test.vertx.RunOnVertxContext; +import io.quarkus.test.vertx.UniAsserter; +import io.smallrye.mutiny.Multi; +import io.smallrye.mutiny.helpers.test.AssertSubscriber; +import it.pagopa.selfcare.onboarding.common.*; +import it.pagopa.selfcare.onboarding.controller.response.*; +import it.pagopa.selfcare.onboarding.entity.*; +import jakarta.inject.Inject; +import java.util.*; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +@QuarkusTest +@QuarkusTestResource(MongoTestResource.class) +class InstitutionServiceDefaultTest { + + @Inject + InstitutionServiceDefault institutionService; + + + @Test + void getInstitutions() { + Onboarding onboarding = new Onboarding(); + onboarding.setInstitution(dummyInstitution()); + PanacheMock.mock(Onboarding.class); + ReactivePanacheQuery query = Mockito.mock(ReactivePanacheQuery.class); + when(query.stream()).thenReturn(Multi.createFrom().item(onboarding)); + when(Onboarding.find(any())).thenReturn(query); + final List institutionIds = List.of("institutionId"); + + AssertSubscriber subscriber = institutionService + .getInstitutions(institutionIds) + .subscribe().withSubscriber(AssertSubscriber.create(20)); + + // Attendi il completamento e verifica i risultati + subscriber.awaitCompletion().assertCompleted(); + List resultList = subscriber.getItems(); + + // Verifica il risultato + assertNotNull(resultList); + assertFalse(resultList.isEmpty()); + resultList.forEach(actual -> { + assertNotNull(actual); + assertEquals(onboarding.getInstitution().getId(), actual.getId()); + }); + } + + @Test + @RunOnVertxContext + void getInstitutions_withEmptyIds(UniAsserter asserter) { + final List institutionIds = List.of(); + PanacheMock.mock(Onboarding.class); + asserter.execute(() -> institutionService.getInstitutions(institutionIds)); + PanacheMock.verifyNoInteractions(Onboarding.class); + } + + private Institution dummyInstitution() { + Institution institution = new Institution(); + institution.setId("institutionId"); + institution.setInstitutionType(InstitutionType.GSP); + return institution; + } +}