/// <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); }
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))); }
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))); }
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(); }
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(); }
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)))); }
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(); }
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); }
/// <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; } }