1
0
mirror of https://github.com/bitwarden/server.git synced 2024-11-21 12:05:42 +01:00

Introduce notification hub pool

This commit is contained in:
Matt Gibson 2024-07-02 10:29:30 -07:00
parent 787996bc8a
commit aa8372052e
No known key found for this signature in database
GPG Key ID: 963EE038B0581878
17 changed files with 704 additions and 32 deletions

View File

@ -0,0 +1,11 @@
using Microsoft.Azure.NotificationHubs;
namespace Bit.Core.NotificationHub;
public interface INotificationHubProxy
{
Task DeleteInstallationAsync(string installationId);
Task DeleteInstallationAsync(string installationId, CancellationToken cancellationToken);
Task PatchInstallationAsync(string installationId, IList<PartialUpdateOperation> operations);
Task PatchInstallationAsync(string installationId, IList<PartialUpdateOperation> operations, CancellationToken cancellationToken);
}

View File

@ -0,0 +1,9 @@
using Microsoft.Azure.NotificationHubs;
namespace Bit.Core.NotificationHub;
public interface INotificationHubPool
{
NotificationHubClient ClientFor(Guid comb);
}

View File

@ -0,0 +1,27 @@
using Microsoft.Azure.NotificationHubs;
namespace Bit.Core.NotificationHub;
public class NotificationHubClientProxy : INotificationHubProxy
{
private readonly IEnumerable<INotificationHubClient> _clients;
public NotificationHubClientProxy(IEnumerable<INotificationHubClient> clients)
{
_clients = clients;
}
private async Task ApplyToAllClientsAsync(Func<INotificationHubClient, Task> action)
{
var tasks = _clients.Select(async c => await action(c));
await Task.WhenAll(tasks);
}
// partial INotificationHubClient implementation
// Note: Any other methods that are needed can simply be delegated as done here.
public async Task DeleteInstallationAsync(string installationId) => await ApplyToAllClientsAsync((c) => c.DeleteInstallationAsync(installationId));
public async Task DeleteInstallationAsync(string installationId, CancellationToken cancellationToken) => await ApplyToAllClientsAsync(c => c.DeleteInstallationAsync(installationId, cancellationToken));
public async Task PatchInstallationAsync(string installationId, IList<PartialUpdateOperation> operations) => await ApplyToAllClientsAsync(c => c.PatchInstallationAsync(installationId, operations));
public async Task PatchInstallationAsync(string installationId, IList<PartialUpdateOperation> operations, CancellationToken cancellationToken) => await ApplyToAllClientsAsync(c => c.PatchInstallationAsync(installationId, operations, cancellationToken));
}

View File

