From be7929c81dc6ebd7db0b60e47d06265892cf7a34 Mon Sep 17 00:00:00 2001 From: Maciej Zieniuk Date: Fri, 8 Nov 2024 10:35:19 +0000 Subject: [PATCH] PM-10600: Unit tests for NotificationsApiPushNotificationService Also added HttpClientCustomize fixture, that provides IHttpClientFactory, HttpClient and MockedHttpMessageHandler. The latter can be used to mock requests and provide expected responses. Expanded HttpRequestMatcher to be able to respond with content. --- .../MockedHttpClient/HttpRequestMatcher.cs | 7 +- .../AutoFixture/HttpClientFixtures.cs | 51 ++++++++++ ...icationsApiPushNotificationServiceTests.cs | 93 +++++++++++++------ 3 files changed, 124 insertions(+), 27 deletions(-) create mode 100644 test/Core.Test/AutoFixture/HttpClientFixtures.cs diff --git a/test/Common/MockedHttpClient/HttpRequestMatcher.cs b/test/Common/MockedHttpClient/HttpRequestMatcher.cs index 7e4d0d2da..2457b0f3f 100644 --- a/test/Common/MockedHttpClient/HttpRequestMatcher.cs +++ b/test/Common/MockedHttpClient/HttpRequestMatcher.cs @@ -74,11 +74,16 @@ public class HttpRequestMatcher : IHttpRequestMatcher /// Note, after specifying a response, you can no longer further specify match criteria. /// /// + /// /// - public MockedHttpResponse RespondWith(HttpStatusCode statusCode) + public MockedHttpResponse RespondWith(HttpStatusCode statusCode, HttpContent? content = null) { _responseSpecified = true; _mockedResponse = new MockedHttpResponse(statusCode); + if (content != null) + { + _mockedResponse.WithContent(content); + } return _mockedResponse; } diff --git a/test/Core.Test/AutoFixture/HttpClientFixtures.cs b/test/Core.Test/AutoFixture/HttpClientFixtures.cs new file mode 100644 index 000000000..55dd5c6e6 --- /dev/null +++ b/test/Core.Test/AutoFixture/HttpClientFixtures.cs @@ -0,0 +1,51 @@ +#nullable enable +using AutoFixture; +using AutoFixture.Kernel; +using Bit.Test.Common.AutoFixture.Attributes; +using Bit.Test.Common.MockedHttpClient; +using NSubstitute; + +namespace Bit.Core.Test.AutoFixture; + +public class HttpClientFactoryBuilder : ISpecimenBuilder +{ + private MockedHttpMessageHandler? _mockedHttpMessageHandler; + + public object Create(object request, ISpecimenContext context) + { + var type = request as Type; + if (type == typeof(IHttpClientFactory)) + { + var handler = context.Create(); + var httpClientFactory = Substitute.For(); + httpClientFactory.CreateClient(Arg.Any()).Returns(handler.ToHttpClient()); + return httpClientFactory; + } + + if (type == typeof(MockedHttpMessageHandler)) + { + return _mockedHttpMessageHandler ??= new MockedHttpMessageHandler(); + } + + if (type == typeof(HttpClient)) + { + var handler = context.Create(); + return handler.ToHttpClient(); + } + + return new NoSpecimen(); + } +} + +public class HttpClientCustomizeAttribute : BitCustomizeAttribute +{ + public override ICustomization GetCustomization() => new HttpClientFixtures(); +} + +public class HttpClientFixtures : ICustomization +{ + public void Customize(IFixture fixture) + { + fixture.Customizations.Add(new HttpClientFactoryBuilder()); + } +} diff --git a/test/Core.Test/Services/NotificationsApiPushNotificationServiceTests.cs b/test/Core.Test/Services/NotificationsApiPushNotificationServiceTests.cs index d1ba15d6a..9c10a055a 100644 --- a/test/Core.Test/Services/NotificationsApiPushNotificationServiceTests.cs +++ b/test/Core.Test/Services/NotificationsApiPushNotificationServiceTests.cs @@ -1,41 +1,82 @@ -using Bit.Core.Services; -using Bit.Core.Settings; +#nullable enable +using System.Net; +using System.Net.Http.Json; +using Bit.Core.Context; +using Bit.Core.Enums; +using Bit.Core.Models; +using Bit.Core.NotificationCenter.Entities; +using Bit.Core.Services; +using Bit.Core.Test.AutoFixture; +using Bit.Core.Test.AutoFixture.CurrentContextFixtures; +using Bit.Core.Test.NotificationCenter.AutoFixture; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using Bit.Test.Common.MockedHttpClient; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; using NSubstitute; using Xunit; namespace Bit.Core.Test.Services; +[SutProviderCustomize] +[HttpClientCustomize] public class NotificationsApiPushNotificationServiceTests { - private readonly NotificationsApiPushNotificationService _sut; - - private readonly IHttpClientFactory _httpFactory; - private readonly GlobalSettings _globalSettings; - private readonly IHttpContextAccessor _httpContextAccessor; - private readonly ILogger _logger; - - public NotificationsApiPushNotificationServiceTests() + [Theory] + [BitAutoData] + [NotificationCustomize] + [CurrentContextCustomize] + public async void PushSyncNotificationAsync_Notification_Sent( + SutProvider sutProvider, Notification notification, + Guid deviceIdentifier, ICurrentContext currentContext, MockedHttpMessageHandler mockedHttpMessageHandler) { - _httpFactory = Substitute.For(); - _globalSettings = new GlobalSettings(); - _httpContextAccessor = Substitute.For(); - _logger = Substitute.For>(); + var tokenResponse = mockedHttpMessageHandler + .When(request => + request.Method == HttpMethod.Post && request.RequestUri!.ToString().EndsWith("/connect/token")) + .RespondWith(HttpStatusCode.OK, new StringContent("{\"access_token\":\"token\"}")); + var sendResponse = mockedHttpMessageHandler + .When(request => + { + if (request.Method != HttpMethod.Post || !request.RequestUri!.ToString().EndsWith("/send") || + request.Content is not JsonContent jsonContent || jsonContent.Value == null) + { + return false; + } - _sut = new NotificationsApiPushNotificationService( - _httpFactory, - _globalSettings, - _httpContextAccessor, - _logger - ); + var pushNotificationData = (PushNotificationData)jsonContent.Value; + return MatchMessage(PushType.SyncNotification, pushNotificationData, + new SyncNotificationEquals(notification), deviceIdentifier.ToString()); + }) + .RespondWith(HttpStatusCode.OK); + + currentContext.DeviceIdentifier.Returns(deviceIdentifier.ToString()); + sutProvider.GetDependency().HttpContext!.RequestServices + .GetService(Arg.Any()).Returns(currentContext); + + await sutProvider.Sut.PushSyncNotificationAsync(notification); + + Assert.Equal(1, tokenResponse.NumberOfResponses); + Assert.Equal(1, sendResponse.NumberOfResponses); } - // 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() + private static bool MatchMessage(PushType pushType, PushNotificationData pushNotificationData, + IEquatable expectedPayloadEquatable, string contextId) { - Assert.NotNull(_sut); + return pushNotificationData.Type == pushType && + expectedPayloadEquatable.Equals(pushNotificationData.Payload) && + pushNotificationData.ContextId == contextId; + } + + private class SyncNotificationEquals(Notification notification) : IEquatable + { + public bool Equals(SyncNotificationPushNotification? other) + { + return other != null && + other.Id == notification.Id && + other.UserId == notification.UserId && + other.OrganizationId == notification.OrganizationId && + other.ClientType == notification.ClientType && + other.RevisionDate == notification.RevisionDate; + } } }