diff --git a/test/Core.Test/Models/Api/Request/PushSendRequestModelTests.cs b/test/Core.Test/Models/Api/Request/PushSendRequestModelTests.cs new file mode 100644 index 000000000000..4163d32028f9 --- /dev/null +++ b/test/Core.Test/Models/Api/Request/PushSendRequestModelTests.cs @@ -0,0 +1,76 @@ +#nullable enable +using System.ComponentModel.DataAnnotations; +using Bit.Core.Enums; +using Bit.Core.Models.Api; +using Xunit; + +namespace Bit.Core.Test.Models.Api.Request; + +public class PushSendRequestModelTests +{ + [Theory] + [InlineData(null, null)] + [InlineData(null, "")] + [InlineData(null, " ")] + [InlineData("", null)] + [InlineData(" ", null)] + [InlineData("", "")] + [InlineData(" ", " ")] + public void Validate_UserIdOrganizationIdNullOrEmpty_Invalid(string? userId, string? organizationId) + { + var model = new PushSendRequestModel + { + UserId = userId, + OrganizationId = organizationId, + Type = PushType.SyncCiphers, + Payload = "test" + }; + + var results = Validate(model); + + Assert.Single(results); + Assert.Contains(results, result => result.ErrorMessage == "UserId or OrganizationId is required."); + } + + [Fact] + public void Validate_RequiredPayloadFieldNotProvided_Invalid() + { + var model = new PushSendRequestModel + { + UserId = Guid.NewGuid().ToString(), + OrganizationId = Guid.NewGuid().ToString(), + Type = PushType.SyncCiphers + }; + + var results = Validate(model); + + Assert.Single(results); + Assert.Contains(results, result => result.ErrorMessage == "The Payload field is required."); + } + + [Fact] + public void Validate_AllFieldsPresent_Valid() + { + var model = new PushSendRequestModel + { + UserId = Guid.NewGuid().ToString(), + OrganizationId = Guid.NewGuid().ToString(), + Type = PushType.SyncCiphers, + Payload = "test payload", + Identifier = Guid.NewGuid().ToString(), + ClientType = ClientType.All, + DeviceId = Guid.NewGuid().ToString() + }; + + var results = Validate(model); + + Assert.Empty(results); + } + + private static List Validate(PushSendRequestModel model) + { + var results = new List(); + Validator.TryValidateObject(model, new ValidationContext(model), results); + return results; + } +} diff --git a/test/Core.Test/NotificationHub/NotificationHubPushNotificationServiceTests.cs b/test/Core.Test/NotificationHub/NotificationHubPushNotificationServiceTests.cs index ea9ce54131e8..4a20f517299f 100644 --- a/test/Core.Test/NotificationHub/NotificationHubPushNotificationServiceTests.cs +++ b/test/Core.Test/NotificationHub/NotificationHubPushNotificationServiceTests.cs @@ -1,42 +1,163 @@ -using Bit.Core.NotificationHub; +using System.Text.Json; +using Bit.Core.Enums; +using Bit.Core.Models; +using Bit.Core.Models.Data; +using Bit.Core.NotificationCenter.Entities; +using Bit.Core.NotificationHub; using Bit.Core.Repositories; -using Bit.Core.Services; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; +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.NotificationHub; +[SutProviderCustomize] public class NotificationHubPushNotificationServiceTests { - private readonly NotificationHubPushNotificationService _sut; + [Theory] + [BitAutoData] + [NotificationCustomize] + public async void PushSyncNotificationAsync_Global_NotSent( + SutProvider sutProvider, Notification notification) + { + await sutProvider.Sut.PushSyncNotificationAsync(notification); + + await sutProvider.GetDependency() + .Received(0) + .AllClients + .Received(0) + .SendTemplateNotificationAsync(Arg.Any>(), Arg.Any()); + await sutProvider.GetDependency() + .Received(0) + .UpsertAsync(Arg.Any()); + } + + [Theory] + [BitAutoData(false)] + [BitAutoData(true)] + [NotificationCustomize(false)] + public async void PushSyncNotificationAsync_UserIdProvidedClientTypeAll_SentToUser( + bool organizationIdNull, SutProvider sutProvider, + Notification notification) + { + if (organizationIdNull) + { + notification.OrganizationId = null; + } + + notification.ClientType = ClientType.All; + var expectedSyncNotification = ToSyncNotificationPushNotification(notification); + + await sutProvider.Sut.PushSyncNotificationAsync(notification); + + await AssertSendTemplateNotificationAsync(sutProvider, PushType.SyncNotification, expectedSyncNotification, + $"(template:payload_userId:{notification.UserId})"); + await sutProvider.GetDependency() + .Received(0) + .UpsertAsync(Arg.Any()); + } + + [Theory] + [BitAutoData(false, ClientType.Browser)] + [BitAutoData(false, ClientType.Desktop)] + [BitAutoData(false, ClientType.Web)] + [BitAutoData(false, ClientType.Mobile)] + [BitAutoData(true, ClientType.Browser)] + [BitAutoData(true, ClientType.Desktop)] + [BitAutoData(true, ClientType.Web)] + [BitAutoData(true, ClientType.Mobile)] + [NotificationCustomize(false)] + public async void PushSyncNotificationAsync_UserIdProvidedClientTypeNotAll_SentToUser(bool organizationIdNull, + ClientType clientType, SutProvider sutProvider, + Notification notification) + { + if (organizationIdNull) + { + notification.OrganizationId = null; + } + + notification.ClientType = clientType; + var expectedSyncNotification = ToSyncNotificationPushNotification(notification); + + await sutProvider.Sut.PushSyncNotificationAsync(notification); + + await AssertSendTemplateNotificationAsync(sutProvider, PushType.SyncNotification, expectedSyncNotification, + $"(template:payload_userId:{notification.UserId} && clientType:{clientType})"); + await sutProvider.GetDependency() + .Received(0) + .UpsertAsync(Arg.Any()); + } + + [Theory] + [BitAutoData] + [NotificationCustomize(false)] + public async void PushSyncNotificationAsync_UserIdNullOrganizationIdProvidedClientTypeAll_SentToOrganization( + SutProvider sutProvider, Notification notification) + { + notification.UserId = null; + notification.ClientType = ClientType.All; + var expectedSyncNotification = ToSyncNotificationPushNotification(notification); + + await sutProvider.Sut.PushSyncNotificationAsync(notification); + + await AssertSendTemplateNotificationAsync(sutProvider, PushType.SyncNotification, expectedSyncNotification, + $"(template:payload && organizationId:{notification.OrganizationId})"); + await sutProvider.GetDependency() + .Received(0) + .UpsertAsync(Arg.Any()); + } + + [Theory] + [BitAutoData(ClientType.Browser)] + [BitAutoData(ClientType.Desktop)] + [BitAutoData(ClientType.Web)] + [BitAutoData(ClientType.Mobile)] + [NotificationCustomize(false)] + public async void PushSyncNotificationAsync_UserIdNullOrganizationIdProvidedClientTypeNotAll_SentToOrganization( + ClientType clientType, SutProvider sutProvider, + Notification notification) + { + notification.UserId = null; + notification.ClientType = clientType; + + var expectedSyncNotification = ToSyncNotificationPushNotification(notification); + + await sutProvider.Sut.PushSyncNotificationAsync(notification); + + await AssertSendTemplateNotificationAsync(sutProvider, PushType.SyncNotification, expectedSyncNotification, + $"(template:payload && organizationId:{notification.OrganizationId} && clientType:{clientType})"); + await sutProvider.GetDependency() + .Received(0) + .UpsertAsync(Arg.Any()); + } - private readonly IInstallationDeviceRepository _installationDeviceRepository; - private readonly INotificationHubPool _notificationHubPool; - private readonly IHttpContextAccessor _httpContextAccessor; - private readonly ILogger _logger; + private static SyncNotificationPushNotification ToSyncNotificationPushNotification(Notification notification) => + new() + { + Id = notification.Id, + UserId = notification.UserId, + OrganizationId = notification.OrganizationId, + ClientType = notification.ClientType, + RevisionDate = notification.RevisionDate + }; - public NotificationHubPushNotificationServiceTests() + private static async Task AssertSendTemplateNotificationAsync( + SutProvider sutProvider, PushType type, object payload, string tag) { - _installationDeviceRepository = Substitute.For(); - _httpContextAccessor = Substitute.For(); - _notificationHubPool = Substitute.For(); - _logger = Substitute.For>(); - - _sut = new NotificationHubPushNotificationService( - _installationDeviceRepository, - _notificationHubPool, - _httpContextAccessor, - _logger - ); + await sutProvider.GetDependency() + .Received(1) + .AllClients + .Received(1) + .SendTemplateNotificationAsync( + Arg.Is>(dictionary => MatchingSendPayload(dictionary, type, payload)), + tag); } - // Remove this test when we add actual tests. It only proves that - // we've properly constructed the system under test. - [Fact(Skip = "Needs additional work")] - public void ServiceExists() + private static bool MatchingSendPayload(IDictionary dictionary, PushType type, object payload) { - Assert.NotNull(_sut); + return dictionary.ContainsKey("type") && dictionary["type"].Equals(((byte)type).ToString()) && + dictionary.ContainsKey("payload") && dictionary["payload"].Equals(JsonSerializer.Serialize(payload)); } }