diff --git a/.rhcicd/clowdapp-engine.yaml b/.rhcicd/clowdapp-engine.yaml index 7459af91c5..003a7d6934 100644 --- a/.rhcicd/clowdapp-engine.yaml +++ b/.rhcicd/clowdapp-engine.yaml @@ -151,8 +151,6 @@ objects: key: client-id - name: PROCESSOR_EMAIL_BOP_ENV value: ${BACKOFFICE_CLIENT_ENV} - - name: PROCESSOR_EMAIL_BOP_SKIP_USERS_RESOLUTION - value: ${PROCESSOR_EMAIL_BOP_SKIP_USERS_RESOLUTION} - name: PROCESSOR_EMAIL_BOP_URL value: ${BACKOFFICE_SCHEME}://${BACKOFFICE_HOST}:${BACKOFFICE_PORT}/v1/sendEmails - name: PROCESSOR_EMAIL_NO_REPLY @@ -242,10 +240,6 @@ objects: value: ${NOTIFICATIONS_DRAWER_ENABLED} - name: NOTIFICATIONS_USE_MBOP_FOR_FETCHING_USERS value: ${NOTIFICATIONS_USE_MBOP_FOR_FETCHING_USERS} - - name: NOTIFICATIONS_WEBHOOK_CONNECTOR_ENABLED - value: ${NOTIFICATIONS_WEBHOOK_CONNECTOR_ENABLED} - - name: NOTIFICATIONS_EMAIL_CONNECTOR_ENABLED - value: ${NOTIFICATIONS_EMAIL_CONNECTOR_ENABLED} - name: NOTIFICATIONS_DRAWER_CONNECTOR_ENABLED value: ${NOTIFICATIONS_DRAWER_CONNECTOR_ENABLED} - name: NOTIFICATIONS_ASYNC_AGGREGATION_ENABLED @@ -331,9 +325,6 @@ parameters: - name: NOTIFICATIONS_LOG_LEVEL description: Log level for com.redhat.cloud.notifications value: INFO -- name: PROCESSOR_EMAIL_BOP_SKIP_USERS_RESOLUTION - description: Should BOP skip transforming usernames from our payload into email addresses using the IT Users Service? - value: "false" - name: QUARKUS_HIBERNATE_ORM_LOG_SQL value: "false" - name: QUARKUS_LOG_CLOUDWATCH_API_CALL_TIMEOUT @@ -416,11 +407,6 @@ parameters: value: "false" - name: NOTIFICATIONS_USE_MBOP_FOR_FETCHING_USERS value: "false" -- name: NOTIFICATIONS_WEBHOOK_CONNECTOR_ENABLED - value: "false" -- name: NOTIFICATIONS_EMAIL_CONNECTOR_ENABLED - description: Is the email connector enabled to process emails there instead of in the engine? - value: "false" - name: NOTIFICATIONS_DRAWER_CONNECTOR_ENABLED description: Is the drawer connector enabled to process them instead of in the engine? value: "false" diff --git a/common/src/main/java/com/redhat/cloud/notifications/config/FeatureFlipper.java b/common/src/main/java/com/redhat/cloud/notifications/config/FeatureFlipper.java index 4b83ba43e3..685b830190 100644 --- a/common/src/main/java/com/redhat/cloud/notifications/config/FeatureFlipper.java +++ b/common/src/main/java/com/redhat/cloud/notifications/config/FeatureFlipper.java @@ -80,21 +80,12 @@ public class FeatureFlipper { @ConfigProperty(name = "notifications.use-mbop-for-fetching-users", defaultValue = "false") boolean useMBOPForFetchingUsers; - @ConfigProperty(name = "notifications.webhook-connector.enabled", defaultValue = "false") - boolean webhookConnectorEnabled; - - @ConfigProperty(name = "notifications.email-connector.enabled", defaultValue = "false") - boolean emailConnectorEnabled; - @ConfigProperty(name = "notifications.drawer-connector.enabled", defaultValue = "false") boolean drawerConnectorEnabled; @ConfigProperty(name = "notifications.async-aggregation.enabled", defaultValue = "true") boolean asyncAggregation; - @ConfigProperty(name = "processor.email.bop.skip-users-resolution", defaultValue = "false") - boolean skipBopUsersResolution; - @ConfigProperty(name = "processor.email.aggregation.use-recipients-resolver-clowdapp.enabled", defaultValue = "false") boolean useRecipientsResolverClowdappForDailyDigestEnabled; @@ -114,11 +105,8 @@ void logFeaturesStatusAtStartup(@Observes StartupEvent event) { Log.infof("The integration with the export service is %s", exportServiceIntegrationEnabled ? "enabled" : "disabled"); Log.infof("Drawer feature is %s", drawerEnabled ? "enabled" : "disabled"); Log.infof("The use of BOP/MBOP for fetching users is %s", useMBOPForFetchingUsers ? "enabled" : "disabled"); - Log.infof("The webhook connector is %s", webhookConnectorEnabled ? "enabled" : "disabled"); - Log.infof("The email connector is %s", emailConnectorEnabled ? "enabled" : "disabled"); Log.infof("The drawer connector is %s", drawerConnectorEnabled ? "enabled" : "disabled"); Log.infof("The async aggregation is %s", asyncAggregation ? "enabled" : "disabled"); - Log.infof("The BOP users resolution is %s", !skipBopUsersResolution ? "enabled" : "disabled"); Log.infof("The Recipients resolver usage for daily digest is %s", useRecipientsResolverClowdappForDailyDigestEnabled ? "enabled" : "disabled"); } @@ -244,24 +232,6 @@ public void setUseMBOPForFetchingUsers(final boolean useMBOPForFetchingUsers) { this.useMBOPForFetchingUsers = useMBOPForFetchingUsers; } - public boolean isWebhookConnectorEnabled() { - return webhookConnectorEnabled; - } - - public void setWebhookConnectorEnabled(boolean webhookConnectorEnabled) { - checkTestLaunchMode(); - this.webhookConnectorEnabled = webhookConnectorEnabled; - } - - public boolean isEmailConnectorEnabled() { - return this.emailConnectorEnabled; - } - - public void setEmailConnectorEnabled(final boolean emailConnectorEnabled) { - checkTestLaunchMode(); - this.emailConnectorEnabled = emailConnectorEnabled; - } - public boolean isDrawerConnectorEnabled() { return drawerConnectorEnabled; } @@ -280,15 +250,6 @@ public void setAsyncAggregation(boolean asyncAggregation) { this.asyncAggregation = asyncAggregation; } - public boolean isSkipBopUsersResolution() { - return skipBopUsersResolution; - } - - public void setSkipBopUsersResolution(boolean skipBopUsersResolution) { - checkTestLaunchMode(); - this.skipBopUsersResolution = skipBopUsersResolution; - } - public boolean isUseRecipientsResolverClowdappForDailyDigestEnabled() { return useRecipientsResolverClowdappForDailyDigestEnabled; } diff --git a/engine/pom.xml b/engine/pom.xml index 38aa7b7d87..1110e1b972 100644 --- a/engine/pom.xml +++ b/engine/pom.xml @@ -105,12 +105,6 @@ ${apache.commons.csv.version} - - - io.smallrye.reactive - smallrye-mutiny-vertx-web-client - - com.redhat.cloud.event diff --git a/engine/src/main/java/com/redhat/cloud/notifications/events/EndpointErrorFromConnectorHelper.java b/engine/src/main/java/com/redhat/cloud/notifications/events/EndpointErrorFromConnectorHelper.java index a442e73ffd..12431381b1 100644 --- a/engine/src/main/java/com/redhat/cloud/notifications/events/EndpointErrorFromConnectorHelper.java +++ b/engine/src/main/java/com/redhat/cloud/notifications/events/EndpointErrorFromConnectorHelper.java @@ -1,7 +1,6 @@ package com.redhat.cloud.notifications.events; import com.redhat.cloud.notifications.db.repositories.EndpointRepository; -import com.redhat.cloud.notifications.db.repositories.NotificationHistoryRepository; import com.redhat.cloud.notifications.models.Endpoint; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.MeterRegistry; @@ -31,9 +30,6 @@ public class EndpointErrorFromConnectorHelper { @ConfigProperty(name = "processor.webhook.max-server-errors", defaultValue = "10") int maxServerErrors; - @Inject - NotificationHistoryRepository notificationHistoryRepository; - private Counter disabledWebhooksServerErrorCount; private Counter disabledWebhooksClientErrorCount; public static final String CLIENT_TAG_VALUE = "client"; diff --git a/engine/src/main/java/com/redhat/cloud/notifications/events/EndpointProcessor.java b/engine/src/main/java/com/redhat/cloud/notifications/events/EndpointProcessor.java index 24f877c76d..a581a39433 100644 --- a/engine/src/main/java/com/redhat/cloud/notifications/events/EndpointProcessor.java +++ b/engine/src/main/java/com/redhat/cloud/notifications/events/EndpointProcessor.java @@ -1,7 +1,6 @@ package com.redhat.cloud.notifications.events; import com.redhat.cloud.notifications.DelayedThrower; -import com.redhat.cloud.notifications.config.FeatureFlipper; import com.redhat.cloud.notifications.db.repositories.EndpointRepository; import com.redhat.cloud.notifications.ingress.Action; import com.redhat.cloud.notifications.models.Endpoint; @@ -56,9 +55,6 @@ public class EndpointProcessor { @Inject EmailSubscriptionTypeProcessor emailProcessor; - @Inject - FeatureFlipper featureFlipper; - @Inject SlackProcessor slackProcessor; @@ -130,11 +126,7 @@ public void process(Event event) { if (isAggregatorEvent(event)) { emailProcessor.processAggregation(event); } else { - if (this.featureFlipper.isEmailConnectorEnabled()) { - emailConnectorProcessor.process(event, endpointsByTypeEntry.getValue()); - } else { - emailProcessor.process(event, endpointsByTypeEntry.getValue()); - } + emailConnectorProcessor.process(event, endpointsByTypeEntry.getValue()); } break; case WEBHOOK: diff --git a/engine/src/main/java/com/redhat/cloud/notifications/processors/email/Email.java b/engine/src/main/java/com/redhat/cloud/notifications/processors/email/Email.java deleted file mode 100644 index 9f4d6d3fab..0000000000 --- a/engine/src/main/java/com/redhat/cloud/notifications/processors/email/Email.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.redhat.cloud.notifications.processors.email; - -import java.util.HashSet; -import java.util.Set; - -/** - * Input JSON format accepted by the BOP - */ -public class Email { - private String subject; - private String body; - private Set recipients; - private Set ccList; - private Set bccList; - private String bodyType; - - public Email() { - recipients = new HashSet<>(); - ccList = new HashSet<>(); - bccList = new HashSet<>(); - } - - public String getSubject() { - return subject; - } - - public void setSubject(String subject) { - this.subject = subject; - } - - public String getBody() { - return body; - } - - public void setBody(String body) { - this.body = body; - } - - public Set getRecipients() { - return recipients; - } - - public void setRecipients(Set recipients) { - this.recipients = recipients; - } - - public Set getCcList() { - return ccList; - } - - public void setCcList(Set ccList) { - this.ccList = ccList; - } - - public Set getBccList() { - return bccList; - } - - public void setBccList(Set bccList) { - this.bccList = bccList; - } - - public String getBodyType() { - return bodyType; - } - - public void setBodyType(String bodyType) { - this.bodyType = bodyType; - } -} diff --git a/engine/src/main/java/com/redhat/cloud/notifications/processors/email/EmailSender.java b/engine/src/main/java/com/redhat/cloud/notifications/processors/email/EmailSender.java deleted file mode 100644 index 0cdf703ebe..0000000000 --- a/engine/src/main/java/com/redhat/cloud/notifications/processors/email/EmailSender.java +++ /dev/null @@ -1,188 +0,0 @@ -package com.redhat.cloud.notifications.processors.email; - -import com.redhat.cloud.notifications.config.FeatureFlipper; -import com.redhat.cloud.notifications.events.EventWrapper; -import com.redhat.cloud.notifications.ingress.Action; -import com.redhat.cloud.notifications.models.Endpoint; -import com.redhat.cloud.notifications.models.Event; -import com.redhat.cloud.notifications.models.EventType; -import com.redhat.cloud.notifications.models.NotificationHistory; -import com.redhat.cloud.notifications.processors.webclient.BopWebClient; -import com.redhat.cloud.notifications.processors.webhooks.WebhookTypeProcessor; -import com.redhat.cloud.notifications.recipients.User; -import com.redhat.cloud.notifications.recipients.mbop.Constants; -import com.redhat.cloud.notifications.templates.TemplateService; -import com.redhat.cloud.notifications.utils.LineBreakCleaner; -import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.core.instrument.Timer; -import io.quarkus.logging.Log; -import io.quarkus.qute.TemplateInstance; -import io.vertx.core.json.JsonObject; -import io.vertx.mutiny.core.buffer.Buffer; -import io.vertx.mutiny.ext.web.client.HttpRequest; -import io.vertx.mutiny.ext.web.client.WebClient; -import jakarta.annotation.PostConstruct; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; -import org.eclipse.microprofile.config.inject.ConfigProperty; - -import java.time.Duration; -import java.time.LocalDateTime; -import java.time.ZoneOffset; -import java.util.Set; -import java.util.stream.Collectors; - -@ApplicationScoped -public class EmailSender { - - static final String BODY_TYPE_HTML = "html"; - static final ZoneOffset UTC = ZoneOffset.UTC; - - @Inject - @BopWebClient - WebClient bopWebClient; - - @ConfigProperty(name = "processor.email.bop_url") - String bopUrl; - - @ConfigProperty(name = "processor.email.bop_apitoken") - String bopApiToken; - - @ConfigProperty(name = "processor.email.bop_client_id") - String bopClientId; - - @ConfigProperty(name = "processor.email.bop_env") - String bopEnv; - - @Inject - FeatureFlipper featureFlipper; - - @Inject - WebhookTypeProcessor webhookSender; - - @Inject - TemplateService templateService; - - @Inject - MeterRegistry registry; - - private Timer processTime; - - @PostConstruct - public void init() { - processTime = registry.timer("processor.email.process-time"); - /* - * The token value we receive contains a line break because of the standard mime encryption. Gabor Burges tried - * to remove it but that didn't work, so we have to do it here because Vert.x 4 does not allow line breaks in - * HTTP headers. - */ - bopApiToken = LineBreakCleaner.clean(bopApiToken); - } - - public NotificationHistory sendEmail(Set users, Event event, TemplateInstance subject, TemplateInstance body, boolean persistHistory, Endpoint endpoint) { - - NotificationHistory history = null; - if (users.isEmpty()) { - Log.debug("No recipient found for this email"); - return history; - } - - final HttpRequest bopRequest = this.buildBOPHttpRequest(); - LocalDateTime start = LocalDateTime.now(UTC); - - Timer.Sample processedTimer = Timer.start(registry); - - EventType eventType = event.getEventType(); - String bundleName = "NA"; - String applicationName = "NA"; - if (eventType != null) { - bundleName = eventType.getApplication().getBundle().getName(); - applicationName = eventType.getApplication().getName(); - } else if (event.getEventWrapper().getEvent() instanceof Action action) { - bundleName = action.getBundle(); - applicationName = action.getApplication(); - } - - // uses canonical EmailSubscription - try { - - // TODO Add recipients processing from policies-notifications processing (failed recipients) - // by checking the NotificationHistory's details section (if missing payload - fix in WebhookTypeProcessor) - - // TODO If the call fails - we should probably rollback Kafka topic (if BOP is down for example) - // also add metrics for these failures - - history = webhookSender.doHttpRequest( - event, endpoint, - bopRequest, - getPayload(users, event.getEventWrapper(), subject, body), - "POST", - bopUrl, - persistHistory); - return history; - } catch (Exception e) { - Log.error("Email sending failed", e); - } finally { - processedTimer.stop(registry.timer("processor.email.processed", "bundle", bundleName, "application", applicationName)); - processTime.record(Duration.between(start, LocalDateTime.now(UTC))); - return history; - } - } - - private JsonObject getPayload(Set users, EventWrapper eventWrapper, TemplateInstance subject, TemplateInstance body) { - - String renderedSubject; - String renderedBody; - try { - renderedSubject = templateService.renderTemplate(eventWrapper.getEvent(), subject); - renderedBody = templateService.renderTemplate(eventWrapper.getEvent(), body); - } catch (Exception e) { - Log.warnf(e, - "Unable to render template for %s.", - eventWrapper.getKey().toString() - ); - throw e; - } - if (featureFlipper.isSkipBopUsersResolution()) { - SendEmailsRequest request = new SendEmailsRequest(); - request.addEmail(buildEmail( - users, - renderedSubject, - renderedBody - )); - return JsonObject.mapFrom(request); - } else { - Emails emails = new Emails(); - emails.addEmail(buildEmail( - users, - renderedSubject, - renderedBody - )); - return JsonObject.mapFrom(emails); - } - } - - protected HttpRequest buildBOPHttpRequest() { - return bopWebClient - .postAbs(bopUrl) - .putHeader(Constants.MBOP_APITOKEN_HEADER, bopApiToken) - .putHeader(Constants.MBOP_CLIENT_ID_HEADER, bopClientId) - .putHeader(Constants.MBOP_ENV_HEADER, bopEnv); - } - - protected Email buildEmail(Set recipients, String subject, String body) { - Set usersEmail; - if (featureFlipper.isSkipBopUsersResolution()) { - usersEmail = recipients.stream().map(User::getEmail).collect(Collectors.toSet()); - } else { - usersEmail = recipients.stream().map(User::getUsername).collect(Collectors.toSet()); - } - - Email email = new Email(); - email.setBodyType(BODY_TYPE_HTML); - email.setBccList(usersEmail); - email.setSubject(subject); - email.setBody(body); - return email; - } -} diff --git a/engine/src/main/java/com/redhat/cloud/notifications/processors/email/EmailSubscriptionTypeProcessor.java b/engine/src/main/java/com/redhat/cloud/notifications/processors/email/EmailSubscriptionTypeProcessor.java index ba9d87c73c..d918643ff6 100644 --- a/engine/src/main/java/com/redhat/cloud/notifications/processors/email/EmailSubscriptionTypeProcessor.java +++ b/engine/src/main/java/com/redhat/cloud/notifications/processors/email/EmailSubscriptionTypeProcessor.java @@ -6,7 +6,6 @@ import com.redhat.cloud.notifications.db.repositories.EmailAggregationRepository; import com.redhat.cloud.notifications.db.repositories.EndpointRepository; import com.redhat.cloud.notifications.db.repositories.EventRepository; -import com.redhat.cloud.notifications.db.repositories.NotificationHistoryRepository; import com.redhat.cloud.notifications.db.repositories.TemplateRepository; import com.redhat.cloud.notifications.events.EventWrapperAction; import com.redhat.cloud.notifications.ingress.Action; @@ -20,9 +19,6 @@ import com.redhat.cloud.notifications.models.EndpointType; import com.redhat.cloud.notifications.models.Event; import com.redhat.cloud.notifications.models.EventType; -import com.redhat.cloud.notifications.models.InstantEmailTemplate; -import com.redhat.cloud.notifications.models.NotificationHistory; -import com.redhat.cloud.notifications.models.NotificationStatus; import com.redhat.cloud.notifications.models.SubscriptionType; import com.redhat.cloud.notifications.processors.ConnectorSender; import com.redhat.cloud.notifications.processors.SystemEndpointTypeProcessor; @@ -47,7 +43,6 @@ import java.time.LocalDateTime; import java.time.ZoneOffset; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -55,13 +50,13 @@ import java.util.UUID; import java.util.stream.Collectors; -import static com.redhat.cloud.notifications.models.NotificationHistory.getHistoryStub; - +/* + * This class needs more cleanup but this will be done later to make the reviews easier. + * TODO Stop extending SystemEndpointTypeProcessor. + */ @ApplicationScoped public class EmailSubscriptionTypeProcessor extends SystemEndpointTypeProcessor { - public static final String TOTAL_RECIPIENTS_KEY = "total_recipients"; - public static final String TOTAL_FAILURE_RECIPIENTS_KEY = "total_failure_recipients"; public static final String AGGREGATION_COMMAND_REJECTED_COUNTER_NAME = "aggregation.command.rejected"; public static final String AGGREGATION_COMMAND_PROCESSED_COUNTER_NAME = "aggregation.command.processed"; public static final String AGGREGATION_COMMAND_ERROR_COUNTER_NAME = "aggregation.command.error"; @@ -79,9 +74,6 @@ public class EmailSubscriptionTypeProcessor extends SystemEndpointTypeProcessor @Inject EmailActorsResolver emailActorsResolver; - @Inject - EmailSender emailSender; - @Inject EmailAggregator emailAggregator; @@ -106,9 +98,6 @@ public class EmailSubscriptionTypeProcessor extends SystemEndpointTypeProcessor @Inject EndpointRepository endpointRepository; - @Inject - NotificationHistoryRepository notificationHistoryRepository; - @Inject ApplicationRepository applicationRepository; @@ -139,11 +128,7 @@ void postConstruct() { @Override public void process(Event event, List endpoints) { - if (endpoints != null && !endpoints.isEmpty()) { - this.generateAggregationWhereDue(event); - - sendEmail(event, Set.copyOf(endpoints)); - } + throw new UnsupportedOperationException("No longer used"); } /** @@ -175,26 +160,6 @@ public void generateAggregationWhereDue(final Event event) { } } - private void sendEmail(Event event, Set endpoints) { - final TemplateInstance subject; - final TemplateInstance body; - - Optional instantEmailTemplate = templateRepository - .findInstantEmailTemplate(event.getEventType().getId()); - if (instantEmailTemplate.isEmpty()) { - return; - } else { - String subjectData = instantEmailTemplate.get().getSubjectTemplate().getData(); - subject = templateService.compileTemplate(subjectData, "subject"); - String bodyData = instantEmailTemplate.get().getBodyTemplate().getData(); - body = templateService.compileTemplate(bodyData, "body"); - } - Endpoint endpoint = endpointRepository.getOrCreateDefaultSystemSubscription(event.getAccountId(), event.getOrgId(), EndpointType.EMAIL_SUBSCRIPTION); - - Set userList = getRecipientList(event, endpoints.stream().toList(), SubscriptionType.INSTANT); - emailSender.sendEmail(userList, event, subject, body, true, endpoint); - } - public void processAggregation(Event event) { if (featureFlipper.isAsyncAggregation()) { /* @@ -265,7 +230,6 @@ public void processAggregationSync(Event event, boolean async) { private void processAggregateEmailsByAggregationKey(AggregationCommand aggregationCommand, Optional aggregatorEvent, boolean async) { TemplateInstance subject = null; TemplateInstance body = null; - final long startTime = System.currentTimeMillis(); EmailAggregationKey aggregationKey = aggregationCommand.getAggregationKey(); Optional aggregationEmailTemplate = templateRepository @@ -301,8 +265,6 @@ private void processAggregateEmailsByAggregationKey(AggregationCommand aggregati action.setEventType(event.getEventType().getName()); } - Integer nbRecipientsSuccessfullySent = 0; - Integer nbRecipientsFailureSent = 0; if (subject != null && body != null) { Map> aggregationsByUsers = emailAggregator.getAggregated(aggregationKey, aggregationCommand.getSubscriptionType(), @@ -319,43 +281,31 @@ private void processAggregateEmailsByAggregationKey(AggregationCommand aggregati action.setContext(contextBuilder.build()); event.setEventWrapper(new EventWrapperAction(action)); - if (featureFlipper.isEmailConnectorEnabled()) { - Set recipientsUsernames = aggregation.getValue().stream().map(User::getUsername).collect(Collectors.toSet()); - String subjectStr = templateService.renderTemplate(event.getEventWrapper().getEvent(), subject); - String bodyStr = templateService.renderTemplate(event.getEventWrapper().getEvent(), body); - - Set recipientSettings = extractAndTransformRecipientSettings(event, List.of(endpoint)); - - // Prepare all the data to be sent to the connector. - final EmailNotification emailNotification = new EmailNotification( - bodyStr, - subjectStr, - this.emailActorsResolver.getEmailSender(event), - event.getOrgId(), - recipientSettings, - /* - * The recipients are determined at an earlier stage (see EmailAggregator) using the - * recipients-resolver app and the subscription records from the database. - * The subscribedByDefault value below simply means that recipients-resolver will consider - * the subscribers passed in the request as the recipients candidates of the aggregation email. - */ - recipientsUsernames, - Collections.emptySet(), - false - ); + Set recipientsUsernames = aggregation.getValue().stream().map(User::getUsername).collect(Collectors.toSet()); + String subjectStr = templateService.renderTemplate(event.getEventWrapper().getEvent(), subject); + String bodyStr = templateService.renderTemplate(event.getEventWrapper().getEvent(), body); + + Set recipientSettings = extractAndTransformRecipientSettings(event, List.of(endpoint)); + + // Prepare all the data to be sent to the connector. + final EmailNotification emailNotification = new EmailNotification( + bodyStr, + subjectStr, + this.emailActorsResolver.getEmailSender(event), + event.getOrgId(), + recipientSettings, + /* + * The recipients are determined at an earlier stage (see EmailAggregator) using the + * recipients-resolver app and the subscription records from the database. + * The subscribedByDefault value below simply means that recipients-resolver will consider + * the subscribers passed in the request as the recipients candidates of the aggregation email. + */ + recipientsUsernames, + Collections.emptySet(), + false + ); - connectorSender.send(event, endpoint, JsonObject.mapFrom(emailNotification)); - } else { - NotificationHistory history = emailSender.sendEmail(aggregation.getValue(), event, subject, body, false, endpoint); - if (history != null) { - Integer totalRecipients = (Integer) history.getDetails().get(TOTAL_RECIPIENTS_KEY); - if (NotificationStatus.SUCCESS == history.getStatus()) { - nbRecipientsSuccessfullySent += totalRecipients; - } else { - nbRecipientsFailureSent += totalRecipients; - } - } - } + connectorSender.send(event, endpoint, JsonObject.mapFrom(emailNotification)); } } @@ -363,30 +313,5 @@ private void processAggregateEmailsByAggregationKey(AggregationCommand aggregati if (aggregationCommand.getSubscriptionType().equals(SubscriptionType.DAILY)) { emailAggregationRepository.purgeOldAggregation(aggregationKey, aggregationCommand.getEnd()); } - - // build and persist aggregation history if needed. - // If email connector is enabled, it will take care of history - if (aggregatorEvent.isPresent() && !featureFlipper.isEmailConnectorEnabled()) { - buildAggregatedHistory(startTime, endpoint, event, nbRecipientsSuccessfullySent, nbRecipientsFailureSent); - } - } - - private void buildAggregatedHistory(long startTime, Endpoint endpoint, Event event, Integer nbRecipientsSuccessfullySent, Integer nbRecipientsFailureSent) { - long invocationTime = System.currentTimeMillis() - startTime; - NotificationHistory history = getHistoryStub(endpoint, event, invocationTime, UUID.randomUUID()); - Map details = new HashMap<>(); - details.put(TOTAL_RECIPIENTS_KEY, nbRecipientsSuccessfullySent + nbRecipientsFailureSent); - details.put(TOTAL_FAILURE_RECIPIENTS_KEY, nbRecipientsFailureSent); - if (0 == nbRecipientsFailureSent) { - history.setStatus(NotificationStatus.SUCCESS); - } else { - history.setStatus(NotificationStatus.FAILED_INTERNAL); - } - history.setDetails(details); - try { - notificationHistoryRepository.createNotificationHistory(history); - } catch (Exception e) { - Log.errorf(e, "Notification history creation failed for event %s and endpoint %s", event.getId(), history.getEndpoint()); - } } } diff --git a/engine/src/main/java/com/redhat/cloud/notifications/processors/email/Emails.java b/engine/src/main/java/com/redhat/cloud/notifications/processors/email/Emails.java deleted file mode 100644 index b1293e936c..0000000000 --- a/engine/src/main/java/com/redhat/cloud/notifications/processors/email/Emails.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.redhat.cloud.notifications.processors.email; - -import com.fasterxml.jackson.annotation.JsonProperty; - -import java.util.HashSet; -import java.util.Set; - -@Deprecated(forRemoval = true) -public class Emails { - - @JsonProperty("emails") - private final Set emails; - - Emails() { - this.emails = new HashSet<>(); - } - - void addEmail(final Email email) { - this.emails.add(email); - } - - Set getEmails() { - return emails; - } -} diff --git a/engine/src/main/java/com/redhat/cloud/notifications/processors/email/SendEmailsRequest.java b/engine/src/main/java/com/redhat/cloud/notifications/processors/email/SendEmailsRequest.java deleted file mode 100644 index 3efa4559df..0000000000 --- a/engine/src/main/java/com/redhat/cloud/notifications/processors/email/SendEmailsRequest.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.redhat.cloud.notifications.processors.email; - -import com.fasterxml.jackson.annotation.JsonAutoDetect; - -import java.util.HashSet; -import java.util.Set; - -import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY; - -@JsonAutoDetect(fieldVisibility = ANY) -public class SendEmailsRequest { - - private final Set emails = new HashSet<>(); - private final boolean skipUsersResolution = true; - - public void addEmail(Email email) { - emails.add(email); - } -} diff --git a/engine/src/main/java/com/redhat/cloud/notifications/processors/webclient/BopWebClient.java b/engine/src/main/java/com/redhat/cloud/notifications/processors/webclient/BopWebClient.java deleted file mode 100644 index 3ee5c98593..0000000000 --- a/engine/src/main/java/com/redhat/cloud/notifications/processors/webclient/BopWebClient.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.redhat.cloud.notifications.processors.webclient; - -import jakarta.inject.Qualifier; - -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -/** - * Use this qualifier to inject a {@link io.vertx.mutiny.ext.web.client.WebClient WebClient} instance to process BOP requests. - */ -@Qualifier -@Retention(RUNTIME) -@Target({METHOD, FIELD}) -public @interface BopWebClient { -} diff --git a/engine/src/main/java/com/redhat/cloud/notifications/processors/webclient/SslVerificationDisabled.java b/engine/src/main/java/com/redhat/cloud/notifications/processors/webclient/SslVerificationDisabled.java deleted file mode 100644 index 62d89ed070..0000000000 --- a/engine/src/main/java/com/redhat/cloud/notifications/processors/webclient/SslVerificationDisabled.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.redhat.cloud.notifications.processors.webclient; - -import jakarta.inject.Qualifier; - -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -/** - * Use this qualifier to inject a {@link io.vertx.mutiny.ext.web.client.WebClient WebClient} instance that will trust - * all SSL server certificates. - */ -@Qualifier -@Retention(RUNTIME) -@Target({METHOD, FIELD}) -public @interface SslVerificationDisabled { -} diff --git a/engine/src/main/java/com/redhat/cloud/notifications/processors/webclient/SslVerificationEnabled.java b/engine/src/main/java/com/redhat/cloud/notifications/processors/webclient/SslVerificationEnabled.java deleted file mode 100644 index 25bd8d9299..0000000000 --- a/engine/src/main/java/com/redhat/cloud/notifications/processors/webclient/SslVerificationEnabled.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.redhat.cloud.notifications.processors.webclient; - -import jakarta.inject.Qualifier; - -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -/** - * Use this qualifier to inject a {@link io.vertx.mutiny.ext.web.client.WebClient WebClient} instance that will verify - * SSL server certificates. - */ -@Qualifier -@Retention(RUNTIME) -@Target({METHOD, FIELD}) -public @interface SslVerificationEnabled { -} diff --git a/engine/src/main/java/com/redhat/cloud/notifications/processors/webclient/WebClientProducer.java b/engine/src/main/java/com/redhat/cloud/notifications/processors/webclient/WebClientProducer.java deleted file mode 100644 index 1f94a3a7d3..0000000000 --- a/engine/src/main/java/com/redhat/cloud/notifications/processors/webclient/WebClientProducer.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.redhat.cloud.notifications.processors.webclient; - -import io.quarkus.logging.Log; -import io.vertx.ext.web.client.WebClientOptions; -import io.vertx.mutiny.core.Vertx; -import io.vertx.mutiny.ext.web.client.WebClient; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.inject.Produces; -import jakarta.inject.Inject; -import jakarta.inject.Singleton; -import org.eclipse.microprofile.config.inject.ConfigProperty; - -import java.util.Optional; - -@ApplicationScoped -public class WebClientProducer { - - @Inject - Vertx vertx; - - @ConfigProperty(name = "webclient.max_pool_size") - Optional maxPoolSize; - - @Produces - @Singleton - @SslVerificationEnabled - public WebClient securedWebClient() { - return WebClient.create(vertx, buildOptions(false)); - } - - @Produces - @Singleton - @SslVerificationDisabled - public WebClient unsecuredWebClient() { - return WebClient.create(vertx, buildOptions(true)); - } - - @Produces - @Singleton - @BopWebClient - public WebClient bopWebClient() { - return WebClient.create(vertx, buildOptions(true)); - } - - private WebClientOptions buildOptions(boolean trustAll) { - WebClientOptions options = new WebClientOptions() - .setTrustAll(trustAll) - .setConnectTimeout(3000); // TODO Should this be configurable by the system? We need a maximum in any case - if (maxPoolSize.isPresent()) { - Log.debugf("Producing a WebClient with a configured max pool size: %d", maxPoolSize.get()); - options = options.setMaxPoolSize(maxPoolSize.get()); - } - return options; - } -} diff --git a/engine/src/main/java/com/redhat/cloud/notifications/processors/webhooks/ServerErrorException.java b/engine/src/main/java/com/redhat/cloud/notifications/processors/webhooks/ServerErrorException.java deleted file mode 100644 index 8345899a47..0000000000 --- a/engine/src/main/java/com/redhat/cloud/notifications/processors/webhooks/ServerErrorException.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.redhat.cloud.notifications.processors.webhooks; - -/** - * This exception is thrown when the response of a webhook call contains a 5xx HTTP status. - * Such status can be caused by a temporary remote issue so we should retry the call. - */ -public class ServerErrorException extends RuntimeException { - - public ServerErrorException() { - } -} diff --git a/engine/src/main/java/com/redhat/cloud/notifications/processors/webhooks/WebhookTypeProcessor.java b/engine/src/main/java/com/redhat/cloud/notifications/processors/webhooks/WebhookTypeProcessor.java index dbb44eaddd..dc68cda4ed 100644 --- a/engine/src/main/java/com/redhat/cloud/notifications/processors/webhooks/WebhookTypeProcessor.java +++ b/engine/src/main/java/com/redhat/cloud/notifications/processors/webhooks/WebhookTypeProcessor.java @@ -2,89 +2,29 @@ import com.redhat.cloud.notifications.DelayedThrower; import com.redhat.cloud.notifications.config.FeatureFlipper; -import com.redhat.cloud.notifications.db.repositories.EndpointRepository; -import com.redhat.cloud.notifications.events.IntegrationDisabledNotifier; import com.redhat.cloud.notifications.models.Endpoint; import com.redhat.cloud.notifications.models.Event; -import com.redhat.cloud.notifications.models.NotificationHistory; -import com.redhat.cloud.notifications.models.NotificationStatus; import com.redhat.cloud.notifications.models.WebhookProperties; import com.redhat.cloud.notifications.processors.ConnectorSender; import com.redhat.cloud.notifications.processors.EndpointTypeProcessor; -import com.redhat.cloud.notifications.processors.email.EmailSubscriptionTypeProcessor; -import com.redhat.cloud.notifications.processors.webclient.SslVerificationDisabled; -import com.redhat.cloud.notifications.processors.webclient.SslVerificationEnabled; import com.redhat.cloud.notifications.routers.sources.SecretUtils; import com.redhat.cloud.notifications.transformers.BaseTransformer; -import dev.failsafe.Failsafe; -import dev.failsafe.RetryPolicy; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.MeterRegistry; -import io.netty.channel.ConnectTimeoutException; import io.quarkus.logging.Log; -import io.vertx.core.VertxException; -import io.vertx.core.http.HttpMethod; import io.vertx.core.json.JsonObject; -import io.vertx.mutiny.core.buffer.Buffer; -import io.vertx.mutiny.ext.web.client.HttpRequest; -import io.vertx.mutiny.ext.web.client.HttpResponse; -import io.vertx.mutiny.ext.web.client.WebClient; import jakarta.annotation.PostConstruct; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; -import org.eclipse.microprofile.config.inject.ConfigProperty; -import java.io.IOException; -import java.time.Duration; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.UUID; import static com.redhat.cloud.notifications.events.EndpointProcessor.DELAYED_EXCEPTION_MSG; -import static com.redhat.cloud.notifications.models.EndpointType.EMAIL_SUBSCRIPTION; -import static com.redhat.cloud.notifications.models.NotificationHistory.getHistoryStub; @ApplicationScoped public class WebhookTypeProcessor extends EndpointTypeProcessor { public static final String PROCESSED_WEBHOOK_COUNTER = "processor.webhook.processed"; - public static final String PROCESSED_EMAIL_COUNTER = "processor.email.processed"; - public static final String FAILED_WEBHOOK_COUNTER = "processor.webhook.failed"; - public static final String SUCCESSFUL_WEBHOOK_COUNTER = "processor.webhook.successful"; - public static final String FAILED_EMAIL_COUNTER = "processor.email.failed"; - public static final String RETRIED_WEBHOOK_COUNTER = "processor.webhook.retried"; - public static final String RETRIED_EMAIL_COUNTER = "processor.email.retried"; - public static final String SUCCESSFUL_EMAIL_COUNTER = "processor.email.successful"; - public static final String DISABLED_WEBHOOKS_COUNTER = "processor.webhook.disabled.endpoints"; - public static final String ERROR_TYPE_TAG_KEY = "error_type"; - public static final String CLIENT_TAG_VALUE = "client"; - public static final String SERVER_TAG_VALUE = "server"; - private static final String TOKEN_HEADER = "X-Insight-Token"; - private static final String CONNECTION_CLOSED_MSG = "Connection was closed"; - - @ConfigProperty(name = "processor.webhook.retry.max-attempts", defaultValue = "3") - int maxRetryAttempts; - - @ConfigProperty(name = "processor.webhook.retry.back-off.initial-value", defaultValue = "1S") - Duration initialRetryBackOff; - - @ConfigProperty(name = "processor.webhook.retry.back-off.max-value", defaultValue = "30S") - Duration maxRetryBackOff; - - @ConfigProperty(name = "processor.webhook.await-timeout", defaultValue = "60S") - Duration awaitTimeout; - - @ConfigProperty(name = "processor.webhook.max-server-errors", defaultValue = "10") - int maxServerErrors; - - @Inject - @SslVerificationEnabled - WebClient securedWebClient; - - @Inject - @SslVerificationDisabled - WebClient unsecuredWebClient; @Inject BaseTransformer transformer; @@ -92,12 +32,6 @@ public class WebhookTypeProcessor extends EndpointTypeProcessor { @Inject FeatureFlipper featureFlipper; - @Inject - EndpointRepository endpointRepository; - - @Inject - IntegrationDisabledNotifier integrationDisabledNotifier; - @Inject MeterRegistry registry; @@ -105,16 +39,6 @@ public class WebhookTypeProcessor extends EndpointTypeProcessor { SecretUtils secretUtils; private Counter processedWebhookCount; - private Counter failedWebhookCount; - private Counter retriedWebhookCount; - private Counter successWebhookCount; - private Counter processedEmailCount; - private Counter failedEmailCount; - private Counter retriedEmailCount; - private Counter successEmailCount; - private Counter disabledWebhooksClientErrorCount; - private Counter disabledWebhooksServerErrorCount; - private RetryPolicy retryPolicy; @Inject ConnectorSender connectorSender; @@ -122,22 +46,6 @@ public class WebhookTypeProcessor extends EndpointTypeProcessor { @PostConstruct void postConstruct() { processedWebhookCount = registry.counter(PROCESSED_WEBHOOK_COUNTER); - failedWebhookCount = registry.counter(FAILED_WEBHOOK_COUNTER); - retriedWebhookCount = registry.counter(RETRIED_WEBHOOK_COUNTER); - successWebhookCount = registry.counter(SUCCESSFUL_WEBHOOK_COUNTER); - - processedEmailCount = registry.counter(PROCESSED_EMAIL_COUNTER); - failedEmailCount = registry.counter(FAILED_EMAIL_COUNTER); - retriedEmailCount = registry.counter(RETRIED_EMAIL_COUNTER); - successEmailCount = registry.counter(SUCCESSFUL_EMAIL_COUNTER); - - disabledWebhooksClientErrorCount = registry.counter(DISABLED_WEBHOOKS_COUNTER, ERROR_TYPE_TAG_KEY, CLIENT_TAG_VALUE); - disabledWebhooksServerErrorCount = registry.counter(DISABLED_WEBHOOKS_COUNTER, ERROR_TYPE_TAG_KEY, SERVER_TAG_VALUE); - retryPolicy = RetryPolicy.builder() - .handleIf(this::shouldRetry) - .withBackoff(initialRetryBackOff, maxRetryBackOff) - .withMaxRetries(maxRetryAttempts) - .build(); } @Override @@ -158,6 +66,7 @@ public void process(Event event, List endpoints) { } private void process(Event event, Endpoint endpoint) { + processedWebhookCount.increment(); WebhookProperties properties = endpoint.getProperties(WebhookProperties.class); @@ -170,215 +79,11 @@ private void process(Event event, Endpoint endpoint) { this.secretUtils.loadSecretsForEndpoint(endpoint); } - if (featureFlipper.isWebhookConnectorEnabled()) { - final JsonObject connectorData = new JsonObject(); - - connectorData.put("endpoint_properties", properties); - connectorData.put("payload", payload); - - connectorSender.send(event, endpoint, connectorData); - } else { - final HttpRequest req = getWebClient(properties.getDisableSslVerification()) - .requestAbs(HttpMethod.valueOf(properties.getMethod().name()), properties.getUrl()); - - if (properties.getSecretToken() != null && !properties.getSecretToken().isBlank()) { - req.putHeader(TOKEN_HEADER, properties.getSecretToken()); - } - - if (properties.getBasicAuthentication() != null) { - req.basicAuthentication(properties.getBasicAuthentication().getUsername(), properties.getBasicAuthentication().getPassword()); - } - - doHttpRequest(event, endpoint, req, payload, properties.getMethod().name(), properties.getUrl(), true); - } - } - - private WebClient getWebClient(boolean disableSSLVerification) { - if (disableSSLVerification) { - return unsecuredWebClient; - } else { - return securedWebClient; - } - } - - public NotificationHistory doHttpRequest(Event event, Endpoint endpoint, HttpRequest req, JsonObject payload, String method, String url, boolean persistHistory) { - final long startTime = System.currentTimeMillis(); - boolean isEmailEndpoint = endpoint.getType() == EMAIL_SUBSCRIPTION; - final NotificationHistory history = buildNotificationHistory(event, endpoint, startTime); - incrementProcessedMetrics(isEmailEndpoint); - - try { - Failsafe.with(retryPolicy).run((context) -> { - if (context.isRetry()) { - updateRetryMetrics(isEmailEndpoint); - } - - // TODO NOTIF-488 We may want to move to a non-reactive HTTP client in the future. - HttpResponse resp = req.sendJsonObject(payload).await().atMost(awaitTimeout); - - boolean serverError = false; - boolean shouldResetEndpointServerErrors = false; - Map details = new HashMap<>(); - if (isEmailEndpoint) { - try { - int totalRecipients = payload.getJsonArray("emails").getJsonObject(0).getJsonArray("bccList").size(); - details.put(EmailSubscriptionTypeProcessor.TOTAL_RECIPIENTS_KEY, totalRecipients); - history.setDetails(details); - } catch (Exception ex) { - Log.error("Could not set the total_recipients field in the history details", ex); - } - } - if (resp.statusCode() >= 200 && resp.statusCode() < 300) { - // Accepted - Log.debugf("Webhook request to %s was successful: %d", url, resp.statusCode()); - history.setStatus(NotificationStatus.SUCCESS); - shouldResetEndpointServerErrors = true; - } else if (resp.statusCode() >= 500) { - // Temporary error, allow retry - serverError = true; - Log.debugf("Webhook request to %s failed: %d %s", url, resp.statusCode(), resp.statusMessage()); - history.setStatus(NotificationStatus.FAILED_INTERNAL); - if (featureFlipper.isDisableWebhookEndpointsOnFailure()) { - if (!isEmailEndpoint) { - /* - * The target endpoint returned a 5xx status. That kind of error happens in case of remote - * server failure, which is usually something temporary. Sending another notification to - * the same endpoint may work in the future, so the endpoint is only disabled if the max - * number of endpoint failures allowed from the configuration is exceeded. - */ - boolean disabled = endpointRepository.incrementEndpointServerErrors(endpoint.getId(), maxServerErrors); - if (disabled) { - disabledWebhooksServerErrorCount.increment(); - Log.infof("Endpoint %s was disabled because we received too many 5xx status while calling it", endpoint.getId()); - integrationDisabledNotifier.tooManyServerErrors(endpoint, maxServerErrors); - } - } - } - } else { - // Redirects etc should have been followed by the vertx (test this) - if (isEmailEndpoint) { - Log.warnf("Webhook request to %s failed: %d %s %s", url, resp.statusCode(), resp.statusMessage(), payload); - } else { - Log.debugf("Webhook request to %s failed: %d %s %s", url, resp.statusCode(), resp.statusMessage(), payload); - } - history.setStatus(NotificationStatus.FAILED_INTERNAL); - // TODO NOTIF-512 Should we disable endpoints in case of 3xx status code? - if (featureFlipper.isDisableWebhookEndpointsOnFailure()) { - if (!isEmailEndpoint && resp.statusCode() >= 400 && resp.statusCode() < 500) { - /* - * The target endpoint returned a 4xx status. That kind of error requires an update of the - * endpoint settings (URL, secret token...). The endpoint will most likely never return a - * successful status code with the current settings, so it is disabled immediately. - */ - boolean disabled = endpointRepository.disableEndpoint(endpoint.getId()); - if (disabled) { - disabledWebhooksClientErrorCount.increment(); - Log.infof("Endpoint %s was disabled because we received a 4xx status while calling it", endpoint.getId()); - integrationDisabledNotifier.clientError(endpoint, resp.statusCode()); - } - } else { - /* - * 3xx status codes may be considered has a failure soon, but first we need to confirm - * that Vert.x is correctly following the redirections. - */ - shouldResetEndpointServerErrors = true; - } - } - } - - if (featureFlipper.isDisableWebhookEndpointsOnFailure()) { - if (!isEmailEndpoint && shouldResetEndpointServerErrors) { - // When a target endpoint is successfully called, its server errors counter is reset in the DB. - boolean reset = endpointRepository.resetEndpointServerErrors(endpoint.getId()); - if (reset) { - Log.tracef("The server errors counter of endpoint %s was just reset", endpoint.getId()); - } - } - } - - if (history.getStatus() == NotificationStatus.FAILED_INTERNAL) { - details.put("url", url); - details.put("method", method); - details.put("code", resp.statusCode()); - details.put("response_body", resp.bodyAsString()); - history.setDetails(details); - } - - if (serverError) { - throw new ServerErrorException(); - } - }); - } catch (Exception e) { - if (!(e instanceof ServerErrorException)) { - history.setStatus(NotificationStatus.FAILED_INTERNAL); - - Log.debugf("Failed: %s", e.getMessage()); - - Map details = new HashMap<>(); - details.put("url", url); - details.put("method", method); - details.put("error_message", e.getMessage()); // TODO This message isn't always the most descriptive.. - history.setDetails(details); - } - } finally { - updateMetrics(history.getStatus(), isEmailEndpoint); - if (persistHistory) { - history.setInvocationTime(System.currentTimeMillis() - startTime); - persistNotificationHistory(history); - } - return history; - } - } - - private void updateMetrics(NotificationStatus status, boolean isEmailEndpoint) { - if (NotificationStatus.FAILED_INTERNAL == status || NotificationStatus.FAILED_EXTERNAL == status) { - if (isEmailEndpoint) { - failedEmailCount.increment(); - } else { - failedWebhookCount.increment(); - } - } else { - if (isEmailEndpoint) { - successEmailCount.increment(); - } else { - successWebhookCount.increment(); - } - } - } - - private void updateRetryMetrics(boolean isEmailEndpoint) { - if (isEmailEndpoint) { - retriedEmailCount.increment(); - } else { - retriedWebhookCount.increment(); - } - } + final JsonObject connectorData = new JsonObject(); - private void incrementProcessedMetrics(boolean isEmailEndpoint) { - if (isEmailEndpoint) { - processedEmailCount.increment(); - } else { - processedWebhookCount.increment(); - } - } - - private NotificationHistory buildNotificationHistory(Event event, Endpoint endpoint, long startTime) { - long invocationTime = System.currentTimeMillis() - startTime; - return getHistoryStub(endpoint, event, invocationTime, UUID.randomUUID()); - } + connectorData.put("endpoint_properties", JsonObject.mapFrom(properties)); + connectorData.put("payload", payload); - /** - * Returns {@code true} if we should retry when the given {@code throwable} is thrown during a webhook call. - *
    - *
  • {@link ServerErrorException} is thrown when the call was successful but the remote server replied with a 5xx HTTP status.
  • - *
  • {@link IOException} is thrown when the connection between us and the remote server was reset during the call.
  • - *
  • {@link ConnectTimeoutException} is thrown when the remote server did not respond at all to our call.
  • - *
