#nullable enable using Bit.Core.Enums; using Bit.Core.NotificationHub; using Bit.Core.Utilities; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Microsoft.Azure.NotificationHubs; using NSubstitute; using Xunit; namespace Bit.Core.Test.NotificationHub; [SutProviderCustomize] public class NotificationHubPushRegistrationServiceTests { [Theory] [RepeatingPatternBitAutoData([null, "", " "])] public async Task CreateOrUpdateRegistrationAsync_PushTokenNullOrEmpty_InstallationNotCreated(string? pushToken, SutProvider sutProvider, Guid deviceId, Guid userId, Guid identifier, Guid organizationId, Guid installationId) { await sutProvider.Sut.CreateOrUpdateRegistrationAsync(new PushRegistrationData(pushToken), deviceId.ToString(), userId.ToString(), identifier.ToString(), DeviceType.Android, [organizationId.ToString()], installationId); sutProvider.GetDependency() .Received(0) .ClientFor(deviceId); } [Theory] [RepeatingPatternBitAutoData([false, true], [false, true], [false, true])] public async Task CreateOrUpdateRegistrationAsync_DeviceTypeAndroid_InstallationCreated(bool identifierNull, bool partOfOrganizationId, bool installationIdNull, SutProvider sutProvider, Guid deviceId, Guid userId, Guid? identifier, Guid organizationId, Guid installationId) { var notificationHubClient = Substitute.For(); sutProvider.GetDependency().ClientFor(Arg.Any()).Returns(notificationHubClient); var pushToken = "test push token"; await sutProvider.Sut.CreateOrUpdateRegistrationAsync(new PushRegistrationData(pushToken), deviceId.ToString(), userId.ToString(), identifierNull ? null : identifier.ToString(), DeviceType.Android, partOfOrganizationId ? [organizationId.ToString()] : [], installationIdNull ? Guid.Empty : installationId); sutProvider.GetDependency() .Received(1) .ClientFor(deviceId); await notificationHubClient .Received(1) .CreateOrUpdateInstallationAsync(Arg.Is(installation => installation.InstallationId == deviceId.ToString() && installation.PushChannel == pushToken && installation.Platform == NotificationPlatform.FcmV1 && installation.Tags.Contains($"userId:{userId}") && installation.Tags.Contains("clientType:Mobile") && (identifierNull || installation.Tags.Contains($"deviceIdentifier:{identifier}")) && (!partOfOrganizationId || installation.Tags.Contains($"organizationId:{organizationId}")) && (installationIdNull || installation.Tags.Contains($"installationId:{installationId}")) && installation.Templates.Count == 3)); await notificationHubClient .Received(1) .CreateOrUpdateInstallationAsync(Arg.Is(installation => MatchingInstallationTemplate( installation.Templates, "template:payload", "{\"message\":{\"data\":{\"type\":\"$(type)\",\"payload\":\"$(payload)\"}}}", new List { "template:payload", $"template:payload_userId:{userId}", "clientType:Mobile", identifierNull ? null : $"template:payload_deviceIdentifier:{identifier}", partOfOrganizationId ? $"organizationId:{organizationId}" : null, installationIdNull ? null : $"installationId:{installationId}", }))); await notificationHubClient .Received(1) .CreateOrUpdateInstallationAsync(Arg.Is(installation => MatchingInstallationTemplate( installation.Templates, "template:message", "{\"message\":{\"data\":{\"type\":\"$(type)\"},\"notification\":{\"title\":\"$(title)\",\"body\":\"$(message)\"}}}", new List { "template:message", $"template:message_userId:{userId}", "clientType:Mobile", identifierNull ? null : $"template:message_deviceIdentifier:{identifier}", partOfOrganizationId ? $"organizationId:{organizationId}" : null, installationIdNull ? null : $"installationId:{installationId}", }))); await notificationHubClient .Received(1) .CreateOrUpdateInstallationAsync(Arg.Is(installation => MatchingInstallationTemplate( installation.Templates, "template:badgeMessage", "{\"message\":{\"data\":{\"type\":\"$(type)\"},\"notification\":{\"title\":\"$(title)\",\"body\":\"$(message)\"}}}", new List { "template:badgeMessage", $"template:badgeMessage_userId:{userId}", "clientType:Mobile", identifierNull ? null : $"template:badgeMessage_deviceIdentifier:{identifier}", partOfOrganizationId ? $"organizationId:{organizationId}" : null, installationIdNull ? null : $"installationId:{installationId}", }))); } [Theory] [RepeatingPatternBitAutoData([false, true], [false, true], [false, true])] public async Task CreateOrUpdateRegistrationAsync_DeviceTypeIOS_InstallationCreated(bool identifierNull, bool partOfOrganizationId, bool installationIdNull, SutProvider sutProvider, Guid deviceId, Guid userId, Guid identifier, Guid organizationId, Guid installationId) { var notificationHubClient = Substitute.For(); sutProvider.GetDependency().ClientFor(Arg.Any()).Returns(notificationHubClient); var pushToken = "test push token"; await sutProvider.Sut.CreateOrUpdateRegistrationAsync(new PushRegistrationData(pushToken), deviceId.ToString(), userId.ToString(), identifierNull ? null : identifier.ToString(), DeviceType.iOS, partOfOrganizationId ? [organizationId.ToString()] : [], installationIdNull ? Guid.Empty : installationId); sutProvider.GetDependency() .Received(1) .ClientFor(deviceId); await notificationHubClient .Received(1) .CreateOrUpdateInstallationAsync(Arg.Is(installation => installation.InstallationId == deviceId.ToString() && installation.PushChannel == pushToken && installation.Platform == NotificationPlatform.Apns && installation.Tags.Contains($"userId:{userId}") && installation.Tags.Contains("clientType:Mobile") && (identifierNull || installation.Tags.Contains($"deviceIdentifier:{identifier}")) && (!partOfOrganizationId || installation.Tags.Contains($"organizationId:{organizationId}")) && (installationIdNull || installation.Tags.Contains($"installationId:{installationId}")) && installation.Templates.Count == 3)); await notificationHubClient .Received(1) .CreateOrUpdateInstallationAsync(Arg.Is(installation => MatchingInstallationTemplate( installation.Templates, "template:payload", "{\"data\":{\"type\":\"#(type)\",\"payload\":\"$(payload)\"},\"aps\":{\"content-available\":1}}", new List { "template:payload", $"template:payload_userId:{userId}", "clientType:Mobile", identifierNull ? null : $"template:payload_deviceIdentifier:{identifier}", partOfOrganizationId ? $"organizationId:{organizationId}" : null, installationIdNull ? null : $"installationId:{installationId}", }))); await notificationHubClient .Received(1) .CreateOrUpdateInstallationAsync(Arg.Is(installation => MatchingInstallationTemplate( installation.Templates, "template:message", "{\"data\":{\"type\":\"#(type)\"},\"aps\":{\"alert\":\"$(message)\",\"badge\":null,\"content-available\":1}}", new List { "template:message", $"template:message_userId:{userId}", "clientType:Mobile", identifierNull ? null : $"template:message_deviceIdentifier:{identifier}", partOfOrganizationId ? $"organizationId:{organizationId}" : null, installationIdNull ? null : $"installationId:{installationId}", }))); await notificationHubClient .Received(1) .CreateOrUpdateInstallationAsync(Arg.Is(installation => MatchingInstallationTemplate( installation.Templates, "template:badgeMessage", "{\"data\":{\"type\":\"#(type)\"},\"aps\":{\"alert\":\"$(message)\",\"badge\":\"#(badge)\",\"content-available\":1}}", new List { "template:badgeMessage", $"template:badgeMessage_userId:{userId}", "clientType:Mobile", identifierNull ? null : $"template:badgeMessage_deviceIdentifier:{identifier}", partOfOrganizationId ? $"organizationId:{organizationId}" : null, installationIdNull ? null : $"installationId:{installationId}", }))); } [Theory] [RepeatingPatternBitAutoData([false, true], [false, true], [false, true])] public async Task CreateOrUpdateRegistrationAsync_DeviceTypeAndroidAmazon_InstallationCreated(bool identifierNull, bool partOfOrganizationId, bool installationIdNull, SutProvider sutProvider, Guid deviceId, Guid userId, Guid identifier, Guid organizationId, Guid installationId) { var notificationHubClient = Substitute.For(); sutProvider.GetDependency().ClientFor(Arg.Any()).Returns(notificationHubClient); var pushToken = "test push token"; await sutProvider.Sut.CreateOrUpdateRegistrationAsync(new PushRegistrationData(pushToken), deviceId.ToString(), userId.ToString(), identifierNull ? null : identifier.ToString(), DeviceType.AndroidAmazon, partOfOrganizationId ? [organizationId.ToString()] : [], installationIdNull ? Guid.Empty : installationId); sutProvider.GetDependency() .Received(1) .ClientFor(deviceId); await notificationHubClient .Received(1) .CreateOrUpdateInstallationAsync(Arg.Is(installation => installation.InstallationId == deviceId.ToString() && installation.PushChannel == pushToken && installation.Platform == NotificationPlatform.Adm && installation.Tags.Contains($"userId:{userId}") && installation.Tags.Contains("clientType:Mobile") && (identifierNull || installation.Tags.Contains($"deviceIdentifier:{identifier}")) && (!partOfOrganizationId || installation.Tags.Contains($"organizationId:{organizationId}")) && (installationIdNull || installation.Tags.Contains($"installationId:{installationId}")) && installation.Templates.Count == 3)); await notificationHubClient .Received(1) .CreateOrUpdateInstallationAsync(Arg.Is(installation => MatchingInstallationTemplate( installation.Templates, "template:payload", "{\"data\":{\"type\":\"#(type)\",\"payload\":\"$(payload)\"}}", new List { "template:payload", $"template:payload_userId:{userId}", "clientType:Mobile", identifierNull ? null : $"template:payload_deviceIdentifier:{identifier}", partOfOrganizationId ? $"organizationId:{organizationId}" : null, installationIdNull ? null : $"installationId:{installationId}", }))); await notificationHubClient .Received(1) .CreateOrUpdateInstallationAsync(Arg.Is(installation => MatchingInstallationTemplate( installation.Templates, "template:message", "{\"data\":{\"type\":\"#(type)\",\"message\":\"$(message)\"}}", new List { "template:message", $"template:message_userId:{userId}", "clientType:Mobile", identifierNull ? null : $"template:message_deviceIdentifier:{identifier}", partOfOrganizationId ? $"organizationId:{organizationId}" : null, installationIdNull ? null : $"installationId:{installationId}", }))); await notificationHubClient .Received(1) .CreateOrUpdateInstallationAsync(Arg.Is(installation => MatchingInstallationTemplate( installation.Templates, "template:badgeMessage", "{\"data\":{\"type\":\"#(type)\",\"message\":\"$(message)\"}}", new List { "template:badgeMessage", $"template:badgeMessage_userId:{userId}", "clientType:Mobile", identifierNull ? null : $"template:badgeMessage_deviceIdentifier:{identifier}", partOfOrganizationId ? $"organizationId:{organizationId}" : null, installationIdNull ? null : $"installationId:{installationId}", }))); } [Theory] [BitAutoData(DeviceType.ChromeBrowser)] [BitAutoData(DeviceType.ChromeExtension)] [BitAutoData(DeviceType.MacOsDesktop)] public async Task CreateOrUpdateRegistrationAsync_DeviceTypeNotMobile_InstallationCreated(DeviceType deviceType, SutProvider sutProvider, Guid deviceId, Guid userId, Guid identifier, Guid organizationId, Guid installationId) { var notificationHubClient = Substitute.For(); sutProvider.GetDependency().ClientFor(Arg.Any()).Returns(notificationHubClient); var pushToken = "test push token"; await sutProvider.Sut.CreateOrUpdateRegistrationAsync(new PushRegistrationData(pushToken), deviceId.ToString(), userId.ToString(), identifier.ToString(), deviceType, [organizationId.ToString()], installationId); sutProvider.GetDependency() .Received(1) .ClientFor(deviceId); await notificationHubClient .Received(1) .CreateOrUpdateInstallationAsync(Arg.Is(installation => installation.InstallationId == deviceId.ToString() && installation.PushChannel == pushToken && installation.Tags.Contains($"userId:{userId}") && installation.Tags.Contains($"clientType:{DeviceTypes.ToClientType(deviceType)}") && installation.Tags.Contains($"deviceIdentifier:{identifier}") && installation.Tags.Contains($"organizationId:{organizationId}") && installation.Tags.Contains($"installationId:{installationId}") && installation.Templates.Count == 0)); } private static bool MatchingInstallationTemplate(IDictionary templates, string key, string body, List tags) { var tagsNoNulls = tags.FindAll(tag => tag != null); return templates.ContainsKey(key) && templates[key].Body == body && templates[key].Tags.Count == tagsNoNulls.Count && templates[key].Tags.All(tagsNoNulls.Contains); } }