From 54fd0c5dcb79a3a46d4f42892b819a5c3e7423f1 Mon Sep 17 00:00:00 2001 From: Priyank Saxena Date: Tue, 28 Sep 2021 15:49:14 +0530 Subject: [PATCH] Company Communicator v4.1.5 (#509) --- Manifest/manifest_authors.json | 2 +- Manifest/manifest_users.json | 2 +- .../Repositories/BaseRepository.cs | 4 +- .../NotificationDataRepository.cs | 27 ++- .../ClientApp/package-lock.json | 2 +- .../ClientApp/package.json | 2 +- .../NotificationDataRepositoryTests.cs | 215 ++++++++++++++++++ 7 files changed, 244 insertions(+), 10 deletions(-) create mode 100644 Source/Test/CompanyCommunicator.Common.Test/Repositories/NotificationData/NotificationDataRepositoryTests.cs diff --git a/Manifest/manifest_authors.json b/Manifest/manifest_authors.json index ba6aa5ee2..f069ae919 100644 --- a/Manifest/manifest_authors.json +++ b/Manifest/manifest_authors.json @@ -1,7 +1,7 @@ { "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json", "manifestVersion": "1.5", - "version": "4.1.4", + "version": "4.1.5", "id": "1c07cd26-a088-4db8-8928-ace382fa219f", "packageName": "com.microsoft.teams.companycommunicator.authors", "developer": { diff --git a/Manifest/manifest_users.json b/Manifest/manifest_users.json index c43d80dd3..be5985a3c 100644 --- a/Manifest/manifest_users.json +++ b/Manifest/manifest_users.json @@ -1,7 +1,7 @@ { "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json", "manifestVersion": "1.5", - "version": "4.1.4", + "version": "4.1.5", "id": "148a66bb-e83d-425a-927d-09f4299a9274", "packageName": "com.microsoft.teams.companycommunicator", "developer": { diff --git a/Source/CompanyCommunicator.Common/Repositories/BaseRepository.cs b/Source/CompanyCommunicator.Common/Repositories/BaseRepository.cs index 9911f124e..4dd7b787e 100644 --- a/Source/CompanyCommunicator.Common/Repositories/BaseRepository.cs +++ b/Source/CompanyCommunicator.Common/Repositories/BaseRepository.cs @@ -59,7 +59,7 @@ public BaseRepository( protected ILogger Logger { get; } /// - public async Task CreateOrUpdateAsync(T entity) + public virtual async Task CreateOrUpdateAsync(T entity) { try { @@ -113,7 +113,7 @@ public async Task DeleteAsync(T entity) } /// - public async Task GetAsync(string partitionKey, string rowKey) + public virtual async Task GetAsync(string partitionKey, string rowKey) { try { diff --git a/Source/CompanyCommunicator.Common/Repositories/NotificationData/NotificationDataRepository.cs b/Source/CompanyCommunicator.Common/Repositories/NotificationData/NotificationDataRepository.cs index 95f0368bd..5cad11b52 100644 --- a/Source/CompanyCommunicator.Common/Repositories/NotificationData/NotificationDataRepository.cs +++ b/Source/CompanyCommunicator.Common/Repositories/NotificationData/NotificationDataRepository.cs @@ -16,6 +16,13 @@ namespace Microsoft.Teams.Apps.CompanyCommunicator.Common.Repositories.Notificat /// public class NotificationDataRepository : BaseRepository, INotificationDataRepository { + /// + /// Maximum length of error and warning messages to save in the entity. + /// This limit ensures that we don't hit the Azure table storage limits for the max size of the data + /// in a column, and the total size of an entity. + /// + public const int MaxMessageLengthToSave = 1024; + /// /// Initializes a new instance of the class. /// @@ -172,8 +179,14 @@ public async Task SaveExceptionInNotificationDataEntityAsync( notificationDataEntityId); if (notificationDataEntity != null) { - notificationDataEntity.ErrorMessage = - this.AppendNewLine(notificationDataEntity.ErrorMessage, errorMessage); + var newMessage = this.AppendNewLine(notificationDataEntity.ErrorMessage, errorMessage); + + // Restrict the total length of stored message to avoid hitting table storage limits + if (newMessage.Length <= MaxMessageLengthToSave) + { + notificationDataEntity.ErrorMessage = newMessage; + } + notificationDataEntity.Status = NotificationStatus.Failed.ToString(); // Set the end date as current date. @@ -195,8 +208,14 @@ public async Task SaveWarningInNotificationDataEntityAsync( notificationDataEntityId); if (notificationDataEntity != null) { - notificationDataEntity.WarningMessage = - this.AppendNewLine(notificationDataEntity.WarningMessage, warningMessage); + var newMessage = this.AppendNewLine(notificationDataEntity.WarningMessage, warningMessage); + + // Restrict the total length of stored message to avoid hitting table storage limits + if (newMessage.Length <= MaxMessageLengthToSave) + { + notificationDataEntity.WarningMessage = newMessage; + } + await this.CreateOrUpdateAsync(notificationDataEntity); } } diff --git a/Source/CompanyCommunicator/ClientApp/package-lock.json b/Source/CompanyCommunicator/ClientApp/package-lock.json index 593b7edd9..f756ac4f2 100644 --- a/Source/CompanyCommunicator/ClientApp/package-lock.json +++ b/Source/CompanyCommunicator/ClientApp/package-lock.json @@ -1,6 +1,6 @@ { "name": "company-communicator", - "version": "4.1.4", + "version": "4.1.5", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/Source/CompanyCommunicator/ClientApp/package.json b/Source/CompanyCommunicator/ClientApp/package.json index 0b76cc079..d164499ff 100644 --- a/Source/CompanyCommunicator/ClientApp/package.json +++ b/Source/CompanyCommunicator/ClientApp/package.json @@ -1,6 +1,6 @@ { "name": "company-communicator", - "version": "4.1.4", + "version": "4.1.5", "private": true, "dependencies": { "@fluentui/react-northstar": "^0.52.0", diff --git a/Source/Test/CompanyCommunicator.Common.Test/Repositories/NotificationData/NotificationDataRepositoryTests.cs b/Source/Test/CompanyCommunicator.Common.Test/Repositories/NotificationData/NotificationDataRepositoryTests.cs new file mode 100644 index 000000000..5ae1a6eaa --- /dev/null +++ b/Source/Test/CompanyCommunicator.Common.Test/Repositories/NotificationData/NotificationDataRepositoryTests.cs @@ -0,0 +1,215 @@ +// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +// + +namespace Microsoft.Teams.App.CompanyCommunicator.Common.Test.Repositories.NotificationData +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using FluentAssertions; + using Microsoft.Extensions.Logging; + using Microsoft.Extensions.Options; + using Microsoft.Teams.Apps.CompanyCommunicator.Common.Repositories; + using Microsoft.Teams.Apps.CompanyCommunicator.Common.Repositories.NotificationData; + using Moq; + using Xunit; + + /// + /// Notification Data Repository unit tests. + /// + public class NotificationDataRepositoryTests + { + private Mock> logger = new Mock>(); + private Mock> repositoryOptions = new Mock>(); + private TableRowKeyGenerator rowKeyGenerator = new TableRowKeyGenerator(); + + /// + /// Gets data for SaveExceptionInNotificationDataEntityAsync_SavesExceptionInfo and SaveWarningInNotificationDataEntityAsync_SavesWarningInfo. + /// + public static IEnumerable SaveMessageTestCasesData + { + get + { + var testCases = new List + { + new SaveMessageTestData + { + InitialMessage = null, + ShouldUpdateMessage = true, + FailMessage = "Should update null message.", + }, + new SaveMessageTestData + { + InitialMessage = "Short message", + ShouldUpdateMessage = true, + FailMessage = "Should update message not exceeding max length.", + }, + new SaveMessageTestData + { + InitialMessage = new string('x', NotificationDataRepository.MaxMessageLengthToSave - 1), + ShouldUpdateMessage = false, + FailMessage = "Should not update message that will exceed max length.", + }, + new SaveMessageTestData + { + InitialMessage = new string('x', NotificationDataRepository.MaxMessageLengthToSave), + ShouldUpdateMessage = false, + FailMessage = "Should not update message that is already at max length.", + }, + }; + return testCases.Select(c => new object[] { c }); + } + } + + /// + /// Check if NotificationData repository can be instantiated successfully. + /// + [Fact] + public void CreateInstance_AllParameters_ShouldBeSuccess() + { + // Arrange + this.repositoryOptions.Setup(x => x.Value).Returns(new RepositoryOptions() + { + StorageAccountConnectionString = "UseDevelopmentStorage=true", + EnsureTableExists = false, + }); + Action action = () => new NotificationDataRepository(this.logger.Object, this.repositoryOptions.Object, this.rowKeyGenerator); + + // Act and Assert. + action.Should().NotThrow(); + } + + /// + /// Check that SaveExceptionInNotificationDataEntityAsync saves the exception info to table storage, up to a maximum length. + /// + /// Test data. + /// Tracking task. + [Theory] + [MemberData(nameof(SaveMessageTestCasesData))] + public async Task SaveExceptionInNotificationDataEntityAsync_SavesExceptionInfo(SaveMessageTestData testData) + { + const string testEntityId = "testEntityId"; + const string testMessage = "New error message."; + + // Arrange + var mockRepository = this.CreateMockableNotificationDataRepository(); + var repository = mockRepository.Object; + + mockRepository.Setup(t => t.GetAsync(NotificationDataTableNames.SentNotificationsPartition, testEntityId)) + .Returns(Task.FromResult(new NotificationDataEntity + { + Id = testEntityId, + ErrorMessage = testData.InitialMessage, + })); + mockRepository.Setup(t => t.CreateOrUpdateAsync(It.IsAny())).Returns(Task.CompletedTask); + + // Act + await repository.SaveExceptionInNotificationDataEntityAsync(testEntityId, testMessage); + + // Assert + mockRepository.Verify(t => t.CreateOrUpdateAsync(It.Is(e => + e.Id == testEntityId && + e.Status == NotificationStatus.Failed.ToString()))); + + if (testData.ShouldUpdateMessage) + { + mockRepository.Verify( + t => t.CreateOrUpdateAsync(It.Is(e => + e.ErrorMessage != null && e.ErrorMessage.EndsWith(testMessage))), + testData.FailMessage); + } + else + { + mockRepository.Verify( + t => t.CreateOrUpdateAsync(It.Is(e => + e.ErrorMessage == testData.InitialMessage)), + testData.FailMessage); + } + } + + /// + /// Check that SaveWarningInNotificationDataEntityAsync saves the warning info to table storage, up to a maximum length. + /// + /// Test data. + /// Tracking task. + [Theory] + [MemberData(nameof(SaveMessageTestCasesData))] + public async Task SaveWarningInNotificationDataEntityAsync_SavesWarningInfo(SaveMessageTestData testData) + { + const string testEntityId = "testEntityId"; + const string testMessage = "New error message."; + + // Arrange + var mockRepository = this.CreateMockableNotificationDataRepository(); + var repository = mockRepository.Object; + + mockRepository.Setup(t => t.GetAsync(NotificationDataTableNames.SentNotificationsPartition, testEntityId)) + .Returns(Task.FromResult(new NotificationDataEntity + { + Id = testEntityId, + WarningMessage = testData.InitialMessage, + })); + mockRepository.Setup(t => t.CreateOrUpdateAsync(It.IsAny())).Returns(Task.CompletedTask); + + // Act + await repository.SaveWarningInNotificationDataEntityAsync(testEntityId, testMessage); + + // Assert + mockRepository.Verify(t => t.CreateOrUpdateAsync(It.Is(e => + e.Id == testEntityId))); + + if (testData.ShouldUpdateMessage) + { + mockRepository.Verify( + t => t.CreateOrUpdateAsync(It.Is(e => + e.WarningMessage != null && e.WarningMessage.EndsWith(testMessage))), + testData.FailMessage); + } + else + { + mockRepository.Verify( + t => t.CreateOrUpdateAsync(It.Is(e => + e.WarningMessage == testData.InitialMessage)), + testData.FailMessage); + } + } + + private Mock CreateMockableNotificationDataRepository() + { + this.repositoryOptions.Setup(x => x.Value).Returns(new RepositoryOptions() + { + StorageAccountConnectionString = "UseDevelopmentStorage=true", + EnsureTableExists = false, + }); + + var mock = new Mock(this.logger.Object, this.repositoryOptions.Object, this.rowKeyGenerator); + mock.CallBase = true; + + return mock; + } + + /// + /// Data for tests that check message saving to table. + /// + public class SaveMessageTestData + { + /// + /// Gets or sets the initial message value. + /// + public string InitialMessage { get; set; } + + /// + /// Gets or sets a value indicating whether the message should be updated. + /// + public bool ShouldUpdateMessage { get; set; } + + /// + /// Gets or sets the message to print if the test case fails. + /// + public string FailMessage { get; set; } + } + } +}