1
0
mirror of https://github.com/bitwarden/server.git synced 2024-11-25 12:45:18 +01:00

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.
This commit is contained in:
Maciej Zieniuk 2024-11-08 10:35:19 +00:00
parent 91567ed686
commit be7929c81d
No known key found for this signature in database
GPG Key ID: 9CACE59F1272ACD9
3 changed files with 124 additions and 27 deletions

View File

@ -74,11 +74,16 @@ public class HttpRequestMatcher : IHttpRequestMatcher
/// Note, after specifying a response, you can no longer further specify match criteria.
/// </summary>
/// <param name="statusCode"></param>
/// <param name="content"></param>
/// <returns></returns>
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;
}

View File

@ -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<MockedHttpMessageHandler>();
var httpClientFactory = Substitute.For<IHttpClientFactory>();
httpClientFactory.CreateClient(Arg.Any<string>()).Returns(handler.ToHttpClient());
return httpClientFactory;
}
if (type == typeof(MockedHttpMessageHandler))
{
return _mockedHttpMessageHandler ??= new MockedHttpMessageHandler();
}
if (type == typeof(HttpClient))
{
var handler = context.Create<MockedHttpMessageHandler>();
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());
}
}

View File

@ -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<NotificationsApiPushNotificationService> _logger;
public NotificationsApiPushNotificationServiceTests()
[Theory]
[BitAutoData]
[NotificationCustomize]
[CurrentContextCustomize]
public async void PushSyncNotificationAsync_Notification_Sent(
SutProvider<NotificationsApiPushNotificationService> sutProvider, Notification notification,
Guid deviceIdentifier, ICurrentContext currentContext, MockedHttpMessageHandler mockedHttpMessageHandler)
{
_httpFactory = Substitute.For<IHttpClientFactory>();
_globalSettings = new GlobalSettings();
_httpContextAccessor = Substitute.For<IHttpContextAccessor>();
_logger = Substitute.For<ILogger<NotificationsApiPushNotificationService>>();
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<SyncNotificationPushNotification>)jsonContent.Value;
return MatchMessage(PushType.SyncNotification, pushNotificationData,
new SyncNotificationEquals(notification), deviceIdentifier.ToString());
})
.RespondWith(HttpStatusCode.OK);
currentContext.DeviceIdentifier.Returns(deviceIdentifier.ToString());
sutProvider.GetDependency<IHttpContextAccessor>().HttpContext!.RequestServices
.GetService(Arg.Any<Type>()).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<T>(PushType pushType, PushNotificationData<T> pushNotificationData,
IEquatable<T> expectedPayloadEquatable, string contextId)
{
Assert.NotNull(_sut);
return pushNotificationData.Type == pushType &&
expectedPayloadEquatable.Equals(pushNotificationData.Payload) &&
pushNotificationData.ContextId == contextId;
}
private class SyncNotificationEquals(Notification notification) : IEquatable<SyncNotificationPushNotification>
{
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;
}
}
}