1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-05 05:00:19 -05:00

NotificationsHub Push Tests

This commit is contained in:
Justin Baur 2025-03-06 08:09:31 -05:00
parent 17da1c496e
commit 1d65e69200
No known key found for this signature in database
2 changed files with 640 additions and 3 deletions

View File

@ -32,20 +32,22 @@ public class NotificationHubPushNotificationService : IPushNotificationService
private readonly INotificationHubPool _notificationHubPool;
private readonly ILogger _logger;
private readonly IGlobalSettings _globalSettings;
private readonly TimeProvider _timeProvider;
public NotificationHubPushNotificationService(
IInstallationDeviceRepository installationDeviceRepository,
INotificationHubPool notificationHubPool,
IHttpContextAccessor httpContextAccessor,
ILogger<NotificationHubPushNotificationService> logger,
IGlobalSettings globalSettings)
IGlobalSettings globalSettings,
TimeProvider timeProvider)
{
_installationDeviceRepository = installationDeviceRepository;
_httpContextAccessor = httpContextAccessor;
_notificationHubPool = notificationHubPool;
_logger = logger;
_globalSettings = globalSettings;
_timeProvider = timeProvider;
if (globalSettings.Installation.Id == Guid.Empty)
{
logger.LogWarning("Installation ID is not set. Push notifications for installations will not work.");
@ -152,7 +154,7 @@ public class NotificationHubPushNotificationService : IPushNotificationService
private async Task PushUserAsync(Guid userId, PushType type, bool excludeCurrentContext = false)
{
var message = new UserPushNotification { UserId = userId, Date = DateTime.UtcNow };
var message = new UserPushNotification { UserId = userId, Date = _timeProvider.GetUtcNow().UtcDateTime };
await SendPayloadToUserAsync(userId, type, message, excludeCurrentContext);
}

View File

@ -1,15 +1,25 @@
#nullable enable
using System.Text.Json;
using System.Text.Json.Nodes;
using Bit.Core.Auth.Entities;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Models;
using Bit.Core.Models.Data;
using Bit.Core.NotificationCenter.Entities;
using Bit.Core.NotificationCenter.Enums;
using Bit.Core.NotificationHub;
using Bit.Core.Repositories;
using Bit.Core.Settings;
using Bit.Core.Test.NotificationCenter.AutoFixture;
using Bit.Core.Tools.Entities;
using Bit.Core.Vault.Entities;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Time.Testing;
using NSubstitute;
using Xunit;
@ -19,6 +29,10 @@ namespace Bit.Core.Test.NotificationHub;
[NotificationStatusCustomize]
public class NotificationHubPushNotificationServiceTests
{
private static readonly string _deviceIdentifier = "test_device_identifier";
private static readonly DateTime _now = DateTime.UtcNow;
private static readonly Guid _installationId = Guid.Parse("da73177b-513f-4444-b582-595c890e1022");
[Theory]
[BitAutoData]
[NotificationCustomize]
@ -496,6 +510,627 @@ public class NotificationHubPushNotificationServiceTests
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
}
[Fact]
public async Task PushSyncCipherCreateAsync_SendExpectedData()
{
var collectionId = Guid.NewGuid();
var userId = Guid.NewGuid();
var cipher = new Cipher
{
Id = Guid.NewGuid(),
UserId = userId,
RevisionDate = DateTime.UtcNow,
};
var expectedPayload = new JsonObject
{
["Id"] = cipher.Id,
["UserId"] = cipher.UserId,
["OrganizationId"] = cipher.OrganizationId,
["CollectionIds"] = new JsonArray(collectionId),
["RevisionDate"] = cipher.RevisionDate,
};
await VerifyNotificationAsync(
async sut => await sut.PushSyncCipherCreateAsync(cipher, [collectionId]),
PushType.SyncCipherCreate,
expectedPayload,
$"(template:payload_userId:{userId} && !deviceIdentifier:{_deviceIdentifier})"
);
}
[Fact]
public async Task PushSyncCipherUpdateAsync_SendExpectedData()
{
var collectionId = Guid.NewGuid();
var userId = Guid.NewGuid();
var cipher = new Cipher
{
Id = Guid.NewGuid(),
UserId = userId,
RevisionDate = DateTime.UtcNow,
};
var expectedPayload = new JsonObject
{
["Id"] = cipher.Id,
["UserId"] = cipher.UserId,
["OrganizationId"] = cipher.OrganizationId,
["CollectionIds"] = new JsonArray(collectionId),
["RevisionDate"] = cipher.RevisionDate,
};
await VerifyNotificationAsync(
async sut => await sut.PushSyncCipherUpdateAsync(cipher, [collectionId]),
PushType.SyncCipherUpdate,
expectedPayload,
$"(template:payload_userId:{userId} && !deviceIdentifier:{_deviceIdentifier})"
);
}
[Fact]
public async Task PushSyncCipherDeleteAsync_SendExpectedData()
{
var userId = Guid.NewGuid();
var cipher = new Cipher
{
Id = Guid.NewGuid(),
UserId = userId,
RevisionDate = DateTime.UtcNow,
};
var expectedPayload = new JsonObject
{
["Id"] = cipher.Id,
["UserId"] = cipher.UserId,
["OrganizationId"] = cipher.OrganizationId,
["CollectionIds"] = null,
["RevisionDate"] = cipher.RevisionDate,
};
await VerifyNotificationAsync(
async sut => await sut.PushSyncCipherDeleteAsync(cipher),
PushType.SyncLoginDelete,
expectedPayload,
$"(template:payload_userId:{userId} && !deviceIdentifier:{_deviceIdentifier})"
);
}
[Fact]
public async Task PushSyncFolderCreateAsync_SendExpectedData()
{
var userId = Guid.NewGuid();
var folder = new Folder
{
Id = Guid.NewGuid(),
UserId = userId,
RevisionDate = DateTime.UtcNow,
};
var expectedPayload = new JsonObject
{
["Id"] = folder.Id,
["UserId"] = folder.UserId,
["RevisionDate"] = folder.RevisionDate,
};
await VerifyNotificationAsync(
async sut => await sut.PushSyncFolderCreateAsync(folder),
PushType.SyncFolderCreate,
expectedPayload,
$"(template:payload_userId:{userId} && !deviceIdentifier:{_deviceIdentifier})"
);
}
[Fact]
public async Task PushSyncFolderUpdateAsync_SendExpectedData()
{
var userId = Guid.NewGuid();
var folder = new Folder
{
Id = Guid.NewGuid(),
UserId = userId,
RevisionDate = DateTime.UtcNow,
};
var expectedPayload = new JsonObject
{
["Id"] = folder.Id,
["UserId"] = folder.UserId,
["RevisionDate"] = folder.RevisionDate,
};
await VerifyNotificationAsync(
async sut => await sut.PushSyncFolderUpdateAsync(folder),
PushType.SyncFolderUpdate,
expectedPayload,
$"(template:payload_userId:{userId} && !deviceIdentifier:{_deviceIdentifier})"
);
}
[Fact]
public async Task PushSyncSendCreateAsync_SendExpectedData()
{
var userId = Guid.NewGuid();
var send = new Send
{
Id = Guid.NewGuid(),
UserId = userId,
RevisionDate = DateTime.UtcNow,
};
var expectedPayload = new JsonObject
{
["Id"] = send.Id,
["UserId"] = send.UserId,
["RevisionDate"] = send.RevisionDate,
};
await VerifyNotificationAsync(
async sut => await sut.PushSyncSendCreateAsync(send),
PushType.SyncSendCreate,
expectedPayload,
$"(template:payload_userId:{userId} && !deviceIdentifier:{_deviceIdentifier})"
);
}
[Fact]
public async Task PushAuthRequestAsync_SendExpectedData()
{
var userId = Guid.NewGuid();
var authRequest = new AuthRequest
{
Id = Guid.NewGuid(),
UserId = userId,
};
var expectedPayload = new JsonObject
{
["Id"] = authRequest.Id,
["UserId"] = authRequest.UserId,
};
await VerifyNotificationAsync(
async sut => await sut.PushAuthRequestAsync(authRequest),
PushType.AuthRequest,
expectedPayload,
$"(template:payload_userId:{userId} && !deviceIdentifier:{_deviceIdentifier})"
);
}
[Fact]
public async Task PushAuthRequestResponseAsync_SendExpectedData()
{
var userId = Guid.NewGuid();
var authRequest = new AuthRequest
{
Id = Guid.NewGuid(),
UserId = userId,
};
var expectedPayload = new JsonObject
{
["Id"] = authRequest.Id,
["UserId"] = authRequest.UserId,
};
await VerifyNotificationAsync(
async sut => await sut.PushAuthRequestResponseAsync(authRequest),
PushType.AuthRequestResponse,
expectedPayload,
$"(template:payload_userId:{userId} && !deviceIdentifier:{_deviceIdentifier})"
);
}
[Fact]
public async Task PushSyncSendUpdateAsync_SendExpectedData()
{
var userId = Guid.NewGuid();
var send = new Send
{
Id = Guid.NewGuid(),
UserId = userId,
RevisionDate = DateTime.UtcNow,
};
var expectedPayload = new JsonObject
{
["Id"] = send.Id,
["UserId"] = send.UserId,
["RevisionDate"] = send.RevisionDate,
};
await VerifyNotificationAsync(
async sut => await sut.PushSyncSendUpdateAsync(send),
PushType.SyncSendUpdate,
expectedPayload,
$"(template:payload_userId:{userId} && !deviceIdentifier:{_deviceIdentifier})"
);
}
[Fact]
public async Task PushSyncSendDeleteAsync_SendExpectedData()
{
var userId = Guid.NewGuid();
var send = new Send
{
Id = Guid.NewGuid(),
UserId = userId,
RevisionDate = DateTime.UtcNow,
};
var expectedPayload = new JsonObject
{
["Id"] = send.Id,
["UserId"] = send.UserId,
["RevisionDate"] = send.RevisionDate,
};
await VerifyNotificationAsync(
async sut => await sut.PushSyncSendDeleteAsync(send),
PushType.SyncSendDelete,
expectedPayload,
$"(template:payload_userId:{userId} && !deviceIdentifier:{_deviceIdentifier})"
);
}
[Fact]
public async Task PushSyncCiphersAsync_SendExpectedData()
{
var userId = Guid.NewGuid();
var expectedPayload = new JsonObject
{
["UserId"] = userId,
["Date"] = _now,
};
await VerifyNotificationAsync(
async sut => await sut.PushSyncCiphersAsync(userId),
PushType.SyncCiphers,
expectedPayload,
$"(template:payload_userId:{userId})"
);
}
[Fact]
public async Task PushSyncVaultAsync_SendExpectedData()
{
var userId = Guid.NewGuid();
var expectedPayload = new JsonObject
{
["UserId"] = userId,
["Date"] = _now,
};
await VerifyNotificationAsync(
async sut => await sut.PushSyncVaultAsync(userId),
PushType.SyncVault,
expectedPayload,
$"(template:payload_userId:{userId})"
);
}
[Fact]
public async Task PushSyncOrganizationsAsync_SendExpectedData()
{
var userId = Guid.NewGuid();
var expectedPayload = new JsonObject
{
["UserId"] = userId,
["Date"] = _now,
};
await VerifyNotificationAsync(
async sut => await sut.PushSyncOrganizationsAsync(userId),
PushType.SyncOrganizations,
expectedPayload,
$"(template:payload_userId:{userId})"
);
}
[Fact]
public async Task PushSyncOrgKeysAsync_SendExpectedData()
{
var userId = Guid.NewGuid();
var expectedPayload = new JsonObject
{
["UserId"] = userId,
["Date"] = _now,
};
await VerifyNotificationAsync(
async sut => await sut.PushSyncOrgKeysAsync(userId),
PushType.SyncOrgKeys,
expectedPayload,
$"(template:payload_userId:{userId})"
);
}
[Fact]
public async Task PushSyncSettingsAsync_SendExpectedData()
{
var userId = Guid.NewGuid();
var expectedPayload = new JsonObject
{
["UserId"] = userId,
["Date"] = _now,
};
await VerifyNotificationAsync(
async sut => await sut.PushSyncSettingsAsync(userId),
PushType.SyncSettings,
expectedPayload,
$"(template:payload_userId:{userId})"
);
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task PushLogOutAsync_SendExpectedData(bool excludeCurrentContext)
{
var userId = Guid.NewGuid();
var expectedPayload = new JsonObject
{
["UserId"] = userId,
["Date"] = _now,
};
var expectedTag = excludeCurrentContext
? $"(template:payload_userId:{userId} && !deviceIdentifier:{_deviceIdentifier})"
: $"(template:payload_userId:{userId})";
await VerifyNotificationAsync(
async sut => await sut.PushLogOutAsync(userId, excludeCurrentContext),
PushType.LogOut,
expectedPayload,
expectedTag
);
}
[Fact]
public async Task PushSyncFolderDeleteAsync_SendExpectedData()
{
var userId = Guid.NewGuid();
var folder = new Folder
{
Id = Guid.NewGuid(),
UserId = userId,
RevisionDate = DateTime.UtcNow,
};
var expectedPayload = new JsonObject
{
["Id"] = folder.Id,
["UserId"] = folder.UserId,
["RevisionDate"] = folder.RevisionDate,
};
await VerifyNotificationAsync(
async sut => await sut.PushSyncFolderDeleteAsync(folder),
PushType.SyncFolderDelete,
expectedPayload,
$"(template:payload_userId:{userId} && !deviceIdentifier:{_deviceIdentifier})"
);
}
[Theory]
[InlineData(true, null, null)]
[InlineData(false, "e8e08ce8-8a26-4a65-913a-ba1d8c478b2f", null)]
[InlineData(false, null, "2f53ee32-edf9-4169-b276-760fe92e03bf")]
public async Task PushNotificationAsync_SendExpectedData(bool global, string? userId, string? organizationId)
{
var notification = new Notification
{
Id = Guid.NewGuid(),
Priority = Priority.High,
Global = global,
ClientType = ClientType.All,
UserId = userId != null ? Guid.Parse(userId) : null,
OrganizationId = organizationId != null ? Guid.Parse(organizationId) : null,
Title = "My Title",
Body = "My Body",
CreationDate = DateTime.UtcNow.AddDays(-1),
RevisionDate = DateTime.UtcNow,
};
JsonNode? installationId = global ? _installationId : null;
var expectedPayload = new JsonObject
{
["Id"] = notification.Id,
["Priority"] = 3,
["Global"] = global,
["ClientType"] = 0,
["UserId"] = notification.UserId,
["OrganizationId"] = notification.OrganizationId,
["InstallationId"] = installationId,
["Title"] = notification.Title,
["Body"] = notification.Body,
["CreationDate"] = notification.CreationDate,
["RevisionDate"] = notification.RevisionDate,
["ReadDate"] = null,
["DeletedDate"] = null,
};
string expectedTag;
if (global)
{
expectedTag = $"(template:payload && installationId:{_installationId} && !deviceIdentifier:{_deviceIdentifier})";
}
else if (notification.OrganizationId.HasValue)
{
expectedTag = "(template:payload && organizationId:2f53ee32-edf9-4169-b276-760fe92e03bf && !deviceIdentifier:test_device_identifier)";
}
else
{
expectedTag = $"(template:payload_userId:{userId} && !deviceIdentifier:{_deviceIdentifier})";
}
await VerifyNotificationAsync(
async sut => await sut.PushNotificationAsync(notification),
PushType.Notification,
expectedPayload,
expectedTag
);
}
[Theory]
[InlineData(true, null, null)]
[InlineData(false, "e8e08ce8-8a26-4a65-913a-ba1d8c478b2f", null)]
[InlineData(false, null, "2f53ee32-edf9-4169-b276-760fe92e03bf")]
public async Task PushNotificationStatusAsync_SendExpectedData(bool global, string? userId, string? organizationId)
{
var notification = new Notification
{
Id = Guid.NewGuid(),
Priority = Priority.High,
Global = global,
ClientType = ClientType.All,
UserId = userId != null ? Guid.Parse(userId) : null,
OrganizationId = organizationId != null ? Guid.Parse(organizationId) : null,
Title = "My Title",
Body = "My Body",
CreationDate = DateTime.UtcNow.AddDays(-1),
RevisionDate = DateTime.UtcNow,
};
var notificationStatus = new NotificationStatus
{
ReadDate = DateTime.UtcNow.AddDays(-1),
DeletedDate = DateTime.UtcNow,
};
JsonNode? installationId = global ? _installationId : null;
var expectedPayload = new JsonObject
{
["Id"] = notification.Id,
["Priority"] = 3,
["Global"] = global,
["ClientType"] = 0,
["UserId"] = notification.UserId,
["OrganizationId"] = notification.OrganizationId,
["InstallationId"] = installationId,
["Title"] = notification.Title,
["Body"] = notification.Body,
["CreationDate"] = notification.CreationDate,
["RevisionDate"] = notification.RevisionDate,
["ReadDate"] = notificationStatus.ReadDate,
["DeletedDate"] = notificationStatus.DeletedDate,
};
string expectedTag;
if (global)
{
expectedTag = $"(template:payload && installationId:{_installationId} && !deviceIdentifier:{_deviceIdentifier})";
}
else if (notification.OrganizationId.HasValue)
{
expectedTag = "(template:payload && organizationId:2f53ee32-edf9-4169-b276-760fe92e03bf && !deviceIdentifier:test_device_identifier)";
}
else
{
expectedTag = $"(template:payload_userId:{userId} && !deviceIdentifier:{_deviceIdentifier})";
}
await VerifyNotificationAsync(
async sut => await sut.PushNotificationStatusAsync(notification, notificationStatus),
PushType.NotificationStatus,
expectedPayload,
expectedTag
);
}
private async Task VerifyNotificationAsync(Func<NotificationHubPushNotificationService, Task> test,
PushType type, JsonNode expectedPayload, string tag)
{
var installationDeviceRepository = Substitute.For<IInstallationDeviceRepository>();
var notificationHubPool = Substitute.For<INotificationHubPool>();
var notificationHubProxy = Substitute.For<INotificationHubProxy>();
notificationHubPool.AllClients
.Returns(notificationHubProxy);
var httpContextAccessor = Substitute.For<IHttpContextAccessor>();
var httpContext = new DefaultHttpContext();
var serviceCollection = new ServiceCollection();
var currentContext = Substitute.For<ICurrentContext>();
currentContext.DeviceIdentifier = _deviceIdentifier;
serviceCollection.AddSingleton(currentContext);
httpContext.RequestServices = serviceCollection.BuildServiceProvider();
httpContextAccessor.HttpContext
.Returns(httpContext);
var globalSettings = new Core.Settings.GlobalSettings();
globalSettings.Installation.Id = _installationId;
var fakeTimeProvider = new FakeTimeProvider();
fakeTimeProvider.SetUtcNow(_now);
var sut = new NotificationHubPushNotificationService(
installationDeviceRepository,
notificationHubPool,
httpContextAccessor,
NullLogger<NotificationHubPushNotificationService>.Instance,
globalSettings,
fakeTimeProvider
);
// Act
await test(sut);
// Assert
var calls = notificationHubProxy.ReceivedCalls();
var methodInfo = typeof(INotificationHubProxy).GetMethod(nameof(INotificationHubProxy.SendTemplateNotificationAsync));
var call = Assert.Single(calls, c => c.GetMethodInfo() == methodInfo);
var arguments = call.GetArguments();
var dictionaryArg = (Dictionary<string, string>)arguments[0]!;
var tagArg = (string)arguments[1]!;
Assert.Equal(2, dictionaryArg.Count);
Assert.True(dictionaryArg.TryGetValue("type", out var typeString));
Assert.True(byte.TryParse(typeString, out var typeByte));
Assert.Equal(type, (PushType)typeByte);
Assert.True(dictionaryArg.TryGetValue("payload", out var payloadString));
var actualPayloadNode = JsonNode.Parse(payloadString);
Assert.True(JsonNode.DeepEquals(expectedPayload, actualPayloadNode));
Assert.Equal(tag, tagArg);
}
private static NotificationPushNotification ToNotificationPushNotification(Notification notification,
NotificationStatus? notificationStatus, Guid? installationId) =>
new()