diff --git a/src/Core/Platform/Push/Services/NotificationsApiPushNotificationService.cs b/src/Core/Platform/Push/Services/NotificationsApiPushNotificationService.cs index 53a0de9a27..b7ba0c275b 100644 --- a/src/Core/Platform/Push/Services/NotificationsApiPushNotificationService.cs +++ b/src/Core/Platform/Push/Services/NotificationsApiPushNotificationService.cs @@ -24,12 +24,14 @@ public class NotificationsApiPushNotificationService : BaseIdentityClientService { private readonly IGlobalSettings _globalSettings; private readonly IHttpContextAccessor _httpContextAccessor; + private readonly TimeProvider _timeProvider; public NotificationsApiPushNotificationService( IHttpClientFactory httpFactory, GlobalSettings globalSettings, IHttpContextAccessor httpContextAccessor, - ILogger logger) + ILogger logger, + TimeProvider timeProvider) : base( httpFactory, globalSettings.BaseServiceUri.InternalNotifications, @@ -41,6 +43,7 @@ public class NotificationsApiPushNotificationService : BaseIdentityClientService { _globalSettings = globalSettings; _httpContextAccessor = httpContextAccessor; + _timeProvider = timeProvider; } public async Task PushSyncCipherCreateAsync(Cipher cipher, IEnumerable collectionIds) @@ -148,7 +151,7 @@ public class NotificationsApiPushNotificationService : BaseIdentityClientService var message = new UserPushNotification { UserId = userId, - Date = DateTime.UtcNow + Date = _timeProvider.GetUtcNow().UtcDateTime, }; await SendMessageAsync(type, message, excludeCurrentContext); diff --git a/test/Core.Test/Platform/Push/Services/NotificationsApiPushNotificationServiceTests.cs b/test/Core.Test/Platform/Push/Services/NotificationsApiPushNotificationServiceTests.cs index 07f348a5ba..587afbbd2a 100644 --- a/test/Core.Test/Platform/Push/Services/NotificationsApiPushNotificationServiceTests.cs +++ b/test/Core.Test/Platform/Push/Services/NotificationsApiPushNotificationServiceTests.cs @@ -1,41 +1,383 @@ -using Bit.Core.Platform.Push; -using Bit.Core.Settings; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; -using NSubstitute; -using Xunit; +using System.Text.Json.Nodes; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.Auth.Entities; +using Bit.Core.NotificationCenter.Entities; +using Bit.Core.Platform.Push; +using Bit.Core.Tools.Entities; +using Bit.Core.Vault.Entities; +using Microsoft.Extensions.Logging.Abstractions; namespace Bit.Core.Test.Platform.Push.Services; -public class NotificationsApiPushNotificationServiceTests +public class NotificationsApiPushNotificationServiceTests : PushTestBase { - private readonly NotificationsApiPushNotificationService _sut; - - private readonly IHttpClientFactory _httpFactory; - private readonly GlobalSettings _globalSettings; - private readonly IHttpContextAccessor _httpContextAccessor; - private readonly ILogger _logger; - public NotificationsApiPushNotificationServiceTests() { - _httpFactory = Substitute.For(); - _globalSettings = new GlobalSettings(); - _httpContextAccessor = Substitute.For(); - _logger = Substitute.For>(); + GlobalSettings.BaseServiceUri.InternalNotifications = "https://localhost:7777"; + GlobalSettings.BaseServiceUri.InternalIdentity = "https://localhost:8888"; + } - _sut = new NotificationsApiPushNotificationService( - _httpFactory, - _globalSettings, - _httpContextAccessor, - _logger + protected override string ExpectedClientUrl() => "https://localhost:7777/send"; + + protected override IPushNotificationService CreateService() + { + return new NotificationsApiPushNotificationService( + HttpClientFactory, + GlobalSettings, + HttpContextAccessor, + NullLogger.Instance, + FakeTimeProvider ); } - // 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() + protected override JsonNode GetPushSyncCipherCreatePayload(Cipher cipher, Guid collectionId) { - Assert.NotNull(_sut); + return new JsonObject + { + ["Type"] = 1, + ["Payload"] = new JsonObject + { + ["Id"] = cipher.Id, + ["UserId"] = cipher.UserId, + ["OrganizationId"] = null, + ["CollectionIds"] = new JsonArray(collectionId), + ["RevisionDate"] = cipher.RevisionDate, + }, + ["ContextId"] = DeviceIdentifier, + }; + } + protected override JsonNode GetPushSyncCipherUpdatePayload(Cipher cipher, Guid collectionId) + { + return new JsonObject + { + ["Type"] = 0, + ["Payload"] = new JsonObject + { + ["Id"] = cipher.Id, + ["UserId"] = cipher.UserId, + ["OrganizationId"] = null, + ["CollectionIds"] = new JsonArray(collectionId), + ["RevisionDate"] = cipher.RevisionDate, + }, + ["ContextId"] = DeviceIdentifier, + }; + } + protected override JsonNode GetPushSyncCipherDeletePayload(Cipher cipher) + { + return new JsonObject + { + ["Type"] = 2, + ["Payload"] = new JsonObject + { + ["Id"] = cipher.Id, + ["UserId"] = cipher.UserId, + ["OrganizationId"] = null, + ["CollectionIds"] = null, + ["RevisionDate"] = cipher.RevisionDate, + }, + ["ContextId"] = DeviceIdentifier, + }; + } + + protected override JsonNode GetPushSyncFolderCreatePayload(Folder folder) + { + return new JsonObject + { + ["Type"] = 7, + ["Payload"] = new JsonObject + { + ["Id"] = folder.Id, + ["UserId"] = folder.UserId, + ["RevisionDate"] = folder.RevisionDate, + }, + ["ContextId"] = DeviceIdentifier, + }; + } + + protected override JsonNode GetPushSyncFolderUpdatePayload(Folder folder) + { + return new JsonObject + { + ["Type"] = 8, + ["Payload"] = new JsonObject + { + ["Id"] = folder.Id, + ["UserId"] = folder.UserId, + ["RevisionDate"] = folder.RevisionDate, + }, + ["ContextId"] = DeviceIdentifier, + }; + } + + protected override JsonNode GetPushSyncFolderDeletePayload(Folder folder) + { + return new JsonObject + { + ["Type"] = 3, + ["Payload"] = new JsonObject + { + ["Id"] = folder.Id, + ["UserId"] = folder.UserId, + ["RevisionDate"] = folder.RevisionDate, + }, + ["ContextId"] = DeviceIdentifier, + }; + } + + protected override JsonNode GetPushSyncCiphersPayload(Guid userId) + { + return new JsonObject + { + ["Type"] = 4, + ["Payload"] = new JsonObject + { + ["UserId"] = userId, + ["Date"] = FakeTimeProvider.GetUtcNow().UtcDateTime, + }, + ["ContextId"] = null, + }; + } + + protected override JsonNode GetPushSyncVaultPayload(Guid userId) + { + return new JsonObject + { + ["Type"] = 5, + ["Payload"] = new JsonObject + { + ["UserId"] = userId, + ["Date"] = FakeTimeProvider.GetUtcNow().UtcDateTime, + }, + ["ContextId"] = null, + }; + } + + protected override JsonNode GetPushSyncOrganizationsPayload(Guid userId) + { + return new JsonObject + { + ["Type"] = 17, + ["Payload"] = new JsonObject + { + ["UserId"] = userId, + ["Date"] = FakeTimeProvider.GetUtcNow().UtcDateTime, + }, + ["ContextId"] = null, + }; + } + + protected override JsonNode GetPushSyncOrgKeysPayload(Guid userId) + { + return new JsonObject + { + ["Type"] = 6, + ["Payload"] = new JsonObject + { + ["UserId"] = userId, + ["Date"] = FakeTimeProvider.GetUtcNow().UtcDateTime, + }, + ["ContextId"] = null, + }; + } + + protected override JsonNode GetPushSyncSettingsPayload(Guid userId) + { + return new JsonObject + { + ["Type"] = 10, + ["Payload"] = new JsonObject + { + ["UserId"] = userId, + ["Date"] = FakeTimeProvider.GetUtcNow().UtcDateTime, + }, + ["ContextId"] = null, + }; + } + + protected override JsonNode GetPushLogOutPayload(Guid userId, bool excludeCurrentContext) + { + JsonNode? contextId = excludeCurrentContext ? DeviceIdentifier : null; + + return new JsonObject + { + ["Type"] = 11, + ["Payload"] = new JsonObject + { + ["UserId"] = userId, + ["Date"] = FakeTimeProvider.GetUtcNow().UtcDateTime, + }, + ["ContextId"] = contextId, + }; + } + + protected override JsonNode GetPushSendCreatePayload(Send send) + { + return new JsonObject + { + ["Type"] = 12, + ["Payload"] = new JsonObject + { + ["Id"] = send.Id, + ["UserId"] = send.UserId, + ["RevisionDate"] = send.RevisionDate, + }, + ["ContextId"] = null, + }; + } + + protected override JsonNode GetPushSendUpdatePayload(Send send) + { + return new JsonObject + { + ["Type"] = 13, + ["Payload"] = new JsonObject + { + ["Id"] = send.Id, + ["UserId"] = send.UserId, + ["RevisionDate"] = send.RevisionDate, + }, + ["ContextId"] = null, + }; + } + + protected override JsonNode GetPushSendDeletePayload(Send send) + { + return new JsonObject + { + ["Type"] = 14, + ["Payload"] = new JsonObject + { + ["Id"] = send.Id, + ["UserId"] = send.UserId, + ["RevisionDate"] = send.RevisionDate, + }, + ["ContextId"] = null, + }; + } + + protected override JsonNode GetPushAuthRequestPayload(AuthRequest authRequest) + { + return new JsonObject + { + ["Type"] = 15, + ["Payload"] = new JsonObject + { + ["Id"] = authRequest.Id, + ["UserId"] = authRequest.UserId, + }, + ["ContextId"] = DeviceIdentifier, + }; + } + + protected override JsonNode GetPushAuthRequestResponsePayload(AuthRequest authRequest) + { + return new JsonObject + { + ["Type"] = 16, + ["Payload"] = new JsonObject + { + ["Id"] = authRequest.Id, + ["UserId"] = authRequest.UserId, + }, + ["ContextId"] = DeviceIdentifier, + }; + } + + protected override JsonNode GetPushNotificationResponsePayload(Notification notification, Guid? userId, Guid? organizationId) + { + JsonNode? installationId = notification.Global ? GlobalSettings.Installation.Id : null; + + return new JsonObject + { + ["Type"] = 20, + ["Payload"] = new JsonObject + { + ["Id"] = notification.Id, + ["Priority"] = 3, + ["Global"] = notification.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, + }, + ["ContextId"] = DeviceIdentifier, + }; + } + + protected override JsonNode GetPushNotificationStatusResponsePayload(Notification notification, NotificationStatus notificationStatus, Guid? userId, Guid? organizationId) + { + JsonNode? installationId = notification.Global ? GlobalSettings.Installation.Id : null; + + return new JsonObject + { + ["Type"] = 21, + ["Payload"] = new JsonObject + { + ["Id"] = notification.Id, + ["Priority"] = 3, + ["Global"] = notification.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, + }, + ["ContextId"] = DeviceIdentifier, + }; + } + + protected override JsonNode GetPushSyncOrganizationStatusResponsePayload(Organization organization) + { + return new JsonObject + { + ["Type"] = 18, + ["Payload"] = new JsonObject + { + ["OrganizationId"] = organization.Id, + ["Enabled"] = organization.Enabled, + }, + ["ContextId"] = null, + }; + } + + protected override JsonNode GetPushSyncOrganizationCollectionManagementSettingsResponsePayload(Organization organization) + { + return new JsonObject + { + ["Type"] = 19, + ["Payload"] = new JsonObject + { + ["OrganizationId"] = organization.Id, + ["LimitCollectionCreation"] = organization.LimitCollectionCreation, + ["LimitCollectionDeletion"] = organization.LimitCollectionDeletion, + ["LimitItemDeletion"] = organization.LimitItemDeletion, + }, + ["ContextId"] = null, + }; + } + + protected override JsonNode GetPushPendingSecurityTasksResponsePayload(Guid userId) + { + return new JsonObject + { + ["Type"] = 22, + ["Payload"] = new JsonObject + { + ["UserId"] = userId, + ["Date"] = FakeTimeProvider.GetUtcNow().UtcDateTime, + }, + ["ContextId"] = null, + }; } } diff --git a/test/Core.Test/Platform/Push/Services/PushTestBase.cs b/test/Core.Test/Platform/Push/Services/PushTestBase.cs new file mode 100644 index 0000000000..d9b77f9980 --- /dev/null +++ b/test/Core.Test/Platform/Push/Services/PushTestBase.cs @@ -0,0 +1,491 @@ +// TODO: Move to own file +using System.IdentityModel.Tokens.Jwt; +using System.Net; +using System.Net.Http.Json; +using System.Text.Json; +using System.Text.Json.Nodes; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.Auth.Entities; +using Bit.Core.Context; +using Bit.Core.Enums; +using Bit.Core.NotificationCenter.Entities; +using Bit.Core.NotificationCenter.Enums; +using Bit.Core.Platform.Push; +using Bit.Core.Settings; +using Bit.Core.Tools.Entities; +using Bit.Core.Vault.Entities; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Time.Testing; +using NSubstitute; +using RichardSzalay.MockHttp; +using Xunit; + +public abstract class PushTestBase +{ + protected static readonly string DeviceIdentifier = "test_device_identifier"; + + protected readonly MockHttpMessageHandler MockClient = new(); + protected readonly MockHttpMessageHandler MockIdentityClient = new(); + + protected readonly IHttpClientFactory HttpClientFactory; + protected readonly GlobalSettings GlobalSettings; + protected readonly IHttpContextAccessor HttpContextAccessor; + protected readonly FakeTimeProvider FakeTimeProvider; + + public PushTestBase() + { + HttpClientFactory = Substitute.For(); + + // Mock HttpClient + HttpClientFactory.CreateClient("client") + .Returns(new HttpClient(MockClient)); + + HttpClientFactory.CreateClient("identity") + .Returns(new HttpClient(MockIdentityClient)); + + GlobalSettings = new GlobalSettings(); + HttpContextAccessor = Substitute.For(); + + FakeTimeProvider = new FakeTimeProvider(); + + FakeTimeProvider.SetUtcNow(DateTimeOffset.UtcNow); + } + + protected abstract IPushNotificationService CreateService(); + + protected abstract string ExpectedClientUrl(); + + protected abstract JsonNode GetPushSyncCipherCreatePayload(Cipher cipher, Guid collectionId); + protected abstract JsonNode GetPushSyncCipherUpdatePayload(Cipher cipher, Guid collectionId); + protected abstract JsonNode GetPushSyncCipherDeletePayload(Cipher cipher); + protected abstract JsonNode GetPushSyncFolderCreatePayload(Folder folder); + protected abstract JsonNode GetPushSyncFolderUpdatePayload(Folder folder); + protected abstract JsonNode GetPushSyncFolderDeletePayload(Folder folder); + protected abstract JsonNode GetPushSyncCiphersPayload(Guid userId); + protected abstract JsonNode GetPushSyncVaultPayload(Guid userId); + protected abstract JsonNode GetPushSyncOrganizationsPayload(Guid userId); + protected abstract JsonNode GetPushSyncOrgKeysPayload(Guid userId); + protected abstract JsonNode GetPushSyncSettingsPayload(Guid userId); + protected abstract JsonNode GetPushLogOutPayload(Guid userId, bool excludeCurrentContext); + protected abstract JsonNode GetPushSendCreatePayload(Send send); + protected abstract JsonNode GetPushSendUpdatePayload(Send send); + protected abstract JsonNode GetPushSendDeletePayload(Send send); + protected abstract JsonNode GetPushAuthRequestPayload(AuthRequest authRequest); + protected abstract JsonNode GetPushAuthRequestResponsePayload(AuthRequest authRequest); + protected abstract JsonNode GetPushNotificationResponsePayload(Notification notification, Guid? userId, Guid? organizationId); + protected abstract JsonNode GetPushNotificationStatusResponsePayload(Notification notification, NotificationStatus notificationStatus, Guid? userId, Guid? organizationId); + protected abstract JsonNode GetPushSyncOrganizationStatusResponsePayload(Organization organization); + protected abstract JsonNode GetPushSyncOrganizationCollectionManagementSettingsResponsePayload(Organization organization); + protected abstract JsonNode GetPushPendingSecurityTasksResponsePayload(Guid userId); + + [Fact] + public async Task PushSyncCipherCreateAsync_SendsExpectedResponse() + { + var collectionId = Guid.NewGuid(); + + var cipher = new Cipher + { + Id = Guid.NewGuid(), + UserId = Guid.NewGuid(), + OrganizationId = null, + RevisionDate = DateTime.UtcNow, + }; + + await VerifyNotificationAsync( + async sut => await sut.PushSyncCipherCreateAsync(cipher, [collectionId]), + GetPushSyncCipherCreatePayload(cipher, collectionId) + ); + } + + [Fact] + public async Task PushSyncCipherUpdateAsync_SendsExpectedResponse() + { + var collectionId = Guid.NewGuid(); + + var cipher = new Cipher + { + Id = Guid.NewGuid(), + UserId = Guid.NewGuid(), + OrganizationId = null, + RevisionDate = DateTime.UtcNow, + }; + + await VerifyNotificationAsync( + async sut => await sut.PushSyncCipherUpdateAsync(cipher, [collectionId]), + GetPushSyncCipherUpdatePayload(cipher, collectionId) + ); + } + + [Fact] + public async Task PushSyncCipherDeleteAsync_SendsExpectedResponse() + { + var collectionId = Guid.NewGuid(); + + var cipher = new Cipher + { + Id = Guid.NewGuid(), + UserId = Guid.NewGuid(), + OrganizationId = null, + RevisionDate = DateTime.UtcNow, + }; + + await VerifyNotificationAsync( + async sut => await sut.PushSyncCipherDeleteAsync(cipher), + GetPushSyncCipherDeletePayload(cipher) + ); + } + + [Fact] + public async Task PushSyncFolderCreateAsync_SendsExpectedResponse() + { + var folder = new Folder + { + Id = Guid.NewGuid(), + UserId = Guid.NewGuid(), + RevisionDate = DateTime.UtcNow, + }; + + await VerifyNotificationAsync( + async sut => await sut.PushSyncFolderCreateAsync(folder), + GetPushSyncFolderCreatePayload(folder) + ); + } + + [Fact] + public async Task PushSyncFolderUpdateAsync_SendsExpectedResponse() + { + var collectionId = Guid.NewGuid(); + + var folder = new Folder + { + Id = Guid.NewGuid(), + UserId = Guid.NewGuid(), + RevisionDate = DateTime.UtcNow, + }; + + await VerifyNotificationAsync( + async sut => await sut.PushSyncFolderUpdateAsync(folder), + GetPushSyncFolderUpdatePayload(folder) + ); + } + + [Fact] + public async Task PushSyncFolderDeleteAsync_SendsExpectedResponse() + { + var collectionId = Guid.NewGuid(); + + var folder = new Folder + { + Id = Guid.NewGuid(), + UserId = Guid.NewGuid(), + RevisionDate = DateTime.UtcNow, + }; + + await VerifyNotificationAsync( + async sut => await sut.PushSyncFolderDeleteAsync(folder), + GetPushSyncFolderDeletePayload(folder) + ); + } + + [Fact] + public async Task PushSyncCiphersAsync_SendsExpectedResponse() + { + var userId = Guid.NewGuid(); + + await VerifyNotificationAsync( + async sut => await sut.PushSyncCiphersAsync(userId), + GetPushSyncCiphersPayload(userId) + ); + } + + [Fact] + public async Task PushSyncVaultAsync_SendsExpectedResponse() + { + var userId = Guid.NewGuid(); + + await VerifyNotificationAsync( + async sut => await sut.PushSyncVaultAsync(userId), + GetPushSyncVaultPayload(userId) + ); + } + + [Fact] + public async Task PushSyncOrganizationsAsync_SendsExpectedResponse() + { + var userId = Guid.NewGuid(); + + await VerifyNotificationAsync( + async sut => await sut.PushSyncOrganizationsAsync(userId), + GetPushSyncOrganizationsPayload(userId) + ); + } + + [Fact] + public async Task PushSyncOrgKeysAsync_SendsExpectedResponse() + { + var userId = Guid.NewGuid(); + + await VerifyNotificationAsync( + async sut => await sut.PushSyncOrgKeysAsync(userId), + GetPushSyncOrgKeysPayload(userId) + ); + } + + [Fact] + public async Task PushSyncSettingsAsync_SendsExpectedResponse() + { + var userId = Guid.NewGuid(); + + await VerifyNotificationAsync( + async sut => await sut.PushSyncSettingsAsync(userId), + GetPushSyncSettingsPayload(userId) + ); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task PushLogOutAsync_SendsExpectedResponse(bool excludeCurrentContext) + { + var userId = Guid.NewGuid(); + + await VerifyNotificationAsync( + async sut => await sut.PushLogOutAsync(userId, excludeCurrentContext), + GetPushLogOutPayload(userId, excludeCurrentContext) + ); + } + + [Fact] + public async Task PushSyncSendCreateAsync_SendsExpectedResponse() + { + var send = new Send + { + Id = Guid.NewGuid(), + UserId = Guid.NewGuid(), + RevisionDate = DateTime.UtcNow, + }; + + await VerifyNotificationAsync( + async sut => await sut.PushSyncSendCreateAsync(send), + GetPushSendCreatePayload(send) + ); + } + + [Fact] + public async Task PushSyncSendUpdateAsync_SendsExpectedResponse() + { + var send = new Send + { + Id = Guid.NewGuid(), + UserId = Guid.NewGuid(), + RevisionDate = DateTime.UtcNow, + }; + + await VerifyNotificationAsync( + async sut => await sut.PushSyncSendUpdateAsync(send), + GetPushSendUpdatePayload(send) + ); + } + + [Fact] + public async Task PushSyncSendDeleteAsync_SendsExpectedResponse() + { + var send = new Send + { + Id = Guid.NewGuid(), + UserId = Guid.NewGuid(), + RevisionDate = DateTime.UtcNow, + }; + + await VerifyNotificationAsync( + async sut => await sut.PushSyncSendDeleteAsync(send), + GetPushSendDeletePayload(send) + ); + } + + [Fact] + public async Task PushAuthRequestAsync_SendsExpectedResponse() + { + var authRequest = new AuthRequest + { + Id = Guid.NewGuid(), + UserId = Guid.NewGuid(), + }; + + await VerifyNotificationAsync( + async sut => await sut.PushAuthRequestAsync(authRequest), + GetPushAuthRequestPayload(authRequest) + ); + } + + [Fact] + public async Task PushAuthRequestResponseAsync_SendsExpectedResponse() + { + var authRequest = new AuthRequest + { + Id = Guid.NewGuid(), + UserId = Guid.NewGuid(), + }; + + await VerifyNotificationAsync( + async sut => await sut.PushAuthRequestResponseAsync(authRequest), + GetPushAuthRequestResponsePayload(authRequest) + ); + } + + [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_SendsExpectedResponse(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, + }; + + await VerifyNotificationAsync( + async sut => await sut.PushNotificationAsync(notification), + GetPushNotificationResponsePayload(notification, notification.UserId, notification.OrganizationId) + ); + } + + [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_SendsExpectedResponse(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, + DeletedDate = DateTime.UtcNow, + }; + + await VerifyNotificationAsync( + async sut => await sut.PushNotificationStatusAsync(notification, notificationStatus), + GetPushNotificationStatusResponsePayload(notification, notificationStatus, notification.UserId, notification.OrganizationId) + ); + } + + [Fact] + public async Task PushSyncOrganizationStatusAsync_SendsExpectedResponse() + { + var organization = new Organization + { + Id = Guid.NewGuid(), + Enabled = true, + }; + + await VerifyNotificationAsync( + async sut => await sut.PushSyncOrganizationStatusAsync(organization), + GetPushSyncOrganizationStatusResponsePayload(organization) + ); + } + + [Fact] + public async Task PushSyncOrganizationCollectionManagementSettingsAsync_SendsExpectedResponse() + { + var organization = new Organization + { + Id = Guid.NewGuid(), + Enabled = true, + LimitCollectionCreation = true, + LimitCollectionDeletion = true, + LimitItemDeletion = true, + }; + + await VerifyNotificationAsync( + async sut => await sut.PushSyncOrganizationCollectionManagementSettingsAsync(organization), + GetPushSyncOrganizationCollectionManagementSettingsResponsePayload(organization) + ); + } + + [Fact] + public async Task PushPendingSecurityTasksAsync_SendsExpectedResponse() + { + var userId = Guid.NewGuid(); + + await VerifyNotificationAsync( + async sut => await sut.PushPendingSecurityTasksAsync(userId), + GetPushPendingSecurityTasksResponsePayload(userId) + ); + } + + private async Task VerifyNotificationAsync( + Func test, + JsonNode expectedRequestBody + ) + { + var httpContext = new DefaultHttpContext(); + + var serviceCollection = new ServiceCollection(); + var currentContext = Substitute.For(); + currentContext.DeviceIdentifier = DeviceIdentifier; + serviceCollection.AddSingleton(currentContext); + + httpContext.RequestServices = serviceCollection.BuildServiceProvider(); + + HttpContextAccessor.HttpContext + .Returns(httpContext); + + var connectTokenRequest = MockIdentityClient + .Expect(HttpMethod.Post, "https://localhost:8888/connect/token") + .Respond(HttpStatusCode.OK, JsonContent.Create(new + { + access_token = CreateAccessToken(DateTime.UtcNow.AddDays(1)), + })); + + var clientRequest = MockClient + .Expect(HttpMethod.Post, ExpectedClientUrl()) + .With(request => + { + if (request.Content is not JsonContent jsonContent) + { + return false; + } + + // TODO: What options? + var actualString = JsonSerializer.Serialize(jsonContent.Value); + var actualNode = JsonNode.Parse(actualString); + + return JsonNode.DeepEquals(actualNode, expectedRequestBody); + }) + .Respond(HttpStatusCode.OK); + + await test(CreateService()); + + Assert.Equal(1, MockClient.GetMatchCount(clientRequest)); + } + + protected static string CreateAccessToken(DateTime expirationTime) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var token = new JwtSecurityToken(expires: expirationTime); + return tokenHandler.WriteToken(token); + } +} diff --git a/test/Core.Test/Platform/Push/Services/RelayPushNotificationServiceTests.cs b/test/Core.Test/Platform/Push/Services/RelayPushNotificationServiceTests.cs index b0756815e1..c554484f98 100644 --- a/test/Core.Test/Platform/Push/Services/RelayPushNotificationServiceTests.cs +++ b/test/Core.Test/Platform/Push/Services/RelayPushNotificationServiceTests.cs @@ -1,102 +1,92 @@ #nullable enable -using System.IdentityModel.Tokens.Jwt; -using System.Net; -using System.Net.Http.Json; -using System.Text.Json; using System.Text.Json.Nodes; using Bit.Core.AdminConsole.Entities; using Bit.Core.Auth.Entities; -using Bit.Core.Context; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.NotificationCenter.Entities; -using Bit.Core.NotificationCenter.Enums; using Bit.Core.Platform.Push.Internal; using Bit.Core.Repositories; using Bit.Core.Settings; using Bit.Core.Tools.Entities; using Bit.Core.Vault.Entities; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Time.Testing; using NSubstitute; -using RichardSzalay.MockHttp; using Xunit; namespace Bit.Core.Test.Platform.Push.Services; -public class RelayPushNotificationServiceTests +public class RelayPushNotificationServiceTests : PushTestBase { private static readonly Guid _deviceId = Guid.Parse("c4730f80-caaa-4772-97bd-5c0d23a2baa3"); - private static readonly string _deviceIdentifier = "test_device_identifier"; - - private readonly RelayPushNotificationService _sut; - - private readonly MockHttpMessageHandler _mockPushClient = new(); - private readonly MockHttpMessageHandler _mockIdentityClient = new(); - - private readonly IHttpClientFactory _httpFactory; private readonly IDeviceRepository _deviceRepository; - private readonly GlobalSettings _globalSettings; - private readonly IHttpContextAccessor _httpContextAccessor; - private readonly ILogger _logger; - private readonly FakeTimeProvider _fakeTimeProvider; public RelayPushNotificationServiceTests() { - _httpFactory = Substitute.For(); - - // Mock HttpClient - _httpFactory.CreateClient("client") - .Returns(new HttpClient(_mockPushClient)); - - _httpFactory.CreateClient("identity") - .Returns(new HttpClient(_mockIdentityClient)); - _deviceRepository = Substitute.For(); - _globalSettings = new GlobalSettings(); - _httpContextAccessor = Substitute.For(); - _logger = Substitute.For>(); - _globalSettings.PushRelayBaseUri = "https://localhost:7777"; - _globalSettings.Installation.Id = Guid.Parse("478c608a-99fd-452a-94f0-af271654e6ee"); - _globalSettings.Installation.IdentityUri = "https://localhost:8888"; + _deviceRepository.GetByIdentifierAsync(DeviceIdentifier) + .Returns(new Device + { + Id = _deviceId, + }); - _fakeTimeProvider = new FakeTimeProvider(); + GlobalSettings.PushRelayBaseUri = "https://localhost:7777"; + GlobalSettings.Installation.Id = Guid.Parse("478c608a-99fd-452a-94f0-af271654e6ee"); + GlobalSettings.Installation.IdentityUri = "https://localhost:8888"; + } - _fakeTimeProvider.SetUtcNow(DateTimeOffset.UtcNow); - - _sut = new RelayPushNotificationService( - _httpFactory, + protected override RelayPushNotificationService CreateService() + { + return new RelayPushNotificationService( + HttpClientFactory, _deviceRepository, - _globalSettings, - _httpContextAccessor, - _logger, - _fakeTimeProvider + GlobalSettings, + HttpContextAccessor, + NullLogger.Instance, + FakeTimeProvider + ); + } + + protected override string ExpectedClientUrl() => "https://localhost:7777/push/send"; + + [Fact] + public async Task SendPayloadToInstallationAsync_ThrowsNotImplementedException() + { + var sut = CreateService(); + await Assert.ThrowsAsync( + async () => await sut.SendPayloadToInstallationAsync("installation_id", PushType.AuthRequest, new {}, null) ); } [Fact] - public async Task PushSyncCipherCreateAsync_SendsExpectedResponse() + public async Task SendPayloadToUserAsync_ThrowsNotImplementedException() { - var collectionId = Guid.NewGuid(); + var sut = CreateService(); + await Assert.ThrowsAsync( + async () => await sut.SendPayloadToUserAsync("user_id", PushType.AuthRequest, new {}, null) + ); + } - var cipher = new Cipher - { - Id = Guid.NewGuid(), - UserId = Guid.NewGuid(), - OrganizationId = null, - RevisionDate = DateTime.UtcNow, - }; + [Fact] + public async Task SendPayloadToOrganizationAsync_ThrowsNotImplementedException() + { + var sut = CreateService(); + await Assert.ThrowsAsync( + async () => await sut.SendPayloadToOrganizationAsync("organization_id", PushType.AuthRequest, new {}, null) + ); + } - var expectedPayload = new JsonObject + protected override JsonNode GetPushSyncCipherCreatePayload(Cipher cipher, Guid collectionIds) + { + return new JsonObject { ["UserId"] = cipher.UserId, ["OrganizationId"] = null, ["DeviceId"] = _deviceId, - ["Identifier"] = _deviceIdentifier, + ["Identifier"] = DeviceIdentifier, ["Type"] = 1, ["Payload"] = new JsonObject { @@ -111,32 +101,16 @@ public class RelayPushNotificationServiceTests ["ClientType"] = null, ["InstallationId"] = null, }; - - await VerifyNotificationAsync( - async sut => await sut.PushSyncCipherCreateAsync(cipher, [collectionId]), - expectedPayload - ); } - [Fact] - public async Task PushSyncCipherUpdateAsync_SendsExpectedResponse() + protected override JsonNode GetPushSyncCipherUpdatePayload(Cipher cipher, Guid collectionIds) { - var collectionId = Guid.NewGuid(); - - var cipher = new Cipher - { - Id = Guid.NewGuid(), - UserId = Guid.NewGuid(), - OrganizationId = null, - RevisionDate = DateTime.UtcNow, - }; - - var expectedPayload = new JsonObject + return new JsonObject { ["UserId"] = cipher.UserId, ["OrganizationId"] = null, ["DeviceId"] = _deviceId, - ["Identifier"] = _deviceIdentifier, + ["Identifier"] = DeviceIdentifier, ["Type"] = 0, ["Payload"] = new JsonObject { @@ -151,32 +125,16 @@ public class RelayPushNotificationServiceTests ["ClientType"] = null, ["InstallationId"] = null, }; - - await VerifyNotificationAsync( - async sut => await sut.PushSyncCipherUpdateAsync(cipher, [collectionId]), - expectedPayload - ); } - [Fact] - public async Task PushSyncCipherDeleteAsync_SendsExpectedResponse() + protected override JsonNode GetPushSyncCipherDeletePayload(Cipher cipher) { - var collectionId = Guid.NewGuid(); - - var cipher = new Cipher - { - Id = Guid.NewGuid(), - UserId = Guid.NewGuid(), - OrganizationId = null, - RevisionDate = DateTime.UtcNow, - }; - - var expectedPayload = new JsonObject + return new JsonObject { ["UserId"] = cipher.UserId, ["OrganizationId"] = null, ["DeviceId"] = _deviceId, - ["Identifier"] = _deviceIdentifier, + ["Identifier"] = DeviceIdentifier, ["Type"] = 2, ["Payload"] = new JsonObject { @@ -189,31 +147,16 @@ public class RelayPushNotificationServiceTests ["ClientType"] = null, ["InstallationId"] = null, }; - - await VerifyNotificationAsync( - async sut => await sut.PushSyncCipherDeleteAsync(cipher), - expectedPayload - ); } - [Fact] - public async Task PushSyncFolderCreateAsync_SendsExpectedResponse() + protected override JsonNode GetPushSyncFolderCreatePayload(Folder folder) { - var collectionId = Guid.NewGuid(); - - var folder = new Folder - { - Id = Guid.NewGuid(), - UserId = Guid.NewGuid(), - RevisionDate = DateTime.UtcNow, - }; - - var expectedPayload = new JsonObject + return new JsonObject { ["UserId"] = folder.UserId, ["OrganizationId"] = null, ["DeviceId"] = _deviceId, - ["Identifier"] = _deviceIdentifier, + ["Identifier"] = DeviceIdentifier, ["Type"] = 7, ["Payload"] = new JsonObject { @@ -224,31 +167,16 @@ public class RelayPushNotificationServiceTests ["ClientType"] = null, ["InstallationId"] = null, }; - - await VerifyNotificationAsync( - async sut => await sut.PushSyncFolderCreateAsync(folder), - expectedPayload - ); } - [Fact] - public async Task PushSyncFolderUpdateAsync_SendsExpectedResponse() + protected override JsonNode GetPushSyncFolderUpdatePayload(Folder folder) { - var collectionId = Guid.NewGuid(); - - var folder = new Folder - { - Id = Guid.NewGuid(), - UserId = Guid.NewGuid(), - RevisionDate = DateTime.UtcNow, - }; - - var expectedPayload = new JsonObject + return new JsonObject { ["UserId"] = folder.UserId, ["OrganizationId"] = null, ["DeviceId"] = _deviceId, - ["Identifier"] = _deviceIdentifier, + ["Identifier"] = DeviceIdentifier, ["Type"] = 8, ["Payload"] = new JsonObject { @@ -259,31 +187,16 @@ public class RelayPushNotificationServiceTests ["ClientType"] = null, ["InstallationId"] = null, }; - - await VerifyNotificationAsync( - async sut => await sut.PushSyncFolderUpdateAsync(folder), - expectedPayload - ); } - [Fact] - public async Task PushSyncFolderDeleteAsync_SendsExpectedResponse() + protected override JsonNode GetPushSyncFolderDeletePayload(Folder folder) { - var collectionId = Guid.NewGuid(); - - var folder = new Folder - { - Id = Guid.NewGuid(), - UserId = Guid.NewGuid(), - RevisionDate = DateTime.UtcNow, - }; - - var expectedPayload = new JsonObject + return new JsonObject { ["UserId"] = folder.UserId, ["OrganizationId"] = null, ["DeviceId"] = _deviceId, - ["Identifier"] = _deviceIdentifier, + ["Identifier"] = DeviceIdentifier, ["Type"] = 3, ["Payload"] = new JsonObject { @@ -294,19 +207,11 @@ public class RelayPushNotificationServiceTests ["ClientType"] = null, ["InstallationId"] = null, }; - - await VerifyNotificationAsync( - async sut => await sut.PushSyncFolderDeleteAsync(folder), - expectedPayload - ); } - [Fact] - public async Task PushSyncCiphersAsync_SendsExpectedResponse() + protected override JsonNode GetPushSyncCiphersPayload(Guid userId) { - var userId = Guid.NewGuid(); - - var expectedPayload = new JsonObject + return new JsonObject { ["UserId"] = userId, ["OrganizationId"] = null, @@ -316,24 +221,16 @@ public class RelayPushNotificationServiceTests ["Payload"] = new JsonObject { ["UserId"] = userId, - ["Date"] = _fakeTimeProvider.GetUtcNow().UtcDateTime, + ["Date"] = FakeTimeProvider.GetUtcNow().UtcDateTime, }, ["ClientType"] = null, ["InstallationId"] = null, }; - - await VerifyNotificationAsync( - async sut => await sut.PushSyncCiphersAsync(userId), - expectedPayload - ); } - [Fact] - public async Task PushSyncVaultAsync_SendsExpectedResponse() + protected override JsonNode GetPushSyncVaultPayload(Guid userId) { - var userId = Guid.NewGuid(); - - var expectedPayload = new JsonObject + return new JsonObject { ["UserId"] = userId, ["OrganizationId"] = null, @@ -343,24 +240,16 @@ public class RelayPushNotificationServiceTests ["Payload"] = new JsonObject { ["UserId"] = userId, - ["Date"] = _fakeTimeProvider.GetUtcNow().UtcDateTime, + ["Date"] = FakeTimeProvider.GetUtcNow().UtcDateTime, }, ["ClientType"] = null, ["InstallationId"] = null, }; - - await VerifyNotificationAsync( - async sut => await sut.PushSyncVaultAsync(userId), - expectedPayload - ); } - [Fact] - public async Task PushSyncOrganizationsAsync_SendsExpectedResponse() + protected override JsonNode GetPushSyncOrganizationsPayload(Guid userId) { - var userId = Guid.NewGuid(); - - var expectedPayload = new JsonObject + return new JsonObject { ["UserId"] = userId, ["OrganizationId"] = null, @@ -370,24 +259,16 @@ public class RelayPushNotificationServiceTests ["Payload"] = new JsonObject { ["UserId"] = userId, - ["Date"] = _fakeTimeProvider.GetUtcNow().UtcDateTime, + ["Date"] = FakeTimeProvider.GetUtcNow().UtcDateTime, }, ["ClientType"] = null, ["InstallationId"] = null, }; - - await VerifyNotificationAsync( - async sut => await sut.PushSyncOrganizationsAsync(userId), - expectedPayload - ); } - [Fact] - public async Task PushSyncOrgKeysAsync_SendsExpectedResponse() + protected override JsonNode GetPushSyncOrgKeysPayload(Guid userId) { - var userId = Guid.NewGuid(); - - var expectedPayload = new JsonObject + return new JsonObject { ["UserId"] = userId, ["OrganizationId"] = null, @@ -397,24 +278,16 @@ public class RelayPushNotificationServiceTests ["Payload"] = new JsonObject { ["UserId"] = userId, - ["Date"] = _fakeTimeProvider.GetUtcNow().UtcDateTime, + ["Date"] = FakeTimeProvider.GetUtcNow().UtcDateTime, }, ["ClientType"] = null, ["InstallationId"] = null, }; - - await VerifyNotificationAsync( - async sut => await sut.PushSyncOrgKeysAsync(userId), - expectedPayload - ); } - [Fact] - public async Task PushSyncSettingsAsync_SendsExpectedResponse() + protected override JsonNode GetPushSyncSettingsPayload(Guid userId) { - var userId = Guid.NewGuid(); - - var expectedPayload = new JsonObject + return new JsonObject { ["UserId"] = userId, ["OrganizationId"] = null, @@ -424,28 +297,18 @@ public class RelayPushNotificationServiceTests ["Payload"] = new JsonObject { ["UserId"] = userId, - ["Date"] = _fakeTimeProvider.GetUtcNow().UtcDateTime, + ["Date"] = FakeTimeProvider.GetUtcNow().UtcDateTime, }, ["ClientType"] = null, ["InstallationId"] = null, }; - - await VerifyNotificationAsync( - async sut => await sut.PushSyncSettingsAsync(userId), - expectedPayload - ); } - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task PushLogOutAsync_SendsExpectedResponse(bool excludeCurrentContext) + protected override JsonNode GetPushLogOutPayload(Guid userId, bool excludeCurrentContext) { - var userId = Guid.NewGuid(); + JsonNode? identifier = excludeCurrentContext ? DeviceIdentifier : null; - JsonNode? identifier = excludeCurrentContext ? _deviceIdentifier : null; - - var expectedPayload = new JsonObject + return new JsonObject { ["UserId"] = userId, ["OrganizationId"] = null, @@ -455,34 +318,20 @@ public class RelayPushNotificationServiceTests ["Payload"] = new JsonObject { ["UserId"] = userId, - ["Date"] = _fakeTimeProvider.GetUtcNow().UtcDateTime, + ["Date"] = FakeTimeProvider.GetUtcNow().UtcDateTime, }, ["ClientType"] = null, ["InstallationId"] = null, }; - - await VerifyNotificationAsync( - async sut => await sut.PushLogOutAsync(userId, excludeCurrentContext), - expectedPayload - ); } - - [Fact] - public async Task PushSyncSendCreateAsync_SendsExpectedResponse() + protected override JsonNode GetPushSendCreatePayload(Send send) { - var send = new Send - { - Id = Guid.NewGuid(), - UserId = Guid.NewGuid(), - RevisionDate = DateTime.UtcNow, - }; - - var expectedPayload = new JsonObject + return new JsonObject { ["UserId"] = send.UserId, ["OrganizationId"] = null, ["DeviceId"] = _deviceId, - ["Identifier"] = _deviceIdentifier, + ["Identifier"] = DeviceIdentifier, ["Type"] = 12, ["Payload"] = new JsonObject { @@ -493,29 +342,15 @@ public class RelayPushNotificationServiceTests ["ClientType"] = null, ["InstallationId"] = null, }; - - await VerifyNotificationAsync( - async sut => await sut.PushSyncSendCreateAsync(send), - expectedPayload - ); } - - [Fact] - public async Task PushSyncSendUpdateAsync_SendsExpectedResponse() + protected override JsonNode GetPushSendUpdatePayload(Send send) { - var send = new Send - { - Id = Guid.NewGuid(), - UserId = Guid.NewGuid(), - RevisionDate = DateTime.UtcNow, - }; - - var expectedPayload = new JsonObject + return new JsonObject { ["UserId"] = send.UserId, ["OrganizationId"] = null, ["DeviceId"] = _deviceId, - ["Identifier"] = _deviceIdentifier, + ["Identifier"] = DeviceIdentifier, ["Type"] = 13, ["Payload"] = new JsonObject { @@ -526,29 +361,15 @@ public class RelayPushNotificationServiceTests ["ClientType"] = null, ["InstallationId"] = null, }; - - await VerifyNotificationAsync( - async sut => await sut.PushSyncSendUpdateAsync(send), - expectedPayload - ); } - - [Fact] - public async Task PushSyncSendDeleteAsync_SendsExpectedResponse() + protected override JsonNode GetPushSendDeletePayload(Send send) { - var send = new Send - { - Id = Guid.NewGuid(), - UserId = Guid.NewGuid(), - RevisionDate = DateTime.UtcNow, - }; - - var expectedPayload = new JsonObject + return new JsonObject { ["UserId"] = send.UserId, ["OrganizationId"] = null, ["DeviceId"] = _deviceId, - ["Identifier"] = _deviceIdentifier, + ["Identifier"] = DeviceIdentifier, ["Type"] = 14, ["Payload"] = new JsonObject { @@ -559,28 +380,15 @@ public class RelayPushNotificationServiceTests ["ClientType"] = null, ["InstallationId"] = null, }; - - await VerifyNotificationAsync( - async sut => await sut.PushSyncSendDeleteAsync(send), - expectedPayload - ); } - - [Fact] - public async Task PushAuthRequestAsync_SendsExpectedResponse() + protected override JsonNode GetPushAuthRequestPayload(AuthRequest authRequest) { - var authRequest = new AuthRequest - { - Id = Guid.NewGuid(), - UserId = Guid.NewGuid(), - }; - - var expectedPayload = new JsonObject + return new JsonObject { ["UserId"] = authRequest.UserId, ["OrganizationId"] = null, ["DeviceId"] = _deviceId, - ["Identifier"] = _deviceIdentifier, +["Identifier"] = DeviceIdentifier, ["Type"] = 15, ["Payload"] = new JsonObject { @@ -590,28 +398,15 @@ public class RelayPushNotificationServiceTests ["ClientType"] = null, ["InstallationId"] = null, }; - - await VerifyNotificationAsync( - async sut => await sut.PushAuthRequestAsync(authRequest), - expectedPayload - ); } - - [Fact] - public async Task PushAuthRequestResponseAsync_SendsExpectedResponse() + protected override JsonNode GetPushAuthRequestResponsePayload(AuthRequest authRequest) { - var authRequest = new AuthRequest - { - Id = Guid.NewGuid(), - UserId = Guid.NewGuid(), - }; - - var expectedPayload = new JsonObject + return new JsonObject { ["UserId"] = authRequest.UserId, ["OrganizationId"] = null, ["DeviceId"] = _deviceId, - ["Identifier"] = _deviceIdentifier, + ["Identifier"] = DeviceIdentifier, ["Type"] = 16, ["Payload"] = new JsonObject { @@ -621,47 +416,23 @@ public class RelayPushNotificationServiceTests ["ClientType"] = null, ["InstallationId"] = null, }; - - await VerifyNotificationAsync( - async sut => await sut.PushAuthRequestResponseAsync(authRequest), - expectedPayload - ); } - - [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_SendsExpectedResponse(bool global, string? userId, string? organizationId) + protected override JsonNode GetPushNotificationResponsePayload(Notification notification, Guid? userId, Guid? 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 = notification.Global ? GlobalSettings.Installation.Id : null; - JsonNode? installationId = global ? _globalSettings.Installation.Id : null; - - var expectedPayload = new JsonObject + return new JsonObject { ["UserId"] = notification.UserId, ["OrganizationId"] = notification.OrganizationId, ["DeviceId"] = _deviceId, - ["Identifier"] = _deviceIdentifier, + ["Identifier"] = DeviceIdentifier, ["Type"] = 20, ["Payload"] = new JsonObject { ["Id"] = notification.Id, ["Priority"] = 3, - ["Global"] = global, + ["Global"] = notification.Global, ["ClientType"] = 0, ["UserId"] = notification.UserId, ["OrganizationId"] = notification.OrganizationId, @@ -676,53 +447,23 @@ public class RelayPushNotificationServiceTests ["ClientType"] = 0, ["InstallationId"] = installationId?.DeepClone(), }; - - await VerifyNotificationAsync( - async sut => await sut.PushNotificationAsync(notification), - expectedPayload - ); } - - [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_SendsExpectedResponse(bool global, string? userId, string? organizationId) + protected override JsonNode GetPushNotificationStatusResponsePayload(Notification notification, NotificationStatus notificationStatus, Guid? userId, Guid? 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 = notification.Global ? GlobalSettings.Installation.Id : null; - var notificationStatus = new NotificationStatus - { - ReadDate = DateTime.UtcNow, - DeletedDate = DateTime.UtcNow, - }; - - JsonNode? installationId = global ? _globalSettings.Installation.Id : null; - - var expectedPayload = new JsonObject + return new JsonObject { ["UserId"] = notification.UserId, ["OrganizationId"] = notification.OrganizationId, ["DeviceId"] = _deviceId, - ["Identifier"] = _deviceIdentifier, + ["Identifier"] = DeviceIdentifier, ["Type"] = 21, ["Payload"] = new JsonObject { ["Id"] = notification.Id, ["Priority"] = 3, - ["Global"] = global, + ["Global"] = notification.Global, ["ClientType"] = 0, ["UserId"] = notification.UserId, ["OrganizationId"] = notification.OrganizationId, @@ -737,23 +478,10 @@ public class RelayPushNotificationServiceTests ["ClientType"] = 0, ["InstallationId"] = installationId?.DeepClone(), }; - - await VerifyNotificationAsync( - async sut => await sut.PushNotificationStatusAsync(notification, notificationStatus), - expectedPayload - ); } - - [Fact] - public async Task PushSyncOrganizationStatusAsync_SendsExpectedResponse() + protected override JsonNode GetPushSyncOrganizationStatusResponsePayload(Organization organization) { - var organization = new Organization - { - Id = Guid.NewGuid(), - Enabled = true, - }; - - var expectedPayload = new JsonObject + return new JsonObject { ["UserId"] = null, ["OrganizationId"] = organization.Id, @@ -768,26 +496,10 @@ public class RelayPushNotificationServiceTests ["ClientType"] = null, ["InstallationId"] = null, }; - - await VerifyNotificationAsync( - async sut => await sut.PushSyncOrganizationStatusAsync(organization), - expectedPayload - ); } - - [Fact] - public async Task PushSyncOrganizationCollectionManagementSettingsAsync_SendsExpectedResponse() + protected override JsonNode GetPushSyncOrganizationCollectionManagementSettingsResponsePayload(Organization organization) { - var organization = new Organization - { - Id = Guid.NewGuid(), - Enabled = true, - LimitCollectionCreation = true, - LimitCollectionDeletion = true, - LimitItemDeletion = true, - }; - - var expectedPayload = new JsonObject + return new JsonObject { ["UserId"] = null, ["OrganizationId"] = organization.Id, @@ -804,19 +516,11 @@ public class RelayPushNotificationServiceTests ["ClientType"] = null, ["InstallationId"] = null, }; - - await VerifyNotificationAsync( - async sut => await sut.PushSyncOrganizationCollectionManagementSettingsAsync(organization), - expectedPayload - ); } - [Fact] - public async Task PushPendingSecurityTasksAsync_SendsExpectedResponse() + protected override JsonNode GetPushPendingSecurityTasksResponsePayload(Guid userId) { - var userId = Guid.NewGuid(); - - var expectedPayload = new JsonObject + return new JsonObject { ["UserId"] = userId, ["OrganizationId"] = null, @@ -826,95 +530,10 @@ public class RelayPushNotificationServiceTests ["Payload"] = new JsonObject { ["UserId"] = userId, - ["Date"] = _fakeTimeProvider.GetUtcNow().UtcDateTime, + ["Date"] = FakeTimeProvider.GetUtcNow().UtcDateTime, }, ["ClientType"] = null, ["InstallationId"] = null, }; - - await VerifyNotificationAsync( - async sut => await sut.PushPendingSecurityTasksAsync(userId), - expectedPayload - ); - } - - [Fact] - public async Task SendPayloadToInstallationAsync_ThrowsNotImplementedException() - { - await Assert.ThrowsAsync( - async () => await _sut.SendPayloadToInstallationAsync("installation_id", PushType.AuthRequest, new {}, null) - ); - } - - [Fact] - public async Task SendPayloadToUserAsync_ThrowsNotImplementedException() - { - await Assert.ThrowsAsync( - async () => await _sut.SendPayloadToUserAsync("user_id", PushType.AuthRequest, new {}, null) - ); - } - - [Fact] - public async Task SendPayloadToOrganizationAsync_ThrowsNotImplementedException() - { - await Assert.ThrowsAsync( - async () => await _sut.SendPayloadToOrganizationAsync("organization_id", PushType.AuthRequest, new {}, null) - ); - } - - private async Task VerifyNotificationAsync(Func test, JsonNode expectedRequestBody) - { - var httpContext = new DefaultHttpContext(); - - var serviceCollection = new ServiceCollection(); - var currentContext = Substitute.For(); - currentContext.DeviceIdentifier = _deviceIdentifier; - serviceCollection.AddSingleton(currentContext); - - httpContext.RequestServices = serviceCollection.BuildServiceProvider(); - - _httpContextAccessor.HttpContext - .Returns(httpContext); - - _deviceRepository.GetByIdentifierAsync(_deviceIdentifier) - .Returns(new Device - { - Id = _deviceId, - }); - - var connectTokenRequest = _mockIdentityClient - .Expect(HttpMethod.Post, "https://localhost:8888/connect/token") - .Respond(HttpStatusCode.OK, JsonContent.Create(new - { - access_token = CreateAccessToken(DateTime.UtcNow.AddDays(1)), - })); - - var pushSendRequest = _mockPushClient - .Expect(HttpMethod.Post, "https://localhost:7777/push/send") - .With(request => - { - if (request.Content is not JsonContent jsonContent) - { - return false; - } - - // TODO: What options? - var actualString = JsonSerializer.Serialize(jsonContent.Value); - var actualNode = JsonNode.Parse(actualString); - - return JsonNode.DeepEquals(actualNode, expectedRequestBody); - }) - .Respond(HttpStatusCode.OK); - - await test(_sut); - - Assert.Equal(1, _mockPushClient.GetMatchCount(pushSendRequest)); - } - - private static string CreateAccessToken(DateTime expirationTime) - { - var tokenHandler = new JwtSecurityTokenHandler(); - var token = new JwtSecurityToken(expires: expirationTime); - return tokenHandler.WriteToken(token); } }