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; + } } }