예제 #1
0
        /// <summary>
        /// Update the aggregated results with the given sent notification data.
        /// </summary>
        /// <param name="sentNotification">The sent notification data entity.</param>
        public void UpdateAggregatedResults(SentNotificationDataEntity sentNotification)
        {
            this.CurrentTotalNotificationCount++;

            if (sentNotification.DeliveryStatus == SentNotificationDataEntity.Succeeded)
            {
                this.SucceededCount++;
            }
            else if (sentNotification.DeliveryStatus == SentNotificationDataEntity.Failed)
            {
                this.FailedCount++;
            }
            else if (sentNotification.DeliveryStatus == SentNotificationDataEntity.Throttled)
            {
                this.ThrottledCount++;
            }
            else if (sentNotification.DeliveryStatus == SentNotificationDataEntity.RecipientNotFound)
            {
                this.RecipientNotFoundCount++;
            }

            if (sentNotification.SentDate != null &&
                (this.LastSentDate == null ||
                 this.LastSentDate < sentNotification.SentDate))
            {
                this.LastSentDate = sentNotification.SentDate;
            }
        }
        private async Task SaveSentNotificationData(
            string notificationId,
            string aadId,
            int totalNumberOfThrottles,
            bool isStatusCodeFromCreateConversation,
            HttpStatusCode statusCode)
        {
            var updatedSentNotificationDataEntity = new SentNotificationDataEntity
            {
                PartitionKey                       = notificationId,
                RowKey                             = aadId,
                AadId                              = aadId,
                TotalNumberOfThrottles             = totalNumberOfThrottles,
                SentDate                           = DateTime.UtcNow,
                IsStatusCodeFromCreateConversation = isStatusCodeFromCreateConversation,
                StatusCode                         = (int)statusCode,
            };

            if (statusCode == HttpStatusCode.Created)
            {
                updatedSentNotificationDataEntity.DeliveryStatus = SentNotificationDataEntity.Succeeded;
            }
            else if (statusCode == HttpStatusCode.TooManyRequests)
            {
                updatedSentNotificationDataEntity.DeliveryStatus = SentNotificationDataEntity.Throttled;
            }
            else
            {
                updatedSentNotificationDataEntity.DeliveryStatus = SentNotificationDataEntity.Failed;
            }

            var operation = TableOperation.InsertOrMerge(updatedSentNotificationDataEntity);

            await CompanyCommunicatorSendFunction.sentNotificationDataRepository.Table.ExecuteAsync(operation);
        }
예제 #3
0
        public async Task UpdateSentNotification_Status_Failed_Test()
        {
            // Arrange
            var notificationService = GetNotificationService();

            notificationData = new SentNotificationDataEntity()
            {
                StatusCode     = (int)HttpStatusCode.NotFound,
                DeliveryStatus = SentNotificationDataEntity.Failed
            };
            sentNotificationDataRepository
            .Setup(x => x.GetAsync(It.IsAny <string>(), It.IsAny <string>()))
            .ReturnsAsync(notificationData);
            sentNotificationDataRepository
            .Setup(x => x.InsertOrMergeAsync(It.IsAny <SentNotificationDataEntity>()))
            .Returns(Task.CompletedTask);

            // Act
            Func <Task> task = async() => await notificationService.UpdateSentNotification(notificationId, recipientId, totalNumberOfSendThrottles, 11, string.Empty, string.Empty, string.Empty);

            // Assert
            await task.Should().NotThrowAsync <Exception>();

            sentNotificationDataRepository.Verify(x => x.InsertOrMergeAsync(It.Is <SentNotificationDataEntity>(x => x.StatusCode == notificationData.StatusCode)));
        }
예제 #4
0
        public async Task UpdateSentNotification_Status_FaultedAndRetrying_Test()
        {
            // Arrange
            var notificationService = this.GetNotificationService();
            var notificationData    = new SentNotificationDataEntity()
            {
                StatusCode     = SentNotificationDataEntity.FaultedAndRetryingStatusCode,
                DeliveryStatus = SentNotificationDataEntity.Retrying,
            };

            this.sentNotificationDataRepository
            .Setup(x => x.GetAsync(It.IsAny <string>(), It.IsAny <string>()))
            .ReturnsAsync(notificationData);
            this.sentNotificationDataRepository
            .Setup(x => x.InsertOrMergeAsync(It.IsAny <SentNotificationDataEntity>()))
            .Returns(Task.CompletedTask);

            // Act
            Func <Task> task = async() => await notificationService.UpdateSentNotification(this.notificationId, this.recipientId, this.totalNumberOfSendThrottles, SentNotificationDataEntity.FaultedAndRetryingStatusCode, string.Empty, string.Empty);

            // Assert
            await task.Should().NotThrowAsync <Exception>();

            this.sentNotificationDataRepository.Verify(x => x.InsertOrMergeAsync(It.Is <SentNotificationDataEntity>(x => x.StatusCode == notificationData.StatusCode)));
        }
