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) +}