Skip to content

Commit

Permalink
[RHCLOUD-36586] Various improvement of email connector:
Browse files Browse the repository at this point in the history
* refactor tests
* use a jackson mapper to read incoming payload data
* rename external criteria to extenal criterion
  • Loading branch information
g-duval committed Dec 4, 2024
1 parent 1f9bf2e commit a29d582
Show file tree
Hide file tree
Showing 16 changed files with 435 additions and 230 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@

public abstract class ConnectorRoutesTest extends CamelQuarkusTestSupport {

private static final String KAFKA_SOURCE_MOCK = "direct:kafka-source-mock";
public static final String KAFKA_SOURCE_MOCK = "direct:kafka-source-mock";

@Inject
protected ConnectorConfig connectorConfig;
Expand Down
6 changes: 6 additions & 0 deletions connector-email/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@
<!-- Scope: test -->
<!-- Some test dependencies are declared in the "profiles" section of this POM. -->

<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-mockito</artifactId>
<scope>test</scope>
</dependency>

<!-- Camel -->
<dependency>
<groupId>org.apache.camel.quarkus</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.redhat.cloud.notifications.connector.email;

import com.redhat.cloud.notifications.connector.OutgoingCloudEventBuilder;
import io.vertx.core.json.JsonObject;
import jakarta.annotation.Priority;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Alternative;
import org.apache.camel.Exchange;
import org.apache.camel.Message;
import java.util.Optional;
import java.util.Set;

import static com.redhat.cloud.notifications.connector.ExchangeProperty.SUCCESSFUL;

@ApplicationScoped
@Alternative
@Priority(0) // The value doesn't matter.
public class CloudEventHistoryBuilder extends OutgoingCloudEventBuilder {

public static final String TOTAL_RECIPIENTS_KEY = "total_recipients";
public static final String TOTAL_FAILURE_RECIPIENTS_KEY = "total_failure_recipients";

@Override
public void process(Exchange exchange) throws Exception {
int totalRecipients = exchange.getProperty(TOTAL_RECIPIENTS_KEY, Integer.class);
Optional<Set<String>> recipientsWithError = Optional.ofNullable(exchange.getProperty("TODO", Set.class));
exchange.setProperty(SUCCESSFUL, recipientsWithError.isEmpty() || recipientsWithError.get().size() == 0);
super.process(exchange);

Message in = exchange.getIn();
JsonObject cloudEvent = new JsonObject(in.getBody(String.class));
JsonObject data = new JsonObject(cloudEvent.getString("data"));
data.getJsonObject("details").put(TOTAL_RECIPIENTS_KEY, totalRecipients);

if (recipientsWithError.isPresent()) {
data.getJsonObject("details").put(TOTAL_FAILURE_RECIPIENTS_KEY, recipientsWithError.get().size());
}

cloudEvent.put("data", data.encode());
in.setBody(cloudEvent.encode());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,12 @@
import com.redhat.cloud.notifications.connector.CloudEventDataExtractor;
import com.redhat.cloud.notifications.connector.email.config.EmailConnectorConfig;
import com.redhat.cloud.notifications.connector.email.constants.ExchangeProperty;
import com.redhat.cloud.notifications.connector.email.model.settings.RecipientSettings;
import io.vertx.core.json.JsonArray;
import com.redhat.cloud.notifications.connector.email.model.EmailNotification;
import io.vertx.core.json.JsonObject;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.apache.camel.Exchange;

import java.util.List;
import java.util.Set;

import static java.util.stream.Collectors.toSet;
Expand All @@ -30,38 +28,21 @@ public class EmailCloudEventDataExtractor extends CloudEventDataExtractor {
@Override
public void extract(final Exchange exchange, final JsonObject cloudEventData) {

final List<RecipientSettings> recipientSettings = cloudEventData.getJsonArray("recipient_settings")
.stream()
.map(JsonObject.class::cast)
.map(jsonSetting -> jsonSetting.mapTo(RecipientSettings.class))
.toList();

final Set<String> subscribers = cloudEventData.getJsonArray("subscribers", JsonArray.of())
.stream()
.map(String.class::cast)
.collect(toSet());

final Set<String> unsubscribers = cloudEventData.getJsonArray("unsubscribers", JsonArray.of())
.stream()
.map(String.class::cast)
.collect(toSet());

final JsonObject recipientsAuthorizationCriterion = cloudEventData.getJsonObject("recipients_authorization_criterion");

final Set<String> emails = recipientSettings.stream()
EmailNotification emailNotification = cloudEventData.mapTo(EmailNotification.class);
final Set<String> emails = emailNotification.recipientSettings().stream()
.filter(settings -> settings.getEmails() != null)
.flatMap(settings -> settings.getEmails().stream())
.collect(toSet());

exchange.setProperty(ExchangeProperty.RENDERED_BODY, cloudEventData.getString("email_body"));
exchange.setProperty(ExchangeProperty.RENDERED_SUBJECT, cloudEventData.getString("email_subject"));
exchange.setProperty(ExchangeProperty.RECIPIENT_SETTINGS, recipientSettings);
exchange.setProperty(ExchangeProperty.SUBSCRIBED_BY_DEFAULT, cloudEventData.getBoolean("subscribed_by_default"));
exchange.setProperty(ExchangeProperty.SUBSCRIBERS, subscribers);
exchange.setProperty(ExchangeProperty.UNSUBSCRIBERS, unsubscribers);
exchange.setProperty(ExchangeProperty.AUTHORIZATION_CRITERIA, recipientsAuthorizationCriterion);
exchange.setProperty(ExchangeProperty.RENDERED_BODY, emailNotification.emailBody());
exchange.setProperty(ExchangeProperty.RENDERED_SUBJECT, emailNotification.emailSubject());
exchange.setProperty(ExchangeProperty.RECIPIENT_SETTINGS, emailNotification.recipientSettings());
exchange.setProperty(ExchangeProperty.SUBSCRIBED_BY_DEFAULT, emailNotification.subscribedByDefault());
exchange.setProperty(ExchangeProperty.SUBSCRIBERS, emailNotification.subscribers());
exchange.setProperty(ExchangeProperty.UNSUBSCRIBERS, emailNotification.unsubscribers());
exchange.setProperty(ExchangeProperty.AUTHORIZATION_CRITERION, emailNotification.externalAuthorizationCriterion());
exchange.setProperty(ExchangeProperty.EMAIL_RECIPIENTS, emails);
exchange.setProperty(ExchangeProperty.EMAIL_SENDER, cloudEventData.getString("email_sender"));
exchange.setProperty(ExchangeProperty.EMAIL_SENDER, emailNotification.emailSender());

exchange.setProperty(ExchangeProperty.USE_EMAIL_BOP_V1_SSL, emailConnectorConfig.isEnableBopServiceWithSslChecks());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,5 @@ public class ExchangeProperty {

public static final String USE_EMAIL_BOP_V1_SSL = "use_email_bop_V1_ssl";

public static final String AUTHORIZATION_CRITERIA = "authorization_criteria";
public static final String AUTHORIZATION_CRITERION = "authorization_criterion";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.redhat.cloud.notifications.connector.email.model;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.redhat.cloud.notifications.connector.email.model.settings.RecipientSettings;
import io.vertx.core.json.JsonObject;
import java.util.Collection;

/**
* Represents the data structure that the email connector is expecting.
* @param emailBody the rendered body of the email to be sent.
* @param emailSubject the rendered subject of the email to be sent.
* @param emailSender the sender that will appear in the email when
* the user receives it.
* @param orgId the organization ID associated with the
* triggered event.
* @param recipientSettings the collection of recipient settings extracted
* from both the event and the related endpoints
* to the event.
* @param subscribers the list of usernames who subscribed to the
* event type.
* @param unsubscribers the list of usernames who unsubscribed from the
* event type.
* @param subscribedByDefault true if the event type is subscribed by
* default.
* @param externalAuthorizationCriterion forward received authorization criterion.
*
*/
public record EmailNotification(
@JsonProperty("email_body") String emailBody,
@JsonProperty("email_subject") String emailSubject,
@JsonProperty("email_sender") String emailSender,
@JsonProperty("orgId") String orgId,
@JsonProperty("recipient_settings") Collection<RecipientSettings> recipientSettings,
@JsonProperty("subscribers") Collection<String> subscribers,
@JsonProperty("unsubscribers") Collection<String> unsubscribers,
@JsonProperty("subscribed_by_default") boolean subscribedByDefault,
@JsonProperty("external_authorization_criterion") JsonObject externalAuthorizationCriterion
) { }

Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,5 @@ public class RecipientsQuery {

public boolean subscribedByDefault;

public JsonObject authorizationCriteria;
public JsonObject authorizationCriterion;
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public void process(final Exchange exchange) throws JsonProcessingException {
List<RecipientSettings> recipientSettings = exchange.getProperty(ExchangeProperty.RECIPIENT_SETTINGS, List.class);
Set<String> subscribers = exchange.getProperty(ExchangeProperty.SUBSCRIBERS, Set.class);
Set<String> unsubscribers = exchange.getProperty(ExchangeProperty.UNSUBSCRIBERS, Set.class);
JsonObject authorizationCriteria = exchange.getProperty(ExchangeProperty.AUTHORIZATION_CRITERIA, JsonObject.class);
JsonObject authorizationCriterion = exchange.getProperty(ExchangeProperty.AUTHORIZATION_CRITERION, JsonObject.class);

boolean subscribedByDefault = exchange.getProperty(ExchangeProperty.SUBSCRIBED_BY_DEFAULT, boolean.class);
final String orgId = exchange.getProperty(ORG_ID, String.class);
Expand All @@ -37,7 +37,7 @@ public void process(final Exchange exchange) throws JsonProcessingException {
recipientsQuery.orgId = orgId;
recipientsQuery.recipientSettings = Set.copyOf(recipientSettings);
recipientsQuery.subscribedByDefault = subscribedByDefault;
recipientsQuery.authorizationCriteria = authorizationCriteria;
recipientsQuery.authorizationCriterion = authorizationCriterion;

// Serialize the payload.
exchange.getMessage().setBody(objectMapper.writeValueAsString(recipientsQuery));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

import static com.redhat.cloud.notifications.connector.email.CloudEventHistoryBuilder.TOTAL_RECIPIENTS_KEY;
import static java.util.stream.Collectors.toSet;

@ApplicationScoped
Expand Down Expand Up @@ -49,6 +50,7 @@ public void process(final Exchange exchange) throws JsonProcessingException {
emails.removeAll(forbiddenEmail);
}
recipientsList.addAll(emails);
exchange.setProperty(TOTAL_RECIPIENTS_KEY, recipientsList.size());

// We have to remove one from the limit, because a default recipient (like [email protected]) will be automatically added
exchange.setProperty(ExchangeProperty.FILTERED_USERS, partition(recipientsList, emailConnectorConfig.getMaxRecipientsPerEmail() - 1));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.redhat.cloud.notifications.connector.email;

import com.redhat.cloud.notifications.connector.email.config.EmailConnectorConfig;
import com.redhat.cloud.notifications.connector.http.SslTrustAllManager;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.junit.mockito.InjectSpy;
import jakarta.inject.Inject;
import org.apache.camel.Endpoint;
import org.apache.camel.quarkus.test.CamelQuarkusTestSupport;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import static org.mockito.Mockito.when;

@QuarkusTest
public class BuildBopEndpointTest extends CamelQuarkusTestSupport {

@InjectSpy
EmailConnectorConfig emailConnectorConfig;

@Inject
EmailRouteBuilder emailRouteBuilder;

@Override
public boolean isUseAdviceWith() {
return true;
}

/**
* Disables the route builder to ensure that the Camel Context does not get
* started before the routes have been advised. More information is
* available at the <a href="https://people.apache.org/~dkulp/camel/camel-test.html">dkulp's Apache Camel Test documentation page</a>.
* @return {@code false} in order to stop the Camel Context from booting
* before the routes have been advised.
*/
@Override
public boolean isUseRouteBuilder() {
return false;
}

/**
* Tests that the function under test creates the BOP endpoint with the
* {@link SslTrustAllManager} class as the SSL context parameters, and that
* that class is essentially a NOOP class.
* @throws Exception if the endpoint could not be created.
*/
@Test
void testBuildBOPEndpoint() throws Exception {
String initialBopUrl = emailConnectorConfig.getBopURL();
when(emailConnectorConfig.getBopURL()).thenReturn("https://test.com");

Endpoint bopEndpoint = this.emailRouteBuilder.setUpBOPEndpointV1().resolve(this.context);
Assertions.assertEquals(this.emailConnectorConfig.getBopURL(), bopEndpoint.getEndpointBaseUri(), "the base URI of the endpoint is not the same as the one set through the properties");

final String bopEndpointURI = bopEndpoint.getEndpointUri();
Assertions.assertTrue(bopEndpointURI.contains("trustManager%3Dcom.redhat.cloud.notifications.connector.http.SslTrustAllManager"), "the endpoint does not contain a reference to the SslTrustAllManager");
Assertions.assertTrue(bopEndpointURI.contains("x509HostnameVerifier=NO_OP"), "the base URI does not contain a mention to the NO_OP hostname verifier");
}
}
Loading

0 comments on commit a29d582

Please sign in to comment.