예제 #5
0
        public async Task ArgumentNullExceptionTest()
        {
            // Arrange
            var    activityContext               = this.GetTeamsConversationActivity();
            string notificationId                = "notificationId";
            string batchPartitionKey             = "batchPartitionKey";
            SentNotificationDataEntity recipient = new SentNotificationDataEntity();

            // Act
            Func <Task> task = async() => await activityContext.CreateConversationAsync((null /*notificationId*/, batchPartitionKey, recipient), this.logger.Object);

            Func <Task> task1 = async() => await activityContext.CreateConversationAsync((notificationId, null /*batchPartitionKey*/, recipient), this.logger.Object);

            Func <Task> task2 = async() => await activityContext.CreateConversationAsync((notificationId, batchPartitionKey, null /*recipient*/), this.logger.Object);

            Func <Task> task3 = async() => await activityContext.CreateConversationAsync((notificationId, batchPartitionKey, recipient), null /*log*/);

            // Assert
            await task.Should().ThrowAsync <ArgumentNullException>("notificationId is null");

            await task1.Should().ThrowAsync <ArgumentNullException>("batch partition key is null");

            await task2.Should().ThrowAsync <ArgumentNullException>("recipient is null");

            await task3.Should().ThrowAsync <ArgumentNullException>("log is null");
        }
        public async Task CreateConversationAsyncTest_TeamRecipientType()
        {
            // Arrange
            var activityContext = this.GetTeamsConversationActivity();
            var notificationId  = "notificationId";
            SentNotificationDataEntity reciepient = new SentNotificationDataEntity()
            {
                RecipientType = SentNotificationDataEntity.TeamRecipientType,
            };

            // Act
            Func <Task> task = async() => await activityContext.CreateConversationAsync((notificationId, reciepient), this.logger.Object);

            // Assert
            await task.Should().NotThrowAsync();
        }
예제 #7
0
        public async Task CreateConversationAsync()
        {
            // Arrange
            string notificationId  = "notificationId";
            string serviceUrl      = "serviceUrlAppSettings";
            var    activityContext = this.GetTeamsConversationActivity();
            SentNotificationDataEntity recipient = new SentNotificationDataEntity()
            {
                UserId     = "userId",
                TenantId   = "tenantId",
                ServiceUrl = "serviceUrl",
                UserType   = "Member",
            };
            CreateConversationResponse response = new CreateConversationResponse()
            {
                Result         = Result.Succeeded,
                ConversationId = "conversationId",
            };

            this.conversationService
            .Setup(x => x.CreateUserConversationAsync(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <string>(), It.IsAny <int>(), this.logger.Object))
            .ReturnsAsync(response);
            this.appSettingsService
            .Setup(x => x.GetServiceUrlAsync())
            .Returns(Task.FromResult(serviceUrl));
            this.sentNotificationDataRepository
            .Setup(x => x.InsertOrMergeAsync(It.IsAny <SentNotificationDataEntity>()))
            .Returns(Task.CompletedTask);
            this.userDataRepository
            .Setup(x => x.InsertOrMergeAsync(It.IsAny <UserDataEntity>()))
            .Returns(Task.CompletedTask);

            // Act
            Func <Task> task = async() => await activityContext.CreateConversationAsync((notificationId, "batchPartitionKey", recipient), this.logger.Object);

            // Assert
            await task.Should().NotThrowAsync();

            this.conversationService.Verify(x => x.CreateUserConversationAsync(
                                                It.Is <string>(x => recipient.UserId.Equals(x)),
                                                It.Is <string>(x => recipient.TenantId.Equals(x)),
                                                It.Is <string>(x => recipient.ServiceUrl.Equals(x)),
                                                It.IsAny <int>(),
                                                this.logger.Object));
            this.userDataRepository.Verify(x => x.InsertOrMergeAsync(It.Is <UserDataEntity>(x => recipient.UserId.Equals(x.UserId))));
            this.sentNotificationDataRepository.Verify(x => x.InsertOrMergeAsync(It.IsAny <SentNotificationDataEntity>()), Times.Exactly(2));
        }
        public async Task CreateConversationAsync_UserIdNullOrEmpty()
        {
            // Arrange
            var activityContext = this.GetTeamsConversationActivity(false /*proactivelyInstallUserApp*/);
            var notificationId  = "notificationId";
            SentNotificationDataEntity recipient = new SentNotificationDataEntity()
            {
                UserId      = string.Empty,
                RecipientId = "recipientId",
            };

            // Act
            Func <Task> task = async() => await activityContext.CreateConversationAsync((notificationId, recipient), this.logger.Object);

            // Assert
            await task.Should().NotThrowAsync();
        }
