diff --git a/test/Api.Test/NotificationCenter/Controllers/NotificationsControllerTest.cs b/test/Api.Test/NotificationCenter/Controllers/NotificationsControllerTest.cs index 8358f2b6c3..c0101dbebb 100644 --- a/test/Api.Test/NotificationCenter/Controllers/NotificationsControllerTest.cs +++ b/test/Api.Test/NotificationCenter/Controllers/NotificationsControllerTest.cs @@ -1,10 +1,13 @@ #nullable enable +using System.Text.Json; using Bit.Api.NotificationCenter.Controllers; using Bit.Api.NotificationCenter.Models.Request; -using Bit.Core.NotificationCenter.Entities; +using Bit.Core.NotificationCenter.Commands.Interfaces; +using Bit.Core.NotificationCenter.Models.Data; using Bit.Core.NotificationCenter.Models.Filter; using Bit.Core.NotificationCenter.Queries.Interfaces; using Bit.Core.Test.NotificationCenter.AutoFixture; +using Bit.Core.Utilities; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; @@ -17,36 +20,188 @@ namespace Bit.Api.Test.NotificationCenter.Controllers; public class NotificationsControllerTest { [Theory] - [BitAutoData] - [NotificationListCustomize(20)] - public async Task List_DefaultFilter_ReturnedMatchingNotifications(SutProvider sutProvider, - List notifications) + [BitAutoData([null, null])] + [BitAutoData([null, false])] + [BitAutoData([null, true])] + [BitAutoData(false, null)] + [BitAutoData(true, null)] + [BitAutoData(false, false)] + [BitAutoData(false, true)] + [BitAutoData(true, false)] + [BitAutoData(true, true)] + [NotificationStatusDetailsListCustomize(5)] + public async Task List_StatusFilter_ReturnedMatchingNotifications(bool? readStatusFilter, bool? deletedStatusFilter, + SutProvider sutProvider, + IEnumerable notificationStatusDetailsEnumerable) { - sutProvider.GetDependency() - .GetByUserIdStatusFilterAsync(Arg.Any()) - .Returns(notifications); + var notificationStatusDetailsList = notificationStatusDetailsEnumerable + .OrderByDescending(n => n.Priority) + .ThenByDescending(n => n.CreationDate) + .ToList(); - var expectedNotificationGroupedById = notifications + sutProvider.GetDependency() + .GetByUserIdStatusFilterAsync(Arg.Any()) + .Returns(notificationStatusDetailsList); + + var expectedNotificationStatusDetailsMap = notificationStatusDetailsList .Take(10) .ToDictionary(n => n.Id); - var filter = new NotificationFilterRequestModel(); + var listResponse = await sutProvider.Sut.List(new NotificationFilterRequestModel + { + ReadStatusFilter = readStatusFilter, + DeletedStatusFilter = deletedStatusFilter + }); - var listResponse = await sutProvider.Sut.List(filter); + Assert.Equal("list", listResponse.Object); + Assert.Equal(5, listResponse.Data.Count()); + Assert.All(listResponse.Data, notificationResponseModel => + { + Assert.Equal("notification", notificationResponseModel.Object); + Assert.True(expectedNotificationStatusDetailsMap.ContainsKey(notificationResponseModel.Id)); + var expectedNotificationStatusDetails = expectedNotificationStatusDetailsMap[notificationResponseModel.Id]; + Assert.NotNull(expectedNotificationStatusDetails); + Assert.Equal(expectedNotificationStatusDetails.Id, notificationResponseModel.Id); + Assert.Equal(expectedNotificationStatusDetails.Priority, notificationResponseModel.Priority); + Assert.Equal(expectedNotificationStatusDetails.Title, notificationResponseModel.Title); + Assert.Equal(expectedNotificationStatusDetails.Body, notificationResponseModel.Body); + Assert.Equal(expectedNotificationStatusDetails.RevisionDate, notificationResponseModel.Date); + Assert.Equal(expectedNotificationStatusDetails.ReadDate, notificationResponseModel.ReadDate); + Assert.Equal(expectedNotificationStatusDetails.DeletedDate, notificationResponseModel.DeletedDate); + }); + Assert.Null(listResponse.ContinuationToken); + await sutProvider.GetDependency() + .Received(1) + .GetByUserIdStatusFilterAsync(Arg.Is(filter => + filter.Read == readStatusFilter && filter.Deleted == deletedStatusFilter)); + } + + [Theory] + [BitAutoData] + [NotificationStatusDetailsListCustomize(19)] + public async Task List_PagingNoContinuationToken_ReturnedFirst10MatchingNotifications( + SutProvider sutProvider, + IEnumerable notificationStatusDetailsEnumerable) + { + var notificationStatusDetailsList = notificationStatusDetailsEnumerable + .OrderByDescending(n => n.Priority) + .ThenByDescending(n => n.CreationDate) + .ToList(); + + sutProvider.GetDependency() + .GetByUserIdStatusFilterAsync(Arg.Any()) + .Returns(notificationStatusDetailsList); + + var expectedNotificationStatusDetailsMap = notificationStatusDetailsList + .Take(10) + .ToDictionary(n => n.Id); + + var listResponse = await sutProvider.Sut.List(new NotificationFilterRequestModel()); + + Assert.Equal("list", listResponse.Object); Assert.Equal(10, listResponse.Data.Count()); Assert.All(listResponse.Data, notificationResponseModel => { - var expectedNotification = expectedNotificationGroupedById[notificationResponseModel.Id]; - Assert.NotNull(expectedNotification); - Assert.Equal(expectedNotification.Id, notificationResponseModel.Id); - Assert.Equal(expectedNotification.Priority, notificationResponseModel.Priority); - Assert.Equal(expectedNotification.Title, notificationResponseModel.Title); - Assert.Equal(expectedNotification.Body, notificationResponseModel.Body); - Assert.Equal(expectedNotification.RevisionDate, notificationResponseModel.Date); Assert.Equal("notification", notificationResponseModel.Object); + Assert.True(expectedNotificationStatusDetailsMap.ContainsKey(notificationResponseModel.Id)); + var expectedNotificationStatusDetails = expectedNotificationStatusDetailsMap[notificationResponseModel.Id]; + Assert.NotNull(expectedNotificationStatusDetails); + Assert.Equal(expectedNotificationStatusDetails.Id, notificationResponseModel.Id); + Assert.Equal(expectedNotificationStatusDetails.Priority, notificationResponseModel.Priority); + Assert.Equal(expectedNotificationStatusDetails.Title, notificationResponseModel.Title); + Assert.Equal(expectedNotificationStatusDetails.Body, notificationResponseModel.Body); + Assert.Equal(expectedNotificationStatusDetails.RevisionDate, notificationResponseModel.Date); + Assert.Equal(expectedNotificationStatusDetails.ReadDate, notificationResponseModel.ReadDate); + Assert.Equal(expectedNotificationStatusDetails.DeletedDate, notificationResponseModel.DeletedDate); + }); + + var expectedContinuationToken = new Dictionary + { + { "priority", notificationStatusDetailsList[9].Priority }, + { "date", notificationStatusDetailsList[9].CreationDate } + }; + var expectedJsonContinuationToken = JsonSerializer.Serialize(expectedContinuationToken); + var expectedBase64EncodedJsonContinuationToken = + CoreHelpers.Base64UrlEncodeString(expectedJsonContinuationToken); + Assert.Equal(expectedBase64EncodedJsonContinuationToken, listResponse.ContinuationToken); + } + + [Theory] + [BitAutoData] + [NotificationStatusDetailsListCustomize(19)] + public async Task List_PagingUsingContinuationToken_ReturnedLast9MatchingNotifications( + SutProvider sutProvider, + IEnumerable notificationStatusDetailsEnumerable) + { + var notificationStatusDetailsList = notificationStatusDetailsEnumerable + .OrderByDescending(n => n.Priority) + .ThenByDescending(n => n.CreationDate) + .ToList(); + + sutProvider.GetDependency() + .GetByUserIdStatusFilterAsync(Arg.Any()) + .Returns(notificationStatusDetailsList); + + var expectedNotificationStatusDetailsMap = notificationStatusDetailsList + .Skip(10) + .ToDictionary(n => n.Id); + + var continuationToken = new Dictionary + { + { "priority", notificationStatusDetailsList[9].Priority }, + { "date", notificationStatusDetailsList[9].CreationDate } + }; + var jsonContinuationToken = JsonSerializer.Serialize(continuationToken); + var base64EncodedJsonContinuationToken = CoreHelpers.Base64UrlEncodeString(jsonContinuationToken); + + var listResponse = await sutProvider.Sut.List(new NotificationFilterRequestModel + { + ContinuationToken = base64EncodedJsonContinuationToken + }); + + Assert.Equal("list", listResponse.Object); + Assert.Equal(9, listResponse.Data.Count()); + Assert.All(listResponse.Data, notificationResponseModel => + { + Assert.Equal("notification", notificationResponseModel.Object); + Assert.True(expectedNotificationStatusDetailsMap.ContainsKey(notificationResponseModel.Id)); + var expectedNotificationStatusDetails = expectedNotificationStatusDetailsMap[notificationResponseModel.Id]; + Assert.NotNull(expectedNotificationStatusDetails); + Assert.Equal(expectedNotificationStatusDetails.Id, notificationResponseModel.Id); + Assert.Equal(expectedNotificationStatusDetails.Priority, notificationResponseModel.Priority); + Assert.Equal(expectedNotificationStatusDetails.Title, notificationResponseModel.Title); + Assert.Equal(expectedNotificationStatusDetails.Body, notificationResponseModel.Body); + Assert.Equal(expectedNotificationStatusDetails.RevisionDate, notificationResponseModel.Date); + Assert.Equal(expectedNotificationStatusDetails.ReadDate, notificationResponseModel.ReadDate); + Assert.Equal(expectedNotificationStatusDetails.DeletedDate, notificationResponseModel.DeletedDate); }); Assert.Null(listResponse.ContinuationToken); - Assert.Equal("list", listResponse.Object); + } + + [Theory] + [BitAutoData] + public async Task MarkAsDeleted_NotificationId_MarkedAsDeleted( + SutProvider sutProvider, + Guid notificationId) + { + await sutProvider.Sut.MarkAsDeleted(notificationId); + + await sutProvider.GetDependency() + .Received(1) + .MarkDeletedAsync(notificationId); + } + + [Theory] + [BitAutoData] + public async Task MarkAsRead_NotificationId_MarkedAsRead( + SutProvider sutProvider, + Guid notificationId) + { + await sutProvider.Sut.MarkAsRead(notificationId); + + await sutProvider.GetDependency() + .Received(1) + .MarkReadAsync(notificationId); } } diff --git a/test/Core.Test/NotificationCenter/AutoFixture/NotificationFixtures.cs b/test/Core.Test/NotificationCenter/AutoFixture/NotificationFixtures.cs index 2fe42a4132..f14a0746aa 100644 --- a/test/Core.Test/NotificationCenter/AutoFixture/NotificationFixtures.cs +++ b/test/Core.Test/NotificationCenter/AutoFixture/NotificationFixtures.cs @@ -1,7 +1,5 @@ #nullable enable using AutoFixture; -using AutoFixture.Dsl; -using AutoFixture.Kernel; using Bit.Core.NotificationCenter.Entities; using Bit.Test.Common.AutoFixture.Attributes; @@ -11,44 +9,19 @@ public class NotificationCustomization(bool global) : ICustomization { public void Customize(IFixture fixture) { - fixture.Customize(GetSpecimenBuilder); - } - - public ISpecimenBuilder GetSpecimenBuilder(ICustomizationComposer customizationComposer) - { - var postprocessComposer = customizationComposer.With(n => n.Id, Guid.NewGuid()) - .With(n => n.Global, global); - - postprocessComposer = global - ? postprocessComposer.Without(n => n.UserId) - : postprocessComposer.With(n => n.UserId, Guid.NewGuid()); - - return global - ? postprocessComposer.Without(n => n.OrganizationId) - : postprocessComposer.With(n => n.OrganizationId, Guid.NewGuid()); - } -} - -public class NotificationListCustomization(int count) : ICustomization -{ - public void Customize(IFixture fixture) - { - fixture.Customize>(composer => composer.FromFactory(() => + fixture.Customize(composer => { - var notificationCustomization = new NotificationCustomization(true); + var postprocessComposer = composer.With(n => n.Id, Guid.NewGuid()) + .With(n => n.Global, global); - var notifications = new List(); - for (var i = 0; i < count; i++) - { - var customizationComposer = fixture.Build(); - var postprocessComposer = - customizationComposer.FromFactory( - notificationCustomization.GetSpecimenBuilder(customizationComposer)); - notifications.Add(postprocessComposer.Create()); - } + postprocessComposer = global + ? postprocessComposer.Without(n => n.UserId) + : postprocessComposer.With(n => n.UserId, Guid.NewGuid()); - return notifications; - })); + return global + ? postprocessComposer.Without(n => n.OrganizationId) + : postprocessComposer.With(n => n.OrganizationId, Guid.NewGuid()); + }); } } @@ -56,8 +29,3 @@ public class NotificationCustomizeAttribute(bool global = true) : BitCustomizeAt { public override ICustomization GetCustomization() => new NotificationCustomization(global); } - -public class NotificationListCustomizeAttribute(int count) : BitCustomizeAttribute -{ - public override ICustomization GetCustomization() => new NotificationListCustomization(count); -} diff --git a/test/Core.Test/NotificationCenter/AutoFixture/NotificationStatusDetailsFixtures.cs b/test/Core.Test/NotificationCenter/AutoFixture/NotificationStatusDetailsFixtures.cs index 1e1d066d16..71c9878f42 100644 --- a/test/Core.Test/NotificationCenter/AutoFixture/NotificationStatusDetailsFixtures.cs +++ b/test/Core.Test/NotificationCenter/AutoFixture/NotificationStatusDetailsFixtures.cs @@ -9,9 +9,32 @@ public class NotificationStatusDetailsCustomization : ICustomization { public void Customize(IFixture fixture) { - fixture.Customize(composer => composer.With(n => n.Id, Guid.NewGuid()) - .With(n => n.UserId, Guid.NewGuid()) - .With(n => n.OrganizationId, Guid.NewGuid())); + fixture.Customize(composer => + { + return composer.With(n => n.Id, Guid.NewGuid()) + .With(n => n.UserId, Guid.NewGuid()) + .With(n => n.OrganizationId, Guid.NewGuid()); + }); + } +} + +public class NotificationStatusDetailsListCustomization(int count) : ICustomization +{ + public void Customize(IFixture fixture) + { + var customization = new NotificationStatusDetailsCustomization(); + fixture.Customize>(composer => composer.FromFactory(() => + { + var notifications = new List(); + for (var i = 0; i < count; i++) + { + customization.Customize(fixture); + var notificationStatusDetails = fixture.Create(); + notifications.Add(notificationStatusDetails); + } + + return notifications; + })); } } @@ -19,3 +42,8 @@ public class NotificationStatusDetailsCustomizeAttribute : BitCustomizeAttribute { public override ICustomization GetCustomization() => new NotificationStatusDetailsCustomization(); } + +public class NotificationStatusDetailsListCustomizeAttribute(int count) : BitCustomizeAttribute +{ + public override ICustomization GetCustomization() => new NotificationStatusDetailsListCustomization(count); +}