From 738febf031619c3616b82a6a428118f426775e3b Mon Sep 17 00:00:00 2001 From: Maciej Zieniuk <167752252+mzieniukbw@users.noreply.github.com> Date: Thu, 3 Oct 2024 22:13:43 +0200 Subject: [PATCH] PM-11123: Notification Status Details view (#4848) * PM-11123: Notification Status Details view * PM-11123: Test Typo * PM-11123: New line missing * PM-11123: Delete unnecessary field * PM-11123: Moved NotificationStatusDetails to Models/Data --- .../Models/Data/NotificationStatusDetails.cs | 25 +++++++ ...tNotificationStatusDetailsForUserQuery.cs} | 9 ++- ...etNotificationStatusDetailsForUserQuery.cs | 10 +++ .../IGetNotificationsForUserQuery.cs | 10 --- .../Repositories/INotificationRepository.cs | 4 +- .../Repositories/NotificationRepository.cs | 5 +- .../Repositories/NotificationRepository.cs | 75 ++++++------------- .../NotificationStatusDetailsViewQuery.cs | 63 ++++++++++++++++ .../Notification_ReadByUserIdAndStatus.sql | 17 ++--- .../Views/NotificationStatusDetailsView.sql | 13 ++++ .../AutoFixture/NotificationFixtures.cs | 6 +- .../NotificationStatusDetailsFixtures.cs | 21 ++++++ .../AutoFixture/NotificationStatusFixtures.cs | 3 +- ...tificationStatusDetailsForUserQueryTest.cs | 55 ++++++++++++++ .../GetNotificationsForUserQueryTest.cs | 55 -------------- ...10-03_00_NotificationStatusDetailsView.sql | 61 +++++++++++++++ 16 files changed, 295 insertions(+), 137 deletions(-) create mode 100644 src/Core/NotificationCenter/Models/Data/NotificationStatusDetails.cs rename src/Core/NotificationCenter/Queries/{GetNotificationsForUserQuery.cs => GetNotificationStatusDetailsForUserQuery.cs} (73%) create mode 100644 src/Core/NotificationCenter/Queries/Interfaces/IGetNotificationStatusDetailsForUserQuery.cs delete mode 100644 src/Core/NotificationCenter/Queries/Interfaces/IGetNotificationsForUserQuery.cs create mode 100644 src/Infrastructure.EntityFramework/NotificationCenter/Repositories/Queries/NotificationStatusDetailsViewQuery.cs create mode 100644 src/Sql/NotificationCenter/dbo/Views/NotificationStatusDetailsView.sql create mode 100644 test/Core.Test/NotificationCenter/AutoFixture/NotificationStatusDetailsFixtures.cs create mode 100644 test/Core.Test/NotificationCenter/Queries/GetNotificationStatusDetailsForUserQueryTest.cs delete mode 100644 test/Core.Test/NotificationCenter/Queries/GetNotificationsForUserQueryTest.cs create mode 100644 util/Migrator/DbScripts/2024-10-03_00_NotificationStatusDetailsView.sql diff --git a/src/Core/NotificationCenter/Models/Data/NotificationStatusDetails.cs b/src/Core/NotificationCenter/Models/Data/NotificationStatusDetails.cs new file mode 100644 index 000000000..d48985e72 --- /dev/null +++ b/src/Core/NotificationCenter/Models/Data/NotificationStatusDetails.cs @@ -0,0 +1,25 @@ +#nullable enable +using System.ComponentModel.DataAnnotations; +using Bit.Core.Enums; +using Bit.Core.NotificationCenter.Enums; + +namespace Bit.Core.NotificationCenter.Models.Data; + +public class NotificationStatusDetails +{ + // Notification fields + public Guid Id { get; set; } + public Priority Priority { get; set; } + public bool Global { get; set; } + public ClientType ClientType { get; set; } + public Guid? UserId { get; set; } + public Guid? OrganizationId { get; set; } + [MaxLength(256)] + public string? Title { get; set; } + public string? Body { get; set; } + public DateTime CreationDate { get; set; } + public DateTime RevisionDate { get; set; } + // Notification Status fields + public DateTime? ReadDate { get; set; } + public DateTime? DeletedDate { get; set; } +} diff --git a/src/Core/NotificationCenter/Queries/GetNotificationsForUserQuery.cs b/src/Core/NotificationCenter/Queries/GetNotificationStatusDetailsForUserQuery.cs similarity index 73% rename from src/Core/NotificationCenter/Queries/GetNotificationsForUserQuery.cs rename to src/Core/NotificationCenter/Queries/GetNotificationStatusDetailsForUserQuery.cs index 9354d796d..0a783a59b 100644 --- a/src/Core/NotificationCenter/Queries/GetNotificationsForUserQuery.cs +++ b/src/Core/NotificationCenter/Queries/GetNotificationStatusDetailsForUserQuery.cs @@ -1,7 +1,7 @@ #nullable enable using Bit.Core.Context; using Bit.Core.Exceptions; -using Bit.Core.NotificationCenter.Entities; +using Bit.Core.NotificationCenter.Models.Data; using Bit.Core.NotificationCenter.Models.Filter; using Bit.Core.NotificationCenter.Queries.Interfaces; using Bit.Core.NotificationCenter.Repositories; @@ -9,19 +9,20 @@ using Bit.Core.Utilities; namespace Bit.Core.NotificationCenter.Queries; -public class GetNotificationsForUserQuery : IGetNotificationsForUserQuery +public class GetNotificationStatusDetailsForUserQuery : IGetNotificationStatusDetailsForUserQuery { private readonly ICurrentContext _currentContext; private readonly INotificationRepository _notificationRepository; - public GetNotificationsForUserQuery(ICurrentContext currentContext, + public GetNotificationStatusDetailsForUserQuery(ICurrentContext currentContext, INotificationRepository notificationRepository) { _currentContext = currentContext; _notificationRepository = notificationRepository; } - public async Task> GetByUserIdStatusFilterAsync(NotificationStatusFilter statusFilter) + public async Task> GetByUserIdStatusFilterAsync( + NotificationStatusFilter statusFilter) { if (!_currentContext.UserId.HasValue) { diff --git a/src/Core/NotificationCenter/Queries/Interfaces/IGetNotificationStatusDetailsForUserQuery.cs b/src/Core/NotificationCenter/Queries/Interfaces/IGetNotificationStatusDetailsForUserQuery.cs new file mode 100644 index 000000000..456a0e940 --- /dev/null +++ b/src/Core/NotificationCenter/Queries/Interfaces/IGetNotificationStatusDetailsForUserQuery.cs @@ -0,0 +1,10 @@ +#nullable enable +using Bit.Core.NotificationCenter.Models.Data; +using Bit.Core.NotificationCenter.Models.Filter; + +namespace Bit.Core.NotificationCenter.Queries.Interfaces; + +public interface IGetNotificationStatusDetailsForUserQuery +{ + Task> GetByUserIdStatusFilterAsync(NotificationStatusFilter statusFilter); +} diff --git a/src/Core/NotificationCenter/Queries/Interfaces/IGetNotificationsForUserQuery.cs b/src/Core/NotificationCenter/Queries/Interfaces/IGetNotificationsForUserQuery.cs deleted file mode 100644 index f50c7745e..000000000 --- a/src/Core/NotificationCenter/Queries/Interfaces/IGetNotificationsForUserQuery.cs +++ /dev/null @@ -1,10 +0,0 @@ -#nullable enable -using Bit.Core.NotificationCenter.Entities; -using Bit.Core.NotificationCenter.Models.Filter; - -namespace Bit.Core.NotificationCenter.Queries.Interfaces; - -public interface IGetNotificationsForUserQuery -{ - Task> GetByUserIdStatusFilterAsync(NotificationStatusFilter statusFilter); -} diff --git a/src/Core/NotificationCenter/Repositories/INotificationRepository.cs b/src/Core/NotificationCenter/Repositories/INotificationRepository.cs index 623e759df..2c3faed91 100644 --- a/src/Core/NotificationCenter/Repositories/INotificationRepository.cs +++ b/src/Core/NotificationCenter/Repositories/INotificationRepository.cs @@ -1,6 +1,7 @@ #nullable enable using Bit.Core.Enums; using Bit.Core.NotificationCenter.Entities; +using Bit.Core.NotificationCenter.Models.Data; using Bit.Core.NotificationCenter.Models.Filter; using Bit.Core.Repositories; @@ -23,7 +24,8 @@ public interface INotificationRepository : IRepository /// /// /// Ordered by priority (highest to lowest) and creation date (descending). + /// Includes all fields from and /// - Task> GetByUserIdAndStatusAsync(Guid userId, ClientType clientType, + Task> GetByUserIdAndStatusAsync(Guid userId, ClientType clientType, NotificationStatusFilter? statusFilter); } diff --git a/src/Infrastructure.Dapper/NotificationCenter/Repositories/NotificationRepository.cs b/src/Infrastructure.Dapper/NotificationCenter/Repositories/NotificationRepository.cs index 40bfd4b0e..f70c50f49 100644 --- a/src/Infrastructure.Dapper/NotificationCenter/Repositories/NotificationRepository.cs +++ b/src/Infrastructure.Dapper/NotificationCenter/Repositories/NotificationRepository.cs @@ -2,6 +2,7 @@ using System.Data; using Bit.Core.Enums; using Bit.Core.NotificationCenter.Entities; +using Bit.Core.NotificationCenter.Models.Data; using Bit.Core.NotificationCenter.Models.Filter; using Bit.Core.NotificationCenter.Repositories; using Bit.Core.Settings; @@ -23,12 +24,12 @@ public class NotificationRepository : Repository, INotificat { } - public async Task> GetByUserIdAndStatusAsync(Guid userId, + public async Task> GetByUserIdAndStatusAsync(Guid userId, ClientType clientType, NotificationStatusFilter? statusFilter) { await using var connection = new SqlConnection(ConnectionString); - var results = await connection.QueryAsync( + var results = await connection.QueryAsync( "[dbo].[Notification_ReadByUserIdAndStatus]", new { UserId = userId, ClientType = clientType, statusFilter?.Read, statusFilter?.Deleted }, commandType: CommandType.StoredProcedure); diff --git a/src/Infrastructure.EntityFramework/NotificationCenter/Repositories/NotificationRepository.cs b/src/Infrastructure.EntityFramework/NotificationCenter/Repositories/NotificationRepository.cs index 03ae63c59..a413e7874 100644 --- a/src/Infrastructure.EntityFramework/NotificationCenter/Repositories/NotificationRepository.cs +++ b/src/Infrastructure.EntityFramework/NotificationCenter/Repositories/NotificationRepository.cs @@ -1,9 +1,11 @@ #nullable enable using AutoMapper; using Bit.Core.Enums; +using Bit.Core.NotificationCenter.Models.Data; using Bit.Core.NotificationCenter.Models.Filter; using Bit.Core.NotificationCenter.Repositories; using Bit.Infrastructure.EntityFramework.NotificationCenter.Models; +using Bit.Infrastructure.EntityFramework.NotificationCenter.Repositories.Queries; using Bit.Infrastructure.EntityFramework.Repositories; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; @@ -20,34 +22,13 @@ public class NotificationRepository : Repository> GetByUserIdAsync(Guid userId, ClientType clientType) - { - return await GetByUserIdAndStatusAsync(userId, clientType, new NotificationStatusFilter()); - } - - public async Task> GetByUserIdAndStatusAsync(Guid userId, - ClientType clientType, NotificationStatusFilter? statusFilter) { await using var scope = ServiceScopeFactory.CreateAsyncScope(); var dbContext = GetDatabaseContext(scope); - var notificationQuery = BuildNotificationQuery(dbContext, userId, clientType); + var notificationStatusDetailsViewQuery = new NotificationStatusDetailsViewQuery(userId, clientType); - if (statusFilter != null && (statusFilter.Read != null || statusFilter.Deleted != null)) - { - notificationQuery = from n in notificationQuery - join ns in dbContext.NotificationStatuses on n.Id equals ns.NotificationId - where - ns.UserId == userId && - ( - statusFilter.Read == null || - (statusFilter.Read == true ? ns.ReadDate != null : ns.ReadDate == null) || - statusFilter.Deleted == null || - (statusFilter.Deleted == true ? ns.DeletedDate != null : ns.DeletedDate == null) - ) - select n; - } - - var notifications = await notificationQuery + var notifications = await notificationStatusDetailsViewQuery.Run(dbContext) .OrderByDescending(n => n.Priority) .ThenByDescending(n => n.CreationDate) .ToListAsync(); @@ -55,38 +36,28 @@ public class NotificationRepository : Repository>(notifications); } - private static IQueryable BuildNotificationQuery(DatabaseContext dbContext, Guid userId, - ClientType clientType) + public async Task> GetByUserIdAndStatusAsync(Guid userId, + ClientType clientType, NotificationStatusFilter? statusFilter) { - var clientTypes = new[] { ClientType.All }; - if (clientType != ClientType.All) + await using var scope = ServiceScopeFactory.CreateAsyncScope(); + var dbContext = GetDatabaseContext(scope); + + var notificationStatusDetailsViewQuery = new NotificationStatusDetailsViewQuery(userId, clientType); + + var query = notificationStatusDetailsViewQuery.Run(dbContext); + if (statusFilter != null && (statusFilter.Read != null || statusFilter.Deleted != null)) { - clientTypes = [ClientType.All, clientType]; + query = from n in query + where statusFilter.Read == null || + (statusFilter.Read == true ? n.ReadDate != null : n.ReadDate == null) || + statusFilter.Deleted == null || + (statusFilter.Deleted == true ? n.DeletedDate != null : n.DeletedDate == null) + select n; } - return from n in dbContext.Notifications - join ou in dbContext.OrganizationUsers.Where(ou => ou.UserId == userId) - on n.OrganizationId equals ou.OrganizationId into grouping - from ou in grouping.DefaultIfEmpty() - where - clientTypes.Contains(n.ClientType) && - ( - ( - n.Global && - n.UserId == null && - n.OrganizationId == null - ) || - ( - !n.Global && - n.UserId == userId && - (n.OrganizationId == null || ou != null) - ) || - ( - !n.Global && - n.UserId == null && - ou != null - ) - ) - select n; + return await query + .OrderByDescending(n => n.Priority) + .ThenByDescending(n => n.CreationDate) + .ToListAsync(); } } diff --git a/src/Infrastructure.EntityFramework/NotificationCenter/Repositories/Queries/NotificationStatusDetailsViewQuery.cs b/src/Infrastructure.EntityFramework/NotificationCenter/Repositories/Queries/NotificationStatusDetailsViewQuery.cs new file mode 100644 index 000000000..2f8bade1d --- /dev/null +++ b/src/Infrastructure.EntityFramework/NotificationCenter/Repositories/Queries/NotificationStatusDetailsViewQuery.cs @@ -0,0 +1,63 @@ +#nullable enable +using Bit.Core.Enums; +using Bit.Core.NotificationCenter.Models.Data; +using Bit.Infrastructure.EntityFramework.Repositories; +using Bit.Infrastructure.EntityFramework.Repositories.Queries; + +namespace Bit.Infrastructure.EntityFramework.NotificationCenter.Repositories.Queries; + +public class NotificationStatusDetailsViewQuery(Guid userId, ClientType clientType) : IQuery +{ + public IQueryable Run(DatabaseContext dbContext) + { + var clientTypes = new[] { ClientType.All }; + if (clientType != ClientType.All) + { + clientTypes = [ClientType.All, clientType]; + } + + var query = from n in dbContext.Notifications + join ou in dbContext.OrganizationUsers.Where(ou => ou.UserId == userId) + on n.OrganizationId equals ou.OrganizationId into groupingOrganizationUsers + from ou in groupingOrganizationUsers.DefaultIfEmpty() + join ns in dbContext.NotificationStatuses.Where(ns => ns.UserId == userId) on n.Id equals ns.NotificationId + into groupingNotificationStatus + from ns in groupingNotificationStatus.DefaultIfEmpty() + where + clientTypes.Contains(n.ClientType) && + ( + ( + n.Global && + n.UserId == null && + n.OrganizationId == null + ) || + ( + !n.Global && + n.UserId == userId && + (n.OrganizationId == null || ou != null) + ) || + ( + !n.Global && + n.UserId == null && + ou != null + ) + ) + select new { n, ns }; + + return query.Select(x => new NotificationStatusDetails + { + Id = x.n.Id, + Priority = x.n.Priority, + Global = x.n.Global, + ClientType = x.n.ClientType, + UserId = x.n.UserId, + OrganizationId = x.n.OrganizationId, + Title = x.n.Title, + Body = x.n.Body, + CreationDate = x.n.CreationDate, + RevisionDate = x.n.RevisionDate, + ReadDate = x.ns != null ? x.ns.ReadDate : null, + DeletedDate = x.ns != null ? x.ns.DeletedDate : null, + }); + } +} diff --git a/src/Sql/NotificationCenter/dbo/Stored Procedures/Notification_ReadByUserIdAndStatus.sql b/src/Sql/NotificationCenter/dbo/Stored Procedures/Notification_ReadByUserIdAndStatus.sql index baf144501..b98f85f73 100644 --- a/src/Sql/NotificationCenter/dbo/Stored Procedures/Notification_ReadByUserIdAndStatus.sql +++ b/src/Sql/NotificationCenter/dbo/Stored Procedures/Notification_ReadByUserIdAndStatus.sql @@ -8,12 +8,11 @@ BEGIN SET NOCOUNT ON SELECT n.* - FROM [dbo].[NotificationView] n + FROM [dbo].[NotificationStatusDetailsView] n LEFT JOIN [dbo].[OrganizationUserView] ou ON n.[OrganizationId] = ou.[OrganizationId] AND ou.[UserId] = @UserId - LEFT JOIN [dbo].[NotificationStatusView] ns ON n.[Id] = ns.[NotificationId] - AND ns.[UserId] = @UserId - WHERE [ClientType] IN (0, CASE WHEN @ClientType != 0 THEN @ClientType END) + WHERE (n.[NotificationStatusUserId] IS NULL OR n.[NotificationStatusUserId] = @UserId) + AND [ClientType] IN (0, CASE WHEN @ClientType != 0 THEN @ClientType END) AND ([Global] = 1 OR (n.[UserId] = @UserId AND (n.[OrganizationId] IS NULL @@ -21,14 +20,14 @@ BEGIN OR (n.[UserId] IS NULL AND ou.[OrganizationId] IS NOT NULL)) AND ((@Read IS NULL AND @Deleted IS NULL) - OR (ns.[NotificationId] IS NOT NULL + OR (n.[NotificationStatusUserId] IS NOT NULL AND ((@Read IS NULL - OR IIF((@Read = 1 AND ns.[ReadDate] IS NOT NULL) OR - (@Read = 0 AND ns.[ReadDate] IS NULL), + OR IIF((@Read = 1 AND n.[ReadDate] IS NOT NULL) OR + (@Read = 0 AND n.[ReadDate] IS NULL), 1, 0) = 1) OR (@Deleted IS NULL - OR IIF((@Deleted = 1 AND ns.[DeletedDate] IS NOT NULL) OR - (@Deleted = 0 AND ns.[DeletedDate] IS NULL), + OR IIF((@Deleted = 1 AND n.[DeletedDate] IS NOT NULL) OR + (@Deleted = 0 AND n.[DeletedDate] IS NULL), 1, 0) = 1)))) ORDER BY [Priority] DESC, n.[CreationDate] DESC END diff --git a/src/Sql/NotificationCenter/dbo/Views/NotificationStatusDetailsView.sql b/src/Sql/NotificationCenter/dbo/Views/NotificationStatusDetailsView.sql new file mode 100644 index 000000000..5264be200 --- /dev/null +++ b/src/Sql/NotificationCenter/dbo/Views/NotificationStatusDetailsView.sql @@ -0,0 +1,13 @@ +CREATE VIEW [dbo].[NotificationStatusDetailsView] +AS +SELECT + N.*, + NS.UserId AS NotificationStatusUserId, + NS.ReadDate, + NS.DeletedDate +FROM + [dbo].[Notification] AS N +LEFT JOIN + [dbo].[NotificationStatus] as NS +ON + N.[Id] = NS.[NotificationId] diff --git a/test/Core.Test/NotificationCenter/AutoFixture/NotificationFixtures.cs b/test/Core.Test/NotificationCenter/AutoFixture/NotificationFixtures.cs index 4cdee8de9..f14a0746a 100644 --- a/test/Core.Test/NotificationCenter/AutoFixture/NotificationFixtures.cs +++ b/test/Core.Test/NotificationCenter/AutoFixture/NotificationFixtures.cs @@ -1,4 +1,5 @@ -using AutoFixture; +#nullable enable +using AutoFixture; using Bit.Core.NotificationCenter.Entities; using Bit.Test.Common.AutoFixture.Attributes; @@ -24,8 +25,7 @@ public class NotificationCustomization(bool global) : ICustomization } } -public class NotificationCustomizeAttribute(bool global = true) - : BitCustomizeAttribute +public class NotificationCustomizeAttribute(bool global = true) : BitCustomizeAttribute { public override ICustomization GetCustomization() => new NotificationCustomization(global); } diff --git a/test/Core.Test/NotificationCenter/AutoFixture/NotificationStatusDetailsFixtures.cs b/test/Core.Test/NotificationCenter/AutoFixture/NotificationStatusDetailsFixtures.cs new file mode 100644 index 000000000..1e1d066d1 --- /dev/null +++ b/test/Core.Test/NotificationCenter/AutoFixture/NotificationStatusDetailsFixtures.cs @@ -0,0 +1,21 @@ +#nullable enable +using AutoFixture; +using Bit.Core.NotificationCenter.Models.Data; +using Bit.Test.Common.AutoFixture.Attributes; + +namespace Bit.Core.Test.NotificationCenter.AutoFixture; + +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())); + } +} + +public class NotificationStatusDetailsCustomizeAttribute : BitCustomizeAttribute +{ + public override ICustomization GetCustomization() => new NotificationStatusDetailsCustomization(); +} diff --git a/test/Core.Test/NotificationCenter/AutoFixture/NotificationStatusFixtures.cs b/test/Core.Test/NotificationCenter/AutoFixture/NotificationStatusFixtures.cs index 6f7bacbe0..40eccb342 100644 --- a/test/Core.Test/NotificationCenter/AutoFixture/NotificationStatusFixtures.cs +++ b/test/Core.Test/NotificationCenter/AutoFixture/NotificationStatusFixtures.cs @@ -1,4 +1,5 @@ -using AutoFixture; +#nullable enable +using AutoFixture; using Bit.Core.NotificationCenter.Entities; using Bit.Test.Common.AutoFixture.Attributes; diff --git a/test/Core.Test/NotificationCenter/Queries/GetNotificationStatusDetailsForUserQueryTest.cs b/test/Core.Test/NotificationCenter/Queries/GetNotificationStatusDetailsForUserQueryTest.cs new file mode 100644 index 000000000..7d9c26560 --- /dev/null +++ b/test/Core.Test/NotificationCenter/Queries/GetNotificationStatusDetailsForUserQueryTest.cs @@ -0,0 +1,55 @@ +#nullable enable +using Bit.Core.Context; +using Bit.Core.Enums; +using Bit.Core.Exceptions; +using Bit.Core.NotificationCenter.Models.Data; +using Bit.Core.NotificationCenter.Models.Filter; +using Bit.Core.NotificationCenter.Queries; +using Bit.Core.NotificationCenter.Repositories; +using Bit.Core.Test.NotificationCenter.AutoFixture; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using NSubstitute; +using Xunit; + +namespace Bit.Core.Test.NotificationCenter.Queries; + +[SutProviderCustomize] +[NotificationStatusDetailsCustomize] +public class GetNotificationStatusDetailsForUserQueryTest +{ + private static void Setup(SutProvider sutProvider, + List notificationsStatusDetails, NotificationStatusFilter statusFilter, Guid? userId) + { + sutProvider.GetDependency().UserId.Returns(userId); + sutProvider.GetDependency().GetByUserIdAndStatusAsync( + userId.GetValueOrDefault(Guid.NewGuid()), Arg.Any(), statusFilter) + .Returns(notificationsStatusDetails); + } + + [Theory] + [BitAutoData] + public async Task GetByUserIdStatusFilterAsync_NotLoggedIn_NotFoundException( + SutProvider sutProvider, + List notificationsStatusDetails, NotificationStatusFilter notificationStatusFilter) + { + Setup(sutProvider, notificationsStatusDetails, notificationStatusFilter, userId: null); + + await Assert.ThrowsAsync(() => + sutProvider.Sut.GetByUserIdStatusFilterAsync(notificationStatusFilter)); + } + + [Theory] + [BitAutoData] + public async Task GetByUserIdStatusFilterAsync_NotificationsFound_Returned( + SutProvider sutProvider, + List notificationsStatusDetails, NotificationStatusFilter notificationStatusFilter) + { + Setup(sutProvider, notificationsStatusDetails, notificationStatusFilter, Guid.NewGuid()); + + var actualNotificationsStatusDetails = + await sutProvider.Sut.GetByUserIdStatusFilterAsync(notificationStatusFilter); + + Assert.Equal(notificationsStatusDetails, actualNotificationsStatusDetails); + } +} diff --git a/test/Core.Test/NotificationCenter/Queries/GetNotificationsForUserQueryTest.cs b/test/Core.Test/NotificationCenter/Queries/GetNotificationsForUserQueryTest.cs deleted file mode 100644 index 75c150c8d..000000000 --- a/test/Core.Test/NotificationCenter/Queries/GetNotificationsForUserQueryTest.cs +++ /dev/null @@ -1,55 +0,0 @@ -#nullable enable -using Bit.Core.Context; -using Bit.Core.Enums; -using Bit.Core.Exceptions; -using Bit.Core.NotificationCenter.Models.Filter; -using Bit.Core.NotificationCenter.Queries; -using Bit.Core.NotificationCenter.Repositories; -using Bit.Core.Test.NotificationCenter.AutoFixture; -using Bit.Test.Common.AutoFixture; -using Bit.Test.Common.AutoFixture.Attributes; - -namespace Bit.Core.Test.NotificationCenter.Queries; - -using Bit.Core.NotificationCenter.Entities; -using NSubstitute; -using Xunit; - -[SutProviderCustomize] -[NotificationCustomize] -public class GetNotificationsForUserQueryTest -{ - private static void Setup(SutProvider sutProvider, - List notifications, NotificationStatusFilter statusFilter, Guid? userId) - { - sutProvider.GetDependency().UserId.Returns(userId); - sutProvider.GetDependency().GetByUserIdAndStatusAsync( - userId.GetValueOrDefault(Guid.NewGuid()), Arg.Any(), statusFilter) - .Returns(notifications); - } - - [Theory] - [BitAutoData] - public async Task GetByUserIdStatusFilterAsync_NotLoggedIn_NotFoundException( - SutProvider sutProvider, - List notifications, NotificationStatusFilter notificationStatusFilter) - { - Setup(sutProvider, notifications, notificationStatusFilter, userId: null); - - await Assert.ThrowsAsync(() => - sutProvider.Sut.GetByUserIdStatusFilterAsync(notificationStatusFilter)); - } - - [Theory] - [BitAutoData] - public async Task GetByUserIdStatusFilterAsync_NotificationsFound_Returned( - SutProvider sutProvider, - List notifications, NotificationStatusFilter notificationStatusFilter) - { - Setup(sutProvider, notifications, notificationStatusFilter, Guid.NewGuid()); - - var actualNotifications = await sutProvider.Sut.GetByUserIdStatusFilterAsync(notificationStatusFilter); - - Assert.Equal(notifications, actualNotifications); - } -} diff --git a/util/Migrator/DbScripts/2024-10-03_00_NotificationStatusDetailsView.sql b/util/Migrator/DbScripts/2024-10-03_00_NotificationStatusDetailsView.sql new file mode 100644 index 000000000..5d9e4fec2 --- /dev/null +++ b/util/Migrator/DbScripts/2024-10-03_00_NotificationStatusDetailsView.sql @@ -0,0 +1,61 @@ +-- View NotificationStatusDetailsView + +IF EXISTS(SELECT * + FROM sys.views + WHERE [Name] = 'NotificationStatusDetailsView') +BEGIN +DROP VIEW [dbo].[NotificationStatusDetailsView] +END +GO + +CREATE VIEW [dbo].[NotificationStatusDetailsView] +AS +SELECT + N.*, + NS.UserId AS NotificationStatusUserId, + NS.ReadDate, + NS.DeletedDate +FROM + [dbo].[Notification] AS N +LEFT JOIN + [dbo].[NotificationStatus] as NS +ON + N.[Id] = NS.[NotificationId] +GO + +-- Stored Procedure Notification_ReadByUserIdAndStatus + +CREATE OR ALTER PROCEDURE [dbo].[Notification_ReadByUserIdAndStatus] + @UserId UNIQUEIDENTIFIER, + @ClientType TINYINT, + @Read BIT, + @Deleted BIT +AS +BEGIN + SET NOCOUNT ON + + SELECT n.* + FROM [dbo].[NotificationStatusDetailsView] n + LEFT JOIN [dbo].[OrganizationUserView] ou ON n.[OrganizationId] = ou.[OrganizationId] + AND ou.[UserId] = @UserId + WHERE (n.[NotificationStatusUserId] IS NULL OR n.[NotificationStatusUserId] = @UserId) + AND [ClientType] IN (0, CASE WHEN @ClientType != 0 THEN @ClientType END) + AND ([Global] = 1 + OR (n.[UserId] = @UserId + AND (n.[OrganizationId] IS NULL + OR ou.[OrganizationId] IS NOT NULL)) + OR (n.[UserId] IS NULL + AND ou.[OrganizationId] IS NOT NULL)) + AND ((@Read IS NULL AND @Deleted IS NULL) + OR (n.[NotificationStatusUserId] IS NOT NULL + AND ((@Read IS NULL + OR IIF((@Read = 1 AND n.[ReadDate] IS NOT NULL) OR + (@Read = 0 AND n.[ReadDate] IS NULL), + 1, 0) = 1) + OR (@Deleted IS NULL + OR IIF((@Deleted = 1 AND n.[DeletedDate] IS NOT NULL) OR + (@Deleted = 0 AND n.[DeletedDate] IS NULL), + 1, 0) = 1)))) + ORDER BY [Priority] DESC, n.[CreationDate] DESC +END +GO