예제 #9
0
        public async Task ProactiveAppInstallationEnabledTest()
        {
            // Arrange
            var    activityContext = this.GetTeamsConversationActivity(true /*proactivelyInstallUserApp*/);
            var    notificationId  = "notificationId";
            var    appId           = "appId";
            var    chatId          = "chatId";
            string serviceUrl      = "serviceUrl";
            SentNotificationDataEntity recipient = new SentNotificationDataEntity()
            {
                UserId      = string.Empty,
                RecipientId = "recipientId",
                UserType    = "Member",
            };

            this.appSettingsService
            .Setup(x => x.GetUserAppIdAsync())
            .Returns(Task.FromResult(appId));
            this.appManagerService
            .Setup(x => x.InstallAppForUserAsync(It.IsAny <string>(), It.IsAny <string>()))
            .Returns(Task.CompletedTask);
            this.chatsService
            .Setup(x => x.GetChatThreadIdAsync(It.IsAny <string>(), It.IsAny <string>()))
            .Returns(Task.FromResult(chatId));
            this.appSettingsService
            .Setup(x => x.GetServiceUrlAsync())
            .Returns(Task.FromResult(serviceUrl));

            // Act
            Func <Task> task = async() => await activityContext.CreateConversationAsync((notificationId, "batchPartitionKey", recipient), this.logger.Object);

            // Assert
            await task.Should().NotThrowAsync();

            this.appManagerService.Verify(x => x.InstallAppForUserAsync(
                                              It.Is <string>(x => appId.Equals(x)),
                                              It.Is <string>(x => recipient.RecipientId.Equals(x))));
            this.chatsService.Verify(x => x.GetChatThreadIdAsync(
                                         It.Is <string>(x => recipient.RecipientId.Equals(x)),
                                         It.Is <string>(x => appId.Equals(x))));
            this.sentNotificationDataRepository.Verify(x => x.InsertOrMergeAsync(
                                                           It.Is <SentNotificationDataEntity>(x => recipient.RecipientId.Equals(
                                                                                                  x.RecipientId) &&
                                                                                              chatId.Equals(x.ConversationId) &&
                                                                                              serviceUrl.Equals(x.ServiceUrl))));
        }
예제 #10
0
        public async Task NotificationWithInitializationStatusTest()
        {
            // Arrange
            var notificationService = GetNotificationService();
            SentNotificationDataEntity notificationData = new SentNotificationDataEntity()
            {
                StatusCode = SentNotificationDataEntity.InitializationStatusCode
            };

            sentNotificationDataRepository
            .Setup(x => x.GetAsync(It.IsAny <string>(), It.IsAny <string>()))
            .ReturnsAsync(notificationData);

            // Act
            var serviceResponse = await notificationService.IsPendingNotification(sendQueueMessageContent);

            // Assert
            serviceResponse.Should().BeTrue();
        }
예제 #11
0
        public async Task TeamConversation_GuestUser_ShouldNotDoAnything()
        {
            // Arrange
            var activityContext = this.GetTeamsConversationActivity(true /*proactivelyInstallUserApp*/);
            var notificationId  = "notificationId";
            SentNotificationDataEntity recipient = new SentNotificationDataEntity()
            {
                UserId      = string.Empty,
                RecipientId = "recipientId",
                UserType    = "Guest",
            };

            // Act
            Func <Task> task = async() => await activityContext.CreateConversationAsync((notificationId, "batchPartitionKey", recipient), this.logger.Object);

            // Assert
            await task.Should().NotThrowAsync();

            this.appSettingsService.Verify(x => x.GetUserAppIdAsync(), Times.Never);
        }
