diff --git a/test/Core.Test/AutoFixture/QueueClientFixture.cs b/test/Core.Test/AutoFixture/QueueClientFixtures.cs similarity index 91% rename from test/Core.Test/AutoFixture/QueueClientFixture.cs rename to test/Core.Test/AutoFixture/QueueClientFixtures.cs index 1b5a82595c..2a722f3853 100644 --- a/test/Core.Test/AutoFixture/QueueClientFixture.cs +++ b/test/Core.Test/AutoFixture/QueueClientFixtures.cs @@ -23,10 +23,10 @@ public class QueueClientBuilder : ISpecimenBuilder public class QueueClientCustomizeAttribute : BitCustomizeAttribute { - public override ICustomization GetCustomization() => new QueueClientFixture(); + public override ICustomization GetCustomization() => new QueueClientFixtures(); } -public class QueueClientFixture : ICustomization +public class QueueClientFixtures : ICustomization { public void Customize(IFixture fixture) { diff --git a/test/Core.Test/Models/Api/Request/PushSendRequestModelTests.cs b/test/Core.Test/Models/Api/Request/PushSendRequestModelTests.cs index 4163d32028..41a6c25bf2 100644 --- a/test/Core.Test/Models/Api/Request/PushSendRequestModelTests.cs +++ b/test/Core.Test/Models/Api/Request/PushSendRequestModelTests.cs @@ -1,7 +1,10 @@ #nullable enable using System.ComponentModel.DataAnnotations; +using System.Text.Json; using Bit.Core.Enums; using Bit.Core.Models.Api; +using Bit.Core.Utilities; +using Bit.Test.Common.AutoFixture.Attributes; using Xunit; namespace Bit.Core.Test.Models.Api.Request; @@ -32,20 +35,35 @@ public class PushSendRequestModelTests Assert.Contains(results, result => result.ErrorMessage == "UserId or OrganizationId is required."); } - [Fact] - public void Validate_RequiredPayloadFieldNotProvided_Invalid() + [Theory] + [BitAutoData("Payload")] + [BitAutoData("Type")] + public void Validate_RequiredFieldNotProvided_Invalid(string requiredField) { var model = new PushSendRequestModel { UserId = Guid.NewGuid().ToString(), OrganizationId = Guid.NewGuid().ToString(), - Type = PushType.SyncCiphers + Type = PushType.SyncCiphers, + Payload = "test" }; - var results = Validate(model); + var dictionary = new Dictionary(); + foreach (var property in model.GetType().GetProperties()) + { + if (property.Name == requiredField) + { + continue; + } - Assert.Single(results); - Assert.Contains(results, result => result.ErrorMessage == "The Payload field is required."); + dictionary[property.Name] = property.GetValue(model); + } + + var serialized = JsonSerializer.Serialize(dictionary, JsonHelpers.IgnoreWritingNull); + var jsonException = + Assert.Throws(() => JsonSerializer.Deserialize(serialized)); + Assert.Contains($"missing required properties, including the following: {requiredField}", + jsonException.Message); } [Fact] @@ -70,7 +88,7 @@ public class PushSendRequestModelTests private static List Validate(PushSendRequestModel model) { var results = new List(); - Validator.TryValidateObject(model, new ValidationContext(model), results); + Validator.TryValidateObject(model, new ValidationContext(model), results, true); return results; } } diff --git a/test/Core.Test/NotificationHub/NotificationHubPushNotificationServiceTests.cs b/test/Core.Test/NotificationHub/NotificationHubPushNotificationServiceTests.cs index 4a20f51729..dc391b9801 100644 --- a/test/Core.Test/NotificationHub/NotificationHubPushNotificationServiceTests.cs +++ b/test/Core.Test/NotificationHub/NotificationHubPushNotificationServiceTests.cs @@ -1,4 +1,5 @@ -using System.Text.Json; +#nullable enable +using System.Text.Json; using Bit.Core.Enums; using Bit.Core.Models; using Bit.Core.Models.Data; @@ -133,6 +134,78 @@ public class NotificationHubPushNotificationServiceTests .UpsertAsync(Arg.Any()); } + [Theory] + [BitAutoData([null])] + [BitAutoData(ClientType.All)] + public async void SendPayloadToUserAsync_ClientTypeNullOrAll_SentToUser(ClientType? clientType, + SutProvider sutProvider, Guid userId, PushType pushType, string payload, + string identifier) + { + await sutProvider.Sut.SendPayloadToUserAsync(userId.ToString(), pushType, payload, identifier, null, + clientType); + + await AssertSendTemplateNotificationAsync(sutProvider, pushType, payload, + $"(template:payload_userId:{userId} && !deviceIdentifier:{identifier})"); + await sutProvider.GetDependency() + .Received(0) + .UpsertAsync(Arg.Any()); + } + + [Theory] + [BitAutoData(ClientType.Browser)] + [BitAutoData(ClientType.Desktop)] + [BitAutoData(ClientType.Mobile)] + [BitAutoData(ClientType.Web)] + public async void SendPayloadToUserAsync_ClientTypeExplicit_SentToUserAndClientType(ClientType clientType, + SutProvider sutProvider, Guid userId, PushType pushType, string payload, + string identifier) + { + await sutProvider.Sut.SendPayloadToUserAsync(userId.ToString(), pushType, payload, identifier, null, + clientType); + + await AssertSendTemplateNotificationAsync(sutProvider, pushType, payload, + $"(template:payload_userId:{userId} && !deviceIdentifier:{identifier} && clientType:{clientType})"); + await sutProvider.GetDependency() + .Received(0) + .UpsertAsync(Arg.Any()); + } + + [Theory] + [BitAutoData([null])] + [BitAutoData(ClientType.All)] + public async void SendPayloadToOrganizationAsync_ClientTypeNullOrAll_SentToOrganization(ClientType? clientType, + SutProvider sutProvider, Guid organizationId, PushType pushType, + string payload, string identifier) + { + await sutProvider.Sut.SendPayloadToOrganizationAsync(organizationId.ToString(), pushType, payload, identifier, + null, clientType); + + await AssertSendTemplateNotificationAsync(sutProvider, pushType, payload, + $"(template:payload && organizationId:{organizationId} && !deviceIdentifier:{identifier})"); + await sutProvider.GetDependency() + .Received(0) + .UpsertAsync(Arg.Any()); + } + + [Theory] + [BitAutoData(ClientType.Browser)] + [BitAutoData(ClientType.Desktop)] + [BitAutoData(ClientType.Mobile)] + [BitAutoData(ClientType.Web)] + public async void SendPayloadToOrganizationAsync_ClientTypeExplicit_SentToOrganizationAndClientType( + ClientType clientType, SutProvider sutProvider, Guid organizationId, + PushType pushType, string payload, string identifier) + { + await sutProvider.Sut.SendPayloadToOrganizationAsync(organizationId.ToString(), pushType, payload, identifier, + null, clientType); + + await AssertSendTemplateNotificationAsync(sutProvider, pushType, payload, + $"(template:payload && organizationId:{organizationId} && !deviceIdentifier:{identifier} && clientType:{clientType})"); + await sutProvider.GetDependency() + .Received(0) + .UpsertAsync(Arg.Any()); + } + private static SyncNotificationPushNotification ToSyncNotificationPushNotification(Notification notification) => new() { diff --git a/test/Core.Test/Services/MultiServicePushNotificationServiceTests.cs b/test/Core.Test/Services/MultiServicePushNotificationServiceTests.cs index 68d6c50a7e..58571987b2 100644 --- a/test/Core.Test/Services/MultiServicePushNotificationServiceTests.cs +++ b/test/Core.Test/Services/MultiServicePushNotificationServiceTests.cs @@ -1,45 +1,63 @@ -using AutoFixture; +#nullable enable +using Bit.Core.Enums; +using Bit.Core.NotificationCenter.Entities; using Bit.Core.Services; +using Bit.Core.Test.NotificationCenter.AutoFixture; using Bit.Test.Common.AutoFixture; -using Microsoft.Extensions.Logging; +using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; using Xunit; -using GlobalSettingsCustomization = Bit.Test.Common.AutoFixture.GlobalSettings; namespace Bit.Core.Test.Services; +[SutProviderCustomize] public class MultiServicePushNotificationServiceTests { - private readonly MultiServicePushNotificationService _sut; - - private readonly ILogger _logger; - private readonly ILogger _relayLogger; - private readonly ILogger _hubLogger; - private readonly IEnumerable _services; - private readonly Settings.GlobalSettings _globalSettings; - - public MultiServicePushNotificationServiceTests() + [Theory] + [BitAutoData] + [NotificationCustomize] + public async Task PushSyncNotificationAsync_Notification_Sent( + SutProvider sutProvider, Notification notification) { - _logger = Substitute.For>(); - _relayLogger = Substitute.For>(); - _hubLogger = Substitute.For>(); + await sutProvider.Sut.PushSyncNotificationAsync(notification); - var fixture = new Fixture().WithAutoNSubstitutions().Customize(new GlobalSettingsCustomization()); - _services = fixture.CreateMany(); - _globalSettings = fixture.Create(); - - _sut = new MultiServicePushNotificationService( - _services, - _logger, - _globalSettings - ); + await sutProvider.GetDependency>() + .First() + .Received(1) + .PushSyncNotificationAsync(notification); } - // Remove this test when we add actual tests. It only proves that - // we've properly constructed the system under test. - [Fact] - public void ServiceExists() + [Theory] + [BitAutoData([null, null])] + [BitAutoData(ClientType.All, null)] + [BitAutoData([null, "test device id"])] + [BitAutoData(ClientType.All, "test device id")] + public async Task SendPayloadToUserAsync_Message_Sent(ClientType? clientType, string? deviceId, string userId, + PushType type, object payload, string identifier, SutProvider sutProvider) { - Assert.NotNull(_sut); + await sutProvider.Sut.SendPayloadToUserAsync(userId, type, payload, identifier, deviceId, clientType); + + await sutProvider.GetDependency>() + .First() + .Received(1) + .SendPayloadToUserAsync(userId, type, payload, identifier, deviceId, clientType); + } + + [Theory] + [BitAutoData([null, null])] + [BitAutoData(ClientType.All, null)] + [BitAutoData([null, "test device id"])] + [BitAutoData(ClientType.All, "test device id")] + public async Task SendPayloadToOrganizationAsync_Message_Sent(ClientType? clientType, string? deviceId, + string organizationId, PushType type, object payload, string identifier, + SutProvider sutProvider) + { + await sutProvider.Sut.SendPayloadToOrganizationAsync(organizationId, type, payload, identifier, deviceId, + clientType); + + await sutProvider.GetDependency>() + .First() + .Received(1) + .SendPayloadToOrganizationAsync(organizationId, type, payload, identifier, deviceId, clientType); } }