From a4ac1d66bdf795f0215ad98fdb0550ce300ed939 Mon Sep 17 00:00:00 2001 From: empassaro <113031808+empassaro@users.noreply.github.com> Date: Wed, 27 Nov 2024 09:26:38 +0100 Subject: [PATCH] SELC-5619 refactor: removed Notification resend api and related classes (#619) --- apps/onboarding-ms/src/main/docs/openapi.json | 77 --------- apps/onboarding-ms/src/main/docs/openapi.yaml | 55 ------- .../controller/NotificationController.java | 32 ---- .../onboarding/mapper/OnboardingMapper.java | 8 - .../service/NotificationService.java | 8 - .../service/NotificationServiceDefault.java | 115 -------------- .../NotificationControllerTest.java | 40 ----- .../NotificationServiceDefaultTest.java | 150 ------------------ 8 files changed, 485 deletions(-) delete mode 100644 apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/controller/NotificationController.java delete mode 100644 apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/service/NotificationService.java delete mode 100644 apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/service/NotificationServiceDefault.java delete mode 100644 apps/onboarding-ms/src/test/java/it/pagopa/selfcare/onboarding/controller/NotificationControllerTest.java delete mode 100644 apps/onboarding-ms/src/test/java/it/pagopa/selfcare/onboarding/service/NotificationServiceDefaultTest.java diff --git a/apps/onboarding-ms/src/main/docs/openapi.json b/apps/onboarding-ms/src/main/docs/openapi.json index 61c9ca486..3053df1cd 100644 --- a/apps/onboarding-ms/src/main/docs/openapi.json +++ b/apps/onboarding-ms/src/main/docs/openapi.json @@ -13,8 +13,6 @@ } ], "tags" : [ { "name" : "Aggregates Controller" - }, { - "name" : "Notification Controller" }, { "name" : "Onboarding" }, { @@ -209,44 +207,6 @@ } ] } }, - "/v1/notification/resend" : { - "post" : { - "tags" : [ "Notification Controller" ], - "summary" : "Resend onboarding notifications for onboarding which are retrieved given a set of filters", - "description" : "Resends notifications for all onboarding records that match the specified filter criteria. This allows administrators to trigger notification processes again for specific onboardings based on parameters such as product ID, tax code, institution ID, and more.", - "operationId" : "resendOnboardingNotifications", - "requestBody" : { - "content" : { - "application/json" : { - "schema" : { - "$ref" : "#/components/schemas/OnboardingGetFilters" - } - } - } - }, - "responses" : { - "200" : { - "description" : "OK", - "content" : { - "application/json" : { - "schema" : { - "type" : "string" - } - } - } - }, - "401" : { - "description" : "Not Authorized" - }, - "403" : { - "description" : "Not Allowed" - } - }, - "security" : [ { - "SecurityScheme" : [ ] - } ] - } - }, "/v1/onboarding" : { "get" : { "tags" : [ "Onboarding Controller" ], @@ -2294,43 +2254,6 @@ } } }, - "OnboardingGetFilters" : { - "type" : "object", - "properties" : { - "productId" : { - "type" : "string" - }, - "institutionId" : { - "type" : "string" - }, - "onboardingId" : { - "type" : "string" - }, - "subunitCode" : { - "type" : "string" - }, - "taxCode" : { - "type" : "string" - }, - "status" : { - "type" : "string" - }, - "from" : { - "type" : "string" - }, - "to" : { - "type" : "string" - }, - "page" : { - "format" : "int32", - "type" : "integer" - }, - "size" : { - "format" : "int32", - "type" : "integer" - } - } - }, "OnboardingGetResponse" : { "type" : "object", "properties" : { diff --git a/apps/onboarding-ms/src/main/docs/openapi.yaml b/apps/onboarding-ms/src/main/docs/openapi.yaml index a999d7f70..3a8087a21 100644 --- a/apps/onboarding-ms/src/main/docs/openapi.yaml +++ b/apps/onboarding-ms/src/main/docs/openapi.yaml @@ -10,7 +10,6 @@ servers: description: Auto generated value tags: - name: Aggregates Controller -- name: Notification Controller - name: Onboarding - name: Onboarding Controller - name: billing-portal @@ -153,35 +152,6 @@ paths: description: Not Allowed security: - SecurityScheme: [] - /v1/notification/resend: - post: - tags: - - Notification Controller - summary: Resend onboarding notifications for onboarding which are retrieved - given a set of filters - description: "Resends notifications for all onboarding records that match the\ - \ specified filter criteria. This allows administrators to trigger notification\ - \ processes again for specific onboardings based on parameters such as product\ - \ ID, tax code, institution ID, and more." - operationId: resendOnboardingNotifications - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/OnboardingGetFilters" - responses: - "200": - description: OK - content: - application/json: - schema: - type: string - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] /v1/onboarding: get: tags: @@ -1663,31 +1633,6 @@ components: type: string reasonForReject: type: string - OnboardingGetFilters: - type: object - properties: - productId: - type: string - institutionId: - type: string - onboardingId: - type: string - subunitCode: - type: string - taxCode: - type: string - status: - type: string - from: - type: string - to: - type: string - page: - format: int32 - type: integer - size: - format: int32 - type: integer OnboardingGetResponse: type: object properties: diff --git a/apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/controller/NotificationController.java b/apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/controller/NotificationController.java deleted file mode 100644 index 1c32952f7..000000000 --- a/apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/controller/NotificationController.java +++ /dev/null @@ -1,32 +0,0 @@ -package it.pagopa.selfcare.onboarding.controller; - -import io.quarkus.security.Authenticated; -import io.smallrye.mutiny.Uni; -import it.pagopa.selfcare.onboarding.model.OnboardingGetFilters; -import it.pagopa.selfcare.onboarding.service.NotificationService; -import jakarta.ws.rs.POST; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.core.MediaType; -import lombok.AllArgsConstructor; -import org.eclipse.microprofile.openapi.annotations.Operation; -import org.eclipse.microprofile.openapi.annotations.tags.Tag; - -@Authenticated -@Path("/v1/notification") -@Tag(name = "Notification Controller") -@AllArgsConstructor -public class NotificationController { - private final NotificationService notificationService; - @Operation( - summary = "Resend onboarding notifications for onboarding which are retrieved given a set of filters", - description = "Resends notifications for all onboarding records that match the specified filter criteria. This allows administrators to trigger notification processes again for specific onboardings based on parameters such as product ID, tax code, institution ID, and more." - ) - @POST - @Produces(MediaType.APPLICATION_JSON) - @Path("/resend") - public Uni resendOnboardingNotifications(OnboardingGetFilters filters) { - return notificationService.resendOnboardingNotifications(filters) - .replaceWith("Request taken charge"); - } -} diff --git a/apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/mapper/OnboardingMapper.java b/apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/mapper/OnboardingMapper.java index 20405141b..29826d695 100644 --- a/apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/mapper/OnboardingMapper.java +++ b/apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/mapper/OnboardingMapper.java @@ -89,14 +89,6 @@ default LocalDateTime getActivatedAt(OnboardingImportContract onboardingImportCo return onboardingImportContract.getCreatedAt(); } - @Mapping(target = "workflowType", source = "workflowType", qualifiedByName = "toWorkflowType") - @Mapping(target = "users", source = "users", qualifiedByName = "toUsers") - @Mapping(target = "createdAt", source = "createdAt", qualifiedByName = "toOffsetDateTime") - @Mapping(target = "updatedAt", source = "updatedAt", qualifiedByName = "toOffsetDateTime") - @Mapping(target = "activatedAt", source = "activatedAt", qualifiedByName = "toOffsetDateTime") - @Mapping(target = "expiringDate", source = "expiringDate", qualifiedByName = "toOffsetDateTime") - org.openapi.quarkus.onboarding_functions_json.model.Onboarding mapOnboardingForNotification(Onboarding onboarding); - @Named("toWorkflowType") default org.openapi.quarkus.onboarding_functions_json.model.WorkflowType toWorkflowType(WorkflowType workflowType) { if (Objects.isNull(workflowType)) { diff --git a/apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/service/NotificationService.java b/apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/service/NotificationService.java deleted file mode 100644 index 74bac9839..000000000 --- a/apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/service/NotificationService.java +++ /dev/null @@ -1,8 +0,0 @@ -package it.pagopa.selfcare.onboarding.service; - -import io.smallrye.mutiny.Uni; -import it.pagopa.selfcare.onboarding.model.OnboardingGetFilters; - -public interface NotificationService { - Uni resendOnboardingNotifications(OnboardingGetFilters filters); -} diff --git a/apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/service/NotificationServiceDefault.java b/apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/service/NotificationServiceDefault.java deleted file mode 100644 index 222cc54a8..000000000 --- a/apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/service/NotificationServiceDefault.java +++ /dev/null @@ -1,115 +0,0 @@ -package it.pagopa.selfcare.onboarding.service; - -import io.quarkus.mongodb.panache.reactive.ReactivePanacheQuery; -import io.smallrye.mutiny.Multi; -import io.smallrye.mutiny.Uni; -import io.smallrye.mutiny.infrastructure.Infrastructure; -import it.pagopa.selfcare.onboarding.entity.Onboarding; -import it.pagopa.selfcare.onboarding.exception.InvalidRequestException; -import it.pagopa.selfcare.onboarding.mapper.OnboardingMapper; -import it.pagopa.selfcare.onboarding.model.OnboardingGetFilters; -import it.pagopa.selfcare.onboarding.util.QueryUtils; -import it.pagopa.selfcare.onboarding.util.SortEnum; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; -import org.apache.http.HttpStatus; -import org.bson.Document; -import org.eclipse.microprofile.rest.client.inject.RestClient; -import org.jboss.logging.Logger; -import org.jboss.resteasy.reactive.ClientWebApplicationException; -import org.openapi.quarkus.onboarding_functions_json.api.NotificationApi; -import org.openapi.quarkus.onboarding_functions_json.model.OnboardingStatus; - -import java.util.List; -import java.util.Map; -import java.util.concurrent.atomic.AtomicInteger; - -@ApplicationScoped -public class NotificationServiceDefault implements NotificationService { - private static final Logger LOG = Logger.getLogger(NotificationServiceDefault.class); - - private static final int PAGE_SIZE = 100; - - @Inject - OnboardingMapper onboardingMapper; - - @RestClient - @Inject - NotificationApi notificationApi; - - @Override - public Uni resendOnboardingNotifications(OnboardingGetFilters filters) { - checkFilters(filters); - Uni.createFrom().item(filters) - .emitOn(Infrastructure.getDefaultWorkerPool()) - .flatMap(this::asyncSendNotifications) - .subscribe() - .with( - ignored -> LOG.infof("Resent notifications for onboarding with filters: %s", filters), - e -> LOG.errorf("Error resending notifications for onboarding with filters: %s", filters, e) - ); - - return Uni.createFrom().voidItem(); - } - - private void checkFilters(OnboardingGetFilters filters) { - if(filters.getStatus() == null) { - throw new InvalidRequestException("Status is required"); - } - - if(!OnboardingStatus.COMPLETED.name().equals(filters.getStatus()) && !OnboardingStatus.DELETED.name().equals(filters.getStatus())) { - throw new InvalidRequestException("Status must be COMPLETED or DELETED"); - } - } - - public Uni asyncSendNotifications(OnboardingGetFilters filters) { - LOG.infof("Resending notifications for onboarding with filters: %s", filters); - - Document sort = QueryUtils.buildSortDocument(Onboarding.Fields.createdAt.name(), SortEnum.DESC); - Map queryParameter = QueryUtils.createMapForOnboardingQueryParameter(filters); - Document query = QueryUtils.buildQuery(queryParameter); - - return Multi.createBy().repeating() - .uni( - AtomicInteger::new, - currentPage -> runQueryAndSendNotification(query, sort, currentPage.getAndIncrement()) - ) - .until(countRetrievedOnboardings -> countRetrievedOnboardings == 0 || countRetrievedOnboardings != PAGE_SIZE) - .collect() - .asList() - .flatMap(ignored -> Uni.createFrom().nullItem()); - } - - private Uni runQueryAndSendNotification(Document query, Document sort, int page) { - LOG.infof("Running query and sending notification for page %d", page); - return runQuery(query, sort).page(page, PAGE_SIZE).stream() - .map(onboardingMapper::mapOnboardingForNotification) - .call(this::sendNotification) - .collect() - .asList() - .map(List::size) - .invoke(countRetrievedOnboardings -> LOG.infof("Sent %d notifications for page %d", countRetrievedOnboardings, page)); - - } - - private Uni sendNotification(org.openapi.quarkus.onboarding_functions_json.model.Onboarding onboarding) { - LOG.infof("Trying to send notification for onboarding with id %s", onboarding.getId()); - return notificationApi.apiNotificationPost(null, onboarding) - .onItem().invoke(ignored -> LOG.infof("Notification sent for onboarding with id %s", onboarding.getId())) - .onFailure().invoke(e -> LOG.errorv(e, "Error sending notification for onboarding with id %s", onboarding.getId())) - .onFailure(this::shouldIgnoreException).recoverWithNull() - .map(ignored -> null); - } - - private boolean shouldIgnoreException(Throwable t) { - if (t instanceof ClientWebApplicationException e) { - return e.getResponse().getStatus() == HttpStatus.SC_INTERNAL_SERVER_ERROR; - } - - return false; - } - - private ReactivePanacheQuery runQuery(Document query, Document sort) { - return Onboarding.find(query, sort); - } -} diff --git a/apps/onboarding-ms/src/test/java/it/pagopa/selfcare/onboarding/controller/NotificationControllerTest.java b/apps/onboarding-ms/src/test/java/it/pagopa/selfcare/onboarding/controller/NotificationControllerTest.java deleted file mode 100644 index 611e88613..000000000 --- a/apps/onboarding-ms/src/test/java/it/pagopa/selfcare/onboarding/controller/NotificationControllerTest.java +++ /dev/null @@ -1,40 +0,0 @@ -package it.pagopa.selfcare.onboarding.controller; - -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 it.pagopa.selfcare.onboarding.model.OnboardingGetFilters; -import it.pagopa.selfcare.onboarding.service.NotificationService; -import org.junit.jupiter.api.Test; - -import static io.restassured.RestAssured.given; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.verify; - -@QuarkusTest -@TestHTTPEndpoint(NotificationController.class) -@QuarkusTestResource(MongoTestResource.class) -class NotificationControllerTest { - @InjectMock - NotificationService notificationService; - - @Test - @TestSecurity(user = "userJwt") - void resendOnboardingNotifications_succeeds() { - OnboardingGetFilters filters = OnboardingGetFilters.builder() - .status("COMPLETED").build(); - - given() - .when() - .body(filters) - .contentType(ContentType.JSON).post("/resend") - .then() - .statusCode(200); - - verify(notificationService).resendOnboardingNotifications(any()); - } -} \ No newline at end of file diff --git a/apps/onboarding-ms/src/test/java/it/pagopa/selfcare/onboarding/service/NotificationServiceDefaultTest.java b/apps/onboarding-ms/src/test/java/it/pagopa/selfcare/onboarding/service/NotificationServiceDefaultTest.java deleted file mode 100644 index 75fab7975..000000000 --- a/apps/onboarding-ms/src/test/java/it/pagopa/selfcare/onboarding/service/NotificationServiceDefaultTest.java +++ /dev/null @@ -1,150 +0,0 @@ -package it.pagopa.selfcare.onboarding.service; - -import io.quarkus.mongodb.panache.reactive.ReactivePanacheQuery; -import io.quarkus.panache.mock.PanacheMock; -import io.quarkus.test.InjectMock; -import io.quarkus.test.junit.QuarkusTest; -import io.quarkus.test.vertx.RunOnVertxContext; -import io.smallrye.mutiny.Multi; -import io.smallrye.mutiny.Uni; -import io.smallrye.mutiny.helpers.test.UniAssertSubscriber; -import it.pagopa.selfcare.onboarding.common.OnboardingStatus; -import it.pagopa.selfcare.onboarding.entity.Onboarding; -import it.pagopa.selfcare.onboarding.exception.InvalidRequestException; -import it.pagopa.selfcare.onboarding.model.OnboardingGetFilters; -import jakarta.inject.Inject; -import org.apache.hc.core5.http.HttpStatus; -import org.bson.Document; -import org.eclipse.microprofile.rest.client.inject.RestClient; -import org.jboss.resteasy.reactive.ClientWebApplicationException; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.openapi.quarkus.onboarding_functions_json.api.NotificationApi; - -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - -@QuarkusTest -class NotificationServiceDefaultTest { - - @Inject - NotificationServiceDefault notificationServiceDefault; - - @InjectMock - @RestClient - NotificationApi notificationApi; - - @Test - @RunOnVertxContext - @DisplayName("Should return void item and take in charge the notification resend") - void shouldReturnVoidItemAndTakeInChargeNotificationResend() { - OnboardingGetFilters filters = OnboardingGetFilters.builder().status("COMPLETED").build(); - - UniAssertSubscriber subscriber = notificationServiceDefault.resendOnboardingNotifications(filters) - .subscribe() - .withSubscriber(UniAssertSubscriber.create()); - - subscriber.assertCompleted().assertItem(null); - } - - @Test - @DisplayName("Should try to send all notifications when notifications api calls succeed") - void shouldTryToSendAllNotifications() { - OnboardingGetFilters filters = OnboardingGetFilters.builder().status("COMPLETED").build(); - mockOnboardingFind(); - when(notificationApi.apiNotificationPost(any(), any())) - .thenReturn(Uni.createFrom().nullItem()); - - UniAssertSubscriber subscriber = notificationServiceDefault.asyncSendNotifications(filters) - .subscribe() - .withSubscriber(UniAssertSubscriber.create()); - - subscriber.assertCompleted().awaitSubscription().assertItem(null); - verify(notificationApi, times(3)).apiNotificationPost(any(), any()); - subscriber.cancel(); - } - - @Test - @DisplayName("Should try to send all notifications when notifications api calls throw ignorable error") - void shouldTryToSendAllNotificationsWhenApiThrowIgnorableError() { - OnboardingGetFilters filters = OnboardingGetFilters.builder().status("COMPLETED").build(); - mockOnboardingFind(); - - when(notificationApi.apiNotificationPost(any(), any())) - .thenReturn(Uni.createFrom().nullItem()) // first call - .thenReturn(Uni.createFrom().failure(new ClientWebApplicationException(HttpStatus.SC_INTERNAL_SERVER_ERROR))) // second call - .thenReturn(Uni.createFrom().nullItem()); // third call - - UniAssertSubscriber subscriber = notificationServiceDefault.asyncSendNotifications(filters) - .subscribe() - .withSubscriber(UniAssertSubscriber.create()); - - subscriber.assertCompleted().awaitSubscription(); - verify(notificationApi, times(3)).apiNotificationPost(any(), any()); - subscriber.cancel(); - } - - @Test - @RunOnVertxContext - @DisplayName("Should not send all notifications when notifications api calls throw non ignorable error") - void shouldNotSendAllNotificationsWhenApiThrowNonIgnorableError() { - OnboardingGetFilters filters = OnboardingGetFilters.builder().status("COMPLETED").build(); - mockOnboardingFind(); - - when(notificationApi.apiNotificationPost(any(), any())) - .thenReturn(Uni.createFrom().nullItem()) // first call - .thenReturn(Uni.createFrom().failure(new ClientWebApplicationException(HttpStatus.SC_TOO_MANY_REQUESTS))); // second call - - UniAssertSubscriber subscriber = notificationServiceDefault.asyncSendNotifications(filters) - .subscribe() - .withSubscriber(UniAssertSubscriber.create()); - - subscriber.assertFailed().awaitSubscription(); - verify(notificationApi, times(2)).apiNotificationPost(any(), any()); - } - - private void mockOnboardingFind() { - ReactivePanacheQuery query = mock(ReactivePanacheQuery.class); - ReactivePanacheQuery queryPage = mock(ReactivePanacheQuery.class); - PanacheMock.mock(Onboarding.class); - when(Onboarding.find((Document) any(), any())).thenReturn(query); - when(query.page(anyInt(), anyInt())).thenReturn(queryPage); - List onboardingList = List.of(createDummyOnboarding(), createDummyOnboarding(), createDummyOnboarding()); - when(queryPage.stream()).thenReturn(Multi.createFrom().iterable(onboardingList)); - - } - - private Onboarding createDummyOnboarding() { - Onboarding onboarding = new Onboarding(); - onboarding.setId("id"); - onboarding.setStatus(OnboardingStatus.COMPLETED); - return onboarding; - } - - @Test - @DisplayName("Should resend notifications for deleted status") - void shouldResendNotificationsForDeletedStatus() { - OnboardingGetFilters filters = OnboardingGetFilters.builder().status("DELETED").build(); - - notificationServiceDefault.resendOnboardingNotifications(filters); - } - - @Test - @DisplayName("Should throw InvalidRequestException when status is null") - void shouldThrowInvalidRequestExceptionWhenStatusIsNull() { - OnboardingGetFilters filters = OnboardingGetFilters.builder().build(); - - assertThrows(InvalidRequestException.class, () -> notificationServiceDefault.resendOnboardingNotifications(filters)); - } - - @Test - @DisplayName("Should throw InvalidRequestException when status is not COMPLETED or DELETED") - void shouldThrowInvalidRequestExceptionWhenStatusIsNotCompletedOrDeleted() { - OnboardingGetFilters filters = OnboardingGetFilters.builder().status("INVALID_STATUS").build(); - - assertThrows(InvalidRequestException.class, () -> notificationServiceDefault.resendOnboardingNotifications(filters)); - } -} \ No newline at end of file