예제 #12
0
        /// <summary>
        /// Processes the notification's result data.
        /// </summary>
        /// <param name="notificationId">The notification Id.</param>
        /// <param name="recipientId">The recipient's unique identifier.
        ///     If the recipient is a user, this should be the AAD Id.
        ///     If the recipient is a team, this should be the team Id.</param>
        /// <param name="totalNumberOfSendThrottles">The total number of throttled requests to send the notification.</param>
        /// <param name="isStatusCodeFromCreateConversation">A flag indicating if the status code is from a create conversation request.</param>
        /// <param name="statusCode">The final status code.</param>
        /// <param name="allSendStatusCodes">A comma separated list representing all of the status code responses received when trying
        /// to send the notification to the recipient.</param>
        /// <param name="errorMessage">The error message to store in the database.</param>
        /// <param name="log">The logger.</param>
        /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
        public async Task ProcessResultDataAsync(
            string notificationId,
            string recipientId,
            int totalNumberOfSendThrottles,
            bool isStatusCodeFromCreateConversation,
            int statusCode,
            string allSendStatusCodes,
            string errorMessage,
            ILogger log)
        {
            try
            {
                // Storing this time before making the database call to have a time-stamp closer to when the notification
                // was sent.
                var currentDateTimeUtc = DateTime.UtcNow;

                var existingSentNotificationDataEntity = await this.sentNotificationDataRepository
                                                         .GetAsync(partitionKey : notificationId, rowKey : recipientId);

                // Set initial values.
                var allSendStatusCodesToStore      = allSendStatusCodes;
                var numberOfFunctionAttemptsToSend = 1;

                // Replace the initial values if, for some reason, the message has already been sent/attempted.
                // When the initial row is set up, the status code is set to the InitializationStatusCode (likely 0).
                // Thus, if the status code is no longer the InitializationStatusCode (likely 0), then a notification
                // has already been sent/attempted for this recipient and a result has been stored. If this is the case,
                // then append the current result to the existing results.
                if (existingSentNotificationDataEntity != null &&
                    existingSentNotificationDataEntity.StatusCode != SentNotificationDataEntity.InitializationStatusCode)
                {
                    allSendStatusCodesToStore
                        = $"{existingSentNotificationDataEntity.AllSendStatusCodes ?? string.Empty}{allSendStatusCodes}";
                    numberOfFunctionAttemptsToSend = existingSentNotificationDataEntity.NumberOfFunctionAttemptsToSend + 1;
                }

                var updatedSentNotificationDataEntity = new SentNotificationDataEntity
                {
                    PartitionKey = notificationId,
                    RowKey       = recipientId,
                    RecipientId  = recipientId,
                    TotalNumberOfSendThrottles = totalNumberOfSendThrottles,
                    SentDate = currentDateTimeUtc,
                    IsStatusCodeFromCreateConversation = isStatusCodeFromCreateConversation,
                    StatusCode                     = (int)statusCode,
                    ErrorMessage                   = errorMessage,
                    AllSendStatusCodes             = allSendStatusCodesToStore,
                    NumberOfFunctionAttemptsToSend = numberOfFunctionAttemptsToSend,
                };

                if (statusCode == (int)HttpStatusCode.Created)
                {
                    updatedSentNotificationDataEntity.DeliveryStatus = SentNotificationDataEntity.Succeeded;
                }
                else if (statusCode == (int)HttpStatusCode.TooManyRequests)
                {
                    updatedSentNotificationDataEntity.DeliveryStatus = SentNotificationDataEntity.Throttled;
                }
                else if (statusCode == (int)HttpStatusCode.NotFound)
                {
                    updatedSentNotificationDataEntity.DeliveryStatus = SentNotificationDataEntity.RecipientNotFound;
                }
                else if (statusCode == SentNotificationDataEntity.FaultedAndRetryingStatusCode)
                {
                    // This is a special case where an exception was thrown in the function.
                    // The system will try to add the queue message back to the queue and will try to
                    // send the notification again. For now, we will store the current state as retrying in
                    // the repository. If the system tries to send the notification repeatedly and reaches
                    // the dead letter maximum number of attempts, then the system should store a status of
                    // "Failed". In that case, the status code will not be faulted and retrying.
                    updatedSentNotificationDataEntity.DeliveryStatus = SentNotificationDataEntity.Retrying;
                }
                else
                {
                    updatedSentNotificationDataEntity.DeliveryStatus = SentNotificationDataEntity.Failed;
                }

                await this.sentNotificationDataRepository.InsertOrMergeAsync(updatedSentNotificationDataEntity);
            }
            catch (Exception e)
            {
                log.LogError(e, $"ERROR: {e.GetType()}: {e.Message}");
                throw;
            }
        }