@ -0,0 +1,120 @@
using Bit.Core.Settings;
using Bit.Core.Utilities;
using Microsoft.Azure.NotificationHubs;
class NotificationHubConnection
{
public string HubName { get; init; }
public string ConnectionString { get; init; }
public bool EnableSendTracing { get; init; }
private NotificationHubClient _hubClient;
/// <summary>
/// Gets the NotificationHubClient for this connection.
///
/// If the client is null, it will be initialized.
///
/// <throws>Exception</throws> if the connection is invalid.
/// </summary>
public NotificationHubClient HubClient
{
get
{
if (_hubClient == null)
{
if (!IsValid)
{
throw new Exception("Invalid notification hub settings");
}
Init();
}
return _hubClient;
}
private set
{
_hubClient = value;
}
}
/// <summary>
/// Gets the start date for registration.
///
/// If null, registration is always disabled.
/// </summary>
public DateTime? RegistrationStartDate { get; init; }
/// <summary>
/// Gets the end date for registration.
///
/// If null, registration has no end date.
/// </summary>
public DateTime? RegistrationEndDate { get; init; }
/// <summary>
/// Gets whether all data needed to generate a connection to Notification Hub is present.
/// </summary>
public bool IsValid
{
get
{
{
var invalid = string.IsNullOrWhiteSpace(HubName) || string.IsNullOrWhiteSpace(ConnectionString);
return !invalid;
}
}
}
/// <summary>
/// Gets whether registration is enabled for the given comb ID.
/// This is based off of the generation time encoded in the comb ID.
/// </summary>
/// <param name="comb"></param>
/// <returns></returns>
public bool RegistrationEnabled(Guid comb)
{
var combTime = CoreHelpers.DateFromComb(comb);
return RegistrationEnabled(combTime);
}
/// <summary>
/// Gets whether registration is enabled for the given time.
/// </summary>
/// <param name="queryTime">The time to check</param>
/// <returns></returns>
public bool RegistrationEnabled(DateTime queryTime)
{
if (queryTime >= RegistrationEndDate || RegistrationStartDate == null)
{
return false;
}
return RegistrationStartDate <= queryTime;
}
private NotificationHubConnection() { }
/// <summary>
/// Creates a new NotificationHubConnection from the given settings.
/// </summary>
/// <param name="settings"></param>
/// <returns></returns>
public static NotificationHubConnection From(GlobalSettings.NotificationHubSettings settings)
{
return new()
{
HubName = settings.HubName,
ConnectionString = settings.ConnectionString,
EnableSendTracing = settings.EnableSendTracing,
// Comb time is not precise enough for millisecond accuracy
RegistrationStartDate = settings.RegistrationStartDate.HasValue ? Truncate(settings.RegistrationStartDate.Value, TimeSpan.FromMilliseconds(10)) : null,
RegistrationEndDate = settings.RegistrationEndDate
};
}
private NotificationHubConnection Init()
{
HubClient = NotificationHubClient.CreateClientFromConnectionString(ConnectionString, HubName, EnableSendTracing);
return this;
}
private static DateTime Truncate(DateTime dateTime, TimeSpan resolution)
{
return dateTime.AddTicks(-(dateTime.Ticks % resolution.Ticks));
}
}

View File

@ -0,0 +1,58 @@
using Bit.Core.Settings;
using Bit.Core.Utilities;
using Microsoft.Azure.NotificationHubs;
using Microsoft.Extensions.Logging;
namespace Bit.Core.NotificationHub;
public class NotificationHubPool : INotificationHubPool
{
private List<NotificationHubConnection> _connections { get; }
private readonly IEnumerable<INotificationHubClient> _clients;
private readonly ILogger<NotificationHubPool> _logger;
public NotificationHubPool(ILogger<NotificationHubPool> logger, GlobalSettings globalSettings)
{
_logger = logger;
_connections = filterInvalidHubs(globalSettings.NotificationHubPool.NotificationHubSettings);
_clients = _connections.Select(c => c.HubClient);
}
private List<NotificationHubConnection> filterInvalidHubs(IEnumerable<GlobalSettings.NotificationHubSettings> hubs)
{
List<NotificationHubConnection> result = new();
foreach (var hub in hubs)
{
var connection = NotificationHubConnection.From(hub);
if (!connection.IsValid)
{
_logger.LogWarning("Invalid notification hub settings: {0}", hub.HubName ?? "hub name missing");
continue;
}
result.Add(connection);
}
return result;
}
/// <summary>
/// Gets the NotificationHubClient for the given comb ID.
/// </summary>
/// <param name="comb"></param>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
public NotificationHubClient ClientFor(Guid comb)
{
var possibleConnections = _connections.Where(c => c.RegistrationEnabled(comb)).ToArray();
if (possibleConnections.Length == 0)
{
throw new InvalidOperationException($"No valid notification hubs are available for the given comb ({comb}).\n" +
$"The comb's datetime is {CoreHelpers.DateFromComb(comb)}." +
$"Hub start and end times are configured as follows:\n" +
string.Join("\n", _connections.Select(c => $"Hub {c.HubName} - Start: {c.RegistrationStartDate}, End: {c.RegistrationEndDate}")));
}
return possibleConnections[CoreHelpers.BinForComb(comb, possibleConnections.Length)].HubClient;
}
public INotificationHubProxy AllClients { get { return new NotificationHubClientProxy(_clients); } }
}

View File