- */ - private boolean shouldRetry(Throwable throwable) { - return throwable instanceof ServerErrorException || - throwable instanceof IOException || - throwable instanceof ConnectTimeoutException || - throwable instanceof VertxException && CONNECTION_CLOSED_MSG.equals(throwable.getMessage()); + connectorSender.send(event, endpoint, connectorData); } } diff --git a/engine/src/main/java/com/redhat/cloud/notifications/transformers/BaseTransformer.java b/engine/src/main/java/com/redhat/cloud/notifications/transformers/BaseTransformer.java index 957bb2c5fd..785971845a 100644 --- a/engine/src/main/java/com/redhat/cloud/notifications/transformers/BaseTransformer.java +++ b/engine/src/main/java/com/redhat/cloud/notifications/transformers/BaseTransformer.java @@ -1,6 +1,5 @@ package com.redhat.cloud.notifications.transformers; -import com.fasterxml.jackson.databind.ObjectMapper; import com.redhat.cloud.notifications.events.EventWrapper; import com.redhat.cloud.notifications.events.EventWrapperAction; import com.redhat.cloud.notifications.events.EventWrapperCloudEvent; @@ -10,7 +9,6 @@ import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; import java.util.Map; import java.util.stream.Collectors; @@ -18,9 +16,6 @@ @ApplicationScoped public class BaseTransformer { - @Inject - ObjectMapper objectMapper; - // JSON property names' definition. public static final String ACCOUNT_ID = "account_id"; public static final String APPLICATION = "application"; diff --git a/engine/src/main/java/com/redhat/cloud/notifications/utils/LineBreakCleaner.java b/engine/src/main/java/com/redhat/cloud/notifications/utils/LineBreakCleaner.java deleted file mode 100644 index 2ac9663ae1..0000000000 --- a/engine/src/main/java/com/redhat/cloud/notifications/utils/LineBreakCleaner.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.redhat.cloud.notifications.utils; - -public class LineBreakCleaner { - - public static String clean(String value) { - if (value == null) { - return null; - } else { - return value.replace("\r", "").replace("\n", ""); - } - } -} diff --git a/engine/src/main/resources/application.properties b/engine/src/main/resources/application.properties index 61a9cb65f2..f36dc3d52a 100644 --- a/engine/src/main/resources/application.properties +++ b/engine/src/main/resources/application.properties @@ -150,9 +150,6 @@ reinject.enabled=false # Use this property to load the templates from the DB. Temp, to be removed soon. notifications.use-templates-from-db=false -%test.processor.webhook.retry.back-off.initial-value=0.001S -%test.processor.webhook.retry.back-off.max-value=0.01S - # Sources integration URLs and details. It is used to store the secrets' data for the camel and webhook endpoints. quarkus.rest-client.sources.read-timeout=1000 quarkus.rest-client.sources.url=${clowder.endpoints.sources-api-svc.url:http://localhost:8000} diff --git a/engine/src/test/java/com/redhat/cloud/notifications/TestHelpers.java b/engine/src/test/java/com/redhat/cloud/notifications/TestHelpers.java index da8a278f91..42e738bf3f 100644 --- a/engine/src/test/java/com/redhat/cloud/notifications/TestHelpers.java +++ b/engine/src/test/java/com/redhat/cloud/notifications/TestHelpers.java @@ -41,7 +41,7 @@ public class TestHelpers { public static final String policyName1 = "Foobar"; public static final String policyId2 = "0123-456-789-5721f"; public static final String policyName2 = "Latest foo is installed"; - public static final String eventType = "test-email-subscription-instant"; + public static final String eventType = "policy-triggered"; public static EmailAggregation createEmailAggregation(String orgId, String bundle, String application, String policyId, String inventory_id) { return createEmailAggregation(orgId, bundle, application, policyId, inventory_id, null); diff --git a/engine/src/test/java/com/redhat/cloud/notifications/events/EndpointErrorFromConnectorHelperTest.java b/engine/src/test/java/com/redhat/cloud/notifications/events/EndpointErrorFromConnectorHelperTest.java index b5ee8f5258..c24e17d20c 100644 --- a/engine/src/test/java/com/redhat/cloud/notifications/events/EndpointErrorFromConnectorHelperTest.java +++ b/engine/src/test/java/com/redhat/cloud/notifications/events/EndpointErrorFromConnectorHelperTest.java @@ -19,13 +19,13 @@ import java.util.Map; import java.util.UUID; +import static com.redhat.cloud.notifications.events.EndpointErrorFromConnectorHelper.CLIENT_TAG_VALUE; import static com.redhat.cloud.notifications.events.EndpointErrorFromConnectorHelper.DISABLED_WEBHOOKS_COUNTER; import static com.redhat.cloud.notifications.events.EndpointErrorFromConnectorHelper.DISABLE_ENDPOINT_CLIENT_ERRORS; import static com.redhat.cloud.notifications.events.EndpointErrorFromConnectorHelper.ERROR_TYPE_TAG_KEY; import static com.redhat.cloud.notifications.events.EndpointErrorFromConnectorHelper.HTTP_STATUS_CODE; import static com.redhat.cloud.notifications.events.EndpointErrorFromConnectorHelper.INCREMENT_ENDPOINT_SERVER_ERRORS; import static com.redhat.cloud.notifications.events.EndpointErrorFromConnectorHelper.SERVER_TAG_VALUE; -import static com.redhat.cloud.notifications.processors.webhooks.WebhookTypeProcessor.CLIENT_TAG_VALUE; import static org.mockito.Mockito.*; @QuarkusTest diff --git a/engine/src/test/java/com/redhat/cloud/notifications/events/LifecycleITest.java b/engine/src/test/java/com/redhat/cloud/notifications/events/LifecycleITest.java index 16c04df5e2..3e2b62aa4f 100644 --- a/engine/src/test/java/com/redhat/cloud/notifications/events/LifecycleITest.java +++ b/engine/src/test/java/com/redhat/cloud/notifications/events/LifecycleITest.java @@ -1,7 +1,6 @@ package com.redhat.cloud.notifications.events; import com.redhat.cloud.notifications.MicrometerAssertionHelper; -import com.redhat.cloud.notifications.MockServerLifecycleManager; import com.redhat.cloud.notifications.TestLifecycleManager; import com.redhat.cloud.notifications.db.ResourceHelpers; import com.redhat.cloud.notifications.db.repositories.EndpointRepository; @@ -27,35 +26,34 @@ import com.redhat.cloud.notifications.models.NotificationHistory; import com.redhat.cloud.notifications.models.SystemSubscriptionProperties; import com.redhat.cloud.notifications.models.WebhookProperties; -import com.redhat.cloud.notifications.processors.email.EmailSender; import com.redhat.cloud.notifications.recipients.User; import com.redhat.cloud.notifications.recipients.rbac.RbacRecipientUsersProvider; import io.quarkus.test.InjectMock; import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.junit.QuarkusTest; -import io.quarkus.test.junit.mockito.InjectSpy; +import io.smallrye.reactive.messaging.kafka.api.KafkaMessageMetadata; import io.smallrye.reactive.messaging.memory.InMemoryConnector; +import io.smallrye.reactive.messaging.memory.InMemorySink; +import io.vertx.core.json.JsonObject; import jakarta.enterprise.inject.Any; import jakarta.inject.Inject; import jakarta.persistence.EntityManager; import jakarta.transaction.Transactional; +import org.eclipse.microprofile.reactive.messaging.Message; import org.junit.jupiter.api.Test; import org.mockito.Mockito; -import org.mockserver.model.HttpRequest; import java.io.IOException; import java.io.UncheckedIOException; import java.time.Duration; import java.time.LocalDateTime; import java.util.List; +import java.util.Map; import java.util.UUID; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.function.Function; import java.util.function.Supplier; +import java.util.stream.Collectors; import static com.redhat.cloud.notifications.MockServerLifecycleManager.getMockServerUrl; -import static com.redhat.cloud.notifications.ReflectionHelper.updateField; import static com.redhat.cloud.notifications.TestConstants.DEFAULT_ORG_ID; import static com.redhat.cloud.notifications.TestHelpers.serializeAction; import static com.redhat.cloud.notifications.events.EndpointProcessor.PROCESSED_ENDPOINTS_COUNTER_NAME; @@ -66,10 +64,12 @@ import static com.redhat.cloud.notifications.models.EndpointType.EMAIL_SUBSCRIPTION; import static com.redhat.cloud.notifications.models.EndpointType.WEBHOOK; import static com.redhat.cloud.notifications.models.SubscriptionType.INSTANT; +import static com.redhat.cloud.notifications.processors.ConnectorSender.TOCAMEL_CHANNEL; +import static com.redhat.cloud.notifications.processors.ConnectorSender.X_RH_NOTIFICATIONS_CONNECTOR_HEADER; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.awaitility.Awaitility.await; -import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.eq; -import static org.mockserver.model.HttpResponse.response; @QuarkusTest @QuarkusTestResource(TestLifecycleManager.class) @@ -86,7 +86,6 @@ public class LifecycleITest { private static final String BUNDLE_NAME = "my-bundle"; private static final String EVENT_TYPE_NAME = "all"; private static final String WEBHOOK_MOCK_PATH = "/test/lifecycle"; - private static final String EMAIL_SENDER_MOCK_PATH = "/test-email-sender/lifecycle"; private static final String SECRET_TOKEN = "super-secret-token"; @Inject @@ -99,10 +98,6 @@ public class LifecycleITest { @InjectMock RbacRecipientUsersProvider rbacRecipientUsersProvider; - // InjectSpy allows us to update the fields via reflection (Inject does not) - @InjectSpy - EmailSender emailSender; - @Inject EntityManager entityManager; @@ -121,7 +116,7 @@ void test() { Bundle bundle = resourceHelpers.createBundle(BUNDLE_NAME); Application app = resourceHelpers.createApp(bundle.getId(), APP_NAME); EventType eventType = resourceHelpers.createEventType(app.getId(), EVENT_TYPE_NAME); - setupEmailMock(accountId, username); + setupEmailMock(username); // We also need behavior groups. BehaviorGroup behaviorGroup1 = createBehaviorGroup(accountId, bundle); @@ -154,9 +149,9 @@ void test() { pushMessage(2, 0, 0, 0); // Let's check the notifications history. - retry(() -> checkEndpointHistory(endpoint1, 1, true)); - retry(() -> checkEndpointHistory(endpoint2, 1, true)); - retry(() -> checkEndpointHistory(emailEndpoint, 0, true)); + retry(() -> checkEndpointHistory(endpoint1, 1)); + retry(() -> checkEndpointHistory(endpoint2, 1)); + retry(() -> checkEndpointHistory(emailEndpoint, 0)); // We'll link the event type with the default behavior group addEventTypeBehavior(eventType.getId(), defaultBehaviorGroup.getId()); @@ -168,10 +163,10 @@ void test() { pushMessage(3, 1, 0, 0); // Let's check the notifications history again. - retry(() -> checkEndpointHistory(endpoint1, 2, true)); - retry(() -> checkEndpointHistory(endpoint2, 2, true)); - retry(() -> checkEndpointHistory(endpoint3, 1, false)); - retry(() -> checkEndpointHistory(emailEndpoint, 0, true)); + retry(() -> checkEndpointHistory(endpoint1, 2)); + retry(() -> checkEndpointHistory(endpoint2, 2)); + retry(() -> checkEndpointHistory(endpoint3, 1)); + retry(() -> checkEndpointHistory(emailEndpoint, 0)); // Lets subscribe the user to the email preferences subscribeUserPreferences(username, eventType.getId()); @@ -180,10 +175,10 @@ void test() { pushMessage(3, 1, 1, 0); // Let's check the notifications history again. - retry(() -> checkEndpointHistory(endpoint1, 3, true)); - retry(() -> checkEndpointHistory(endpoint2, 3, true)); - retry(() -> checkEndpointHistory(endpoint3, 2, false)); - retry(() -> checkEndpointHistory(emailEndpoint, 1, true)); + retry(() -> checkEndpointHistory(endpoint1, 3)); + retry(() -> checkEndpointHistory(endpoint2, 3)); + retry(() -> checkEndpointHistory(endpoint3, 2)); + retry(() -> checkEndpointHistory(emailEndpoint, 1)); /* * Let's change the behavior group actions configuration by adding an action to the second behavior group. @@ -195,10 +190,10 @@ void test() { pushMessage(3, 1, 1, 0); // Let's check the notifications history again. - retry(() -> checkEndpointHistory(endpoint1, 4, true)); - retry(() -> checkEndpointHistory(endpoint2, 4, true)); - retry(() -> checkEndpointHistory(endpoint3, 3, false)); - retry(() -> checkEndpointHistory(emailEndpoint, 2, true)); + retry(() -> checkEndpointHistory(endpoint1, 4)); + retry(() -> checkEndpointHistory(endpoint2, 4)); + retry(() -> checkEndpointHistory(endpoint3, 3)); + retry(() -> checkEndpointHistory(emailEndpoint, 2)); /* * What happens if we unlink the event type from the behavior groups? @@ -210,10 +205,10 @@ void test() { pushMessage(0, 0, 0, 0); // The notifications history should be exactly the same than last time. - retry(() -> checkEndpointHistory(endpoint1, 4, true)); - retry(() -> checkEndpointHistory(endpoint2, 4, true)); - retry(() -> checkEndpointHistory(endpoint3, 3, false)); - retry(() -> checkEndpointHistory(emailEndpoint, 2, true)); + retry(() -> checkEndpointHistory(endpoint1, 4)); + retry(() -> checkEndpointHistory(endpoint2, 4)); + retry(() -> checkEndpointHistory(endpoint3, 3)); + retry(() -> checkEndpointHistory(emailEndpoint, 2)); // Linking the default behavior group again addEventTypeBehavior(eventType.getId(), defaultBehaviorGroup.getId()); @@ -304,17 +299,8 @@ private void pushMessage(int expectedWebhookCalls, int expectedEmailEndpoints, i micrometerAssertionHelper.saveCounterValuesBeforeTest(REJECTED_COUNTER_NAME, PROCESSING_EXCEPTION_COUNTER_NAME, PROCESSED_MESSAGES_COUNTER_NAME, PROCESSED_ENDPOINTS_COUNTER_NAME); - Runnable waitForWebhooks = setupCountdownCalls( - expectedWebhookCalls, - "HttpServer never received the requests", - this::setupWebhookMock - ); - - Runnable waitForEmails = setupCountdownCalls( - expectedSentEmails, - "Emails were never sent", - this::setupEmailMockServer - ); + InMemorySink inMemorySink = inMemoryConnector.sink(TOCAMEL_CHANNEL); + inMemorySink.clear(); try { emitMockedIngressAction(); @@ -322,8 +308,18 @@ private void pushMessage(int expectedWebhookCalls, int expectedEmailEndpoints, i throw new UncheckedIOException(e); } - waitForWebhooks.run(); - waitForEmails.run(); + await().until(() -> inMemorySink.received().size() == expectedWebhookCalls + expectedSentEmails); + + Map messagesCountByConnectorHeader = inMemorySink.received().stream() + .collect(Collectors.groupingBy(this::extractConnectorHeader, Collectors.counting())); + + if (expectedWebhookCalls > 0) { + assertEquals(expectedWebhookCalls, messagesCountByConnectorHeader.get("webhook"), "HttpServer never received the requests"); + } + + if (expectedSentEmails > 0) { + assertEquals(expectedSentEmails, messagesCountByConnectorHeader.get("email_subscription"), "Emails were never sent"); + } /* * If the message isn't supposed to trigger any notification, we need to wait a bit @@ -344,28 +340,12 @@ private void pushMessage(int expectedWebhookCalls, int expectedEmailEndpoints, i micrometerAssertionHelper.clearSavedValues(); } - private Runnable setupCountdownCalls(int expected, String failMessage, Function setup) { - CountDownLatch requestCounter = new CountDownLatch(expected); - final HttpRequest request; - - if (expected > 0) { - request = setup.apply(requestCounter); - } else { - request = null; - } - - return () -> { - if (expected > 0) { - try { - if (!requestCounter.await(30, TimeUnit.SECONDS)) { - fail(failMessage); - } - } catch (InterruptedException ex) { - throw new RuntimeException(ex); - } - MockServerLifecycleManager.getClient().clear(request); - } - }; + private String extractConnectorHeader(Message message) { + byte[] actualConnectorHeader = message.getMetadata(KafkaMessageMetadata.class) + .get() + .getHeaders().headers(X_RH_NOTIFICATIONS_CONNECTOR_HEADER) + .iterator().next().value(); + return new String(actualConnectorHeader, UTF_8); } private void emitMockedIngressAction() throws IOException { @@ -390,7 +370,7 @@ private void emitMockedIngressAction() throws IOException { inMemoryConnector.source("ingress").send(serializedAction); } - private void setupEmailMock(String accountId, String username) { + private void setupEmailMock(String username) { resourceHelpers.createBlankInstantEmailTemplate(BUNDLE_NAME, APP_NAME, EVENT_TYPE_NAME); User user = new User(); @@ -405,52 +385,6 @@ private void setupEmailMock(String accountId, String username) { eq(DEFAULT_ORG_ID), eq(true) )).thenReturn(List.of(user)); - - updateField( - emailSender, - "bopUrl", - getMockServerUrl() + EMAIL_SENDER_MOCK_PATH, - EmailSender.class - ); - } - - private HttpRequest setupEmailMockServer(CountDownLatch requestsCounter) { - HttpRequest expectedRequestPattern = new HttpRequest() - .withPath(EMAIL_SENDER_MOCK_PATH) - .withMethod("POST"); - - MockServerLifecycleManager.getClient() - .withSecure(false) - .when(expectedRequestPattern) - .respond(request -> { - requestsCounter.countDown(); - return response().withStatusCode(200).withBody("Success"); - }); - - return expectedRequestPattern; - } - - private HttpRequest setupWebhookMock(CountDownLatch requestsCounter) { - HttpRequest expectedRequestPattern = new HttpRequest() - .withPath(WEBHOOK_MOCK_PATH) - .withMethod("POST"); - - MockServerLifecycleManager.getClient() - .withSecure(false) - .when(expectedRequestPattern) - .respond(request -> { - requestsCounter.countDown(); - List header = request.getHeader("X-Insight-Token"); - if (header != null && header.size() == 1 && SECRET_TOKEN.equals(header.get(0))) { - return response().withStatusCode(200) - .withBody("Success"); - } else { - return response().withStatusCode(400) - .withBody("{ \"message\": \"Time is running out\" }"); - } - }); - - return expectedRequestPattern; } @Transactional @@ -479,10 +413,9 @@ private void retry(Supplier checkEndpointHistoryResult) { } @Transactional - boolean checkEndpointHistory(Endpoint endpoint, int expectedHistoryEntries, boolean expectedInvocationResult) { - return entityManager.createQuery("FROM NotificationHistory WHERE endpoint = :endpoint AND invocationResult = :invocationResult", NotificationHistory.class) + boolean checkEndpointHistory(Endpoint endpoint, int expectedHistoryEntries) { + return entityManager.createQuery("FROM NotificationHistory WHERE endpoint = :endpoint", NotificationHistory.class) .setParameter("endpoint", endpoint) - .setParameter("invocationResult", expectedInvocationResult) .getResultList().size() == expectedHistoryEntries; } diff --git a/engine/src/test/java/com/redhat/cloud/notifications/processors/email/EmailSubscriptionTypeProcessorTest.java b/engine/src/test/java/com/redhat/cloud/notifications/processors/email/EmailSubscriptionTypeProcessorTest.java index 33dea90d85..742bdfb0f1 100644 --- a/engine/src/test/java/com/redhat/cloud/notifications/processors/email/EmailSubscriptionTypeProcessorTest.java +++ b/engine/src/test/java/com/redhat/cloud/notifications/processors/email/EmailSubscriptionTypeProcessorTest.java @@ -5,47 +5,40 @@ import com.redhat.cloud.notifications.config.FeatureFlipper; import com.redhat.cloud.notifications.db.ResourceHelpers; import com.redhat.cloud.notifications.db.repositories.EmailAggregationRepository; -import com.redhat.cloud.notifications.events.EventWrapperAction; import com.redhat.cloud.notifications.ingress.Action; -import com.redhat.cloud.notifications.ingress.Context; import com.redhat.cloud.notifications.ingress.Metadata; import com.redhat.cloud.notifications.ingress.Parser; import com.redhat.cloud.notifications.ingress.Payload; import com.redhat.cloud.notifications.models.AggregationCommand; import com.redhat.cloud.notifications.models.AggregationEmailTemplate; import com.redhat.cloud.notifications.models.Application; -import com.redhat.cloud.notifications.models.Bundle; import com.redhat.cloud.notifications.models.EmailAggregationKey; import com.redhat.cloud.notifications.models.Endpoint; -import com.redhat.cloud.notifications.models.EndpointType; import com.redhat.cloud.notifications.models.Event; -import com.redhat.cloud.notifications.models.EventType; -import com.redhat.cloud.notifications.models.NotificationHistory; -import com.redhat.cloud.notifications.models.SystemSubscriptionProperties; +import com.redhat.cloud.notifications.processors.ConnectorSender; import com.redhat.cloud.notifications.recipients.RecipientResolver; import com.redhat.cloud.notifications.recipients.RecipientSettings; import com.redhat.cloud.notifications.recipients.User; -import io.quarkus.qute.TemplateInstance; import io.quarkus.test.InjectMock; import io.quarkus.test.junit.QuarkusTest; import io.quarkus.test.junit.mockito.InjectSpy; import io.smallrye.reactive.messaging.memory.InMemoryConnector; +import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; import jakarta.enterprise.inject.Any; import jakarta.inject.Inject; import jakarta.persistence.EntityManager; import jakarta.persistence.NoResultException; -import jakarta.transaction.Transactional; import org.apache.commons.lang3.RandomStringUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; import java.time.LocalDateTime; import java.time.ZoneOffset; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.UUID; import static com.redhat.cloud.notifications.events.EventConsumer.INGRESS_CHANNEL; import static com.redhat.cloud.notifications.models.SubscriptionType.DAILY; @@ -54,16 +47,12 @@ import static com.redhat.cloud.notifications.processors.email.EmailSubscriptionTypeProcessor.AGGREGATION_COMMAND_REJECTED_COUNTER_NAME; import static com.redhat.cloud.notifications.processors.email.EmailSubscriptionTypeProcessor.AGGREGATION_CONSUMED_TIMER_NAME; import static java.time.ZoneOffset.UTC; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anySet; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.timeout; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -74,9 +63,6 @@ class EmailSubscriptionTypeProcessorTest { final String APP_NAME = "notifications"; final String EVENT_TYPE_NAME = "aggregation"; - @Inject - EmailSubscriptionTypeProcessor testee; - @Inject @Any InMemoryConnector inMemoryConnector; @@ -88,7 +74,7 @@ class EmailSubscriptionTypeProcessorTest { RecipientResolver recipientResolver; @InjectMock - EmailSender sender; + ConnectorSender connectorSender; @Inject MicrometerAssertionHelper micrometerAssertionHelper; @@ -108,24 +94,8 @@ void beforeEach() { featureFlipper.setUseDefaultTemplate(false); } - @Test - void shouldNotProcessWhenEndpointsAreNull() { - testee.process(new Event(), null); - verify(sender, never()).sendEmail(any(Set.class), any(Event.class), any(TemplateInstance.class), any(TemplateInstance.class), anyBoolean(), any(Endpoint.class)); - } - - @Test - void shouldNotProcessWhenEndpointsAreEmpty() { - testee.process(new Event(), List.of()); - verify(sender, never()).sendEmail(any(Set.class), any(Event.class), any(TemplateInstance.class), any(TemplateInstance.class), anyBoolean(), any(Endpoint.class)); - } - @Test void shouldSuccessfullySendOneAggregatedEmailWithTwoRecipients() { - NotificationHistory nh = new NotificationHistory(); - nh.setDetails(Map.of(EmailSubscriptionTypeProcessor.TOTAL_RECIPIENTS_KEY, 1)); - when(sender.sendEmail(any(Set.class), any(Event.class), any(TemplateInstance.class), any(TemplateInstance.class), anyBoolean(), any(Endpoint.class))).thenReturn(nh); - micrometerAssertionHelper.saveCounterValuesBeforeTest(AGGREGATION_COMMAND_REJECTED_COUNTER_NAME, AGGREGATION_COMMAND_PROCESSED_COUNTER_NAME, AGGREGATION_COMMAND_ERROR_COUNTER_NAME); // Because this test will use a real Payload Aggregator @@ -143,7 +113,7 @@ void shouldSuccessfullySendOneAggregatedEmailWithTwoRecipients() { DAILY ); - createAggregatorEventTypeIfNeeded(INGRESS_CHANNEL); + createAggregatorEventTypeIfNeeded(); User user1 = new User(); user1.setUsername("foo"); @@ -200,9 +170,21 @@ void shouldSuccessfullySendOneAggregatedEmailWithTwoRecipients() { eq(aggregationCommand2.getEnd()) ); - verify(sender, timeout(5000L).times(1)).sendEmail(eq(Set.of(user1, user2)), any(), any(TemplateInstance.class), any(TemplateInstance.class), anyBoolean(), any(Endpoint.class)); - verify(sender, timeout(5000L).times(1)).sendEmail(eq(Set.of(user3)), any(), any(TemplateInstance.class), any(TemplateInstance.class), anyBoolean(), any(Endpoint.class)); - getEventHistory(INGRESS_CHANNEL); + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(JsonObject.class); + verify(connectorSender, timeout(5000L).times(2)).send(any(Event.class), any(Endpoint.class), argumentCaptor.capture()); + List capturedPayloads = argumentCaptor.getAllValues(); + assertTrue( + capturedPayloads.stream().anyMatch(capturedPayload -> { + JsonArray subscribers = capturedPayload.getJsonArray("subscribers"); + return subscribers.size() == 2 && subscribers.contains(user1.getUsername()) && subscribers.contains(user2.getUsername()); + }) + ); + assertTrue( + capturedPayloads.stream().anyMatch(capturedPayload -> { + JsonArray subscribers = capturedPayload.getJsonArray("subscribers"); + return subscribers.size() == 1 && subscribers.contains(user3.getUsername()); + }) + ); } finally { if (null != blankAgg2) { resourceHelpers.deleteEmailTemplatesById(blankAgg2.getId()); @@ -211,27 +193,13 @@ void shouldSuccessfullySendOneAggregatedEmailWithTwoRecipients() { micrometerAssertionHelper.clearSavedValues(); } - private void createAggregatorEventTypeIfNeeded(String channel) { - if (INGRESS_CHANNEL.equals(channel)) { - Application aggregatorApp = resourceHelpers.findOrCreateApplication(BUNDLE_NAME, APP_NAME); - String eventTypeName = EVENT_TYPE_NAME; - try { - resourceHelpers.findEventType(aggregatorApp.getId(), eventTypeName); - } catch (NoResultException nre) { - resourceHelpers.createEventType(aggregatorApp.getId(), eventTypeName); - } - } - } - - @Transactional - public void getEventHistory(String channel) { - if (INGRESS_CHANNEL.equals(channel)) { - String query = "SELECT nh FROM NotificationHistory nh WHERE nh.event.eventType.name = 'aggregation'"; - List histories = entityManager.createQuery(query, NotificationHistory.class).getResultList(); - assertFalse(histories.isEmpty()); - entityManager.createQuery("delete from NotificationHistory").executeUpdate(); - histories = entityManager.createQuery(query, NotificationHistory.class).getResultList(); - assertTrue(histories.isEmpty()); + private void createAggregatorEventTypeIfNeeded() { + Application aggregatorApp = resourceHelpers.findOrCreateApplication(BUNDLE_NAME, APP_NAME); + String eventTypeName = EVENT_TYPE_NAME; + try { + resourceHelpers.findEventType(aggregatorApp.getId(), eventTypeName); + } catch (NoResultException nre) { + resourceHelpers.createEventType(aggregatorApp.getId(), eventTypeName); } } @@ -256,64 +224,4 @@ private String buildAggregatorAction(AggregationCommand aggregationCommand) { return Parser.encode(action); } - - @Test - void shouldSendSingleEmailWithTwoRecieversUsingTemplatesFromDatabase() { - try { - featureFlipper.setUseDefaultTemplate(true); - - User user1 = new User(); - user1.setUsername("foo"); - User user2 = new User(); - user2.setUsername("bar"); - - when(recipientResolver.recipientUsers(any(), any(), any(), eq(false))) - .thenReturn(Set.of(user1, user2)); - - Bundle bundle = new Bundle(); - bundle.setName("rhel"); - Application application = new Application(); - application.setId(UUID.randomUUID()); - application.setName("policies"); - application.setBundle(bundle); - EventType eventType = new EventType(); - eventType.setId(UUID.randomUUID()); - eventType.setApplication(application); - Event event = new Event(); - event.setOrgId("123456"); - event.setEventType(eventType); - event.setId(UUID.randomUUID()); - event.setEventWrapper(new EventWrapperAction( - new Action.ActionBuilder() - .withOrgId("123456") - .withEventType("triggered") - .withApplication("policies") - .withBundle("rhel") - .withTimestamp(LocalDateTime.of(2022, 8, 24, 13, 30, 0, 0)) - .withContext( - new Context.ContextBuilder() - .withAdditionalProperty("foo", "im foo") - .withAdditionalProperty("bar", Map.of("baz", "im baz")) - .build() - ) - .withEvents(List.of( - new com.redhat.cloud.notifications.ingress.Event.EventBuilder() - .withMetadata(new Metadata()) - .withPayload(new Payload()) - .build() - )) - .build() - )); - - Endpoint endpoint = new Endpoint(); - endpoint.setProperties(new SystemSubscriptionProperties()); - endpoint.setType(EndpointType.EMAIL_SUBSCRIPTION); - - testee.process(event, List.of(endpoint)); - verify(sender, times(1)).sendEmail(eq(Set.of(user1, user2)), eq(event), any(TemplateInstance.class), any(TemplateInstance.class), eq(true), any(Endpoint.class)); - } finally { - featureFlipper.setUseDefaultTemplate(false); - } - } - } diff --git a/engine/src/test/java/com/redhat/cloud/notifications/processors/email/EmailSubscriptionTypeProcessorWithMigratedTemplateTest.java b/engine/src/test/java/com/redhat/cloud/notifications/processors/email/EmailSubscriptionTypeProcessorWithMigratedTemplateTest.java deleted file mode 100644 index bc5152c404..0000000000 --- a/engine/src/test/java/com/redhat/cloud/notifications/processors/email/EmailSubscriptionTypeProcessorWithMigratedTemplateTest.java +++ /dev/null @@ -1,193 +0,0 @@ -package com.redhat.cloud.notifications.processors.email; - -import com.redhat.cloud.notifications.TestHelpers; -import com.redhat.cloud.notifications.TestLifecycleManager; -import com.redhat.cloud.notifications.db.repositories.TemplateRepository; -import com.redhat.cloud.notifications.events.EventWrapperAction; -import com.redhat.cloud.notifications.ingress.Action; -import com.redhat.cloud.notifications.models.Endpoint; -import com.redhat.cloud.notifications.models.EndpointType; -import com.redhat.cloud.notifications.models.Event; -import com.redhat.cloud.notifications.models.EventType; -import com.redhat.cloud.notifications.models.SystemSubscriptionProperties; -import com.redhat.cloud.notifications.processors.webhooks.WebhookTypeProcessor; -import com.redhat.cloud.notifications.recipients.itservice.ITUserService; -import com.redhat.cloud.notifications.recipients.itservice.pojo.request.ITUserRequest; -import com.redhat.cloud.notifications.recipients.itservice.pojo.response.AccountRelationship; -import com.redhat.cloud.notifications.recipients.itservice.pojo.response.Authentication; -import com.redhat.cloud.notifications.recipients.itservice.pojo.response.ITUserResponse; -import com.redhat.cloud.notifications.recipients.itservice.pojo.response.PersonalInformation; -import io.quarkus.test.InjectMock; -import io.quarkus.test.common.QuarkusTestResource; -import io.quarkus.test.junit.QuarkusTest; -import io.quarkus.test.junit.mockito.InjectSpy; -import io.vertx.core.json.JsonObject; -import jakarta.inject.Inject; -import jakarta.persistence.EntityManager; -import jakarta.transaction.Transactional; -import org.eclipse.microprofile.rest.client.inject.RestClient; -import org.junit.jupiter.api.Test; -import org.mockito.Mockito; - -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -import static com.redhat.cloud.notifications.TestConstants.DEFAULT_ORG_ID; -import static com.redhat.cloud.notifications.models.SubscriptionType.INSTANT; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -@QuarkusTest -@QuarkusTestResource(TestLifecycleManager.class) -public class EmailSubscriptionTypeProcessorWithMigratedTemplateTest { - - @Inject - EmailSubscriptionTypeProcessor emailProcessor; - - @Inject - EntityManager entityManager; - - @InjectMock - @RestClient - ITUserService itUserService; - - @InjectMock - WebhookTypeProcessor webhookSender; - - @InjectSpy - TemplateRepository templateRepository; - - String commonTest() { - mockGetUsers(1); - - final String tenant = "instant-email-tenant"; - final String username = "username-0"; - String bundle = "rhel"; - String application = "policies"; - - addEventTypeSubscription(DEFAULT_ORG_ID, username); - - final List bodyRequests = new ArrayList<>(); - - doAnswer(invocation -> { - Object[] args = invocation.getArguments(); - bodyRequests.add(String.valueOf(args[3])); // The fourth argument is email Payload - return null; - }).when(webhookSender) - .doHttpRequest(any(), any(), any(), any(), anyString(), anyString(), anyBoolean()); - - Action emailActionMessage = TestHelpers.createPoliciesAction(tenant, bundle, application, "My test machine"); - List events = getEventTypes(); - - Event event = new Event(); - event.setEventType(events.get(0)); - event.setEventWrapper(new EventWrapperAction(emailActionMessage)); - event.setOrgId(DEFAULT_ORG_ID); - - SystemSubscriptionProperties properties = new SystemSubscriptionProperties(); - - Endpoint ep = new Endpoint(); - ep.setType(EndpointType.EMAIL_SUBSCRIPTION); - ep.setName("positive feeling"); - ep.setDescription("needle in the haystack"); - ep.setEnabled(true); - ep.setProperties(properties); - - emailProcessor.process(event, List.of(ep)); - - assertEquals(1, bodyRequests.size()); - JsonObject email = new JsonObject(bodyRequests.get(0)); - String bodyRequest = email.getJsonArray("emails").getJsonObject(0).getString("body"); - - assertTrue(bodyRequest.contains(TestHelpers.policyId1), "Body should contain policy id" + TestHelpers.policyId1); - assertTrue(bodyRequest.contains(TestHelpers.policyName1), "Body should contain policy name" + TestHelpers.policyName1); - - assertTrue(bodyRequest.contains(TestHelpers.policyId2), "Body should contain policy id" + TestHelpers.policyId2); - assertTrue(bodyRequest.contains(TestHelpers.policyName2), "Body should contain policy name" + TestHelpers.policyName2); - - // Display name - assertTrue(bodyRequest.contains("My test machine"), "Body should contain the display_name"); - - return bodyRequest; - } - - @Test - void testEmailSubscriptionInstantFromDatabase() { - String renderedEmailFromDb = commonTest(); - verify(templateRepository, times(1)).findInstantEmailTemplate(any(UUID.class)); - - String renderedEmailFromFs = commonTest(); - assertEquals(renderedEmailFromDb, renderedEmailFromFs); - } - - private void mockGetUsers(int elements) { - MockedUserAnswer answer = new MockedUserAnswer(elements); - Mockito.when(itUserService.getUsers(Mockito.any(ITUserRequest.class) - )).then(invocationOnMock -> answer.mockedUserAnswer()); - } - - static class MockedUserAnswer { - - private final int expectedElements; - - MockedUserAnswer(int expectedElements) { - this.expectedElements = expectedElements; - } - - List mockedUserAnswer() { - - List users = new ArrayList<>(); - for (int i = 0; i < expectedElements; ++i) { - ITUserResponse user = new ITUserResponse(); - - user.authentications = new ArrayList<>(); - user.authentications.add(new Authentication()); - user.authentications.get(0).principal = String.format("username-%d", i); - - com.redhat.cloud.notifications.recipients.itservice.pojo.response.Email email = new com.redhat.cloud.notifications.recipients.itservice.pojo.response.Email(); - email.address = String.format("username-%d@foobardotcom", i); - user.accountRelationships = new ArrayList<>(); - user.accountRelationships.add(new AccountRelationship()); - user.accountRelationships.get(0).emails = List.of(email); - - user.personalInformation = new PersonalInformation(); - user.personalInformation.firstName = "foo"; - user.personalInformation.lastNames = "bar"; - - users.add(user); - } - - return users; - } - } - - @Transactional - public int addEventTypeSubscription(String orgId, String username) { - String query = "INSERT INTO email_subscriptions(org_id, user_id, event_type_id, subscription_type, subscribed) " + - "VALUES (:orgId, :userId, :eventTypeId, :subscriptionType, :subscribed) " + - "ON CONFLICT (org_id, user_id, event_type_id, subscription_type) DO NOTHING"; // The value is already on the database, this is OK - - // HQL does not support the ON CONFLICT clause so we need a native query here - return entityManager.createNativeQuery(query) - .setParameter("orgId", orgId) - .setParameter("userId", username) - .setParameter("eventTypeId", getEventTypes().get(0).getId()) - .setParameter("subscriptionType", INSTANT.name()) - .setParameter("subscribed", true) - .executeUpdate(); - } - - List getEventTypes() { - String query = "From EventType where name= :name"; - return entityManager.createQuery(query, EventType.class) - .setParameter("name", "policy-triggered") - .getResultList(); - } -} diff --git a/engine/src/test/java/com/redhat/cloud/notifications/processors/email/EmailTest.java b/engine/src/test/java/com/redhat/cloud/notifications/processors/email/EmailTest.java deleted file mode 100644 index c48a02c012..0000000000 --- a/engine/src/test/java/com/redhat/cloud/notifications/processors/email/EmailTest.java +++ /dev/null @@ -1,371 +0,0 @@ -package com.redhat.cloud.notifications.processors.email; - -import com.redhat.cloud.notifications.MockServerLifecycleManager; -import com.redhat.cloud.notifications.TestHelpers; -import com.redhat.cloud.notifications.db.repositories.NotificationHistoryRepository; -import com.redhat.cloud.notifications.events.EventWrapperAction; -import com.redhat.cloud.notifications.ingress.Action; -import com.redhat.cloud.notifications.ingress.Context; -import com.redhat.cloud.notifications.ingress.Metadata; -import com.redhat.cloud.notifications.ingress.Payload; -import com.redhat.cloud.notifications.models.Endpoint; -import com.redhat.cloud.notifications.models.EndpointType; -import com.redhat.cloud.notifications.models.Event; -import com.redhat.cloud.notifications.models.NotificationHistory; -import com.redhat.cloud.notifications.models.NotificationStatus; -import com.redhat.cloud.notifications.models.SystemSubscriptionProperties; -import com.redhat.cloud.notifications.recipients.itservice.ITUserService; -import com.redhat.cloud.notifications.recipients.itservice.pojo.request.ITUserRequest; -import com.redhat.cloud.notifications.recipients.itservice.pojo.response.AccountRelationship; -import com.redhat.cloud.notifications.recipients.itservice.pojo.response.Authentication; -import com.redhat.cloud.notifications.recipients.itservice.pojo.response.ITUserResponse; -import com.redhat.cloud.notifications.recipients.itservice.pojo.response.PersonalInformation; -import com.redhat.cloud.notifications.recipients.mbop.Constants; -import io.quarkus.test.InjectMock; -import io.quarkus.test.junit.mockito.InjectSpy; -import io.vertx.core.json.JsonArray; -import io.vertx.core.json.JsonObject; -import jakarta.inject.Inject; -import jakarta.persistence.EntityManager; -import jakarta.transaction.Transactional; -import org.eclipse.microprofile.rest.client.inject.RestClient; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.Mockito; -import org.mockserver.mock.action.ExpectationResponseCallback; -import org.mockserver.model.HttpRequest; - -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.List; -import java.util.stream.Collectors; - -import static com.redhat.cloud.notifications.TestConstants.DEFAULT_ORG_ID; -import static com.redhat.cloud.notifications.models.SubscriptionType.INSTANT; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockserver.model.HttpResponse.response; - -//@QuarkusTest -//@TestInstance(TestInstance.Lifecycle.PER_CLASS) -//@QuarkusTestResource(TestLifecycleManager.class) -public class EmailTest { - - @Inject - EmailSubscriptionTypeProcessor emailProcessor; - - @Inject - EntityManager entityManager; - - @InjectMock - @RestClient - ITUserService itUserService; - - // InjectSpy allows us to update the fields via reflection (Inject does not) - @InjectSpy - EmailSender emailSender; - - @InjectMock - NotificationHistoryRepository notificationHistoryRepository; - - static final String BOP_TOKEN = "test-token"; - static final String BOP_ENV = "unitTest"; - static final String BOP_CLIENT_ID = "test-client-id"; - -// @BeforeAll -// void init() { -// String url = String.format("http://%s/v1/sendEmails", mockServerConfig.getRunningAddress()); -// -// updateField(emailSender, "bopUrl", url, EmailSender.class); -// updateField(emailSender, "bopApiToken", BOP_TOKEN, EmailSender.class); -// updateField(emailSender, "bopEnv", BOP_ENV, EmailSender.class); -// updateField(emailSender, "bopClientId", BOP_CLIENT_ID, EmailSender.class); -// } - - private HttpRequest getMockHttpRequest(ExpectationResponseCallback verifyEmptyRequest) { - HttpRequest postReq = new HttpRequest() - .withPath("/v1/sendEmails") - .withMethod("POST"); - MockServerLifecycleManager.getClient() - .withSecure(false) - .when(postReq) - .respond(verifyEmptyRequest); - return postReq; - } - - @Test - @Disabled - void testEmailSubscriptionInstant() { - mockGetUsers(8); - - final String tenant = "instant-email-tenant"; - final String[] usernames = {"username-1", "username-2", "username-4"}; - String bundle = "rhel"; - String application = "policies"; - - for (String username : usernames) { - subscribe(DEFAULT_ORG_ID, username, bundle, application); - } - - final List bodyRequests = new ArrayList<>(); - - ExpectationResponseCallback verifyEmptyRequest = req -> { - assertEquals(BOP_TOKEN, req.getHeader(Constants.MBOP_APITOKEN_HEADER).get(0)); - assertEquals(BOP_CLIENT_ID, req.getHeader(Constants.MBOP_CLIENT_ID_HEADER).get(0)); - assertEquals(BOP_ENV, req.getHeader(Constants.MBOP_ENV_HEADER).get(0)); - bodyRequests.add(req.getBodyAsString()); - return response().withStatusCode(200); - }; - - HttpRequest postReq = getMockHttpRequest(verifyEmptyRequest); - - Action emailActionMessage = TestHelpers.createPoliciesAction(tenant, bundle, application, "My test machine"); - - Event event = new Event(); - event.setEventWrapper(new EventWrapperAction(emailActionMessage)); - - SystemSubscriptionProperties properties = new SystemSubscriptionProperties(); - - Endpoint ep = new Endpoint(); - ep.setType(EndpointType.EMAIL_SUBSCRIPTION); - ep.setName("positive feeling"); - ep.setDescription("needle in the haystack"); - ep.setEnabled(true); - ep.setProperties(properties); - - try { - emailProcessor.process(event, List.of(ep)); - ArgumentCaptor historyArgumentCaptor = ArgumentCaptor.forClass(NotificationHistory.class); - verify(notificationHistoryRepository, times(1)).createNotificationHistory(historyArgumentCaptor.capture()); - List historyEntries = historyArgumentCaptor.getAllValues(); - - NotificationHistory history = historyEntries.get(0); - assertTrue(history.isInvocationResult()); - assertEquals(NotificationStatus.SUCCESS, history.getStatus()); - - assertEquals(3, bodyRequests.size()); - List emailRequests = emailRequestIsOK(bodyRequests, usernames); - - for (int i = 0; i < usernames.length; ++i) { - JsonObject body = emailRequests.get(i); - JsonArray emails = body.getJsonArray("emails"); - assertNotNull(emails); - assertEquals(1, emails.size()); - JsonObject firstEmail = emails.getJsonObject(0); - JsonArray recipients = firstEmail.getJsonArray("recipients"); - assertEquals(1, recipients.size()); - assertEquals(usernames[i], recipients.getString(0)); - - JsonArray bccList = firstEmail.getJsonArray("bccList"); - assertEquals(0, bccList.size()); - - String bodyRequest = body.toString(); - - assertTrue(bodyRequest.contains(TestHelpers.policyId1), "Body should contain policy id" + TestHelpers.policyId1); - assertTrue(bodyRequest.contains(TestHelpers.policyName1), "Body should contain policy name" + TestHelpers.policyName1); - - assertTrue(bodyRequest.contains(TestHelpers.policyId2), "Body should contain policy id" + TestHelpers.policyId2); - assertTrue(bodyRequest.contains(TestHelpers.policyName2), "Body should contain policy name" + TestHelpers.policyName2); - - // Display name - assertTrue(bodyRequest.contains("My test machine"), "Body should contain the display_name"); - - // Formatted date - assertTrue(bodyRequest.contains("03 Aug 2020 15:22 UTC")); - } - } catch (Exception e) { - e.printStackTrace(); - fail(e); - } finally { - // Remove expectations - MockServerLifecycleManager.getClient().clear(postReq); - } - clearSubscriptions(); - } - - @Test - @Disabled - void testEmailSubscriptionInstantWrongPayload() { - mockGetUsers(8); - final String tenant = "instant-email-tenant-wrong-payload"; - final String[] usernames = {"username-1", "username-2", "username-4"}; - String bundle = "rhel"; - String application = "policies"; - - for (String username : usernames) { - subscribe(DEFAULT_ORG_ID, username, bundle, application); - } - - final List bodyRequests = new ArrayList<>(); - - ExpectationResponseCallback verifyEmptyRequest = req -> { - assertEquals(BOP_TOKEN, req.getHeader(Constants.MBOP_APITOKEN_HEADER).get(0)); - assertEquals(BOP_CLIENT_ID, req.getHeader(Constants.MBOP_CLIENT_ID_HEADER).get(0)); - assertEquals(BOP_ENV, req.getHeader(Constants.MBOP_ENV_HEADER).get(0)); - bodyRequests.add(req.getBodyAsString()); - return response().withStatusCode(200); - }; - - HttpRequest postReq = getMockHttpRequest(verifyEmptyRequest); - - Action emailActionMessage = new Action(); - emailActionMessage.setBundle(bundle); - emailActionMessage.setApplication(application); - emailActionMessage.setTimestamp(LocalDateTime.of(2020, 10, 3, 15, 22, 13, 25)); - emailActionMessage.setEventType(TestHelpers.eventType); - emailActionMessage.setRecipients(List.of()); - emailActionMessage.setContext( - new Context.ContextBuilder() - .withAdditionalProperty("inventory_id-wrong", "host-01") - .withAdditionalProperty("system_check_in-wrong", "2020-08-03T15:22:42.199046") - .withAdditionalProperty("display_name-wrong", "My test machine") - .withAdditionalProperty("tags-what?", List.of()) - .build() - ); - emailActionMessage.setEvents(List.of( - new com.redhat.cloud.notifications.ingress.Event.EventBuilder() - .withMetadata(new Metadata.MetadataBuilder().build()) - .withPayload(new Payload.PayloadBuilder().withAdditionalProperty("foo", "bar").build()) - .build() - )); - - emailActionMessage.setAccountId(tenant); - emailActionMessage.setOrgId(DEFAULT_ORG_ID); - - Event event = new Event(); - event.setEventWrapper(new EventWrapperAction(emailActionMessage)); - - SystemSubscriptionProperties properties = new SystemSubscriptionProperties(); - - Endpoint ep = new Endpoint(); - ep.setType(EndpointType.EMAIL_SUBSCRIPTION); - ep.setName("positive feeling"); - ep.setDescription("needle in the haystack"); - ep.setEnabled(true); - ep.setProperties(properties); - - try { - emailProcessor.process(event, List.of(ep)); - ArgumentCaptor historyArgumentCaptor = ArgumentCaptor.forClass(NotificationHistory.class); - verify(notificationHistoryRepository, times(0)).createNotificationHistory(historyArgumentCaptor.capture()); - List historyEntries = historyArgumentCaptor.getAllValues(); - - // The processor returns a null history value but Multi does not support null values so the resulting Multi is empty. - assertTrue(historyEntries.isEmpty()); - - // No email, invalid payload - assertEquals(0, bodyRequests.size()); - } catch (Exception e) { - e.printStackTrace(); - fail(e); - } finally { - // Remove expectations - MockServerLifecycleManager.getClient().clear(postReq); - } - clearSubscriptions(); - } - - private String usernameOfRequest(String request, String[] users) { - for (String user : users) { - if (request.contains(user)) { - return user; - } - } - throw new RuntimeException("No username was found in the request"); - } - - private List emailRequestIsOK(List requests, String[] usersArray) { - List emailJson = new ArrayList<>(); - - requests.sort(Comparator.comparing(s -> usernameOfRequest(s, usersArray))); - List userList = Arrays.stream(usersArray).sorted().collect(Collectors.toList()); - - assertEquals(requests.size(), userList.size()); - for (int i = 0; i < userList.size(); ++i) { - JsonObject email = new JsonObject(requests.get(i)); - JsonArray emails = email.getJsonArray("emails"); - assertNotNull(emails); - assertEquals(1, emails.size()); - JsonObject firstEmail = emails.getJsonObject(0); - JsonArray recipients = firstEmail.getJsonArray("recipients"); - assertEquals(1, recipients.size()); - assertEquals(userList.get(i), recipients.getString(0)); - - JsonArray bccList = firstEmail.getJsonArray("bccList"); - assertEquals(0, bccList.size()); - - emailJson.add(email); - } - - return emailJson; - } - - private void mockGetUsers(int elements) { - MockedUserAnswer answer = new MockedUserAnswer(elements); - Mockito.when(itUserService.getUsers(Mockito.any(ITUserRequest.class) - )).then(invocationOnMock -> answer.mockedUserAnswer()); - } - - static class MockedUserAnswer { - - private final int expectedElements; - - MockedUserAnswer(int expectedElements) { - this.expectedElements = expectedElements; - } - - List mockedUserAnswer() { - - List users = new ArrayList<>(); - for (int i = 0; i < expectedElements; ++i) { - ITUserResponse user = new ITUserResponse(); - - user.authentications = new ArrayList<>(); - user.authentications.add(new Authentication()); - user.authentications.get(0).principal = String.format("username-%d", i); - - com.redhat.cloud.notifications.recipients.itservice.pojo.response.Email email = new com.redhat.cloud.notifications.recipients.itservice.pojo.response.Email(); - email.address = String.format("username-%d@foobardotcom", i); - user.accountRelationships = new ArrayList<>(); - user.accountRelationships.add(new AccountRelationship()); - user.accountRelationships.get(0).emails = List.of(email); - - user.personalInformation = new PersonalInformation(); - user.personalInformation.firstName = "foo"; - user.personalInformation.lastNames = "bar"; - - users.add(user); - } - - return users; - } - } - - @Transactional - void subscribe(String orgId, String username, String bundleName, String applicationName) { - String query = "INSERT INTO endpoint_email_subscriptions(org_id, user_id, application_id, subscription_type) " + - "SELECT :orgId, :userId, a.id, :subscriptionType " + - "FROM applications a, bundles b WHERE a.bundle_id = b.id AND a.name = :applicationName AND b.name = :bundleName " + - "ON CONFLICT (org_id, user_id, application_id, subscription_type) DO NOTHING"; - entityManager.createNativeQuery(query) - .setParameter("orgId", orgId) - .setParameter("userId", username) - .setParameter("bundleName", bundleName) - .setParameter("applicationName", applicationName) - .setParameter("subscriptionType", INSTANT.name()) - .executeUpdate(); - } - - @Transactional - void clearSubscriptions() { - entityManager.createNativeQuery("DELETE FROM endpoint_email_subscriptions") - .executeUpdate(); - } -} diff --git a/engine/src/test/java/com/redhat/cloud/notifications/processors/webhook/WebhookTest.java b/engine/src/test/java/com/redhat/cloud/notifications/processors/webhook/WebhookTest.java index ba02056726..862cf82406 100644 --- a/engine/src/test/java/com/redhat/cloud/notifications/processors/webhook/WebhookTest.java +++ b/engine/src/test/java/com/redhat/cloud/notifications/processors/webhook/WebhookTest.java @@ -1,12 +1,10 @@ package com.redhat.cloud.notifications.processors.webhook; import com.redhat.cloud.notifications.MicrometerAssertionHelper; -import com.redhat.cloud.notifications.MockServerLifecycleManager; import com.redhat.cloud.notifications.TestLifecycleManager; import com.redhat.cloud.notifications.config.FeatureFlipper; import com.redhat.cloud.notifications.db.repositories.NotificationHistoryRepository; import com.redhat.cloud.notifications.events.EventWrapperAction; -import com.redhat.cloud.notifications.events.IntegrationDisabledNotifier; import com.redhat.cloud.notifications.ingress.Action; import com.redhat.cloud.notifications.ingress.Context; import com.redhat.cloud.notifications.ingress.Metadata; @@ -20,83 +18,45 @@ import com.redhat.cloud.notifications.models.WebhookProperties; import com.redhat.cloud.notifications.processors.webhooks.WebhookTypeProcessor; import com.redhat.cloud.notifications.transformers.BaseTransformer; -import dev.failsafe.Failsafe; import io.quarkus.test.InjectMock; import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.junit.QuarkusTest; import io.smallrye.reactive.messaging.memory.InMemoryConnector; import io.smallrye.reactive.messaging.memory.InMemorySink; -import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; import jakarta.annotation.PostConstruct; import jakarta.enterprise.inject.Any; import jakarta.inject.Inject; -import jakarta.persistence.EntityManager; -import jakarta.transaction.Transactional; import org.eclipse.microprofile.reactive.messaging.Message; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; -import org.mockito.MockedStatic; -import org.mockserver.mock.action.ExpectationResponseCallback; -import org.mockserver.model.HttpRequest; import java.time.LocalDateTime; -import java.util.ArrayList; import java.util.List; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicInteger; -import static com.redhat.cloud.notifications.MockServerLifecycleManager.getMockServerUrl; import static com.redhat.cloud.notifications.TestConstants.DEFAULT_ORG_ID; import static com.redhat.cloud.notifications.processors.ConnectorSender.TOCAMEL_CHANNEL; -import static com.redhat.cloud.notifications.processors.webhooks.WebhookTypeProcessor.CLIENT_TAG_VALUE; -import static com.redhat.cloud.notifications.processors.webhooks.WebhookTypeProcessor.DISABLED_WEBHOOKS_COUNTER; -import static com.redhat.cloud.notifications.processors.webhooks.WebhookTypeProcessor.ERROR_TYPE_TAG_KEY; -import static com.redhat.cloud.notifications.processors.webhooks.WebhookTypeProcessor.FAILED_EMAIL_COUNTER; -import static com.redhat.cloud.notifications.processors.webhooks.WebhookTypeProcessor.FAILED_WEBHOOK_COUNTER; -import static com.redhat.cloud.notifications.processors.webhooks.WebhookTypeProcessor.PROCESSED_EMAIL_COUNTER; import static com.redhat.cloud.notifications.processors.webhooks.WebhookTypeProcessor.PROCESSED_WEBHOOK_COUNTER; -import static com.redhat.cloud.notifications.processors.webhooks.WebhookTypeProcessor.RETRIED_EMAIL_COUNTER; -import static com.redhat.cloud.notifications.processors.webhooks.WebhookTypeProcessor.RETRIED_WEBHOOK_COUNTER; -import static com.redhat.cloud.notifications.processors.webhooks.WebhookTypeProcessor.SERVER_TAG_VALUE; -import static com.redhat.cloud.notifications.processors.webhooks.WebhookTypeProcessor.SUCCESSFUL_EMAIL_COUNTER; -import static com.redhat.cloud.notifications.processors.webhooks.WebhookTypeProcessor.SUCCESSFUL_WEBHOOK_COUNTER; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockserver.model.HttpResponse.response; @QuarkusTest @QuarkusTestResource(TestLifecycleManager.class) public class WebhookTest { - private static final int MAX_RETRIES = 3; - - private static final int MAX_ATTEMPTS = MAX_RETRIES + 1; - @Inject WebhookTypeProcessor webhookTypeProcessor; - @Inject - EntityManager entityManager; - @Inject MicrometerAssertionHelper micrometerAssertionHelper; @Inject FeatureFlipper featureFlipper; - @InjectMock - IntegrationDisabledNotifier integrationDisabledNotifier; - @InjectMock NotificationHistoryRepository notificationHistoryRepository; @@ -117,8 +77,7 @@ void postConstruct() { @BeforeEach void beforeEach() { inMemorySink.clear(); - micrometerAssertionHelper.saveCounterValuesBeforeTest(PROCESSED_WEBHOOK_COUNTER, PROCESSED_EMAIL_COUNTER, FAILED_WEBHOOK_COUNTER, FAILED_EMAIL_COUNTER, RETRIED_WEBHOOK_COUNTER, RETRIED_EMAIL_COUNTER, SUCCESSFUL_WEBHOOK_COUNTER, SUCCESSFUL_EMAIL_COUNTER); - micrometerAssertionHelper.saveCounterValueWithTagsBeforeTest(DISABLED_WEBHOOKS_COUNTER, ERROR_TYPE_TAG_KEY); + micrometerAssertionHelper.saveCounterValuesBeforeTest(PROCESSED_WEBHOOK_COUNTER); } @AfterEach @@ -126,254 +85,33 @@ void afterEach() { micrometerAssertionHelper.clearSavedValues(); } - private HttpRequest getMockHttpRequest(String path, ExpectationResponseCallback expectationResponseCallback) { - HttpRequest postReq = new HttpRequest() - .withPath(path) - .withMethod("POST"); - MockServerLifecycleManager.getClient() - .withSecure(false) - .when(postReq) - .respond(expectationResponseCallback); - return postReq; - } - - List commonTestWebhook(boolean isEmail) { - String url = getMockServerUrl() + "/foobar"; - - final List bodyRequests = new ArrayList<>(); - ExpectationResponseCallback verifyEmptyRequest = req -> { - bodyRequests.add(req.getBodyAsString()); - return response().withStatusCode(200); - }; - - HttpRequest postReq = getMockHttpRequest("/foobar", verifyEmptyRequest); - + @Test + void testWebhookUsingConnector() { + String testUrl = "https://my.webhook.connector.com"; Action webhookActionMessage = buildWebhookAction(); Event event = new Event(); event.setEventWrapper(new EventWrapperAction(webhookActionMessage)); - Endpoint ep = buildWebhookEndpoint(url); - if (isEmail) { - ep.setType(EndpointType.EMAIL_SUBSCRIPTION); - } - try { - webhookTypeProcessor.process(event, List.of(ep)); - ArgumentCaptor historyArgumentCaptor = ArgumentCaptor.forClass(NotificationHistory.class); - verify(notificationHistoryRepository, times(1)).createNotificationHistory(historyArgumentCaptor.capture()); - NotificationHistory history = historyArgumentCaptor.getAllValues().get(0); - assertTrue(history.isInvocationResult()); - assertEquals(NotificationStatus.SUCCESS, history.getStatus()); - } catch (Exception e) { - fail(e); - } finally { - // Remove expectations - MockServerLifecycleManager.getClient().clear(postReq); - } - return bodyRequests; - } - - @Test - void testEmailWebhook() { - commonTestWebhook(true); - validateCounters(0, 1, 0, 1, 0, 0, 0, 0); - } - - @Test - void testWebhookUsingConnector() { - try { - featureFlipper.setWebhookConnectorEnabled(true); - - String testUrl = "https://my.webhook.connector.com"; - Action webhookActionMessage = buildWebhookAction(); - Event event = new Event(); - event.setEventWrapper(new EventWrapperAction(webhookActionMessage)); - Endpoint ep = buildWebhookEndpoint("https://my.webhook.connector.com"); - try { - webhookTypeProcessor.process(event, List.of(ep)); - ArgumentCaptor historyArgumentCaptor = ArgumentCaptor.forClass(NotificationHistory.class); - verify(notificationHistoryRepository, times(1)).createNotificationHistory(historyArgumentCaptor.capture()); - NotificationHistory history = historyArgumentCaptor.getAllValues().get(0); - assertFalse(history.isInvocationResult()); - assertEquals(NotificationStatus.PROCESSING, history.getStatus()); - // Now let's check the Kafka messages sent to the outgoing channel. - // The channel should have received two messages. - assertEquals(1, inMemorySink.received().size()); - - // We'll only check the payload and metadata of the first Kafka message. - Message message = inMemorySink.received().get(0); - JsonObject payload = message.getPayload(); - assertEquals(testUrl, ((WebhookProperties) payload.getValue("endpoint_properties")).getUrl()); - - final JsonObject payloadToSent = transformer.toJsonObject(event); - assertEquals(payloadToSent, payload.getJsonObject("payload")); - - } catch (Exception e) { - fail(e); - } - } finally { - featureFlipper.setWebhookConnectorEnabled(false); - } - } - - @Test - void testWebhook() { - final List bodyRequests = commonTestWebhook(false); - - assertEquals(1, bodyRequests.size()); - JsonObject webhookInput = new JsonObject(bodyRequests.get(0)); - - assertEquals("mybundle", webhookInput.getString("bundle")); - assertEquals("WebhookTest", webhookInput.getString("application")); - assertEquals("testWebhook", webhookInput.getString("event_type")); - assertEquals("tenant", webhookInput.getString("account_id")); - assertEquals(DEFAULT_ORG_ID, webhookInput.getString("org_id")); - - JsonObject webhookInputContext = webhookInput.getJsonObject("context"); - assertEquals("more", webhookInputContext.getString("free")); - assertEquals(1, webhookInputContext.getInteger("format")); - assertEquals("stuff", webhookInputContext.getString("here")); - - JsonArray webhookInputEvents = webhookInput.getJsonArray("events"); - assertEquals(2, webhookInputEvents.size()); - - JsonObject webhookInputPayload1 = webhookInputEvents.getJsonObject(0).getJsonObject("payload"); - assertEquals("thing", webhookInputPayload1.getString("any")); - assertEquals(1, webhookInputPayload1.getInteger("we")); - assertEquals("here", webhookInputPayload1.getString("want")); - } - - @Test - void testRetryWithFinalSuccess() { - testRetry(true, false); - validateCounters(1, 0, 1, 0, 0, 0, MAX_RETRIES, 0); - } - - @Test - void testRetryWithFinalFailure() { - testRetry(false, false); - validateCounters(1, 0, 0, 0, 1, 0, MAX_RETRIES, 0); - } + Endpoint ep = buildWebhookEndpoint("https://my.webhook.connector.com"); - @Test - void testEmailRetryWithFinalSuccess() { - testRetry(true, true); - validateCounters(0, 1, 0, 1, 0, 0, 0, MAX_RETRIES); - } - - @Test - void testEmailRetryWithFinalFailure() { - testRetry(false, true); - validateCounters(0, 1, 0, 0, 0, 1, 0, MAX_RETRIES); - } - - @Test - void testFailuresAsException() { - // Mocks the static Failsafe method "with" to trigger a synthetic runtime exception - try (MockedStatic failsafeMockedStatic = mockStatic(Failsafe.class)) { - failsafeMockedStatic.when(() -> Failsafe.with(any())).thenThrow(new RuntimeException()); - String url = getMockServerUrl() + "/foobar"; - Action action = buildWebhookAction(); - Event event = new Event(); - event.setEventWrapper(new EventWrapperAction(action)); - Endpoint ep = buildWebhookEndpoint(url); - webhookTypeProcessor.process(event, List.of(ep)); - - ArgumentCaptor historyArgumentCaptor = ArgumentCaptor.forClass(NotificationHistory.class); - verify(notificationHistoryRepository, times(1)).createNotificationHistory(historyArgumentCaptor.capture()); - NotificationHistory history = historyArgumentCaptor.getAllValues().get(0); - - assertFalse(history.isInvocationResult()); - assertEquals(NotificationStatus.FAILED_INTERNAL, history.getStatus()); - validateCounters(1, 0, 0, 0, 1, 0, 0, 0); - } - } + webhookTypeProcessor.process(event, List.of(ep)); + ArgumentCaptor historyArgumentCaptor = ArgumentCaptor.forClass(NotificationHistory.class); + verify(notificationHistoryRepository, times(1)).createNotificationHistory(historyArgumentCaptor.capture()); + NotificationHistory history = historyArgumentCaptor.getAllValues().get(0); + assertFalse(history.isInvocationResult()); + assertEquals(NotificationStatus.PROCESSING, history.getStatus()); + // Now let's check the Kafka messages sent to the outgoing channel. + // The channel should have received two messages. + assertEquals(1, inMemorySink.received().size()); - private void testRetry(boolean shouldSucceedEventually, boolean isEmailEndpoint) { - String url = getMockServerUrl() + "/foobar"; + // We'll only check the payload and metadata of the first Kafka message. + Message message = inMemorySink.received().get(0); + JsonObject payload = message.getPayload(); + assertEquals(testUrl, payload.getJsonObject("endpoint_properties").getString("url")); - AtomicInteger callsCounter = new AtomicInteger(); - ExpectationResponseCallback expectationResponseCallback = request -> { - if (callsCounter.incrementAndGet() == MAX_ATTEMPTS && shouldSucceedEventually) { - return response().withStatusCode(200); - } else { - return response().withStatusCode(500); - } - }; + final JsonObject payloadToSent = transformer.toJsonObject(event); + assertEquals(payloadToSent, payload.getJsonObject("payload")); - HttpRequest mockServerRequest = getMockHttpRequest("/foobar", expectationResponseCallback); - try { - Action action = buildWebhookAction(); - Event event = new Event(); - event.setEventWrapper(new EventWrapperAction(action)); - Endpoint ep = buildWebhookEndpoint(url); - if (isEmailEndpoint) { - ep.setType(EndpointType.EMAIL_SUBSCRIPTION); - } - webhookTypeProcessor.process(event, List.of(ep)); - ArgumentCaptor historyArgumentCaptor = ArgumentCaptor.forClass(NotificationHistory.class); - verify(notificationHistoryRepository, times(1)).createNotificationHistory(historyArgumentCaptor.capture()); - NotificationHistory history = historyArgumentCaptor.getAllValues().get(0); - - assertEquals(shouldSucceedEventually, history.isInvocationResult()); - assertEquals(shouldSucceedEventually ? NotificationStatus.SUCCESS : NotificationStatus.FAILED_INTERNAL, history.getStatus()); - assertEquals(MAX_ATTEMPTS, callsCounter.get()); - } finally { - // Remove expectations - MockServerLifecycleManager.getClient().clear(mockServerRequest); - } - } - - @Test - void testDisableEndpointOnClientError() { - featureFlipper.setDisableWebhookEndpointsOnFailure(true); - - HttpRequest mockServerRequest = getMockHttpRequest("/client-error", request -> response().withStatusCode(401)); - try { - Action action = buildWebhookAction(); - Event event = new Event(); - event.setEventWrapper(new EventWrapperAction(action)); - Endpoint ep = buildWebhookEndpoint(getMockServerUrl() + "/client-error"); - persistEndpoint(ep); - assertTrue(ep.isEnabled()); - webhookTypeProcessor.process(event, List.of(ep)); - micrometerAssertionHelper.assertCounterIncrement(DISABLED_WEBHOOKS_COUNTER, 1, ERROR_TYPE_TAG_KEY, CLIENT_TAG_VALUE); - verify(integrationDisabledNotifier, times(1)).clientError(eq(ep), eq(401)); - assertFalse(getEndpoint(ep.getId()).isEnabled()); - } finally { - // Remove expectations - MockServerLifecycleManager.getClient().clear(mockServerRequest); - } - validateCounters(1, 0, 0, 0, 1, 0, 0, 0); - featureFlipper.setDisableWebhookEndpointsOnFailure(false); - } - - @Test - void testDisableEndpointOnServerError() { - featureFlipper.setDisableWebhookEndpointsOnFailure(true); - - HttpRequest mockServerRequest = getMockHttpRequest("/server-error", request -> response().withStatusCode(503)); - try { - Action action = buildWebhookAction(); - Event event = new Event(); - event.setEventWrapper(new EventWrapperAction(action)); - Endpoint ep = buildWebhookEndpoint(getMockServerUrl() + "/server-error"); - persistEndpoint(ep); - assertTrue(ep.isEnabled()); - for (int i = 0; i < 4; i++) { - /* - * The processor retries 3 times in case of server error, - * so the endpoint will actually be called 16 times in this test. - */ - webhookTypeProcessor.process(event, List.of(ep)); - } - micrometerAssertionHelper.assertCounterIncrement(DISABLED_WEBHOOKS_COUNTER, 1, ERROR_TYPE_TAG_KEY, SERVER_TAG_VALUE); - verify(integrationDisabledNotifier, times(1)).tooManyServerErrors(eq(ep), eq(10)); - assertFalse(getEndpoint(ep.getId()).isEnabled()); - } finally { - // Remove expectations - MockServerLifecycleManager.getClient().clear(mockServerRequest); - } - validateCounters(4, 0, 0, 0, 4, 0, 12, 0); - featureFlipper.setDisableWebhookEndpointsOnFailure(false); + micrometerAssertionHelper.assertCounterIncrement(PROCESSED_WEBHOOK_COUNTER, 1); } @Test @@ -390,19 +128,6 @@ void testEmailsOnlyMode() { } finally { featureFlipper.setEmailsOnlyMode(false); } - validateCounters(0, 0, 0, 0, 0, 0, 0, 0); - } - - @Transactional - void persistEndpoint(Endpoint endpoint) { - entityManager.persist(endpoint); - } - - Endpoint getEndpoint(UUID id) { - String hql = "FROM Endpoint WHERE id = :id"; - return entityManager.createQuery(hql, Endpoint.class) - .setParameter("id", id) - .getSingleResult(); } private static Action buildWebhookAction() { @@ -459,16 +184,4 @@ private static Endpoint buildWebhookEndpoint(String url) { return ep; } - - - private void validateCounters(int processedWebhook, int processedEmail, int successfulWebhook, int successfulEmail, int failedWebhook, int failedEmail, int retriedWebhook, int retiedEmail) { - micrometerAssertionHelper.assertCounterIncrement(PROCESSED_WEBHOOK_COUNTER, processedWebhook); - micrometerAssertionHelper.assertCounterIncrement(PROCESSED_EMAIL_COUNTER, processedEmail); - micrometerAssertionHelper.assertCounterIncrement(SUCCESSFUL_WEBHOOK_COUNTER, successfulWebhook); - micrometerAssertionHelper.assertCounterIncrement(SUCCESSFUL_EMAIL_COUNTER, successfulEmail); - micrometerAssertionHelper.assertCounterIncrement(FAILED_WEBHOOK_COUNTER, failedWebhook); - micrometerAssertionHelper.assertCounterIncrement(FAILED_EMAIL_COUNTER, failedEmail); - micrometerAssertionHelper.assertCounterIncrement(RETRIED_WEBHOOK_COUNTER, retriedWebhook); - micrometerAssertionHelper.assertCounterIncrement(RETRIED_EMAIL_COUNTER, retiedEmail); - } } diff --git a/engine/src/test/java/com/redhat/cloud/notifications/templates/TestDefaultTemplate.java b/engine/src/test/java/com/redhat/cloud/notifications/templates/TestDefaultTemplate.java index 6106d4c239..a3b81a594a 100644 --- a/engine/src/test/java/com/redhat/cloud/notifications/templates/TestDefaultTemplate.java +++ b/engine/src/test/java/com/redhat/cloud/notifications/templates/TestDefaultTemplate.java @@ -49,14 +49,14 @@ public void testInstantEmailTitle() { Action action = TestHelpers.createPoliciesAction("", "my-bundle", "my-app", "FooMachine"); String result = generateEmailSubject(EVENT_TYPE_NAME, action); - assertTrue(result.contains("my-bundle/my-app/test-email-subscription-instant triggered"), "Title contains the bundle/app/event-type"); + assertTrue(result.contains("my-bundle/my-app/policy-triggered triggered"), "Title contains the bundle/app/event-type"); } @Test public void testInstantEmailBody() { Action action = TestHelpers.createPoliciesAction("", "my-bundle", "my-app", "FooMachine"); String result = generateEmailBody(EVENT_TYPE_NAME, action); - assertTrue(result.contains("my-bundle/my-app/test-email-subscription-instant notification was triggered"), "Body should contain bundle/app/event-type"); + assertTrue(result.contains("my-bundle/my-app/policy-triggered notification was triggered"), "Body should contain bundle/app/event-type"); assertTrue(result.contains("You are receiving this email because the email template associated with this event type is not configured properly")); } diff --git a/engine/src/test/java/com/redhat/cloud/notifications/utils/LineBreakRemoverTest.java b/engine/src/test/java/com/redhat/cloud/notifications/utils/LineBreakRemoverTest.java deleted file mode 100644 index 3e04b3d291..0000000000 --- a/engine/src/test/java/com/redhat/cloud/notifications/utils/LineBreakRemoverTest.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.redhat.cloud.notifications.utils; - -import org.junit.jupiter.api.Test; - -import static com.redhat.cloud.notifications.utils.LineBreakCleaner.clean; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -public class LineBreakRemoverTest { - - @Test - void testCr() { - String initialValue = "Hello,\r world\r"; - assertTrue(initialValue.contains("\r")); - String cleanedValue = clean(initialValue); - assertFalse(cleanedValue.contains("\r")); - } - - @Test - void testLf() { - String initialValue = "Hello,\n world\n"; - assertTrue(initialValue.contains("\n")); - String cleanedValue = clean(initialValue); - assertFalse(cleanedValue.contains("\n")); - } - - @Test - void testCrLf() { - String initialValue = "Hello,\r\n world\r\n"; - assertTrue(initialValue.contains("\r")); - assertTrue(initialValue.contains("\n")); - String cleanedValue = clean(initialValue); - assertFalse(cleanedValue.contains("\r")); - assertFalse(cleanedValue.contains("\n")); - } - - @Test - void testNull() { - assertNull(clean(null)); - } -}