From a60ad677e9f0eca77444135246f438be5f2cf222 Mon Sep 17 00:00:00 2001 From: Anantha Krishna Bhatta <31894175+akbhatta@users.noreply.github.com> Date: Fri, 4 Jun 2021 17:37:37 -0700 Subject: [PATCH] Notification plugin interface and models (#31) Currently Alerting, ISM and Reporting plugins are expected to make call to Notification plugin to send notification. The plugins can communicate using REST or Transport layer. Transport layer makes call to same node and hence avoids network hop so transport layer communication is preferred. common-utils contains the model (with marshaling/unmarshaling code) for the communication. above mentioned plugins consumes common-utils and uses these models for communication. Note: Plugins runs on different JVM and hence the notification plugins needs to check if the class is same before accessing the values. transport marshaling/unmarshaling ( writeTo(output: StreamOutput) and constructor(input: StreamInput) of models) are used for this purpose. REST APIs in notification plugin uses toXContent and parse marshaling/unmarshaling [Tests] Contains unit test for all model classes Signed-off-by: @akbhatta --- detekt.yml | 10 +- .../notifications/NotificationConstants.kt | 71 +++ .../NotificationsPluginInterface.kt | 215 +++++++ .../notifications/action/BaseResponse.kt | 59 ++ .../action/CreateNotificationConfigRequest.kt | 144 +++++ .../CreateNotificationConfigResponse.kt | 117 ++++ .../action/DeleteNotificationConfigRequest.kt | 134 +++++ .../DeleteNotificationConfigResponse.kt | 135 +++++ .../action/GetFeatureChannelListRequest.kt | 132 +++++ .../action/GetFeatureChannelListResponse.kt | 92 +++ .../action/GetNotificationConfigRequest.kt | 193 +++++++ .../action/GetNotificationConfigResponse.kt | 92 +++ .../action/GetNotificationEventRequest.kt | 193 +++++++ .../action/GetNotificationEventResponse.kt | 92 +++ .../action/GetPluginFeaturesRequest.kt | 126 +++++ .../action/GetPluginFeaturesResponse.kt | 129 +++++ .../action/NotificationsActions.kt | 122 ++++ .../action/SendNotificationRequest.kt | 171 ++++++ .../action/SendNotificationResponse.kt | 117 ++++ .../action/UpdateNotificationConfigRequest.kt | 146 +++++ .../UpdateNotificationConfigResponse.kt | 117 ++++ .../commons/notifications/model/Attachment.kt | 126 +++++ .../notifications/model/BaseConfigData.kt | 33 ++ .../commons/notifications/model/BaseModel.kt | 35 ++ .../notifications/model/ChannelMessage.kt | 137 +++++ .../commons/notifications/model/Chime.kt | 121 ++++ .../commons/notifications/model/ConfigType.kt | 85 +++ .../notifications/model/DeliveryStatus.kt | 126 +++++ .../commons/notifications/model/Email.kt | 138 +++++ .../commons/notifications/model/EmailGroup.kt | 122 ++++ .../model/EmailRecipientStatus.kt | 123 ++++ .../notifications/model/EventSource.kt | 152 +++++ .../notifications/model/EventStatus.kt | 165 ++++++ .../commons/notifications/model/Feature.kt | 71 +++ .../notifications/model/FeatureChannel.kt | 152 +++++ .../notifications/model/FeatureChannelList.kt | 93 +++ .../commons/notifications/model/MethodType.kt | 36 ++ .../notifications/model/NotificationConfig.kt | 175 ++++++ .../model/NotificationConfigInfo.kt | 153 +++++ .../model/NotificationConfigSearchResult.kt | 93 +++ .../notifications/model/NotificationEvent.kt | 127 +++++ .../model/NotificationEventInfo.kt | 153 +++++ .../model/NotificationEventSearchResult.kt | 93 +++ .../notifications/model/SearchResults.kt | 221 ++++++++ .../notifications/model/SeverityType.kt | 71 +++ .../commons/notifications/model/Slack.kt | 121 ++++ .../notifications/model/SmtpAccount.kt | 146 +++++ .../commons/notifications/model/Webhook.kt | 130 +++++ .../commons/notifications/model/XParser.kt | 40 ++ .../model/config/ConfigDataProperties.kt | 95 ++++ .../opensearch/commons/utils/EnumHelpers.kt | 55 ++ .../opensearch/commons/utils/EnumParser.kt | 39 ++ .../org/opensearch/commons/utils/Helpers.kt | 35 ++ .../commons/utils/OpenForTesting.kt | 33 ++ .../commons/utils/SecureClientWrapper.kt | 328 +++++++++++ .../commons/utils/TransportHelpers.kt | 60 ++ .../commons/utils/ValidationHelpers.kt | 75 +++ .../commons/utils/XContentHelpers.kt | 83 +++ .../CreateNotificationConfigRequestTests.kt | 533 ++++++++++++++++++ .../CreateNotificationConfigResponseTests.kt | 92 +++ .../DeleteNotificationConfigRequestTests.kt | 102 ++++ .../DeleteNotificationConfigResponseTests.kt | 103 ++++ .../GetFeatureChannelListRequestTests.kt | 95 ++++ .../GetFeatureChannelListResponseTests.kt | 245 ++++++++ .../GetNotificationConfigRequestTests.kt | 280 +++++++++ .../GetNotificationConfigResponseTests.kt | 312 ++++++++++ .../GetNotificationEventRequestTests.kt | 280 +++++++++ .../GetNotificationEventResponseTests.kt | 391 +++++++++++++ .../action/GetPluginFeaturesRequestTests.kt | 82 +++ .../action/GetPluginFeaturesResponseTests.kt | 129 +++++ .../action/SendNotificationRequestTests.kt | 272 +++++++++ .../action/SendNotificationResponseTests.kt | 92 +++ .../UpdateNotificationConfigRequestTests.kt | 476 ++++++++++++++++ .../UpdateNotificationConfigResponseTests.kt | 92 +++ .../notifications/model/AttachmentTests.kt | 146 +++++ .../model/ChannelMessageTests.kt | 202 +++++++ .../commons/notifications/model/ChimeTests.kt | 109 ++++ .../notifications/model/ConfigTypeTests.kt | 56 ++ .../model/DeliveryStatusTests.kt | 84 +++ .../notifications/model/EmailGroupTests.kt | 141 +++++ .../model/EmailRecipientStatusTests.kt | 94 +++ .../commons/notifications/model/EmailTests.kt | 191 +++++++ .../notifications/model/EventSourceTests.kt | 123 ++++ .../notifications/model/EventStatusTests.kt | 164 ++++++ .../model/FeatureChannelListTests.kt | 232 ++++++++ .../model/FeatureChannelTests.kt | 230 ++++++++ .../notifications/model/FeatureTests.kt | 56 ++ .../model/FilterConfigListTests.kt | 238 ++++++++ .../notifications/model/FilterConfigTests.kt | 157 ++++++ .../notifications/model/MethodTypeTests.kt | 56 ++ .../model/NotificationConfigInfoTests.kt | 275 +++++++++ .../NotificationConfigSearchResultsTests.kt | 342 +++++++++++ .../model/NotificationConfigTests.kt | 266 +++++++++ .../model/NotificationEventInfoTests.kt | 364 ++++++++++++ .../NotificationEventSearchResultTests.kt | 445 +++++++++++++++ .../model/NotificationEventTests.kt | 198 +++++++ .../notifications/model/SeverityTypeTests.kt | 56 ++ .../commons/notifications/model/SlackTests.kt | 109 ++++ .../notifications/model/SmtpAccountTests.kt | 112 ++++ .../notifications/model/WebhookTests.kt | 116 ++++ .../model/config/ConfigPropertiesTests.kt | 88 +++ .../opensearch/commons/utils/TestHelpers.kt | 55 ++ 102 files changed, 14928 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/NotificationConstants.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterface.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/action/BaseResponse.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequest.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigResponse.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigRequest.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigResponse.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequest.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListResponse.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigRequest.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigResponse.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventRequest.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventResponse.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesRequest.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesResponse.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/action/NotificationsActions.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/action/SendNotificationRequest.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/action/SendNotificationResponse.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequest.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigResponse.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/model/Attachment.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/model/BaseConfigData.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/model/BaseModel.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/model/ChannelMessage.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/model/Chime.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/model/ConfigType.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/model/DeliveryStatus.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/model/Email.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/model/EmailGroup.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/model/EmailRecipientStatus.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/model/EventSource.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/model/EventStatus.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/model/Feature.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/model/FeatureChannel.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/model/FeatureChannelList.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/model/MethodType.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfig.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfo.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfigSearchResult.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEvent.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEventInfo.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEventSearchResult.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/model/SearchResults.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/model/SeverityType.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/model/Slack.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/model/SmtpAccount.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/model/Webhook.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/model/XParser.kt create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/model/config/ConfigDataProperties.kt create mode 100644 src/main/kotlin/org/opensearch/commons/utils/EnumHelpers.kt create mode 100644 src/main/kotlin/org/opensearch/commons/utils/EnumParser.kt create mode 100644 src/main/kotlin/org/opensearch/commons/utils/Helpers.kt create mode 100644 src/main/kotlin/org/opensearch/commons/utils/OpenForTesting.kt create mode 100644 src/main/kotlin/org/opensearch/commons/utils/SecureClientWrapper.kt create mode 100644 src/main/kotlin/org/opensearch/commons/utils/TransportHelpers.kt create mode 100644 src/main/kotlin/org/opensearch/commons/utils/ValidationHelpers.kt create mode 100644 src/main/kotlin/org/opensearch/commons/utils/XContentHelpers.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequestTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigResponseTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigRequestTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigResponseTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequestTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListResponseTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigRequestTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigResponseTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventRequestTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventResponseTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesRequestTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesResponseTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/action/SendNotificationRequestTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/action/SendNotificationResponseTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequestTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigResponseTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/model/AttachmentTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/model/ChannelMessageTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/model/ChimeTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/model/ConfigTypeTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/model/DeliveryStatusTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/model/EmailGroupTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/model/EmailRecipientStatusTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/model/EmailTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/model/EventSourceTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/model/EventStatusTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/model/FeatureChannelListTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/model/FeatureChannelTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/model/FeatureTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/model/FilterConfigListTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/model/FilterConfigTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/model/MethodTypeTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfoTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigSearchResultsTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventInfoTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventSearchResultTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/model/SeverityTypeTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/model/SlackTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/model/SmtpAccountTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/model/WebhookTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/model/config/ConfigPropertiesTests.kt create mode 100644 src/test/kotlin/org/opensearch/commons/utils/TestHelpers.kt diff --git a/detekt.yml b/detekt.yml index 1c113609..34ed3c1e 100644 --- a/detekt.yml +++ b/detekt.yml @@ -20,4 +20,12 @@ style: max: 10 ReturnCount: active: true - max: 10 \ No newline at end of file + max: 10 + +complexity: + LargeClass: + excludes: ['**/test/**'] + LongMethod: + excludes: ['**/test/**'] + LongParameterList: + excludes: ['**/test/**'] diff --git a/src/main/kotlin/org/opensearch/commons/notifications/NotificationConstants.kt b/src/main/kotlin/org/opensearch/commons/notifications/NotificationConstants.kt new file mode 100644 index 00000000..344cf0b8 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/NotificationConstants.kt @@ -0,0 +1,71 @@ +package org.opensearch.commons.notifications + +/** + * Class containing Internal constants like JSON tags and defaults. + */ +object NotificationConstants { + const val CONFIG_ID_TAG = "config_id" + const val CONFIG_ID_LIST_TAG = "config_id_list" + const val EVENT_ID_TAG = "event_id" + const val EVENT_ID_LIST_TAG = "event_id_list" + const val EMAIL_ACCOUNT_ID_TAG = "email_account_id" + const val REFERENCE_ID_TAG = "reference_id" + const val CHANNEL_ID_LIST_TAG = "channel_id_list" + const val CONFIG_NAME_TAG = "config_name" + const val CONFIG_TYPE_TAG = "config_type" + const val CONFIG_TAG = "config" + const val EVENT_TAG = "event" + const val EVENT_SOURCE_TAG = "event_source" + const val FEATURE_TAG = "feature" + const val THREAD_CONTEXT_TAG = "context" + const val CHANNEL_MESSAGE_TAG = "channel_message" + const val TEXT_DESCRIPTION_TAG = "text_description" + const val HTML_DESCRIPTION_TAG = "html_description" + const val ATTACHMENT_TAG = "attachment" + const val FILE_NAME_TAG = "file_name" + const val FILE_ENCODING_TAG = "file_encoding" + const val FILE_DATA_TAG = "file_data" + const val FILE_CONTENT_TYPE_TAG = "file_content_type" + const val RECIPIENT_TAG = "recipient" + const val RECIPIENT_LIST_TAG = "recipient_list" + const val EMAIL_RECIPIENT_STATUS_TAG = "email_recipient_status" + const val EMAIL_GROUP_ID_LIST_TAG = "email_group_id_list" + const val STATUS_CODE_TAG = "status_code" + const val STATUS_TEXT_TAG = "status_text" + const val DELIVERY_STATUS_TAG = "delivery_status" + const val NAME_TAG = "name" + const val DESCRIPTION_TAG = "description" + const val IS_ENABLED_TAG = "is_enabled" + const val FEATURE_LIST_TAG = "feature_list" + const val TITLE_TAG = "title" + const val SEVERITY_TAG = "severity" + const val TAGS_TAG = "tags" + const val URL_TAG = "url" + const val HEADER_PARAMS_TAG = "header_params" + const val HOST_TAG = "host" + const val PORT_TAG = "port" + const val METHOD_TAG = "method" + const val FROM_ADDRESS_TAG = "from_address" + const val UPDATED_TIME_TAG = "last_updated_time_ms" + const val CREATED_TIME_TAG = "created_time_ms" + const val TENANT_TAG = "tenant" + const val CONFIG_LIST_TAG = "config_list" + const val EVENT_LIST_TAG = "event_list" + const val FEATURE_CONFIG_LIST_TAG = "feature_channel_list" + const val DELETE_RESPONSE_LIST_TAG = "delete_response_list" + const val FROM_INDEX_TAG = "from_index" + const val MAX_ITEMS_TAG = "max_items" + const val SORT_FIELD_TAG = "sort_field" + const val SORT_ORDER_TAG = "sort_order" + const val FILTER_PARAM_LIST_TAG = "filter_param_list" + const val STATUS_LIST_TAG = "status_list" + const val START_INDEX_TAG = "start_index" + const val TOTAL_HITS_TAG = "total_hits" + const val TOTAL_HIT_RELATION_TAG = "total_hit_relation" + const val QUERY_TAG = "query" + const val COMPACT_TAG = "compact" + const val CONFIG_TYPE_LIST_TAG = "config_type_list" + const val PLUGIN_FEATURES_TAG = "plugin_features" + + 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 new file mode 100644 index 00000000..e82aaba8 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/NotificationsPluginInterface.kt @@ -0,0 +1,215 @@ +/* + * 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 + +import org.opensearch.action.ActionListener +import org.opensearch.client.node.NodeClient +import org.opensearch.commons.ConfigConstants.OPENSEARCH_SECURITY_USER_INFO_THREAD_CONTEXT +import org.opensearch.commons.notifications.action.CreateNotificationConfigRequest +import org.opensearch.commons.notifications.action.CreateNotificationConfigResponse +import org.opensearch.commons.notifications.action.DeleteNotificationConfigRequest +import org.opensearch.commons.notifications.action.DeleteNotificationConfigResponse +import org.opensearch.commons.notifications.action.GetFeatureChannelListRequest +import org.opensearch.commons.notifications.action.GetFeatureChannelListResponse +import org.opensearch.commons.notifications.action.GetNotificationConfigRequest +import org.opensearch.commons.notifications.action.GetNotificationConfigResponse +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.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.SEND_NOTIFICATION_ACTION_TYPE +import org.opensearch.commons.notifications.action.NotificationsActions.UPDATE_NOTIFICATION_CONFIG_ACTION_TYPE +import org.opensearch.commons.notifications.action.SendNotificationRequest +import org.opensearch.commons.notifications.action.SendNotificationResponse +import org.opensearch.commons.notifications.action.UpdateNotificationConfigRequest +import org.opensearch.commons.notifications.action.UpdateNotificationConfigResponse +import org.opensearch.commons.notifications.model.ChannelMessage +import org.opensearch.commons.notifications.model.EventSource +import org.opensearch.commons.utils.SecureClientWrapper + +/** + * All the transport action plugin interfaces for the Notification plugin + */ +object NotificationsPluginInterface { + + /** + * Create notification configuration. + * @param client Node client for making transport action + * @param request The request object + * @param listener The listener for getting response + */ + fun createNotificationConfig( + client: NodeClient, + request: CreateNotificationConfigRequest, + listener: ActionListener + ) { + client.execute( + CREATE_NOTIFICATION_CONFIG_ACTION_TYPE, + request, + listener + ) + } + + /** + * Update notification configuration. + * @param client Node client for making transport action + * @param request The request object + * @param listener The listener for getting response + */ + fun updateNotificationConfig( + client: NodeClient, + request: UpdateNotificationConfigRequest, + listener: ActionListener + ) { + client.execute( + UPDATE_NOTIFICATION_CONFIG_ACTION_TYPE, + request, + listener + ) + } + + /** + * Delete notification configuration. + * @param client Node client for making transport action + * @param request The request object + * @param listener The listener for getting response + */ + fun deleteNotificationConfig( + client: NodeClient, + request: DeleteNotificationConfigRequest, + listener: ActionListener + ) { + client.execute( + DELETE_NOTIFICATION_CONFIG_ACTION_TYPE, + request, + listener + ) + } + + /** + * Get notification configuration. + * @param client Node client for making transport action + * @param request The request object + * @param listener The listener for getting response + */ + fun getNotificationConfig( + client: NodeClient, + request: GetNotificationConfigRequest, + listener: ActionListener + ) { + client.execute( + GET_NOTIFICATION_CONFIG_ACTION_TYPE, + request, + listener + ) + } + + /** + * Get notification events. + * @param client Node client for making transport action + * @param request The request object + * @param listener The listener for getting response + */ + fun getNotificationEvent( + client: NodeClient, + request: GetNotificationEventRequest, + listener: ActionListener + ) { + client.execute( + GET_NOTIFICATION_EVENT_ACTION_TYPE, + request, + listener + ) + } + + /** + * Get notification plugin features. + * @param client Node client for making transport action + * @param request The request object + * @param listener The listener for getting response + */ + fun getPluginFeatures( + client: NodeClient, + request: GetPluginFeaturesRequest, + listener: ActionListener + ) { + client.execute( + GET_PLUGIN_FEATURES_ACTION_TYPE, + request, + listener + ) + } + + /** + * Get notification channel configuration enabled for a feature. + * @param client Node client for making transport action + * @param request The request object + * @param listener The listener for getting response + */ + fun getFeatureChannelList( + client: NodeClient, + request: GetFeatureChannelListRequest, + listener: ActionListener + ) { + client.execute( + GET_FEATURE_CHANNEL_LIST_ACTION_TYPE, + request, + listener + ) + } + + /** + * Send notification API enabled for a feature. No REST API. Internal API only for Inter plugin communication. + * @param client Node client for making transport action + * @param eventSource The notification event information + * @param channelMessage The notification message + * @param channelIds The list of channel ids to send message to. + * @param listener The listener for getting response + */ + fun sendNotification( + client: NodeClient, + eventSource: EventSource, + channelMessage: ChannelMessage, + channelIds: List, + listener: ActionListener + ) { + val threadContext: String? = + client.threadPool().threadContext.getTransient(OPENSEARCH_SECURITY_USER_INFO_THREAD_CONTEXT) + val wrapper = SecureClientWrapper(client) // Executing request in privileged mode + wrapper.execute( + SEND_NOTIFICATION_ACTION_TYPE, + SendNotificationRequest(eventSource, channelMessage, channelIds, threadContext), + listener + ) + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/BaseResponse.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/BaseResponse.kt new file mode 100644 index 00000000..4bfa2271 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/BaseResponse.kt @@ -0,0 +1,59 @@ +/* + * 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.action + +import org.opensearch.action.ActionResponse +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.xcontent.ToXContentObject +import org.opensearch.rest.RestStatus +import java.io.IOException + +/** + * Base response which give REST status. + */ +abstract class BaseResponse : ActionResponse, ToXContentObject { + + /** + * constructor for creating the class + */ + constructor() + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + constructor(input: StreamInput) : super(input) + + /** + * get rest status for the response. Useful override for multi-status response. + * @return RestStatus for the response + */ + open fun getStatus(): RestStatus { + return RestStatus.OK + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequest.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequest.kt new file mode 100644 index 00000000..e62e92dd --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequest.kt @@ -0,0 +1,144 @@ +/* + * 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.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.common.xcontent.ToXContent +import org.opensearch.common.xcontent.ToXContentObject +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.notifications.NotificationConstants.CONFIG_ID_TAG +import org.opensearch.commons.notifications.NotificationConstants.CONFIG_TAG +import org.opensearch.commons.notifications.model.NotificationConfig +import org.opensearch.commons.utils.fieldIfNotNull +import org.opensearch.commons.utils.logger +import org.opensearch.commons.utils.validateId +import java.io.IOException + +/** + * Action request for creating new configuration. + */ +class CreateNotificationConfigRequest : ActionRequest, ToXContentObject { + val configId: String? + val notificationConfig: NotificationConfig + + companion object { + private val log by logger(CreateNotificationConfigResponse::class.java) + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { CreateNotificationConfigRequest(it) } + + /** + * Creator used in REST communication. + * @param parser XContentParser to deserialize data from. + * @param id optional id to use if missed in XContent + */ + @JvmStatic + @Throws(IOException::class) + fun parse(parser: XContentParser, id: String? = null): CreateNotificationConfigRequest { + var configId: String? = id + var notificationConfig: NotificationConfig? = 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) { + CONFIG_ID_TAG -> configId = parser.textOrNull() + CONFIG_TAG -> notificationConfig = NotificationConfig.parse(parser) + else -> { + parser.skipChildren() + log.info("Unexpected field: $fieldName, while parsing CreateNotificationConfigRequest") + } + } + } + notificationConfig ?: throw IllegalArgumentException("$CONFIG_TAG field absent") + if (configId != null) { + validateId(configId) + } + return CreateNotificationConfigRequest(notificationConfig, configId) + } + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + builder!! + return builder.startObject() + .fieldIfNotNull(CONFIG_ID_TAG, configId) + .field(CONFIG_TAG, notificationConfig) + .endObject() + } + + /** + * constructor for creating the class + * @param notificationConfig the notification config object + * @param configId optional id to use for notification config object + */ + constructor(notificationConfig: NotificationConfig, configId: String? = null) { + this.configId = configId + this.notificationConfig = notificationConfig + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + constructor(input: StreamInput) : super(input) { + configId = input.readOptionalString() + notificationConfig = NotificationConfig.reader.read(input)!! + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + override fun writeTo(output: StreamOutput) { + super.writeTo(output) + output.writeOptionalString(configId) + notificationConfig.writeTo(output) + } + + /** + * {@inheritDoc} + */ + override fun validate(): ActionRequestValidationException? { + return null + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigResponse.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigResponse.kt new file mode 100644 index 00000000..742e7be5 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigResponse.kt @@ -0,0 +1,117 @@ +/* + * 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.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.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.notifications.NotificationConstants.CONFIG_ID_TAG +import org.opensearch.commons.utils.logger +import java.io.IOException + +/** + * Action Response for creating new configuration. + */ +class CreateNotificationConfigResponse : BaseResponse { + val configId: String + + companion object { + private val log by logger(CreateNotificationConfigResponse::class.java) + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { CreateNotificationConfigResponse(it) } + + /** + * Creator used in REST communication. + * @param parser XContentParser to deserialize data from. + */ + @JvmStatic + @Throws(IOException::class) + fun parse(parser: XContentParser): CreateNotificationConfigResponse { + var configId: 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) { + CONFIG_ID_TAG -> configId = parser.text() + else -> { + parser.skipChildren() + log.info("Unexpected field: $fieldName, while parsing CreateNotificationConfigResponse") + } + } + } + configId ?: throw IllegalArgumentException("$CONFIG_ID_TAG field absent") + return CreateNotificationConfigResponse(configId) + } + } + + /** + * constructor for creating the class + * @param configId the id of the created notification configuration + */ + constructor(configId: String) { + this.configId = configId + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + constructor(input: StreamInput) : super(input) { + configId = input.readString() + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + override fun writeTo(output: StreamOutput) { + output.writeString(configId) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + builder!! + return builder.startObject() + .field(CONFIG_ID_TAG, configId) + .endObject() + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigRequest.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigRequest.kt new file mode 100644 index 00000000..3699369f --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigRequest.kt @@ -0,0 +1,134 @@ +/* + * 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.action + +import org.opensearch.action.ActionRequest +import org.opensearch.action.ActionRequestValidationException +import org.opensearch.action.ValidateActions +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.ToXContentObject +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.notifications.NotificationConstants.CONFIG_ID_LIST_TAG +import org.opensearch.commons.utils.logger +import org.opensearch.commons.utils.stringList +import java.io.IOException + +/** + * Action Response for creating new configuration. + */ +class DeleteNotificationConfigRequest : ActionRequest, ToXContentObject { + val configIds: Set + + companion object { + private val log by logger(DeleteNotificationConfigRequest::class.java) + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { DeleteNotificationConfigRequest(it) } + + /** + * Creator used in REST communication. + * @param parser XContentParser to deserialize data from. + */ + @JvmStatic + @Throws(IOException::class) + fun parse(parser: XContentParser): DeleteNotificationConfigRequest { + var configIds: Set? = 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) { + CONFIG_ID_LIST_TAG -> configIds = parser.stringList().toSet() + else -> { + parser.skipChildren() + log.info("Unexpected field: $fieldName, while parsing DeleteNotificationConfigRequest") + } + } + } + configIds ?: throw IllegalArgumentException("$CONFIG_ID_LIST_TAG field absent") + return DeleteNotificationConfigRequest(configIds) + } + } + + /** + * constructor for creating the class + * @param configIds the id of the notification configuration + */ + constructor(configIds: Set) { + this.configIds = configIds + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + constructor(input: StreamInput) : super(input) { + configIds = input.readStringList().toSet() + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + override fun writeTo(output: StreamOutput) { + super.writeTo(output) + output.writeStringCollection(configIds) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + builder!! + return builder.startObject() + .field(CONFIG_ID_LIST_TAG, configIds) + .endObject() + } + + /** + * {@inheritDoc} + */ + override fun validate(): ActionRequestValidationException? { + var validationException: ActionRequestValidationException? = null + if (configIds.isNullOrEmpty()) { + validationException = ValidateActions.addValidationError("configIds is null or empty", validationException) + } + return validationException + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigResponse.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigResponse.kt new file mode 100644 index 00000000..e701daa2 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigResponse.kt @@ -0,0 +1,135 @@ +/* + * 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.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.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.notifications.NotificationConstants.DELETE_RESPONSE_LIST_TAG +import org.opensearch.commons.utils.STRING_READER +import org.opensearch.commons.utils.STRING_WRITER +import org.opensearch.commons.utils.enumReader +import org.opensearch.commons.utils.enumWriter +import org.opensearch.commons.utils.logger +import org.opensearch.rest.RestStatus +import java.io.IOException + +/** + * Action Response for creating new configuration. + */ +class DeleteNotificationConfigResponse : BaseResponse { + val configIdToStatus: Map + + companion object { + private val log by logger(DeleteNotificationConfigResponse::class.java) + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { DeleteNotificationConfigResponse(it) } + + /** + * Creator used in REST communication. + * @param parser XContentParser to deserialize data from. + */ + @JvmStatic + @Throws(IOException::class) + fun parse(parser: XContentParser): DeleteNotificationConfigResponse { + var configIdToStatus: Map? = 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) { + DELETE_RESPONSE_LIST_TAG -> configIdToStatus = convertMapStrings(parser.mapStrings()) + else -> { + parser.skipChildren() + log.info("Unexpected field: $fieldName, while parsing DeleteNotificationConfigResponse") + } + } + } + configIdToStatus ?: throw IllegalArgumentException("$DELETE_RESPONSE_LIST_TAG field absent") + return DeleteNotificationConfigResponse(configIdToStatus) + } + + private fun convertMapStrings(inputMap: Map): Map { + return inputMap.mapValues { RestStatus.valueOf(it.value) } + } + } + + /** + * constructor for creating the class + * @param configIdToStatus the ids of the deleted notification configuration with status + */ + constructor(configIdToStatus: Map) { + this.configIdToStatus = configIdToStatus + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + constructor(input: StreamInput) : super(input) { + configIdToStatus = input.readMap(STRING_READER, enumReader(RestStatus::class.java)) + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + override fun writeTo(output: StreamOutput) { + output.writeMap(configIdToStatus, STRING_WRITER, enumWriter(RestStatus::class.java)) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + builder!! + return builder.startObject() + .field(DELETE_RESPONSE_LIST_TAG, configIdToStatus) + .endObject() + } + + override fun getStatus(): RestStatus { + val distinctStatus = configIdToStatus.values.distinct() + return when { + distinctStatus.size > 1 -> RestStatus.MULTI_STATUS + distinctStatus.size == 1 -> distinctStatus[0] + else -> RestStatus.NOT_MODIFIED + } + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequest.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequest.kt new file mode 100644 index 00000000..17e4f6fb --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequest.kt @@ -0,0 +1,132 @@ +/* + * 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.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.common.xcontent.ToXContent +import org.opensearch.common.xcontent.ToXContentObject +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 + +/** + * This request is plugin-only call. i.e. REST interface is not exposed. + * Also the library will remove the user context while making this call + * so that user making this call need not have to set permission to this API. + * Hence the request also contains tenant info for space isolation. + */ +class GetFeatureChannelListRequest : ActionRequest, ToXContentObject { + val feature: Feature + + companion object { + private val log by logger(GetFeatureChannelListRequest::class.java) + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { GetFeatureChannelListRequest(it) } + + /** + * Creator used in REST communication. + * @param parser XContentParser to deserialize data from. + */ + @JvmStatic + @Throws(IOException::class) + fun parse(parser: XContentParser): GetFeatureChannelListRequest { + var feature: Feature? = 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) { + FEATURE_TAG -> feature = Feature.fromTagOrDefault(parser.text()) + else -> { + parser.skipChildren() + log.info("Unexpected field: $fieldName, while parsing GetFeatureChannelListRequest") + } + } + } + feature ?: throw IllegalArgumentException("$FEATURE_TAG field absent") + return GetFeatureChannelListRequest(feature) + } + } + + /** + * constructor for creating the class + * @param feature the caller plugin feature + */ + constructor(feature: Feature) { + this.feature = feature + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + constructor(input: StreamInput) : super(input) { + feature = input.readEnum(Feature::class.java) + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + override fun writeTo(output: StreamOutput) { + super.writeTo(output) + output.writeEnum(feature) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + builder!! + return builder.startObject() + .field(FEATURE_TAG, feature) + .endObject() + } + + /** + * {@inheritDoc} + */ + override fun validate(): ActionRequestValidationException? { + return null + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListResponse.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListResponse.kt new file mode 100644 index 00000000..455109fc --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListResponse.kt @@ -0,0 +1,92 @@ +/* + * 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.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.common.xcontent.XContentParser +import org.opensearch.commons.notifications.model.FeatureChannelList +import java.io.IOException + +/** + * Action Response for creating new configuration. + */ +class GetFeatureChannelListResponse : BaseResponse { + val searchResult: FeatureChannelList + + companion object { + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { GetFeatureChannelListResponse(it) } + + /** + * Creator used in REST communication. + * @param parser XContentParser to deserialize data from. + */ + @JvmStatic + @Throws(IOException::class) + fun parse(parser: XContentParser): GetFeatureChannelListResponse { + return GetFeatureChannelListResponse(FeatureChannelList(parser)) + } + } + + /** + * constructor for creating the class + * @param searchResult the notification configuration list + */ + constructor(searchResult: FeatureChannelList) { + this.searchResult = searchResult + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + constructor(input: StreamInput) : super(input) { + searchResult = FeatureChannelList(input) + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + override fun writeTo(output: StreamOutput) { + searchResult.writeTo(output) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + return searchResult.toXContent(builder, params) + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigRequest.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigRequest.kt new file mode 100644 index 00000000..a410fe7e --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigRequest.kt @@ -0,0 +1,193 @@ +/* + * 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.action + +import org.opensearch.action.ActionRequest +import org.opensearch.action.ActionRequestValidationException +import org.opensearch.action.ValidateActions +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.ToXContentObject +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.notifications.NotificationConstants.CONFIG_ID_LIST_TAG +import org.opensearch.commons.notifications.NotificationConstants.DEFAULT_MAX_ITEMS +import org.opensearch.commons.notifications.NotificationConstants.FILTER_PARAM_LIST_TAG +import org.opensearch.commons.notifications.NotificationConstants.FROM_INDEX_TAG +import org.opensearch.commons.notifications.NotificationConstants.MAX_ITEMS_TAG +import org.opensearch.commons.notifications.NotificationConstants.SORT_FIELD_TAG +import org.opensearch.commons.notifications.NotificationConstants.SORT_ORDER_TAG +import org.opensearch.commons.utils.STRING_READER +import org.opensearch.commons.utils.STRING_WRITER +import org.opensearch.commons.utils.enumReader +import org.opensearch.commons.utils.fieldIfNotNull +import org.opensearch.commons.utils.logger +import org.opensearch.commons.utils.stringList +import org.opensearch.search.sort.SortOrder +import java.io.IOException + +/** + * Action Request for getting notification configuration. + */ +class GetNotificationConfigRequest : ActionRequest, ToXContentObject { + val configIds: Set + val fromIndex: Int + val maxItems: Int + val sortField: String? + val sortOrder: SortOrder? + val filterParams: Map + + companion object { + private val log by logger(GetNotificationConfigRequest::class.java) + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { GetNotificationConfigRequest(it) } + + /** + * Creator used in REST communication. + * @param parser XContentParser to deserialize data from. + */ + @JvmStatic + @Throws(IOException::class) + fun parse(parser: XContentParser): GetNotificationConfigRequest { + var configIdList: Set = setOf() + var fromIndex = 0 + var maxItems = DEFAULT_MAX_ITEMS + var sortField: String? = null + var sortOrder: SortOrder? = null + var filterParams: Map = mapOf() + + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_OBJECT, + parser.currentToken(), + parser + ) + while (parser.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + CONFIG_ID_LIST_TAG -> configIdList = parser.stringList().toSet() + FROM_INDEX_TAG -> fromIndex = parser.intValue() + MAX_ITEMS_TAG -> maxItems = parser.intValue() + SORT_FIELD_TAG -> sortField = parser.textOrNull() + SORT_ORDER_TAG -> sortOrder = SortOrder.fromString(parser.text()) + FILTER_PARAM_LIST_TAG -> filterParams = parser.mapStrings() + else -> { + parser.skipChildren() + log.info("Unexpected field: $fieldName, while parsing GetNotificationConfigRequest") + } + } + } + return GetNotificationConfigRequest(configIdList, fromIndex, maxItems, sortField, sortOrder, filterParams) + } + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + return builder!!.startObject() + .field(CONFIG_ID_LIST_TAG, configIds) + .field(FROM_INDEX_TAG, fromIndex) + .field(MAX_ITEMS_TAG, maxItems) + .fieldIfNotNull(SORT_FIELD_TAG, sortField) + .fieldIfNotNull(SORT_ORDER_TAG, sortOrder) + .field(FILTER_PARAM_LIST_TAG, filterParams) + .endObject() + } + + /** + * constructor for creating the class + * @param configIds the ids of the notification configuration (other parameters are not relevant if ids are present) + * @param fromIndex the starting index for paginated response + * @param maxItems the maximum number of items to return for paginated response + * @param sortField the sort field if response has many items + * @param sortOrder the sort order if response has many items + * @param filterParams the filter parameters + */ + constructor( + configIds: Set = setOf(), + fromIndex: Int = 0, + maxItems: Int = DEFAULT_MAX_ITEMS, + sortField: String? = null, + sortOrder: SortOrder? = null, + filterParams: Map = mapOf() + ) { + this.configIds = configIds + this.fromIndex = fromIndex + this.maxItems = maxItems + this.sortField = sortField + this.sortOrder = sortOrder + this.filterParams = filterParams + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + constructor(input: StreamInput) : super(input) { + configIds = input.readStringList().toSet() + fromIndex = input.readInt() + maxItems = input.readInt() + sortField = input.readOptionalString() + sortOrder = input.readOptionalWriteable(enumReader(SortOrder::class.java)) + filterParams = input.readMap(STRING_READER, STRING_READER) + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + override fun writeTo(output: StreamOutput) { + super.writeTo(output) + output.writeStringCollection(configIds) + output.writeInt(fromIndex) + output.writeInt(maxItems) + output.writeOptionalString(sortField) + output.writeOptionalWriteable(sortOrder) + output.writeMap(filterParams, STRING_WRITER, STRING_WRITER) + } + + /** + * {@inheritDoc} + */ + override fun validate(): ActionRequestValidationException? { + var validationException: ActionRequestValidationException? = null + if (fromIndex < 0) { + validationException = ValidateActions.addValidationError("fromIndex is -ve", validationException) + } + if (maxItems <= 0) { + validationException = ValidateActions.addValidationError("maxItems is not +ve", validationException) + } + return validationException + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigResponse.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigResponse.kt new file mode 100644 index 00000000..a810759b --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigResponse.kt @@ -0,0 +1,92 @@ +/* + * 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.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.common.xcontent.XContentParser +import org.opensearch.commons.notifications.model.NotificationConfigSearchResult +import java.io.IOException + +/** + * Action Response for getting notification configuration. + */ +class GetNotificationConfigResponse : BaseResponse { + val searchResult: NotificationConfigSearchResult + + companion object { + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { GetNotificationConfigResponse(it) } + + /** + * Creator used in REST communication. + * @param parser XContentParser to deserialize data from. + */ + @JvmStatic + @Throws(IOException::class) + fun parse(parser: XContentParser): GetNotificationConfigResponse { + return GetNotificationConfigResponse(NotificationConfigSearchResult(parser)) + } + } + + /** + * constructor for creating the class + * @param searchResult the notification configuration list + */ + constructor(searchResult: NotificationConfigSearchResult) { + this.searchResult = searchResult + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + constructor(input: StreamInput) : super(input) { + searchResult = NotificationConfigSearchResult(input) + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + override fun writeTo(output: StreamOutput) { + searchResult.writeTo(output) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + return searchResult.toXContent(builder, params) + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventRequest.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventRequest.kt new file mode 100644 index 00000000..27e20554 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventRequest.kt @@ -0,0 +1,193 @@ +/* + * 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.action + +import org.opensearch.action.ActionRequest +import org.opensearch.action.ActionRequestValidationException +import org.opensearch.action.ValidateActions +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.ToXContentObject +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.notifications.NotificationConstants.DEFAULT_MAX_ITEMS +import org.opensearch.commons.notifications.NotificationConstants.EVENT_ID_LIST_TAG +import org.opensearch.commons.notifications.NotificationConstants.FILTER_PARAM_LIST_TAG +import org.opensearch.commons.notifications.NotificationConstants.FROM_INDEX_TAG +import org.opensearch.commons.notifications.NotificationConstants.MAX_ITEMS_TAG +import org.opensearch.commons.notifications.NotificationConstants.SORT_FIELD_TAG +import org.opensearch.commons.notifications.NotificationConstants.SORT_ORDER_TAG +import org.opensearch.commons.utils.STRING_READER +import org.opensearch.commons.utils.STRING_WRITER +import org.opensearch.commons.utils.enumReader +import org.opensearch.commons.utils.fieldIfNotNull +import org.opensearch.commons.utils.logger +import org.opensearch.commons.utils.stringList +import org.opensearch.search.sort.SortOrder +import java.io.IOException + +/** + * Action Request for getting notification event. + */ +class GetNotificationEventRequest : ActionRequest, ToXContentObject { + val eventIds: Set + val fromIndex: Int + val maxItems: Int + val sortField: String? + val sortOrder: SortOrder? + val filterParams: Map + + companion object { + private val log by logger(GetNotificationEventRequest::class.java) + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { GetNotificationEventRequest(it) } + + /** + * Creator used in REST communication. + * @param parser XContentParser to deserialize data from. + */ + @JvmStatic + @Throws(IOException::class) + fun parse(parser: XContentParser): GetNotificationEventRequest { + var eventIds: Set = setOf() + var fromIndex = 0 + var maxItems = DEFAULT_MAX_ITEMS + var sortField: String? = null + var sortOrder: SortOrder? = null + var filterParams: Map = mapOf() + + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_OBJECT, + parser.currentToken(), + parser + ) + while (parser.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + EVENT_ID_LIST_TAG -> eventIds = parser.stringList().toSet() + FROM_INDEX_TAG -> fromIndex = parser.intValue() + MAX_ITEMS_TAG -> maxItems = parser.intValue() + SORT_FIELD_TAG -> sortField = parser.textOrNull() + SORT_ORDER_TAG -> sortOrder = SortOrder.fromString(parser.text()) + FILTER_PARAM_LIST_TAG -> filterParams = parser.mapStrings() + else -> { + parser.skipChildren() + log.info("Unexpected field: $fieldName, while parsing GetNotificationEventRequest") + } + } + } + return GetNotificationEventRequest(eventIds, fromIndex, maxItems, sortField, sortOrder, filterParams) + } + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + return builder!!.startObject() + .field(EVENT_ID_LIST_TAG, eventIds) + .field(FROM_INDEX_TAG, fromIndex) + .field(MAX_ITEMS_TAG, maxItems) + .fieldIfNotNull(SORT_FIELD_TAG, sortField) + .fieldIfNotNull(SORT_ORDER_TAG, sortOrder) + .field(FILTER_PARAM_LIST_TAG, filterParams) + .endObject() + } + + /** + * constructor for creating the class + * @param eventIds the ids of the notification events (other parameters are not relevant if ids are present) + * @param fromIndex the starting index for paginated response + * @param maxItems the maximum number of items to return for paginated response + * @param sortField the sort field if response has many items + * @param sortOrder the sort order if response has many items + * @param filterParams the filter parameters + */ + constructor( + eventIds: Set = setOf(), + fromIndex: Int = 0, + maxItems: Int = DEFAULT_MAX_ITEMS, + sortField: String? = null, + sortOrder: SortOrder? = null, + filterParams: Map = mapOf() + ) { + this.eventIds = eventIds + this.fromIndex = fromIndex + this.maxItems = maxItems + this.sortField = sortField + this.sortOrder = sortOrder + this.filterParams = filterParams + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + constructor(input: StreamInput) : super(input) { + eventIds = input.readStringList().toSet() + fromIndex = input.readInt() + maxItems = input.readInt() + sortField = input.readOptionalString() + sortOrder = input.readOptionalWriteable(enumReader(SortOrder::class.java)) + filterParams = input.readMap(STRING_READER, STRING_READER) + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + override fun writeTo(output: StreamOutput) { + super.writeTo(output) + output.writeStringCollection(eventIds) + output.writeInt(fromIndex) + output.writeInt(maxItems) + output.writeOptionalString(sortField) + output.writeOptionalWriteable(sortOrder) + output.writeMap(filterParams, STRING_WRITER, STRING_WRITER) + } + + /** + * {@inheritDoc} + */ + override fun validate(): ActionRequestValidationException? { + var validationException: ActionRequestValidationException? = null + if (fromIndex < 0) { + validationException = ValidateActions.addValidationError("fromIndex is -ve", validationException) + } + if (maxItems <= 0) { + validationException = ValidateActions.addValidationError("maxItems is not +ve", validationException) + } + return validationException + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventResponse.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventResponse.kt new file mode 100644 index 00000000..176d95ea --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventResponse.kt @@ -0,0 +1,92 @@ +/* + * 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.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.common.xcontent.XContentParser +import org.opensearch.commons.notifications.model.NotificationEventSearchResult +import java.io.IOException + +/** + * Action Response for getting notification event. + */ +class GetNotificationEventResponse : BaseResponse { + val searchResult: NotificationEventSearchResult + + companion object { + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { GetNotificationEventResponse(it) } + + /** + * Creator used in REST communication. + * @param parser XContentParser to deserialize data from. + */ + @JvmStatic + @Throws(IOException::class) + fun parse(parser: XContentParser): GetNotificationEventResponse { + return GetNotificationEventResponse(NotificationEventSearchResult(parser)) + } + } + + /** + * constructor for creating the class + * @param searchResult the notification event list + */ + constructor(searchResult: NotificationEventSearchResult) { + this.searchResult = searchResult + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + constructor(input: StreamInput) : super(input) { + searchResult = NotificationEventSearchResult(input) + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + override fun writeTo(output: StreamOutput) { + searchResult.writeTo(output) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + return searchResult.toXContent(builder, params) + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesRequest.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesRequest.kt new file mode 100644 index 00000000..8b9b81b4 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesRequest.kt @@ -0,0 +1,126 @@ +/* + * 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.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.common.xcontent.ToXContent +import org.opensearch.common.xcontent.ToXContentObject +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.notifications.NotificationConstants.COMPACT_TAG +import org.opensearch.commons.utils.logger +import java.io.IOException + +/** + * Action Request for getting notification plugin features. + */ +class GetPluginFeaturesRequest : ActionRequest, ToXContentObject { + val compact: Boolean // Dummy request parameter for transport request + + companion object { + private val log by logger(GetPluginFeaturesRequest::class.java) + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { GetPluginFeaturesRequest(it) } + + /** + * Creator used in REST communication. + * @param parser XContentParser to deserialize data from. + */ + @JvmStatic + @Throws(IOException::class) + fun parse(parser: XContentParser): GetPluginFeaturesRequest { + var compact = false + + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_OBJECT, + parser.currentToken(), + parser + ) + while (parser.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + COMPACT_TAG -> compact = parser.booleanValue() + else -> { + parser.skipChildren() + log.info("Unexpected field: $fieldName, while parsing GetPluginFeaturesRequest") + } + } + } + return GetPluginFeaturesRequest(compact) + } + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + return builder!!.startObject() + .field(COMPACT_TAG, compact) + .endObject() + } + + /** + * constructor for creating the class + * @param compact Dummy request parameter for transport request + */ + constructor(compact: Boolean = false) { + this.compact = compact + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + constructor(input: StreamInput) : super(input) { + compact = input.readBoolean() + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + override fun writeTo(output: StreamOutput) { + super.writeTo(output) + output.writeBoolean(compact) + } + + /** + * {@inheritDoc} + */ + override fun validate(): ActionRequestValidationException? { + return null + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesResponse.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesResponse.kt new file mode 100644 index 00000000..681b943e --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesResponse.kt @@ -0,0 +1,129 @@ +/* + * 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.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.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.notifications.NotificationConstants.CONFIG_TYPE_LIST_TAG +import org.opensearch.commons.notifications.NotificationConstants.PLUGIN_FEATURES_TAG +import org.opensearch.commons.utils.STRING_READER +import org.opensearch.commons.utils.STRING_WRITER +import org.opensearch.commons.utils.logger +import org.opensearch.commons.utils.stringList +import java.io.IOException + +/** + * Action Response for getting notification plugin features. + */ +class GetPluginFeaturesResponse : BaseResponse { + val configTypeList: List + val pluginFeatures: Map + + companion object { + private val log by logger(GetPluginFeaturesResponse::class.java) + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { GetPluginFeaturesResponse(it) } + + /** + * Creator used in REST communication. + * @param parser XContentParser to deserialize data from. + */ + @JvmStatic + @Throws(IOException::class) + fun parse(parser: XContentParser): GetPluginFeaturesResponse { + var configTypeList: List? = null + var pluginFeatures: Map? = 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) { + CONFIG_TYPE_LIST_TAG -> configTypeList = parser.stringList() + PLUGIN_FEATURES_TAG -> pluginFeatures = parser.mapStrings() + else -> { + parser.skipChildren() + log.info("Unexpected field: $fieldName, while parsing DeleteNotificationConfigResponse") + } + } + } + configTypeList ?: throw IllegalArgumentException("$CONFIG_TYPE_LIST_TAG field absent") + pluginFeatures ?: throw IllegalArgumentException("$PLUGIN_FEATURES_TAG field absent") + return GetPluginFeaturesResponse(configTypeList, pluginFeatures) + } + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + return builder!!.startObject() + .field(CONFIG_TYPE_LIST_TAG, configTypeList) + .field(PLUGIN_FEATURES_TAG, pluginFeatures) + .endObject() + } + + /** + * constructor for creating the class + * @param configTypeList the list of config types supported by plugin + * @param pluginFeatures the map of plugin features supported to its value + */ + constructor(configTypeList: List, pluginFeatures: Map) { + this.configTypeList = configTypeList + this.pluginFeatures = pluginFeatures + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + constructor(input: StreamInput) : super(input) { + configTypeList = input.readStringList() + pluginFeatures = input.readMap(STRING_READER, STRING_READER) + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + override fun writeTo(output: StreamOutput) { + output.writeStringCollection(configTypeList) + output.writeMap(pluginFeatures, STRING_WRITER, STRING_WRITER) + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/NotificationsActions.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/NotificationsActions.kt new file mode 100644 index 00000000..fe7457d0 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/NotificationsActions.kt @@ -0,0 +1,122 @@ +/* + * 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.action + +import org.opensearch.action.ActionType + +/** + * All the transport action information for the Notification plugin + */ +object NotificationsActions { + /** + * Create notification configuration transport action name. + */ + const val CREATE_NOTIFICATION_CONFIG_NAME = "cluster:admin/opensearch/notifications/configs/create" + + /** + * Update notification configuration transport action name. + */ + const val UPDATE_NOTIFICATION_CONFIG_NAME = "cluster:admin/opensearch/notifications/configs/update" + + /** + * Delete notification configuration transport action name. + */ + const val DELETE_NOTIFICATION_CONFIG_NAME = "cluster:admin/opensearch/notifications/configs/delete" + + /** + * Get notification configuration transport action name. + */ + const val GET_NOTIFICATION_CONFIG_NAME = "cluster:admin/opensearch/notifications/configs/get" + + /** + * Get notification events transport action name. + */ + const val GET_NOTIFICATION_EVENT_NAME = "cluster:admin/opensearch/notifications/events/get" + + /** + * Get notification plugin features transport action name. + */ + const val GET_PLUGIN_FEATURES_NAME = "cluster:admin/opensearch/notifications/features" + + /** + * Get Config List for feature. Internal only - Inter plugin communication. + */ + const val GET_FEATURE_CHANNEL_LIST_NAME = "cluster:admin/opensearch/notifications/feature/channels/get" + + /** + * Send notification message. Internal only - Inter plugin communication. + */ + const val SEND_NOTIFICATION_NAME = "cluster:admin/opensearch/notifications/feature/send" + + /** + * Create notification configuration transport action type. + */ + val CREATE_NOTIFICATION_CONFIG_ACTION_TYPE = + ActionType(CREATE_NOTIFICATION_CONFIG_NAME, ::CreateNotificationConfigResponse) + + /** + * Update notification configuration transport action type. + */ + val UPDATE_NOTIFICATION_CONFIG_ACTION_TYPE = + ActionType(UPDATE_NOTIFICATION_CONFIG_NAME, ::UpdateNotificationConfigResponse) + + /** + * Delete notification configuration transport action type. + */ + val DELETE_NOTIFICATION_CONFIG_ACTION_TYPE = + ActionType(DELETE_NOTIFICATION_CONFIG_NAME, ::DeleteNotificationConfigResponse) + + /** + * Get notification configuration transport action type. + */ + val GET_NOTIFICATION_CONFIG_ACTION_TYPE = + ActionType(GET_NOTIFICATION_CONFIG_NAME, ::GetNotificationConfigResponse) + + /** + * Get notification events transport action type. + */ + val GET_NOTIFICATION_EVENT_ACTION_TYPE = + ActionType(GET_NOTIFICATION_EVENT_NAME, ::GetNotificationEventResponse) + + /** + * Get notification plugin features transport action type. + */ + val GET_PLUGIN_FEATURES_ACTION_TYPE = + ActionType(GET_PLUGIN_FEATURES_NAME, ::GetPluginFeaturesResponse) + + /** + * Get Config List for feature transport action type. + */ + val GET_FEATURE_CHANNEL_LIST_ACTION_TYPE = + ActionType(GET_FEATURE_CHANNEL_LIST_NAME, ::GetFeatureChannelListResponse) + + /** + * Send notification transport action type. Internal only - Inter plugin communication. + */ + val SEND_NOTIFICATION_ACTION_TYPE = + ActionType(SEND_NOTIFICATION_NAME, ::SendNotificationResponse) +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/SendNotificationRequest.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/SendNotificationRequest.kt new file mode 100644 index 00000000..9943ed57 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/SendNotificationRequest.kt @@ -0,0 +1,171 @@ +/* + * 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.action + +import org.opensearch.action.ActionRequest +import org.opensearch.action.ActionRequestValidationException +import org.opensearch.action.ValidateActions +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.ToXContentObject +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.notifications.NotificationConstants.CHANNEL_ID_LIST_TAG +import org.opensearch.commons.notifications.NotificationConstants.CHANNEL_MESSAGE_TAG +import org.opensearch.commons.notifications.NotificationConstants.EVENT_SOURCE_TAG +import org.opensearch.commons.notifications.NotificationConstants.THREAD_CONTEXT_TAG +import org.opensearch.commons.notifications.model.ChannelMessage +import org.opensearch.commons.notifications.model.EventSource +import org.opensearch.commons.utils.fieldIfNotNull +import org.opensearch.commons.utils.logger +import org.opensearch.commons.utils.stringList +import java.io.IOException + +/** + * Action Request to send notification. + */ +class SendNotificationRequest : ActionRequest, ToXContentObject { + val eventSource: EventSource + val channelMessage: ChannelMessage + val channelIds: List + val threadContext: String? + + companion object { + private val log by logger(SendNotificationRequest::class.java) + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { SendNotificationRequest(it) } + + /** + * Creator used in REST communication. + * @param parser XContentParser to deserialize data from. + */ + @JvmStatic + @Throws(IOException::class) + fun parse(parser: XContentParser): SendNotificationRequest { + var eventSource: EventSource? = null + var channelMessage: ChannelMessage? = null + var channelIds: List? = null + var threadContext: 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) { + EVENT_SOURCE_TAG -> eventSource = EventSource.parse(parser) + CHANNEL_MESSAGE_TAG -> channelMessage = ChannelMessage.parse(parser) + CHANNEL_ID_LIST_TAG -> channelIds = parser.stringList() + THREAD_CONTEXT_TAG -> threadContext = parser.textOrNull() + else -> { + parser.skipChildren() + log.info("Unexpected field: $fieldName, while parsing SendNotificationRequest") + } + } + } + eventSource ?: throw IllegalArgumentException("$EVENT_SOURCE_TAG field absent") + channelMessage ?: throw IllegalArgumentException("$CHANNEL_MESSAGE_TAG field absent") + channelIds ?: throw IllegalArgumentException("$CHANNEL_ID_LIST_TAG field absent") + return SendNotificationRequest(eventSource, channelMessage, channelIds, threadContext) + } + } + + /** + * constructor for creating the class + * @param eventSource the notification info + * @param channelMessage the message to be sent to channel + * @param channelIds the ids of the notification configuration channel + * @param threadContext the user info thread context + */ + constructor( + eventSource: EventSource, + channelMessage: ChannelMessage, + channelIds: List, + threadContext: String? + ) { + this.eventSource = eventSource + this.channelMessage = channelMessage + this.channelIds = channelIds + this.threadContext = threadContext + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + constructor(input: StreamInput) : super(input) { + eventSource = EventSource.reader.read(input) + channelMessage = ChannelMessage.reader.read(input) + channelIds = input.readStringList() + threadContext = input.readOptionalString() + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + override fun writeTo(output: StreamOutput) { + super.writeTo(output) + eventSource.writeTo(output) + channelMessage.writeTo(output) + output.writeStringCollection(channelIds) + output.writeOptionalString(threadContext) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + builder!! + return builder.startObject() + .field(EVENT_SOURCE_TAG, eventSource) + .field(CHANNEL_MESSAGE_TAG, channelMessage) + .field(CHANNEL_ID_LIST_TAG, channelIds) + .fieldIfNotNull(THREAD_CONTEXT_TAG, threadContext) + .endObject() + } + + /** + * {@inheritDoc} + */ + override fun validate(): ActionRequestValidationException? { + var validationException: ActionRequestValidationException? = null + if (channelIds.isEmpty()) { + validationException = ValidateActions.addValidationError("channelIds is empty", validationException) + } + return validationException + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/SendNotificationResponse.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/SendNotificationResponse.kt new file mode 100644 index 00000000..38338fc5 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/SendNotificationResponse.kt @@ -0,0 +1,117 @@ +/* + * 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.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.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.notifications.NotificationConstants.EVENT_ID_TAG +import org.opensearch.commons.utils.logger +import java.io.IOException + +/** + * Action Response for send notification. + */ +class SendNotificationResponse : BaseResponse { + val notificationId: String + + companion object { + private val log by logger(SendNotificationResponse::class.java) + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { SendNotificationResponse(it) } + + /** + * Creator used in REST communication. + * @param parser XContentParser to deserialize data from. + */ + @JvmStatic + @Throws(IOException::class) + fun parse(parser: XContentParser): SendNotificationResponse { + var notificationId: 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) { + EVENT_ID_TAG -> notificationId = parser.text() + else -> { + parser.skipChildren() + log.info("Unexpected field: $fieldName, while parsing SendNotificationResponse") + } + } + } + notificationId ?: throw IllegalArgumentException("$EVENT_ID_TAG field absent") + return SendNotificationResponse(notificationId) + } + } + + /** + * constructor for creating the class + * @param configId the id of the created notification configuration + */ + constructor(configId: String) { + this.notificationId = configId + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + constructor(input: StreamInput) : super(input) { + notificationId = input.readString() + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + override fun writeTo(output: StreamOutput) { + output.writeString(notificationId) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + builder!! + return builder.startObject() + .field(EVENT_ID_TAG, notificationId) + .endObject() + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequest.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequest.kt new file mode 100644 index 00000000..dcba7b71 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequest.kt @@ -0,0 +1,146 @@ +/* + * 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.action + +import org.opensearch.action.ActionRequest +import org.opensearch.action.ActionRequestValidationException +import org.opensearch.action.ValidateActions +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.ToXContentObject +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.notifications.NotificationConstants.CONFIG_ID_TAG +import org.opensearch.commons.notifications.NotificationConstants.CONFIG_TAG +import org.opensearch.commons.notifications.model.NotificationConfig +import org.opensearch.commons.utils.logger +import java.io.IOException + +/** + * Action request for updating notification configuration. + */ +class UpdateNotificationConfigRequest : ActionRequest, ToXContentObject { + val configId: String + val notificationConfig: NotificationConfig + + companion object { + private val log by logger(UpdateNotificationConfigRequest::class.java) + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { UpdateNotificationConfigRequest(it) } + + /** + * Creator used in REST communication. + * @param parser XContentParser to deserialize data from. + * @param id optional id to use if missed in XContent + */ + @JvmStatic + @Throws(IOException::class) + fun parse(parser: XContentParser, id: String? = null): UpdateNotificationConfigRequest { + var configId: String? = id + var notificationConfig: NotificationConfig? = 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) { + CONFIG_ID_TAG -> configId = parser.text() + CONFIG_TAG -> notificationConfig = NotificationConfig.parse(parser) + else -> { + parser.skipChildren() + log.info("Unexpected field: $fieldName, while parsing UpdateNotificationConfigRequest") + } + } + } + configId ?: throw IllegalArgumentException("$CONFIG_ID_TAG field absent") + notificationConfig ?: throw IllegalArgumentException("$CONFIG_TAG field absent") + return UpdateNotificationConfigRequest(configId, notificationConfig) + } + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + builder!! + return builder.startObject() + .field(CONFIG_ID_TAG, configId) + .field(CONFIG_TAG, notificationConfig) + .endObject() + } + + /** + * constructor for creating the class + * @param configId the id notification config object + * @param notificationConfig the notification config object + */ + constructor(configId: String, notificationConfig: NotificationConfig) { + this.configId = configId + this.notificationConfig = notificationConfig + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + constructor(input: StreamInput) : super(input) { + configId = input.readString() + notificationConfig = NotificationConfig.reader.read(input)!! + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + override fun writeTo(output: StreamOutput) { + super.writeTo(output) + output.writeString(configId) + notificationConfig.writeTo(output) + } + + /** + * {@inheritDoc} + */ + override fun validate(): ActionRequestValidationException? { + var validationException: ActionRequestValidationException? = null + if (Strings.isNullOrEmpty(configId)) { + validationException = ValidateActions.addValidationError("configId is null or empty", validationException) + } + return validationException + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigResponse.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigResponse.kt new file mode 100644 index 00000000..0e15a69b --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigResponse.kt @@ -0,0 +1,117 @@ +/* + * 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.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.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.notifications.NotificationConstants.CONFIG_ID_TAG +import org.opensearch.commons.utils.logger +import java.io.IOException + +/** + * Action Response for creating new configuration. + */ +class UpdateNotificationConfigResponse : BaseResponse { + val configId: String + + companion object { + private val log by logger(UpdateNotificationConfigResponse::class.java) + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { UpdateNotificationConfigResponse(it) } + + /** + * Creator used in REST communication. + * @param parser XContentParser to deserialize data from. + */ + @JvmStatic + @Throws(IOException::class) + fun parse(parser: XContentParser): UpdateNotificationConfigResponse { + var configId: 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) { + CONFIG_ID_TAG -> configId = parser.text() + else -> { + parser.skipChildren() + log.info("Unexpected field: $fieldName, while parsing UpdateNotificationConfigResponse") + } + } + } + configId ?: throw IllegalArgumentException("$CONFIG_ID_TAG field absent") + return UpdateNotificationConfigResponse(configId) + } + } + + /** + * constructor for creating the class + * @param configId the id of the updated notification configuration + */ + constructor(configId: String) { + this.configId = configId + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + constructor(input: StreamInput) : super(input) { + configId = input.readString() + } + + /** + * {@inheritDoc} + */ + @Throws(IOException::class) + override fun writeTo(output: StreamOutput) { + output.writeString(configId) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + builder!! + return builder.startObject() + .field(CONFIG_ID_TAG, configId) + .endObject() + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/Attachment.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Attachment.kt new file mode 100644 index 00000000..282781f0 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Attachment.kt @@ -0,0 +1,126 @@ +/* + * 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.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.FILE_CONTENT_TYPE_TAG +import org.opensearch.commons.notifications.NotificationConstants.FILE_DATA_TAG +import org.opensearch.commons.notifications.NotificationConstants.FILE_ENCODING_TAG +import org.opensearch.commons.notifications.NotificationConstants.FILE_NAME_TAG +import org.opensearch.commons.utils.fieldIfNotNull +import org.opensearch.commons.utils.logger + +/** + * Data class for storing attachment of channel message. + */ +data class Attachment( + val fileName: String, + val fileEncoding: String, + val fileData: String, + val fileContentType: String? +) : BaseModel { + companion object { + private val log by logger(Attachment::class.java) + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { Attachment(it) } + + /** + * Parse the data from parser and create Attachment object + * @param parser data referenced at parser + * @return created Attachment object + */ + fun parse(parser: XContentParser): Attachment { + var fileName: String? = null + var fileEncoding: String? = null + var fileData: String? = null + var fileContentType: String? = null + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_OBJECT, + parser.currentToken(), + parser + ) + while (parser.nextToken() != XContentParser.Token.END_OBJECT) { + val dataType = parser.currentName() + parser.nextToken() + when (dataType) { + FILE_NAME_TAG -> fileName = parser.text() + FILE_ENCODING_TAG -> fileEncoding = parser.text() + FILE_DATA_TAG -> fileData = parser.text() + FILE_CONTENT_TYPE_TAG -> fileContentType = parser.textOrNull() + else -> { + parser.skipChildren() + log.info("Skipping Unknown field $dataType") + } + } + } + fileName ?: throw IllegalArgumentException("attachment:fileName not present") + fileEncoding ?: throw IllegalArgumentException("attachment:fileEncoding not present") + fileData ?: throw IllegalArgumentException("attachment:fileData not present") + return Attachment(fileName, fileEncoding, fileData, fileContentType) + } + } + + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + return builder!!.startObject() + .field(FILE_NAME_TAG, fileName) + .field(FILE_ENCODING_TAG, fileEncoding) + .field(FILE_DATA_TAG, fileData) + .fieldIfNotNull(FILE_CONTENT_TYPE_TAG, fileContentType) + .endObject() + } + + /** + * Constructor used in transport action communication. + * @param input StreamInput stream to deserialize data from. + */ + constructor(input: StreamInput) : this( + fileName = input.readString(), + fileEncoding = input.readString(), + fileData = input.readString(), + fileContentType = input.readOptionalString() + ) + + /** + * {@inheritDoc} + */ + override fun writeTo(output: StreamOutput) { + output.writeString(fileName) + output.writeString(fileEncoding) + output.writeString(fileData) + output.writeOptionalString(fileContentType) + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/BaseConfigData.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/BaseConfigData.kt new file mode 100644 index 00000000..a766d3e0 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/BaseConfigData.kt @@ -0,0 +1,33 @@ +/* + * 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 + +/** + * Marker interface for Channel Data + */ +interface BaseConfigData : BaseModel diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/BaseModel.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/BaseModel.kt new file mode 100644 index 00000000..bdc13931 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/BaseModel.kt @@ -0,0 +1,35 @@ +/* + * 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.common.io.stream.Writeable +import org.opensearch.common.xcontent.ToXContent + +/** + * interface for representing objects. + */ +interface BaseModel : Writeable, ToXContent diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/ChannelMessage.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/ChannelMessage.kt new file mode 100644 index 00000000..5f70efa7 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/ChannelMessage.kt @@ -0,0 +1,137 @@ +/* + * 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.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.ATTACHMENT_TAG +import org.opensearch.commons.notifications.NotificationConstants.HTML_DESCRIPTION_TAG +import org.opensearch.commons.notifications.NotificationConstants.TEXT_DESCRIPTION_TAG +import org.opensearch.commons.utils.fieldIfNotNull +import org.opensearch.commons.utils.logger +import java.io.IOException + +/** + * Data class for storing channel message. + */ +data class ChannelMessage( + val textDescription: String, + val htmlDescription: String?, + val attachment: Attachment? +) : BaseModel { + + init { + require(!Strings.isNullOrEmpty(textDescription)) { "text message part is null or empty" } + } + + companion object { + private val log by logger(ChannelMessage::class.java) + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { ChannelMessage(it) } + + /** + * Creator used in REST communication. + * @param parser XContentParser to deserialize data from. + */ + @JvmStatic + @Throws(IOException::class) + fun parse(parser: XContentParser): ChannelMessage { + + var textDescription: String? = null + var htmlDescription: String? = null + var attachment: Attachment? = 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) { + TEXT_DESCRIPTION_TAG -> textDescription = parser.text() + HTML_DESCRIPTION_TAG -> htmlDescription = parser.textOrNull() + ATTACHMENT_TAG -> attachment = Attachment.parse(parser) + else -> { + parser.skipChildren() + log.info("Skipping Unknown field $fieldName") + } + } + } + + textDescription ?: throw IllegalArgumentException("$TEXT_DESCRIPTION_TAG not present") + return ChannelMessage( + textDescription, + htmlDescription, + attachment + ) + } + } + + /** + * Constructor used in transport action communication. + * @param input StreamInput stream to deserialize data from. + */ + constructor(input: StreamInput) : this( + textDescription = input.readString(), + htmlDescription = input.readOptionalString(), + attachment = input.readOptionalWriteable(Attachment.reader) + ) + + /** + * {@inheritDoc} + */ + override fun writeTo(output: StreamOutput) { + output.writeString(textDescription) + output.writeOptionalString(htmlDescription) + output.writeOptionalWriteable(attachment) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + builder!! + return builder.startObject() + .field(TEXT_DESCRIPTION_TAG, textDescription) + .fieldIfNotNull(HTML_DESCRIPTION_TAG, htmlDescription) + .fieldIfNotNull(ATTACHMENT_TAG, attachment) + .endObject() + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/Chime.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Chime.kt new file mode 100644 index 00000000..2ffb0fd0 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Chime.kt @@ -0,0 +1,121 @@ +/* + * 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.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.URL_TAG +import org.opensearch.commons.utils.logger +import org.opensearch.commons.utils.validateUrl +import java.io.IOException + +/** + * Data class representing Chime channel. + */ +data class Chime( + val url: String +) : BaseConfigData { + + init { + require(!Strings.isNullOrEmpty(url)) { "URL is null or empty" } + validateUrl(url) + } + + companion object { + private val log by logger(Chime::class.java) + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { Chime(it) } + + /** + * Parser to parse xContent + */ + val xParser = XParser { parse(it) } + + /** + * Creator used in REST communication. + * @param parser XContentParser to deserialize data from. + */ + @JvmStatic + @Throws(IOException::class) + fun parse(parser: XContentParser): Chime { + var url: 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) { + URL_TAG -> url = parser.text() + else -> { + parser.skipChildren() + log.info("Unexpected field: $fieldName, while parsing Chime destination") + } + } + } + url ?: throw IllegalArgumentException("$URL_TAG field absent") + return Chime(url) + } + } + + /** + * Constructor used in transport action communication. + * @param input StreamInput stream to deserialize data from. + */ + constructor(input: StreamInput) : this( + url = input.readString() + ) + + /** + * {@inheritDoc} + */ + override fun writeTo(output: StreamOutput) { + output.writeString(url) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + builder!! + return builder.startObject() + .field(URL_TAG, url) + .endObject() + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/ConfigType.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/ConfigType.kt new file mode 100644 index 00000000..0dbe70a6 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/ConfigType.kt @@ -0,0 +1,85 @@ +/* + * 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 + +/** + * Enum for Notification config type + */ +enum class ConfigType(val tag: String) { + NONE("none") { + override fun toString(): String { + return tag + } + }, + SLACK("slack") { + override fun toString(): String { + return tag + } + }, + CHIME("chime") { + override fun toString(): String { + return tag + } + }, + WEBHOOK("webhook") { + override fun toString(): String { + return tag + } + }, + EMAIL("email") { + override fun toString(): String { + return tag + } + }, + SMTP_ACCOUNT("smtp_account") { + override fun toString(): String { + return tag + } + }, + EMAIL_GROUP("email_group") { + override fun toString(): String { + return tag + } + }; + + companion object { + private val tagMap = values().associateBy { it.tag } + + val enumParser = EnumParser { fromTagOrDefault(it) } + + /** + * Get ConfigType from tag or NONE if not found + * @param tag the tag + * @return ConfigType corresponding to tag. NONE if invalid tag. + */ + fun fromTagOrDefault(tag: String): ConfigType { + return tagMap[tag] ?: NONE + } + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/DeliveryStatus.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/DeliveryStatus.kt new file mode 100644 index 00000000..e824242b --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/DeliveryStatus.kt @@ -0,0 +1,126 @@ +/* + * 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.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.STATUS_CODE_TAG +import org.opensearch.commons.notifications.NotificationConstants.STATUS_TEXT_TAG +import org.opensearch.commons.utils.logger +import java.io.IOException + +/** + * Data class representing Delivery Status. + */ +data class DeliveryStatus( + val statusCode: String, + val statusText: String +) : BaseModel { + + init { + require(!Strings.isNullOrEmpty(statusCode)) { "StatusCode is null or empty" } + require(!Strings.isNullOrEmpty(statusText)) { "statusText is null or empty" } + } + + companion object { + private val log by logger(DeliveryStatus::class.java) + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { DeliveryStatus(it) } + + /** + * Creator used in REST communication. + * @param parser XContentParser to deserialize data from. + */ + @JvmStatic + @Throws(IOException::class) + fun parse(parser: XContentParser): DeliveryStatus { + var statusCode: String? = null + var statusText: 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) { + STATUS_CODE_TAG -> statusCode = parser.text() + STATUS_TEXT_TAG -> statusText = parser.text() + else -> { + parser.skipChildren() + log.info("Unexpected field: $fieldName, while parsing deliveryStatus") + } + } + } + statusCode ?: throw IllegalArgumentException("$STATUS_CODE_TAG field absent") + statusText ?: throw IllegalArgumentException("$STATUS_TEXT_TAG field absent") + return DeliveryStatus( + statusCode, + statusText + ) + } + } + + /** + * Constructor used in transport action communication. + * @param input StreamInput stream to deserialize data from. + */ + constructor(input: StreamInput) : this( + statusCode = input.readString(), + statusText = input.readString() + ) + + /** + * {@inheritDoc} + */ + override fun writeTo(output: StreamOutput) { + output.writeString(statusCode) + output.writeString(statusText) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + builder!! + return builder.startObject() + .field(STATUS_CODE_TAG, statusCode) + .field(STATUS_TEXT_TAG, statusText) + .endObject() + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/Email.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Email.kt new file mode 100644 index 00000000..c8da0f85 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Email.kt @@ -0,0 +1,138 @@ +/* + * 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.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.EMAIL_ACCOUNT_ID_TAG +import org.opensearch.commons.notifications.NotificationConstants.EMAIL_GROUP_ID_LIST_TAG +import org.opensearch.commons.notifications.NotificationConstants.RECIPIENT_LIST_TAG +import org.opensearch.commons.utils.logger +import org.opensearch.commons.utils.stringList +import org.opensearch.commons.utils.validateEmail +import java.io.IOException + +/** + * Data class representing Email account and default recipients. + */ +data class Email( + val emailAccountID: String, + val recipients: List, + val emailGroupIds: List +) : BaseConfigData { + + init { + require(!Strings.isNullOrEmpty(emailAccountID)) { "emailAccountID is null or empty" } + recipients.forEach { + validateEmail(it) + } + } + + companion object { + private val log by logger(Email::class.java) + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { Email(it) } + + /** + * Parser to parse xContent + */ + val xParser = XParser { parse(it) } + + /** + * Creator used in REST communication. + * @param parser XContentParser to deserialize data from. + */ + @JvmStatic + @Throws(IOException::class) + fun parse(parser: XContentParser): Email { + var emailAccountID: String? = null + var recipients: List = listOf() + var emailGroupIds: List = listOf() + + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_OBJECT, + parser.currentToken(), + parser + ) + while (parser.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + EMAIL_ACCOUNT_ID_TAG -> emailAccountID = parser.text() + RECIPIENT_LIST_TAG -> recipients = parser.stringList() + EMAIL_GROUP_ID_LIST_TAG -> emailGroupIds = parser.stringList() + else -> { + parser.skipChildren() + log.info("Unexpected field: $fieldName, while parsing Email") + } + } + } + emailAccountID ?: throw IllegalArgumentException("$EMAIL_ACCOUNT_ID_TAG field absent") + return Email(emailAccountID, recipients, emailGroupIds) + } + } + + /** + * Constructor used in transport action communication. + * @param input StreamInput stream to deserialize data from. + */ + constructor(input: StreamInput) : this( + emailAccountID = input.readString(), + recipients = input.readStringList(), + emailGroupIds = input.readStringList() + ) + + /** + * {@inheritDoc} + */ + override fun writeTo(output: StreamOutput) { + output.writeString(emailAccountID) + output.writeStringCollection(recipients) + output.writeStringCollection(emailGroupIds) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + builder!! + return builder.startObject() + .field(EMAIL_ACCOUNT_ID_TAG, emailAccountID) + .field(RECIPIENT_LIST_TAG, recipients) + .field(EMAIL_GROUP_ID_LIST_TAG, emailGroupIds) + .endObject() + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/EmailGroup.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/EmailGroup.kt new file mode 100644 index 00000000..f3beecd8 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/EmailGroup.kt @@ -0,0 +1,122 @@ +/* + * 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.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.RECIPIENT_LIST_TAG +import org.opensearch.commons.utils.logger +import org.opensearch.commons.utils.stringList +import org.opensearch.commons.utils.validateEmail +import java.io.IOException + +/** + * Data class representing Email group. + */ +data class EmailGroup( + val recipients: List +) : BaseConfigData { + + init { + recipients.forEach { + validateEmail(it) + } + } + + companion object { + private val log by logger(EmailGroup::class.java) + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { EmailGroup(it) } + + /** + * Parser to parse xContent + */ + val xParser = XParser { parse(it) } + + /** + * Creator used in REST communication. + * @param parser XContentParser to deserialize data from. + */ + @JvmStatic + @Throws(IOException::class) + fun parse(parser: XContentParser): EmailGroup { + var recipients: List? = 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) { + RECIPIENT_LIST_TAG -> recipients = parser.stringList() + else -> { + parser.skipChildren() + log.info("Unexpected field: $fieldName, while parsing EmailGroup") + } + } + } + recipients ?: throw IllegalArgumentException("$RECIPIENT_LIST_TAG field absent") + return EmailGroup(recipients) + } + } + + /** + * Constructor used in transport action communication. + * @param input StreamInput stream to deserialize data from. + */ + constructor(input: StreamInput) : this( + recipients = input.readStringList() + ) + + /** + * {@inheritDoc} + */ + override fun writeTo(output: StreamOutput) { + output.writeStringCollection(recipients) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + builder!! + return builder.startObject() + .field(RECIPIENT_LIST_TAG, recipients) + .endObject() + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/EmailRecipientStatus.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/EmailRecipientStatus.kt new file mode 100644 index 00000000..a65224de --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/EmailRecipientStatus.kt @@ -0,0 +1,123 @@ +/* + * 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.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.DELIVERY_STATUS_TAG +import org.opensearch.commons.notifications.NotificationConstants.RECIPIENT_TAG +import org.opensearch.commons.utils.logger +import org.opensearch.commons.utils.validateEmail +import java.io.IOException + +/** + * Data class representing Email Recipient Status. + */ +data class EmailRecipientStatus( + val recipient: String, + val deliveryStatus: DeliveryStatus +) : BaseModel { + + init { + validateEmail(recipient) + } + + companion object { + private val log by logger(EmailRecipientStatus::class.java) + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { EmailRecipientStatus(it) } + + /** + * Creator used in REST communication. + * @param parser XContentParser to deserialize data from. + */ + @JvmStatic + @Throws(IOException::class) + fun parse(parser: XContentParser): EmailRecipientStatus { + var recipient: String? = null + var deliveryStatus: DeliveryStatus? = 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) { + RECIPIENT_TAG -> recipient = parser.text() + DELIVERY_STATUS_TAG -> deliveryStatus = DeliveryStatus.parse(parser) + else -> { + parser.skipChildren() + log.info("Unexpected field: $fieldName, while parsing Email Recipient Status") + } + } + } + recipient ?: throw IllegalArgumentException("$RECIPIENT_TAG field absent") + deliveryStatus ?: throw IllegalArgumentException("$DELIVERY_STATUS_TAG field absent") + return EmailRecipientStatus(recipient, deliveryStatus) + } + } + + /** + * Constructor used in transport action communication. + * @param input StreamInput stream to deserialize data from. + */ + constructor(input: StreamInput) : this( + recipient = input.readString(), + deliveryStatus = DeliveryStatus.reader.read(input) + ) + + /** + * {@inheritDoc} + */ + override fun writeTo(output: StreamOutput) { + output.writeString(recipient) + deliveryStatus.writeTo(output) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + builder!! + return builder.startObject() + .field(RECIPIENT_TAG, recipient) + .field(DELIVERY_STATUS_TAG, deliveryStatus) + .endObject() + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/EventSource.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/EventSource.kt new file mode 100644 index 00000000..caf86dcd --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/EventSource.kt @@ -0,0 +1,152 @@ +/* + * 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.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.FEATURE_TAG +import org.opensearch.commons.notifications.NotificationConstants.REFERENCE_ID_TAG +import org.opensearch.commons.notifications.NotificationConstants.SEVERITY_TAG +import org.opensearch.commons.notifications.NotificationConstants.TAGS_TAG +import org.opensearch.commons.notifications.NotificationConstants.TITLE_TAG +import org.opensearch.commons.utils.logger +import org.opensearch.commons.utils.stringList +import java.io.IOException + +/** + * Data class representing Notification event source. + */ +data class EventSource( + val title: String, + val referenceId: String, + val feature: Feature, + val severity: SeverityType = SeverityType.INFO, + val tags: List = listOf() +) : BaseModel { + + init { + require(!Strings.isNullOrEmpty(title)) { "name is null or empty" } + } + + companion object { + private val log by logger(EventSource::class.java) + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { EventSource(it) } + + /** + * Creator used in REST communication. + * @param parser XContentParser to deserialize data from. + */ + @JvmStatic + @Throws(IOException::class) + fun parse(parser: XContentParser): EventSource { + var title: String? = null + var referenceId: String? = null + var feature: Feature? = null + var severity: SeverityType = SeverityType.INFO + var tags: List = emptyList() + + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_OBJECT, + parser.currentToken(), + parser + ) + while (parser.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + TITLE_TAG -> title = parser.text() + REFERENCE_ID_TAG -> referenceId = parser.text() + FEATURE_TAG -> feature = Feature.fromTagOrDefault(parser.text()) + SEVERITY_TAG -> severity = SeverityType.fromTagOrDefault(parser.text()) + TAGS_TAG -> tags = parser.stringList() + else -> { + parser.skipChildren() + log.info("Unexpected field: $fieldName, while parsing EventSource") + } + } + } + title ?: throw IllegalArgumentException("$TITLE_TAG field absent") + referenceId ?: throw IllegalArgumentException("$REFERENCE_ID_TAG field absent") + feature ?: throw IllegalArgumentException("$FEATURE_TAG field absent") + + return EventSource( + title, + referenceId, + feature, + severity, + tags + ) + } + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + builder!! + return builder.startObject() + .field(TITLE_TAG, title) + .field(REFERENCE_ID_TAG, referenceId) + .field(FEATURE_TAG, feature.tag) + .field(SEVERITY_TAG, severity.tag) + .field(TAGS_TAG, tags) + .endObject() + } + + /** + * Constructor used in transport action communication. + * @param input StreamInput stream to deserialize data from. + */ + constructor(input: StreamInput) : this( + title = input.readString(), + referenceId = input.readString(), + feature = input.readEnum(Feature::class.java), + severity = input.readEnum(SeverityType::class.java), + tags = input.readStringList() + ) + + /** + * {@inheritDoc} + */ + override fun writeTo(output: StreamOutput) { + output.writeString(title) + output.writeString(referenceId) + output.writeEnum(feature) + output.writeEnum(severity) + output.writeStringCollection(tags) + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/EventStatus.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/EventStatus.kt new file mode 100644 index 00000000..1233a997 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/EventStatus.kt @@ -0,0 +1,165 @@ +/* + * 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.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.CONFIG_ID_TAG +import org.opensearch.commons.notifications.NotificationConstants.CONFIG_NAME_TAG +import org.opensearch.commons.notifications.NotificationConstants.CONFIG_TYPE_TAG +import org.opensearch.commons.notifications.NotificationConstants.DELIVERY_STATUS_TAG +import org.opensearch.commons.notifications.NotificationConstants.EMAIL_RECIPIENT_STATUS_TAG +import org.opensearch.commons.utils.fieldIfNotNull +import org.opensearch.commons.utils.logger +import org.opensearch.commons.utils.objectList +import java.io.IOException + +/** + * Data class representing Notification Event Status. + */ +data class EventStatus( + val configId: String, + val configName: String, + val configType: ConfigType, + val emailRecipientStatus: List = listOf(), + val deliveryStatus: DeliveryStatus? = null +) : BaseModel { + + init { + require(!Strings.isNullOrEmpty(configId)) { "config id is null or empty" } + require(!Strings.isNullOrEmpty(configName)) { "config name is null or empty" } + when (configType) { + ConfigType.CHIME -> requireNotNull(deliveryStatus) + ConfigType.WEBHOOK -> requireNotNull(deliveryStatus) + ConfigType.SLACK -> requireNotNull(deliveryStatus) + ConfigType.EMAIL -> require(emailRecipientStatus.isNotEmpty()) + ConfigType.NONE -> log.info("Some config field not recognized") + else -> { + log.info("non-allowed config type for Status") + } + } + } + + companion object { + private val log by logger(NotificationConfig::class.java) + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { EventStatus(it) } + + /** + * Creator used in REST communication. + * @param parser XContentParser to deserialize data from. + */ + @JvmStatic + @Throws(IOException::class) + fun parse(parser: XContentParser): EventStatus { + var configName: String? = null + var configId: String? = null + var configType: ConfigType? = null + var emailRecipientStatus: List = listOf() + var deliveryStatus: DeliveryStatus? = 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) { + CONFIG_NAME_TAG -> configName = parser.text() + CONFIG_ID_TAG -> configId = parser.text() + CONFIG_TYPE_TAG -> configType = ConfigType.fromTagOrDefault(parser.text()) + EMAIL_RECIPIENT_STATUS_TAG -> emailRecipientStatus = parser.objectList { EmailRecipientStatus.parse(it) } + DELIVERY_STATUS_TAG -> deliveryStatus = DeliveryStatus.parse(parser) + else -> { + parser.skipChildren() + log.info("Unexpected field: $fieldName, while parsing EventStatus") + } + } + } + configName ?: throw IllegalArgumentException("$CONFIG_NAME_TAG field absent") + configId ?: throw IllegalArgumentException("$CONFIG_ID_TAG field absent") + configType ?: throw IllegalArgumentException("$CONFIG_TYPE_TAG field absent") + + return EventStatus( + configId, + configName, + configType, + emailRecipientStatus, + deliveryStatus + ) + } + } + + /** + * Constructor used in transport action communication. + * @param input StreamInput stream to deserialize data from. + */ + constructor(input: StreamInput) : this( + configId = input.readString(), + configName = input.readString(), + configType = input.readEnum(ConfigType::class.java), + emailRecipientStatus = input.readList(EmailRecipientStatus.reader), + deliveryStatus = input.readOptionalWriteable(DeliveryStatus.reader) + ) + + /** + * {@inheritDoc} + */ + override fun writeTo(output: StreamOutput) { + output.writeString(configId) + output.writeString(configName) + output.writeEnum(configType) + output.writeCollection(emailRecipientStatus) + output.writeOptionalWriteable(deliveryStatus) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + builder!! + return builder.startObject() + .field(CONFIG_ID_TAG, configId) + .field(CONFIG_TYPE_TAG, configType.tag) + .field(CONFIG_NAME_TAG, configName) + .field(EMAIL_RECIPIENT_STATUS_TAG, emailRecipientStatus) + .fieldIfNotNull(DELIVERY_STATUS_TAG, deliveryStatus) + .endObject() + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/Feature.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Feature.kt new file mode 100644 index 00000000..84b37139 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Feature.kt @@ -0,0 +1,71 @@ +/* + * 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/FeatureChannel.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/FeatureChannel.kt new file mode 100644 index 00000000..a108d2ff --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/FeatureChannel.kt @@ -0,0 +1,152 @@ +/* + * 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.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.CONFIG_ID_TAG +import org.opensearch.commons.notifications.NotificationConstants.CONFIG_TYPE_TAG +import org.opensearch.commons.notifications.NotificationConstants.DESCRIPTION_TAG +import org.opensearch.commons.notifications.NotificationConstants.IS_ENABLED_TAG +import org.opensearch.commons.notifications.NotificationConstants.NAME_TAG +import org.opensearch.commons.utils.logger +import java.io.IOException + +/** + * Data class representing Notification config for exposed for other plugins. + */ +data class FeatureChannel( + val configId: String, + val name: String, + val description: String, + val configType: ConfigType, + val isEnabled: Boolean = true +) : BaseModel { + + init { + require(!Strings.isNullOrEmpty(name)) { "name is null or empty" } + require(!Strings.isNullOrEmpty(configId)) { "config id is null or empty" } + } + + companion object { + private val log by logger(FeatureChannel::class.java) + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { FeatureChannel(it) } + + /** + * Creator used in REST communication. + * @param parser XContentParser to deserialize data from. + */ + @Suppress("ComplexMethod") + @JvmStatic + @Throws(IOException::class) + fun parse(parser: XContentParser): FeatureChannel { + var configId: String? = null + var name: String? = null + var description = "" + var configType: ConfigType? = null + var isEnabled = true + + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_OBJECT, + parser.currentToken(), + parser + ) + while (parser.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + CONFIG_ID_TAG -> configId = parser.text() + NAME_TAG -> name = parser.text() + DESCRIPTION_TAG -> description = parser.text() + CONFIG_TYPE_TAG -> configType = ConfigType.fromTagOrDefault(parser.text()) + IS_ENABLED_TAG -> isEnabled = parser.booleanValue() + else -> { + parser.skipChildren() + log.info("Unexpected field: $fieldName, while parsing FeatureChannel") + } + } + } + configId ?: throw IllegalArgumentException("$CONFIG_ID_TAG field absent") + name ?: throw IllegalArgumentException("$NAME_TAG field absent") + configType ?: throw IllegalArgumentException("$CONFIG_TYPE_TAG field absent") + return FeatureChannel( + configId, + name, + description, + configType, + isEnabled + ) + } + } + + /** + * Constructor used in transport action communication. + * @param input StreamInput stream to deserialize data from. + */ + constructor(input: StreamInput) : this( + configId = input.readString(), + name = input.readString(), + description = input.readString(), + configType = input.readEnum(ConfigType::class.java), + isEnabled = input.readBoolean() + ) + + /** + * {@inheritDoc} + */ + override fun writeTo(output: StreamOutput) { + output.writeString(configId) + output.writeString(name) + output.writeString(description) + output.writeEnum(configType) + output.writeBoolean(isEnabled) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + builder!! + return builder.startObject() + .field(CONFIG_ID_TAG, configId) + .field(NAME_TAG, name) + .field(DESCRIPTION_TAG, description) + .field(CONFIG_TYPE_TAG, configType.tag) + .field(IS_ENABLED_TAG, isEnabled) + .endObject() + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/FeatureChannelList.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/FeatureChannelList.kt new file mode 100644 index 00000000..6fc57b22 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/FeatureChannelList.kt @@ -0,0 +1,93 @@ +/* + * 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.apache.lucene.search.TotalHits +import org.opensearch.action.search.SearchResponse +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_CONFIG_LIST_TAG + +/** + * FeatureChannel search results + */ +class FeatureChannelList : SearchResults { + + /** + * single item result constructor + */ + constructor(objectItem: FeatureChannel) : super(FEATURE_CONFIG_LIST_TAG, objectItem) + + /** + * multiple items result constructor + */ + constructor(objectList: List) : this( + 0, + objectList.size.toLong(), + TotalHits.Relation.EQUAL_TO, + objectList + ) + + /** + * all param constructor + */ + constructor( + startIndex: Long, + totalHits: Long, + totalHitRelation: TotalHits.Relation, + objectList: List + ) : super(startIndex, totalHits, totalHitRelation, FEATURE_CONFIG_LIST_TAG, objectList) + + /** + * Constructor used in transport action communication. + * @param input StreamInput stream to deserialize data from. + */ + constructor(input: StreamInput) : super(input, FeatureChannel.reader) + + /** + * Construct object from XContentParser + */ + constructor(parser: XContentParser) : super(parser, FEATURE_CONFIG_LIST_TAG) + + /** + * Construct object from SearchResponse + */ + constructor(from: Long, response: SearchResponse, searchHitParser: SearchHitParser) : super( + from, + response, + searchHitParser, + FEATURE_CONFIG_LIST_TAG + ) + + /** + * {@inheritDoc} + */ + override fun parseItem(parser: XContentParser): FeatureChannel { + return FeatureChannel.parse(parser) + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/MethodType.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/MethodType.kt new file mode 100644 index 00000000..99f5694a --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/MethodType.kt @@ -0,0 +1,36 @@ +package org.opensearch.commons.notifications.model + +import org.opensearch.commons.utils.EnumParser + +enum class MethodType(val tag: String) { + NONE("none") { + override fun toString(): String { + return tag + } + }, + SSL("ssl") { + override fun toString(): String { + return tag + } + }, + START_TLS("start_tls") { + override fun toString(): String { + return tag + } + }; + + companion object { + private val tagMap = values().associateBy { it.tag } + + val enumParser = EnumParser { fromTagOrDefault(it) } + + /** + * Get MethodType from tag or NONE if not found + * @param tag the tag + * @return MethodType corresponding to tag. NONE if invalid tag. + */ + fun fromTagOrDefault(tag: String): MethodType { + 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 new file mode 100644 index 00000000..30d72147 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfig.kt @@ -0,0 +1,175 @@ +/* + * 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.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.CONFIG_TYPE_TAG +import org.opensearch.commons.notifications.NotificationConstants.DESCRIPTION_TAG +import org.opensearch.commons.notifications.NotificationConstants.FEATURE_LIST_TAG +import org.opensearch.commons.notifications.NotificationConstants.IS_ENABLED_TAG +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.fieldIfNotNull +import org.opensearch.commons.utils.logger +import java.io.IOException +import java.util.EnumSet + +/** + * Data class representing Notification config. + */ +data class NotificationConfig( + val name: String, + val description: String, + val configType: ConfigType, + val features: EnumSet, + val configData: BaseConfigData?, + val isEnabled: Boolean = true +) : BaseModel { + + init { + require(!Strings.isNullOrEmpty(name)) { "name is null or empty" } + if (!validateConfigData(configType, configData)) { + throw IllegalArgumentException("ConfigType: $configType and data doesn't match") + } + if (configType === ConfigType.NONE) { + log.info("Some config field not recognized") + } + } + + companion object { + private val log by logger(NotificationConfig::class.java) + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { NotificationConfig(it) } + + /** + * Creator used in REST communication. + * @param parser XContentParser to deserialize data from. + */ + @Suppress("ComplexMethod") + @JvmStatic + @Throws(IOException::class) + fun parse(parser: XContentParser): NotificationConfig { + var name: String? = null + var description = "" + var configType: ConfigType? = null + var features: EnumSet? = null + var isEnabled = true + var configData: BaseConfigData? = 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) { + 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) + IS_ENABLED_TAG -> isEnabled = parser.booleanValue() + else -> { + val configTypeForTag = ConfigType.fromTagOrDefault(fieldName) + if (configTypeForTag != ConfigType.NONE && configData == null) { + configData = createConfigData(configTypeForTag, parser) + } else { + parser.skipChildren() + log.info("Unexpected field: $fieldName, while parsing configuration") + } + } + } + } + name ?: throw IllegalArgumentException("$NAME_TAG field absent") + configType ?: throw IllegalArgumentException("$CONFIG_TYPE_TAG field absent") + features ?: throw IllegalArgumentException("$FEATURE_LIST_TAG field absent") + return NotificationConfig( + name, + description, + configType, + features, + configData, + isEnabled + ) + } + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + builder!! + return builder.startObject() + .field(NAME_TAG, name) + .field(DESCRIPTION_TAG, description) + .field(CONFIG_TYPE_TAG, configType.tag) + .field(FEATURE_LIST_TAG, features) + .field(IS_ENABLED_TAG, isEnabled) + .fieldIfNotNull(configType.tag, configData) + .endObject() + } + + /** + * Constructor used in transport action communication. + * @param input StreamInput stream to deserialize data from. + */ + constructor(input: StreamInput) : this( + name = input.readString(), + description = input.readString(), + configType = input.readEnum(ConfigType::class.java), + features = input.readEnumSet(Feature::class.java), + isEnabled = input.readBoolean(), + configData = input.readOptionalWriteable(getReaderForConfigType(input.readEnum(ConfigType::class.java))) + ) + + /** + * {@inheritDoc} + */ + override fun writeTo(output: StreamOutput) { + output.writeString(name) + output.writeString(description) + output.writeEnum(configType) + output.writeEnumSet(features) + output.writeBoolean(isEnabled) + // Reading config types multiple times in constructor + output.writeEnum(configType) + output.writeOptionalWriteable(configData) + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfo.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfo.kt new file mode 100644 index 00000000..7a949bbb --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfo.kt @@ -0,0 +1,153 @@ +/* + * 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.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.CONFIG_ID_TAG +import org.opensearch.commons.notifications.NotificationConstants.CONFIG_TAG +import org.opensearch.commons.notifications.NotificationConstants.CREATED_TIME_TAG +import org.opensearch.commons.notifications.NotificationConstants.TENANT_TAG +import org.opensearch.commons.notifications.NotificationConstants.UPDATED_TIME_TAG +import org.opensearch.commons.utils.logger +import java.io.IOException +import java.time.Instant + +/** + * Data class representing Notification config. + */ +data class NotificationConfigInfo( + val configId: String, + val lastUpdatedTime: Instant, + val createdTime: Instant, + val tenant: String, + val notificationConfig: NotificationConfig +) : BaseModel { + + init { + require(!Strings.isNullOrEmpty(configId)) { "config id is null or empty" } + } + + companion object { + private val log by logger(NotificationConfigInfo::class.java) + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { NotificationConfigInfo(it) } + + /** + * Creator used in REST communication. + * @param parser XContentParser to deserialize data from. + */ + @JvmStatic + @Throws(IOException::class) + fun parse(parser: XContentParser): NotificationConfigInfo { + var configId: String? = null + var lastUpdatedTime: Instant? = null + var createdTime: Instant? = null + var tenant: String? = null + var notificationConfig: NotificationConfig? = 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) { + CONFIG_ID_TAG -> configId = parser.text() + UPDATED_TIME_TAG -> lastUpdatedTime = Instant.ofEpochMilli(parser.longValue()) + CREATED_TIME_TAG -> createdTime = Instant.ofEpochMilli(parser.longValue()) + TENANT_TAG -> tenant = parser.text() + CONFIG_TAG -> notificationConfig = NotificationConfig.parse(parser) + else -> { + parser.skipChildren() + log.info("Unexpected field: $fieldName, while parsing configuration") + } + } + } + configId ?: throw IllegalArgumentException("$CONFIG_ID_TAG field absent") + lastUpdatedTime ?: throw IllegalArgumentException("$UPDATED_TIME_TAG field absent") + createdTime ?: throw IllegalArgumentException("$CREATED_TIME_TAG field absent") + tenant = tenant ?: "" + notificationConfig ?: throw IllegalArgumentException("$CONFIG_TAG field absent") + return NotificationConfigInfo( + configId, + lastUpdatedTime, + createdTime, + tenant, + notificationConfig + ) + } + } + + /** + * Constructor used in transport action communication. + * @param input StreamInput stream to deserialize data from. + */ + constructor(input: StreamInput) : this( + configId = input.readString(), + lastUpdatedTime = input.readInstant(), + createdTime = input.readInstant(), + tenant = input.readString(), + notificationConfig = NotificationConfig.reader.read(input) + ) + + /** + * {@inheritDoc} + */ + override fun writeTo(output: StreamOutput) { + output.writeString(configId) + output.writeInstant(lastUpdatedTime) + output.writeInstant(createdTime) + output.writeString(tenant) + notificationConfig.writeTo(output) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + builder!! + return builder.startObject() + .field(CONFIG_ID_TAG, configId) + .field(UPDATED_TIME_TAG, lastUpdatedTime.toEpochMilli()) + .field(CREATED_TIME_TAG, createdTime.toEpochMilli()) + .field(TENANT_TAG, tenant) + .field(CONFIG_TAG, notificationConfig) + .endObject() + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfigSearchResult.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfigSearchResult.kt new file mode 100644 index 00000000..48952335 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationConfigSearchResult.kt @@ -0,0 +1,93 @@ +/* + * 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.apache.lucene.search.TotalHits +import org.opensearch.action.search.SearchResponse +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.commons.notifications.NotificationConstants.CONFIG_LIST_TAG + +/** + * NotificationConfig search results + */ +class NotificationConfigSearchResult : SearchResults { + + /** + * single item result constructor + */ + constructor(objectItem: NotificationConfigInfo) : super(CONFIG_LIST_TAG, objectItem) + + /** + * multiple items result constructor + */ + constructor(objectList: List) : this( + 0, + objectList.size.toLong(), + TotalHits.Relation.EQUAL_TO, + objectList + ) + + /** + * all param constructor + */ + constructor( + startIndex: Long, + totalHits: Long, + totalHitRelation: TotalHits.Relation, + objectList: List + ) : super(startIndex, totalHits, totalHitRelation, CONFIG_LIST_TAG, objectList) + + /** + * Constructor used in transport action communication. + * @param input StreamInput stream to deserialize data from. + */ + constructor(input: StreamInput) : super(input, NotificationConfigInfo.reader) + + /** + * Construct object from XContentParser + */ + constructor(parser: XContentParser) : super(parser, CONFIG_LIST_TAG) + + /** + * Construct object from SearchResponse + */ + constructor(from: Long, response: SearchResponse, searchHitParser: SearchHitParser) : super( + from, + response, + searchHitParser, + CONFIG_LIST_TAG + ) + + /** + * {@inheritDoc} + */ + override fun parseItem(parser: XContentParser): NotificationConfigInfo { + return NotificationConfigInfo.parse(parser) + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEvent.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEvent.kt new file mode 100644 index 00000000..c6bf6928 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEvent.kt @@ -0,0 +1,127 @@ +/* + * 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.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.EVENT_SOURCE_TAG +import org.opensearch.commons.notifications.NotificationConstants.STATUS_LIST_TAG +import org.opensearch.commons.utils.logger +import org.opensearch.commons.utils.objectList +import java.io.IOException + +/** + * Data class representing Notification event. + */ +data class NotificationEvent( + val eventSource: EventSource, + val statusList: List = listOf() +) : BaseModel { + + init { + require(statusList.isNotEmpty()) { "statusList is null or empty" } + } + + companion object { + private val log by logger(NotificationEvent::class.java) + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { NotificationEvent(it) } + + /** + * Creator used in REST communication. + * @param parser XContentParser to deserialize data from. + */ + @JvmStatic + @Throws(IOException::class) + fun parse(parser: XContentParser): NotificationEvent { + var eventSource: EventSource? = null + var statusList: List = listOf() + + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_OBJECT, + parser.currentToken(), + parser + ) + while (parser.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + EVENT_SOURCE_TAG -> eventSource = EventSource.parse(parser) + STATUS_LIST_TAG -> statusList = parser.objectList { EventStatus.parse(it) } + else -> { + parser.skipChildren() + log.info("Unexpected field: $fieldName, while parsing notification event") + } + } + } + eventSource ?: throw IllegalArgumentException("$EVENT_SOURCE_TAG field absent") + if (statusList.isEmpty()) { + throw IllegalArgumentException("$STATUS_LIST_TAG field absent or empty") + } + return NotificationEvent( + eventSource, + statusList + ) + } + } + + /** + * Constructor used in transport action communication. + * @param input StreamInput stream to deserialize data from. + */ + constructor(input: StreamInput) : this( + eventSource = EventSource.reader.read(input), + statusList = input.readList(EventStatus.reader) + ) + + /** + * {@inheritDoc} + */ + override fun writeTo(output: StreamOutput) { + eventSource.writeTo(output) + output.writeList(statusList) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + builder!! + return builder.startObject() + .field(EVENT_SOURCE_TAG, eventSource) + .field(STATUS_LIST_TAG, statusList) + .endObject() + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEventInfo.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEventInfo.kt new file mode 100644 index 00000000..27f9d0ad --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEventInfo.kt @@ -0,0 +1,153 @@ +/* + * 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.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.CREATED_TIME_TAG +import org.opensearch.commons.notifications.NotificationConstants.EVENT_ID_TAG +import org.opensearch.commons.notifications.NotificationConstants.EVENT_TAG +import org.opensearch.commons.notifications.NotificationConstants.TENANT_TAG +import org.opensearch.commons.notifications.NotificationConstants.UPDATED_TIME_TAG +import org.opensearch.commons.utils.logger +import java.io.IOException +import java.time.Instant + +/** + * Data class representing Notification event with information. + */ +data class NotificationEventInfo( + val eventId: String, + val lastUpdatedTime: Instant, + val createdTime: Instant, + val tenant: String, + val notificationEvent: NotificationEvent +) : BaseModel { + + init { + require(!Strings.isNullOrEmpty(eventId)) { "event id is null or empty" } + } + + companion object { + private val log by logger(NotificationEventInfo::class.java) + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { NotificationEventInfo(it) } + + /** + * Creator used in REST communication. + * @param parser XContentParser to deserialize data from. + */ + @JvmStatic + @Throws(IOException::class) + fun parse(parser: XContentParser): NotificationEventInfo { + var eventId: String? = null + var lastUpdatedTime: Instant? = null + var createdTime: Instant? = null + var tenant: String? = null + var notificationEvent: NotificationEvent? = 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) { + EVENT_ID_TAG -> eventId = parser.text() + UPDATED_TIME_TAG -> lastUpdatedTime = Instant.ofEpochMilli(parser.longValue()) + CREATED_TIME_TAG -> createdTime = Instant.ofEpochMilli(parser.longValue()) + TENANT_TAG -> tenant = parser.text() + EVENT_TAG -> notificationEvent = NotificationEvent.parse(parser) + else -> { + parser.skipChildren() + log.info("Unexpected field: $fieldName, while parsing event info") + } + } + } + eventId ?: throw IllegalArgumentException("$EVENT_ID_TAG field absent") + lastUpdatedTime ?: throw IllegalArgumentException("$UPDATED_TIME_TAG field absent") + createdTime ?: throw IllegalArgumentException("$CREATED_TIME_TAG field absent") + tenant = tenant ?: "" + notificationEvent ?: throw IllegalArgumentException("$EVENT_TAG field absent") + return NotificationEventInfo( + eventId, + lastUpdatedTime, + createdTime, + tenant, + notificationEvent + ) + } + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + builder!! + return builder.startObject() + .field(EVENT_ID_TAG, eventId) + .field(UPDATED_TIME_TAG, lastUpdatedTime.toEpochMilli()) + .field(CREATED_TIME_TAG, createdTime.toEpochMilli()) + .field(TENANT_TAG, tenant) + .field(EVENT_TAG, notificationEvent) + .endObject() + } + + /** + * Constructor used in transport action communication. + * @param input StreamInput stream to deserialize data from. + */ + constructor(input: StreamInput) : this( + eventId = input.readString(), + lastUpdatedTime = input.readInstant(), + createdTime = input.readInstant(), + tenant = input.readString(), + notificationEvent = NotificationEvent.reader.read(input) + ) + + /** + * {@inheritDoc} + */ + override fun writeTo(output: StreamOutput) { + output.writeString(eventId) + output.writeInstant(lastUpdatedTime) + output.writeInstant(createdTime) + output.writeString(tenant) + notificationEvent.writeTo(output) + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEventSearchResult.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEventSearchResult.kt new file mode 100644 index 00000000..71ed1ba5 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/NotificationEventSearchResult.kt @@ -0,0 +1,93 @@ +/* + * 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.apache.lucene.search.TotalHits +import org.opensearch.action.search.SearchResponse +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.commons.notifications.NotificationConstants.EVENT_LIST_TAG + +/** + * Notification Event search results + */ +class NotificationEventSearchResult : SearchResults { + + /** + * single item result constructor + */ + constructor(objectItem: NotificationEventInfo) : super(EVENT_LIST_TAG, objectItem) + + /** + * multiple items result constructor + */ + constructor(objectList: List) : this( + 0, + objectList.size.toLong(), + TotalHits.Relation.EQUAL_TO, + objectList + ) + + /** + * all param constructor + */ + constructor( + startIndex: Long, + totalHits: Long, + totalHitRelation: TotalHits.Relation, + objectList: List + ) : super(startIndex, totalHits, totalHitRelation, EVENT_LIST_TAG, objectList) + + /** + * Constructor used in transport action communication. + * @param input StreamInput stream to deserialize data from. + */ + constructor(input: StreamInput) : super(input, NotificationEventInfo.reader) + + /** + * Construct object from XContentParser + */ + constructor(parser: XContentParser) : super(parser, EVENT_LIST_TAG) + + /** + * Construct object from SearchResponse + */ + constructor(from: Long, response: SearchResponse, searchHitParser: SearchHitParser) : super( + from, + response, + searchHitParser, + EVENT_LIST_TAG + ) + + /** + * {@inheritDoc} + */ + override fun parseItem(parser: XContentParser): NotificationEventInfo { + return NotificationEventInfo.parse(parser) + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/SearchResults.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/SearchResults.kt new file mode 100644 index 00000000..868cfebe --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/SearchResults.kt @@ -0,0 +1,221 @@ +/* + * 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.apache.lucene.search.TotalHits.Relation +import org.apache.lucene.search.TotalHits.Relation.EQUAL_TO +import org.apache.lucene.search.TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO +import org.opensearch.action.search.SearchResponse +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.Params +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.notifications.NotificationConstants.START_INDEX_TAG +import org.opensearch.commons.notifications.NotificationConstants.TOTAL_HITS_TAG +import org.opensearch.commons.notifications.NotificationConstants.TOTAL_HIT_RELATION_TAG +import org.opensearch.commons.utils.logger +import org.opensearch.search.SearchHit + +abstract class SearchResults : BaseModel { + val startIndex: Long + val totalHits: Long + val totalHitRelation: Relation + val objectListFieldName: String + val objectList: List + + interface SearchHitParser { + fun parse(searchHit: SearchHit): ItemClass + } + + companion object { + private val log by logger(SearchResults::class.java) + private fun convertRelation(totalHitRelation: Relation): String { + return if (totalHitRelation == EQUAL_TO) { + "eq" + } else { + "gte" + } + } + + private fun convertRelation(totalHitRelation: String): Relation { + return if (totalHitRelation == "eq") { + EQUAL_TO + } else { + GREATER_THAN_OR_EQUAL_TO + } + } + } + + constructor( + objectListFieldName: String, + objectItem: ItemClass + ) { + this.startIndex = 0 + this.totalHits = 1 + this.totalHitRelation = EQUAL_TO + this.objectListFieldName = objectListFieldName + this.objectList = listOf(objectItem) + } + + constructor( + startIndex: Long, + totalHits: Long, + totalHitRelation: Relation, + objectListFieldName: String, + objectList: List + ) { + this.startIndex = startIndex + this.totalHits = totalHits + this.totalHitRelation = totalHitRelation + this.objectListFieldName = objectListFieldName + this.objectList = objectList + } + + constructor( + from: Long, + response: SearchResponse, + searchHitParser: SearchHitParser, + objectListFieldName: String + ) { + val mutableList: MutableList = mutableListOf() + response.hits.forEach { + mutableList.add(searchHitParser.parse(it)) + } + val totalHits = response.hits.totalHits + val totalHitsVal: Long + val totalHitsRelation: Relation + if (totalHits == null) { + totalHitsVal = mutableList.size.toLong() + totalHitsRelation = EQUAL_TO + } else { + totalHitsVal = totalHits.value + totalHitsRelation = totalHits.relation + } + this.startIndex = from + this.totalHits = totalHitsVal + this.totalHitRelation = totalHitsRelation + this.objectListFieldName = objectListFieldName + this.objectList = mutableList + } + + /** + * Parse the data from parser and create object + * @param parser data referenced at parser + */ + constructor(parser: XContentParser, objectListFieldName: String) { + var startIndex: Long = 0 + var totalHits: Long = 0 + var totalHitRelation: Relation = EQUAL_TO + var objectList: List? = null + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.currentToken(), parser) + while (XContentParser.Token.END_OBJECT != parser.nextToken()) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + START_INDEX_TAG -> startIndex = parser.longValue() + TOTAL_HITS_TAG -> totalHits = parser.longValue() + TOTAL_HIT_RELATION_TAG -> totalHitRelation = convertRelation(parser.text()) + objectListFieldName -> objectList = parseItemList(parser) + else -> { + parser.skipChildren() + log.info("Skipping Unknown field $fieldName") + } + } + } + objectList ?: throw IllegalArgumentException("$objectListFieldName field absent") + if (totalHits == 0L) { + totalHits = objectList.size.toLong() + } + this.startIndex = startIndex + this.totalHits = totalHits + this.totalHitRelation = totalHitRelation + this.objectListFieldName = objectListFieldName + this.objectList = objectList + } + + /** + * Parse the item list from parser + * @param parser data referenced at parser + * @return created list of items + */ + private fun parseItemList(parser: XContentParser): List { + val retList: MutableList = mutableListOf() + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_ARRAY, parser.currentToken(), parser) + while (parser.nextToken() != XContentParser.Token.END_ARRAY) { + retList.add(parseItem(parser)) + } + return retList + } + + /** + * Parse the object item + * @param parser data referenced at parser + * @return created item + */ + abstract fun parseItem(parser: XContentParser): ItemClass + + /** + * Constructor used in transport action communication. + * @param input StreamInput stream to deserialize data from. + * @param reader StreamInput reader class for reading item. + */ + constructor(input: StreamInput, reader: Writeable.Reader) : this( + startIndex = input.readLong(), + totalHits = input.readLong(), + totalHitRelation = input.readEnum(Relation::class.java), + objectListFieldName = input.readString(), + objectList = input.readList(reader) + ) + + /** + * {@inheritDoc} + */ + override fun writeTo(output: StreamOutput) { + output.writeLong(startIndex) + output.writeLong(totalHits) + output.writeEnum(totalHitRelation) + output.writeString(objectListFieldName) + output.writeList(objectList) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: Params?): XContentBuilder { + builder!!.startObject() + .field(START_INDEX_TAG, startIndex) + .field(TOTAL_HITS_TAG, totalHits) + .field(TOTAL_HIT_RELATION_TAG, convertRelation(totalHitRelation)) + .startArray(objectListFieldName) + objectList.forEach { it.toXContent(builder, params) } + return builder.endArray().endObject() + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/SeverityType.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/SeverityType.kt new file mode 100644 index 00000000..6f84f07b --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/SeverityType.kt @@ -0,0 +1,71 @@ +/* + * 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 + +/** + * Notification severity type + */ +enum class SeverityType(val tag: String) { + NONE("none") { + override fun toString(): String { + return tag + } + }, + HIGH("high") { + override fun toString(): String { + return tag + } + }, + INFO("info") { + override fun toString(): String { + return tag + } + }, + CRITICAL("critical") { + override fun toString(): String { + return tag + } + }; + + companion object { + private val tagMap = values().associateBy { it.tag } + + val enumParser = EnumParser { fromTagOrDefault(it) } + + /** + * Get SeverityType from tag or NONE if not found + * @param tag the tag + * @return SeverityType corresponding to tag. NONE if invalid tag. + */ + fun fromTagOrDefault(tag: String): SeverityType { + return tagMap[tag] ?: NONE + } + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/Slack.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Slack.kt new file mode 100644 index 00000000..e5d3c3f6 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Slack.kt @@ -0,0 +1,121 @@ +/* + * 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.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.URL_TAG +import org.opensearch.commons.utils.logger +import org.opensearch.commons.utils.validateUrl +import java.io.IOException + +/** + * Data class representing Slack channel. + */ +data class Slack( + val url: String +) : BaseConfigData { + + init { + require(!Strings.isNullOrEmpty(url)) { "URL is null or empty" } + validateUrl(url) + } + + companion object { + private val log by logger(Slack::class.java) + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { Slack(it) } + + /** + * Parser to parse xContent + */ + val xParser = XParser { parse(it) } + + /** + * Creator used in REST communication. + * @param parser XContentParser to deserialize data from. + */ + @JvmStatic + @Throws(IOException::class) + fun parse(parser: XContentParser): Slack { + var url: 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) { + URL_TAG -> url = parser.text() + else -> { + parser.skipChildren() + log.info("Unexpected field: $fieldName, while parsing Slack destination") + } + } + } + url ?: throw IllegalArgumentException("$URL_TAG field absent") + return Slack(url) + } + } + + /** + * Constructor used in transport action communication. + * @param input StreamInput stream to deserialize data from. + */ + constructor(input: StreamInput) : this( + url = input.readString() + ) + + /** + * {@inheritDoc} + */ + override fun writeTo(output: StreamOutput) { + output.writeString(url) + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + builder!! + return builder.startObject() + .field(URL_TAG, url) + .endObject() + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/SmtpAccount.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/SmtpAccount.kt new file mode 100644 index 00000000..21e2155e --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/SmtpAccount.kt @@ -0,0 +1,146 @@ +/* + * 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.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.HOST_TAG +import org.opensearch.commons.notifications.NotificationConstants.METHOD_TAG +import org.opensearch.commons.notifications.NotificationConstants.PORT_TAG +import org.opensearch.commons.utils.logger +import org.opensearch.commons.utils.validateEmail +import java.io.IOException + +/** + * Data class representing SMTP account channel. + */ +data class SmtpAccount( + val host: String, + val port: Int, + val method: MethodType, + val fromAddress: String +) : BaseConfigData { + + init { + require(!Strings.isNullOrEmpty(host)) { "host is null or empty" } + require(port > 0) { "port should be positive value" } + validateEmail(fromAddress) + } + + companion object { + private val log by logger(SmtpAccount::class.java) + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { SmtpAccount(it) } + + /** + * Parser to parse xContent + */ + val xParser = XParser { parse(it) } + + @JvmStatic + @Throws(IOException::class) + fun parse(parser: XContentParser): SmtpAccount { + var host: String? = null + var port: Int? = null + var method: MethodType? = 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) { + HOST_TAG -> host = parser.text() + PORT_TAG -> port = parser.intValue() + METHOD_TAG -> method = MethodType.fromTagOrDefault(parser.text()) + FROM_ADDRESS_TAG -> fromAddress = parser.text() + else -> { + parser.skipChildren() + log.info("Unexpected field: $fieldName, while parsing SmtpAccount") + } + } + } + host ?: throw IllegalArgumentException("$HOST_TAG field absent") + port ?: throw IllegalArgumentException("$PORT_TAG field absent") + method ?: throw IllegalArgumentException("$METHOD_TAG field absent") + fromAddress ?: throw IllegalArgumentException("$FROM_ADDRESS_TAG field absent") + return SmtpAccount( + host, + port, + method, + fromAddress + ) + } + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + return builder!!.startObject() + .field(HOST_TAG, host) + .field(PORT_TAG, port) + .field(METHOD_TAG, method.tag) + .field(FROM_ADDRESS_TAG, fromAddress) + .endObject() + } + + /** + * Constructor used in transport action communication. + * @param input StreamInput stream to deserialize data from. + */ + constructor(input: StreamInput) : this( + host = input.readString(), + port = input.readInt(), + method = input.readEnum(MethodType::class.java), + fromAddress = input.readString() + ) + + /** + * {@inheritDoc} + */ + override fun writeTo(out: StreamOutput) { + out.writeString(host) + out.writeInt(port) + out.writeEnum(method) + out.writeString(fromAddress) + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/Webhook.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Webhook.kt new file mode 100644 index 00000000..55a4cb3e --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Webhook.kt @@ -0,0 +1,130 @@ +/* + * 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.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.HEADER_PARAMS_TAG +import org.opensearch.commons.notifications.NotificationConstants.URL_TAG +import org.opensearch.commons.utils.STRING_READER +import org.opensearch.commons.utils.STRING_WRITER +import org.opensearch.commons.utils.logger +import org.opensearch.commons.utils.validateUrl +import java.io.IOException + +/** + * Data class representing Webhook channel. + */ +data class Webhook( + val url: String, + val headerParams: Map = mapOf() +) : BaseConfigData { + + init { + require(!Strings.isNullOrEmpty(url)) { "URL is null or empty" } + validateUrl(url) + } + + companion object { + private val log by logger(Webhook::class.java) + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { Webhook(it) } + + /** + * Parser to parse xContent + */ + val xParser = XParser { parse(it) } + + /** + * Creator used in REST communication. + * @param parser XContentParser to deserialize data from. + */ + @JvmStatic + @Throws(IOException::class) + fun parse(parser: XContentParser): Webhook { + var url: String? = null + var headerParams: Map = mapOf() + + XContentParserUtils.ensureExpectedToken( + XContentParser.Token.START_OBJECT, + parser.currentToken(), + parser + ) + while (parser.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = parser.currentName() + parser.nextToken() + when (fieldName) { + URL_TAG -> url = parser.text() + HEADER_PARAMS_TAG -> headerParams = parser.mapStrings() + else -> { + parser.skipChildren() + log.info("Unexpected field: $fieldName, while parsing Webhook destination") + } + } + } + url ?: throw IllegalArgumentException("$URL_TAG field absent") + return Webhook(url, headerParams) + } + } + + /** + * {@inheritDoc} + */ + override fun toXContent(builder: XContentBuilder?, params: ToXContent.Params?): XContentBuilder { + builder!! + return builder.startObject() + .field(URL_TAG, url) + .field(HEADER_PARAMS_TAG, headerParams) + .endObject() + } + + /** + * Constructor used in transport action communication. + * @param input StreamInput stream to deserialize data from. + */ + constructor(input: StreamInput) : this( + url = input.readString(), + headerParams = input.readMap(STRING_READER, STRING_READER) + ) + + /** + * {@inheritDoc} + */ + override fun writeTo(output: StreamOutput) { + output.writeString(url) + output.writeMap(headerParams, STRING_WRITER, STRING_WRITER) + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/XParser.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/XParser.kt new file mode 100644 index 00000000..894b21a9 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/XParser.kt @@ -0,0 +1,40 @@ +/* + * 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.common.xcontent.XContentParser + +/** + * Functional interface to create config data object using XContentParser + */ +fun interface XParser { + /** + * Creator used in REST communication. + * @param parser XContentParser to deserialize data from. + */ + fun parse(parser: XContentParser): V +} 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 new file mode 100644 index 00000000..1500879c --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/config/ConfigDataProperties.kt @@ -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 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.config + +import org.opensearch.common.io.stream.Writeable.Reader +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.commons.notifications.model.BaseConfigData +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.Slack +import org.opensearch.commons.notifications.model.SmtpAccount +import org.opensearch.commons.notifications.model.Webhook +import org.opensearch.commons.notifications.model.XParser + +internal object ConfigDataProperties { + /** + * Properties for ConfigTypes. + * This data class is used to provide contract across configTypes without reading into config data classes. + */ + private data class ConfigProperty( + val configDataReader: Reader, + val configDataParser: XParser + ) + + private val CONFIG_PROPERTIES_MAP = mapOf( + Pair(ConfigType.SLACK, ConfigProperty(Slack.reader, Slack.xParser)), + 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.EMAIL_GROUP, ConfigProperty(EmailGroup.reader, EmailGroup.xParser)), + Pair(ConfigType.SMTP_ACCOUNT, ConfigProperty(SmtpAccount.reader, SmtpAccount.xParser)) + ) + + /** + * Get Reader for provided config type + * @param @ConfigType + * @return Reader + */ + fun getReaderForConfigType(configType: ConfigType): Reader { + return CONFIG_PROPERTIES_MAP[configType]?.configDataReader + ?: throw IllegalArgumentException("Transport action used with unknown ConfigType:$configType") + } + + /** + * Validate config data is of ConfigType + */ + fun validateConfigData(configType: ConfigType, configData: BaseConfigData?): Boolean { + return when (configType) { + ConfigType.SLACK -> configData is Slack + ConfigType.WEBHOOK -> configData is Webhook + ConfigType.EMAIL -> configData is Email + ConfigType.EMAIL_GROUP -> configData is EmailGroup + ConfigType.SMTP_ACCOUNT -> configData is SmtpAccount + ConfigType.CHIME -> configData is Chime + ConfigType.NONE -> true + } + } + + /** + * Creates config data from parser for given configType + * @param configType the ConfigType + * @param parser parser for ConfigType + * @return created BaseConfigData on success. null if configType is not recognized + * + */ + fun createConfigData(configType: ConfigType, parser: XContentParser): BaseConfigData? { + return CONFIG_PROPERTIES_MAP[configType]?.configDataParser?.parse(parser) + } +} diff --git a/src/main/kotlin/org/opensearch/commons/utils/EnumHelpers.kt b/src/main/kotlin/org/opensearch/commons/utils/EnumHelpers.kt new file mode 100644 index 00000000..3a18407e --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/utils/EnumHelpers.kt @@ -0,0 +1,55 @@ +/* + * 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.utils + +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.io.stream.Writeable +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import java.util.EnumSet + +inline fun > XContentParser.enumSet(enumParser: EnumParser): EnumSet { + val retSet: EnumSet = EnumSet.noneOf(E::class.java) + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_ARRAY, currentToken(), this) + while (nextToken() != XContentParser.Token.END_ARRAY) { + retSet.add(enumParser.fromTagOrDefault(text())) + } + return retSet +} + +inline fun > enumReader(enumClass: Class): Writeable.Reader { + return Writeable.Reader { + it.readEnum(enumClass) + } +} + +@Suppress("UnusedPrivateMember") +inline fun > enumWriter(ignore: Class): Writeable.Writer { + return Writeable.Writer { streamOutput: StreamOutput, value: E -> + streamOutput.writeEnum(value) + } +} diff --git a/src/main/kotlin/org/opensearch/commons/utils/EnumParser.kt b/src/main/kotlin/org/opensearch/commons/utils/EnumParser.kt new file mode 100644 index 00000000..2a678f1f --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/utils/EnumParser.kt @@ -0,0 +1,39 @@ +/* + * 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.utils + +/** + * Functional interface to create config data object using XContentParser + */ +fun interface EnumParser { + /** + * Get Enum from tag or default value if not found + * @param tag the tag + * @return Enum corresponding to tag. default value if invalid tag. + */ + fun fromTagOrDefault(tag: String): E +} diff --git a/src/main/kotlin/org/opensearch/commons/utils/Helpers.kt b/src/main/kotlin/org/opensearch/commons/utils/Helpers.kt new file mode 100644 index 00000000..ae08c1d0 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/utils/Helpers.kt @@ -0,0 +1,35 @@ +/* + * 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.utils + +import org.apache.logging.log4j.LogManager +import org.apache.logging.log4j.Logger + +fun logger(forClass: Class): Lazy { + return lazy { LogManager.getLogger(forClass) } +} diff --git a/src/main/kotlin/org/opensearch/commons/utils/OpenForTesting.kt b/src/main/kotlin/org/opensearch/commons/utils/OpenForTesting.kt new file mode 100644 index 00000000..ccbd1536 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/utils/OpenForTesting.kt @@ -0,0 +1,33 @@ +/* + * 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.utils + +/** + * Annotation to elevate access for testing purpose. + */ +annotation class OpenForTesting diff --git a/src/main/kotlin/org/opensearch/commons/utils/SecureClientWrapper.kt b/src/main/kotlin/org/opensearch/commons/utils/SecureClientWrapper.kt new file mode 100644 index 00000000..45fa29bd --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/utils/SecureClientWrapper.kt @@ -0,0 +1,328 @@ +/* + * 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.utils + +import org.opensearch.action.ActionFuture +import org.opensearch.action.ActionListener +import org.opensearch.action.ActionRequest +import org.opensearch.action.ActionResponse +import org.opensearch.action.ActionType +import org.opensearch.action.bulk.BulkRequest +import org.opensearch.action.bulk.BulkResponse +import org.opensearch.action.delete.DeleteRequest +import org.opensearch.action.delete.DeleteResponse +import org.opensearch.action.explain.ExplainRequest +import org.opensearch.action.explain.ExplainResponse +import org.opensearch.action.fieldcaps.FieldCapabilitiesRequest +import org.opensearch.action.fieldcaps.FieldCapabilitiesResponse +import org.opensearch.action.get.GetRequest +import org.opensearch.action.get.GetResponse +import org.opensearch.action.get.MultiGetRequest +import org.opensearch.action.get.MultiGetResponse +import org.opensearch.action.index.IndexRequest +import org.opensearch.action.index.IndexResponse +import org.opensearch.action.search.ClearScrollRequest +import org.opensearch.action.search.ClearScrollResponse +import org.opensearch.action.search.MultiSearchRequest +import org.opensearch.action.search.MultiSearchResponse +import org.opensearch.action.search.SearchRequest +import org.opensearch.action.search.SearchResponse +import org.opensearch.action.search.SearchScrollRequest +import org.opensearch.action.termvectors.MultiTermVectorsRequest +import org.opensearch.action.termvectors.MultiTermVectorsResponse +import org.opensearch.action.termvectors.TermVectorsRequest +import org.opensearch.action.termvectors.TermVectorsResponse +import org.opensearch.action.update.UpdateRequest +import org.opensearch.action.update.UpdateResponse +import org.opensearch.client.Client +import org.opensearch.common.util.concurrent.ThreadContext + +/** + * Wrapper class on [Client] with security context removed. + */ +@Suppress("TooManyFunctions") +class SecureClientWrapper(private val client: Client) : Client by client { + /** + * {@inheritDoc} + */ + override fun execute( + action: ActionType, + request: Request + ): ActionFuture { + client.threadPool().threadContext.stashContext().use { return client.execute(action, request) } + } + + /** + * {@inheritDoc} + */ + override fun execute( + action: ActionType, + request: Request, + listener: ActionListener + ) { + client.threadPool().threadContext.stashContext().use { return client.execute(action, request, listener) } + } + + /** + * {@inheritDoc} + */ + override fun index(request: IndexRequest): ActionFuture { + client.threadPool().threadContext.stashContext().use { return client.index(request) } + } + + /** + * {@inheritDoc} + */ + override fun index(request: IndexRequest, listener: ActionListener) { + client.threadPool().threadContext.stashContext().use { return client.index(request, listener) } + } + + /** + * {@inheritDoc} + */ + override fun update(request: UpdateRequest): ActionFuture { + client.threadPool().threadContext.stashContext().use { return client.update(request) } + } + + /** + * {@inheritDoc} + */ + override fun update(request: UpdateRequest, listener: ActionListener) { + client.threadPool().threadContext.stashContext().use { return client.update(request, listener) } + } + + /** + * {@inheritDoc} + */ + override fun delete(request: DeleteRequest): ActionFuture { + client.threadPool().threadContext.stashContext().use { return client.delete(request) } + } + + /** + * {@inheritDoc} + */ + override fun delete(request: DeleteRequest, listener: ActionListener) { + client.threadPool().threadContext.stashContext().use { return client.delete(request, listener) } + } + + /** + * {@inheritDoc} + */ + override fun bulk(request: BulkRequest): ActionFuture { + client.threadPool().threadContext.stashContext().use { return client.bulk(request) } + } + + /** + * {@inheritDoc} + */ + override fun bulk(request: BulkRequest, listener: ActionListener) { + client.threadPool().threadContext.stashContext().use { return client.bulk(request, listener) } + } + + /** + * {@inheritDoc} + */ + override fun get(request: GetRequest): ActionFuture { + client.threadPool().threadContext.stashContext().use { return client.get(request) } + } + + /** + * {@inheritDoc} + */ + override fun get(request: GetRequest, listener: ActionListener) { + client.threadPool().threadContext.stashContext().use { return client.get(request, listener) } + } + + /** + * {@inheritDoc} + */ + override fun multiGet(request: MultiGetRequest): ActionFuture { + client.threadPool().threadContext.stashContext().use { return client.multiGet(request) } + } + + /** + * {@inheritDoc} + */ + override fun multiGet(request: MultiGetRequest, listener: ActionListener) { + client.threadPool().threadContext.stashContext().use { return client.multiGet(request, listener) } + } + + /** + * {@inheritDoc} + */ + override fun search(request: SearchRequest): ActionFuture { + client.threadPool().threadContext.stashContext().use { return client.search(request) } + } + + /** + * {@inheritDoc} + */ + override fun search(request: SearchRequest, listener: ActionListener) { + client.threadPool().threadContext.stashContext().use { return client.search(request, listener) } + } + + /** + * {@inheritDoc} + */ + override fun searchScroll(request: SearchScrollRequest): ActionFuture { + client.threadPool().threadContext.stashContext().use { return client.searchScroll(request) } + } + + /** + * {@inheritDoc} + */ + override fun searchScroll(request: SearchScrollRequest, listener: ActionListener) { + client.threadPool().threadContext.stashContext().use { return client.searchScroll(request, listener) } + } + + /** + * {@inheritDoc} + */ + override fun multiSearch(request: MultiSearchRequest): ActionFuture { + client.threadPool().threadContext.stashContext().use { return client.multiSearch(request) } + } + + /** + * {@inheritDoc} + */ + override fun multiSearch(request: MultiSearchRequest, listener: ActionListener) { + client.threadPool().threadContext.stashContext().use { return client.multiSearch(request, listener) } + } + + /** + * {@inheritDoc} + */ + override fun termVectors(request: TermVectorsRequest): ActionFuture { + client.threadPool().threadContext.stashContext().use { return client.termVectors(request) } + } + + /** + * {@inheritDoc} + */ + override fun termVectors(request: TermVectorsRequest, listener: ActionListener) { + client.threadPool().threadContext.stashContext().use { return client.termVectors(request, listener) } + } + + /** + * {@inheritDoc} + */ + override fun multiTermVectors(request: MultiTermVectorsRequest): ActionFuture { + client.threadPool().threadContext.stashContext().use { return client.multiTermVectors(request) } + } + + /** + * {@inheritDoc} + */ + override fun multiTermVectors( + request: MultiTermVectorsRequest, + listener: ActionListener + ) { + client.threadPool().threadContext.stashContext().use { return client.multiTermVectors(request, listener) } + } + + /** + * {@inheritDoc} + */ + override fun explain(request: ExplainRequest): ActionFuture { + client.threadPool().threadContext.stashContext().use { return client.explain(request) } + } + + /** + * {@inheritDoc} + */ + override fun explain(request: ExplainRequest, listener: ActionListener) { + client.threadPool().threadContext.stashContext().use { return client.explain(request, listener) } + } + + /** + * {@inheritDoc} + */ + override fun clearScroll(request: ClearScrollRequest): ActionFuture { + client.threadPool().threadContext.stashContext().use { return client.clearScroll(request) } + } + + /** + * {@inheritDoc} + */ + override fun clearScroll(request: ClearScrollRequest, listener: ActionListener) { + client.threadPool().threadContext.stashContext().use { return client.clearScroll(request, listener) } + } + + /** + * {@inheritDoc} + */ + override fun fieldCaps(request: FieldCapabilitiesRequest): ActionFuture { + client.threadPool().threadContext.stashContext().use { return client.fieldCaps(request) } + } + + /** + * {@inheritDoc} + */ + override fun fieldCaps(request: FieldCapabilitiesRequest, listener: ActionListener) { + client.threadPool().threadContext.stashContext().use { return client.fieldCaps(request, listener) } + } + + /** + * Executes the given [block] function on this resource and then closes it down correctly whether an exception + * is thrown or not. + * + * In case if the resource is being closed due to an exception occurred in [block], and the closing also fails with an exception, + * the latter is added to the [suppressed][java.lang.Throwable.addSuppressed] exceptions of the former. + * + * @param block a function to process this [AutoCloseable] resource. + * @return the result of [block] function invoked on this resource. + */ + @Suppress("TooGenericExceptionCaught") + private inline fun T.use(block: (T) -> R): R { + var exception: Throwable? = null + try { + return block(this) + } catch (e: Throwable) { + exception = e + throw e + } finally { + closeFinally(exception) + } + } + + /** + * Closes this [AutoCloseable], suppressing possible exception or error thrown by [AutoCloseable.close] function when + * it's being closed due to some other [cause] exception occurred. + * + * The suppressed exception is added to the list of suppressed exceptions of [cause] exception. + */ + @Suppress("TooGenericExceptionCaught") + private fun ThreadContext.StoredContext.closeFinally(cause: Throwable?) = when (cause) { + null -> close() + else -> try { + close() + } catch (closeException: Throwable) { + cause.addSuppressed(closeException) + } + } +} diff --git a/src/main/kotlin/org/opensearch/commons/utils/TransportHelpers.kt b/src/main/kotlin/org/opensearch/commons/utils/TransportHelpers.kt new file mode 100644 index 00000000..4763a48a --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/utils/TransportHelpers.kt @@ -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 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.utils + +import org.opensearch.common.io.stream.InputStreamStreamInput +import org.opensearch.common.io.stream.OutputStreamStreamOutput +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.io.stream.Writeable +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream + +val STRING_READER = Writeable.Reader { + it.readString() +} + +val STRING_WRITER = Writeable.Writer { output: StreamOutput, value: String -> + output.writeString(value) +} + +/** + * Re create the object from the writeable. + * This method needs to be inline and reified so that when this is called from + * doExecute() of transport action, the object may be created from other JVM. + */ +inline fun recreateObject(writeable: Writeable, block: (StreamInput) -> Request): Request { + ByteArrayOutputStream().use { byteArrayOutputStream -> + OutputStreamStreamOutput(byteArrayOutputStream).use { + writeable.writeTo(it) + InputStreamStreamInput(ByteArrayInputStream(byteArrayOutputStream.toByteArray())).use { streamInput -> + return block(streamInput) + } + } + } +} diff --git a/src/main/kotlin/org/opensearch/commons/utils/ValidationHelpers.kt b/src/main/kotlin/org/opensearch/commons/utils/ValidationHelpers.kt new file mode 100644 index 00000000..93bcdc77 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/utils/ValidationHelpers.kt @@ -0,0 +1,75 @@ +/* + * 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.utils + +import java.net.URL + +// Valid ID characters = (All Base64 chars + "_-") to support UUID format and Base64 encoded IDs +private val VALID_ID_CHARS: Set = (('a'..'z') + ('A'..'Z') + ('0'..'9') + '+' + '/' + '_' + '-').toSet() + +fun validateUrl(urlString: String) { + require(isValidUrl(urlString)) { "Invalid URL or unsupported" } + val url = URL(urlString) + require("https" == url.protocol) // Support only HTTPS. HTTP and other protocols not supported + // TODO : Add hosts deny list +} + +fun validateEmail(email: String) { + require(isValidEmail(email)) { "Invalid email address" } +} + +fun validateId(idString: String) { + require(isValidId(idString)) { "Invalid characters in id : ${idString.filterNot { VALID_ID_CHARS.contains(it) }}" } +} + +fun isValidUrl(urlString: String): Boolean { + val url = URL(urlString) // throws MalformedURLException if URL is invalid + // TODO : Add hosts deny list + return ("https" == url.protocol) // Support only HTTPS. HTTP and other protocols not supported +} + +/** + * RFC 5322 compliant pattern matching: https://www.ietf.org/rfc/rfc5322.txt + * Regex was based off of this post: https://stackoverflow.com/a/201378 + */ +fun isValidEmail(email: String): Boolean { + val validEmailPattern = Regex( + "(?:[a-z0-9!#\$%&'*+\\/=?^_`{|}~-]+(?:\\.[a-z0-9!#\$%&'*+\\/=?^_`{|}~-]+)*" + + "|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")" + + "@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?" + + "|\\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\\.){3}" + + "(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:" + + "(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])", + RegexOption.IGNORE_CASE + ) + return validEmailPattern.matches(email) +} + +fun isValidId(idString: String): Boolean { + return idString.isNotBlank() && idString.all { VALID_ID_CHARS.contains(it) } +} diff --git a/src/main/kotlin/org/opensearch/commons/utils/XContentHelpers.kt b/src/main/kotlin/org/opensearch/commons/utils/XContentHelpers.kt new file mode 100644 index 00000000..92dee033 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/utils/XContentHelpers.kt @@ -0,0 +1,83 @@ +/* + * 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.utils + +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.xcontent.DeprecationHandler +import org.opensearch.common.xcontent.NamedXContentRegistry +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.ToXContentObject +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.common.xcontent.XContentType +import org.opensearch.rest.RestRequest + +fun StreamInput.createJsonParser(): XContentParser { + return XContentType.JSON.xContent() + .createParser(NamedXContentRegistry.EMPTY, DeprecationHandler.IGNORE_DEPRECATIONS, this) +} + +fun RestRequest.contentParserNextToken(): XContentParser { + val parser = this.contentParser() + parser.nextToken() + return parser +} + +fun XContentParser.stringList(): List { + val retList: MutableList = mutableListOf() + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_ARRAY, currentToken(), this) + while (nextToken() != XContentParser.Token.END_ARRAY) { + retList.add(text()) + } + return retList +} + +fun XContentBuilder.fieldIfNotNull(name: String, value: Any?): XContentBuilder { + if (value != null) { + this.field(name, value) + } + return this +} + +fun XContentBuilder.objectIfNotNull(name: String, xContentObject: ToXContentObject?): XContentBuilder { + if (xContentObject != null) { + this.field(name) + xContentObject.toXContent(this, ToXContent.EMPTY_PARAMS) + } + return this +} + +fun XContentParser.objectList(block: (XContentParser) -> T): List { + val retList: MutableList = mutableListOf() + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_ARRAY, currentToken(), this) + while (nextToken() != XContentParser.Token.END_ARRAY) { + retList.add(block(this)) + } + return retList +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequestTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequestTests.kt new file mode 100644 index 00000000..679ded46 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigRequestTests.kt @@ -0,0 +1,533 @@ +/* + * 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.action + +import com.fasterxml.jackson.core.JsonParseException +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.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 +import org.opensearch.commons.notifications.model.SmtpAccount +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 { + + private fun createWebhookContentConfigObject(): NotificationConfig { + val sampleWebhook = Webhook("https://domain.com/sample_webhook_url#1234567890") + return NotificationConfig( + "name", + "description", + ConfigType.WEBHOOK, + EnumSet.of(Feature.INDEX_MANAGEMENT), + isEnabled = true, + configData = sampleWebhook + ) + } + + private fun createSlackContentConfigObject(): NotificationConfig { + val sampleSlack = Slack("https://domain.com/sample_slack_url#1234567890") + return NotificationConfig( + "name", + "description", + ConfigType.SLACK, + EnumSet.of(Feature.INDEX_MANAGEMENT), + isEnabled = true, + configData = sampleSlack + ) + } + + private fun createChimeContentConfigObject(): NotificationConfig { + val sampleChime = Chime("https://domain.com/sample_chime_url#1234567890") + return NotificationConfig( + "name", + "description", + ConfigType.CHIME, + EnumSet.of(Feature.INDEX_MANAGEMENT), + isEnabled = true, + configData = sampleChime + ) + } + + private fun createEmailGroupContentConfigObject(): NotificationConfig { + val sampleEmailGroup = EmailGroup(listOf("dummy@company.com")) + return NotificationConfig( + "name", + "description", + ConfigType.EMAIL_GROUP, + EnumSet.of(Feature.INDEX_MANAGEMENT), + isEnabled = true, + configData = sampleEmailGroup + ) + } + + private fun createEmailContentConfigObject(): NotificationConfig { + val sampleEmail = Email( + emailAccountID = "sample_1@dummy.com", + recipients = listOf("sample_2@dummy.com"), + emailGroupIds = listOf("sample_3@dummy.com") + ) + return NotificationConfig( + "name", + "description", + ConfigType.EMAIL, + EnumSet.of(Feature.INDEX_MANAGEMENT), + isEnabled = true, + configData = sampleEmail + ) + } + + private fun createSmtpAccountContentConfigObject(): NotificationConfig { + val sampleSmtpAccount = SmtpAccount( + host = "http://dummy.com", + port = 11, + method = MethodType.SSL, + fromAddress = "sample@dummy.com" + ) + return NotificationConfig( + "name", + "description", + ConfigType.SMTP_ACCOUNT, + EnumSet.of(Feature.INDEX_MANAGEMENT), + isEnabled = true, + configData = sampleSmtpAccount + ) + } + + @Test + fun `Create config serialize and deserialize transport object should be equal webhook`() { + val configRequest = CreateNotificationConfigRequest( + createWebhookContentConfigObject() + ) + val recreatedObject = + recreateObject(configRequest) { + CreateNotificationConfigRequest( + it + ) + } + assertNull(recreatedObject.validate()) + assertEquals(configRequest.notificationConfig, recreatedObject.notificationConfig) + } + + @Test + fun `Create config serialize and deserialize transport object should be equal slack`() { + val configRequest = CreateNotificationConfigRequest( + createSlackContentConfigObject() + ) + val recreatedObject = + recreateObject(configRequest) { + CreateNotificationConfigRequest( + it + ) + } + assertNull(recreatedObject.validate()) + assertEquals(configRequest.notificationConfig, recreatedObject.notificationConfig) + } + + @Test + fun `Create config serialize and deserialize transport object should be equal chime`() { + val configRequest = CreateNotificationConfigRequest( + createChimeContentConfigObject() + ) + val recreatedObject = + recreateObject(configRequest) { + CreateNotificationConfigRequest( + it + ) + } + assertNull(recreatedObject.validate()) + assertEquals(configRequest.notificationConfig, recreatedObject.notificationConfig) + } + + @Test + fun `Create config serialize and deserialize transport object should be equal email`() { + val configRequest = CreateNotificationConfigRequest( + createEmailContentConfigObject() + ) + val recreatedObject = + recreateObject(configRequest) { + CreateNotificationConfigRequest( + it + ) + } + assertNull(recreatedObject.validate()) + assertEquals(configRequest.notificationConfig, recreatedObject.notificationConfig) + } + + @Test + fun `Create config serialize and deserialize transport object should be equal emailGroup`() { + val configRequest = CreateNotificationConfigRequest( + createEmailGroupContentConfigObject() + ) + val recreatedObject = + recreateObject(configRequest) { + CreateNotificationConfigRequest( + it + ) + } + assertNull(recreatedObject.validate()) + assertEquals(configRequest.notificationConfig, recreatedObject.notificationConfig) + } + + @Test + fun `Create config serialize and deserialize transport object should be equal SmtpAccount`() { + val configRequest = CreateNotificationConfigRequest( + createSmtpAccountContentConfigObject() + ) + val recreatedObject = + recreateObject(configRequest) { + CreateNotificationConfigRequest( + it + ) + } + assertNull(recreatedObject.validate()) + assertEquals(configRequest.notificationConfig, recreatedObject.notificationConfig) + } + + @Test + fun `Create config serialize and deserialize using json object should be equal`() { + val configRequest = CreateNotificationConfigRequest( + createWebhookContentConfigObject() + ) + val jsonString = getJsonString(configRequest) + val recreatedObject = createObjectFromJsonString(jsonString) { CreateNotificationConfigRequest.parse(it) } + assertEquals(configRequest.notificationConfig, recreatedObject.notificationConfig) + } + + @Test + fun `Create config serialize and deserialize using json object should be equal slack`() { + val configRequest = CreateNotificationConfigRequest( + createSlackContentConfigObject() + ) + val jsonString = getJsonString(configRequest) + val recreatedObject = createObjectFromJsonString(jsonString) { CreateNotificationConfigRequest.parse(it) } + assertEquals(configRequest.notificationConfig, recreatedObject.notificationConfig) + } + + @Test + fun `Create config serialize and deserialize using json object should be equal chime`() { + val configRequest = CreateNotificationConfigRequest( + createChimeContentConfigObject() + ) + val jsonString = getJsonString(configRequest) + val recreatedObject = createObjectFromJsonString(jsonString) { CreateNotificationConfigRequest.parse(it) } + assertEquals(configRequest.notificationConfig, recreatedObject.notificationConfig) + } + + @Test + fun `Create config serialize and deserialize using json object should be equal email`() { + val configRequest = CreateNotificationConfigRequest( + createEmailContentConfigObject() + ) + val jsonString = getJsonString(configRequest) + val recreatedObject = createObjectFromJsonString(jsonString) { CreateNotificationConfigRequest.parse(it) } + assertEquals(configRequest.notificationConfig, recreatedObject.notificationConfig) + } + + @Test + fun `Create config serialize and deserialize using json object should be equal EmailGroup`() { + val configRequest = CreateNotificationConfigRequest( + createEmailGroupContentConfigObject() + ) + val jsonString = getJsonString(configRequest) + val recreatedObject = createObjectFromJsonString(jsonString) { CreateNotificationConfigRequest.parse(it) } + assertEquals(configRequest.notificationConfig, recreatedObject.notificationConfig) + } + + @Test + fun `Create config serialize and deserialize using json object should be equal SmtpAccount`() { + val configRequest = CreateNotificationConfigRequest( + createSmtpAccountContentConfigObject() + ) + val jsonString = getJsonString(configRequest) + val recreatedObject = createObjectFromJsonString(jsonString) { CreateNotificationConfigRequest.parse(it) } + assertEquals(configRequest.notificationConfig, recreatedObject.notificationConfig) + } + + @Test + fun `Create config should deserialize json object using parser slack`() { + val sampleSlack = Slack("https://domain.com/sample_slack_url#1234567890") + val config = NotificationConfig( + "name", + "description", + ConfigType.SLACK, + EnumSet.of(Feature.INDEX_MANAGEMENT), + isEnabled = true, + configData = sampleSlack + ) + + val jsonString = """ + { + "config":{ + "name":"name", + "description":"description", + "config_type":"slack", + "feature_list":["index_management"], + "is_enabled":true, + "slack":{"url":"https://domain.com/sample_slack_url#1234567890"} + } + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { CreateNotificationConfigRequest.parse(it) } + assertEquals(config, recreatedObject.notificationConfig) + } + + @Test + fun `Create config should deserialize json object using parser webhook`() { + val sampleWebhook = Webhook("https://domain.com/sample_webhook_url#1234567890") + val config = NotificationConfig( + "name", + "description", + ConfigType.WEBHOOK, + EnumSet.of(Feature.INDEX_MANAGEMENT), + isEnabled = true, + configData = sampleWebhook + ) + + val jsonString = """ + { + "config":{ + "name":"name", + "description":"description", + "config_type":"webhook", + "feature_list":["index_management"], + "is_enabled":true, + "webhook":{"url":"https://domain.com/sample_webhook_url#1234567890"} + } + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { CreateNotificationConfigRequest.parse(it) } + assertEquals(config, recreatedObject.notificationConfig) + } + + @Test + fun `Create config should deserialize json object using parser Chime`() { + val sampleChime = Chime("https://domain.com/sample_chime_url#1234567890") + val config = NotificationConfig( + "name", + "description", + ConfigType.CHIME, + EnumSet.of(Feature.INDEX_MANAGEMENT), + isEnabled = true, + configData = sampleChime + ) + + val jsonString = """ + { + "config_id":"config_id1", + "config":{ + "name":"name", + "description":"description", + "config_type":"chime", + "feature_list":["index_management"], + "is_enabled":true, + "chime":{"url":"https://domain.com/sample_chime_url#1234567890"} + } + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { CreateNotificationConfigRequest.parse(it) } + assertEquals(config, recreatedObject.notificationConfig) + } + + @Test + fun `Create config should deserialize json object using parser Email Group`() { + val sampleEmailGroup = EmailGroup(listOf("dummy@company.com")) + val config = NotificationConfig( + "name", + "description", + ConfigType.EMAIL_GROUP, + EnumSet.of(Feature.INDEX_MANAGEMENT), + isEnabled = true, + configData = sampleEmailGroup + ) + + val jsonString = """ + { + "config_id":"config_id1", + "config":{ + "name":"name", + "description":"description", + "config_type":"email_group", + "feature_list":["index_management"], + "is_enabled":true, + "email_group":{"recipient_list":["dummy@company.com"]} + } + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { CreateNotificationConfigRequest.parse(it) } + assertEquals(config, recreatedObject.notificationConfig) + } + + @Test + fun `Update config should deserialize json object using parser Email`() { + val sampleEmail = Email( + emailAccountID = "sample_1@dummy.com", + recipients = listOf("sample_2@dummy.com"), + emailGroupIds = listOf("sample_3@dummy.com") + ) + val config = NotificationConfig( + "name", + "description", + ConfigType.EMAIL, + EnumSet.of(Feature.INDEX_MANAGEMENT), + isEnabled = true, + configData = sampleEmail + ) + + val jsonString = """ + { + "config_id":"config_id1", + "config":{ + "name":"name", + "description":"description", + "config_type":"email", + "feature_list":["index_management"], + "is_enabled":true, + "email":{"email_account_id":"sample_1@dummy.com","recipient_list":["sample_2@dummy.com"], + "email_group_id_list":["sample_3@dummy.com"] } + } + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { CreateNotificationConfigRequest.parse(it) } + assertEquals(config, recreatedObject.notificationConfig) + } + + @Test + fun `Update config should deserialize json object using parser SmtpAccount`() { + val sampleSmtpAccount = SmtpAccount( + host = "http://dummy.com", + port = 11, + method = MethodType.SSL, + fromAddress = "sample@dummy.com" + ) + val config = NotificationConfig( + "name", + "description", + ConfigType.SMTP_ACCOUNT, + EnumSet.of(Feature.INDEX_MANAGEMENT), + isEnabled = true, + configData = sampleSmtpAccount + ) + + val jsonString = """ + { + "config_id":"config_id1", + "config":{ + "name":"name", + "description":"description", + "config_type":"smtp_account", + "feature_list":["index_management"], + "is_enabled":true, + "smtp_account":{"host":"http://dummy.com", "port":11,"method": "ssl", "from_address": "sample@dummy.com" } + } + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { CreateNotificationConfigRequest.parse(it) } + assertEquals(config, recreatedObject.notificationConfig) + } + + @Test + fun `Create config should throw exception when invalid json object is passed`() { + val jsonString = "sample message" + assertThrows { + createObjectFromJsonString(jsonString) { CreateNotificationConfigRequest.parse(it) } + } + } + + private fun validateSpecialCharsInIdFails(char: Char) { + val str = when (char) { + '"' -> "\\\"" + '\\' -> "\\\\" + else -> "$char" + } + val jsonString = """ + { + "config_id":"config_id1$str", + "config":{ + "name":"name", + "description":"description", + "config_type":"chime", + "feature_list":["index_management"], + "is_enabled":true, + "chime":{"url":"https://domain.com/sample_chime_url#1234567890"} + } + } + """.trimIndent() + assertThrows("Should not accept char:$char") { + createObjectFromJsonString(jsonString) { CreateNotificationConfigRequest.parse(it) } + } + } + + @Test + fun `Create config with special char in config_id should fail`() { + "`~!@#$%^&*()=[]{}|:;'<>,.?\\\"".forEach { + validateSpecialCharsInIdFails(it) + } + } + + @Test + fun `Create config should safely ignore extra field in json object`() { + val sampleSlack = Slack("https://domain.com/sample_slack_url#1234567890") + val config = NotificationConfig( + "name", + "description", + ConfigType.SLACK, + EnumSet.of(Feature.INDEX_MANAGEMENT), + isEnabled = true, + configData = sampleSlack + ) + + val jsonString = """ + { + "config":{ + "name":"name", + "description":"description", + "config_type":"slack", + "feature_list":["index_management"], + "is_enabled":true, + "slack":{"url":"https://domain.com/sample_slack_url#1234567890"}, + "extra_field_1":["extra", "value"], + "extra_field_2":{"extra":"value"}, + "extra_field_3":"extra value 3" + } + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { CreateNotificationConfigRequest.parse(it) } + assertEquals(config, recreatedObject.notificationConfig) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigResponseTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigResponseTests.kt new file mode 100644 index 00000000..b9f0f4e1 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/CreateNotificationConfigResponseTests.kt @@ -0,0 +1,92 @@ +/* + * 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.action + +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 CreateNotificationConfigResponseTests { + + @Test + fun `Create response serialize and deserialize transport object should be equal`() { + val configResponse = CreateNotificationConfigResponse("sample_config_id") + val recreatedObject = recreateObject(configResponse) { CreateNotificationConfigResponse(it) } + assertEquals(configResponse.configId, recreatedObject.configId) + } + + @Test + fun `Create response serialize and deserialize using json object should be equal`() { + val configResponse = CreateNotificationConfigResponse("sample_config_id") + val jsonString = getJsonString(configResponse) + val recreatedObject = createObjectFromJsonString(jsonString) { CreateNotificationConfigResponse.parse(it) } + assertEquals(configResponse.configId, recreatedObject.configId) + } + + @Test + fun `Create response should deserialize json object using parser`() { + val configId = "sample_config_id" + val jsonString = "{\"config_id\":\"$configId\"}" + val recreatedObject = createObjectFromJsonString(jsonString) { CreateNotificationConfigResponse.parse(it) } + assertEquals(configId, recreatedObject.configId) + } + + @Test + fun `Create response should throw exception when invalid json object is passed`() { + val jsonString = "sample message" + assertThrows { + createObjectFromJsonString(jsonString) { CreateNotificationConfigResponse.parse(it) } + } + } + + @Test + fun `Create response should throw exception when configId is replace with configId2 in json object`() { + val jsonString = "{\"config_id2\":\"sample_config_id\"}" + assertThrows { + createObjectFromJsonString(jsonString) { CreateNotificationConfigResponse.parse(it) } + } + } + + @Test + fun `Create response should safely ignore extra field in json object`() { + val configId = "sample_config_id" + val jsonString = """ + { + "config_id":"$configId", + "extra_field_1":["extra", "value"], + "extra_field_2":{"extra":"value"}, + "extra_field_3":"extra value 3" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { CreateNotificationConfigResponse.parse(it) } + assertEquals(configId, recreatedObject.configId) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigRequestTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigRequestTests.kt new file mode 100644 index 00000000..4c0c016d --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigRequestTests.kt @@ -0,0 +1,102 @@ +/* + * 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.action + +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 DeleteNotificationConfigRequestTests { + + @Test + fun `Delete request serialize and deserialize transport object should be equal`() { + val deleteRequest = DeleteNotificationConfigRequest(setOf("sample_config_id")) + val recreatedObject = recreateObject(deleteRequest) { DeleteNotificationConfigRequest(it) } + assertEquals(deleteRequest.configIds, recreatedObject.configIds) + } + + @Test + fun `Delete request serialize and deserialize using json object should be equal`() { + val deleteRequest = DeleteNotificationConfigRequest(setOf("sample_config_id")) + val jsonString = getJsonString(deleteRequest) + val recreatedObject = createObjectFromJsonString(jsonString) { DeleteNotificationConfigRequest.parse(it) } + assertEquals(deleteRequest.configIds, recreatedObject.configIds) + } + + @Test + fun `Delete request should deserialize json object using parser`() { + val configId = "sample_config_id" + val configIds = setOf(configId) + val jsonString = """ + { + "config_id_list":["$configId"] + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { DeleteNotificationConfigRequest.parse(it) } + assertEquals(configIds, recreatedObject.configIds) + } + + @Test + fun `Delete request should throw exception when invalid json object is passed`() { + val jsonString = "sample message" + assertThrows { + createObjectFromJsonString(jsonString) { DeleteNotificationConfigRequest.parse(it) } + } + } + + @Test + fun `Delete request should throw exception when config_id_lists is replace with config_id_lists2 in json object`() { + val jsonString = """ + { + "config_id_lists":["sample_config_id"] + } + """.trimIndent() + assertThrows { + createObjectFromJsonString(jsonString) { DeleteNotificationConfigRequest.parse(it) } + } + } + + @Test + fun `Delete request should safely ignore extra field in json object`() { + val configId = "sample_config_id" + val configIds = setOf(configId) + val jsonString = """ + { + "config_id_list":["$configId"], + "extra_field_1":["extra", "value"], + "extra_field_2":{"extra":"value"}, + "extra_field_3":"extra value 3" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { DeleteNotificationConfigRequest.parse(it) } + assertEquals(configIds, recreatedObject.configIds) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigResponseTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigResponseTests.kt new file mode 100644 index 00000000..5c479b47 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/DeleteNotificationConfigResponseTests.kt @@ -0,0 +1,103 @@ +/* + * 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.action + +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 +import org.opensearch.rest.RestStatus + +internal class DeleteNotificationConfigResponseTests { + + @Test + fun `Delete response serialize and deserialize transport object should be equal`() { + val configResponse = DeleteNotificationConfigResponse(mapOf(Pair("sample_config_id", RestStatus.OK))) + val recreatedObject = recreateObject(configResponse) { DeleteNotificationConfigResponse(it) } + assertEquals(configResponse.configIdToStatus, recreatedObject.configIdToStatus) + } + + @Test + fun `Delete response serialize and deserialize using json object should be equal`() { + val configResponse = DeleteNotificationConfigResponse(mapOf(Pair("sample_config_id", RestStatus.OK))) + val jsonString = getJsonString(configResponse) + val recreatedObject = createObjectFromJsonString(jsonString) { DeleteNotificationConfigResponse.parse(it) } + assertEquals(configResponse.configIdToStatus, recreatedObject.configIdToStatus) + } + + @Test + fun `Delete response should deserialize json object using parser`() { + val configId = "sample_config_id" + val configResponse = DeleteNotificationConfigResponse(mapOf(Pair(configId, RestStatus.OK))) + val jsonString = """ + { + "delete_response_list":{ + "$configId":"OK" + } + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { DeleteNotificationConfigResponse.parse(it) } + assertEquals(configResponse.configIdToStatus, recreatedObject.configIdToStatus) + } + + @Test + fun `Delete response should throw exception when invalid json object is passed`() { + val jsonString = "sample message" + assertThrows { + createObjectFromJsonString(jsonString) { DeleteNotificationConfigResponse.parse(it) } + } + } + + @Test + fun `Delete response should throw exception when configId is replace with configId2 in json object`() { + val jsonString = "{\"config_id2\":\"sample_config_id\"}" + assertThrows { + createObjectFromJsonString(jsonString) { DeleteNotificationConfigResponse.parse(it) } + } + } + + @Test + fun `Delete response should safely ignore extra field in json object`() { + val configId = "sample_config_id" + val configResponse = DeleteNotificationConfigResponse(mapOf(Pair(configId, RestStatus.OK))) + val jsonString = """ + { + "delete_response_list":{ + "$configId":"OK" + }, + "extra_field_1":["extra", "value"], + "extra_field_2":{"extra":"value"}, + "extra_field_3":"extra value 3" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { DeleteNotificationConfigResponse.parse(it) } + assertEquals(configResponse.configIdToStatus, recreatedObject.configIdToStatus) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequestTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequestTests.kt new file mode 100644 index 00000000..c9788a80 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListRequestTests.kt @@ -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 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.action + +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.utils.createObjectFromJsonString +import org.opensearch.commons.utils.getJsonString +import org.opensearch.commons.utils.recreateObject + +internal class GetFeatureChannelListRequestTests { + + private fun assertGetRequestEquals( + expected: GetFeatureChannelListRequest, + actual: GetFeatureChannelListRequest + ) { + assertEquals(expected.feature, actual.feature) + } + + @Test + fun `Get request serialize and deserialize transport object should be equal`() { + 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 jsonString = getJsonString(configRequest) + val recreatedObject = createObjectFromJsonString(jsonString) { GetFeatureChannelListRequest.parse(it) } + assertGetRequestEquals(configRequest, recreatedObject) + } + + @Test + fun `Get request should throw exception when invalid json object is passed`() { + val jsonString = "sample message" + assertThrows { + createObjectFromJsonString(jsonString) { GetFeatureChannelListRequest.parse(it) } + } + } + + @Test + fun `Get request should safely ignore extra field in json object`() { + val configRequest = GetFeatureChannelListRequest(Feature.ALERTING) + val jsonString = """ + { + "feature":"${configRequest.feature}", + "extra_field_1":["extra", "value"], + "extra_field_2":{"extra":"value"}, + "extra_field_3":"extra value 3" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { GetFeatureChannelListRequest.parse(it) } + assertGetRequestEquals(configRequest, recreatedObject) + } + + @Test + fun `Get request should throw exception if feature field is absent in json object`() { + val jsonString = """ + { + } + """.trimIndent() + assertThrows { + createObjectFromJsonString(jsonString) { GetFeatureChannelListRequest.parse(it) } + } + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListResponseTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListResponseTests.kt new file mode 100644 index 00000000..149e966f --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/GetFeatureChannelListResponseTests.kt @@ -0,0 +1,245 @@ +/* + * 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.action + +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.model.ConfigType +import org.opensearch.commons.notifications.model.FeatureChannel +import org.opensearch.commons.notifications.model.FeatureChannelList +import org.opensearch.commons.utils.createObjectFromJsonString +import org.opensearch.commons.utils.getJsonString +import org.opensearch.commons.utils.recreateObject + +internal class GetFeatureChannelListResponseTests { + + private fun assertSearchResultEquals( + expected: FeatureChannelList, + actual: FeatureChannelList + ) { + assertEquals(expected.startIndex, actual.startIndex) + assertEquals(expected.totalHits, actual.totalHits) + assertEquals(expected.totalHitRelation, actual.totalHitRelation) + assertEquals(expected.objectListFieldName, actual.objectListFieldName) + assertEquals(expected.objectList, actual.objectList) + } + + @Test + fun `Get Response serialize and deserialize with config object should be equal`() { + val sampleConfig = FeatureChannel( + "config_id", + "name", + "description", + ConfigType.SLACK + ) + val searchResult = FeatureChannelList(sampleConfig) + val getResponse = GetFeatureChannelListResponse(searchResult) + val recreatedObject = recreateObject(getResponse) { GetFeatureChannelListResponse(it) } + assertSearchResultEquals(searchResult, recreatedObject.searchResult) + } + + @Test + fun `Get Response serialize and deserialize with multiple config object should be equal`() { + val sampleConfig1 = FeatureChannel( + "config_id1", + "name1", + "description1", + ConfigType.SLACK + ) + val sampleConfig2 = FeatureChannel( + "config_id2", + "name2", + "description2", + ConfigType.CHIME + ) + val sampleConfig3 = FeatureChannel( + "config_id3", + "name3", + "description3", + ConfigType.WEBHOOK + ) + val searchResult = FeatureChannelList( + 100, + 1000, + TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO, + listOf(sampleConfig1, sampleConfig2, sampleConfig3) + ) + val getResponse = GetFeatureChannelListResponse(searchResult) + val recreatedObject = recreateObject(getResponse) { GetFeatureChannelListResponse(it) } + assertSearchResultEquals(searchResult, recreatedObject.searchResult) + } + + @Test + fun `Get Response serialize and deserialize using json config object should be equal`() { + val sampleConfig = FeatureChannel( + "config_id", + "name", + "description", + ConfigType.EMAIL_GROUP + ) + val searchResult = FeatureChannelList(sampleConfig) + val getResponse = GetFeatureChannelListResponse(searchResult) + val jsonString = getJsonString(getResponse) + val recreatedObject = createObjectFromJsonString(jsonString) { GetFeatureChannelListResponse.parse(it) } + assertSearchResultEquals(searchResult, recreatedObject.searchResult) + } + + @Test + fun `Get Response serialize and deserialize using json with multiple config object should be equal`() { + val sampleConfig1 = FeatureChannel( + "config_id1", + "name1", + "description1", + ConfigType.SLACK + ) + val sampleConfig2 = FeatureChannel( + "config_id2", + "name2", + "description2", + ConfigType.CHIME + ) + val sampleConfig3 = FeatureChannel( + "config_id3", + "name3", + "description3", + ConfigType.WEBHOOK + ) + val searchResult = FeatureChannelList( + 100, + 1000, + TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO, + listOf(sampleConfig1, sampleConfig2, sampleConfig3) + ) + val getResponse = GetFeatureChannelListResponse(searchResult) + val jsonString = getJsonString(getResponse) + val recreatedObject = createObjectFromJsonString(jsonString) { GetFeatureChannelListResponse.parse(it) } + assertSearchResultEquals(searchResult, recreatedObject.searchResult) + } + + @Test + fun `Get Response should use isEnabled=true if absent in json object`() { + val sampleConfig = FeatureChannel( + "config_id", + "name", + "description", + ConfigType.EMAIL, + true + ) + val searchResult = FeatureChannelList(sampleConfig) + val jsonString = """ + { + "start_index":"0", + "total_hits":"1", + "total_hit_relation":"eq", + "feature_channel_list":[ + { + "config_id":"config_id", + "name":"name", + "description":"description", + "config_type":"email" + } + ] + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { GetFeatureChannelListResponse.parse(it) } + assertSearchResultEquals(searchResult, recreatedObject.searchResult) + } + + @Test + fun `Get Response should safely ignore extra field in json object`() { + val sampleConfig = FeatureChannel( + "config_id", + "name", + "description", + ConfigType.EMAIL + ) + val searchResult = FeatureChannelList(sampleConfig) + val jsonString = """ + { + "start_index":"0", + "total_hits":"1", + "total_hit_relation":"eq", + "feature_channel_list":[ + { + "config_id":"config_id", + "name":"name", + "description":"description", + "config_type":"email", + "is_enabled":true + } + ], + "extra_field_1":["extra", "value"], + "extra_field_2":{"extra":"value"}, + "extra_field_3":"extra value 3" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { GetFeatureChannelListResponse.parse(it) } + assertSearchResultEquals(searchResult, recreatedObject.searchResult) + } + + @Test + fun `Get Response should safely fallback to default if startIndex, totalHits or totalHitRelation field absent in json object`() { + val sampleConfig = FeatureChannel( + "config_id", + "name", + "description", + ConfigType.EMAIL + ) + val searchResult = FeatureChannelList(sampleConfig) + val jsonString = """ + { + "feature_channel_list":[ + { + "config_id":"config_id", + "name":"name", + "description":"description", + "config_type":"email", + "is_enabled":true + } + ] + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { GetFeatureChannelListResponse.parse(it) } + assertSearchResultEquals(searchResult, recreatedObject.searchResult) + } + + @Test + fun `Get Response should throw exception if featureChannelList is absent in json`() { + val jsonString = """ + { + "start_index":"0", + "total_hits":"1", + "total_hit_relation":"eq" + } + """.trimIndent() + Assertions.assertThrows(IllegalArgumentException::class.java) { + createObjectFromJsonString(jsonString) { GetFeatureChannelListResponse.parse(it) } + } + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigRequestTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigRequestTests.kt new file mode 100644 index 00000000..cf892209 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigRequestTests.kt @@ -0,0 +1,280 @@ +/* + * 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.action + +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 +import org.opensearch.search.sort.SortOrder + +internal class GetNotificationConfigRequestTests { + + private fun assertGetRequestEquals( + expected: GetNotificationConfigRequest, + actual: GetNotificationConfigRequest + ) { + assertEquals(expected.configIds, actual.configIds) + assertEquals(expected.fromIndex, actual.fromIndex) + assertEquals(expected.maxItems, actual.maxItems) + assertEquals(expected.sortField, actual.sortField) + assertEquals(expected.sortOrder, actual.sortOrder) + assertEquals(expected.filterParams, actual.filterParams) + } + + @Test + fun `Get request serialize and deserialize transport object should be equal`() { + val configRequest = GetNotificationConfigRequest( + setOf("sample_config_id"), + 0, + 10, + "sortField", + SortOrder.DESC, + mapOf(Pair("filterKey", "filterValue")) + ) + val recreatedObject = recreateObject(configRequest) { GetNotificationConfigRequest(it) } + assertGetRequestEquals(configRequest, recreatedObject) + } + + @Test + fun `Get request serialize and deserialize using json object should be equal`() { + val configRequest = GetNotificationConfigRequest( + setOf("sample_config_id"), + 0, + 10, + "sortField", + SortOrder.ASC, + mapOf(Pair("filterKey", "filterValue")) + ) + val jsonString = getJsonString(configRequest) + val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationConfigRequest.parse(it) } + assertGetRequestEquals(configRequest, recreatedObject) + } + + @Test + fun `Get request with all field should deserialize json object using parser`() { + val configRequest = GetNotificationConfigRequest( + setOf("sample_config_id"), + 10, + 100, + "sortField", + SortOrder.DESC, + mapOf( + Pair("filterKey1", "filterValue1"), + Pair("filterKey2", "true"), + Pair("filterKey3", "filter,Value,3"), + Pair("filterKey4", "4") + ) + ) + val jsonString = """ + { + "config_id_list":["${configRequest.configIds.first()}"], + "from_index":"10", + "max_items":"100", + "sort_field":"sortField", + "sort_order":"desc", + "filter_param_list": { + "filterKey1":"filterValue1", + "filterKey2":"true", + "filterKey3":"filter,Value,3", + "filterKey4":"4" + } + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationConfigRequest.parse(it) } + assertGetRequestEquals(configRequest, recreatedObject) + } + + @Test + fun `Get request with only config_id field should deserialize json object using parser`() { + val configRequest = GetNotificationConfigRequest(configIds = setOf("sample_config_id")) + val jsonString = """ + { + "config_id_list":["${configRequest.configIds.first()}"] + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationConfigRequest.parse(it) } + assertGetRequestEquals(configRequest, recreatedObject) + } + + @Test + fun `Get request with only from_index field should deserialize json object using parser`() { + val configRequest = GetNotificationConfigRequest(fromIndex = 20) + val jsonString = """ + { + "from_index":"20" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationConfigRequest.parse(it) } + assertGetRequestEquals(configRequest, recreatedObject) + } + + @Test + fun `Get request with only max_items field should deserialize json object using parser`() { + val configRequest = GetNotificationConfigRequest(maxItems = 100) + val jsonString = """ + { + "max_items":"100" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationConfigRequest.parse(it) } + assertGetRequestEquals(configRequest, recreatedObject) + } + + @Test + fun `Get request with only sort_field field should deserialize json object using parser`() { + val configRequest = GetNotificationConfigRequest(sortField = "sample_sort_field") + val jsonString = """ + { + "sort_field":"sample_sort_field" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationConfigRequest.parse(it) } + assertGetRequestEquals(configRequest, recreatedObject) + } + + @Test + fun `Get request with only sort_order=asc field should deserialize json object using parser`() { + val configRequest = GetNotificationConfigRequest(sortOrder = SortOrder.ASC) + val jsonString = """ + { + "sort_order":"asc" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationConfigRequest.parse(it) } + assertGetRequestEquals(configRequest, recreatedObject) + } + + @Test + fun `Get request with only sort_order=ASC field should deserialize json object using parser`() { + val configRequest = GetNotificationConfigRequest(sortOrder = SortOrder.ASC) + val jsonString = """ + { + "sort_order":"ASC" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationConfigRequest.parse(it) } + assertGetRequestEquals(configRequest, recreatedObject) + } + + @Test + fun `Get request with only sort_order=desc field should deserialize json object using parser`() { + val configRequest = GetNotificationConfigRequest(sortOrder = SortOrder.DESC) + val jsonString = """ + { + "sort_order":"desc" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationConfigRequest.parse(it) } + assertGetRequestEquals(configRequest, recreatedObject) + } + + @Test + fun `Get request with only sort_order=DESC field should deserialize json object using parser`() { + val configRequest = GetNotificationConfigRequest(sortOrder = SortOrder.DESC) + val jsonString = """ + { + "sort_order":"DESC" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationConfigRequest.parse(it) } + assertGetRequestEquals(configRequest, recreatedObject) + } + + @Test + fun `Get request with invalid sort_order should throw exception`() { + val jsonString = """ + { + "sort_order":"descending" + } + """.trimIndent() + assertThrows { + createObjectFromJsonString(jsonString) { GetNotificationConfigRequest.parse(it) } + } + } + + @Test + fun `Get request with only filter_param_list field should deserialize json object using parser`() { + val configRequest = GetNotificationConfigRequest( + filterParams = mapOf( + Pair("filterKey1", "filterValue1"), + Pair("filterKey2", "true"), + Pair("filterKey3", "filter,Value,3"), + Pair("filterKey4", "4") + ) + ) + val jsonString = """ + { + "filter_param_list": { + "filterKey1":"filterValue1", + "filterKey2":"true", + "filterKey3":"filter,Value,3", + "filterKey4":"4" + } + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationConfigRequest.parse(it) } + assertGetRequestEquals(configRequest, recreatedObject) + } + + @Test + fun `Get request no field should deserialize json object using parser`() { + val configRequest = GetNotificationConfigRequest() + val jsonString = """ + { + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationConfigRequest.parse(it) } + assertGetRequestEquals(configRequest, recreatedObject) + } + + @Test + fun `Get request should throw exception when invalid json object is passed`() { + val jsonString = "sample message" + assertThrows { + createObjectFromJsonString(jsonString) { GetNotificationConfigRequest.parse(it) } + } + } + + @Test + fun `Get request should safely ignore extra field in json object`() { + val configRequest = GetNotificationConfigRequest(configIds = setOf("sample_config_id")) + val jsonString = """ + { + "config_id_list":["${configRequest.configIds.first()}"], + "extra_field_1":["extra", "value"], + "extra_field_2":{"extra":"value"}, + "extra_field_3":"extra value 3" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationConfigRequest.parse(it) } + assertGetRequestEquals(configRequest, recreatedObject) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigResponseTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigResponseTests.kt new file mode 100644 index 00000000..a2e14b40 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationConfigResponseTests.kt @@ -0,0 +1,312 @@ +/* + * 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.action + +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.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 +import org.opensearch.commons.notifications.model.Slack +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 { + + private fun assertSearchResultEquals( + expected: NotificationConfigSearchResult, + actual: NotificationConfigSearchResult + ) { + assertEquals(expected.startIndex, actual.startIndex) + assertEquals(expected.totalHits, actual.totalHits) + assertEquals(expected.totalHitRelation, actual.totalHitRelation) + assertEquals(expected.objectListFieldName, actual.objectListFieldName) + assertEquals(expected.objectList, actual.objectList) + } + + @Test + fun `Search result serialize and deserialize with config object should be equal`() { + val sampleSlack = Slack("https://domain.com/sample_url#1234567890") + val sampleConfig = NotificationConfig( + "name", + "description", + ConfigType.SLACK, + EnumSet.of(Feature.REPORTS), + configData = sampleSlack + ) + val configInfo = NotificationConfigInfo( + "config_id", + Instant.now(), + Instant.now(), + "tenant", + sampleConfig + ) + val searchResult = NotificationConfigSearchResult(configInfo) + val searchResponse = GetNotificationConfigResponse(searchResult) + val recreatedObject = recreateObject(searchResponse) { GetNotificationConfigResponse(it) } + assertSearchResultEquals(searchResult, recreatedObject.searchResult) + } + + @Test + fun `Search result serialize and deserialize with multiple config object should be equal`() { + val sampleConfig1 = NotificationConfig( + "name", + "description", + ConfigType.SLACK, + EnumSet.of(Feature.REPORTS), + configData = Slack("https://domain.com/sample_url#1234567890") + ) + val configInfo1 = NotificationConfigInfo( + "config_id1", + Instant.now(), + Instant.now(), + "tenant", + sampleConfig1 + ) + val sampleConfig2 = NotificationConfig( + "name", + "description", + ConfigType.CHIME, + EnumSet.of(Feature.INDEX_MANAGEMENT), + configData = Chime("https://domain.com/sample_url#1234567890") + ) + val configInfo2 = NotificationConfigInfo( + "config_id2", + Instant.now(), + Instant.now(), + "tenant", + sampleConfig2 + ) + val searchResult = NotificationConfigSearchResult( + 100, + 1000, + TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO, + listOf(configInfo1, configInfo2) + ) + val searchResponse = GetNotificationConfigResponse(searchResult) + val recreatedObject = recreateObject(searchResponse) { GetNotificationConfigResponse(it) } + assertSearchResultEquals(searchResult, recreatedObject.searchResult) + } + + @Test + fun `Search result serialize and deserialize using json config object should be equal`() { + val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) + val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) + val sampleSlack = Slack("https://domain.com/sample_url#1234567890") + val sampleConfig = NotificationConfig( + "name", + "description", + ConfigType.SLACK, + EnumSet.of(Feature.REPORTS), + configData = sampleSlack + ) + val configInfo = NotificationConfigInfo( + "config_id", + lastUpdatedTimeMs, + createdTimeMs, + "tenant", + sampleConfig + ) + val searchResult = NotificationConfigSearchResult(configInfo) + val searchResponse = GetNotificationConfigResponse(searchResult) + val jsonString = getJsonString(searchResponse) + val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationConfigResponse.parse(it) } + assertSearchResultEquals(searchResult, recreatedObject.searchResult) + } + + @Test + fun `Search result serialize and deserialize using json with multiple config object should be equal`() { + val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) + val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) + val sampleConfig1 = NotificationConfig( + "name", + "description", + ConfigType.SLACK, + EnumSet.of(Feature.REPORTS), + configData = Slack("https://domain.com/sample_url#1234567890") + ) + val configInfo1 = NotificationConfigInfo( + "config_id1", + lastUpdatedTimeMs, + createdTimeMs, + "tenant", + sampleConfig1 + ) + val sampleConfig2 = NotificationConfig( + "name", + "description", + ConfigType.CHIME, + EnumSet.of(Feature.INDEX_MANAGEMENT), + configData = Chime("https://domain.com/sample_url#1234567890") + ) + val configInfo2 = NotificationConfigInfo( + "config_id2", + lastUpdatedTimeMs, + createdTimeMs, + "tenant", + sampleConfig2 + ) + val searchResult = NotificationConfigSearchResult( + 100, + 1000, + TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO, + listOf(configInfo1, configInfo2) + ) + val searchResponse = GetNotificationConfigResponse(searchResult) + val jsonString = getJsonString(searchResponse) + val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationConfigResponse.parse(it) } + assertSearchResultEquals(searchResult, recreatedObject.searchResult) + } + + @Test + fun `Search result should safely ignore extra field in json object`() { + val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) + val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) + val sampleSlack = Slack("https://domain.com/sample_slack_url#1234567890") + val sampleConfig = NotificationConfig( + "name", + "description", + ConfigType.SLACK, + EnumSet.of(Feature.INDEX_MANAGEMENT), + isEnabled = true, + configData = sampleSlack + ) + val configInfo = NotificationConfigInfo( + "config-Id", + lastUpdatedTimeMs, + createdTimeMs, + "selectedTenant", + sampleConfig + ) + val searchResult = NotificationConfigSearchResult(configInfo) + val jsonString = """ + { + "start_index":"0", + "total_hits":"1", + "total_hit_relation":"eq", + "config_list":[ + { + "config_id":"config-Id", + "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", + "created_time_ms":"${createdTimeMs.toEpochMilli()}", + "tenant":"selectedTenant", + "config":{ + "name":"name", + "description":"description", + "config_type":"slack", + "feature_list":["index_management"], + "is_enabled":true, + "slack":{"url":"https://domain.com/sample_slack_url#1234567890"} + } + } + ], + "extra_field_1":["extra", "value"], + "extra_field_2":{"extra":"value"}, + "extra_field_3":"extra value 3" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationConfigResponse.parse(it) } + assertSearchResultEquals(searchResult, recreatedObject.searchResult) + } + + @Test + fun `Search result should safely fallback to default if startIndex, totalHits or totalHitRelation field absent in json object`() { + val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) + val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) + val sampleSlack = Slack("https://domain.com/sample_slack_url#1234567890") + val sampleConfig = NotificationConfig( + "name", + "description", + ConfigType.SLACK, + EnumSet.of(Feature.INDEX_MANAGEMENT), + isEnabled = true, + configData = sampleSlack + ) + val configInfo = NotificationConfigInfo( + "config-Id", + lastUpdatedTimeMs, + createdTimeMs, + "selectedTenant", + sampleConfig + ) + val searchResult = NotificationConfigSearchResult(configInfo) + val jsonString = """ + { + "config_list":[ + { + "config_id":"config-Id", + "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", + "created_time_ms":"${createdTimeMs.toEpochMilli()}", + "tenant":"selectedTenant", + "config":{ + "name":"name", + "description":"description", + "config_type":"slack", + "feature_list":["index_management"], + "is_enabled":true, + "slack":{"url":"https://domain.com/sample_slack_url#1234567890"} + } + } + ] + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationConfigResponse.parse(it) } + assertSearchResultEquals(searchResult, recreatedObject.searchResult) + } + + @Test + fun `Search result should throw exception if notificationConfigs is absent in json`() { + val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) + val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) + val jsonString = """ + { + "start_index":"0", + "total_hits":"1", + "total_hit_relation":"eq", + "config_list":[ + { + "config_id":"config-Id", + "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", + "created_time_ms":"${createdTimeMs.toEpochMilli()}", + "tenant":"selectedTenant" + } + ] + } + """.trimIndent() + Assertions.assertThrows(IllegalArgumentException::class.java) { + createObjectFromJsonString(jsonString) { GetNotificationConfigResponse.parse(it) } + } + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventRequestTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventRequestTests.kt new file mode 100644 index 00000000..5e12d5f1 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventRequestTests.kt @@ -0,0 +1,280 @@ +/* + * 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.action + +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 +import org.opensearch.search.sort.SortOrder + +internal class GetNotificationEventRequestTests { + + private fun assertGetRequestEquals( + expected: GetNotificationEventRequest, + actual: GetNotificationEventRequest + ) { + assertEquals(expected.eventIds, actual.eventIds) + assertEquals(expected.fromIndex, actual.fromIndex) + assertEquals(expected.maxItems, actual.maxItems) + assertEquals(expected.sortField, actual.sortField) + assertEquals(expected.sortOrder, actual.sortOrder) + assertEquals(expected.filterParams, actual.filterParams) + } + + @Test + fun `Get request serialize and deserialize transport object should be equal`() { + val configRequest = GetNotificationEventRequest( + setOf("sample_event_id"), + 0, + 10, + "sortField", + SortOrder.DESC, + mapOf(Pair("filterKey", "filterValue")) + ) + val recreatedObject = recreateObject(configRequest) { GetNotificationEventRequest(it) } + assertGetRequestEquals(configRequest, recreatedObject) + } + + @Test + fun `Get request serialize and deserialize using json object should be equal`() { + val configRequest = GetNotificationEventRequest( + setOf("sample_event_id"), + 0, + 10, + "sortField", + SortOrder.ASC, + mapOf(Pair("filterKey", "filterValue")) + ) + val jsonString = getJsonString(configRequest) + val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationEventRequest.parse(it) } + assertGetRequestEquals(configRequest, recreatedObject) + } + + @Test + fun `Get request with all field should deserialize json object using parser`() { + val configRequest = GetNotificationEventRequest( + setOf("sample_event_id"), + 10, + 100, + "sortField", + SortOrder.DESC, + mapOf( + Pair("filterKey1", "filterValue1"), + Pair("filterKey2", "true"), + Pair("filterKey3", "filter,Value,3"), + Pair("filterKey4", "4") + ) + ) + val jsonString = """ + { + "event_id_list":["${configRequest.eventIds.first()}"], + "from_index":"10", + "max_items":"100", + "sort_field":"sortField", + "sort_order":"desc", + "filter_param_list": { + "filterKey1":"filterValue1", + "filterKey2":"true", + "filterKey3":"filter,Value,3", + "filterKey4":"4" + } + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationEventRequest.parse(it) } + assertGetRequestEquals(configRequest, recreatedObject) + } + + @Test + fun `Get request with only event_id field should deserialize json object using parser`() { + val configRequest = GetNotificationEventRequest(eventIds = setOf("sample_event_id")) + val jsonString = """ + { + "event_id_list":["${configRequest.eventIds.first()}"] + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationEventRequest.parse(it) } + assertGetRequestEquals(configRequest, recreatedObject) + } + + @Test + fun `Get request with only from_index field should deserialize json object using parser`() { + val configRequest = GetNotificationEventRequest(fromIndex = 20) + val jsonString = """ + { + "from_index":"20" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationEventRequest.parse(it) } + assertGetRequestEquals(configRequest, recreatedObject) + } + + @Test + fun `Get request with only max_items field should deserialize json object using parser`() { + val configRequest = GetNotificationEventRequest(maxItems = 100) + val jsonString = """ + { + "max_items":"100" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationEventRequest.parse(it) } + assertGetRequestEquals(configRequest, recreatedObject) + } + + @Test + fun `Get request with only sort_field field should deserialize json object using parser`() { + val configRequest = GetNotificationEventRequest(sortField = "sample_sort_field") + val jsonString = """ + { + "sort_field":"sample_sort_field" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationEventRequest.parse(it) } + assertGetRequestEquals(configRequest, recreatedObject) + } + + @Test + fun `Get request with only sort_order=asc field should deserialize json object using parser`() { + val configRequest = GetNotificationEventRequest(sortOrder = SortOrder.ASC) + val jsonString = """ + { + "sort_order":"asc" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationEventRequest.parse(it) } + assertGetRequestEquals(configRequest, recreatedObject) + } + + @Test + fun `Get request with only sort_order=ASC field should deserialize json object using parser`() { + val configRequest = GetNotificationEventRequest(sortOrder = SortOrder.ASC) + val jsonString = """ + { + "sort_order":"ASC" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationEventRequest.parse(it) } + assertGetRequestEquals(configRequest, recreatedObject) + } + + @Test + fun `Get request with only sort_order=desc field should deserialize json object using parser`() { + val configRequest = GetNotificationEventRequest(sortOrder = SortOrder.DESC) + val jsonString = """ + { + "sort_order":"desc" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationEventRequest.parse(it) } + assertGetRequestEquals(configRequest, recreatedObject) + } + + @Test + fun `Get request with only sort_order=DESC field should deserialize json object using parser`() { + val configRequest = GetNotificationEventRequest(sortOrder = SortOrder.DESC) + val jsonString = """ + { + "sort_order":"DESC" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationEventRequest.parse(it) } + assertGetRequestEquals(configRequest, recreatedObject) + } + + @Test + fun `Get request with invalid sort_order should throw exception`() { + val jsonString = """ + { + "sort_order":"descending" + } + """.trimIndent() + assertThrows { + createObjectFromJsonString(jsonString) { GetNotificationEventRequest.parse(it) } + } + } + + @Test + fun `Get request with only filter_param_list field should deserialize json object using parser`() { + val configRequest = GetNotificationEventRequest( + filterParams = mapOf( + Pair("filterKey1", "filterValue1"), + Pair("filterKey2", "true"), + Pair("filterKey3", "filter,Value,3"), + Pair("filterKey4", "4") + ) + ) + val jsonString = """ + { + "filter_param_list": { + "filterKey1":"filterValue1", + "filterKey2":"true", + "filterKey3":"filter,Value,3", + "filterKey4":"4" + } + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationEventRequest.parse(it) } + assertGetRequestEquals(configRequest, recreatedObject) + } + + @Test + fun `Get request no field should deserialize json object using parser`() { + val configRequest = GetNotificationEventRequest() + val jsonString = """ + { + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationEventRequest.parse(it) } + assertGetRequestEquals(configRequest, recreatedObject) + } + + @Test + fun `Get request should throw exception when invalid json object is passed`() { + val jsonString = "sample message" + assertThrows { + createObjectFromJsonString(jsonString) { GetNotificationEventRequest.parse(it) } + } + } + + @Test + fun `Get request should safely ignore extra field in json object`() { + val configRequest = GetNotificationEventRequest(eventIds = setOf("sample_event_id")) + val jsonString = """ + { + "event_id_list":["${configRequest.eventIds.first()}"], + "extra_field_1":["extra", "value"], + "extra_field_2":{"extra":"value"}, + "extra_field_3":"extra value 3" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationEventRequest.parse(it) } + assertGetRequestEquals(configRequest, recreatedObject) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventResponseTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventResponseTests.kt new file mode 100644 index 00000000..4f7d72d1 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/GetNotificationEventResponseTests.kt @@ -0,0 +1,391 @@ +/* + * 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.action + +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.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 +import org.opensearch.commons.notifications.model.SeverityType +import org.opensearch.commons.utils.createObjectFromJsonString +import org.opensearch.commons.utils.getJsonString +import org.opensearch.commons.utils.recreateObject +import java.time.Instant + +internal class GetNotificationEventResponseTests { + + private fun assertSearchResultEquals( + expected: NotificationEventSearchResult, + actual: NotificationEventSearchResult + ) { + assertEquals(expected.startIndex, actual.startIndex) + assertEquals(expected.totalHits, actual.totalHits) + assertEquals(expected.totalHitRelation, actual.totalHitRelation) + assertEquals(expected.objectListFieldName, actual.objectListFieldName) + assertEquals(expected.objectList, actual.objectList) + } + + @Test + fun `Search result serialize and deserialize with event object should be equal`() { + val sampleEventSource = EventSource( + "title", + "reference_id", + Feature.ALERTING, + severity = SeverityType.INFO + ) + val sampleStatus = EventStatus( + "config_id", + "name", + ConfigType.SLACK, + deliveryStatus = DeliveryStatus("404", "invalid recipient") + ) + val sampleEvent = NotificationEvent(sampleEventSource, listOf(sampleStatus)) + val eventInfo = NotificationEventInfo( + "event_id", + Instant.now(), + Instant.now(), + "tenant", + sampleEvent + ) + val searchResult = NotificationEventSearchResult(eventInfo) + val searchResponse = GetNotificationEventResponse(searchResult) + val recreatedObject = recreateObject(searchResponse) { GetNotificationEventResponse(it) } + assertSearchResultEquals(searchResult, recreatedObject.searchResult) + } + + @Test + fun `Search result serialize and deserialize with multiple event status object should be equal`() { + val eventSource1 = EventSource( + "title 1", + "reference_id_1", + Feature.ALERTING, + severity = SeverityType.INFO + ) + val eventSource2 = EventSource( + "title 2", + "reference_id_2", + Feature.REPORTS, + severity = SeverityType.HIGH + ) + val status1 = EventStatus( + "config_id1", + "name", + ConfigType.SLACK, + deliveryStatus = DeliveryStatus("200", "success") + ) + val status2 = EventStatus( + "config_id2", + "name", + ConfigType.CHIME, + deliveryStatus = DeliveryStatus("404", "invalid recipient") + ) + val eventInfo1 = NotificationEventInfo( + "event_id1", + Instant.now(), + Instant.now(), + "tenant", + NotificationEvent(eventSource1, listOf(status1)) + ) + val eventInfo2 = NotificationEventInfo( + "event_id2", + Instant.now(), + Instant.now(), + "tenant", + NotificationEvent(eventSource2, listOf(status2)) + ) + val eventInfo3 = NotificationEventInfo( + "event_id3", + Instant.now(), + Instant.now(), + "tenant", + NotificationEvent(eventSource1, listOf(status1, status2)) + ) + val eventInfo4 = NotificationEventInfo( + "event_id4", + Instant.now(), + Instant.now(), + "tenant", + NotificationEvent(eventSource2, listOf(status1, status2)) + ) + val searchResult = NotificationEventSearchResult( + 100, + 1000, + TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO, + listOf(eventInfo1, eventInfo2, eventInfo3, eventInfo4) + ) + val searchResponse = GetNotificationEventResponse(searchResult) + val recreatedObject = recreateObject(searchResponse) { GetNotificationEventResponse(it) } + assertSearchResultEquals(searchResult, recreatedObject.searchResult) + } + + @Test + fun `Search result serialize and deserialize using json event object should be equal`() { + val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) + val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) + val sampleEventSource = EventSource( + "title", + "reference_id", + Feature.ALERTING, + severity = SeverityType.INFO + ) + val sampleStatus = EventStatus( + "config_id", + "name", + ConfigType.SLACK, + deliveryStatus = DeliveryStatus("404", "invalid recipient") + ) + val sampleEvent = NotificationEvent(sampleEventSource, listOf(sampleStatus)) + val eventInfo = NotificationEventInfo( + "event_id", + lastUpdatedTimeMs, + createdTimeMs, + "tenant", + sampleEvent + ) + val searchResult = NotificationEventSearchResult(eventInfo) + val searchResponse = GetNotificationEventResponse(searchResult) + val jsonString = getJsonString(searchResponse) + val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationEventResponse.parse(it) } + assertSearchResultEquals(searchResult, recreatedObject.searchResult) + } + + @Test + fun `Search result serialize and deserialize using json with multiple event object should be equal`() { + val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) + val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) + val eventSource1 = EventSource( + "title 1", + "reference_id_1", + Feature.ALERTING, + severity = SeverityType.INFO + ) + val eventSource2 = EventSource( + "title 2", + "reference_id_2", + Feature.REPORTS, + severity = SeverityType.HIGH + ) + val status1 = EventStatus( + "config_id1", + "name", + ConfigType.SLACK, + deliveryStatus = DeliveryStatus("200", "success") + ) + val status2 = EventStatus( + "config_id2", + "name", + ConfigType.CHIME, + deliveryStatus = DeliveryStatus("404", "invalid recipient") + ) + val eventInfo1 = NotificationEventInfo( + "event_id1", + lastUpdatedTimeMs, + createdTimeMs, + "tenant", + NotificationEvent(eventSource1, listOf(status1)) + ) + val eventInfo2 = NotificationEventInfo( + "event_id2", + lastUpdatedTimeMs, + createdTimeMs, + "tenant", + NotificationEvent(eventSource2, listOf(status2)) + ) + val searchResult = NotificationEventSearchResult( + 100, + 1000, + TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO, + listOf(eventInfo1, eventInfo2) + ) + val searchResponse = GetNotificationEventResponse(searchResult) + val jsonString = getJsonString(searchResponse) + val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationEventResponse.parse(it) } + assertSearchResultEquals(searchResult, recreatedObject.searchResult) + } + + @Test + fun `Search result should safely ignore extra field in json object`() { + val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) + val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) + val sampleEventSource = EventSource( + "title", + "reference_id", + Feature.ALERTING, + severity = SeverityType.INFO + ) + val sampleStatus = EventStatus( + "config_id", + "name", + ConfigType.SLACK, + deliveryStatus = DeliveryStatus("200", "success") + ) + val sampleEvent = NotificationEvent(sampleEventSource, listOf(sampleStatus)) + val eventInfo = NotificationEventInfo( + "event_id", + lastUpdatedTimeMs, + createdTimeMs, + "selectedTenant", + sampleEvent + ) + val searchResult = NotificationEventSearchResult(eventInfo) + val jsonString = """ + { + "start_index":"0", + "total_hits":"1", + "total_hit_relation":"eq", + "event_list":[ + { + "event_id":"event_id", + "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", + "created_time_ms":"${createdTimeMs.toEpochMilli()}", + "tenant":"selectedTenant", + "event":{ + "event_source":{ + "title":"title", + "reference_id":"reference_id", + "feature":"alerting", + "severity":"info", + "tags":[] + }, + "status_list":[ + { + "config_id":"config_id", + "config_type":"slack", + "config_name":"name", + "delivery_status": + { + "status_code":"200", + "status_text":"success" + } + } + ] + } + } + ], + "extra_field_1":["extra", "value"], + "extra_field_2":{"extra":"value"}, + "extra_field_3":"extra value 3" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationEventResponse.parse(it) } + assertSearchResultEquals(searchResult, recreatedObject.searchResult) + } + + @Test + fun `Search result should safely fallback to default if startIndex, totalHits or totalHitRelation field absent in json object`() { + val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) + val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) + val sampleEventSource = EventSource( + "title", + "reference_id", + Feature.ALERTING, + severity = SeverityType.INFO + ) + val sampleStatus = EventStatus( + "config_id", + "name", + ConfigType.SLACK, + deliveryStatus = DeliveryStatus("200", "success") + ) + val sampleEvent = NotificationEvent(sampleEventSource, listOf(sampleStatus)) + val eventInfo = NotificationEventInfo( + "event_id", + lastUpdatedTimeMs, + createdTimeMs, + "selectedTenant", + sampleEvent + ) + val searchResult = NotificationEventSearchResult(eventInfo) + val jsonString = """ + { + "event_list":[ + { + "event_id":"event_id", + "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", + "created_time_ms":"${createdTimeMs.toEpochMilli()}", + "tenant":"selectedTenant", + "event":{ + "event_source":{ + "title":"title", + "reference_id":"reference_id", + "feature":"alerting", + "severity":"info", + "tags":[] + }, + "status_list":[ + { + "config_id":"config_id", + "config_type":"slack", + "config_name":"name", + "delivery_status": + { + "status_code":"200", + "status_text":"success" + } + } + ] + } + } + ] + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { GetNotificationEventResponse.parse(it) } + assertSearchResultEquals(searchResult, recreatedObject.searchResult) + } + + @Test + fun `Search result should throw exception if event is absent in json`() { + val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) + val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) + val jsonString = """ + { + "start_index":"0", + "total_hits":"1", + "total_hit_relation":"eq", + "event_list":[ + { + "event_id":"event_id", + "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", + "created_time_ms":"${createdTimeMs.toEpochMilli()}", + "tenant":"selectedTenant" + } + ] + } + """.trimIndent() + Assertions.assertThrows(IllegalArgumentException::class.java) { + createObjectFromJsonString(jsonString) { GetNotificationEventResponse.parse(it) } + } + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesRequestTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesRequestTests.kt new file mode 100644 index 00000000..3861fdd9 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesRequestTests.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. + */ + +/* + * 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.action + +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 GetPluginFeaturesRequestTests { + + private fun assertGetRequestEquals( + expected: GetPluginFeaturesRequest, + actual: GetPluginFeaturesRequest + ) { + assertEquals(expected.compact, actual.compact) + } + + @Test + fun `Get request serialize and deserialize transport object should be equal`() { + val request = GetPluginFeaturesRequest() + val recreatedObject = recreateObject(request) { GetPluginFeaturesRequest(it) } + assertGetRequestEquals(request, recreatedObject) + } + + @Test + fun `Get request serialize and deserialize using json object should be equal`() { + val request = GetPluginFeaturesRequest() + val jsonString = getJsonString(request) + val recreatedObject = createObjectFromJsonString(jsonString) { GetPluginFeaturesRequest.parse(it) } + assertGetRequestEquals(request, recreatedObject) + } + + @Test + fun `Get request should throw exception when invalid json object is passed`() { + val jsonString = "sample message" + assertThrows { + createObjectFromJsonString(jsonString) { GetPluginFeaturesRequest.parse(it) } + } + } + + @Test + fun `Get request should safely ignore extra field in json object`() { + val request = GetPluginFeaturesRequest() + val jsonString = """ + { + "extra_field_1":["extra", "value"], + "extra_field_2":{"extra":"value"}, + "extra_field_3":"extra value 3" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { GetPluginFeaturesRequest.parse(it) } + assertGetRequestEquals(request, recreatedObject) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesResponseTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesResponseTests.kt new file mode 100644 index 00000000..3103825f --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/GetPluginFeaturesResponseTests.kt @@ -0,0 +1,129 @@ +/* + * 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.action + +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.opensearch.commons.utils.createObjectFromJsonString +import org.opensearch.commons.utils.getJsonString +import org.opensearch.commons.utils.recreateObject + +internal class GetPluginFeaturesResponseTests { + + private fun assertResponseEquals( + expected: GetPluginFeaturesResponse, + actual: GetPluginFeaturesResponse + ) { + assertEquals(expected.configTypeList, actual.configTypeList) + assertEquals(expected.pluginFeatures, actual.pluginFeatures) + } + + @Test + fun `Get Response serialize and deserialize transport object should be equal`() { + val response = GetPluginFeaturesResponse( + listOf("config_type_1", "config_type_2", "config_type_3"), + mapOf( + Pair("FeatureKey1", "FeatureValue1"), + Pair("FeatureKey2", "FeatureValue2"), + Pair("FeatureKey3", "FeatureValue3") + ) + ) + val recreatedObject = recreateObject(response) { GetPluginFeaturesResponse(it) } + assertResponseEquals(response, recreatedObject) + } + + @Test + fun `Get Response serialize and deserialize using json config object should be equal`() { + val response = GetPluginFeaturesResponse( + listOf("config_type_1", "config_type_2", "config_type_3"), + mapOf( + Pair("FeatureKey1", "FeatureValue1"), + Pair("FeatureKey2", "FeatureValue2"), + Pair("FeatureKey3", "FeatureValue3") + ) + ) + val jsonString = getJsonString(response) + val recreatedObject = createObjectFromJsonString(jsonString) { GetPluginFeaturesResponse.parse(it) } + assertResponseEquals(response, recreatedObject) + } + + @Test + fun `Get Response should safely ignore extra field in json object`() { + val response = GetPluginFeaturesResponse( + listOf("config_type_1", "config_type_2", "config_type_3"), + mapOf( + Pair("FeatureKey1", "FeatureValue1"), + Pair("FeatureKey2", "FeatureValue2"), + Pair("FeatureKey3", "FeatureValue3") + ) + ) + val jsonString = """ + { + "config_type_list":["config_type_1", "config_type_2", "config_type_3"], + "plugin_features":{ + "FeatureKey1":"FeatureValue1", + "FeatureKey2":"FeatureValue2", + "FeatureKey3":"FeatureValue3" + }, + "extra_field_1":["extra", "value"], + "extra_field_2":{"extra":"value"}, + "extra_field_3":"extra value 3" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { GetPluginFeaturesResponse.parse(it) } + assertResponseEquals(response, recreatedObject) + } + + @Test + fun `Get Response should throw exception if config_type_list is absent in json`() { + val jsonString = """ + { + "plugin_features":{ + "FeatureKey1":"FeatureValue1", + "FeatureKey2":"FeatureValue2", + "FeatureKey3":"FeatureValue3" + } + } + """.trimIndent() + Assertions.assertThrows(IllegalArgumentException::class.java) { + createObjectFromJsonString(jsonString) { GetPluginFeaturesResponse.parse(it) } + } + } + + @Test + fun `Get Response should throw exception if plugin_features is absent in json`() { + val jsonString = """ + { + "config_type_list":["config_type_1", "config_type_2", "config_type_3"] + } + """.trimIndent() + Assertions.assertThrows(IllegalArgumentException::class.java) { + createObjectFromJsonString(jsonString) { GetPluginFeaturesResponse.parse(it) } + } + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/SendNotificationRequestTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/SendNotificationRequestTests.kt new file mode 100644 index 00000000..9c8bd2b4 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/SendNotificationRequestTests.kt @@ -0,0 +1,272 @@ +/* + * 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.action + +import com.fasterxml.jackson.core.JsonParseException +import org.junit.jupiter.api.Assertions.assertEquals +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.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 +import org.opensearch.commons.utils.recreateObject + +internal class SendNotificationRequestTests { + + private fun assertGetRequestEquals( + expected: SendNotificationRequest, + actual: SendNotificationRequest + ) { + assertEquals(expected.eventSource, actual.eventSource) + assertEquals(expected.channelMessage, actual.channelMessage) + assertEquals(expected.channelIds, actual.channelIds) + assertEquals(expected.threadContext, actual.threadContext) + assertNull(actual.validate()) + } + + @Test + fun `Send request serialize and deserialize transport object should be equal`() { + val notificationInfo = EventSource( + "title", + "reference_id", + Feature.REPORTS, + SeverityType.HIGH, + listOf("tag1", "tag2") + ) + val channelMessage = ChannelMessage( + "text_description", + "htmlDescription", + null + ) + val request = SendNotificationRequest( + notificationInfo, + channelMessage, + listOf("channelId1", "channelId2"), + "sample-thread-context" + ) + val recreatedObject = recreateObject(request) { SendNotificationRequest(it) } + assertGetRequestEquals(request, recreatedObject) + } + + @Test + fun `Send request serialize and deserialize using json object should be equal`() { + val notificationInfo = EventSource( + "title", + "reference_id", + Feature.INDEX_MANAGEMENT, + SeverityType.CRITICAL, + listOf("tag1", "tag2") + ) + val channelMessage = ChannelMessage( + "text_description", + "htmlDescription", + null + ) + val request = SendNotificationRequest( + notificationInfo, + channelMessage, + listOf("channelId1", "channelId2"), + "sample-thread-context" + ) + val jsonString = getJsonString(request) + val recreatedObject = createObjectFromJsonString(jsonString) { SendNotificationRequest.parse(it) } + assertGetRequestEquals(request, recreatedObject) + } + + @Test + fun `Send request should throw exception when invalid json object is passed`() { + val jsonString = "sample message" + assertThrows { + createObjectFromJsonString(jsonString) { SendNotificationRequest.parse(it) } + } + } + + @Test + fun `Send request should safely ignore extra field in json object`() { + val notificationInfo = EventSource( + "title", + "reference_id", + Feature.ALERTING, + SeverityType.HIGH, + listOf("tag1", "tag2") + ) + val channelMessage = ChannelMessage( + "text_description", + "htmlDescription", + null + ) + val request = SendNotificationRequest( + notificationInfo, + channelMessage, + listOf("channelId1", "channelId2"), + "sample-thread-context" + ) + val jsonString = """ + { + "event_source":{ + "title":"${notificationInfo.title}", + "reference_id":"${notificationInfo.referenceId}", + "feature":"${notificationInfo.feature}", + "severity":"${notificationInfo.severity}", + "tags":["tag1", "tag2"] + }, + "channel_message":{ + "text_description":"${channelMessage.textDescription}", + "html_description":"${channelMessage.htmlDescription}" + }, + "channel_id_list":["channelId1", "channelId2"], + "context":"${request.threadContext}", + "extra_field_1":["extra", "value"], + "extra_field_2":{"extra":"value"}, + "extra_field_3":"extra value 3" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { SendNotificationRequest.parse(it) } + assertGetRequestEquals(request, recreatedObject) + } + + @Test + fun `Send request should safely ignore thread context is absent in json object`() { + val notificationInfo = EventSource( + "title", + "reference_id", + Feature.REPORTS, + SeverityType.INFO, + listOf("tag1", "tag2") + ) + val channelMessage = ChannelMessage( + "text_description", + "htmlDescription", + null + ) + val request = SendNotificationRequest( + notificationInfo, + channelMessage, + listOf("channelId1", "channelId2"), + null + ) + val jsonString = """ + { + "event_source":{ + "title":"${notificationInfo.title}", + "reference_id":"${notificationInfo.referenceId}", + "feature":"${notificationInfo.feature}", + "severity":"${notificationInfo.severity}", + "tags":["tag1", "tag2"] + }, + "channel_message":{ + "text_description":"${channelMessage.textDescription}", + "html_description":"${channelMessage.htmlDescription}" + }, + "channel_id_list":["channelId1", "channelId2"] + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { SendNotificationRequest.parse(it) } + assertGetRequestEquals(request, recreatedObject) + } + + @Test + fun `Send request should throw exception if notificationInfo field is absent in json object`() { + val jsonString = """ + { + "channel_message":{ + "text_description":"text_description" + }, + "channel_id_list":["channelId1", "channelId2"] + } + """.trimIndent() + assertThrows { + createObjectFromJsonString(jsonString) { SendNotificationRequest.parse(it) } + } + } + + @Test + fun `Send request should throw exception if channelMessage field is absent in json object`() { + val jsonString = """ + { + "event_source":{ + "title":"title", + "reference_id":"reference_id", + "feature":"feature", + "severity":"High", + "tags":["tag1", "tag2"] + }, + "channel_id_list":["channelId1", "channelId2"] + } + """.trimIndent() + assertThrows { + createObjectFromJsonString(jsonString) { SendNotificationRequest.parse(it) } + } + } + + @Test + fun `Send request should throw exception if channelIds field is absent in json object`() { + val jsonString = """ + { + "event_source":{ + "title":"title", + "reference_id":"reference_id", + "feature":"feature", + "severity":"High", + "tags":["tag1", "tag2"] + }, + "channel_message":{ + "text_description":"text_description" + } + } + """.trimIndent() + assertThrows { + createObjectFromJsonString(jsonString) { SendNotificationRequest.parse(it) } + } + } + + @Test + fun `Send request validate return exception if channelIds field is empty`() { + val jsonString = """ + { + "event_source":{ + "title":"title", + "reference_id":"reference_id", + "feature":"feature", + "severity":"High", + "tags":["tag1", "tag2"] + }, + "channel_message":{ + "text_description":"text_description" + }, + "channel_id_list":[] + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { SendNotificationRequest.parse(it) } + assertNotNull(recreatedObject.validate()) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/SendNotificationResponseTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/SendNotificationResponseTests.kt new file mode 100644 index 00000000..511c5760 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/SendNotificationResponseTests.kt @@ -0,0 +1,92 @@ +/* + * 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.action + +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 SendNotificationResponseTests { + + @Test + fun `Create response serialize and deserialize transport object should be equal`() { + val configResponse = SendNotificationResponse("sample_notification_id") + val recreatedObject = recreateObject(configResponse) { SendNotificationResponse(it) } + assertEquals(configResponse.notificationId, recreatedObject.notificationId) + } + + @Test + fun `Create response serialize and deserialize using json object should be equal`() { + val configResponse = SendNotificationResponse("sample_notification_id") + val jsonString = getJsonString(configResponse) + val recreatedObject = createObjectFromJsonString(jsonString) { SendNotificationResponse.parse(it) } + assertEquals(configResponse.notificationId, recreatedObject.notificationId) + } + + @Test + fun `Create response should deserialize json object using parser`() { + val notificationId = "sample_notification_id" + val jsonString = "{\"event_id\":\"$notificationId\"}" + val recreatedObject = createObjectFromJsonString(jsonString) { SendNotificationResponse.parse(it) } + assertEquals(notificationId, recreatedObject.notificationId) + } + + @Test + fun `Create response should throw exception when invalid json object is passed`() { + val jsonString = "sample message" + assertThrows { + createObjectFromJsonString(jsonString) { SendNotificationResponse.parse(it) } + } + } + + @Test + fun `Create response should throw exception when notificationId is replace with notificationId2 in json object`() { + val jsonString = "{\"event_id2\":\"sample_notification_id\"}" + assertThrows { + createObjectFromJsonString(jsonString) { SendNotificationResponse.parse(it) } + } + } + + @Test + fun `Create response should safely ignore extra field in json object`() { + val notificationId = "sample_notification_id" + val jsonString = """ + { + "event_id":"$notificationId", + "extra_field_1":["extra", "value"], + "extra_field_2":{"extra":"value"}, + "extra_field_3":"extra value 3" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { SendNotificationResponse.parse(it) } + assertEquals(notificationId, recreatedObject.notificationId) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequestTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequestTests.kt new file mode 100644 index 00000000..607e8077 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigRequestTests.kt @@ -0,0 +1,476 @@ +/* + * 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.action + +import com.fasterxml.jackson.core.JsonParseException +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.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 +import org.opensearch.commons.notifications.model.SmtpAccount +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 { + + private fun createWebhookContentConfigObject(): NotificationConfig { + val sampleWebhook = Webhook("https://domain.com/sample_webhook_url#1234567890") + return NotificationConfig( + "name", + "description", + ConfigType.WEBHOOK, + EnumSet.of(Feature.INDEX_MANAGEMENT), + isEnabled = true, + configData = sampleWebhook + ) + } + + private fun createSlackContentConfigObject(): NotificationConfig { + val sampleSlack = Slack("https://domain.com/sample_slack_url#1234567890") + return NotificationConfig( + "name", + "description", + ConfigType.SLACK, + EnumSet.of(Feature.INDEX_MANAGEMENT), + isEnabled = true, + configData = sampleSlack + ) + } + + private fun createChimeContentConfigObject(): NotificationConfig { + val sampleChime = Chime("https://domain.com/sample_chime_url#1234567890") + return NotificationConfig( + "name", + "description", + ConfigType.CHIME, + EnumSet.of(Feature.INDEX_MANAGEMENT), + isEnabled = true, + configData = sampleChime + ) + } + + private fun createEmailGroupContentConfigObject(): NotificationConfig { + val sampleEmailGroup = EmailGroup(listOf("dummy@company.com")) + return NotificationConfig( + "name", + "description", + ConfigType.EMAIL_GROUP, + EnumSet.of(Feature.INDEX_MANAGEMENT), + isEnabled = true, + configData = sampleEmailGroup + ) + } + + private fun createEmailContentConfigObject(): NotificationConfig { + val sampleEmail = Email( + emailAccountID = "sample_1@dummy.com", + recipients = listOf("sample_2@dummy.com"), + emailGroupIds = listOf("sample_3@dummy.com") + ) + return NotificationConfig( + "name", + "description", + ConfigType.EMAIL, + EnumSet.of(Feature.INDEX_MANAGEMENT), + isEnabled = true, + configData = sampleEmail + ) + } + + private fun createSmtpAccountContentConfigObject(): NotificationConfig { + val sampleSmtpAccount = SmtpAccount( + host = "http://dummy.com", + port = 11, + method = MethodType.SSL, + fromAddress = "sample@dummy.com" + ) + return NotificationConfig( + "name", + "description", + ConfigType.SMTP_ACCOUNT, + EnumSet.of(Feature.INDEX_MANAGEMENT), + isEnabled = true, + configData = sampleSmtpAccount + ) + } + + @Test + fun `Update config serialize and deserialize transport object should be equal Webhook`() { + val configRequest = UpdateNotificationConfigRequest("config_id", createWebhookContentConfigObject()) + val recreatedObject = + recreateObject(configRequest) { UpdateNotificationConfigRequest(it) } + assertNull(recreatedObject.validate()) + assertEquals(configRequest.notificationConfig, recreatedObject.notificationConfig) + assertEquals("config_id", recreatedObject.configId) + } + + @Test + fun `Update config serialize and deserialize transport object should be equal Slack`() { + val configRequest = UpdateNotificationConfigRequest("config_id", createSlackContentConfigObject()) + val recreatedObject = + recreateObject(configRequest) { UpdateNotificationConfigRequest(it) } + assertNull(recreatedObject.validate()) + assertEquals(configRequest.notificationConfig, recreatedObject.notificationConfig) + assertEquals("config_id", recreatedObject.configId) + } + + @Test + fun `Update config serialize and deserialize transport object should be equal Chime`() { + val configRequest = UpdateNotificationConfigRequest("config_id", createChimeContentConfigObject()) + val recreatedObject = + recreateObject(configRequest) { UpdateNotificationConfigRequest(it) } + assertNull(recreatedObject.validate()) + assertEquals(configRequest.notificationConfig, recreatedObject.notificationConfig) + assertEquals("config_id", recreatedObject.configId) + } + + @Test + fun `Update config serialize and deserialize transport object should be equal Email`() { + val configRequest = UpdateNotificationConfigRequest("config_id", createEmailContentConfigObject()) + val recreatedObject = + recreateObject(configRequest) { UpdateNotificationConfigRequest(it) } + assertNull(recreatedObject.validate()) + assertEquals(configRequest.notificationConfig, recreatedObject.notificationConfig) + assertEquals("config_id", recreatedObject.configId) + } + + @Test + fun `Update config serialize and deserialize transport object should be equal EmailGroup`() { + val configRequest = UpdateNotificationConfigRequest("config_id", createEmailGroupContentConfigObject()) + val recreatedObject = + recreateObject(configRequest) { UpdateNotificationConfigRequest(it) } + assertNull(recreatedObject.validate()) + assertEquals(configRequest.notificationConfig, recreatedObject.notificationConfig) + assertEquals("config_id", recreatedObject.configId) + } + + @Test + fun `Update config serialize and deserialize transport object should be equal SmtpAccount`() { + val configRequest = UpdateNotificationConfigRequest("config_id", createSmtpAccountContentConfigObject()) + val recreatedObject = + recreateObject(configRequest) { UpdateNotificationConfigRequest(it) } + assertNull(recreatedObject.validate()) + assertEquals(configRequest.notificationConfig, recreatedObject.notificationConfig) + assertEquals("config_id", recreatedObject.configId) + } + + @Test + fun `Update config serialize and deserialize using json object should be equal webhook`() { + val configRequest = UpdateNotificationConfigRequest("config_id", createWebhookContentConfigObject()) + val jsonString = getJsonString(configRequest) + val recreatedObject = createObjectFromJsonString(jsonString) { UpdateNotificationConfigRequest.parse(it) } + assertEquals(configRequest.notificationConfig, recreatedObject.notificationConfig) + assertEquals("config_id", recreatedObject.configId) + } + + @Test + fun `Update config serialize and deserialize using json object should be equal slack`() { + val configRequest = UpdateNotificationConfigRequest("config_id", createSlackContentConfigObject()) + val jsonString = getJsonString(configRequest) + val recreatedObject = createObjectFromJsonString(jsonString) { UpdateNotificationConfigRequest.parse(it) } + assertEquals(configRequest.notificationConfig, recreatedObject.notificationConfig) + assertEquals("config_id", recreatedObject.configId) + } + + @Test + fun `Update config serialize and deserialize using json object should be equal Email`() { + val configRequest = UpdateNotificationConfigRequest("config_id", createEmailContentConfigObject()) + val jsonString = getJsonString(configRequest) + val recreatedObject = createObjectFromJsonString(jsonString) { UpdateNotificationConfigRequest.parse(it) } + assertEquals(configRequest.notificationConfig, recreatedObject.notificationConfig) + assertEquals("config_id", recreatedObject.configId) + } + + @Test + fun `Update config serialize and deserialize using json object should be equal EmailGroup`() { + val configRequest = UpdateNotificationConfigRequest("config_id", createEmailGroupContentConfigObject()) + val jsonString = getJsonString(configRequest) + val recreatedObject = createObjectFromJsonString(jsonString) { UpdateNotificationConfigRequest.parse(it) } + assertEquals(configRequest.notificationConfig, recreatedObject.notificationConfig) + assertEquals("config_id", recreatedObject.configId) + } + + @Test + fun `Update config serialize and deserialize using json object should be equal SmtpAccount`() { + val configRequest = UpdateNotificationConfigRequest("config_id", createSmtpAccountContentConfigObject()) + val jsonString = getJsonString(configRequest) + val recreatedObject = createObjectFromJsonString(jsonString) { UpdateNotificationConfigRequest.parse(it) } + assertEquals(configRequest.notificationConfig, recreatedObject.notificationConfig) + assertEquals("config_id", recreatedObject.configId) + } + + @Test + fun `Update config serialize and deserialize using json object should be equal chime`() { + val configRequest = UpdateNotificationConfigRequest("config_id", createChimeContentConfigObject()) + val jsonString = getJsonString(configRequest) + val recreatedObject = createObjectFromJsonString(jsonString) { UpdateNotificationConfigRequest.parse(it) } + assertEquals(configRequest.notificationConfig, recreatedObject.notificationConfig) + assertEquals("config_id", recreatedObject.configId) + } + + @Test + fun `Update config should deserialize json object using parser slack`() { + val sampleSlack = Slack("https://domain.com/sample_slack_url#1234567890") + val config = NotificationConfig( + "name", + "description", + ConfigType.SLACK, + EnumSet.of(Feature.INDEX_MANAGEMENT), + isEnabled = true, + configData = sampleSlack + ) + + val jsonString = """ + { + "config_id":"config_id1", + "config":{ + "name":"name", + "description":"description", + "config_type":"slack", + "feature_list":["index_management"], + "is_enabled":true, + "slack":{"url":"https://domain.com/sample_slack_url#1234567890"} + } + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { UpdateNotificationConfigRequest.parse(it) } + assertEquals(config, recreatedObject.notificationConfig) + assertEquals("config_id1", recreatedObject.configId) + } + + @Test + fun `Update config should deserialize json object using parser webhook`() { + val sampleWebhook = Webhook("https://domain.com/sample_webhook_url#1234567890") + val config = NotificationConfig( + "name", + "description", + ConfigType.WEBHOOK, + EnumSet.of(Feature.INDEX_MANAGEMENT), + isEnabled = true, + configData = sampleWebhook + ) + + val jsonString = """ + { + "config_id":"config_id1", + "config":{ + "name":"name", + "description":"description", + "config_type":"webhook", + "feature_list":["index_management"], + "is_enabled":true, + "webhook":{"url":"https://domain.com/sample_webhook_url#1234567890"} + } + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { UpdateNotificationConfigRequest.parse(it) } + assertEquals(config, recreatedObject.notificationConfig) + assertEquals("config_id1", recreatedObject.configId) + } + + @Test + fun `Update config should deserialize json object using parser Chime`() { + val sampleChime = Chime("https://domain.com/sample_chime_url#1234567890") + val config = NotificationConfig( + "name", + "description", + ConfigType.CHIME, + EnumSet.of(Feature.INDEX_MANAGEMENT), + isEnabled = true, + configData = sampleChime + ) + + val jsonString = """ + { + "config_id":"config_id1", + "config":{ + "name":"name", + "description":"description", + "config_type":"chime", + "feature_list":["index_management"], + "is_enabled":true, + "chime":{"url":"https://domain.com/sample_chime_url#1234567890"} + } + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { UpdateNotificationConfigRequest.parse(it) } + assertEquals(config, recreatedObject.notificationConfig) + assertEquals("config_id1", recreatedObject.configId) + } + + @Test + fun `Update config should deserialize json object using parser Email Group`() { + val sampleEmailGroup = EmailGroup(listOf("dummy@company.com")) + val config = NotificationConfig( + "name", + "description", + ConfigType.EMAIL_GROUP, + EnumSet.of(Feature.INDEX_MANAGEMENT), + isEnabled = true, + configData = sampleEmailGroup + ) + + val jsonString = """ + { + "config_id":"config_id1", + "config":{ + "name":"name", + "description":"description", + "config_type":"email_group", + "feature_list":["index_management"], + "is_enabled":true, + "email_group":{"recipient_list":["dummy@company.com"]} + } + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { UpdateNotificationConfigRequest.parse(it) } + assertEquals(config, recreatedObject.notificationConfig) + assertEquals("config_id1", recreatedObject.configId) + } + + @Test + fun `Update config should deserialize json object using parser Email`() { + val sampleEmail = Email( + emailAccountID = "sample_1@dummy.com", + recipients = listOf("sample_2@dummy.com"), + emailGroupIds = listOf("sample_3@dummy.com") + ) + val config = NotificationConfig( + "name", + "description", + ConfigType.EMAIL, + EnumSet.of(Feature.INDEX_MANAGEMENT), + isEnabled = true, + configData = sampleEmail + ) + + val jsonString = """ + { + "config_id":"config_id1", + "config":{ + "name":"name", + "description":"description", + "config_type":"email", + "feature_list":["index_management"], + "is_enabled":true, + "email":{"email_account_id":"sample_1@dummy.com","recipient_list":["sample_2@dummy.com"], + "email_group_id_list":["sample_3@dummy.com"] } + } + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { UpdateNotificationConfigRequest.parse(it) } + assertEquals(config, recreatedObject.notificationConfig) + assertEquals("config_id1", recreatedObject.configId) + } + + @Test + fun `Update config should deserialize json object using parser SmtpAccount`() { + val sampleSmtpAccount = SmtpAccount( + host = "http://dummy.com", + port = 11, + method = MethodType.SSL, + fromAddress = "sample@dummy.com" + ) + val config = NotificationConfig( + "name", + "description", + ConfigType.SMTP_ACCOUNT, + EnumSet.of(Feature.INDEX_MANAGEMENT), + isEnabled = true, + configData = sampleSmtpAccount + ) + + val jsonString = """ + { + "config_id":"config_id1", + "config":{ + "name":"name", + "description":"description", + "config_type":"smtp_account", + "feature_list":["index_management"], + "is_enabled":true, + "smtp_account":{"host":"http://dummy.com", "port":11,"method": "ssl", "from_address": "sample@dummy.com" } + } + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { UpdateNotificationConfigRequest.parse(it) } + assertEquals(config, recreatedObject.notificationConfig) + assertEquals("config_id1", recreatedObject.configId) + } + + @Test + fun `Update config should throw exception when invalid json object is passed`() { + val jsonString = "sample message" + assertThrows { + createObjectFromJsonString(jsonString) { UpdateNotificationConfigRequest.parse(it) } + } + } + + @Test + fun `Update config should safely ignore extra field in json object`() { + val sampleSlack = Slack("https://domain.com/sample_slack_url#1234567890") + val config = NotificationConfig( + "name", + "description", + ConfigType.SLACK, + EnumSet.of(Feature.INDEX_MANAGEMENT), + isEnabled = true, + configData = sampleSlack + ) + + val jsonString = """ + { + "config_id":"config_id1", + "config":{ + "name":"name", + "description":"description", + "config_type":"slack", + "feature_list":["index_management"], + "is_enabled":true, + "slack":{"url":"https://domain.com/sample_slack_url#1234567890"}, + "extra_field_1":["extra", "value"], + "extra_field_2":{"extra":"value"}, + "extra_field_3":"extra value 3" + } + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { UpdateNotificationConfigRequest.parse(it) } + assertEquals(config, recreatedObject.notificationConfig) + assertEquals("config_id1", recreatedObject.configId) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigResponseTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigResponseTests.kt new file mode 100644 index 00000000..aa2839c3 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/action/UpdateNotificationConfigResponseTests.kt @@ -0,0 +1,92 @@ +/* + * 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.action + +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 UpdateNotificationConfigResponseTests { + + @Test + fun `Update response serialize and deserialize transport object should be equal`() { + val configResponse = UpdateNotificationConfigResponse("sample_config_id") + val recreatedObject = recreateObject(configResponse) { UpdateNotificationConfigResponse(it) } + assertEquals(configResponse.configId, recreatedObject.configId) + } + + @Test + fun `Update response serialize and deserialize using json object should be equal`() { + val configResponse = UpdateNotificationConfigResponse("sample_config_id") + val jsonString = getJsonString(configResponse) + val recreatedObject = createObjectFromJsonString(jsonString) { UpdateNotificationConfigResponse.parse(it) } + assertEquals(configResponse.configId, recreatedObject.configId) + } + + @Test + fun `Update response should deserialize json object using parser`() { + val configId = "sample_config_id" + val jsonString = "{\"config_id\":\"$configId\"}" + val recreatedObject = createObjectFromJsonString(jsonString) { UpdateNotificationConfigResponse.parse(it) } + assertEquals(configId, recreatedObject.configId) + } + + @Test + fun `Update response should throw exception when invalid json object is passed`() { + val jsonString = "sample message" + assertThrows { + createObjectFromJsonString(jsonString) { UpdateNotificationConfigResponse.parse(it) } + } + } + + @Test + fun `Update response should throw exception when configId is replace with configId2 in json object`() { + val jsonString = "{\"config_id2\":\"sample_config_id\"}" + assertThrows { + createObjectFromJsonString(jsonString) { UpdateNotificationConfigResponse.parse(it) } + } + } + + @Test + fun `Update response should safely ignore extra field in json object`() { + val configId = "sample_config_id" + val jsonString = """ + { + "config_id":"$configId", + "extra_field_1":["extra", "value"], + "extra_field_2":{"extra":"value"}, + "extra_field_3":"extra value 3" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { UpdateNotificationConfigResponse.parse(it) } + assertEquals(configId, recreatedObject.configId) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/AttachmentTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/AttachmentTests.kt new file mode 100644 index 00000000..b0873cfe --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/AttachmentTests.kt @@ -0,0 +1,146 @@ +/* + * 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 +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.opensearch.commons.utils.createObjectFromJsonString +import org.opensearch.commons.utils.getJsonString +import org.opensearch.commons.utils.recreateObject + +internal class AttachmentTests { + + @Test + fun `Attachment Object serialize and deserialize using transport should be equal`() { + val attachment = Attachment( + "fileName", + "fileEncoding", + "fileData", + "fileContentType" + ) + val recreatedObject = recreateObject(attachment) { Attachment(it) } + assertEquals(attachment, recreatedObject) + } + + @Test + fun `Attachment Object serialize and deserialize using json should be equal`() { + val attachment = Attachment( + "fileName", + "fileEncoding", + "fileData", + "fileContentType" + ) + val jsonString = getJsonString(attachment) + val recreatedObject = createObjectFromJsonString(jsonString) { Attachment.parse(it) } + assertEquals(attachment, recreatedObject) + } + + @Test + fun `Attachment Json parsing should safely ignore extra fields`() { + val attachment = Attachment( + "fileName", + "fileEncoding", + "fileData", + "fileContentType" + ) + val jsonString = """ + { + "file_name":"fileName", + "file_encoding":"fileEncoding", + "file_data":"fileData", + "file_content_type":"fileContentType", + "extra_field_1":["extra", "value"], + "extra_field_2":{"extra":"value"}, + "extra_field_3":"extra value 3" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { Attachment.parse(it) } + assertEquals(attachment, recreatedObject) + } + + @Test + fun `Attachment Json parsing should safely ignore null content type`() { + val attachment = Attachment( + "fileName", + "fileEncoding", + "fileData", + null + ) + val jsonString = """ + { + "file_name":"fileName", + "file_encoding":"fileEncoding", + "file_data":"fileData" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { Attachment.parse(it) } + assertEquals(attachment, recreatedObject) + } + + @Test + fun `Attachment Json parsing should throw exception if file_name is absent`() { + val jsonString = """ + { + "file_encoding":"fileEncoding", + "file_data":"fileData", + "file_content_type":"fileContentType" + } + """.trimIndent() + Assertions.assertThrows(IllegalArgumentException::class.java) { + createObjectFromJsonString(jsonString) { Attachment.parse(it) } + } + } + + @Test + fun `Attachment Json parsing should throw exception if file_encoding is absent`() { + val jsonString = """ + { + "file_name":"fileName", + "file_data":"fileData", + "file_content_type":"fileContentType" + } + """.trimIndent() + Assertions.assertThrows(IllegalArgumentException::class.java) { + createObjectFromJsonString(jsonString) { Attachment.parse(it) } + } + } + + @Test + fun `Attachment Json parsing should throw exception if file_data is absent`() { + val jsonString = """ + { + "file_name":"fileName", + "file_encoding":"fileEncoding", + "file_content_type":"fileContentType" + } + """.trimIndent() + Assertions.assertThrows(IllegalArgumentException::class.java) { + createObjectFromJsonString(jsonString) { Attachment.parse(it) } + } + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/ChannelMessageTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/ChannelMessageTests.kt new file mode 100644 index 00000000..7cda9d66 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/ChannelMessageTests.kt @@ -0,0 +1,202 @@ +/* + * 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 +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.opensearch.commons.utils.createObjectFromJsonString +import org.opensearch.commons.utils.getJsonString +import org.opensearch.commons.utils.recreateObject + +internal class ChannelMessageTests { + + @Test + fun `ChannelMessage Object serialize and deserialize using transport should be equal`() { + val attachment = Attachment( + "fileName", + "fileEncoding", + "fileData", + "fileContentType" + ) + val channelMessage = ChannelMessage( + "textDescription", + "htmlDescription", + attachment + ) + val recreatedObject = recreateObject(channelMessage) { ChannelMessage(it) } + assertEquals(channelMessage, recreatedObject) + } + + @Test + fun `ChannelMessage Object serialize and deserialize using json should be equal`() { + val attachment = Attachment( + "fileName", + "fileEncoding", + "fileData", + "fileContentType" + ) + val channelMessage = ChannelMessage( + "textDescription", + "htmlDescription", + attachment + ) + val jsonString = getJsonString(channelMessage) + val recreatedObject = createObjectFromJsonString(jsonString) { ChannelMessage.parse(it) } + assertEquals(channelMessage, recreatedObject) + } + + @Test + fun `ChannelMessage Json parsing should safely ignore extra fields`() { + val attachment = Attachment( + "fileName", + "fileEncoding", + "fileData", + "fileContentType" + ) + val channelMessage = ChannelMessage( + "textDescription", + "htmlDescription", + attachment + ) + val jsonString = """ + { + "text_description":"textDescription", + "html_description":"htmlDescription", + "attachment":{ + "file_name":"fileName", + "file_encoding":"fileEncoding", + "file_data":"fileData", + "file_content_type":"fileContentType" + }, + "extra_field_1":["extra", "value"], + "extra_field_2":{"extra":"value"}, + "extra_field_3":"extra value 3" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { ChannelMessage.parse(it) } + assertEquals(channelMessage, recreatedObject) + } + + @Test + fun `ChannelMessage Json parsing should safely ignore missing html description`() { + val attachment = Attachment( + "fileName", + "fileEncoding", + "fileData", + "fileContentType" + ) + val channelMessage = ChannelMessage( + "textDescription", + null, + attachment + ) + val jsonString = """ + { + "text_description":"textDescription", + "attachment":{ + "file_name":"fileName", + "file_encoding":"fileEncoding", + "file_data":"fileData", + "file_content_type":"fileContentType" + } + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { ChannelMessage.parse(it) } + assertEquals(channelMessage, recreatedObject) + } + + @Test + fun `ChannelMessage Json parsing should safely ignore missing attachment`() { + val channelMessage = ChannelMessage( + "textDescription", + "htmlDescription", + null + ) + val jsonString = """ + { + "text_description":"textDescription", + "html_description":"htmlDescription" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { ChannelMessage.parse(it) } + assertEquals(channelMessage, recreatedObject) + } + + @Test + fun `ChannelMessage Json parsing should safely ignore both missing html_description and attachment`() { + val channelMessage = ChannelMessage( + "textDescription", + null, + null + ) + val jsonString = """ + { + "text_description":"textDescription" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { ChannelMessage.parse(it) } + assertEquals(channelMessage, recreatedObject) + } + + @Test + fun `ChannelMessage Json parsing should throw exception if text_description is empty`() { + val jsonString = """ + { + "text_description":"", + "html_description":"htmlDescription", + "attachment":{ + "file_name":"fileName", + "file_encoding":"fileEncoding", + "file_data":"fileData", + "file_content_type":"fileContentType" + } + } + """.trimIndent() + Assertions.assertThrows(IllegalArgumentException::class.java) { + createObjectFromJsonString(jsonString) { ChannelMessage.parse(it) } + } + } + + @Test + fun `ChannelMessage Json parsing should throw exception if text_description is absent`() { + val jsonString = """ + { + "html_description":"htmlDescription", + "attachment":{ + "file_name":"fileName", + "file_encoding":"fileEncoding", + "file_data":"fileData", + "file_content_type":"fileContentType" + } + } + """.trimIndent() + Assertions.assertThrows(IllegalArgumentException::class.java) { + createObjectFromJsonString(jsonString) { ChannelMessage.parse(it) } + } + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/ChimeTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/ChimeTests.kt new file mode 100644 index 00000000..f70dc097 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/ChimeTests.kt @@ -0,0 +1,109 @@ +/* + * 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 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 +import java.net.MalformedURLException + +internal class ChimeTests { + + @Test + fun `Chime serialize and deserialize transport object should be equal`() { + val sampleChime = Chime("https://domain.com/sample_url#1234567890") + val recreatedObject = recreateObject(sampleChime) { Chime(it) } + assertEquals(sampleChime, recreatedObject) + } + + @Test + fun `Chime serialize and deserialize using json object should be equal`() { + val sampleChime = Chime("https://domain.com/sample_url#1234567890") + val jsonString = getJsonString(sampleChime) + val recreatedObject = createObjectFromJsonString(jsonString) { Chime.parse(it) } + assertEquals(sampleChime, recreatedObject) + } + + @Test + fun `Chime should deserialize json object using parser`() { + val sampleChime = Chime("https://domain.com/sample_url#1234567890") + val jsonString = "{\"url\":\"${sampleChime.url}\"}" + val recreatedObject = createObjectFromJsonString(jsonString) { Chime.parse(it) } + assertEquals(sampleChime, recreatedObject) + } + + @Test + fun `Chime should throw exception when invalid json object is passed`() { + val jsonString = "sample message" + assertThrows { + createObjectFromJsonString(jsonString) { Chime.parse(it) } + } + } + + @Test + fun `Chime should throw exception when url is replace with url2 in json object`() { + val sampleChime = Chime("https://domain.com/sample_url#1234567890") + val jsonString = "{\"url2\":\"${sampleChime.url}\"}" + assertThrows { + createObjectFromJsonString(jsonString) { Chime.parse(it) } + } + } + + @Test + fun `Chime should throw exception when url is not proper`() { + assertThrows { + Chime("domain.com/sample_url#1234567890") + } + val jsonString = "{\"url\":\"domain.com/sample_url\"}" + assertThrows { + createObjectFromJsonString(jsonString) { Chime.parse(it) } + } + } + + @Test + fun `Chime should throw exception when url protocol is not https`() { + assertThrows { + Chime("http://domain.com/sample_url#1234567890") + } + val jsonString = "{\"url\":\"http://domain.com/sample_url\"}" + assertThrows { + createObjectFromJsonString(jsonString) { Chime.parse(it) } + } + } + + @Test + fun `Chime should safely ignore extra field in json object`() { + val sampleChime = Chime("https://domain.com/sample_url#1234567890") + val jsonString = "{\"url\":\"${sampleChime.url}\", \"another\":\"field\"}" + val recreatedObject = createObjectFromJsonString(jsonString) { Chime.parse(it) } + assertEquals(sampleChime, recreatedObject) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/ConfigTypeTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/ConfigTypeTests.kt new file mode 100644 index 00000000..28b1ec7e --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/ConfigTypeTests.kt @@ -0,0 +1,56 @@ +/* + * 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.ConfigType.Companion.enumParser +import org.opensearch.commons.notifications.model.ConfigType.Companion.fromTagOrDefault + +internal class ConfigTypeTests { + + @Test + fun `toString should return tag`() { + ConfigType.values().forEach { + assertEquals(it.tag, it.toString()) + } + } + + @Test + fun `fromTagOrDefault should return corresponding enum`() { + ConfigType.values().forEach { + assertEquals(it, fromTagOrDefault(it.tag)) + } + } + + @Test + fun `EnumParser fromTagOrDefault should return corresponding enum`() { + ConfigType.values().forEach { + assertEquals(it, enumParser.fromTagOrDefault(it.tag)) + } + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/DeliveryStatusTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/DeliveryStatusTests.kt new file mode 100644 index 00000000..377d2be9 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/DeliveryStatusTests.kt @@ -0,0 +1,84 @@ +/* + * 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 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 DeliveryStatusTests { + @Test + fun `DeliveryStatus serialize and deserialize should be equal`() { + val sampleDeliveryStatus = DeliveryStatus( + "404", + "invalid recipient" + ) + val recreatedObject = recreateObject(sampleDeliveryStatus) { DeliveryStatus(it) } + assertEquals(sampleDeliveryStatus, recreatedObject) + } + + @Test + fun `DeliveryStatus serialize and deserialize using json should be equal`() { + val sampleDeliveryStatus = DeliveryStatus( + "404", + "invalid recipient" + ) + val jsonString = getJsonString(sampleDeliveryStatus) + val recreatedObject = createObjectFromJsonString(jsonString) { DeliveryStatus.parse(it) } + assertEquals(sampleDeliveryStatus, recreatedObject) + } + + @Test + fun `DeliveryStatus should throw exception when invalid json object is passed`() { + val jsonString = "sample message" + assertThrows { + createObjectFromJsonString(jsonString) { DeliveryStatus.parse(it) } + } + } + + @Test + fun `DeliveryStatus should safely ignore extra field in json object`() { + val sampleDeliveryStatus = DeliveryStatus( + "404", + "invalid recipient" + ) + val jsonString = """ + { + "status_code": "404", + "status_text": "invalid recipient", + "extra": "field" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { DeliveryStatus.parse(it) } + assertEquals(sampleDeliveryStatus, recreatedObject) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/EmailGroupTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/EmailGroupTests.kt new file mode 100644 index 00000000..5146d785 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/EmailGroupTests.kt @@ -0,0 +1,141 @@ +/* + * 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 com.fasterxml.jackson.core.JsonParseException +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertDoesNotThrow +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 EmailGroupTests { + + private fun checkValidEmailAddress(emailAddress: String) { + assertDoesNotThrow("should accept $emailAddress") { + EmailGroup(listOf(emailAddress)) + } + } + + private fun checkInvalidEmailAddress(emailAddress: String) { + assertThrows("Should throw an Exception for invalid email $emailAddress") { + EmailGroup(listOf(emailAddress)) + } + } + + @Test + fun `EmailGroup should accept valid email address`() { + checkValidEmailAddress("email1234@email.com") + checkValidEmailAddress("email+1234@email.com") + checkValidEmailAddress("email-1234@email.com") + checkValidEmailAddress("email_1234@email.com") + checkValidEmailAddress("email.1234@email.com") + checkValidEmailAddress("e.ma_il-1+2@test-email-domain.co.uk") + checkValidEmailAddress("email-.+_=#|@domain.com") + checkValidEmailAddress("e@mail.com") + } + + @Test + fun `EmailGroup should throw exception for invalid email address`() { + checkInvalidEmailAddress("email") + checkInvalidEmailAddress("email@") + checkInvalidEmailAddress("email@1234@email.com") + checkInvalidEmailAddress(".email@email.com") + checkInvalidEmailAddress("email.@email.com") + checkInvalidEmailAddress("email..1234@email.com") + checkInvalidEmailAddress("email@email..com") + checkInvalidEmailAddress("email@.com") + checkInvalidEmailAddress("email@email.com.") + checkInvalidEmailAddress("email@.email.com") + checkInvalidEmailAddress("email@email.com-") + checkInvalidEmailAddress("email@email_domain.com") + } + + @Test + fun `EmailGroup serialize and deserialize transport object should be equal`() { + val sampleEmailGroup = EmailGroup(listOf("email1@email.com", "email2@email.com")) + val recreatedObject = recreateObject(sampleEmailGroup) { EmailGroup(it) } + assertEquals(sampleEmailGroup, recreatedObject) + } + + @Test + fun `EmailGroup serialize and deserialize using json object should be equal`() { + val sampleEmailGroup = EmailGroup(listOf("email1@email.com", "email2@email.com")) + val jsonString = getJsonString(sampleEmailGroup) + val recreatedObject = createObjectFromJsonString(jsonString) { EmailGroup.parse(it) } + assertEquals(sampleEmailGroup, recreatedObject) + } + + @Test + fun `EmailGroup should deserialize json object using parser`() { + val sampleEmailGroup = EmailGroup(listOf("email1@email.com", "email2@email.com")) + val jsonString = """ + { + "recipient_list":[ + "${sampleEmailGroup.recipients[0]}", + "${sampleEmailGroup.recipients[1]}" + ] + }" + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { EmailGroup.parse(it) } + assertEquals(sampleEmailGroup, recreatedObject) + } + + @Test + fun `EmailGroup should throw exception when invalid json object is passed`() { + val jsonString = "sample message" + assertThrows { + createObjectFromJsonString(jsonString) { EmailGroup.parse(it) } + } + } + + @Test + fun `EmailGroup should throw exception when recipients is replaced with recipients2 in json object`() { + val sampleEmailGroup = EmailGroup(listOf("email1@email.com", "email2@email.com")) + val jsonString = """ + { + "recipient_list2":[ + "${sampleEmailGroup.recipients[0]}", + "${sampleEmailGroup.recipients[1]}" + ] + }" + """.trimIndent() + assertThrows { + createObjectFromJsonString(jsonString) { EmailGroup.parse(it) } + } + } + + @Test + fun `EmailGroup should safely ignore extra field in json object`() { + val sampleEmailGroup = EmailGroup(listOf("email@email.com")) + val jsonString = "{\"recipient_list\":[\"${sampleEmailGroup.recipients[0]}\"], \"another\":\"field\"}" + val recreatedObject = createObjectFromJsonString(jsonString) { EmailGroup.parse(it) } + assertEquals(sampleEmailGroup, recreatedObject) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/EmailRecipientStatusTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/EmailRecipientStatusTests.kt new file mode 100644 index 00000000..31bb80bd --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/EmailRecipientStatusTests.kt @@ -0,0 +1,94 @@ +/* + * 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 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 EmailRecipientStatusTests { + @Test + fun `EmailRecipientStatus serialize and deserialize should be equal`() { + val sampleEmailRecipientStatus = EmailRecipientStatus( + "sample@email.com", + DeliveryStatus("404", "invalid recipient") + ) + val recreatedObject = recreateObject(sampleEmailRecipientStatus) { EmailRecipientStatus(it) } + assertEquals(sampleEmailRecipientStatus, recreatedObject) + } + + @Test + fun `EmailRecipientStatus serialize and deserialize using json should be equal`() { + val sampleEmailRecipientStatus = EmailRecipientStatus( + "sample@email.com", + DeliveryStatus("404", "invalid recipient") + ) + val jsonString = getJsonString(sampleEmailRecipientStatus) + val recreatedObject = createObjectFromJsonString(jsonString) { EmailRecipientStatus.parse(it) } + assertEquals(sampleEmailRecipientStatus, recreatedObject) + } + + @Test + fun `EmailRecipientStatus should throw exception for invalid recipient`() { + assertThrows("Should throw an Exception for invalid recipient Slack") { + EmailRecipientStatus("slack", DeliveryStatus("404", "invalid recipient")) + } + } + + @Test + fun `EmailRecipientStatus should throw exception when invalid json object is passed`() { + val jsonString = "sample message" + assertThrows { + createObjectFromJsonString(jsonString) { EmailRecipientStatus.parse(it) } + } + } + + @Test + fun `EmailRecipientStatus should safely ignore extra field in json object`() { + val sampleEmailRecipientStatus = EmailRecipientStatus( + "sample@email.com", + DeliveryStatus("200", "Success") + ) + val jsonString = """ + { + "recipient": "sample@email.com", + "delivery_status": { + "status_code": "200", + "status_text": "Success" + }, + "extra": "field" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { EmailRecipientStatus.parse(it) } + assertEquals(sampleEmailRecipientStatus, recreatedObject) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/EmailTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/EmailTests.kt new file mode 100644 index 00000000..6590f08e --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/EmailTests.kt @@ -0,0 +1,191 @@ +/* + * 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 com.fasterxml.jackson.core.JsonParseException +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertDoesNotThrow +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 EmailTests { + + private fun checkValidEmailAddress(emailAddress: String) { + assertDoesNotThrow("should accept $emailAddress") { + Email("sampleId", listOf(emailAddress), listOf()) + } + } + + private fun checkInvalidEmailAddress(emailAddress: String) { + assertThrows("Should throw an Exception for invalid email $emailAddress") { + Email("sampleId", listOf(emailAddress), listOf()) + } + } + + @Test + fun `Email should accept valid email address`() { + checkValidEmailAddress("email1234@email.com") + checkValidEmailAddress("email+1234@email.com") + checkValidEmailAddress("email-1234@email.com") + checkValidEmailAddress("email_1234@email.com") + checkValidEmailAddress("email.1234@email.com") + checkValidEmailAddress("e.ma_il-1+2@test-email-domain.co.uk") + checkValidEmailAddress("email-.+_=#|@domain.com") + checkValidEmailAddress("e@mail.com") + } + + @Test + fun `Email should throw exception for invalid email address`() { + checkInvalidEmailAddress("email") + checkInvalidEmailAddress("email@") + checkInvalidEmailAddress("email@1234@email.com") + checkInvalidEmailAddress(".email@email.com") + checkInvalidEmailAddress("email.@email.com") + checkInvalidEmailAddress("email..1234@email.com") + checkInvalidEmailAddress("email@email..com") + checkInvalidEmailAddress("email@.com") + checkInvalidEmailAddress("email@email.com.") + checkInvalidEmailAddress("email@.email.com") + checkInvalidEmailAddress("email@email.com-") + checkInvalidEmailAddress("email@email_domain.com") + } + + @Test + fun `Email serialize and deserialize transport object should be equal`() { + val sampleEmail = Email( + "sampleAccountId", + listOf("email1@email.com", "email2@email.com"), + listOf("sample_group_id_1", "sample_group_id_2") + ) + val recreatedObject = recreateObject(sampleEmail) { Email(it) } + assertEquals(sampleEmail, recreatedObject) + } + + @Test + fun `Email serialize and deserialize using json object should be equal`() { + val sampleEmail = Email( + "sampleAccountId", + listOf("email1@email.com", "email2@email.com"), + listOf("sample_group_id_1", "sample_group_id_2") + ) + val jsonString = getJsonString(sampleEmail) + val recreatedObject = createObjectFromJsonString(jsonString) { Email.parse(it) } + assertEquals(sampleEmail, recreatedObject) + } + + @Test + fun `Email should deserialize json object using parser`() { + val sampleEmail = Email( + "sampleAccountId", + listOf("email1@email.com", "email2@email.com"), + listOf("sample_group_id_1", "sample_group_id_2") + ) + val jsonString = """ + { + "email_account_id":"${sampleEmail.emailAccountID}", + "recipient_list":[ + "${sampleEmail.recipients[0]}", + "${sampleEmail.recipients[1]}" + ], + "email_group_id_list":[ + "${sampleEmail.emailGroupIds[0]}", + "${sampleEmail.emailGroupIds[1]}" + ] + }" + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { Email.parse(it) } + assertEquals(sampleEmail, recreatedObject) + } + + @Test + fun `Email should throw exception when invalid json object is passed`() { + val jsonString = "sample message" + assertThrows { + createObjectFromJsonString(jsonString) { Email.parse(it) } + } + } + + @Test + fun `Email should throw exception when emailAccountID is replaced with emailAccountID2 in json object`() { + val sampleEmail = Email( + "sampleAccountId", + listOf("email1@email.com", "email2@email.com"), + listOf("sample_group_id_1", "sample_group_id_2") + ) + val jsonString = """ + { + "email_account_id2":"${sampleEmail.emailAccountID}", + "recipient_list":[ + "${sampleEmail.recipients[0]}", + "${sampleEmail.recipients[1]}" + ], + "email_group_id_list":[ + "${sampleEmail.emailGroupIds[0]}", + "${sampleEmail.emailGroupIds[1]}" + ] + }" + """.trimIndent() + assertThrows { + createObjectFromJsonString(jsonString) { Email.parse(it) } + } + } + + @Test + fun `Email should accept without defaultRecipients and defaultEmailGroupIds in json object`() { + val sampleEmail = Email("sampleAccountId", listOf(), listOf()) + val jsonString = """ + { + "email_account_id":"${sampleEmail.emailAccountID}" + }" + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { Email.parse(it) } + assertEquals(sampleEmail, recreatedObject) + } + + @Test + fun `Email should safely ignore extra field in json object`() { + val sampleEmail = Email("sampleAccountId", listOf(), listOf()) + val jsonString = """ + { + "email_account_id":"${sampleEmail.emailAccountID}", + "recipient_list2":[ + "email1@email.com", + "email2@email.com" + ], + "email_group_id_list2":[ + "sample_group_id_1" + ], + "another":"field" + }" + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { Email.parse(it) } + assertEquals(sampleEmail, recreatedObject) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/EventSourceTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/EventSourceTests.kt new file mode 100644 index 00000000..883c5047 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/EventSourceTests.kt @@ -0,0 +1,123 @@ +/* + * 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 +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.opensearch.commons.utils.createObjectFromJsonString +import org.opensearch.commons.utils.getJsonString +import org.opensearch.commons.utils.recreateObject + +internal class EventSourceTests { + + @Test + fun `Event source serialize and deserialize should be equal`() { + val sampleEventSource = EventSource( + "title", + "reference_id", + Feature.ALERTING, + severity = SeverityType.INFO + ) + val recreatedObject = recreateObject(sampleEventSource) { EventSource(it) } + assertEquals(sampleEventSource, recreatedObject) + } + + @Test + fun `Event source serialize and deserialize using json should be equal`() { + val sampleEventSource = EventSource( + "title", + "reference_id", + Feature.ALERTING, + severity = SeverityType.INFO + ) + + val jsonString = getJsonString(sampleEventSource) + val recreatedObject = createObjectFromJsonString(jsonString) { EventSource.parse(it) } + assertEquals(sampleEventSource, recreatedObject) + } + + @Test + fun `Event source should safely ignore extra field in json object`() { + val sampleEventSource = EventSource( + "title", + "reference_id", + Feature.ALERTING, + tags = listOf("tag1", "tag2"), + severity = SeverityType.INFO + ) + val jsonString = """ + { + "title":"title", + "reference_id":"reference_id", + "feature":"alerting", + "severity":"info", + "tags":["tag1", "tag2"], + "extra_field_1":["extra", "value"], + "extra_field_2":{"extra":"value"}, + "extra_field_3":"extra value 3" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { EventSource.parse(it) } + assertEquals(sampleEventSource, recreatedObject) + } + + @Test + fun `Event source should safely ignore unknown feature type in json object`() { + val sampleEventSource = EventSource( + "title", + "reference_id", + Feature.NONE, + tags = listOf("tag1", "tag2"), + severity = SeverityType.INFO + ) + val jsonString = """ + { + "title":"title", + "reference_id":"reference_id", + "feature": "NewFeature", + "severity":"info", + "tags":["tag1", "tag2"] + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { EventSource.parse(it) } + assertEquals(sampleEventSource, recreatedObject) + } + + @Test + fun `Event source throw exception if name is empty`() { + Assertions.assertThrows(IllegalArgumentException::class.java) { + EventSource( + "", + "reference_id", + Feature.ALERTING, + tags = listOf("tag1", "tag2"), + severity = SeverityType.INFO + ) + } + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/EventStatusTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/EventStatusTests.kt new file mode 100644 index 00000000..e9a1634f --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/EventStatusTests.kt @@ -0,0 +1,164 @@ +/* + * 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 com.fasterxml.jackson.core.JsonParseException +import org.junit.jupiter.api.Assertions +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 EventStatusTests { + + @Test + fun `Event Status serialize and deserialize should be equal`() { + val sampleStatus = EventStatus( + "config_id", + "name", + ConfigType.SLACK, + deliveryStatus = DeliveryStatus("404", "invalid recipient") + ) + val recreatedObject = recreateObject(sampleStatus) { EventStatus(it) } + assertEquals(sampleStatus, recreatedObject) + } + + @Test + fun `Event Status serialize and deserialize using json should be equal`() { + val sampleStatus = EventStatus( + "config_id", + "name", + ConfigType.SLACK, + deliveryStatus = DeliveryStatus("404", "invalid recipient") + ) + val jsonString = getJsonString(sampleStatus) + val recreatedObject = createObjectFromJsonString(jsonString) { EventStatus.parse(it) } + assertEquals(sampleStatus, recreatedObject) + } + + @Test + fun `Event Status should safely ignore extra field in json object`() { + val sampleStatus = EventStatus( + "config_id", + "name", + ConfigType.SLACK, + deliveryStatus = DeliveryStatus("404", "invalid recipient") + ) + val jsonString = """ + { + "config_id":"config_id", + "config_type":"slack", + "config_name":"name", + "email_recipient_status":[], + "delivery_status": + { + "status_code":"404", + "status_text":"invalid recipient" + }, + "extra_field_1":["extra", "value"], + "extra_field_2":{"extra":"value"}, + "extra_field_3":"extra value 3" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { EventStatus.parse(it) } + assertEquals(sampleStatus, recreatedObject) + } + + @Test + fun `Event Status should throw exception when config type is email with empty emailRecipientList`() { + val jsonString = """ + { + "config_id":"config_id", + "config_type":"email", + "config_name":"name", + "delivery_status": + { + "status_code":"404", + "status_text":"invalid recipient" + }, + "email_recipient_status":[] + } + """.trimIndent() + assertThrows { + createObjectFromJsonString(jsonString) { EventStatus.parse(it) } + } + } + + @Test + fun `Event should throw exception when invalid json object is passed`() { + val jsonString = "sample message" + assertThrows { + createObjectFromJsonString(jsonString) { EventSource.parse(it) } + } + } + + @Test + fun `Event throw exception if deliveryStatus is empty for config type Slack`() { + Assertions.assertThrows(IllegalArgumentException::class.java) { + EventStatus( + "config_id", + "name", + ConfigType.SLACK + ) + } + } + + @Test + fun `Event throw exception if deliveryStatus is empty for config type Chime`() { + Assertions.assertThrows(IllegalArgumentException::class.java) { + EventStatus( + "config_id", + "name", + ConfigType.CHIME + ) + } + } + + @Test + fun `Event throw exception if deliveryStatus is empty for config type Webhook`() { + Assertions.assertThrows(IllegalArgumentException::class.java) { + EventStatus( + "config_id", + "name", + ConfigType.WEBHOOK + ) + } + } + + @Test + fun `Event throw exception if emailRecipientStatus is empty for config type Email`() { + Assertions.assertThrows(IllegalArgumentException::class.java) { + EventStatus( + "config_id", + "name", + ConfigType.EMAIL + ) + } + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/FeatureChannelListTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/FeatureChannelListTests.kt new file mode 100644 index 00000000..5bd3db62 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/FeatureChannelListTests.kt @@ -0,0 +1,232 @@ +/* + * 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.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.utils.createObjectFromJsonString +import org.opensearch.commons.utils.getJsonString +import org.opensearch.commons.utils.recreateObject + +internal class FeatureChannelListTests { + + private fun assertSearchResultEquals( + expected: FeatureChannelList, + actual: FeatureChannelList + ) { + assertEquals(expected.startIndex, actual.startIndex) + assertEquals(expected.totalHits, actual.totalHits) + assertEquals(expected.totalHitRelation, actual.totalHitRelation) + assertEquals(expected.objectListFieldName, actual.objectListFieldName) + assertEquals(expected.objectList, actual.objectList) + } + + @Test + fun `Feature Channel List serialize and deserialize using transport should be equal`() { + val featureChannel = FeatureChannel( + "configId", + "name", + "description", + ConfigType.SLACK, + true + ) + val featureChannelList = FeatureChannelList(featureChannel) + val recreatedObject = recreateObject(featureChannelList) { FeatureChannelList(it) } + assertSearchResultEquals(featureChannelList, recreatedObject) + } + + @Test + fun `Feature Channel List serialize and deserialize multiple object with default values should be equal`() { + val featureChannel1 = FeatureChannel( + "configId1", + "name1", + "description1", + ConfigType.SLACK, + true + ) + val featureChannel2 = FeatureChannel( + "configId2", + "name2", + "description2", + ConfigType.CHIME, + true + ) + val featureChannelList = FeatureChannelList(listOf(featureChannel1, featureChannel2)) + val expectedResult = FeatureChannelList( + 0, + 2, + TotalHits.Relation.EQUAL_TO, + listOf(featureChannel1, featureChannel2) + ) + val recreatedObject = recreateObject(featureChannelList) { FeatureChannelList(it) } + assertSearchResultEquals(expectedResult, recreatedObject) + } + + @Test + fun `Feature Channel List serialize and deserialize with multiple object should be equal`() { + val featureChannel1 = FeatureChannel( + "configId1", + "name1", + "description1", + ConfigType.SLACK, + true + ) + val featureChannel2 = FeatureChannel( + "configId2", + "name2", + "description2", + ConfigType.CHIME, + true + ) + val featureChannelList = FeatureChannelList( + 100, + 1000, + TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO, + listOf(featureChannel1, featureChannel2) + ) + val recreatedObject = recreateObject(featureChannelList) { FeatureChannelList(it) } + assertSearchResultEquals(featureChannelList, recreatedObject) + } + + @Test + fun `Feature Channel List serialize and deserialize using json should be equal`() { + val featureChannel = FeatureChannel( + "configId", + "name", + "description", + ConfigType.SLACK, + true + ) + val featureChannelList = FeatureChannelList(featureChannel) + val jsonString = getJsonString(featureChannelList) + val recreatedObject = createObjectFromJsonString(jsonString) { FeatureChannelList(it) } + assertSearchResultEquals(featureChannelList, recreatedObject) + } + + @Test + fun `Feature Channel List serialize and deserialize using json with multiple object should be equal`() { + val featureChannel1 = FeatureChannel( + "configId1", + "name1", + "description1", + ConfigType.SLACK, + true + ) + val featureChannel2 = FeatureChannel( + "configId2", + "name2", + "description2", + ConfigType.CHIME, + true + ) + val featureChannelList = FeatureChannelList( + 100, + 1000, + TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO, + listOf(featureChannel1, featureChannel2) + ) + val jsonString = getJsonString(featureChannelList) + val recreatedObject = createObjectFromJsonString(jsonString) { FeatureChannelList(it) } + assertSearchResultEquals(featureChannelList, recreatedObject) + } + + @Test + fun `Feature Channel List should safely ignore extra field in json object`() { + val featureChannel = FeatureChannel( + "configId", + "name", + "description", + ConfigType.SLACK, + true + ) + val featureChannelList = FeatureChannelList(featureChannel) + val jsonString = """ + { + "start_index":"0", + "total_hits":"1", + "total_hit_relation":"eq", + "feature_channel_list":[ + { + "config_id":"configId", + "name":"name", + "description":"description", + "config_type":"slack", + "is_enabled":true + } + ], + "extra_field_1":["extra", "value"], + "extra_field_2":{"extra":"value"}, + "extra_field_3":"extra value 3" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { FeatureChannelList(it) } + assertSearchResultEquals(featureChannelList, recreatedObject) + } + + @Test + fun `Feature Channel List should safely fallback to default if startIndex, totalHits or totalHitRelation field absent in json object`() { + val featureChannel = FeatureChannel( + "configId", + "name", + "description", + ConfigType.SLACK, + true + ) + val featureChannelList = FeatureChannelList(featureChannel) + val jsonString = """ + { + "feature_channel_list":[ + { + "config_id":"configId", + "name":"name", + "description":"description", + "config_type":"slack", + "is_enabled":true + } + ] + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { FeatureChannelList(it) } + assertSearchResultEquals(featureChannelList, recreatedObject) + } + + @Test + fun `Feature Channel List should throw exception if feature_channel_list is absent in json`() { + val jsonString = """ + { + "start_index":"0", + "total_hits":"1", + "total_hit_relation":"eq" + } + """.trimIndent() + Assertions.assertThrows(IllegalArgumentException::class.java) { + createObjectFromJsonString(jsonString) { FeatureChannelList(it) } + } + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/FeatureChannelTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/FeatureChannelTests.kt new file mode 100644 index 00000000..d2b8f009 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/FeatureChannelTests.kt @@ -0,0 +1,230 @@ +/* + * 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 +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.opensearch.commons.utils.createObjectFromJsonString +import org.opensearch.commons.utils.getJsonString +import org.opensearch.commons.utils.recreateObject + +internal class FeatureChannelTests { + + @Test + fun `FeatureChannel Object serialize and deserialize using transport should be equal`() { + val featureChannel = FeatureChannel( + "configId", + "name", + "description", + ConfigType.SLACK, + true + ) + val recreatedObject = recreateObject(featureChannel) { FeatureChannel(it) } + assertEquals(featureChannel, recreatedObject) + } + + @Test + fun `FeatureChannel Object serialize and deserialize using json should be equal`() { + val featureChannel = FeatureChannel( + "configId", + "name", + "description", + ConfigType.CHIME, + false + ) + val jsonString = getJsonString(featureChannel) + val recreatedObject = createObjectFromJsonString(jsonString) { FeatureChannel.parse(it) } + assertEquals(featureChannel, recreatedObject) + } + + @Test + fun `FeatureChannel Json parsing should safely ignore extra fields`() { + val featureChannel = FeatureChannel( + "configId", + "name", + "description", + ConfigType.EMAIL_GROUP, + true + ) + val jsonString = """ + { + "config_id":"configId", + "name":"name", + "description":"description", + "config_type":"email_group", + "is_enabled":true, + "extra_field_1":["extra", "value"], + "extra_field_2":{"extra":"value"}, + "extra_field_3":"extra value 3" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { FeatureChannel.parse(it) } + assertEquals(featureChannel, recreatedObject) + } + + @Test + fun `FeatureChannel Json parsing should safely ignore unknown config type`() { + val featureChannel = FeatureChannel( + "configId", + "name", + "description", + ConfigType.NONE, + true + ) + val jsonString = """ + { + "config_id":"configId", + "name":"name", + "description":"description", + "config_type":"NewConfig", + "is_enabled":true + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { FeatureChannel.parse(it) } + assertEquals(featureChannel, recreatedObject) + } + + @Test + fun `FeatureChannel Json parsing should safely parse if description is absent`() { + val featureChannel = FeatureChannel( + "configId", + "name", + "", + ConfigType.SLACK, + true + ) + val jsonString = """ + { + "config_id":"configId", + "name":"name", + "config_type":"slack", + "is_enabled":true + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { FeatureChannel.parse(it) } + assertEquals(featureChannel, recreatedObject) + } + + @Test + fun `FeatureChannel Json parsing should safely parse if is_enabled is absent`() { + val featureChannel = FeatureChannel( + "configId", + "name", + "description", + ConfigType.SLACK, + true + ) + val jsonString = """ + { + "config_id":"configId", + "name":"name", + "description":"description", + "config_type":"slack" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { FeatureChannel.parse(it) } + assertEquals(featureChannel, recreatedObject) + } + + @Test + fun `FeatureChannel Json parsing should throw exception if config_id is absent`() { + val jsonString = """ + { + "name":"name", + "description":"description", + "config_type":"slack", + "is_enabled":true + } + """.trimIndent() + Assertions.assertThrows(IllegalArgumentException::class.java) { + createObjectFromJsonString(jsonString) { FeatureChannel.parse(it) } + } + } + + @Test + fun `FeatureChannel Json parsing should throw exception if config_id is empty`() { + val jsonString = """ + { + "config_id":"", + "name":"name", + "description":"description", + "config_type":"chime", + "is_enabled":true + } + """.trimIndent() + Assertions.assertThrows(IllegalArgumentException::class.java) { + createObjectFromJsonString(jsonString) { FeatureChannel.parse(it) } + } + } + + @Test + fun `FeatureChannel Json parsing should throw exception if name is absent`() { + val jsonString = """ + { + "config_id":"configId", + "description":"description", + "config_type":"webhook", + "is_enabled":true + } + """.trimIndent() + Assertions.assertThrows(IllegalArgumentException::class.java) { + createObjectFromJsonString(jsonString) { FeatureChannel.parse(it) } + } + } + + @Test + fun `FeatureChannel Json parsing should throw exception if name is empty`() { + val jsonString = """ + { + "config_id":"configId", + "name":"", + "description":"description", + "config_type":"email", + "is_enabled":true + } + """.trimIndent() + Assertions.assertThrows(IllegalArgumentException::class.java) { + createObjectFromJsonString(jsonString) { FeatureChannel.parse(it) } + } + } + + @Test + fun `FeatureChannel Json parsing should throw exception if config_type is absent`() { + val jsonString = """ + { + "config_id":"configId", + "name":"name", + "description":"description", + "is_enabled":true + } + """.trimIndent() + Assertions.assertThrows(IllegalArgumentException::class.java) { + createObjectFromJsonString(jsonString) { FeatureChannel.parse(it) } + } + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/FeatureTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/FeatureTests.kt new file mode 100644 index 00000000..215c2c2c --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/FeatureTests.kt @@ -0,0 +1,56 @@ +/* + * 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/FilterConfigListTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/FilterConfigListTests.kt new file mode 100644 index 00000000..f4ceee7f --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/FilterConfigListTests.kt @@ -0,0 +1,238 @@ +/* + * 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.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.utils.createObjectFromJsonString +import org.opensearch.commons.utils.getJsonString +import org.opensearch.commons.utils.recreateObject + +internal class FilterConfigListTests { + + private fun assertSearchResultEquals( + expected: FeatureChannelList, + actual: FeatureChannelList + ) { + assertEquals(expected.startIndex, actual.startIndex) + assertEquals(expected.totalHits, actual.totalHits) + assertEquals(expected.totalHitRelation, actual.totalHitRelation) + assertEquals(expected.objectListFieldName, actual.objectListFieldName) + assertEquals(expected.objectList, actual.objectList) + } + + @Test + fun `Search result serialize and deserialize with config object should be equal`() { + val sampleConfig = FeatureChannel( + "config_id", + "name", + "description", + ConfigType.SLACK + ) + val searchResult = FeatureChannelList(sampleConfig) + val recreatedObject = recreateObject(searchResult) { FeatureChannelList(it) } + assertSearchResultEquals(searchResult, recreatedObject) + } + + @Test + fun `Search result serialize and deserialize with multiple config object should be equal`() { + val sampleConfig1 = FeatureChannel( + "config_id1", + "name1", + "description1", + ConfigType.SLACK + ) + val sampleConfig2 = FeatureChannel( + "config_id2", + "name2", + "description2", + ConfigType.CHIME + ) + val sampleConfig3 = FeatureChannel( + "config_id3", + "name3", + "description3", + ConfigType.WEBHOOK + ) + val searchResult = FeatureChannelList( + 100, + 1000, + TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO, + listOf(sampleConfig1, sampleConfig2, sampleConfig3) + ) + val recreatedObject = recreateObject(searchResult) { FeatureChannelList(it) } + assertSearchResultEquals(searchResult, recreatedObject) + } + + @Test + fun `Search result serialize and deserialize using json config object should be equal`() { + val sampleConfig = FeatureChannel( + "config_id", + "name", + "description", + ConfigType.EMAIL_GROUP + ) + val searchResult = FeatureChannelList(sampleConfig) + val jsonString = getJsonString(searchResult) + val recreatedObject = createObjectFromJsonString(jsonString) { FeatureChannelList(it) } + assertSearchResultEquals(searchResult, recreatedObject) + } + + @Test + fun `Search result serialize and deserialize using json with multiple config object should be equal`() { + val sampleConfig1 = FeatureChannel( + "config_id1", + "name1", + "description1", + ConfigType.SLACK + ) + val sampleConfig2 = FeatureChannel( + "config_id2", + "name2", + "description2", + ConfigType.CHIME + ) + val sampleConfig3 = FeatureChannel( + "config_id3", + "name3", + "description3", + ConfigType.WEBHOOK + ) + val searchResult = FeatureChannelList( + 100, + 1000, + TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO, + listOf(sampleConfig1, sampleConfig2, sampleConfig3) + ) + val jsonString = getJsonString(searchResult) + val recreatedObject = createObjectFromJsonString(jsonString) { FeatureChannelList(it) } + assertSearchResultEquals(searchResult, recreatedObject) + } + + @Test + fun `Search result should use isEnabled=true if absent in json object`() { + val sampleConfig = FeatureChannel( + "config_id", + "name", + "description", + ConfigType.EMAIL, + true + ) + val searchResult = FeatureChannelList(sampleConfig) + val jsonString = """ + { + "start_index":"0", + "total_hits":"1", + "total_hit_relation":"eq", + "feature_channel_list":[ + { + "config_id":"config_id", + "name":"name", + "description":"description", + "config_type":"email" + } + ] + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { FeatureChannelList(it) } + assertSearchResultEquals(searchResult, recreatedObject) + } + + @Test + fun `Search result should safely ignore extra field in json object`() { + val sampleConfig = FeatureChannel( + "config_id", + "name", + "description", + ConfigType.EMAIL + ) + val searchResult = FeatureChannelList(sampleConfig) + val jsonString = """ + { + "start_index":"0", + "total_hits":"1", + "total_hit_relation":"eq", + "feature_channel_list":[ + { + "config_id":"config_id", + "name":"name", + "description":"description", + "config_type":"email", + "is_enabled":true + } + ], + "extra_field_1":["extra", "value"], + "extra_field_2":{"extra":"value"}, + "extra_field_3":"extra value 3" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { FeatureChannelList(it) } + assertSearchResultEquals(searchResult, recreatedObject) + } + + @Test + fun `Search result should safely fallback to default if startIndex, totalHits or totalHitRelation field absent in json object`() { + val sampleConfig = FeatureChannel( + "config_id", + "name", + "description", + ConfigType.EMAIL + ) + val searchResult = FeatureChannelList(sampleConfig) + val jsonString = """ + { + "feature_channel_list":[ + { + "config_id":"config_id", + "name":"name", + "description":"description", + "config_type":"email", + "is_enabled":true + } + ] + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { FeatureChannelList(it) } + assertSearchResultEquals(searchResult, recreatedObject) + } + + @Test + fun `Search result should throw exception if featureChannelList is absent in json`() { + val jsonString = """ + { + "start_index":"0", + "total_hits":"1", + "total_hit_relation":"eq" + } + """.trimIndent() + Assertions.assertThrows(IllegalArgumentException::class.java) { + createObjectFromJsonString(jsonString) { FeatureChannelList(it) } + } + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/FilterConfigTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/FilterConfigTests.kt new file mode 100644 index 00000000..b5dd66b6 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/FilterConfigTests.kt @@ -0,0 +1,157 @@ +/* + * 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 +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.opensearch.commons.utils.createObjectFromJsonString +import org.opensearch.commons.utils.getJsonString +import org.opensearch.commons.utils.recreateObject + +internal class FilterConfigTests { + + @Test + fun `Config serialize and deserialize with default isEnabled flag should be equal`() { + val sampleConfig = FeatureChannel( + "config_id", + "name", + "description", + ConfigType.SLACK + ) + val recreatedObject = recreateObject(sampleConfig) { FeatureChannel(it) } + assertEquals(sampleConfig, recreatedObject) + } + + @Test + fun `Config serialize and deserialize with isEnabled=false should be equal`() { + val sampleConfig = FeatureChannel( + "config_id", + "name", + "description", + ConfigType.CHIME, + false + ) + val recreatedObject = recreateObject(sampleConfig) { FeatureChannel(it) } + assertEquals(sampleConfig, recreatedObject) + } + + @Test + fun `Config serialize and deserialize using json object with default isEnabled flag should be equal`() { + val sampleConfig = FeatureChannel( + "config_id", + "name", + "description", + ConfigType.WEBHOOK + ) + val jsonString = getJsonString(sampleConfig) + val recreatedObject = createObjectFromJsonString(jsonString) { FeatureChannel.parse(it) } + assertEquals(sampleConfig, recreatedObject) + } + + @Test + fun `Config serialize and deserialize using json object with isEnabled=false should be equal`() { + val sampleConfig = FeatureChannel( + "config_id", + "name", + "description", + ConfigType.EMAIL_GROUP, + false + ) + val jsonString = getJsonString(sampleConfig) + val recreatedObject = createObjectFromJsonString(jsonString) { FeatureChannel.parse(it) } + assertEquals(sampleConfig, recreatedObject) + } + + @Test + fun `Config should safely ignore extra field in json object`() { + val sampleConfig = FeatureChannel( + "config_id", + "name", + "description", + ConfigType.EMAIL + ) + val jsonString = """ + { + "config_id":"config_id", + "name":"name", + "description":"description", + "config_type":"email", + "is_enabled":true, + "extra_field_1":["extra", "value"], + "extra_field_2":{"extra":"value"}, + "extra_field_3":"extra value 3" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { FeatureChannel.parse(it) } + assertEquals(sampleConfig, recreatedObject) + } + + @Test + fun `Config should safely ignore unknown config type in json object`() { + val sampleConfig = FeatureChannel( + "config_id", + "name", + "description", + ConfigType.NONE + ) + val jsonString = """ + { + "config_id":"config_id", + "name":"name", + "description":"description", + "config_type":"NewConfig" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { FeatureChannel.parse(it) } + assertEquals(sampleConfig, recreatedObject) + } + + @Test + fun `Config throw exception if configId is empty`() { + Assertions.assertThrows(IllegalArgumentException::class.java) { + FeatureChannel( + "", + "name", + "description", + ConfigType.EMAIL_GROUP + ) + } + } + + @Test + fun `Config throw exception if name is empty`() { + Assertions.assertThrows(IllegalArgumentException::class.java) { + FeatureChannel( + "config_id", + "", + "description", + ConfigType.EMAIL_GROUP + ) + } + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/MethodTypeTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/MethodTypeTests.kt new file mode 100644 index 00000000..75685d53 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/MethodTypeTests.kt @@ -0,0 +1,56 @@ +/* + * 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.MethodType.Companion.enumParser +import org.opensearch.commons.notifications.model.MethodType.Companion.fromTagOrDefault + +internal class MethodTypeTests { + + @Test + fun `toString should return tag`() { + MethodType.values().forEach { + assertEquals(it.tag, it.toString()) + } + } + + @Test + fun `fromTagOrDefault should return corresponding enum`() { + MethodType.values().forEach { + assertEquals(it, fromTagOrDefault(it.tag)) + } + } + + @Test + fun `EnumParser fromTagOrDefault should return corresponding enum`() { + MethodType.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 new file mode 100644 index 00000000..79c1035d --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigInfoTests.kt @@ -0,0 +1,275 @@ +/* + * 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 +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +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 { + + @Test + fun `Config info serialize and deserialize with config object should be equal`() { + val sampleSlack = Slack("https://domain.com/sample_url#1234567890") + val sampleConfig = NotificationConfig( + "name", + "description", + ConfigType.SLACK, + EnumSet.of(Feature.REPORTS), + configData = sampleSlack + ) + val configInfo = NotificationConfigInfo( + "config_id", + Instant.now(), + Instant.now(), + "tenant", + sampleConfig + ) + val recreatedObject = recreateObject(configInfo) { NotificationConfigInfo(it) } + assertEquals(configInfo, recreatedObject) + } + + @Test + fun `Config info serialize and deserialize using json config object should be equal`() { + val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) + val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) + val sampleSlack = Slack("https://domain.com/sample_url#1234567890") + val sampleConfig = NotificationConfig( + "name", + "description", + ConfigType.SLACK, + EnumSet.of(Feature.REPORTS), + configData = sampleSlack + ) + val configInfo = NotificationConfigInfo( + "config_id", + lastUpdatedTimeMs, + createdTimeMs, + "tenant", + sampleConfig + ) + val jsonString = getJsonString(configInfo) + val recreatedObject = createObjectFromJsonString(jsonString) { NotificationConfigInfo.parse(it) } + assertEquals(configInfo, recreatedObject) + } + + @Test + fun `Config info should take default tenant when field is absent in json object`() { + val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) + val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) + val sampleSlack = Slack("https://domain.com/sample_slack_url#1234567890") + val sampleConfig = NotificationConfig( + "name", + "description", + ConfigType.SLACK, + EnumSet.of(Feature.INDEX_MANAGEMENT), + isEnabled = true, + configData = sampleSlack + ) + val configInfo = NotificationConfigInfo( + "config-Id", + lastUpdatedTimeMs, + createdTimeMs, + "", // Default tenant + sampleConfig + ) + val jsonString = """ + { + "config_id":"config-Id", + "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", + "created_time_ms":"${createdTimeMs.toEpochMilli()}", + "config":{ + "name":"name", + "description":"description", + "config_type":"slack", + "feature_list":["index_management"], + "is_enabled":true, + "slack":{"url":"https://domain.com/sample_slack_url#1234567890"} + } + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { NotificationConfigInfo.parse(it) } + assertEquals(configInfo, recreatedObject) + } + + @Test + fun `Config info should safely ignore extra field in json object`() { + val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) + val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) + val sampleSlack = Slack("https://domain.com/sample_slack_url#1234567890") + val sampleConfig = NotificationConfig( + "name", + "description", + ConfigType.SLACK, + EnumSet.of(Feature.INDEX_MANAGEMENT), + isEnabled = true, + configData = sampleSlack + ) + val configInfo = NotificationConfigInfo( + "config-Id", + lastUpdatedTimeMs, + createdTimeMs, + "selectedTenant", + sampleConfig + ) + val jsonString = """ + { + "config_id":"config-Id", + "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", + "created_time_ms":"${createdTimeMs.toEpochMilli()}", + "tenant":"selectedTenant", + "config":{ + "name":"name", + "description":"description", + "config_type":"slack", + "feature_list":["index_management"], + "is_enabled":true, + "slack":{"url":"https://domain.com/sample_slack_url#1234567890"} + }, + "extra_field_1":["extra", "value"], + "extra_field_2":{"extra":"value"}, + "extra_field_3":"extra value 3" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { NotificationConfigInfo.parse(it) } + assertEquals(configInfo, recreatedObject) + } + + @Test + fun `Config info should throw exception if configId is empty`() { + val sampleSlack = Slack("https://domain.com/sample_url#1234567890") + val sampleConfig = NotificationConfig( + "name", + "description", + ConfigType.SLACK, + EnumSet.of(Feature.REPORTS), + configData = sampleSlack + ) + Assertions.assertThrows(IllegalArgumentException::class.java) { + NotificationConfigInfo( + "", + Instant.now(), + Instant.now(), + "tenant", + sampleConfig + ) + } + } + + @Test + fun `Config info should throw exception if configId is absent in json`() { + val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) + val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) + val jsonString = """ + { + "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", + "created_time_ms":"${createdTimeMs.toEpochMilli()}", + "tenant":"selectedTenant", + "config":{ + "name":"name", + "description":"description", + "config_type":"slack", + "feature_list":["index_management"], + "is_enabled":true, + "slack":{"url":"https://domain.com/sample_slack_url#1234567890"} + } + } + """.trimIndent() + Assertions.assertThrows(IllegalArgumentException::class.java) { + createObjectFromJsonString(jsonString) { NotificationConfigInfo.parse(it) } + } + } + + @Test + fun `Config info should throw exception if lastUpdatedTimeMs is absent in json`() { + val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) + val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) + val jsonString = """ + { + "config_id":"config-Id", + "created_time_ms":"${createdTimeMs.toEpochMilli()}", + "tenant":"selectedTenant", + "config":{ + "name":"name", + "description":"description", + "config_type":"slack", + "feature_list":["index_management"], + "is_enabled":true, + "slack":{"url":"https://domain.com/sample_slack_url#1234567890"} + } + } + """.trimIndent() + Assertions.assertThrows(IllegalArgumentException::class.java) { + createObjectFromJsonString(jsonString) { NotificationConfigInfo.parse(it) } + } + } + + @Test + fun `Config info should throw exception if createdTimeMs is absent in json`() { + val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) + val jsonString = """ + { + "config_id":"config-Id", + "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", + "tenant":"selectedTenant", + "config":{ + "name":"name", + "description":"description", + "config_type":"slack", + "feature_list":["index_management"], + "is_enabled":true, + "slack":{"url":"https://domain.com/sample_slack_url#1234567890"} + } + } + """.trimIndent() + Assertions.assertThrows(IllegalArgumentException::class.java) { + createObjectFromJsonString(jsonString) { NotificationConfigInfo.parse(it) } + } + } + + @Test + fun `Config info should throw exception if notificationConfig is absent in json`() { + val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) + val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) + val jsonString = """ + { + "config_id":"config-Id", + "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", + "created_time_ms":"${createdTimeMs.toEpochMilli()}", + "tenant":"selectedTenant" + } + """.trimIndent() + Assertions.assertThrows(IllegalArgumentException::class.java) { + createObjectFromJsonString(jsonString) { NotificationConfigInfo.parse(it) } + } + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigSearchResultsTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigSearchResultsTests.kt new file mode 100644 index 00000000..a9543533 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigSearchResultsTests.kt @@ -0,0 +1,342 @@ +/* + * 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.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.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 { + + private fun assertSearchResultEquals( + expected: NotificationConfigSearchResult, + actual: NotificationConfigSearchResult + ) { + assertEquals(expected.startIndex, actual.startIndex) + assertEquals(expected.totalHits, actual.totalHits) + assertEquals(expected.totalHitRelation, actual.totalHitRelation) + assertEquals(expected.objectListFieldName, actual.objectListFieldName) + assertEquals(expected.objectList, actual.objectList) + } + + @Test + fun `Search result serialize and deserialize with config object should be equal`() { + val sampleSlack = Slack("https://domain.com/sample_url#1234567890") + val sampleConfig = NotificationConfig( + "name", + "description", + ConfigType.SLACK, + EnumSet.of(Feature.REPORTS), + configData = sampleSlack + ) + val configInfo = NotificationConfigInfo( + "config_id", + Instant.now(), + Instant.now(), + "tenant", + sampleConfig + ) + val searchResult = NotificationConfigSearchResult(configInfo) + val recreatedObject = recreateObject(searchResult) { NotificationConfigSearchResult(it) } + assertSearchResultEquals(searchResult, recreatedObject) + } + + @Test + fun `Search result serialize and deserialize with multiple config default values should be equal`() { + val sampleConfig1 = NotificationConfig( + "name", + "description", + ConfigType.SLACK, + EnumSet.of(Feature.REPORTS), + configData = Slack("https://domain.com/sample_url#1234567890") + ) + val configInfo1 = NotificationConfigInfo( + "config_id1", + Instant.now(), + Instant.now(), + "tenant", + sampleConfig1 + ) + val sampleConfig2 = NotificationConfig( + "name", + "description", + ConfigType.CHIME, + EnumSet.of(Feature.INDEX_MANAGEMENT), + configData = Chime("https://domain.com/sample_url#1234567890") + ) + val configInfo2 = NotificationConfigInfo( + "config_id2", + Instant.now(), + Instant.now(), + "tenant", + sampleConfig2 + ) + val searchResult = NotificationConfigSearchResult(listOf(configInfo1, configInfo2)) + val expectedResult = NotificationConfigSearchResult( + 0, + 2, + TotalHits.Relation.EQUAL_TO, + listOf(configInfo1, configInfo2) + ) + val recreatedObject = recreateObject(searchResult) { NotificationConfigSearchResult(it) } + assertSearchResultEquals(expectedResult, recreatedObject) + } + + @Test + fun `Search result serialize and deserialize with multiple config object should be equal`() { + val sampleConfig1 = NotificationConfig( + "name", + "description", + ConfigType.SLACK, + EnumSet.of(Feature.REPORTS), + configData = Slack("https://domain.com/sample_url#1234567890") + ) + val configInfo1 = NotificationConfigInfo( + "config_id1", + Instant.now(), + Instant.now(), + "tenant", + sampleConfig1 + ) + val sampleConfig2 = NotificationConfig( + "name", + "description", + ConfigType.CHIME, + EnumSet.of(Feature.INDEX_MANAGEMENT), + configData = Chime("https://domain.com/sample_url#1234567890") + ) + val configInfo2 = NotificationConfigInfo( + "config_id2", + Instant.now(), + Instant.now(), + "tenant", + sampleConfig2 + ) + val searchResult = NotificationConfigSearchResult( + 100, + 1000, + TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO, + listOf(configInfo1, configInfo2) + ) + val recreatedObject = recreateObject(searchResult) { NotificationConfigSearchResult(it) } + assertSearchResultEquals(searchResult, recreatedObject) + } + + @Test + fun `Search result serialize and deserialize using json config object should be equal`() { + val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) + val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) + val sampleSlack = Slack("https://domain.com/sample_url#1234567890") + val sampleConfig = NotificationConfig( + "name", + "description", + ConfigType.SLACK, + EnumSet.of(Feature.REPORTS), + configData = sampleSlack + ) + val configInfo = NotificationConfigInfo( + "config_id", + lastUpdatedTimeMs, + createdTimeMs, + "tenant", + sampleConfig + ) + val searchResult = NotificationConfigSearchResult(configInfo) + val jsonString = getJsonString(searchResult) + val recreatedObject = createObjectFromJsonString(jsonString) { NotificationConfigSearchResult(it) } + assertSearchResultEquals(searchResult, recreatedObject) + } + + @Test + fun `Search result serialize and deserialize using json with multiple config object should be equal`() { + val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) + val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) + val sampleConfig1 = NotificationConfig( + "name", + "description", + ConfigType.SLACK, + EnumSet.of(Feature.REPORTS), + configData = Slack("https://domain.com/sample_url#1234567890") + ) + val configInfo1 = NotificationConfigInfo( + "config_id1", + lastUpdatedTimeMs, + createdTimeMs, + "tenant", + sampleConfig1 + ) + val sampleConfig2 = NotificationConfig( + "name", + "description", + ConfigType.CHIME, + EnumSet.of(Feature.INDEX_MANAGEMENT), + configData = Chime("https://domain.com/sample_url#1234567890") + ) + val configInfo2 = NotificationConfigInfo( + "config_id2", + lastUpdatedTimeMs, + createdTimeMs, + "tenant", + sampleConfig2 + ) + val searchResult = NotificationConfigSearchResult( + 100, + 1000, + TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO, + listOf(configInfo1, configInfo2) + ) + val jsonString = getJsonString(searchResult) + val recreatedObject = createObjectFromJsonString(jsonString) { NotificationConfigSearchResult(it) } + assertSearchResultEquals(searchResult, recreatedObject) + } + + @Test + fun `Search result should safely ignore extra field in json object`() { + val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) + val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) + val sampleSlack = Slack("https://domain.com/sample_slack_url#1234567890") + val sampleConfig = NotificationConfig( + "name", + "description", + ConfigType.SLACK, + EnumSet.of(Feature.INDEX_MANAGEMENT), + isEnabled = true, + configData = sampleSlack + ) + val configInfo = NotificationConfigInfo( + "config-Id", + lastUpdatedTimeMs, + createdTimeMs, + "selectedTenant", + sampleConfig + ) + val searchResult = NotificationConfigSearchResult(configInfo) + val jsonString = """ + { + "start_index":"0", + "total_hits":"1", + "total_hit_relation":"eq", + "config_list":[ + { + "config_id":"config-Id", + "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", + "created_time_ms":"${createdTimeMs.toEpochMilli()}", + "tenant":"selectedTenant", + "config":{ + "name":"name", + "description":"description", + "config_type":"slack", + "feature_list":["index_management"], + "is_enabled":true, + "slack":{"url":"https://domain.com/sample_slack_url#1234567890"} + } + } + ], + "extra_field_1":["extra", "value"], + "extra_field_2":{"extra":"value"}, + "extra_field_3":"extra value 3" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { NotificationConfigSearchResult(it) } + assertSearchResultEquals(searchResult, recreatedObject) + } + + @Test + fun `Search result should safely fallback to default if startIndex, totalHits or totalHitRelation field absent in json object`() { + val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) + val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) + val sampleSlack = Slack("https://domain.com/sample_slack_url#1234567890") + val sampleConfig = NotificationConfig( + "name", + "description", + ConfigType.SLACK, + EnumSet.of(Feature.INDEX_MANAGEMENT), + isEnabled = true, + configData = sampleSlack + ) + val configInfo = NotificationConfigInfo( + "config-Id", + lastUpdatedTimeMs, + createdTimeMs, + "selectedTenant", + sampleConfig + ) + val searchResult = NotificationConfigSearchResult(configInfo) + val jsonString = """ + { + "config_list":[ + { + "config_id":"config-Id", + "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", + "created_time_ms":"${createdTimeMs.toEpochMilli()}", + "tenant":"selectedTenant", + "config":{ + "name":"name", + "description":"description", + "config_type":"slack", + "feature_list":["index_management"], + "is_enabled":true, + "slack":{"url":"https://domain.com/sample_slack_url#1234567890"} + } + } + ] + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { NotificationConfigSearchResult(it) } + assertSearchResultEquals(searchResult, recreatedObject) + } + + @Test + fun `Search result should throw exception if notificationConfigs is absent in json`() { + val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) + val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) + val jsonString = """ + { + "start_index":"0", + "total_hits":"1", + "total_hit_relation":"eq", + "config_list":[ + { + "config_id":"config-Id", + "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", + "created_time_ms":"${createdTimeMs.toEpochMilli()}", + "tenant":"selectedTenant" + } + ] + } + """.trimIndent() + Assertions.assertThrows(IllegalArgumentException::class.java) { + createObjectFromJsonString(jsonString) { NotificationConfigSearchResult(it) } + } + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigTests.kt new file mode 100644 index 00000000..ab45e2fc --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationConfigTests.kt @@ -0,0 +1,266 @@ +/* + * 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.utils.createObjectFromJsonString +import org.opensearch.commons.utils.getJsonString +import org.opensearch.commons.utils.recreateObject +import java.util.EnumSet + +internal class NotificationConfigTests { + + @Test + fun `Config serialize and deserialize with slack object should be equal`() { + val sampleSlack = Slack("https://domain.com/sample_url#1234567890") + val sampleConfig = NotificationConfig( + "name", + "description", + ConfigType.SLACK, + EnumSet.of(Feature.REPORTS), + configData = sampleSlack + ) + val recreatedObject = recreateObject(sampleConfig) { NotificationConfig(it) } + assertEquals(sampleConfig, recreatedObject) + } + + @Test + fun `Config serialize and deserialize using json slack object should be equal`() { + val sampleSlack = Slack("https://domain.com/sample_url#1234567890") + val sampleConfig = NotificationConfig( + "name", + "description", + ConfigType.SLACK, + EnumSet.of(Feature.REPORTS), + configData = sampleSlack + ) + val jsonString = getJsonString(sampleConfig) + val recreatedObject = createObjectFromJsonString(jsonString) { NotificationConfig.parse(it) } + assertEquals(sampleConfig, recreatedObject) + } + + @Test + fun `Config serialize and deserialize with chime object should be equal`() { + val sampleChime = Chime("https://domain.com/sample_url#1234567890") + val sampleConfig = NotificationConfig( + "name", + "description", + ConfigType.CHIME, + EnumSet.of(Feature.ALERTING), + configData = sampleChime + ) + val recreatedObject = recreateObject(sampleConfig) { NotificationConfig(it) } + assertEquals(sampleConfig, recreatedObject) + } + + @Test + fun `Config serialize and deserialize with json chime object should be equal`() { + val sampleChime = Chime("https://domain.com/sample_url#1234567890") + val sampleConfig = NotificationConfig( + "name", + "description", + ConfigType.CHIME, + EnumSet.of(Feature.ALERTING), + configData = sampleChime + ) + val jsonString = getJsonString(sampleConfig) + val recreatedObject = createObjectFromJsonString(jsonString) { NotificationConfig.parse(it) } + assertEquals(sampleConfig, recreatedObject) + } + + @Test + fun `Config serialize and deserialize with webhook object should be equal`() { + val sampleWebhook = Webhook("https://domain.com/sample_url#1234567890") + val sampleConfig = NotificationConfig( + "name", + "description", + ConfigType.WEBHOOK, + EnumSet.of(Feature.INDEX_MANAGEMENT), + configData = sampleWebhook + ) + val recreatedObject = recreateObject(sampleConfig) { NotificationConfig(it) } + assertEquals(sampleConfig, recreatedObject) + } + + @Test + fun `Config serialize and deserialize with json webhook object should be equal`() { + val sampleWebhook = Webhook("https://domain.com/sample_url#1234567890") + val sampleConfig = NotificationConfig( + "name", + "description", + ConfigType.WEBHOOK, + EnumSet.of(Feature.INDEX_MANAGEMENT), + configData = sampleWebhook + ) + val jsonString = getJsonString(sampleConfig) + val recreatedObject = createObjectFromJsonString(jsonString) { NotificationConfig.parse(it) } + assertEquals(sampleConfig, recreatedObject) + } + + @Test + fun `Config serialize and deserialize with email object should be equal`() { + val sampleEmail = Email("id_1234567890", listOf("email@domain.com"), listOf("groupId")) + val sampleConfig = NotificationConfig( + "name", + "description", + ConfigType.EMAIL, + EnumSet.of(Feature.INDEX_MANAGEMENT), + configData = sampleEmail + ) + val recreatedObject = recreateObject(sampleConfig) { NotificationConfig(it) } + assertEquals(sampleConfig, recreatedObject) + } + + @Test + fun `Config serialize and deserialize with json email object should be equal`() { + val sampleEmail = Email("id_1234567890", listOf("email@domain.com"), listOf("groupId")) + val sampleConfig = NotificationConfig( + "name", + "description", + ConfigType.EMAIL, + EnumSet.of(Feature.INDEX_MANAGEMENT), + configData = sampleEmail + ) + val jsonString = getJsonString(sampleConfig) + val recreatedObject = createObjectFromJsonString(jsonString) { NotificationConfig.parse(it) } + assertEquals(sampleConfig, recreatedObject) + } + + @Test + fun `Config serialize and deserialize with json smtpAccount object should be equal`() { + val smtpAccount = SmtpAccount("domain.com", 1234, MethodType.SSL, "from@domain.com") + val sampleConfig = NotificationConfig( + "name", + "description", + ConfigType.SMTP_ACCOUNT, + EnumSet.of(Feature.INDEX_MANAGEMENT), + configData = smtpAccount + ) + val jsonString = getJsonString(sampleConfig) + val recreatedObject = createObjectFromJsonString(jsonString) { NotificationConfig.parse(it) } + assertEquals(sampleConfig, recreatedObject) + } + + @Test + fun `Config serialize and deserialize with smtpAccount object should be equal`() { + val sampleSmtpAccount = SmtpAccount("domain.com", 1234, MethodType.SSL, "from@domain.com") + val sampleConfig = NotificationConfig( + "name", + "description", + ConfigType.SMTP_ACCOUNT, + EnumSet.of(Feature.INDEX_MANAGEMENT), + configData = sampleSmtpAccount + ) + val recreatedObject = recreateObject(sampleConfig) { NotificationConfig(it) } + assertEquals(sampleConfig, recreatedObject) + } + + @Test + fun `Config serialize and deserialize with json emailGroup object should be equal`() { + val sampleEmailGroup = EmailGroup(listOf("email@domain.com")) + val sampleConfig = NotificationConfig( + "name", + "description", + ConfigType.EMAIL_GROUP, + EnumSet.of(Feature.INDEX_MANAGEMENT), + configData = sampleEmailGroup + ) + val jsonString = getJsonString(sampleConfig) + val recreatedObject = createObjectFromJsonString(jsonString) { NotificationConfig.parse(it) } + assertEquals(sampleConfig, recreatedObject) + } + + @Test + fun `Config serialize and deserialize with emailGroup object should be equal`() { + val sampleEmailGroup = EmailGroup(listOf("email@domain.com")) + val sampleConfig = NotificationConfig( + "name", + "description", + ConfigType.EMAIL_GROUP, + EnumSet.of(Feature.INDEX_MANAGEMENT), + configData = sampleEmailGroup + ) + val recreatedObject = recreateObject(sampleConfig) { NotificationConfig(it) } + assertEquals(sampleConfig, recreatedObject) + } + + @Test + + fun `Config should safely ignore unknown config type in json object`() { + val sampleSlack = Slack("https://domain.com/sample_slack_url#1234567890") + val sampleConfig = NotificationConfig( + "name", + "description", + ConfigType.NONE, + EnumSet.of(Feature.INDEX_MANAGEMENT), + isEnabled = true, + configData = sampleSlack + ) + val jsonString = """ + { + "name":"name", + "description":"description", + "config_type":"NewConfig", + "feature_list":["index_management"], + "is_enabled":true, + "slack":{"url":"https://domain.com/sample_slack_url#1234567890"}, + "chime":{"url":"https://domain.com/sample_chime_url#1234567890"}, + "webhook":{"url":"https://domain.com/sample_webhook_url#1234567890"}, + "new_config1":{"newField1":"new value 1"}, + "new_config2":{"newField2":"new value 2"} + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { NotificationConfig.parse(it) } + assertEquals(sampleConfig, recreatedObject) + } + + @Test + fun `Config should safely ignore 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), + isEnabled = true, + configData = sampleWebhook + ) + val jsonString = """ + { + "name":"name", + "description":"description", + "config_type":"webhook", + "feature_list":["index_management", "NewFeature1", "NewFeature2"], + "is_enabled":true, + "webhook":{"url":"https://domain.com/sample_webhook_url#1234567890"} + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { NotificationConfig.parse(it) } + assertEquals(sampleConfig, recreatedObject) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventInfoTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventInfoTests.kt new file mode 100644 index 00000000..e23ca1e0 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventInfoTests.kt @@ -0,0 +1,364 @@ +/* + * 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 +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.opensearch.commons.utils.createObjectFromJsonString +import org.opensearch.commons.utils.getJsonString +import org.opensearch.commons.utils.recreateObject +import java.time.Instant + +internal class NotificationEventInfoTests { + + @Test + fun `Event info serialize and deserialize with event object should be equal`() { + val sampleEventSource = EventSource( + "title", + "reference_id", + Feature.ALERTING, + severity = SeverityType.INFO + ) + val sampleStatus = EventStatus( + "config_id", + "name", + ConfigType.SLACK, + deliveryStatus = DeliveryStatus("404", "invalid recipient") + ) + val sampleEvent = NotificationEvent(sampleEventSource, listOf(sampleStatus)) + val eventInfo = NotificationEventInfo( + "event_id", + Instant.now(), + Instant.now(), + "tenant", + sampleEvent + ) + val recreatedObject = recreateObject(eventInfo) { NotificationEventInfo(it) } + assertEquals(eventInfo, recreatedObject) + } + + @Test + fun `Event info serialize and deserialize using json event object should be equal`() { + val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) + val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) + val sampleEventSource = EventSource( + "title", + "reference_id", + Feature.ALERTING, + severity = SeverityType.INFO + ) + val sampleStatus = EventStatus( + "config_id", + "name", + ConfigType.SLACK, + deliveryStatus = DeliveryStatus("200", "success") + ) + val sampleEvent = NotificationEvent(sampleEventSource, listOf(sampleStatus)) + val eventInfo = NotificationEventInfo( + "event_id", + lastUpdatedTimeMs, + createdTimeMs, + "tenant", + sampleEvent + ) + val jsonString = getJsonString(eventInfo) + val recreatedObject = createObjectFromJsonString(jsonString) { NotificationEventInfo.parse(it) } + assertEquals(eventInfo, recreatedObject) + } + + @Test + fun `Event info should take default tenant when field is absent in json object`() { + val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) + val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) + val sampleEventSource = EventSource( + "title", + "reference_id", + Feature.ALERTING, + severity = SeverityType.INFO + ) + val sampleStatus = EventStatus( + "config_id", + "name", + ConfigType.SLACK, + deliveryStatus = DeliveryStatus("200", "success") + ) + val sampleEvent = NotificationEvent(sampleEventSource, listOf(sampleStatus)) + val eventInfo = NotificationEventInfo( + "event_id", + lastUpdatedTimeMs, + createdTimeMs, + "tenant", + sampleEvent + ) + val jsonString = """ + { + "event_id":"event_id", + "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", + "created_time_ms":"${createdTimeMs.toEpochMilli()}", + "tenant":"tenant", + "event":{ + "event_source":{ + "title":"title", + "reference_id":"reference_id", + "feature":"alerting", + "severity":"info", + "tags":[] + }, + "status_list":[ + { + "config_id":"config_id", + "config_type":"slack", + "config_name":"name", + "delivery_status": + { + "status_code":"200", + "status_text":"success" + } + } + ] + } + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { NotificationEventInfo.parse(it) } + assertEquals(eventInfo, recreatedObject) + } + + @Test + fun `Event info should safely ignore extra field in json object`() { + val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) + val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) + val sampleEventSource = EventSource( + "title", + "reference_id", + Feature.ALERTING, + severity = SeverityType.INFO + ) + val sampleStatus = EventStatus( + "config_id", + "name", + ConfigType.SLACK, + deliveryStatus = DeliveryStatus("200", "success") + ) + val sampleEvent = NotificationEvent(sampleEventSource, listOf(sampleStatus)) + val eventInfo = NotificationEventInfo( + "event_id", + lastUpdatedTimeMs, + createdTimeMs, + "tenant", + sampleEvent + ) + val jsonString = """ + { + "event_id":"event_id", + "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", + "created_time_ms":"${createdTimeMs.toEpochMilli()}", + "tenant":"tenant", + "event":{ + "event_source":{ + "title":"title", + "reference_id":"reference_id", + "feature":"alerting", + "severity":"info", + "tags":[] + }, + "status_list":[ + { + "config_id":"config_id", + "config_type":"slack", + "config_name":"name", + "delivery_status": + { + "status_code":"200", + "status_text":"success" + } + } + ] + }, + "extra_field_1":["extra", "value"], + "extra_field_2":{"extra":"value"}, + "extra_field_3":"extra value 3" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { NotificationEventInfo.parse(it) } + assertEquals(eventInfo, recreatedObject) + } + + @Test + fun `Event info should throw exception if event_id is empty`() { + val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) + val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) + val sampleEventSource = EventSource( + "title", + "reference_id", + Feature.ALERTING, + severity = SeverityType.INFO + ) + val sampleStatus = EventStatus( + "event_id", + "name", + ConfigType.SLACK, + deliveryStatus = DeliveryStatus("200", "success") + ) + val sampleEvent = NotificationEvent(sampleEventSource, listOf(sampleStatus)) + Assertions.assertThrows(IllegalArgumentException::class.java) { + NotificationEventInfo( + "", + lastUpdatedTimeMs, + createdTimeMs, + "tenant", + sampleEvent + ) + } + } + + @Test + fun `Event info should throw exception if event_id is absent in json`() { + val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) + val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) + val jsonString = """ + { + "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", + "created_time_ms":"${createdTimeMs.toEpochMilli()}", + "event":{ + "event_source":{ + "title":"title", + "reference_id":"reference_id", + "feature":"alerting", + "severity":"info", + "tags":["tag1", "tag2"] + }, + "status_list":[ + { + "event_id":"event_id", + "config_type":"slack", + "config_name":"name", + "delivery_status": + { + "status_code":"200", + "status_text":"success" + } + } + ] + } + } + """.trimIndent() + Assertions.assertThrows(IllegalArgumentException::class.java) { + createObjectFromJsonString(jsonString) { NotificationEventInfo.parse(it) } + } + } + + @Test + fun `Event info should throw exception if lastUpdatedTimeMs is absent in json`() { + val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) + val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) + val jsonString = """ + { + "event_id":"event_id", + "created_time_ms":"${createdTimeMs.toEpochMilli()}", + "tenant":"selectedTenant", + "event":{ + "event_source":{ + "title":"title", + "reference_id":"reference_id", + "feature":"alerting", + "severity":"info", + "tags":["tag1", "tag2"] + }, + "status_list":[ + { + "event_id":"event_id", + "config_type":"slack", + "config_name":"name", + "delivery_status": + { + "status_code":"200", + "status_text":"success" + } + } + ] + } + } + """.trimIndent() + Assertions.assertThrows(IllegalArgumentException::class.java) { + createObjectFromJsonString(jsonString) { NotificationEventInfo.parse(it) } + } + } + + @Test + fun `Event info should throw exception if createdTimeMs is absent in json`() { + val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) + val jsonString = """ + { + "event_id":"event_id", + "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", + "tenant":"selectedTenant", + "event":{ + "event_source":{ + "title":"title", + "reference_id":"reference_id", + "feature":"alerting", + "severity":"info", + "tags":["tag1", "tag2"] + }, + "status_list":[ + { + "event_id":"event_id", + "config_type":"slack", + "config_name":"name", + "delivery_status": + { + "status_code":"200", + "status_text":"success" + } + } + ] + } + } + """.trimIndent() + Assertions.assertThrows(IllegalArgumentException::class.java) { + createObjectFromJsonString(jsonString) { NotificationEventInfo.parse(it) } + } + } + + @Test + fun `Event info should throw exception if event is absent in json`() { + val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) + val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) + val jsonString = """ + { + "event_id":"event_id", + "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", + "created_time_ms":"${createdTimeMs.toEpochMilli()}", + "tenant":"selectedTenant" + } + """.trimIndent() + Assertions.assertThrows(IllegalArgumentException::class.java) { + createObjectFromJsonString(jsonString) { NotificationEventInfo.parse(it) } + } + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventSearchResultTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventSearchResultTests.kt new file mode 100644 index 00000000..d09fe9d2 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventSearchResultTests.kt @@ -0,0 +1,445 @@ +/* + * 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.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.utils.createObjectFromJsonString +import org.opensearch.commons.utils.getJsonString +import org.opensearch.commons.utils.recreateObject +import java.time.Instant + +internal class NotificationEventSearchResultTests { + + private fun assertSearchResultEquals( + expected: NotificationEventSearchResult, + actual: NotificationEventSearchResult + ) { + assertEquals(expected.startIndex, actual.startIndex) + assertEquals(expected.totalHits, actual.totalHits) + assertEquals(expected.totalHitRelation, actual.totalHitRelation) + assertEquals(expected.objectListFieldName, actual.objectListFieldName) + assertEquals(expected.objectList, actual.objectList) + } + + @Test + fun `Search result serialize and deserialize with event object should be equal`() { + val sampleEventSource = EventSource( + "title", + "reference_id", + Feature.ALERTING, + severity = SeverityType.INFO + ) + val sampleStatus = EventStatus( + "config_id", + "name", + ConfigType.SLACK, + deliveryStatus = DeliveryStatus("404", "invalid recipient") + ) + val sampleEvent = NotificationEvent(sampleEventSource, listOf(sampleStatus)) + val eventInfo = NotificationEventInfo( + "event_id", + Instant.now(), + Instant.now(), + "tenant", + sampleEvent + ) + val searchResult = NotificationEventSearchResult(eventInfo) + val recreatedObject = recreateObject(searchResult) { NotificationEventSearchResult(it) } + assertSearchResultEquals(searchResult, recreatedObject) + } + + @Test + fun `Search result serialize and deserialize with multiple event default values should be equal`() { + val eventSource1 = EventSource( + "title 1", + "reference_id_1", + Feature.ALERTING, + severity = SeverityType.INFO + ) + val eventSource2 = EventSource( + "title 2", + "reference_id_2", + Feature.REPORTS, + severity = SeverityType.HIGH + ) + val status1 = EventStatus( + "config_id1", + "name", + ConfigType.SLACK, + deliveryStatus = DeliveryStatus("200", "success") + ) + val status2 = EventStatus( + "config_id2", + "name", + ConfigType.CHIME, + deliveryStatus = DeliveryStatus("404", "invalid recipient") + ) + val eventInfo1 = NotificationEventInfo( + "event_id1", + Instant.now(), + Instant.now(), + "tenant", + NotificationEvent(eventSource1, listOf(status1)) + ) + val eventInfo2 = NotificationEventInfo( + "event_id2", + Instant.now(), + Instant.now(), + "tenant", + NotificationEvent(eventSource2, listOf(status2)) + ) + val eventInfo3 = NotificationEventInfo( + "event_id3", + Instant.now(), + Instant.now(), + "tenant", + NotificationEvent(eventSource1, listOf(status1, status2)) + ) + val eventInfo4 = NotificationEventInfo( + "event_id4", + Instant.now(), + Instant.now(), + "tenant", + NotificationEvent(eventSource2, listOf(status1, status2)) + ) + val searchResult = NotificationEventSearchResult( + listOf(eventInfo1, eventInfo2, eventInfo3, eventInfo4) + ) + val expectedResult = NotificationEventSearchResult( + 0, + 4, + TotalHits.Relation.EQUAL_TO, + listOf(eventInfo1, eventInfo2, eventInfo3, eventInfo4) + ) + val recreatedObject = recreateObject(searchResult) { NotificationEventSearchResult(it) } + assertSearchResultEquals(expectedResult, recreatedObject) + } + + @Test + fun `Search result serialize and deserialize with multiple event status object should be equal`() { + val eventSource1 = EventSource( + "title 1", + "reference_id_1", + Feature.ALERTING, + severity = SeverityType.INFO + ) + val eventSource2 = EventSource( + "title 2", + "reference_id_2", + Feature.REPORTS, + severity = SeverityType.HIGH + ) + val status1 = EventStatus( + "config_id1", + "name", + ConfigType.SLACK, + deliveryStatus = DeliveryStatus("200", "success") + ) + val status2 = EventStatus( + "config_id2", + "name", + ConfigType.CHIME, + deliveryStatus = DeliveryStatus("404", "invalid recipient") + ) + val eventInfo1 = NotificationEventInfo( + "event_id1", + Instant.now(), + Instant.now(), + "tenant", + NotificationEvent(eventSource1, listOf(status1)) + ) + val eventInfo2 = NotificationEventInfo( + "event_id2", + Instant.now(), + Instant.now(), + "tenant", + NotificationEvent(eventSource2, listOf(status2)) + ) + val eventInfo3 = NotificationEventInfo( + "event_id3", + Instant.now(), + Instant.now(), + "tenant", + NotificationEvent(eventSource1, listOf(status1, status2)) + ) + val eventInfo4 = NotificationEventInfo( + "event_id4", + Instant.now(), + Instant.now(), + "tenant", + NotificationEvent(eventSource2, listOf(status1, status2)) + ) + val searchResult = NotificationEventSearchResult( + 100, + 1000, + TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO, + listOf(eventInfo1, eventInfo2, eventInfo3, eventInfo4) + ) + val recreatedObject = recreateObject(searchResult) { NotificationEventSearchResult(it) } + assertSearchResultEquals(searchResult, recreatedObject) + } + + @Test + fun `Search result serialize and deserialize using json event object should be equal`() { + val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) + val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) + val sampleEventSource = EventSource( + "title", + "reference_id", + Feature.ALERTING, + severity = SeverityType.INFO + ) + val sampleStatus = EventStatus( + "config_id", + "name", + ConfigType.SLACK, + deliveryStatus = DeliveryStatus("404", "invalid recipient") + ) + val sampleEvent = NotificationEvent(sampleEventSource, listOf(sampleStatus)) + val eventInfo = NotificationEventInfo( + "event_id", + lastUpdatedTimeMs, + createdTimeMs, + "tenant", + sampleEvent + ) + val searchResult = NotificationEventSearchResult(eventInfo) + val jsonString = getJsonString(searchResult) + val recreatedObject = createObjectFromJsonString(jsonString) { NotificationEventSearchResult(it) } + assertSearchResultEquals(searchResult, recreatedObject) + } + + @Test + fun `Search result serialize and deserialize using json with multiple event object should be equal`() { + val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) + val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) + val eventSource1 = EventSource( + "title 1", + "reference_id_1", + Feature.ALERTING, + severity = SeverityType.INFO + ) + val eventSource2 = EventSource( + "title 2", + "reference_id_2", + Feature.REPORTS, + severity = SeverityType.HIGH + ) + val status1 = EventStatus( + "config_id1", + "name", + ConfigType.SLACK, + deliveryStatus = DeliveryStatus("200", "success") + ) + val status2 = EventStatus( + "config_id2", + "name", + ConfigType.CHIME, + deliveryStatus = DeliveryStatus("404", "invalid recipient") + ) + val eventInfo1 = NotificationEventInfo( + "event_id1", + lastUpdatedTimeMs, + createdTimeMs, + "tenant", + NotificationEvent(eventSource1, listOf(status1)) + ) + val eventInfo2 = NotificationEventInfo( + "event_id2", + lastUpdatedTimeMs, + createdTimeMs, + "tenant", + NotificationEvent(eventSource2, listOf(status2)) + ) + val searchResult = NotificationEventSearchResult( + 100, + 1000, + TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO, + listOf(eventInfo1, eventInfo2) + ) + val jsonString = getJsonString(searchResult) + val recreatedObject = createObjectFromJsonString(jsonString) { NotificationEventSearchResult(it) } + assertSearchResultEquals(searchResult, recreatedObject) + } + + @Test + fun `Search result should safely ignore extra field in json object`() { + val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) + val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) + val sampleEventSource = EventSource( + "title", + "reference_id", + Feature.ALERTING, + severity = SeverityType.INFO + ) + val sampleStatus = EventStatus( + "config_id", + "name", + ConfigType.SLACK, + deliveryStatus = DeliveryStatus("200", "success") + ) + val sampleEvent = NotificationEvent(sampleEventSource, listOf(sampleStatus)) + val eventInfo = NotificationEventInfo( + "event_id", + lastUpdatedTimeMs, + createdTimeMs, + "selectedTenant", + sampleEvent + ) + val searchResult = NotificationEventSearchResult(eventInfo) + val jsonString = """ + { + "start_index":"0", + "total_hits":"1", + "total_hit_relation":"eq", + "event_list":[ + { + "event_id":"event_id", + "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", + "created_time_ms":"${createdTimeMs.toEpochMilli()}", + "tenant":"selectedTenant", + "event":{ + "event_source":{ + "title":"title", + "reference_id":"reference_id", + "feature":"alerting", + "severity":"info", + "tags":[] + }, + "status_list":[ + { + "config_id":"config_id", + "config_type":"slack", + "config_name":"name", + "delivery_status": + { + "status_code":"200", + "status_text":"success" + } + } + ] + } + } + ], + "extra_field_1":["extra", "value"], + "extra_field_2":{"extra":"value"}, + "extra_field_3":"extra value 3" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { NotificationEventSearchResult(it) } + assertSearchResultEquals(searchResult, recreatedObject) + } + + @Test + fun `Search result should safely fallback to default if startIndex, totalHits or totalHitRelation field absent in json object`() { + val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) + val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) + val sampleEventSource = EventSource( + "title", + "reference_id", + Feature.ALERTING, + severity = SeverityType.INFO + ) + val sampleStatus = EventStatus( + "config_id", + "name", + ConfigType.SLACK, + deliveryStatus = DeliveryStatus("200", "success") + ) + val sampleEvent = NotificationEvent(sampleEventSource, listOf(sampleStatus)) + val eventInfo = NotificationEventInfo( + "event_id", + lastUpdatedTimeMs, + createdTimeMs, + "selectedTenant", + sampleEvent + ) + val searchResult = NotificationEventSearchResult(eventInfo) + val jsonString = """ + { + "event_list":[ + { + "event_id":"event_id", + "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", + "created_time_ms":"${createdTimeMs.toEpochMilli()}", + "tenant":"selectedTenant", + "event":{ + "event_source":{ + "title":"title", + "reference_id":"reference_id", + "feature":"alerting", + "severity":"info", + "tags":[] + }, + "status_list":[ + { + "config_id":"config_id", + "config_type":"slack", + "config_name":"name", + "delivery_status": + { + "status_code":"200", + "status_text":"success" + } + } + ] + } + } + ] + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { NotificationEventSearchResult(it) } + assertSearchResultEquals(searchResult, recreatedObject) + } + + @Test + fun `Search result should throw exception if event is absent in json`() { + val lastUpdatedTimeMs = Instant.ofEpochMilli(Instant.now().toEpochMilli()) + val createdTimeMs = lastUpdatedTimeMs.minusSeconds(1000) + val jsonString = """ + { + "start_index":"0", + "total_hits":"1", + "total_hit_relation":"eq", + "event_list":[ + { + "event_id":"event_id", + "last_updated_time_ms":"${lastUpdatedTimeMs.toEpochMilli()}", + "created_time_ms":"${createdTimeMs.toEpochMilli()}", + "tenant":"selectedTenant" + } + ] + } + """.trimIndent() + Assertions.assertThrows(IllegalArgumentException::class.java) { + createObjectFromJsonString(jsonString) { NotificationEventSearchResult(it) } + } + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventTests.kt new file mode 100644 index 00000000..4a135d0c --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/NotificationEventTests.kt @@ -0,0 +1,198 @@ +/* + * 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 +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.opensearch.commons.utils.createObjectFromJsonString +import org.opensearch.commons.utils.getJsonString +import org.opensearch.commons.utils.recreateObject + +internal class NotificationEventTests { + + @Test + fun `Notification event serialize and deserialize should be equal`() { + val sampleEventSource = EventSource( + "title", + "reference_id", + Feature.ALERTING, + severity = SeverityType.INFO + ) + val sampleStatus = EventStatus( + "config_id", + "name", + ConfigType.SLACK, + deliveryStatus = DeliveryStatus("404", "invalid recipient") + ) + val sampleEvent = NotificationEvent(sampleEventSource, listOf(sampleStatus)) + val recreatedObject = recreateObject(sampleEvent) { NotificationEvent(it) } + assertEquals(sampleEvent, recreatedObject) + } + + @Test + fun `Notification event serialize and deserialize using json should be equal`() { + val sampleEventSource = EventSource( + "title", + "reference_id", + Feature.REPORTS, + severity = SeverityType.INFO + ) + val sampleStatus = EventStatus( + "config_id", + "name", + ConfigType.CHIME, + deliveryStatus = DeliveryStatus("200", "success") + ) + val sampleEvent = NotificationEvent(sampleEventSource, listOf(sampleStatus)) + val jsonString = getJsonString(sampleEvent) + val recreatedObject = createObjectFromJsonString(jsonString) { NotificationEvent.parse(it) } + assertEquals(sampleEvent, recreatedObject) + } + + @Test + fun `Notification event should safely ignore extra field in json object`() { + val sampleEventSource = EventSource( + "title", + "reference_id", + Feature.ALERTING, + tags = listOf("tag1", "tag2"), + severity = SeverityType.INFO + ) + val status1 = EventStatus( + "config_id1", + "name 1", + ConfigType.CHIME, + deliveryStatus = DeliveryStatus("200", "success") + ) + val status2 = EventStatus( + "config_id2", + "name 2", + ConfigType.SLACK, + deliveryStatus = DeliveryStatus("503", "service unavailable") + ) + val sampleEvent = NotificationEvent(sampleEventSource, listOf(status1, status2)) + val jsonString = """ + { + "event_source":{ + "title":"title", + "reference_id":"reference_id", + "feature":"alerting", + "severity":"info", + "tags":["tag1", "tag2"] + }, + "status_list":[ + { + "config_id":"config_id1", + "config_type":"chime", + "config_name":"name 1", + "delivery_status": + { + "status_code":"200", + "status_text":"success" + } + }, + { + "config_id":"config_id2", + "config_type":"slack", + "config_name":"name 2", + "delivery_status": + { + "status_code":"503", + "status_text":"service unavailable" + } + } + ], + "extra_field_1":["extra", "value"], + "extra_field_2":{"extra":"value"}, + "extra_field_3":"extra value 3" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { NotificationEvent.parse(it) } + assertEquals(sampleEvent, recreatedObject) + } + + @Test + fun `Notification event throw exception if event source is absent`() { + val jsonString = """ + { + "status_list":[ + { + "config_id":"config_id", + "config_type":"chime", + "config_name":"name", + "delivery_status": + { + "status_code":"200", + "status_text":"success" + } + } + ] + } + """.trimIndent() + Assertions.assertThrows(IllegalArgumentException::class.java) { + createObjectFromJsonString(jsonString) { NotificationEvent.parse(it) } + } + } + + @Test + fun `Notification event throw exception if status_list is absent`() { + val jsonString = """ + { + "event_source":{ + "title":"title", + "reference_id":"reference_id", + "feature":"alerting", + "severity":"info", + "tags":["tag1", "tag2"] + } + } + """.trimIndent() + Assertions.assertThrows(IllegalArgumentException::class.java) { + createObjectFromJsonString(jsonString) { NotificationEvent.parse(it) } + } + } + + @Test + fun `Notification event throw exception if status_list is empty`() { + val jsonString = """ + { + "event_source":{ + "title":"title", + "reference_id":"reference_id", + "feature":"alerting", + "severity":"info", + "tags":["tag1", "tag2"] + }, + "status_list":[] + } + """.trimIndent() + Assertions.assertThrows(IllegalArgumentException::class.java) { + createObjectFromJsonString(jsonString) { NotificationEvent.parse(it) } + } + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/SeverityTypeTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/SeverityTypeTests.kt new file mode 100644 index 00000000..71fe1460 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/SeverityTypeTests.kt @@ -0,0 +1,56 @@ +/* + * 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.SeverityType.Companion.enumParser +import org.opensearch.commons.notifications.model.SeverityType.Companion.fromTagOrDefault + +internal class SeverityTypeTests { + + @Test + fun `toString should return tag`() { + SeverityType.values().forEach { + assertEquals(it.tag, it.toString()) + } + } + + @Test + fun `fromTagOrDefault should return corresponding enum`() { + SeverityType.values().forEach { + assertEquals(it, fromTagOrDefault(it.tag)) + } + } + + @Test + fun `EnumParser fromTagOrDefault should return corresponding enum`() { + SeverityType.values().forEach { + assertEquals(it, enumParser.fromTagOrDefault(it.tag)) + } + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/SlackTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/SlackTests.kt new file mode 100644 index 00000000..b5bf7783 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/SlackTests.kt @@ -0,0 +1,109 @@ +/* + * 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 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 +import java.net.MalformedURLException + +internal class SlackTests { + + @Test + fun `Slack serialize and deserialize transport object should be equal`() { + val sampleSlack = Slack("https://domain.com/sample_url#1234567890") + val recreatedObject = recreateObject(sampleSlack) { Slack(it) } + assertEquals(sampleSlack, recreatedObject) + } + + @Test + fun `Slack serialize and deserialize using json object should be equal`() { + val sampleSlack = Slack("https://domain.com/sample_url#1234567890") + val jsonString = getJsonString(sampleSlack) + val recreatedObject = createObjectFromJsonString(jsonString) { Slack.parse(it) } + assertEquals(sampleSlack, recreatedObject) + } + + @Test + fun `Slack should deserialize json object using parser`() { + val sampleSlack = Slack("https://domain.com/sample_url#1234567890") + val jsonString = "{\"url\":\"${sampleSlack.url}\"}" + val recreatedObject = createObjectFromJsonString(jsonString) { Slack.parse(it) } + assertEquals(sampleSlack, recreatedObject) + } + + @Test + fun `Slack should throw exception when invalid json object is passed`() { + val jsonString = "sample message" + assertThrows { + createObjectFromJsonString(jsonString) { Slack.parse(it) } + } + } + + @Test + fun `Slack should throw exception when url is replace with url2 in json object`() { + val sampleSlack = Slack("https://domain.com/sample_url#1234567890") + val jsonString = "{\"url2\":\"${sampleSlack.url}\"}" + assertThrows { + createObjectFromJsonString(jsonString) { Slack.parse(it) } + } + } + + @Test + fun `Slack should throw exception when url is not proper`() { + assertThrows { + Slack("domain.com/sample_url#1234567890") + } + val jsonString = "{\"url\":\"domain.com/sample_url\"}" + assertThrows { + createObjectFromJsonString(jsonString) { Slack.parse(it) } + } + } + + @Test + fun `Slack should throw exception when url protocol is not https`() { + assertThrows { + Slack("http://domain.com/sample_url#1234567890") + } + val jsonString = "{\"url\":\"http://domain.com/sample_url\"}" + assertThrows { + createObjectFromJsonString(jsonString) { Slack.parse(it) } + } + } + + @Test + fun `Slack should safely ignore extra field in json object`() { + val sampleSlack = Slack("https://domain.com/sample_url#1234567890") + val jsonString = "{\"url\":\"${sampleSlack.url}\", \"another\":\"field\"}" + val recreatedObject = createObjectFromJsonString(jsonString) { Slack.parse(it) } + assertEquals(sampleSlack, recreatedObject) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/SmtpAccountTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/SmtpAccountTests.kt new file mode 100644 index 00000000..bc924681 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/SmtpAccountTests.kt @@ -0,0 +1,112 @@ +/* + * 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 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 SmtpAccountTests { + + @Test + fun `SmtpAccount serialize and deserialize transport object should be equal`() { + val sampleSmtpAccount = SmtpAccount("domain.com", 1234, MethodType.SSL, "from@domain.com") + val recreatedObject = recreateObject(sampleSmtpAccount) { SmtpAccount(it) } + assertEquals(sampleSmtpAccount, recreatedObject) + } + + @Test + fun `SmtpAccount serialize and deserialize using json object should be equal`() { + val sampleSmtpAccount = SmtpAccount("domain.com", 1234, MethodType.SSL, "from@domain.com") + val jsonString = getJsonString(sampleSmtpAccount) + val recreatedObject = createObjectFromJsonString(jsonString) { SmtpAccount.parse(it) } + assertEquals(sampleSmtpAccount, recreatedObject) + } + + @Test + fun `SmtpAccount should deserialize json object using parser`() { + val sampleSmtpAccount = SmtpAccount("domain.com", 1234, MethodType.SSL, "from@domain.com") + val jsonString = """ + { + "host":"domain.com", + "port":"1234", + "method":"ssl", + "from_address":"from@domain.com" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { SmtpAccount.parse(it) } + assertEquals(sampleSmtpAccount, recreatedObject) + } + + @Test + fun `SmtpAccount should throw exception when invalid json object is passed`() { + val jsonString = "sample message" + assertThrows { + createObjectFromJsonString(jsonString) { SmtpAccount.parse(it) } + } + } + + @Test + fun `SmtpAccount should throw exception when email id is invalid`() { + val jsonString = """ + { + "host":"domain.com", + "port":"1234", + "method":"ssl", + "from_address":".from@domain.com" + } + """.trimIndent() + assertThrows { + createObjectFromJsonString(jsonString) { SmtpAccount.parse(it) } + } + } + + @Test + fun `SmtpAccount should safely ignore extra field in json object`() { + val sampleSmtpAccount = SmtpAccount( + "domain.com", + 1234, MethodType.START_TLS, + "from@domain.com" + ) + val jsonString = """ + { + "host":"domain.com", + "port":"1234", + "method":"start_tls", + "from_address":"from@domain.com", + "extra_field_1":"extra value 1", + "extra_field_2":"extra value 2" + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { SmtpAccount.parse(it) } + assertEquals(sampleSmtpAccount, recreatedObject) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/WebhookTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/WebhookTests.kt new file mode 100644 index 00000000..9ca39c7c --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/WebhookTests.kt @@ -0,0 +1,116 @@ +/* + * 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 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 +import java.net.MalformedURLException + +internal class WebhookTests { + + @Test + fun `Webhook serialize and deserialize transport object should be equal`() { + val sampleWebhook = Webhook("https://domain.com/sample_url#1234567890", mapOf(Pair("key", "value"))) + val recreatedObject = recreateObject(sampleWebhook) { Webhook(it) } + assertEquals(sampleWebhook, recreatedObject) + } + + @Test + fun `Webhook serialize and deserialize using json object should be equal`() { + val sampleWebhook = Webhook("https://domain.com/sample_url#1234567890", mapOf(Pair("key", "value"))) + val jsonString = getJsonString(sampleWebhook) + val recreatedObject = createObjectFromJsonString(jsonString) { Webhook.parse(it) } + assertEquals(sampleWebhook, recreatedObject) + } + + @Test + fun `Webhook should deserialize json object using parser`() { + val sampleWebhook = Webhook("https://domain.com/sample_url#1234567890", mapOf(Pair("key", "value"))) + val jsonString = """ + { + "url":"${sampleWebhook.url}", + "header_params":{ + "key":"value" + } + } + """.trimIndent() + val recreatedObject = createObjectFromJsonString(jsonString) { Webhook.parse(it) } + assertEquals(sampleWebhook, recreatedObject) + } + + @Test + fun `Webhook should throw exception when invalid json object is passed`() { + val jsonString = "sample message" + assertThrows { + createObjectFromJsonString(jsonString) { Webhook.parse(it) } + } + } + + @Test + fun `Webhook should throw exception when url is replace with url2 in json object`() { + val sampleWebhook = Webhook("https://domain.com/sample_url#1234567890") + val jsonString = "{\"url2\":\"${sampleWebhook.url}\"}" + assertThrows { + createObjectFromJsonString(jsonString) { Webhook.parse(it) } + } + } + + @Test + fun `Webhook should throw exception when url is not proper`() { + assertThrows { + Webhook("domain.com/sample_url#1234567890") + } + val jsonString = "{\"url\":\"domain.com/sample_url\"}" + assertThrows { + createObjectFromJsonString(jsonString) { Webhook.parse(it) } + } + } + + @Test + fun `Webhook should throw exception when url protocol is not https`() { + assertThrows { + Webhook("http://domain.com/sample_url#1234567890") + } + val jsonString = "{\"url\":\"http://domain.com/sample_url\"}" + assertThrows { + createObjectFromJsonString(jsonString) { Webhook.parse(it) } + } + } + + @Test + fun `Webhook should safely ignore extra field in json object`() { + val sampleWebhook = Webhook("https://domain.com/sample_url#1234567890") + val jsonString = "{\"url\":\"${sampleWebhook.url}\", \"another\":\"field\"}" + val recreatedObject = createObjectFromJsonString(jsonString) { Webhook.parse(it) } + assertEquals(sampleWebhook, recreatedObject) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/config/ConfigPropertiesTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/config/ConfigPropertiesTests.kt new file mode 100644 index 00000000..0217c53f --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/config/ConfigPropertiesTests.kt @@ -0,0 +1,88 @@ +package org.opensearch.commons.notifications.model.config + +import org.junit.jupiter.api.Test +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.MethodType +import org.opensearch.commons.notifications.model.Slack +import org.opensearch.commons.notifications.model.SmtpAccount +import org.opensearch.commons.notifications.model.Webhook +import org.opensearch.commons.notifications.model.config.ConfigDataProperties.createConfigData +import org.opensearch.commons.notifications.model.config.ConfigDataProperties.getReaderForConfigType +import org.opensearch.commons.utils.createObjectFromJsonString +import org.opensearch.commons.utils.getJsonString +import kotlin.test.assertEquals + +internal class ConfigPropertiesTests { + @Test + fun `Validate config property reader slack`() { + assertEquals(getReaderForConfigType(ConfigType.SLACK), Slack.reader) + } + + @Test + fun `Validate config property reader chime`() { + assertEquals(getReaderForConfigType(ConfigType.CHIME), Chime.reader) + } + + @Test + fun `Validate config property reader webhook`() { + assertEquals(getReaderForConfigType(ConfigType.WEBHOOK), Webhook.reader) + } + + @Test + fun `Validate config property reader email`() { + assertEquals(getReaderForConfigType(ConfigType.EMAIL), Email.reader) + } + + @Test + fun `Validate config property reader EmailGroup`() { + assertEquals(getReaderForConfigType(ConfigType.EMAIL_GROUP), EmailGroup.reader) + } + + @Test + fun `Validate config property reader SmtpAccount`() { + assertEquals(getReaderForConfigType(ConfigType.SMTP_ACCOUNT), SmtpAccount.reader) + } + + @Test + fun `Validate config data parse slack`() { + val sampleSlack = Slack("https://domain.com/sample_url#1234567890") + val jsonString = getJsonString(sampleSlack) + val recreatedObject = createObjectFromJsonString(jsonString) { createConfigData(ConfigType.SLACK, it) } + assertEquals(sampleSlack, recreatedObject) + } + + @Test + fun `Validate config data parse chime`() { + val sampleChime = Chime("https://domain.com/sample_url#1234567890") + val jsonString = getJsonString(sampleChime) + val recreatedObject = createObjectFromJsonString(jsonString) { createConfigData(ConfigType.CHIME, it) } + assertEquals(sampleChime, recreatedObject) + } + + @Test + fun `Validate config data parse webhook`() { + val sampleWebhook = Webhook("https://domain.com/sample_url#1234567890") + val jsonString = getJsonString(sampleWebhook) + val recreatedObject = createObjectFromJsonString(jsonString) { createConfigData(ConfigType.WEBHOOK, it) } + assertEquals(sampleWebhook, recreatedObject) + } + + @Test + fun `Validate config data parse EmailGroup`() { + val sampleEmailGroup = EmailGroup(listOf("email1@email.com", "email2@email.com")) + val jsonString = getJsonString(sampleEmailGroup) + val recreatedObject = createObjectFromJsonString(jsonString) { createConfigData(ConfigType.EMAIL_GROUP, it) } + assertEquals(sampleEmailGroup, recreatedObject) + } + + @Test + fun `Validate config data parse SmtpAccount`() { + val sampleSmtpAccount = SmtpAccount("domain.com", 1234, MethodType.SSL, "from@domain.com") + val jsonString = getJsonString(sampleSmtpAccount) + val recreatedObject = createObjectFromJsonString(jsonString) { createConfigData(ConfigType.SMTP_ACCOUNT, it) } + assertEquals(sampleSmtpAccount, recreatedObject) + } +} diff --git a/src/test/kotlin/org/opensearch/commons/utils/TestHelpers.kt b/src/test/kotlin/org/opensearch/commons/utils/TestHelpers.kt new file mode 100644 index 00000000..35f5d3b9 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/utils/TestHelpers.kt @@ -0,0 +1,55 @@ +/* + * 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.utils + +import org.opensearch.common.xcontent.DeprecationHandler +import org.opensearch.common.xcontent.NamedXContentRegistry +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentFactory +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentType +import java.io.ByteArrayOutputStream + +fun getJsonString(xContent: ToXContent): String { + ByteArrayOutputStream().use { byteArrayOutputStream -> + val builder = XContentFactory.jsonBuilder(byteArrayOutputStream) + xContent.toXContent(builder, ToXContent.EMPTY_PARAMS) + builder.close() + return byteArrayOutputStream.toString("UTF8") + } +} + +inline fun createObjectFromJsonString( + jsonString: String, + block: (XContentParser) -> CreateType +): CreateType { + val parser = XContentType.JSON.xContent() + .createParser(NamedXContentRegistry.EMPTY, DeprecationHandler.IGNORE_DEPRECATIONS, jsonString) + parser.nextToken() + return block(parser) +}