diff --git a/src/main/java/org/opensearch/commons/destination/message/LegacyBaseMessage.java b/src/main/java/org/opensearch/commons/destination/message/LegacyBaseMessage.java new file mode 100644 index 00000000..b5122f31 --- /dev/null +++ b/src/main/java/org/opensearch/commons/destination/message/LegacyBaseMessage.java @@ -0,0 +1,128 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package org.opensearch.commons.destination.message; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Map; + +import org.apache.http.client.utils.URIBuilder; +import org.opensearch.common.Strings; +import org.opensearch.common.io.stream.StreamInput; +import org.opensearch.common.io.stream.StreamOutput; +import org.opensearch.common.io.stream.Writeable; + +/** + * This class holds the generic parameters required for a + * message. + */ +public abstract class LegacyBaseMessage implements Writeable { + + private final LegacyDestinationType destinationType; + protected String destinationName; + protected String url; + private final String content; + + LegacyBaseMessage(final LegacyDestinationType destinationType, final String destinationName, final String content) { + if (destinationType == null) { + throw new IllegalArgumentException("Channel type must be defined"); + } + if (!Strings.hasLength(destinationName)) { + throw new IllegalArgumentException("Channel name must be defined"); + } + this.destinationType = destinationType; + this.destinationName = destinationName; + this.content = content; + } + + LegacyBaseMessage(final LegacyDestinationType destinationType, final String destinationName, final String content, final String url) { + this(destinationType, destinationName, content); + if (url == null) { + throw new IllegalArgumentException("url is invalid or empty"); + } + this.url = url; + } + + LegacyBaseMessage(StreamInput streamInput) throws IOException { + this.destinationType = streamInput.readEnum(LegacyDestinationType.class); + this.destinationName = streamInput.readString(); + this.url = streamInput.readOptionalString(); + this.content = streamInput.readString(); + } + + public void setUrl(String url) { + this.url = url; + } + + public LegacyDestinationType getChannelType() { + return destinationType; + } + + public String getChannelName() { + return destinationName; + } + + public String getMessageContent() { + return content; + } + + public String getUrl() { + return url; + } + + public URI getUri() { + return buildUri(getUrl().trim(), null, null, -1, null, null); + } + + protected URI buildUri(String endpoint, String scheme, String host, int port, String path, Map queryParams) { + try { + if (Strings.isNullOrEmpty(endpoint)) { + if (Strings.isNullOrEmpty(scheme)) { + scheme = "https"; + } + URIBuilder uriBuilder = new URIBuilder(); + if (queryParams != null) { + for (Map.Entry e : queryParams.entrySet()) + uriBuilder.addParameter(e.getKey(), e.getValue()); + } + return uriBuilder.setScheme(scheme).setHost(host).setPort(port).setPath(path).build(); + } + return new URIBuilder(endpoint).build(); + } catch (URISyntaxException exception) { + throw new IllegalStateException("Error creating URI"); + } + } + + @Override + public void writeTo(StreamOutput streamOutput) throws IOException { + streamOutput.writeEnum(destinationType); + streamOutput.writeString(destinationName); + streamOutput.writeOptionalString(url); + streamOutput.writeString(content); + } +} diff --git a/src/main/java/org/opensearch/commons/destination/message/LegacyChimeMessage.java b/src/main/java/org/opensearch/commons/destination/message/LegacyChimeMessage.java new file mode 100644 index 00000000..b457d9aa --- /dev/null +++ b/src/main/java/org/opensearch/commons/destination/message/LegacyChimeMessage.java @@ -0,0 +1,91 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package org.opensearch.commons.destination.message; + +import java.io.IOException; + +import org.opensearch.common.Strings; +import org.opensearch.common.io.stream.StreamInput; + +/** + * This class holds the contents of an Chime message + */ +public class LegacyChimeMessage extends LegacyBaseMessage { + private final String message; + + private LegacyChimeMessage(final String destinationName, final String url, final String message) { + super(LegacyDestinationType.LEGACY_CHIME, destinationName, message, url); + + if (Strings.isNullOrEmpty(message)) { + throw new IllegalArgumentException("Message content is missing"); + } + + this.message = message; + } + + public LegacyChimeMessage(StreamInput streamInput) throws IOException { + super(streamInput); + this.message = super.getMessageContent(); + } + + @Override + public String toString() { + return "DestinationType: " + getChannelType() + ", DestinationName:" + destinationName + ", Url: " + url + ", Message: <...>"; + } + + public static class Builder { + private String message; + private final String destinationName; + private String url; + + public Builder(String destinationName) { + this.destinationName = destinationName; + } + + public LegacyChimeMessage.Builder withMessage(String message) { + this.message = message; + return this; + } + + public LegacyChimeMessage.Builder withUrl(String url) { + this.url = url; + return this; + } + + public LegacyChimeMessage build() { + return new LegacyChimeMessage(this.destinationName, this.url, this.message); + } + } + + public String getMessage() { + return message; + } + + public String getUrl() { + return url; + } +} diff --git a/src/main/java/org/opensearch/commons/destination/message/LegacyCustomWebhookMessage.java b/src/main/java/org/opensearch/commons/destination/message/LegacyCustomWebhookMessage.java new file mode 100644 index 00000000..9645b327 --- /dev/null +++ b/src/main/java/org/opensearch/commons/destination/message/LegacyCustomWebhookMessage.java @@ -0,0 +1,274 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package org.opensearch.commons.destination.message; + +import java.io.IOException; +import java.net.URI; +import java.util.Map; + +import org.apache.http.client.methods.HttpPatch; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpPut; +import org.opensearch.common.Strings; +import org.opensearch.common.io.stream.StreamInput; +import org.opensearch.common.io.stream.StreamOutput; + +/** + * This class holds the content of an CustomWebhook message + */ +public class LegacyCustomWebhookMessage extends LegacyBaseMessage { + + private final String message; + private final String url; + private final String scheme; + private final String host; + private final String method; + private final int port; + private String path; + private final Map queryParams; + private Map headerParams; + + private LegacyCustomWebhookMessage( + final String destinationName, + final String url, + final String scheme, + final String host, + final Integer port, + final String path, + final String method, + final Map queryParams, + final Map headerParams, + final String message + ) { + super(LegacyDestinationType.LEGACY_CUSTOM_WEBHOOK, destinationName, message); + + if (!Strings.isNullOrEmpty(url)) { + setUrl(url.trim()); + } + + if (Strings.isNullOrEmpty(message)) { + throw new IllegalArgumentException("Message content is missing"); + } + + this.scheme = Strings.isNullOrEmpty(scheme) ? "https" : scheme; + this.port = port == null ? -1 : port; + + if (!Strings.isNullOrEmpty(path)) { + if (!path.startsWith("/")) { + this.path = "/" + path; + } + } + + if (Strings.isNullOrEmpty(url) && Strings.isNullOrEmpty(host)) { + throw new IllegalArgumentException("Either fully qualified URL or host name should be provided"); + } + + if (Strings.isNullOrEmpty(method)) { + // Default to POST for backwards compatibility + this.method = "POST"; + } else if (!HttpPost.METHOD_NAME.equals(method) && !HttpPut.METHOD_NAME.equals(method) && !HttpPatch.METHOD_NAME.equals(method)) { + throw new IllegalArgumentException("Invalid method supplied. Only POST, PUT and PATCH are allowed"); + } else { + this.method = method; + } + + this.message = message; + this.url = url; + this.host = host; + this.queryParams = queryParams; + this.headerParams = headerParams; + } + + public LegacyCustomWebhookMessage(StreamInput streamInput) throws IOException { + super(streamInput); + this.message = super.getMessageContent(); + this.url = streamInput.readOptionalString(); + this.scheme = null; + this.host = null; + this.method = streamInput.readOptionalString(); + this.port = -1; + this.path = null; + this.queryParams = null; + if (streamInput.readBoolean()) { + @SuppressWarnings("unchecked") + Map headerParams = (Map) (Map) streamInput.readMap(); + this.headerParams = headerParams; + } + } + + @Override + public String toString() { + return "DestinationType: " + + getChannelType() + + ", DestinationName:" + + destinationName + + ", Url: " + + url + + ", scheme: " + + scheme + + ", Host: " + + host + + ", Port: " + + port + + ", Path: " + + path + + ", Method: " + + method + + ", Message: <...>"; + } + + public static class Builder { + private String message; + private final String destinationName; + private String url; + private String scheme; + private String host; + private Integer port; + private String path; + private String method; + private Map queryParams; + private Map headerParams; + + public Builder(String destinationName) { + this.destinationName = destinationName; + } + + public LegacyCustomWebhookMessage.Builder withScheme(String scheme) { + this.scheme = scheme; + return this; + } + + public LegacyCustomWebhookMessage.Builder withHost(String host) { + this.host = host; + return this; + } + + public LegacyCustomWebhookMessage.Builder withPort(Integer port) { + this.port = port; + return this; + } + + public LegacyCustomWebhookMessage.Builder withPath(String path) { + this.path = path; + return this; + } + + public LegacyCustomWebhookMessage.Builder withMethod(String method) { + this.method = method; + return this; + } + + public LegacyCustomWebhookMessage.Builder withQueryParams(Map queryParams) { + this.queryParams = queryParams; + return this; + } + + public LegacyCustomWebhookMessage.Builder withHeaderParams(Map headerParams) { + this.headerParams = headerParams; + return this; + } + + public LegacyCustomWebhookMessage.Builder withMessage(String message) { + this.message = message; + return this; + } + + public LegacyCustomWebhookMessage.Builder withUrl(String url) { + this.url = url; + return this; + } + + public LegacyCustomWebhookMessage build() { + return new LegacyCustomWebhookMessage( + this.destinationName, + this.url, + this.scheme, + this.host, + this.port, + this.path, + this.method, + this.queryParams, + this.headerParams, + this.message + ); + } + } + + public String getScheme() { + return scheme; + } + + public String getHost() { + return host; + } + + public int getPort() { + return port; + } + + public String getPath() { + return path; + } + + public String getMethod() { + return method; + } + + public Map getQueryParams() { + return queryParams; + } + + public Map getHeaderParams() { + return headerParams; + } + + public URI getUri() { + return buildUri(getUrl(), getScheme(), getHost(), getPort(), getPath(), getQueryParams()); + } + + public String getMessage() { + return message; + } + + @Override + public void writeTo(StreamOutput streamOutput) throws IOException { + super.writeTo(streamOutput); + // Making LegacyCustomWebhookMessage streamable is purely to support the new pass through API from ISM -> Notification plugin + // and it only supports LegacyCustomWebhookMessage when the url is already constructed by ISM. + if (Strings.isNullOrEmpty(getUrl())) { + throw new IllegalStateException("Cannot use LegacyCustomWebhookMessage across transport wire without defining full url."); + } + streamOutput.writeOptionalString(url); + streamOutput.writeOptionalString(method); + streamOutput.writeBoolean(headerParams != null); + if (headerParams != null) { + @SuppressWarnings("unchecked") + Map headerParams = (Map) (Map) this.headerParams; + streamOutput.writeMap(headerParams); + } + } +} diff --git a/src/main/java/org/opensearch/commons/destination/message/LegacyDestinationType.java b/src/main/java/org/opensearch/commons/destination/message/LegacyDestinationType.java new file mode 100644 index 00000000..85689510 --- /dev/null +++ b/src/main/java/org/opensearch/commons/destination/message/LegacyDestinationType.java @@ -0,0 +1,36 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package org.opensearch.commons.destination.message; + +/** + * Supported legacy notification destinations for Index Management + */ +public enum LegacyDestinationType { + LEGACY_CHIME, + LEGACY_SLACK, + LEGACY_CUSTOM_WEBHOOK +} diff --git a/src/main/java/org/opensearch/commons/destination/message/LegacySlackMessage.java b/src/main/java/org/opensearch/commons/destination/message/LegacySlackMessage.java new file mode 100644 index 00000000..f426894a --- /dev/null +++ b/src/main/java/org/opensearch/commons/destination/message/LegacySlackMessage.java @@ -0,0 +1,95 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package org.opensearch.commons.destination.message; + +import java.io.IOException; + +import org.opensearch.common.Strings; +import org.opensearch.common.io.stream.StreamInput; + +/** + * This class holds the content of an Slack message + */ +public class LegacySlackMessage extends LegacyBaseMessage { + private final String message; + + private LegacySlackMessage(final String destinationName, final String url, final String message) { + super(LegacyDestinationType.LEGACY_SLACK, destinationName, message, url); + + if (Strings.isNullOrEmpty(url)) { // add URL validation + throw new IllegalArgumentException("Fully qualified URL is missing/invalid: " + url); + } + + if (Strings.isNullOrEmpty(message)) { + throw new IllegalArgumentException("Message content is missing"); + } + + this.message = message; + } + + public LegacySlackMessage(StreamInput streamInput) throws IOException { + super(streamInput); + this.message = super.getMessageContent(); + } + + @Override + public String toString() { + return "DestinationType: " + getChannelType() + ", DestinationName:" + destinationName + ", Url: " + url + ", Message: <...>"; + } + + public static class Builder { + private String message; + private String destinationName; + private String url; + + public Builder(String channelName) { + this.destinationName = channelName; + } + + public LegacySlackMessage.Builder withMessage(String message) { + this.message = message; + return this; + } + + public LegacySlackMessage.Builder withUrl(String url) { + this.url = url; + return this; + } + + public LegacySlackMessage build() { + return new LegacySlackMessage(this.destinationName, this.url, this.message); + } + } + + public String getMessage() { + return message; + } + + public String getUrl() { + return url; + } +} diff --git a/src/main/java/org/opensearch/commons/destination/response/LegacyBaseResponse.java b/src/main/java/org/opensearch/commons/destination/response/LegacyBaseResponse.java new file mode 100644 index 00000000..bbdd8d70 --- /dev/null +++ b/src/main/java/org/opensearch/commons/destination/response/LegacyBaseResponse.java @@ -0,0 +1,60 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package org.opensearch.commons.destination.response; + +import java.io.IOException; + +import org.opensearch.common.io.stream.StreamInput; +import org.opensearch.common.io.stream.StreamOutput; +import org.opensearch.common.io.stream.Writeable; + +/** + * This class holds the generic response attributes + */ +public abstract class LegacyBaseResponse implements Writeable { + protected Integer statusCode; + + LegacyBaseResponse(final Integer statusCode) { + if (statusCode == null) { + throw new IllegalArgumentException("status code is invalid"); + } + this.statusCode = statusCode; + } + + public LegacyBaseResponse(StreamInput streamInput) throws IOException { + this.statusCode = streamInput.readInt(); + } + + public int getStatusCode() { + return statusCode; + } + + @Override + public void writeTo(StreamOutput streamOutput) throws IOException { + streamOutput.writeInt(statusCode); + } +} diff --git a/src/main/java/org/opensearch/commons/destination/response/LegacyDestinationResponse.java b/src/main/java/org/opensearch/commons/destination/response/LegacyDestinationResponse.java new file mode 100644 index 00000000..bb927747 --- /dev/null +++ b/src/main/java/org/opensearch/commons/destination/response/LegacyDestinationResponse.java @@ -0,0 +1,82 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package org.opensearch.commons.destination.response; + +import java.io.IOException; + +import org.opensearch.common.io.stream.StreamInput; +import org.opensearch.common.io.stream.StreamOutput; + +/** + * This class is a place holder for destination response metadata + */ +public class LegacyDestinationResponse extends LegacyBaseResponse { + + private final String responseContent; + + private LegacyDestinationResponse(final String responseString, final Integer statusCode) { + super(statusCode); + if (responseString == null) { + throw new IllegalArgumentException("Response is missing"); + } + this.responseContent = responseString; + } + + public LegacyDestinationResponse(StreamInput streamInput) throws IOException { + super(streamInput); + this.responseContent = streamInput.readString(); + } + + public static class Builder { + private String responseContent; + private Integer statusCode; + + public LegacyDestinationResponse.Builder withResponseContent(String responseContent) { + this.responseContent = responseContent; + return this; + } + + public LegacyDestinationResponse.Builder withStatusCode(Integer statusCode) { + this.statusCode = statusCode; + return this; + } + + public LegacyDestinationResponse build() { + return new LegacyDestinationResponse(responseContent, statusCode); + } + } + + public String getResponseContent() { + return this.responseContent; + } + + @Override + public void writeTo(StreamOutput streamOutput) throws IOException { + super.writeTo(streamOutput); + streamOutput.writeString(responseContent); + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/NotificationConstants.kt b/src/main/kotlin/org/opensearch/commons/notifications/NotificationConstants.kt index 04f3affe..55dc9a44 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/NotificationConstants.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/NotificationConstants.kt @@ -42,8 +42,9 @@ object NotificationConstants { const val TAGS_TAG = "tags" const val URL_TAG = "url" const val HEADER_PARAMS_TAG = "header_params" - const val TOPIC_ARN_FIELD = "topic_arn" - const val ROLE_ARN_FIELD = "role_arn" + const val TOPIC_ARN_TAG = "topic_arn" + const val ROLE_ARN_TAG = "role_arn" + const val REGION_TAG = "region" const val HOST_TAG = "host" const val PORT_TAG = "port" const val METHOD_TAG = "method" @@ -69,5 +70,9 @@ object NotificationConstants { const val CONFIG_TYPE_LIST_TAG = "config_type_list" const val PLUGIN_FEATURES_TAG = "plugin_features" + const val FEATURE_ALERTING = "alerting" + const val FEATURE_INDEX_MANAGEMENT = "index_management" + const val FEATURE_REPORTS = "reports" + const val DEFAULT_MAX_ITEMS = 1000 } diff --git a/src/main/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterface.kt b/src/main/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterface.kt index 2f043351..c0646d2d 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterface.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterface.kt @@ -31,6 +31,7 @@ import org.opensearch.action.ActionResponse import org.opensearch.client.node.NodeClient import org.opensearch.common.io.stream.Writeable import org.opensearch.commons.ConfigConstants.OPENSEARCH_SECURITY_USER_INFO_THREAD_CONTEXT +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_INDEX_MANAGEMENT import org.opensearch.commons.notifications.action.BaseResponse import org.opensearch.commons.notifications.action.CreateNotificationConfigRequest import org.opensearch.commons.notifications.action.CreateNotificationConfigResponse @@ -44,12 +45,15 @@ import org.opensearch.commons.notifications.action.GetNotificationEventRequest import org.opensearch.commons.notifications.action.GetNotificationEventResponse import org.opensearch.commons.notifications.action.GetPluginFeaturesRequest import org.opensearch.commons.notifications.action.GetPluginFeaturesResponse +import org.opensearch.commons.notifications.action.LegacyPublishNotificationRequest +import org.opensearch.commons.notifications.action.LegacyPublishNotificationResponse import org.opensearch.commons.notifications.action.NotificationsActions.CREATE_NOTIFICATION_CONFIG_ACTION_TYPE import org.opensearch.commons.notifications.action.NotificationsActions.DELETE_NOTIFICATION_CONFIG_ACTION_TYPE import org.opensearch.commons.notifications.action.NotificationsActions.GET_FEATURE_CHANNEL_LIST_ACTION_TYPE import org.opensearch.commons.notifications.action.NotificationsActions.GET_NOTIFICATION_CONFIG_ACTION_TYPE import org.opensearch.commons.notifications.action.NotificationsActions.GET_NOTIFICATION_EVENT_ACTION_TYPE import org.opensearch.commons.notifications.action.NotificationsActions.GET_PLUGIN_FEATURES_ACTION_TYPE +import org.opensearch.commons.notifications.action.NotificationsActions.LEGACY_PUBLISH_NOTIFICATION_ACTION_TYPE import org.opensearch.commons.notifications.action.NotificationsActions.SEND_NOTIFICATION_ACTION_TYPE import org.opensearch.commons.notifications.action.NotificationsActions.UPDATE_NOTIFICATION_CONFIG_ACTION_TYPE import org.opensearch.commons.notifications.action.SendNotificationRequest @@ -217,6 +221,30 @@ object NotificationsPluginInterface { ) } + /** + * Publishes a notification API using the legacy notification implementation. No REST API. + * Internal API only for the Index Management plugin. + * @param client Node client for making transport action + * @param request The legacy publish notification request + * @param listener The listener for getting response + */ + fun publishLegacyNotification( + client: NodeClient, + request: LegacyPublishNotificationRequest, + listener: ActionListener + ) { + if (request.feature != FEATURE_INDEX_MANAGEMENT) { + // Do not change this; do not pass in FEATURE_INDEX_MANAGEMENT if you are not the Index Management plugin. + throw IllegalArgumentException("The publish notification method only supports the Index Management feature.") + } + + client.execute( + LEGACY_PUBLISH_NOTIFICATION_ACTION_TYPE, + request, + wrapActionListener(listener) { response -> recreateObject(response) { LegacyPublishNotificationResponse(it) } } + ) + } + /** * Wrap action listener on concrete response class by a new created one on ActionResponse. * This is required because the response may be loaded by different classloader across plugins. diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequest.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequest.kt index 17e4f6fb..309a31bc 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequest.kt @@ -37,7 +37,6 @@ import org.opensearch.common.xcontent.XContentBuilder import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.notifications.NotificationConstants.FEATURE_TAG -import org.opensearch.commons.notifications.model.Feature import org.opensearch.commons.utils.logger import java.io.IOException @@ -48,7 +47,7 @@ import java.io.IOException * Hence the request also contains tenant info for space isolation. */ class GetFeatureChannelListRequest : ActionRequest, ToXContentObject { - val feature: Feature + val feature: String companion object { private val log by logger(GetFeatureChannelListRequest::class.java) @@ -65,7 +64,7 @@ class GetFeatureChannelListRequest : ActionRequest, ToXContentObject { @JvmStatic @Throws(IOException::class) fun parse(parser: XContentParser): GetFeatureChannelListRequest { - var feature: Feature? = null + var feature: String? = null XContentParserUtils.ensureExpectedToken( XContentParser.Token.START_OBJECT, @@ -76,7 +75,7 @@ class GetFeatureChannelListRequest : ActionRequest, ToXContentObject { val fieldName = parser.currentName() parser.nextToken() when (fieldName) { - FEATURE_TAG -> feature = Feature.fromTagOrDefault(parser.text()) + FEATURE_TAG -> feature = parser.text() else -> { parser.skipChildren() log.info("Unexpected field: $fieldName, while parsing GetFeatureChannelListRequest") @@ -92,7 +91,7 @@ class GetFeatureChannelListRequest : ActionRequest, ToXContentObject { * constructor for creating the class * @param feature the caller plugin feature */ - constructor(feature: Feature) { + constructor(feature: String) { this.feature = feature } @@ -101,7 +100,7 @@ class GetFeatureChannelListRequest : ActionRequest, ToXContentObject { */ @Throws(IOException::class) constructor(input: StreamInput) : super(input) { - feature = input.readEnum(Feature::class.java) + feature = input.readString() } /** @@ -110,7 +109,7 @@ class GetFeatureChannelListRequest : ActionRequest, ToXContentObject { @Throws(IOException::class) override fun writeTo(output: StreamOutput) { super.writeTo(output) - output.writeEnum(feature) + output.writeString(feature) } /** diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequest.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequest.kt new file mode 100644 index 00000000..e5807102 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequest.kt @@ -0,0 +1,82 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.commons.notifications.action + +import org.opensearch.action.ActionRequest +import org.opensearch.action.ActionRequestValidationException +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.io.stream.Writeable +import org.opensearch.commons.destination.message.LegacyBaseMessage +import org.opensearch.commons.destination.message.LegacyChimeMessage +import org.opensearch.commons.destination.message.LegacyCustomWebhookMessage +import org.opensearch.commons.destination.message.LegacyDestinationType +import org.opensearch.commons.destination.message.LegacySlackMessage +import java.io.IOException + +/** + * Action Request to publish notification. This is a legacy implementation. + * This should not be used going forward, instead use [SendNotificationRequest]. + */ +class LegacyPublishNotificationRequest : ActionRequest { + val baseMessage: LegacyBaseMessage + val feature: String + + companion object { + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { LegacyPublishNotificationRequest(it) } + } + + /** + * constructor for creating the class + * @param baseMessage the base message to send + * @param feature the feature that is trying to use this request + */ + constructor( + baseMessage: LegacyBaseMessage, + feature: String + ) { + this.baseMessage = baseMessage + this.feature = feature + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + constructor(input: StreamInput) : super(input) { + baseMessage = when (requireNotNull(input.readEnum(LegacyDestinationType::class.java)) { "Destination type cannot be null" }) { + LegacyDestinationType.LEGACY_CHIME -> LegacyChimeMessage(input) + LegacyDestinationType.LEGACY_CUSTOM_WEBHOOK -> LegacyCustomWebhookMessage(input) + LegacyDestinationType.LEGACY_SLACK -> LegacySlackMessage(input) + } + feature = input.readString() + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + override fun writeTo(output: StreamOutput) { + super.writeTo(output) + output.writeEnum(baseMessage.channelType) + baseMessage.writeTo(output) + output.writeString(feature) + } + + /** + * {@inheritDoc} + */ + override fun validate(): ActionRequestValidationException? = null +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationResponse.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationResponse.kt new file mode 100644 index 00000000..60f68e71 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationResponse.kt @@ -0,0 +1,63 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.commons.notifications.action + +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.io.stream.Writeable +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.commons.destination.response.LegacyDestinationResponse +import java.io.IOException + +/** + * Action Response for legacy publish notification. + */ +class LegacyPublishNotificationResponse : BaseResponse { + val destinationResponse: LegacyDestinationResponse + + companion object { + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { LegacyPublishNotificationResponse(it) } + } + + /** + * constructor for creating the class + * @param destinationResponse the response of the published notification + */ + constructor(destinationResponse: LegacyDestinationResponse) { + this.destinationResponse = destinationResponse + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + constructor(input: StreamInput) : super(input) { + destinationResponse = LegacyDestinationResponse(input) + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + override fun writeTo(output: StreamOutput) { + destinationResponse.writeTo(output) + } + + // This class is only used across transport wire and does not need to implement toXContent + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + throw IllegalStateException("Legacy notification response is not intended for REST or persistence and does not support XContent.") + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/NotificationsActions.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/NotificationsActions.kt index fe7457d0..d93dd29a 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/NotificationsActions.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/NotificationsActions.kt @@ -72,6 +72,12 @@ object NotificationsActions { */ const val SEND_NOTIFICATION_NAME = "cluster:admin/opensearch/notifications/feature/send" + /** + * Publish legacy notification message. Internal only - Inter plugin communication. + * Only for the Index Management plugin. + */ + const val LEGACY_PUBLISH_NOTIFICATION_NAME = "cluster:admin/opensearch/notifications/feature/publish" + /** * Create notification configuration transport action type. */ @@ -119,4 +125,11 @@ object NotificationsActions { */ val SEND_NOTIFICATION_ACTION_TYPE = ActionType(SEND_NOTIFICATION_NAME, ::SendNotificationResponse) + + /** + * Send legacy notification transport action type. Internal only - Inter plugin communication. + * Only for the Index Management plugin. + */ + val LEGACY_PUBLISH_NOTIFICATION_ACTION_TYPE = + ActionType(LEGACY_PUBLISH_NOTIFICATION_NAME, ::LegacyPublishNotificationResponse) } diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/ConfigType.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/ConfigType.kt index d5400a75..85c10e35 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/ConfigType.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/ConfigType.kt @@ -62,6 +62,11 @@ enum class ConfigType(val tag: String) { return tag } }, + SES_ACCOUNT("ses_account") { + override fun toString(): String { + return tag + } + }, SMTP_ACCOUNT("smtp_account") { override fun toString(): String { return tag diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/EventSource.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/EventSource.kt index caf86dcd..98c46d2a 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/EventSource.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/EventSource.kt @@ -49,7 +49,7 @@ import java.io.IOException data class EventSource( val title: String, val referenceId: String, - val feature: Feature, + val feature: String, val severity: SeverityType = SeverityType.INFO, val tags: List = listOf() ) : BaseModel { @@ -75,7 +75,7 @@ data class EventSource( fun parse(parser: XContentParser): EventSource { var title: String? = null var referenceId: String? = null - var feature: Feature? = null + var feature: String? = null var severity: SeverityType = SeverityType.INFO var tags: List = emptyList() @@ -90,7 +90,7 @@ data class EventSource( when (fieldName) { TITLE_TAG -> title = parser.text() REFERENCE_ID_TAG -> referenceId = parser.text() - FEATURE_TAG -> feature = Feature.fromTagOrDefault(parser.text()) + FEATURE_TAG -> feature = parser.text() SEVERITY_TAG -> severity = SeverityType.fromTagOrDefault(parser.text()) TAGS_TAG -> tags = parser.stringList() else -> { @@ -121,7 +121,7 @@ data class EventSource( return builder.startObject() .field(TITLE_TAG, title) .field(REFERENCE_ID_TAG, referenceId) - .field(FEATURE_TAG, feature.tag) + .field(FEATURE_TAG, feature) .field(SEVERITY_TAG, severity.tag) .field(TAGS_TAG, tags) .endObject() @@ -134,7 +134,7 @@ data class EventSource( constructor(input: StreamInput) : this( title = input.readString(), referenceId = input.readString(), - feature = input.readEnum(Feature::class.java), + feature = input.readString(), severity = input.readEnum(SeverityType::class.java), tags = input.readStringList() ) @@ -145,7 +145,7 @@ data class EventSource( override fun writeTo(output: StreamOutput) { output.writeString(title) output.writeString(referenceId) - output.writeEnum(feature) + output.writeString(feature) output.writeEnum(severity) output.writeStringCollection(tags) } diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/Feature.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Feature.kt deleted file mode 100644 index 84b37139..00000000 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/Feature.kt +++ /dev/null @@ -1,71 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * - */ - -package org.opensearch.commons.notifications.model - -import org.opensearch.commons.utils.EnumParser - -/** - * Features using notification plugin - */ -enum class Feature(val tag: String) { - NONE("none") { - override fun toString(): String { - return tag - } - }, - ALERTING("alerting") { - override fun toString(): String { - return tag - } - }, - INDEX_MANAGEMENT("index_management") { - override fun toString(): String { - return tag - } - }, - REPORTS("reports") { - override fun toString(): String { - return tag - } - }; - - companion object { - private val tagMap = values().associateBy { it.tag } - - val enumParser = EnumParser { fromTagOrDefault(it) } - - /** - * Get Feature from tag or NONE if not found - * @param tag the tag - * @return Feature corresponding to tag. NONE if invalid tag. - */ - fun fromTagOrDefault(tag: String): Feature { - return tagMap[tag] ?: NONE - } - } -} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfig.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfig.kt index 30d72147..a0f7f5ad 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfig.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfig.kt @@ -42,11 +42,12 @@ import org.opensearch.commons.notifications.NotificationConstants.NAME_TAG import org.opensearch.commons.notifications.model.config.ConfigDataProperties.createConfigData import org.opensearch.commons.notifications.model.config.ConfigDataProperties.getReaderForConfigType import org.opensearch.commons.notifications.model.config.ConfigDataProperties.validateConfigData -import org.opensearch.commons.utils.enumSet +import org.opensearch.commons.utils.STRING_READER +import org.opensearch.commons.utils.STRING_WRITER import org.opensearch.commons.utils.fieldIfNotNull import org.opensearch.commons.utils.logger +import org.opensearch.commons.utils.stringList import java.io.IOException -import java.util.EnumSet /** * Data class representing Notification config. @@ -55,7 +56,7 @@ data class NotificationConfig( val name: String, val description: String, val configType: ConfigType, - val features: EnumSet, + val features: Set, val configData: BaseConfigData?, val isEnabled: Boolean = true ) : BaseModel { @@ -89,7 +90,7 @@ data class NotificationConfig( var name: String? = null var description = "" var configType: ConfigType? = null - var features: EnumSet? = null + var features: Set? = null var isEnabled = true var configData: BaseConfigData? = null XContentParserUtils.ensureExpectedToken( @@ -104,7 +105,7 @@ data class NotificationConfig( NAME_TAG -> name = parser.text() DESCRIPTION_TAG -> description = parser.text() CONFIG_TYPE_TAG -> configType = ConfigType.fromTagOrDefault(parser.text()) - FEATURE_LIST_TAG -> features = parser.enumSet(Feature.enumParser) + FEATURE_LIST_TAG -> features = parser.stringList().toSet() IS_ENABLED_TAG -> isEnabled = parser.booleanValue() else -> { val configTypeForTag = ConfigType.fromTagOrDefault(fieldName) @@ -154,7 +155,7 @@ data class NotificationConfig( name = input.readString(), description = input.readString(), configType = input.readEnum(ConfigType::class.java), - features = input.readEnumSet(Feature::class.java), + features = input.readSet(STRING_READER), isEnabled = input.readBoolean(), configData = input.readOptionalWriteable(getReaderForConfigType(input.readEnum(ConfigType::class.java))) ) @@ -166,7 +167,7 @@ data class NotificationConfig( output.writeString(name) output.writeString(description) output.writeEnum(configType) - output.writeEnumSet(features) + output.writeCollection(features, STRING_WRITER) output.writeBoolean(isEnabled) // Reading config types multiple times in constructor output.writeEnum(configType) diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/SesAccount.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/SesAccount.kt new file mode 100644 index 00000000..d529b407 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/SesAccount.kt @@ -0,0 +1,124 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.commons.notifications.model + +import org.opensearch.common.Strings +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.io.stream.Writeable +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.notifications.NotificationConstants.FROM_ADDRESS_TAG +import org.opensearch.commons.notifications.NotificationConstants.REGION_TAG +import org.opensearch.commons.notifications.NotificationConstants.ROLE_ARN_TAG +import org.opensearch.commons.utils.logger +import org.opensearch.commons.utils.validateEmail +import org.opensearch.commons.utils.validateIamRoleArn +import java.io.IOException + +/** + * Data class representing SES account channel. + */ +data class SesAccount( + val awsRegion: String, + val roleArn: String?, + val fromAddress: String +) : BaseConfigData { + + init { + require(!Strings.isNullOrEmpty(awsRegion)) { "awsRegion is null or empty" } + validateEmail(fromAddress) + if (roleArn != null) { + validateIamRoleArn(roleArn) + } + } + + companion object { + private val log by logger(SesAccount::class.java) + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { SesAccount(it) } + + /** + * Parser to parse xContent + */ + val xParser = XParser { parse(it) } + + @JvmStatic + @Throws(IOException::class) + fun parse(parser: XContentParser): SesAccount { + var awsRegion: String? = null + var roleArn: String? = null + var fromAddress: String? = null + + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_OBJECT, + parser.currentToken(), + parser + ) + while (parser.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + REGION_TAG -> awsRegion = parser.text() + ROLE_ARN_TAG -> roleArn = parser.text() + FROM_ADDRESS_TAG -> fromAddress = parser.text() + else -> { + parser.skipChildren() + log.info("Unexpected field: $fieldName, while parsing SesAccount") + } + } + } + awsRegion ?: throw IllegalArgumentException("$REGION_TAG field absent") + fromAddress ?: throw IllegalArgumentException("$FROM_ADDRESS_TAG field absent") + return SesAccount( + awsRegion, + roleArn, + fromAddress + ) + } + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + return builder!!.startObject() + .field(REGION_TAG, awsRegion) + .field(ROLE_ARN_TAG, roleArn) + .field(FROM_ADDRESS_TAG, fromAddress) + .endObject() + } + + /** + * Constructor used in transport action communication. + * @param input StreamInput stream to deserialize data from. + */ + constructor(input: StreamInput) : this( + awsRegion = input.readString(), + roleArn = input.readOptionalString(), + fromAddress = input.readString() + ) + + /** + * {@inheritDoc} + */ + override fun writeTo(out: StreamOutput) { + out.writeString(awsRegion) + out.writeOptionalString(roleArn) + out.writeString(fromAddress) + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/SNS.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Sns.kt similarity index 63% rename from src/main/kotlin/org/opensearch/commons/notifications/model/SNS.kt rename to src/main/kotlin/org/opensearch/commons/notifications/model/Sns.kt index 12cdba4f..5e723926 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/SNS.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Sns.kt @@ -17,30 +17,30 @@ import org.opensearch.common.xcontent.ToXContent import org.opensearch.common.xcontent.XContentBuilder import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils -import org.opensearch.commons.notifications.NotificationConstants.ROLE_ARN_FIELD -import org.opensearch.commons.notifications.NotificationConstants.TOPIC_ARN_FIELD +import org.opensearch.commons.notifications.NotificationConstants.ROLE_ARN_TAG +import org.opensearch.commons.notifications.NotificationConstants.TOPIC_ARN_TAG import org.opensearch.commons.utils.fieldIfNotNull import org.opensearch.commons.utils.logger -import org.opensearch.commons.utils.validateIAMRoleArn +import org.opensearch.commons.utils.validateIamRoleArn import java.io.IOException import java.util.regex.Pattern /** * SNS notification data model */ -data class SNS(val topicARN: String, val roleARN: String?) : BaseConfigData { +data class Sns(val topicArn: String, val roleArn: String?) : BaseConfigData { init { - require(SNS_ARN_REGEX.matcher(topicARN).find()) { "Invalid AWS SNS topic ARN: $topicARN" } - if (roleARN != null) { - validateIAMRoleArn(roleARN) + require(SNS_ARN_REGEX.matcher(topicArn).find()) { "Invalid AWS SNS topic ARN: $topicArn" } + if (roleArn != null) { + validateIamRoleArn(roleArn) } } override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { return builder.startObject() - .field(TOPIC_ARN_FIELD, topicARN) - .fieldIfNotNull(ROLE_ARN_FIELD, roleARN) + .field(TOPIC_ARN_TAG, topicArn) + .fieldIfNotNull(ROLE_ARN_TAG, roleArn) .endObject() } @@ -49,18 +49,18 @@ data class SNS(val topicARN: String, val roleARN: String?) : BaseConfigData { * @param input StreamInput stream to deserialize data from. */ constructor(input: StreamInput) : this( - topicARN = input.readString(), - roleARN = input.readOptionalString() + topicArn = input.readString(), + roleArn = input.readOptionalString() ) @Throws(IOException::class) override fun writeTo(out: StreamOutput) { - out.writeString(topicARN) - out.writeOptionalString(roleARN) + out.writeString(topicArn) + out.writeOptionalString(roleArn) } companion object { - private val log by logger(SNS::class.java) + private val log by logger(Sns::class.java) private val SNS_ARN_REGEX = Pattern.compile("^arn:aws(-[^:]+)?:sns:([a-zA-Z0-9-]+):([0-9]{12}):([a-zA-Z0-9-_]+)$") @@ -68,7 +68,7 @@ data class SNS(val topicARN: String, val roleARN: String?) : BaseConfigData { /** * reader to create instance of class from writable. */ - val reader = Writeable.Reader { SNS(it) } + val reader = Writeable.Reader { Sns(it) } /** * Parser to parse xContent @@ -77,36 +77,25 @@ data class SNS(val topicARN: String, val roleARN: String?) : BaseConfigData { @JvmStatic @Throws(IOException::class) - fun parse(xcp: XContentParser): SNS { - var topicARN: String? = null - var roleARN: String? = null + fun parse(xcp: XContentParser): Sns { + var topicArn: String? = null + var roleArn: String? = null XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { val fieldName = xcp.currentName() xcp.nextToken() when (fieldName) { - TOPIC_ARN_FIELD -> topicARN = xcp.textOrNull() - ROLE_ARN_FIELD -> roleARN = xcp.textOrNull() + TOPIC_ARN_TAG -> topicArn = xcp.textOrNull() + ROLE_ARN_TAG -> roleArn = xcp.textOrNull() else -> { xcp.skipChildren() log.info("Unexpected field: $fieldName, while parsing SNS destination") } } } - topicARN ?: throw IllegalArgumentException("$TOPIC_ARN_FIELD field absent") - return SNS(topicARN, roleARN) - } - - @JvmStatic - @Throws(IOException::class) - fun readFrom(sin: StreamInput): SNS? { - return if (sin.readBoolean()) { - SNS( - topicARN = sin.readString(), - roleARN = sin.readOptionalString() - ) - } else null + topicArn ?: throw IllegalArgumentException("$TOPIC_ARN_TAG field absent") + return Sns(topicArn, roleArn) } } } diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/config/ConfigDataProperties.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/config/ConfigDataProperties.kt index 34bd2edc..e6844934 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/config/ConfigDataProperties.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/config/ConfigDataProperties.kt @@ -33,9 +33,10 @@ import org.opensearch.commons.notifications.model.Chime import org.opensearch.commons.notifications.model.ConfigType import org.opensearch.commons.notifications.model.Email import org.opensearch.commons.notifications.model.EmailGroup -import org.opensearch.commons.notifications.model.SNS +import org.opensearch.commons.notifications.model.SesAccount import org.opensearch.commons.notifications.model.Slack import org.opensearch.commons.notifications.model.SmtpAccount +import org.opensearch.commons.notifications.model.Sns import org.opensearch.commons.notifications.model.Webhook import org.opensearch.commons.notifications.model.XParser @@ -54,7 +55,8 @@ internal object ConfigDataProperties { Pair(ConfigType.CHIME, ConfigProperty(Chime.reader, Chime.xParser)), Pair(ConfigType.WEBHOOK, ConfigProperty(Webhook.reader, Webhook.xParser)), Pair(ConfigType.EMAIL, ConfigProperty(Email.reader, Email.xParser)), - Pair(ConfigType.SNS, ConfigProperty(SNS.reader, SNS.xParser)), + Pair(ConfigType.SNS, ConfigProperty(Sns.reader, Sns.xParser)), + Pair(ConfigType.SES_ACCOUNT, ConfigProperty(SesAccount.reader, SesAccount.xParser)), Pair(ConfigType.EMAIL_GROUP, ConfigProperty(EmailGroup.reader, EmailGroup.xParser)), Pair(ConfigType.SMTP_ACCOUNT, ConfigProperty(SmtpAccount.reader, SmtpAccount.xParser)) ) @@ -80,7 +82,8 @@ internal object ConfigDataProperties { ConfigType.EMAIL_GROUP -> configData is EmailGroup ConfigType.SMTP_ACCOUNT -> configData is SmtpAccount ConfigType.CHIME -> configData is Chime - ConfigType.SNS -> configData is SNS + ConfigType.SNS -> configData is Sns + ConfigType.SES_ACCOUNT -> configData is SesAccount ConfigType.NONE -> true } } diff --git a/src/main/kotlin/org/opensearch/commons/utils/ValidationHelpers.kt b/src/main/kotlin/org/opensearch/commons/utils/ValidationHelpers.kt index fc117923..9f9082c4 100644 --- a/src/main/kotlin/org/opensearch/commons/utils/ValidationHelpers.kt +++ b/src/main/kotlin/org/opensearch/commons/utils/ValidationHelpers.kt @@ -71,7 +71,7 @@ fun isValidId(idString: String): Boolean { return idString.isNotBlank() && idString.all { VALID_ID_CHARS.contains(it) } } -fun validateIAMRoleArn(roleARN: String) { +fun validateIamRoleArn(roleArn: String) { val roleArnRegex = Pattern.compile("^arn:aws(-[^:]+)?:iam::([0-9]{12}):([a-zA-Z_0-9+=,.@\\-_/]+)$") - require(roleArnRegex.matcher(roleARN).find()) { "Invalid AWS role ARN: $roleARN " } + require(roleArnRegex.matcher(roleArn).find()) { "Invalid AWS role ARN: $roleArn " } } diff --git a/src/test/java/org/opensearch/commons/destination/message/LegacyChimeMessageTest.java b/src/test/java/org/opensearch/commons/destination/message/LegacyChimeMessageTest.java new file mode 100644 index 00000000..4477789e --- /dev/null +++ b/src/test/java/org/opensearch/commons/destination/message/LegacyChimeMessageTest.java @@ -0,0 +1,100 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package org.opensearch.commons.destination.message; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; +import org.opensearch.common.io.stream.BytesStreamOutput; +import org.opensearch.common.io.stream.StreamInput; + +public class LegacyChimeMessageTest { + + @Test + public void testBuildingLegacyChimeMessage() { + LegacyChimeMessage message = new LegacyChimeMessage.Builder("custom_webhook") + .withMessage("Hello world") + .withUrl("https://amazon.com") + .build(); + + assertEquals("custom_webhook", message.destinationName); + assertEquals(LegacyDestinationType.LEGACY_CHIME, message.getChannelType()); + assertEquals("Hello world", message.getMessageContent()); + assertEquals("https://amazon.com", message.url); + } + + @Test + public void testRoundTrippingLegacyChimeMessage() throws IOException { + LegacyChimeMessage message = new LegacyChimeMessage.Builder("custom_webhook") + .withMessage("Hello world") + .withUrl("https://amazon.com") + .build(); + BytesStreamOutput out = new BytesStreamOutput(); + message.writeTo(out); + + StreamInput in = StreamInput.wrap(out.bytes().toBytesRef().bytes); + LegacyChimeMessage newMessage = new LegacyChimeMessage(in); + + assertEquals(newMessage.destinationName, message.destinationName); + assertEquals(newMessage.getChannelType(), message.getChannelType()); + assertEquals(newMessage.getMessageContent(), message.getMessageContent()); + assertEquals(newMessage.url, message.url); + } + + @Test + public void testContentMissingMessage() { + try { + new LegacyChimeMessage.Builder("custom_webhook").withUrl("https://amazon.com").build(); + fail("Building legacy chime message without message should fail"); + } catch (IllegalArgumentException e) { + assertEquals("Message content is missing", e.getMessage()); + } + } + + @Test + public void testUrlMissingMessage() { + try { + new LegacyChimeMessage.Builder("custom_webhook").withMessage("Hello world").build(); + fail("Building legacy chime message without url should fail"); + } catch (IllegalArgumentException e) { + assertEquals("url is invalid or empty", e.getMessage()); + } + } + + @Test + public void testMissingDestinationName() { + try { + new LegacyChimeMessage.Builder(null).withMessage("Hello world").withUrl("https://amazon.com").build(); + fail("Building legacy chime message with null destination name should fail"); + } catch (IllegalArgumentException e) { + assertEquals("Channel name must be defined", e.getMessage()); + } + } +} diff --git a/src/test/java/org/opensearch/commons/destination/message/LegacyCustomWebhookMessageTest.java b/src/test/java/org/opensearch/commons/destination/message/LegacyCustomWebhookMessageTest.java new file mode 100644 index 00000000..fba614fd --- /dev/null +++ b/src/test/java/org/opensearch/commons/destination/message/LegacyCustomWebhookMessageTest.java @@ -0,0 +1,177 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package org.opensearch.commons.destination.message; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.junit.jupiter.api.Test; +import org.opensearch.common.io.stream.BytesStreamOutput; +import org.opensearch.common.io.stream.StreamInput; + +public class LegacyCustomWebhookMessageTest { + + @Test + public void testBuildingLegacyCustomWebhookMessage() { + LegacyCustomWebhookMessage message = new LegacyCustomWebhookMessage.Builder("custom_webhook") + .withMessage("Hello world") + .withUrl("https://amazon.com") + .build(); + + assertEquals("custom_webhook", message.destinationName); + assertEquals(LegacyDestinationType.LEGACY_CUSTOM_WEBHOOK, message.getChannelType()); + assertEquals("Hello world", message.getMessageContent()); + assertEquals("https://amazon.com", message.getUrl()); + } + + @Test + public void testRoundTrippingLegacyCustomWebhookMessageWithUrl() throws IOException { + LegacyCustomWebhookMessage message = new LegacyCustomWebhookMessage.Builder("custom_webhook") + .withMessage("Hello world") + .withUrl("https://amazon.com") + .build(); + BytesStreamOutput out = new BytesStreamOutput(); + message.writeTo(out); + + StreamInput in = StreamInput.wrap(out.bytes().toBytesRef().bytes); + LegacyCustomWebhookMessage newMessage = new LegacyCustomWebhookMessage(in); + + assertEquals(newMessage.destinationName, message.destinationName); + assertEquals(newMessage.getChannelType(), message.getChannelType()); + assertEquals(newMessage.getMessageContent(), message.getMessageContent()); + assertEquals(newMessage.getUrl(), message.getUrl()); + } + + @Test + public void testRoundTrippingLegacyCustomWebhookMessageWithHostFails() throws IOException { + Map queryParams = new HashMap(); + queryParams.put("token", "sometoken"); + Map headers = new HashMap(); + headers.put("x-token", "sometoken"); + LegacyCustomWebhookMessage message = new LegacyCustomWebhookMessage.Builder("custom_webhook") + .withMessage("Hello world") + .withHost("hooks.chime.aws") + .withPath("incomingwebhooks/abc") + .withMethod(HttpPost.METHOD_NAME) + .withQueryParams(queryParams) + .withHeaderParams(headers) + .withPort(8000) + .withScheme("https") + .build(); + BytesStreamOutput out = new BytesStreamOutput(); + try { + message.writeTo(out); + fail("Writing LegacyCustomWebhookMessage with host instead of url to stream output should fail"); + } catch (IllegalStateException e) { + assertEquals("Cannot use LegacyCustomWebhookMessage across transport wire without defining full url.", e.getMessage()); + } + } + + @Test + public void testContentMissingMessage() { + try { + new LegacyCustomWebhookMessage.Builder("custom_webhook").withUrl("https://amazon.com").build(); + fail("Building legacy custom webhook message without message should fail"); + } catch (IllegalArgumentException e) { + assertEquals("Message content is missing", e.getMessage()); + } + } + + @Test + public void testMissingDestinationName() { + try { + new LegacyCustomWebhookMessage.Builder(null).withMessage("Hello world").withUrl("https://amazon.com").build(); + fail("Building legacy custom webhook message with null destination name should fail"); + } catch (IllegalArgumentException e) { + assertEquals("Channel name must be defined", e.getMessage()); + } + } + + @Test + public void testUnsupportedHttpMethods() { + try { + new LegacyCustomWebhookMessage.Builder("custom_webhook") + .withMessage("Hello world") + .withUrl("https://amazon.com") + .withMethod(HttpGet.METHOD_NAME) + .build(); + fail("Building legacy custom webhook message with unsupported http methods should fail"); + } catch (IllegalArgumentException e) { + assertEquals("Invalid method supplied. Only POST, PUT and PATCH are allowed", e.getMessage()); + } + } + + @Test + public void testURLandHostNameMissingOrEmpty() { + try { + new LegacyCustomWebhookMessage.Builder("custom_webhook").withMessage("Hello world").withMethod(HttpGet.METHOD_NAME).build(); + fail("Building legacy custom webhook message missing or empty url and host name should fail"); + } catch (IllegalArgumentException e) { + assertEquals("Either fully qualified URL or host name should be provided", e.getMessage()); + } + + try { + new LegacyCustomWebhookMessage.Builder("custom_webhook") + .withMessage("Hello world") + .withUrl("") + .withMethod(HttpGet.METHOD_NAME) + .build(); + fail("Building legacy custom webhook message with missing or empty url and host name should fail"); + } catch (IllegalArgumentException e) { + assertEquals("Either fully qualified URL or host name should be provided", e.getMessage()); + } + + try { + new LegacyCustomWebhookMessage.Builder("custom_webhook") + .withMessage("Hello world") + .withHost("") + .withMethod(HttpGet.METHOD_NAME) + .build(); + fail("Building legacy custom webhook message with missing or empty url and host name should fail"); + } catch (IllegalArgumentException e) { + assertEquals("Either fully qualified URL or host name should be provided", e.getMessage()); + } + + try { + new LegacyCustomWebhookMessage.Builder("custom_webhook") + .withMessage("Hello world") + .withUrl("") + .withHost("") + .withMethod(HttpGet.METHOD_NAME) + .build(); + fail("Building legacy custom webhook message with missing or empty url and host name should fail"); + } catch (IllegalArgumentException e) { + assertEquals("Either fully qualified URL or host name should be provided", e.getMessage()); + } + } +} diff --git a/src/test/java/org/opensearch/commons/destination/message/LegacySlackMessageTest.java b/src/test/java/org/opensearch/commons/destination/message/LegacySlackMessageTest.java new file mode 100644 index 00000000..a520fc8e --- /dev/null +++ b/src/test/java/org/opensearch/commons/destination/message/LegacySlackMessageTest.java @@ -0,0 +1,110 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package org.opensearch.commons.destination.message; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; +import org.opensearch.common.io.stream.BytesStreamOutput; +import org.opensearch.common.io.stream.StreamInput; + +public class LegacySlackMessageTest { + + @Test + public void testBuildingLegacySlackMessage() { + LegacySlackMessage message = new LegacySlackMessage.Builder("custom_webhook") + .withMessage("Hello world") + .withUrl("https://amazon.com") + .build(); + + assertEquals("custom_webhook", message.destinationName); + assertEquals(LegacyDestinationType.LEGACY_SLACK, message.getChannelType()); + assertEquals("Hello world", message.getMessageContent()); + assertEquals("https://amazon.com", message.url); + } + + @Test + public void testRoundTrippingLegacySlackMessage() throws IOException { + LegacySlackMessage message = new LegacySlackMessage.Builder("custom_webhook") + .withMessage("Hello world") + .withUrl("https://amazon.com") + .build(); + BytesStreamOutput out = new BytesStreamOutput(); + message.writeTo(out); + + StreamInput in = StreamInput.wrap(out.bytes().toBytesRef().bytes); + LegacySlackMessage newMessage = new LegacySlackMessage(in); + + assertEquals(newMessage.destinationName, message.destinationName); + assertEquals(newMessage.getChannelType(), message.getChannelType()); + assertEquals(newMessage.getMessageContent(), message.getMessageContent()); + assertEquals(newMessage.url, message.url); + } + + @Test + public void testContentMissingMessage() { + try { + new LegacySlackMessage.Builder("custom_webhook").withUrl("https://amazon.com").build(); + fail("Building legacy slack message without message should fail"); + } catch (IllegalArgumentException e) { + assertEquals("Message content is missing", e.getMessage()); + } + } + + @Test + public void testUrlMissingMessage() { + try { + new LegacySlackMessage.Builder("custom_webhook").withMessage("Hello world").build(); + fail("Building legacy slack message without url should fail"); + } catch (IllegalArgumentException e) { + assertEquals("url is invalid or empty", e.getMessage()); + } + } + + @Test + public void testMissingDestinationName() { + try { + new LegacySlackMessage.Builder(null).withMessage("Hello world").withUrl("https://amazon.com").build(); + fail("Building legacy slack message with null destination name should fail"); + } catch (IllegalArgumentException e) { + assertEquals("Channel name must be defined", e.getMessage()); + } + } + + @Test + public void testUrlEmptyMessage() { + try { + new LegacySlackMessage.Builder("custom_webhook").withMessage("Hello world").withUrl("").build(); + fail("Building legacy slack message with empty url should fail"); + } catch (IllegalArgumentException e) { + assertEquals("Fully qualified URL is missing/invalid: ", e.getMessage()); + } + } +} diff --git a/src/test/java/org/opensearch/commons/destination/response/LegacyDestinationResponseTest.java b/src/test/java/org/opensearch/commons/destination/response/LegacyDestinationResponseTest.java new file mode 100644 index 00000000..6175827e --- /dev/null +++ b/src/test/java/org/opensearch/commons/destination/response/LegacyDestinationResponseTest.java @@ -0,0 +1,82 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package org.opensearch.commons.destination.response; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; +import org.opensearch.common.io.stream.BytesStreamOutput; +import org.opensearch.common.io.stream.StreamInput; + +public class LegacyDestinationResponseTest { + + @Test + public void testBuildingLegacyDestinationResponse() { + LegacyDestinationResponse res = new LegacyDestinationResponse.Builder() + .withStatusCode(200) + .withResponseContent("Hello world") + .build(); + + assertEquals(200, res.statusCode); + assertEquals("Hello world", res.getResponseContent()); + } + + @Test + public void testRoundTrippingLegacyDestinationResponse() throws IOException { + LegacyDestinationResponse res = new LegacyDestinationResponse.Builder() + .withStatusCode(200) + .withResponseContent("Hello world") + .build(); + BytesStreamOutput out = new BytesStreamOutput(); + res.writeTo(out); + + StreamInput in = StreamInput.wrap(out.bytes().toBytesRef().bytes); + LegacyDestinationResponse newRes = new LegacyDestinationResponse(in); + + assertEquals(res.statusCode, newRes.statusCode, "Round tripping doesn't work"); + assertEquals(res.getResponseContent(), newRes.getResponseContent(), "Round tripping doesn't work"); + } + + @Test + public void testMissingLegacyDestinationResponse() { + try { + new LegacyDestinationResponse.Builder().withStatusCode(200).build(); + fail("Creating LegacyDestinationResponse without response content should fail"); + } catch (IllegalArgumentException ignored) {} + } + + @Test + public void testMissingLegacyDestinationStatusCode() { + try { + new LegacyDestinationResponse.Builder().withResponseContent("Hello world").build(); + fail("Creating LegacyDestinationResponse without status code should fail"); + } catch (IllegalArgumentException ignored) {} + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt index 99f558f4..343fecbd 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterfaceTests.kt @@ -25,6 +25,10 @@ import org.mockito.junit.jupiter.MockitoExtension import org.opensearch.action.ActionListener import org.opensearch.action.ActionType import org.opensearch.client.node.NodeClient +import org.opensearch.commons.destination.response.LegacyDestinationResponse +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_ALERTING +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_INDEX_MANAGEMENT +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_REPORTS import org.opensearch.commons.notifications.action.CreateNotificationConfigRequest import org.opensearch.commons.notifications.action.CreateNotificationConfigResponse import org.opensearch.commons.notifications.action.DeleteNotificationConfigRequest @@ -37,6 +41,8 @@ import org.opensearch.commons.notifications.action.GetNotificationEventRequest import org.opensearch.commons.notifications.action.GetNotificationEventResponse import org.opensearch.commons.notifications.action.GetPluginFeaturesRequest import org.opensearch.commons.notifications.action.GetPluginFeaturesResponse +import org.opensearch.commons.notifications.action.LegacyPublishNotificationRequest +import org.opensearch.commons.notifications.action.LegacyPublishNotificationResponse import org.opensearch.commons.notifications.action.SendNotificationResponse import org.opensearch.commons.notifications.action.UpdateNotificationConfigRequest import org.opensearch.commons.notifications.action.UpdateNotificationConfigResponse @@ -45,7 +51,6 @@ import org.opensearch.commons.notifications.model.ConfigType import org.opensearch.commons.notifications.model.DeliveryStatus import org.opensearch.commons.notifications.model.EventSource import org.opensearch.commons.notifications.model.EventStatus -import org.opensearch.commons.notifications.model.Feature import org.opensearch.commons.notifications.model.FeatureChannel import org.opensearch.commons.notifications.model.FeatureChannelList import org.opensearch.commons.notifications.model.NotificationConfig @@ -58,7 +63,6 @@ import org.opensearch.commons.notifications.model.SeverityType import org.opensearch.commons.notifications.model.Slack import org.opensearch.rest.RestStatus import java.time.Instant -import java.util.EnumSet @Suppress("UNCHECKED_CAST") @ExtendWith(MockitoExtension::class) @@ -198,7 +202,7 @@ internal class NotificationsPluginInterfaceTests { val notificationInfo = EventSource( "title", "reference_id", - Feature.REPORTS, + FEATURE_REPORTS, SeverityType.HIGH, listOf("tag1", "tag2") ) @@ -223,13 +227,33 @@ internal class NotificationsPluginInterfaceTests { verify(listener, times(1)).onResponse(eq(response)) } + @Test + fun publishLegacyNotification() { + val request = mock(LegacyPublishNotificationRequest::class.java) + val res = LegacyPublishNotificationResponse(LegacyDestinationResponse.Builder().withStatusCode(200).withResponseContent("Nice!").build()) + val l: ActionListener = + mock(ActionListener::class.java) as ActionListener + + doAnswer { + (it.getArgument(2) as ActionListener) + .onResponse(res) + }.whenever(client).execute(any(ActionType::class.java), any(), any()) + + doAnswer { + FEATURE_INDEX_MANAGEMENT + }.whenever(request).feature + + NotificationsPluginInterface.publishLegacyNotification(client, request, l) + verify(l, times(1)).onResponse(eq(res)) + } + private fun mockGetNotificationConfigResponse(): GetNotificationConfigResponse { val sampleSlack = Slack("https://domain.com/sample_url#1234567890") val sampleConfig = NotificationConfig( "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.REPORTS), + setOf(FEATURE_REPORTS), configData = sampleSlack ) val configInfo = NotificationConfigInfo( @@ -246,7 +270,7 @@ internal class NotificationsPluginInterfaceTests { val sampleEventSource = EventSource( "title", "reference_id", - Feature.ALERTING, + FEATURE_ALERTING, severity = SeverityType.INFO ) val sampleStatus = EventStatus( diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequestTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequestTests.kt index 679ded46..f190e7f6 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequestTests.kt @@ -31,11 +31,11 @@ import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_INDEX_MANAGEMENT import org.opensearch.commons.notifications.model.Chime import org.opensearch.commons.notifications.model.ConfigType import org.opensearch.commons.notifications.model.Email import org.opensearch.commons.notifications.model.EmailGroup -import org.opensearch.commons.notifications.model.Feature import org.opensearch.commons.notifications.model.MethodType import org.opensearch.commons.notifications.model.NotificationConfig import org.opensearch.commons.notifications.model.Slack @@ -44,7 +44,6 @@ import org.opensearch.commons.notifications.model.Webhook import org.opensearch.commons.utils.createObjectFromJsonString import org.opensearch.commons.utils.getJsonString import org.opensearch.commons.utils.recreateObject -import java.util.EnumSet internal class CreateNotificationConfigRequestTests { @@ -54,7 +53,7 @@ internal class CreateNotificationConfigRequestTests { "name", "description", ConfigType.WEBHOOK, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleWebhook ) @@ -66,7 +65,7 @@ internal class CreateNotificationConfigRequestTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleSlack ) @@ -78,7 +77,7 @@ internal class CreateNotificationConfigRequestTests { "name", "description", ConfigType.CHIME, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleChime ) @@ -90,7 +89,7 @@ internal class CreateNotificationConfigRequestTests { "name", "description", ConfigType.EMAIL_GROUP, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleEmailGroup ) @@ -106,7 +105,7 @@ internal class CreateNotificationConfigRequestTests { "name", "description", ConfigType.EMAIL, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleEmail ) @@ -123,7 +122,7 @@ internal class CreateNotificationConfigRequestTests { "name", "description", ConfigType.SMTP_ACCOUNT, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleSmtpAccount ) @@ -286,7 +285,7 @@ internal class CreateNotificationConfigRequestTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleSlack ) @@ -314,7 +313,7 @@ internal class CreateNotificationConfigRequestTests { "name", "description", ConfigType.WEBHOOK, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleWebhook ) @@ -342,7 +341,7 @@ internal class CreateNotificationConfigRequestTests { "name", "description", ConfigType.CHIME, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleChime ) @@ -371,7 +370,7 @@ internal class CreateNotificationConfigRequestTests { "name", "description", ConfigType.EMAIL_GROUP, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleEmailGroup ) @@ -404,7 +403,7 @@ internal class CreateNotificationConfigRequestTests { "name", "description", ConfigType.EMAIL, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleEmail ) @@ -439,7 +438,7 @@ internal class CreateNotificationConfigRequestTests { "name", "description", ConfigType.SMTP_ACCOUNT, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleSmtpAccount ) @@ -507,7 +506,7 @@ internal class CreateNotificationConfigRequestTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleSlack ) diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequestTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequestTests.kt index c9788a80..8c5c770f 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequestTests.kt @@ -30,7 +30,9 @@ import com.fasterxml.jackson.core.JsonParseException import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows -import org.opensearch.commons.notifications.model.Feature +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_ALERTING +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_INDEX_MANAGEMENT +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_REPORTS import org.opensearch.commons.utils.createObjectFromJsonString import org.opensearch.commons.utils.getJsonString import org.opensearch.commons.utils.recreateObject @@ -46,14 +48,14 @@ internal class GetFeatureChannelListRequestTests { @Test fun `Get request serialize and deserialize transport object should be equal`() { - val configRequest = GetFeatureChannelListRequest(Feature.REPORTS) + val configRequest = GetFeatureChannelListRequest(FEATURE_REPORTS) val recreatedObject = recreateObject(configRequest) { GetFeatureChannelListRequest(it) } assertGetRequestEquals(configRequest, recreatedObject) } @Test fun `Get request serialize and deserialize using json object should be equal`() { - val configRequest = GetFeatureChannelListRequest(Feature.INDEX_MANAGEMENT) + val configRequest = GetFeatureChannelListRequest(FEATURE_INDEX_MANAGEMENT) val jsonString = getJsonString(configRequest) val recreatedObject = createObjectFromJsonString(jsonString) { GetFeatureChannelListRequest.parse(it) } assertGetRequestEquals(configRequest, recreatedObject) @@ -69,7 +71,7 @@ internal class GetFeatureChannelListRequestTests { @Test fun `Get request should safely ignore extra field in json object`() { - val configRequest = GetFeatureChannelListRequest(Feature.ALERTING) + val configRequest = GetFeatureChannelListRequest(FEATURE_ALERTING) val jsonString = """ { "feature":"${configRequest.feature}", diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigResponseTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigResponseTests.kt index a2e14b40..2ec08913 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigResponseTests.kt @@ -30,9 +30,10 @@ import org.apache.lucene.search.TotalHits import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_INDEX_MANAGEMENT +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_REPORTS import org.opensearch.commons.notifications.model.Chime import org.opensearch.commons.notifications.model.ConfigType -import org.opensearch.commons.notifications.model.Feature import org.opensearch.commons.notifications.model.NotificationConfig import org.opensearch.commons.notifications.model.NotificationConfigInfo import org.opensearch.commons.notifications.model.NotificationConfigSearchResult @@ -41,7 +42,6 @@ import org.opensearch.commons.utils.createObjectFromJsonString import org.opensearch.commons.utils.getJsonString import org.opensearch.commons.utils.recreateObject import java.time.Instant -import java.util.EnumSet internal class GetNotificationConfigResponseTests { @@ -63,7 +63,7 @@ internal class GetNotificationConfigResponseTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.REPORTS), + setOf(FEATURE_REPORTS), configData = sampleSlack ) val configInfo = NotificationConfigInfo( @@ -85,7 +85,7 @@ internal class GetNotificationConfigResponseTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.REPORTS), + setOf(FEATURE_REPORTS), configData = Slack("https://domain.com/sample_url#1234567890") ) val configInfo1 = NotificationConfigInfo( @@ -99,7 +99,7 @@ internal class GetNotificationConfigResponseTests { "name", "description", ConfigType.CHIME, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), configData = Chime("https://domain.com/sample_url#1234567890") ) val configInfo2 = NotificationConfigInfo( @@ -129,7 +129,7 @@ internal class GetNotificationConfigResponseTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.REPORTS), + setOf(FEATURE_REPORTS), configData = sampleSlack ) val configInfo = NotificationConfigInfo( @@ -154,7 +154,7 @@ internal class GetNotificationConfigResponseTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.REPORTS), + setOf(FEATURE_REPORTS), configData = Slack("https://domain.com/sample_url#1234567890") ) val configInfo1 = NotificationConfigInfo( @@ -168,7 +168,7 @@ internal class GetNotificationConfigResponseTests { "name", "description", ConfigType.CHIME, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), configData = Chime("https://domain.com/sample_url#1234567890") ) val configInfo2 = NotificationConfigInfo( @@ -199,7 +199,7 @@ internal class GetNotificationConfigResponseTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleSlack ) @@ -250,7 +250,7 @@ internal class GetNotificationConfigResponseTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleSlack ) diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventResponseTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventResponseTests.kt index 4f7d72d1..1895d1d3 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventResponseTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventResponseTests.kt @@ -30,11 +30,12 @@ import org.apache.lucene.search.TotalHits import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_ALERTING +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_REPORTS import org.opensearch.commons.notifications.model.ConfigType import org.opensearch.commons.notifications.model.DeliveryStatus import org.opensearch.commons.notifications.model.EventSource import org.opensearch.commons.notifications.model.EventStatus -import org.opensearch.commons.notifications.model.Feature import org.opensearch.commons.notifications.model.NotificationEvent import org.opensearch.commons.notifications.model.NotificationEventInfo import org.opensearch.commons.notifications.model.NotificationEventSearchResult @@ -62,7 +63,7 @@ internal class GetNotificationEventResponseTests { val sampleEventSource = EventSource( "title", "reference_id", - Feature.ALERTING, + FEATURE_ALERTING, severity = SeverityType.INFO ) val sampleStatus = EventStatus( @@ -90,13 +91,13 @@ internal class GetNotificationEventResponseTests { val eventSource1 = EventSource( "title 1", "reference_id_1", - Feature.ALERTING, + FEATURE_ALERTING, severity = SeverityType.INFO ) val eventSource2 = EventSource( "title 2", "reference_id_2", - Feature.REPORTS, + FEATURE_REPORTS, severity = SeverityType.HIGH ) val status1 = EventStatus( @@ -157,7 +158,7 @@ internal class GetNotificationEventResponseTests { val sampleEventSource = EventSource( "title", "reference_id", - Feature.ALERTING, + FEATURE_ALERTING, severity = SeverityType.INFO ) val sampleStatus = EventStatus( @@ -188,13 +189,13 @@ internal class GetNotificationEventResponseTests { val eventSource1 = EventSource( "title 1", "reference_id_1", - Feature.ALERTING, + FEATURE_ALERTING, severity = SeverityType.INFO ) val eventSource2 = EventSource( "title 2", "reference_id_2", - Feature.REPORTS, + FEATURE_REPORTS, severity = SeverityType.HIGH ) val status1 = EventStatus( @@ -242,7 +243,7 @@ internal class GetNotificationEventResponseTests { val sampleEventSource = EventSource( "title", "reference_id", - Feature.ALERTING, + FEATURE_ALERTING, severity = SeverityType.INFO ) val sampleStatus = EventStatus( @@ -310,7 +311,7 @@ internal class GetNotificationEventResponseTests { val sampleEventSource = EventSource( "title", "reference_id", - Feature.ALERTING, + FEATURE_ALERTING, severity = SeverityType.INFO ) val sampleStatus = EventStatus( diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequestTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequestTests.kt new file mode 100644 index 00000000..349a1c14 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequestTests.kt @@ -0,0 +1,42 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.commons.notifications.action + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Test +import org.opensearch.commons.destination.message.LegacyChimeMessage +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_INDEX_MANAGEMENT +import org.opensearch.commons.utils.recreateObject + +internal class LegacyPublishNotificationRequestTests { + + private fun assertRequestEquals( + expected: LegacyPublishNotificationRequest, + actual: LegacyPublishNotificationRequest + ) { + assertEquals(expected.baseMessage.channelName, actual.baseMessage.channelName) + assertEquals(expected.baseMessage.channelType, actual.baseMessage.channelType) + assertEquals(expected.baseMessage.messageContent, actual.baseMessage.messageContent) + assertEquals(expected.baseMessage.url, actual.baseMessage.url) + assertEquals(expected.feature, actual.feature) + assertNull(actual.validate()) + } + + @Test + fun `publish request serialize and deserialize transport object should be equal`() { + val baseMessage = LegacyChimeMessage.Builder("chime_message").withMessage("Hello world").withUrl("https://amazon.com").build() + val request = LegacyPublishNotificationRequest(baseMessage, FEATURE_INDEX_MANAGEMENT) + val recreatedObject = recreateObject(request) { LegacyPublishNotificationRequest(it) } + assertRequestEquals(request, recreatedObject) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationResponseTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationResponseTests.kt new file mode 100644 index 00000000..4b6f8e0b --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationResponseTests.kt @@ -0,0 +1,29 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.commons.notifications.action + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.opensearch.commons.destination.response.LegacyDestinationResponse +import org.opensearch.commons.utils.recreateObject + +internal class LegacyPublishNotificationResponseTests { + + @Test + fun `Create response serialize and deserialize transport object should be equal`() { + val res = LegacyDestinationResponse.Builder().withStatusCode(200).withResponseContent("Hello world").build() + val configResponse = LegacyPublishNotificationResponse(res) + val recreatedObject = recreateObject(configResponse) { LegacyPublishNotificationResponse(it) } + assertEquals(configResponse.destinationResponse.statusCode, recreatedObject.destinationResponse.statusCode) + assertEquals(configResponse.destinationResponse.responseContent, recreatedObject.destinationResponse.responseContent) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/SendNotificationRequestTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/SendNotificationRequestTests.kt index 9c8bd2b4..31f6f549 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/SendNotificationRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/SendNotificationRequestTests.kt @@ -32,9 +32,11 @@ import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_ALERTING +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_INDEX_MANAGEMENT +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_REPORTS import org.opensearch.commons.notifications.model.ChannelMessage import org.opensearch.commons.notifications.model.EventSource -import org.opensearch.commons.notifications.model.Feature import org.opensearch.commons.notifications.model.SeverityType import org.opensearch.commons.utils.createObjectFromJsonString import org.opensearch.commons.utils.getJsonString @@ -58,7 +60,7 @@ internal class SendNotificationRequestTests { val notificationInfo = EventSource( "title", "reference_id", - Feature.REPORTS, + FEATURE_REPORTS, SeverityType.HIGH, listOf("tag1", "tag2") ) @@ -82,7 +84,7 @@ internal class SendNotificationRequestTests { val notificationInfo = EventSource( "title", "reference_id", - Feature.INDEX_MANAGEMENT, + FEATURE_INDEX_MANAGEMENT, SeverityType.CRITICAL, listOf("tag1", "tag2") ) @@ -115,7 +117,7 @@ internal class SendNotificationRequestTests { val notificationInfo = EventSource( "title", "reference_id", - Feature.ALERTING, + FEATURE_ALERTING, SeverityType.HIGH, listOf("tag1", "tag2") ) @@ -159,7 +161,7 @@ internal class SendNotificationRequestTests { val notificationInfo = EventSource( "title", "reference_id", - Feature.REPORTS, + FEATURE_REPORTS, SeverityType.INFO, listOf("tag1", "tag2") ) diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequestTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequestTests.kt index 607e8077..2dfcff69 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequestTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequestTests.kt @@ -31,11 +31,11 @@ import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_INDEX_MANAGEMENT import org.opensearch.commons.notifications.model.Chime import org.opensearch.commons.notifications.model.ConfigType import org.opensearch.commons.notifications.model.Email import org.opensearch.commons.notifications.model.EmailGroup -import org.opensearch.commons.notifications.model.Feature import org.opensearch.commons.notifications.model.MethodType import org.opensearch.commons.notifications.model.NotificationConfig import org.opensearch.commons.notifications.model.Slack @@ -44,7 +44,6 @@ import org.opensearch.commons.notifications.model.Webhook import org.opensearch.commons.utils.createObjectFromJsonString import org.opensearch.commons.utils.getJsonString import org.opensearch.commons.utils.recreateObject -import java.util.EnumSet internal class UpdateNotificationConfigRequestTests { @@ -54,7 +53,7 @@ internal class UpdateNotificationConfigRequestTests { "name", "description", ConfigType.WEBHOOK, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleWebhook ) @@ -66,7 +65,7 @@ internal class UpdateNotificationConfigRequestTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleSlack ) @@ -78,7 +77,7 @@ internal class UpdateNotificationConfigRequestTests { "name", "description", ConfigType.CHIME, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleChime ) @@ -90,7 +89,7 @@ internal class UpdateNotificationConfigRequestTests { "name", "description", ConfigType.EMAIL_GROUP, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleEmailGroup ) @@ -106,7 +105,7 @@ internal class UpdateNotificationConfigRequestTests { "name", "description", ConfigType.EMAIL, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleEmail ) @@ -123,7 +122,7 @@ internal class UpdateNotificationConfigRequestTests { "name", "description", ConfigType.SMTP_ACCOUNT, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleSmtpAccount ) @@ -250,7 +249,7 @@ internal class UpdateNotificationConfigRequestTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleSlack ) @@ -280,7 +279,7 @@ internal class UpdateNotificationConfigRequestTests { "name", "description", ConfigType.WEBHOOK, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleWebhook ) @@ -310,7 +309,7 @@ internal class UpdateNotificationConfigRequestTests { "name", "description", ConfigType.CHIME, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleChime ) @@ -340,7 +339,7 @@ internal class UpdateNotificationConfigRequestTests { "name", "description", ConfigType.EMAIL_GROUP, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleEmailGroup ) @@ -374,7 +373,7 @@ internal class UpdateNotificationConfigRequestTests { "name", "description", ConfigType.EMAIL, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleEmail ) @@ -410,7 +409,7 @@ internal class UpdateNotificationConfigRequestTests { "name", "description", ConfigType.SMTP_ACCOUNT, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleSmtpAccount ) @@ -448,7 +447,7 @@ internal class UpdateNotificationConfigRequestTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleSlack ) diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/EventSourceTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/EventSourceTests.kt index 883c5047..6a87ed3f 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/EventSourceTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/EventSourceTests.kt @@ -29,6 +29,7 @@ package org.opensearch.commons.notifications.model import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_ALERTING import org.opensearch.commons.utils.createObjectFromJsonString import org.opensearch.commons.utils.getJsonString import org.opensearch.commons.utils.recreateObject @@ -40,7 +41,7 @@ internal class EventSourceTests { val sampleEventSource = EventSource( "title", "reference_id", - Feature.ALERTING, + FEATURE_ALERTING, severity = SeverityType.INFO ) val recreatedObject = recreateObject(sampleEventSource) { EventSource(it) } @@ -52,7 +53,7 @@ internal class EventSourceTests { val sampleEventSource = EventSource( "title", "reference_id", - Feature.ALERTING, + FEATURE_ALERTING, severity = SeverityType.INFO ) @@ -66,7 +67,7 @@ internal class EventSourceTests { val sampleEventSource = EventSource( "title", "reference_id", - Feature.ALERTING, + FEATURE_ALERTING, tags = listOf("tag1", "tag2"), severity = SeverityType.INFO ) @@ -87,11 +88,11 @@ internal class EventSourceTests { } @Test - fun `Event source should safely ignore unknown feature type in json object`() { + fun `Event source should safely accepts unknown feature type in json object`() { val sampleEventSource = EventSource( "title", "reference_id", - Feature.NONE, + "NewFeature", tags = listOf("tag1", "tag2"), severity = SeverityType.INFO ) @@ -114,7 +115,7 @@ internal class EventSourceTests { EventSource( "", "reference_id", - Feature.ALERTING, + FEATURE_ALERTING, tags = listOf("tag1", "tag2"), severity = SeverityType.INFO ) diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/FeatureTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/FeatureTests.kt deleted file mode 100644 index 215c2c2c..00000000 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/FeatureTests.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - * - */ -package org.opensearch.commons.notifications.model - -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test -import org.opensearch.commons.notifications.model.Feature.Companion.enumParser -import org.opensearch.commons.notifications.model.Feature.Companion.fromTagOrDefault - -internal class FeatureTests { - - @Test - fun `toString should return tag`() { - Feature.values().forEach { - assertEquals(it.tag, it.toString()) - } - } - - @Test - fun `fromTagOrDefault should return corresponding enum`() { - Feature.values().forEach { - assertEquals(it, fromTagOrDefault(it.tag)) - } - } - - @Test - fun `EnumParser fromTagOrDefault should return corresponding enum`() { - Feature.values().forEach { - assertEquals(it, enumParser.fromTagOrDefault(it.tag)) - } - } -} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfoTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfoTests.kt index 79c1035d..409decd7 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfoTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfoTests.kt @@ -29,11 +29,12 @@ package org.opensearch.commons.notifications.model import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_INDEX_MANAGEMENT +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_REPORTS import org.opensearch.commons.utils.createObjectFromJsonString import org.opensearch.commons.utils.getJsonString import org.opensearch.commons.utils.recreateObject import java.time.Instant -import java.util.EnumSet internal class NotificationConfigInfoTests { @@ -44,7 +45,7 @@ internal class NotificationConfigInfoTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.REPORTS), + setOf(FEATURE_REPORTS), configData = sampleSlack ) val configInfo = NotificationConfigInfo( @@ -67,7 +68,7 @@ internal class NotificationConfigInfoTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.REPORTS), + setOf(FEATURE_REPORTS), configData = sampleSlack ) val configInfo = NotificationConfigInfo( @@ -91,7 +92,7 @@ internal class NotificationConfigInfoTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleSlack ) @@ -130,7 +131,7 @@ internal class NotificationConfigInfoTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleSlack ) @@ -171,7 +172,7 @@ internal class NotificationConfigInfoTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.REPORTS), + setOf(FEATURE_REPORTS), configData = sampleSlack ) Assertions.assertThrows(IllegalArgumentException::class.java) { diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigSearchResultsTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigSearchResultsTests.kt index a9543533..6a10935f 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigSearchResultsTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigSearchResultsTests.kt @@ -30,11 +30,12 @@ import org.apache.lucene.search.TotalHits import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_INDEX_MANAGEMENT +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_REPORTS import org.opensearch.commons.utils.createObjectFromJsonString import org.opensearch.commons.utils.getJsonString import org.opensearch.commons.utils.recreateObject import java.time.Instant -import java.util.EnumSet internal class NotificationConfigSearchResultsTests { @@ -56,7 +57,7 @@ internal class NotificationConfigSearchResultsTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.REPORTS), + setOf(FEATURE_REPORTS), configData = sampleSlack ) val configInfo = NotificationConfigInfo( @@ -77,7 +78,7 @@ internal class NotificationConfigSearchResultsTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.REPORTS), + setOf(FEATURE_REPORTS), configData = Slack("https://domain.com/sample_url#1234567890") ) val configInfo1 = NotificationConfigInfo( @@ -91,7 +92,7 @@ internal class NotificationConfigSearchResultsTests { "name", "description", ConfigType.CHIME, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), configData = Chime("https://domain.com/sample_url#1234567890") ) val configInfo2 = NotificationConfigInfo( @@ -118,7 +119,7 @@ internal class NotificationConfigSearchResultsTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.REPORTS), + setOf(FEATURE_REPORTS), configData = Slack("https://domain.com/sample_url#1234567890") ) val configInfo1 = NotificationConfigInfo( @@ -132,7 +133,7 @@ internal class NotificationConfigSearchResultsTests { "name", "description", ConfigType.CHIME, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), configData = Chime("https://domain.com/sample_url#1234567890") ) val configInfo2 = NotificationConfigInfo( @@ -161,7 +162,7 @@ internal class NotificationConfigSearchResultsTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.REPORTS), + setOf(FEATURE_REPORTS), configData = sampleSlack ) val configInfo = NotificationConfigInfo( @@ -185,7 +186,7 @@ internal class NotificationConfigSearchResultsTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.REPORTS), + setOf(FEATURE_REPORTS), configData = Slack("https://domain.com/sample_url#1234567890") ) val configInfo1 = NotificationConfigInfo( @@ -199,7 +200,7 @@ internal class NotificationConfigSearchResultsTests { "name", "description", ConfigType.CHIME, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), configData = Chime("https://domain.com/sample_url#1234567890") ) val configInfo2 = NotificationConfigInfo( @@ -229,7 +230,7 @@ internal class NotificationConfigSearchResultsTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleSlack ) @@ -280,7 +281,7 @@ internal class NotificationConfigSearchResultsTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleSlack ) diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigTests.kt index ab45e2fc..c7126ab0 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigTests.kt @@ -28,10 +28,12 @@ package org.opensearch.commons.notifications.model import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_ALERTING +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_INDEX_MANAGEMENT +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_REPORTS import org.opensearch.commons.utils.createObjectFromJsonString import org.opensearch.commons.utils.getJsonString import org.opensearch.commons.utils.recreateObject -import java.util.EnumSet internal class NotificationConfigTests { @@ -42,7 +44,7 @@ internal class NotificationConfigTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.REPORTS), + setOf(FEATURE_REPORTS), configData = sampleSlack ) val recreatedObject = recreateObject(sampleConfig) { NotificationConfig(it) } @@ -56,7 +58,7 @@ internal class NotificationConfigTests { "name", "description", ConfigType.SLACK, - EnumSet.of(Feature.REPORTS), + setOf(FEATURE_REPORTS), configData = sampleSlack ) val jsonString = getJsonString(sampleConfig) @@ -71,7 +73,7 @@ internal class NotificationConfigTests { "name", "description", ConfigType.CHIME, - EnumSet.of(Feature.ALERTING), + setOf(FEATURE_ALERTING), configData = sampleChime ) val recreatedObject = recreateObject(sampleConfig) { NotificationConfig(it) } @@ -85,7 +87,7 @@ internal class NotificationConfigTests { "name", "description", ConfigType.CHIME, - EnumSet.of(Feature.ALERTING), + setOf(FEATURE_ALERTING), configData = sampleChime ) val jsonString = getJsonString(sampleConfig) @@ -100,7 +102,7 @@ internal class NotificationConfigTests { "name", "description", ConfigType.WEBHOOK, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), configData = sampleWebhook ) val recreatedObject = recreateObject(sampleConfig) { NotificationConfig(it) } @@ -114,7 +116,7 @@ internal class NotificationConfigTests { "name", "description", ConfigType.WEBHOOK, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), configData = sampleWebhook ) val jsonString = getJsonString(sampleConfig) @@ -129,7 +131,7 @@ internal class NotificationConfigTests { "name", "description", ConfigType.EMAIL, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), configData = sampleEmail ) val recreatedObject = recreateObject(sampleConfig) { NotificationConfig(it) } @@ -143,7 +145,7 @@ internal class NotificationConfigTests { "name", "description", ConfigType.EMAIL, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), configData = sampleEmail ) val jsonString = getJsonString(sampleConfig) @@ -158,7 +160,7 @@ internal class NotificationConfigTests { "name", "description", ConfigType.SMTP_ACCOUNT, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), configData = smtpAccount ) val jsonString = getJsonString(sampleConfig) @@ -173,7 +175,7 @@ internal class NotificationConfigTests { "name", "description", ConfigType.SMTP_ACCOUNT, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), configData = sampleSmtpAccount ) val recreatedObject = recreateObject(sampleConfig) { NotificationConfig(it) } @@ -187,7 +189,7 @@ internal class NotificationConfigTests { "name", "description", ConfigType.EMAIL_GROUP, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), configData = sampleEmailGroup ) val jsonString = getJsonString(sampleConfig) @@ -202,7 +204,7 @@ internal class NotificationConfigTests { "name", "description", ConfigType.EMAIL_GROUP, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), configData = sampleEmailGroup ) val recreatedObject = recreateObject(sampleConfig) { NotificationConfig(it) } @@ -217,7 +219,7 @@ internal class NotificationConfigTests { "name", "description", ConfigType.NONE, - EnumSet.of(Feature.INDEX_MANAGEMENT), + setOf(FEATURE_INDEX_MANAGEMENT), isEnabled = true, configData = sampleSlack ) @@ -240,13 +242,13 @@ internal class NotificationConfigTests { } @Test - fun `Config should safely ignore unknown feature type in json object`() { + fun `Config should safely accepts unknown feature type in json object`() { val sampleWebhook = Webhook("https://domain.com/sample_webhook_url#1234567890") val sampleConfig = NotificationConfig( "name", "description", ConfigType.WEBHOOK, - EnumSet.of(Feature.INDEX_MANAGEMENT, Feature.NONE), + setOf(FEATURE_INDEX_MANAGEMENT, "NewFeature1", "NewFeature2"), isEnabled = true, configData = sampleWebhook ) diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventInfoTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventInfoTests.kt index e23ca1e0..9f45fbb0 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventInfoTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventInfoTests.kt @@ -29,6 +29,7 @@ package org.opensearch.commons.notifications.model import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_ALERTING import org.opensearch.commons.utils.createObjectFromJsonString import org.opensearch.commons.utils.getJsonString import org.opensearch.commons.utils.recreateObject @@ -41,7 +42,7 @@ internal class NotificationEventInfoTests { val sampleEventSource = EventSource( "title", "reference_id", - Feature.ALERTING, + FEATURE_ALERTING, severity = SeverityType.INFO ) val sampleStatus = EventStatus( @@ -69,7 +70,7 @@ internal class NotificationEventInfoTests { val sampleEventSource = EventSource( "title", "reference_id", - Feature.ALERTING, + FEATURE_ALERTING, severity = SeverityType.INFO ) val sampleStatus = EventStatus( @@ -98,7 +99,7 @@ internal class NotificationEventInfoTests { val sampleEventSource = EventSource( "title", "reference_id", - Feature.ALERTING, + FEATURE_ALERTING, severity = SeverityType.INFO ) val sampleStatus = EventStatus( @@ -155,7 +156,7 @@ internal class NotificationEventInfoTests { val sampleEventSource = EventSource( "title", "reference_id", - Feature.ALERTING, + FEATURE_ALERTING, severity = SeverityType.INFO ) val sampleStatus = EventStatus( @@ -215,7 +216,7 @@ internal class NotificationEventInfoTests { val sampleEventSource = EventSource( "title", "reference_id", - Feature.ALERTING, + FEATURE_ALERTING, severity = SeverityType.INFO ) val sampleStatus = EventStatus( diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventSearchResultTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventSearchResultTests.kt index d09fe9d2..078d4910 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventSearchResultTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventSearchResultTests.kt @@ -30,6 +30,8 @@ import org.apache.lucene.search.TotalHits import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_ALERTING +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_REPORTS import org.opensearch.commons.utils.createObjectFromJsonString import org.opensearch.commons.utils.getJsonString import org.opensearch.commons.utils.recreateObject @@ -53,7 +55,7 @@ internal class NotificationEventSearchResultTests { val sampleEventSource = EventSource( "title", "reference_id", - Feature.ALERTING, + FEATURE_ALERTING, severity = SeverityType.INFO ) val sampleStatus = EventStatus( @@ -80,13 +82,13 @@ internal class NotificationEventSearchResultTests { val eventSource1 = EventSource( "title 1", "reference_id_1", - Feature.ALERTING, + FEATURE_ALERTING, severity = SeverityType.INFO ) val eventSource2 = EventSource( "title 2", "reference_id_2", - Feature.REPORTS, + FEATURE_REPORTS, severity = SeverityType.HIGH ) val status1 = EventStatus( @@ -147,13 +149,13 @@ internal class NotificationEventSearchResultTests { val eventSource1 = EventSource( "title 1", "reference_id_1", - Feature.ALERTING, + FEATURE_ALERTING, severity = SeverityType.INFO ) val eventSource2 = EventSource( "title 2", "reference_id_2", - Feature.REPORTS, + FEATURE_REPORTS, severity = SeverityType.HIGH ) val status1 = EventStatus( @@ -213,7 +215,7 @@ internal class NotificationEventSearchResultTests { val sampleEventSource = EventSource( "title", "reference_id", - Feature.ALERTING, + FEATURE_ALERTING, severity = SeverityType.INFO ) val sampleStatus = EventStatus( @@ -243,13 +245,13 @@ internal class NotificationEventSearchResultTests { val eventSource1 = EventSource( "title 1", "reference_id_1", - Feature.ALERTING, + FEATURE_ALERTING, severity = SeverityType.INFO ) val eventSource2 = EventSource( "title 2", "reference_id_2", - Feature.REPORTS, + FEATURE_REPORTS, severity = SeverityType.HIGH ) val status1 = EventStatus( @@ -296,7 +298,7 @@ internal class NotificationEventSearchResultTests { val sampleEventSource = EventSource( "title", "reference_id", - Feature.ALERTING, + FEATURE_ALERTING, severity = SeverityType.INFO ) val sampleStatus = EventStatus( @@ -364,7 +366,7 @@ internal class NotificationEventSearchResultTests { val sampleEventSource = EventSource( "title", "reference_id", - Feature.ALERTING, + FEATURE_ALERTING, severity = SeverityType.INFO ) val sampleStatus = EventStatus( diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventTests.kt index 4a135d0c..92c53fd8 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventTests.kt @@ -29,6 +29,8 @@ package org.opensearch.commons.notifications.model import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_ALERTING +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_REPORTS import org.opensearch.commons.utils.createObjectFromJsonString import org.opensearch.commons.utils.getJsonString import org.opensearch.commons.utils.recreateObject @@ -40,7 +42,7 @@ internal class NotificationEventTests { val sampleEventSource = EventSource( "title", "reference_id", - Feature.ALERTING, + FEATURE_ALERTING, severity = SeverityType.INFO ) val sampleStatus = EventStatus( @@ -59,7 +61,7 @@ internal class NotificationEventTests { val sampleEventSource = EventSource( "title", "reference_id", - Feature.REPORTS, + FEATURE_REPORTS, severity = SeverityType.INFO ) val sampleStatus = EventStatus( @@ -79,7 +81,7 @@ internal class NotificationEventTests { val sampleEventSource = EventSource( "title", "reference_id", - Feature.ALERTING, + FEATURE_ALERTING, tags = listOf("tag1", "tag2"), severity = SeverityType.INFO ) diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/SesAccountTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/SesAccountTests.kt new file mode 100644 index 00000000..c99506fd --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/SesAccountTests.kt @@ -0,0 +1,151 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.commons.notifications.model + +import com.fasterxml.jackson.core.JsonParseException +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.opensearch.commons.utils.createObjectFromJsonString +import org.opensearch.commons.utils.getJsonString +import org.opensearch.commons.utils.recreateObject + +internal class SesAccountTests { + + @Test + fun `SES should throw exception if empty region`() { + assertThrows { + SesAccount("", null, "from@domain.com") + } + val jsonString = """ + { + "region":"", + "from_address":"from@domain.com" + } + """.trimIndent() + assertThrows { + createObjectFromJsonString(jsonString) { SesAccount.parse(it) } + } + } + + @Test + fun `SES should throw exception if invalid role ARN`() { + assertThrows { + SesAccount("us-east-1", "arn:aws:iam:us-east-1:0123456789:role-test", "from@domain.com") + } + val jsonString = """ + { + "region":"us-east-1", + "role_arn":"arn:aws:iam:us-east-1:0123456789:role-test", + "from_address":"from@domain.com" + } + """.trimIndent() + assertThrows { + createObjectFromJsonString(jsonString) { SesAccount.parse(it) } + } + } + + @Test + fun `SES should throw exception when email id is invalid`() { + val jsonString = """ + { + "region":"us-east-1", + "from_address":".from@domain.com" + } + """.trimIndent() + assertThrows { + createObjectFromJsonString(jsonString) { SesAccount.parse(it) } + } + } + + @Test + fun `SES serialize and deserialize transport object should be equal`() { + val sesAccount = SesAccount("us-east-1", "arn:aws:iam::012345678912:role/iam-test", "from@domain.com") + val recreatedObject = recreateObject(sesAccount) { SesAccount(it) } + assertEquals(sesAccount, recreatedObject) + } + + @Test + fun `SES serialize and deserialize using json object should be equal`() { + val sesAccount = SesAccount("us-east-1", "arn:aws:iam::012345678912:role/iam-test", "from@domain.com") + val jsonString = getJsonString(sesAccount) + val recreatedObject = createObjectFromJsonString(jsonString) { SesAccount.parse(it) } + assertEquals(sesAccount, recreatedObject) + } + + @Test + fun `SES should deserialize json object using parser`() { + val sesAccount = SesAccount("us-east-1", "arn:aws:iam::012345678912:role/iam-test", "from@domain.com") + val jsonString = """ + { + "region":"${sesAccount.awsRegion}", + "role_arn":"${sesAccount.roleArn}", + "from_address":"${sesAccount.fromAddress}" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { SesAccount.parse(it) } + assertEquals(sesAccount, recreatedObject) + } + + @Test + fun `SES should throw exception when invalid json object is passed`() { + val jsonString = "sample message" + assertThrows { + createObjectFromJsonString(jsonString) { SesAccount.parse(it) } + } + } + + @Test + fun `SES should throw exception when region is replace with region2 in json object`() { + val jsonString = """ + { + "region2":"us-east-1", + "role_arn":"arn:aws:iam::012345678912:role/iam-test", + "from_address":"from@domain.com" + } + """.trimIndent() + assertThrows { + createObjectFromJsonString(jsonString) { SesAccount.parse(it) } + } + } + + @Test + fun `SES should throw exception when from_address is replace with from_address2 in json object`() { + val jsonString = """ + { + "region":"us-east-1", + "role_arn":"arn:aws:iam::012345678912:role/iam-test", + "from_address2":"from@domain.com" + } + """.trimIndent() + assertThrows { + createObjectFromJsonString(jsonString) { SesAccount.parse(it) } + } + } + + @Test + fun `SES should safely ignore extra field in json object`() { + val sesAccount = SesAccount("us-east-1", "arn:aws:iam::012345678912:role/iam-test", "from@domain.com") + val jsonString = """ + { + "region":"${sesAccount.awsRegion}", + "role_arn":"${sesAccount.roleArn}", + "from_address":"${sesAccount.fromAddress}", + "extra_field_1":["extra", "value"], + "extra_field_2":{"extra":"value"}, + "extra_field_3":"extra value 3" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { SesAccount.parse(it) } + assertEquals(sesAccount, recreatedObject) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/SNSTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/SnsTests.kt similarity index 65% rename from src/test/kotlin/org/opensearch/commons/notifications/model/SNSTests.kt rename to src/test/kotlin/org/opensearch/commons/notifications/model/SnsTests.kt index 46d7cfc8..03d494d2 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/SNSTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/SnsTests.kt @@ -19,87 +19,87 @@ import org.opensearch.commons.utils.createObjectFromJsonString import org.opensearch.commons.utils.getJsonString import org.opensearch.commons.utils.recreateObject -internal class SNSTests { +internal class SnsTests { @Test fun `SNS should throw exception if empty topic`() { assertThrows(IllegalArgumentException::class.java) { - SNS("", null) + Sns("", null) } val jsonString = "{\"topic_arn\":\"\"}" assertThrows(IllegalArgumentException::class.java) { - createObjectFromJsonString(jsonString) { SNS.parse(it) } + createObjectFromJsonString(jsonString) { Sns.parse(it) } } } @Test fun `SNS should throw exception if invalid topic ARN`() { assertThrows(IllegalArgumentException::class.java) { - SNS("arn:aws:es:us-east-1:012345678989:test", null) + Sns("arn:aws:es:us-east-1:012345678989:test", null) } val jsonString = "{\"topic_arn\":\"arn:aws:es:us-east-1:012345678989:test\"}" assertThrows(IllegalArgumentException::class.java) { - createObjectFromJsonString(jsonString) { SNS.parse(it) } + createObjectFromJsonString(jsonString) { Sns.parse(it) } } } @Test fun `SNS should throw exception if invalid role ARN`() { assertThrows(IllegalArgumentException::class.java) { - SNS("arn:aws:sns:us-east-1:012345678912:topic-test", "arn:aws:iam:us-east-1:0123456789:role-test") + Sns("arn:aws:sns:us-east-1:012345678912:topic-test", "arn:aws:iam:us-east-1:0123456789:role-test") } val jsonString = "{\"topic_arn\":\"arn:aws:sns:us-east-1:012345678912:topic-test\",\"role_arn\":\"arn:aws:iam:us-east-1:0123456789:role-test\"}" assertThrows(IllegalArgumentException::class.java) { - createObjectFromJsonString(jsonString) { SNS.parse(it) } + createObjectFromJsonString(jsonString) { Sns.parse(it) } } } @Test fun `SNS serialize and deserialize transport object should be equal`() { - val sampleSNS = SNS("arn:aws:sns:us-east-1:012345678912:topic-test", "arn:aws:iam::012345678912:role/iam-test") - val recreatedObject = recreateObject(sampleSNS) { SNS(it) } - Assertions.assertEquals(sampleSNS, recreatedObject) + val sampleSns = Sns("arn:aws:sns:us-east-1:012345678912:topic-test", "arn:aws:iam::012345678912:role/iam-test") + val recreatedObject = recreateObject(sampleSns) { Sns(it) } + Assertions.assertEquals(sampleSns, recreatedObject) } @Test fun `SNS serialize and deserialize using json object should be equal`() { - val sampleSNS = SNS("arn:aws:sns:us-east-1:012345678912:topic-test", "arn:aws:iam::012345678912:role/iam-test") - val jsonString = getJsonString(sampleSNS) - val recreatedObject = createObjectFromJsonString(jsonString) { SNS.parse(it) } - Assertions.assertEquals(sampleSNS, recreatedObject) + val sampleSns = Sns("arn:aws:sns:us-east-1:012345678912:topic-test", "arn:aws:iam::012345678912:role/iam-test") + val jsonString = getJsonString(sampleSns) + val recreatedObject = createObjectFromJsonString(jsonString) { Sns.parse(it) } + Assertions.assertEquals(sampleSns, recreatedObject) } @Test fun `SNS should deserialize json object using parser`() { - val sampleSNS = SNS("arn:aws:sns:us-east-1:012345678912:topic-test", "arn:aws:iam::012345678912:role/iam-test") - val jsonString = "{\"topic_arn\":\"${sampleSNS.topicARN}\",\"role_arn\":\"${sampleSNS.roleARN}\"}" - val recreatedObject = createObjectFromJsonString(jsonString) { SNS.parse(it) } - Assertions.assertEquals(sampleSNS, recreatedObject) + val sampleSns = Sns("arn:aws:sns:us-east-1:012345678912:topic-test", "arn:aws:iam::012345678912:role/iam-test") + val jsonString = "{\"topic_arn\":\"${sampleSns.topicArn}\",\"role_arn\":\"${sampleSns.roleArn}\"}" + val recreatedObject = createObjectFromJsonString(jsonString) { Sns.parse(it) } + Assertions.assertEquals(sampleSns, recreatedObject) } @Test fun `SNS should throw exception when invalid json object is passed`() { val jsonString = "sample message" assertThrows(JsonParseException::class.java) { - createObjectFromJsonString(jsonString) { SNS.parse(it) } + createObjectFromJsonString(jsonString) { Sns.parse(it) } } } @Test fun `SNS should throw exception when arn is replace with arn2 in json object`() { - val sampleSNS = SNS("arn:aws:sns:us-east-1:012345678912:topic-test", "arn:aws:iam::012345678912:role/iam-test") - val jsonString = "{\"topic_arn2\":\"${sampleSNS.topicARN}\",\"role_arn\":\"${sampleSNS.roleARN}\"}" + val sampleSns = Sns("arn:aws:sns:us-east-1:012345678912:topic-test", "arn:aws:iam::012345678912:role/iam-test") + val jsonString = "{\"topic_arn2\":\"${sampleSns.topicArn}\",\"role_arn\":\"${sampleSns.roleArn}\"}" assertThrows(IllegalArgumentException::class.java) { - createObjectFromJsonString(jsonString) { SNS.parse(it) } + createObjectFromJsonString(jsonString) { Sns.parse(it) } } } @Test fun `SNS should safely ignore extra field in json object`() { - val sampleSNS = SNS("arn:aws:sns:us-east-1:012345678912:topic-test", null) - val jsonString = "{\"topic_arn\":\"${sampleSNS.topicARN}\", \"another\":\"field\"}" - val recreatedObject = createObjectFromJsonString(jsonString) { SNS.parse(it) } - Assertions.assertEquals(sampleSNS, recreatedObject) + val sampleSns = Sns("arn:aws:sns:us-east-1:012345678912:topic-test", null) + val jsonString = "{\"topic_arn\":\"${sampleSns.topicArn}\", \"another\":\"field\"}" + val recreatedObject = createObjectFromJsonString(jsonString) { Sns.parse(it) } + Assertions.assertEquals(sampleSns, recreatedObject) } }