@ -6,6 +6,7 @@ using Bit.Core.Enums;
using Bit.Core.Models;
using Bit.Core.Models.Data;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Settings;
using Bit.Core.Tools.Entities;
using Bit.Core.Vault.Entities;
@ -13,7 +14,7 @@ using Microsoft.AspNetCore.Http;
using Microsoft.Azure.NotificationHubs;
using Microsoft.Extensions.Logging;
namespace Bit.Core.Services;
namespace Bit.Core.NotificationHub;
public class NotificationHubPushNotificationService : IPushNotificationService
{

View File

@ -1,17 +1,19 @@
using Bit.Core.Enums;
using Bit.Core.Models.Data;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Settings;
using Microsoft.Azure.NotificationHubs;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Bit.Core.Services;
namespace Bit.Core.NotificationHub;
public class NotificationHubPushRegistrationService : IPushRegistrationService
{
private readonly IInstallationDeviceRepository _installationDeviceRepository;
private readonly GlobalSettings _globalSettings;
private readonly INotificationHubPool _notificationHubPool;
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<NotificationHubPushRegistrationService> _logger;
private Dictionary<NotificationHubType, NotificationHubClient> _clients = [];
@ -19,11 +21,13 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService
public NotificationHubPushRegistrationService(
IInstallationDeviceRepository installationDeviceRepository,
GlobalSettings globalSettings,
INotificationHubPool notificationHubPool,
IServiceProvider serviceProvider,
ILogger<NotificationHubPushRegistrationService> logger)
{
_installationDeviceRepository = installationDeviceRepository;
_globalSettings = globalSettings;
_notificationHubPool = notificationHubPool;
_serviceProvider = serviceProvider;
_logger = logger;

View File

@ -1,5 +1,6 @@
using Bit.Core.Auth.Entities;
using Bit.Core.Enums;
using Bit.Core.NotificationHub;
using Bit.Core.Repositories;
using Bit.Core.Settings;
using Bit.Core.Tools.Entities;

View File

@ -65,7 +65,9 @@ public class GlobalSettings : IGlobalSettings
public virtual SentrySettings Sentry { get; set; } = new SentrySettings();
public virtual SyslogSettings Syslog { get; set; } = new SyslogSettings();
public virtual ILogLevelSettings MinLogLevel { get; set; } = new LogLevelSettings();
// TODO MDG: delete this property
public virtual List<NotificationHubSettings> NotificationHubs { get; set; } = new();
public virtual NotificationHubPoolSettings NotificationHubPool { get; set; } = new();
public virtual YubicoSettings Yubico { get; set; } = new YubicoSettings();
public virtual DuoSettings Duo { get; set; } = new DuoSettings();
public virtual BraintreeSettings Braintree { get; set; } = new BraintreeSettings();
@ -417,7 +419,7 @@ public class GlobalSettings : IGlobalSettings
public string ConnectionString
{
get => _connectionString;
set => _connectionString = value.Trim('"');
set => _connectionString = value?.Trim('"');
}
public string HubName { get; set; }
/// <summary>
@ -425,13 +427,37 @@ public class GlobalSettings : IGlobalSettings
/// Enabling this will result in delayed responses because the Hub must wait on delivery to the PNS. This should ONLY be enabled in a non-production environment, as results are throttled.
/// </summary>
public bool EnableSendTracing { get; set; } = false;
/// <summary>
/// At least one hub configuration should have registration enabled, preferably the General hub as a safety net.
/// </summary>
public bool EnableRegistration { get; set; }
/// <summary>
/// The date and time at which registration will be enabled.
///
/// **This value should not be updated once set, as it is used to determine installation location of devices.**
///
/// If null, registration is disabled.
///
/// </summary>
public DateTime? RegistrationStartDate { get; set; }
/// <summary>
/// The date and time at which registration will be disabled.
///
/// **This value should not be updated once set, as it is used to determine installation location of devices.**
///
/// If null, hub registration has no yet known expiry.
/// </summary>
public DateTime? RegistrationEndDate { get; set; }
public NotificationHubType HubType { get; set; }
}
public class NotificationHubPoolSettings
{
/// <summary>
/// List of Notification Hub settings to use for sending push notifications.
///
/// Note that hubs on the same namespace share active device limits, so multiple namespaces should be used to increase capacity.
/// </summary>
public List<NotificationHubSettings> NotificationHubSettings { get; set; } = new();
}
public class YubicoSettings
{
public string ClientId { get; set; }

View File

@ -95,7 +95,7 @@ public static class CoreHelpers
return new DateTime(_baseDateTicks + time.Ticks, DateTimeKind.Utc);
}
internal static double BinForComb(Guid combGuid, int binCount)
internal static long BinForComb(Guid combGuid, int binCount)
{
// From System.Web.Util.HashCodeCombiner
uint CombineHashCodes(uint h1, byte h2)

View File

@ -21,6 +21,7 @@ using Bit.Core.Enums;
using Bit.Core.HostedServices;
using Bit.Core.Identity;
using Bit.Core.IdentityServer;
using Bit.Core.NotificationHub;
using Bit.Core.OrganizationFeatures;
using Bit.Core.Repositories;
using Bit.Core.Resources;

View File

@ -0,0 +1,205 @@
using Bit.Core.Settings;
using Bit.Core.Utilities;
using Xunit;
namespace Bit.Core.Test.NotificationHub;
public class NotificationHubConnectionTests
{
[Fact]
public void IsValid_ConnectionStringIsNull_ReturnsFalse()
{
// Arrange
var hub = new GlobalSettings.NotificationHubSettings()
{
ConnectionString = null,
HubName = "hub",
RegistrationStartDate = DateTime.UtcNow,
RegistrationEndDate = DateTime.UtcNow.AddDays(1)
};
// Act
var connection = NotificationHubConnection.From(hub);
// Assert
Assert.False(connection.IsValid);
}
[Fact]
public void IsValid_HubNameIsNull_ReturnsFalse()
{
// Arrange
var hub = new GlobalSettings.NotificationHubSettings()
{
ConnectionString = "Endpoint=sb://example.servicebus.windows.net/;",
HubName = null,
RegistrationStartDate = DateTime.UtcNow,
RegistrationEndDate = DateTime.UtcNow.AddDays(1)
};
// Act
var connection = NotificationHubConnection.From(hub);
// Assert
Assert.False(connection.IsValid);
}
[Fact]
public void IsValid_ConnectionStringAndHubNameAreNotNull_ReturnsTrue()
{
// Arrange
var hub = new GlobalSettings.NotificationHubSettings()
{
ConnectionString = "connection",
HubName = "hub",
RegistrationStartDate = DateTime.UtcNow,
RegistrationEndDate = DateTime.UtcNow.AddDays(1)
};
// Act
var connection = NotificationHubConnection.From(hub);
// Assert
Assert.True(connection.IsValid);
}
[Fact]
public void RegistrationEnabled_QueryTimeIsBeforeStartDate_ReturnsFalse()
{
// Arrange
var hub = new GlobalSettings.NotificationHubSettings()
{
ConnectionString = "connection",
HubName = "hub",
RegistrationStartDate = DateTime.UtcNow.AddDays(1),
RegistrationEndDate = DateTime.UtcNow.AddDays(2)
};
var connection = NotificationHubConnection.From(hub);
// Act
var result = connection.RegistrationEnabled(DateTime.UtcNow);
// Assert
Assert.False(result);
}
[Fact]
public void RegistrationEnabled_QueryTimeIsAfterEndDate_ReturnsFalse()
{
// Arrange
var hub = new GlobalSettings.NotificationHubSettings()
{
ConnectionString = "connection",
HubName = "hub",
RegistrationStartDate = DateTime.UtcNow,
RegistrationEndDate = DateTime.UtcNow.AddDays(1)
};
var connection = NotificationHubConnection.From(hub);
// Act
var result = connection.RegistrationEnabled(DateTime.UtcNow.AddDays(2));
// Assert
Assert.False(result);
}
[Fact]
public void RegistrationEnabled_NullStartDate_ReturnsFalse()
{
// Arrange
var hub = new GlobalSettings.NotificationHubSettings()
{
ConnectionString = "connection",
HubName = "hub",
RegistrationStartDate = null,
RegistrationEndDate = DateTime.UtcNow.AddDays(1)
};
var connection = NotificationHubConnection.From(hub);
// Act
var result = connection.RegistrationEnabled(DateTime.UtcNow);
// Assert
Assert.False(result);
}
[Fact]
public void RegistrationEnabled_QueryTimeIsBetweenStartDateAndEndDate_ReturnsTrue()
{
// Arrange
var hub = new GlobalSettings.NotificationHubSettings()
{
ConnectionString = "connection",
HubName = "hub",
RegistrationStartDate = DateTime.UtcNow,
RegistrationEndDate = DateTime.UtcNow.AddDays(1)
};
var connection = NotificationHubConnection.From(hub);
// Act
var result = connection.RegistrationEnabled(DateTime.UtcNow.AddHours(1));
// Assert
Assert.True(result);
}
[Fact]
public void RegistrationEnabled_CombTimeIsBeforeStartDate_ReturnsFalse()
{
// Arrange
var hub = new GlobalSettings.NotificationHubSettings()
{
ConnectionString = "connection",
HubName = "hub",
RegistrationStartDate = DateTime.UtcNow.AddDays(1),
RegistrationEndDate = DateTime.UtcNow.AddDays(2)
};
var connection = NotificationHubConnection.From(hub);
// Act
var result = connection.RegistrationEnabled(CoreHelpers.GenerateComb(Guid.NewGuid(), DateTime.UtcNow));
// Assert
Assert.False(result);
}
[Fact]
public void RegistrationEnabled_CombTimeIsAfterEndDate_ReturnsFalse()
{
// Arrange
var hub = new GlobalSettings.NotificationHubSettings()
{
ConnectionString = "connection",
HubName = "hub",
RegistrationStartDate = DateTime.UtcNow,
RegistrationEndDate = DateTime.UtcNow.AddDays(1)
};
var connection = NotificationHubConnection.From(hub);
// Act
var result = connection.RegistrationEnabled(CoreHelpers.GenerateComb(Guid.NewGuid(), DateTime.UtcNow.AddDays(2)));
// Assert
Assert.False(result);
}
[Fact]
public void RegistrationEnabled_CombTimeIsBetweenStartDateAndEndDate_ReturnsTrue()
{
// Arrange
var hub = new GlobalSettings.NotificationHubSettings()
{
ConnectionString = "connection",
HubName = "hub",
RegistrationStartDate = DateTime.UtcNow,
RegistrationEndDate = DateTime.UtcNow.AddDays(1)
};
var connection = NotificationHubConnection.From(hub);
// Act
var result = connection.RegistrationEnabled(CoreHelpers.GenerateComb(Guid.NewGuid(), DateTime.UtcNow.AddHours(1)));
// Assert
Assert.True(result);
}
}

View File

@ -0,0 +1,156 @@
using Bit.Core.NotificationHub;
using Bit.Core.Settings;
using Bit.Core.Utilities;
using Microsoft.Extensions.Logging;
using NSubstitute;
using Xunit;
using static Bit.Core.Settings.GlobalSettings;
namespace Bit.Core.Test.NotificationHub;
public class NotificationHubPoolTests
{
[Fact]
public void NotificationHubPool_WarnsOnMissingConnectionString()
{
// Arrange
var globalSettings = new GlobalSettings()
{
NotificationHubPool = new NotificationHubPoolSettings()
{
NotificationHubSettings = new() {
new() {
ConnectionString = null,
HubName = "hub",
RegistrationStartDate = DateTime.UtcNow,
RegistrationEndDate = DateTime.UtcNow.AddDays(1)
}
}
}
};
var logger = Substitute.For<ILogger<NotificationHubPool>>();
// Act
var sut = new NotificationHubPool(logger, globalSettings);
// Assert
logger.Received().Log(LogLevel.Warning, Arg.Any<EventId>(),
Arg.Is<object>(o => o.ToString() == "Invalid notification hub settings: hub"),
null,
Arg.Any<Func<object, Exception, string>>());
}
[Fact]
public void NotificationHubPool_WarnsOnMissingHubName()
{
// Arrange
var globalSettings = new GlobalSettings()
{
NotificationHubPool = new NotificationHubPoolSettings()
{
NotificationHubSettings = new() {
new() {
ConnectionString = "connection",
HubName = null,
RegistrationStartDate = DateTime.UtcNow,
RegistrationEndDate = DateTime.UtcNow.AddDays(1)
}
}
}
};
var logger = Substitute.For<ILogger<NotificationHubPool>>();
// Act
var sut = new NotificationHubPool(logger, globalSettings);
// Assert
logger.Received().Log(LogLevel.Warning, Arg.Any<EventId>(),
Arg.Is<object>(o => o.ToString() == "Invalid notification hub settings: hub name missing"),
null,
Arg.Any<Func<object, Exception, string>>());
}
[Fact]
public void NotificationHubPool_ClientFor_ThrowsOnNoValidHubs()
{
// Arrange
var globalSettings = new GlobalSettings()
{
NotificationHubPool = new NotificationHubPoolSettings()
{
NotificationHubSettings = new() {
new() {
ConnectionString = "connection",
HubName = "hub",
RegistrationStartDate = null,
RegistrationEndDate = null,
}
}
}
};
var logger = Substitute.For<ILogger<NotificationHubPool>>();
var sut = new NotificationHubPool(logger, globalSettings);
// Act
Action act = () => sut.ClientFor(Guid.NewGuid());
// Assert
Assert.Throws<InvalidOperationException>(act);
}
[Fact]
public void NotificationHubPool_ClientFor_ReturnsClient()
{
// Arrange
var globalSettings = new GlobalSettings()
{
NotificationHubPool = new NotificationHubPoolSettings()
{
NotificationHubSettings = new() {
new() {
ConnectionString = "Endpoint=sb://example.servicebus.windows.net/;SharedAccessKey=example///example=",
HubName = "hub",
RegistrationStartDate = DateTime.UtcNow,
RegistrationEndDate = DateTime.UtcNow.AddDays(1),
}
}
}
};
var logger = Substitute.For<ILogger<NotificationHubPool>>();
var sut = new NotificationHubPool(logger, globalSettings);
// Act
var client = sut.ClientFor(CoreHelpers.GenerateComb(Guid.NewGuid(), DateTime.UtcNow));
// Assert
Assert.NotNull(client);
}
[Fact]
public void NotificationHubPool_AllClients_ReturnsProxy()
{
// Arrange
var globalSettings = new GlobalSettings()
{
NotificationHubPool = new NotificationHubPoolSettings()
{
NotificationHubSettings = new() {
new() {
ConnectionString = "connection",
HubName = "hub",
RegistrationStartDate = DateTime.UtcNow,
RegistrationEndDate = DateTime.UtcNow.AddDays(1),
}
}
}
};
var logger = Substitute.For<ILogger<NotificationHubPool>>();
var sut = new NotificationHubPool(logger, globalSettings);
// Act
var proxy = sut.AllClients;
// Assert
Assert.NotNull(proxy);
}
}

View File

@ -0,0 +1,52 @@
using AutoFixture;
using Bit.Core.NotificationHub;
using Bit.Test.Common.AutoFixture;
using Microsoft.Azure.NotificationHubs;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.NotificationHub;
public class NotificationHubProxyTests
{
private readonly IEnumerable<INotificationHubClient> _clients;
public NotificationHubProxyTests()
{
_clients = new Fixture().WithAutoNSubstitutions().CreateMany<INotificationHubClient>();
}
public static IEnumerable<object[]> ClientMethods =
[
[
(NotificationHubClientProxy c) => c.DeleteInstallationAsync("test"),
(INotificationHubClient c) => c.DeleteInstallationAsync("test"),
],
[
(NotificationHubClientProxy c) => c.DeleteInstallationAsync("test", default),
(INotificationHubClient c) => c.DeleteInstallationAsync("test", default),
],
[
(NotificationHubClientProxy c) => c.PatchInstallationAsync("test", new List<PartialUpdateOperation>()),
(INotificationHubClient c) => c.PatchInstallationAsync("test", Arg.Any<List<PartialUpdateOperation>>()),
],
[
(NotificationHubClientProxy c) => c.PatchInstallationAsync("test", new List<PartialUpdateOperation>(), default),
(INotificationHubClient c) => c.PatchInstallationAsync("test", Arg.Any<List<PartialUpdateOperation>>(), default)
]
];
[Theory]
[MemberData(nameof(ClientMethods))]
public async void CallsAllClients(Func<NotificationHubClientProxy, Task> proxyMethod, Func<INotificationHubClient, Task> clientMethod)
{
var clients = _clients.ToArray();
var proxy = new NotificationHubClientProxy(clients);
await proxyMethod(proxy);
foreach (var client in clients)
{
await clientMethod(client.Received());
}
}
}

View File

@ -1,4 +1,5 @@
using Bit.Core.Repositories;
using Bit.Core.NotificationHub;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Settings;
using Microsoft.AspNetCore.Http;
@ -6,7 +7,7 @@ using Microsoft.Extensions.Logging;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.Services;
namespace Bit.Core.Test.NotificationHub;
public class NotificationHubPushNotificationServiceTests
{

View File

@ -1,11 +1,11 @@
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.NotificationHub;
using Bit.Core.Repositories;
using Bit.Core.Settings;
using Microsoft.Extensions.Logging;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.Services;
namespace Bit.Core.Test.NotificationHub;
public class NotificationHubPushRegistrationServiceTests
{
@ -15,6 +15,7 @@ public class NotificationHubPushRegistrationServiceTests
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<NotificationHubPushRegistrationService> _logger;
private readonly GlobalSettings _globalSettings;
private readonly INotificationHubPool _notificationHubPool;
public NotificationHubPushRegistrationServiceTests()
{
@ -22,10 +23,12 @@ public class NotificationHubPushRegistrationServiceTests
_serviceProvider = Substitute.For<IServiceProvider>();
_logger = Substitute.For<ILogger<NotificationHubPushRegistrationService>>();
_globalSettings = new GlobalSettings();
_notificationHubPool = Substitute.For<INotificationHubPool>();
_sut = new NotificationHubPushRegistrationService(
_installationDeviceRepository,
_globalSettings,
_notificationHubPool,
_serviceProvider,
_logger
);

View File

@ -34,33 +34,30 @@ public class CoreHelpersTests
// the comb are working properly
}
public static IEnumerable<object[]> GenerateCombCases = new[]
{
new object[]
{
public static IEnumerable<object[]> GuidSeedCases = [
[
Guid.Parse("a58db474-43d8-42f1-b4ee-0c17647cd0c0"), // Input Guid
new DateTime(2022, 3, 12, 12, 12, 0, DateTimeKind.Utc), // Input Time
Guid.Parse("a58db474-43d8-42f1-b4ee-ae5600c90cc1"), // Expected Comb
},
new object[]
{
],
[
Guid.Parse("f776e6ee-511f-4352-bb28-88513002bdeb"),
new DateTime(2021, 5, 10, 10, 52, 0, DateTimeKind.Utc),
Guid.Parse("f776e6ee-511f-4352-bb28-ad2400b313c1"),
},
new object[]
{
],
[
Guid.Parse("51a25fc7-3cad-497d-8e2f-8d77011648a1"),
new DateTime(1999, 2, 26, 16, 53, 13, DateTimeKind.Utc),
Guid.Parse("51a25fc7-3cad-497d-8e2f-8d77011649cd"),
},
new object[]
{
],
[
Guid.Parse("bfb8f353-3b32-4a9e-bef6-24fe0b54bfb0"),
new DateTime(2024, 10, 20, 1, 32, 16, DateTimeKind.Utc),
Guid.Parse("bfb8f353-3b32-4a9e-bef6-b20f00195780"),
}
};
]
];
public static IEnumerable<object[]> GenerateCombCases = GuidSeedCases.Zip([
Guid.Parse("a58db474-43d8-42f1-b4ee-ae5600c90cc1"), // Expected Comb for each Guid Seed case
Guid.Parse("f776e6ee-511f-4352-bb28-ad2400b313c1"),
Guid.Parse("51a25fc7-3cad-497d-8e2f-8d77011649cd"),
Guid.Parse("bfb8f353-3b32-4a9e-bef6-b20f00195780"),
]).Select((zip) => new object[] { zip.Item1[0], zip.Item1[1], zip.Item2 });
[Theory]
[MemberData(nameof(GenerateCombCases))]
@ -72,7 +69,7 @@ public class CoreHelpersTests
}
[Theory]
[MemberData(nameof(GenerateCombCases))]
[MemberData(nameof(GuidSeedCases))]
public void DateFromComb_WithComb_Success(Guid inputGuid, DateTime inputTime)
{
var comb = CoreHelpers.GenerateComb(inputGuid, inputTime);