1
0
mirror of https://github.com/bitwarden/server synced 2025-12-13 14:53:34 +00:00

[PM-17562] Update logs to use custom categories (#6145)

* [PM-17562] Update logs to use custom categories

* Added tests to verify hardcoded names match the real type
This commit is contained in:
Brant DeBow
2025-08-01 08:46:00 -04:00
committed by GitHub
parent 6f4a0c4a61
commit ccedefb8b8
10 changed files with 192 additions and 60 deletions

View File

@@ -10,9 +10,9 @@ namespace Bit.Core.Services;
public abstract class EventLoggingListenerService : BackgroundService public abstract class EventLoggingListenerService : BackgroundService
{ {
protected readonly IEventMessageHandler _handler; protected readonly IEventMessageHandler _handler;
protected ILogger<EventLoggingListenerService> _logger; protected ILogger _logger;
protected EventLoggingListenerService(IEventMessageHandler handler, ILogger<EventLoggingListenerService> logger) protected EventLoggingListenerService(IEventMessageHandler handler, ILogger logger)
{ {
_handler = handler; _handler = handler;
_logger = logger; _logger = logger;

View File

@@ -16,7 +16,8 @@ public class AzureServiceBusEventListenerService<TConfiguration> : EventLoggingL
TConfiguration configuration, TConfiguration configuration,
IEventMessageHandler handler, IEventMessageHandler handler,
IAzureServiceBusService serviceBusService, IAzureServiceBusService serviceBusService,
ILogger<AzureServiceBusEventListenerService<TConfiguration>> logger) : base(handler, logger) ILoggerFactory loggerFactory)
: base(handler, CreateLogger(loggerFactory, configuration))
{ {
_processor = serviceBusService.CreateProcessor( _processor = serviceBusService.CreateProcessor(
topicName: configuration.EventTopicName, topicName: configuration.EventTopicName,
@@ -39,6 +40,12 @@ public class AzureServiceBusEventListenerService<TConfiguration> : EventLoggingL
await base.StopAsync(cancellationToken); await base.StopAsync(cancellationToken);
} }
private static ILogger CreateLogger(ILoggerFactory loggerFactory, TConfiguration configuration)
{
return loggerFactory.CreateLogger(
categoryName: $"Bit.Core.Services.AzureServiceBusEventListenerService.{configuration.EventSubscriptionName}");
}
internal Task ProcessErrorAsync(ProcessErrorEventArgs args) internal Task ProcessErrorAsync(ProcessErrorEventArgs args)
{ {
_logger.LogError( _logger.LogError(

View File

@@ -14,16 +14,17 @@ public class AzureServiceBusIntegrationListenerService<TConfiguration> : Backgro
private readonly IAzureServiceBusService _serviceBusService; private readonly IAzureServiceBusService _serviceBusService;
private readonly IIntegrationHandler _handler; private readonly IIntegrationHandler _handler;
private readonly ServiceBusProcessor _processor; private readonly ServiceBusProcessor _processor;
private readonly ILogger<AzureServiceBusIntegrationListenerService<TConfiguration>> _logger; private readonly ILogger _logger;
public AzureServiceBusIntegrationListenerService( public AzureServiceBusIntegrationListenerService(
TConfiguration configuration, TConfiguration configuration,
IIntegrationHandler handler, IIntegrationHandler handler,
IAzureServiceBusService serviceBusService, IAzureServiceBusService serviceBusService,
ILogger<AzureServiceBusIntegrationListenerService<TConfiguration>> logger) ILoggerFactory loggerFactory)
{ {
_handler = handler; _handler = handler;
_logger = logger; _logger = loggerFactory.CreateLogger(
categoryName: $"Bit.Core.Services.AzureServiceBusIntegrationListenerService.{configuration.IntegrationSubscriptionName}");
_maxRetries = configuration.MaxRetries; _maxRetries = configuration.MaxRetries;
_serviceBusService = serviceBusService; _serviceBusService = serviceBusService;

View File

@@ -19,7 +19,8 @@ public class RabbitMqEventListenerService<TConfiguration> : EventLoggingListener
IEventMessageHandler handler, IEventMessageHandler handler,
TConfiguration configuration, TConfiguration configuration,
IRabbitMqService rabbitMqService, IRabbitMqService rabbitMqService,
ILogger<RabbitMqEventListenerService<TConfiguration>> logger) : base(handler, logger) ILoggerFactory loggerFactory)
: base(handler, CreateLogger(loggerFactory, configuration))
{ {
_queueName = configuration.EventQueueName; _queueName = configuration.EventQueueName;
_rabbitMqService = rabbitMqService; _rabbitMqService = rabbitMqService;
@@ -66,4 +67,10 @@ public class RabbitMqEventListenerService<TConfiguration> : EventLoggingListener
} }
base.Dispose(); base.Dispose();
} }
private static ILogger CreateLogger(ILoggerFactory loggerFactory, TConfiguration configuration)
{
return loggerFactory.CreateLogger(
categoryName: $"Bit.Core.Services.RabbitMqEventListenerService.{configuration.EventQueueName}");
}
} }

View File

@@ -20,14 +20,14 @@ public class RabbitMqIntegrationListenerService<TConfiguration> : BackgroundServ
private readonly IIntegrationHandler _handler; private readonly IIntegrationHandler _handler;
private readonly Lazy<Task<IChannel>> _lazyChannel; private readonly Lazy<Task<IChannel>> _lazyChannel;
private readonly IRabbitMqService _rabbitMqService; private readonly IRabbitMqService _rabbitMqService;
private readonly ILogger<RabbitMqIntegrationListenerService<TConfiguration>> _logger; private readonly ILogger _logger;
private readonly TimeProvider _timeProvider; private readonly TimeProvider _timeProvider;
public RabbitMqIntegrationListenerService( public RabbitMqIntegrationListenerService(
IIntegrationHandler handler, IIntegrationHandler handler,
TConfiguration configuration, TConfiguration configuration,
IRabbitMqService rabbitMqService, IRabbitMqService rabbitMqService,
ILogger<RabbitMqIntegrationListenerService<TConfiguration>> logger, ILoggerFactory loggerFactory,
TimeProvider timeProvider) TimeProvider timeProvider)
{ {
_handler = handler; _handler = handler;
@@ -36,9 +36,10 @@ public class RabbitMqIntegrationListenerService<TConfiguration> : BackgroundServ
_retryQueueName = configuration.IntegrationRetryQueueName; _retryQueueName = configuration.IntegrationRetryQueueName;
_queueName = configuration.IntegrationQueueName; _queueName = configuration.IntegrationQueueName;
_rabbitMqService = rabbitMqService; _rabbitMqService = rabbitMqService;
_logger = logger;
_timeProvider = timeProvider; _timeProvider = timeProvider;
_lazyChannel = new Lazy<Task<IChannel>>(() => _rabbitMqService.CreateChannelAsync()); _lazyChannel = new Lazy<Task<IChannel>>(() => _rabbitMqService.CreateChannelAsync());
_logger = loggerFactory.CreateLogger(
categoryName: $"Bit.Core.Services.RabbitMqIntegrationListenerService.{configuration.IntegrationQueueName}"); ;
} }
public override async Task StartAsync(CancellationToken cancellationToken) public override async Task StartAsync(CancellationToken cancellationToken)

View File

@@ -890,7 +890,7 @@ public static class ServiceCollectionExtensions
configuration: listenerConfiguration, configuration: listenerConfiguration,
handler: provider.GetRequiredKeyedService<IEventMessageHandler>(serviceKey: listenerConfiguration.RoutingKey), handler: provider.GetRequiredKeyedService<IEventMessageHandler>(serviceKey: listenerConfiguration.RoutingKey),
serviceBusService: provider.GetRequiredService<IAzureServiceBusService>(), serviceBusService: provider.GetRequiredService<IAzureServiceBusService>(),
logger: provider.GetRequiredService<ILogger<AzureServiceBusEventListenerService<TListenerConfig>>>() loggerFactory: provider.GetRequiredService<ILoggerFactory>()
) )
) )
); );
@@ -900,7 +900,7 @@ public static class ServiceCollectionExtensions
configuration: listenerConfiguration, configuration: listenerConfiguration,
handler: provider.GetRequiredService<IIntegrationHandler<TConfig>>(), handler: provider.GetRequiredService<IIntegrationHandler<TConfig>>(),
serviceBusService: provider.GetRequiredService<IAzureServiceBusService>(), serviceBusService: provider.GetRequiredService<IAzureServiceBusService>(),
logger: provider.GetRequiredService<ILogger<AzureServiceBusIntegrationListenerService<TListenerConfig>>>() loggerFactory: provider.GetRequiredService<ILoggerFactory>()
) )
) )
); );
@@ -941,7 +941,7 @@ public static class ServiceCollectionExtensions
handler: provider.GetRequiredService<EventRepositoryHandler>(), handler: provider.GetRequiredService<EventRepositoryHandler>(),
configuration: repositoryConfiguration, configuration: repositoryConfiguration,
rabbitMqService: provider.GetRequiredService<IRabbitMqService>(), rabbitMqService: provider.GetRequiredService<IRabbitMqService>(),
logger: provider.GetRequiredService<ILogger<RabbitMqEventListenerService<RepositoryListenerConfiguration>>>() loggerFactory: provider.GetRequiredService<ILoggerFactory>()
) )
) )
); );
@@ -958,7 +958,7 @@ public static class ServiceCollectionExtensions
configuration: repositoryConfiguration, configuration: repositoryConfiguration,
handler: provider.GetRequiredService<AzureTableStorageEventHandler>(), handler: provider.GetRequiredService<AzureTableStorageEventHandler>(),
serviceBusService: provider.GetRequiredService<IAzureServiceBusService>(), serviceBusService: provider.GetRequiredService<IAzureServiceBusService>(),
logger: provider.GetRequiredService<ILogger<AzureServiceBusEventListenerService<RepositoryListenerConfiguration>>>() loggerFactory: provider.GetRequiredService<ILoggerFactory>()
) )
) )
); );
@@ -992,7 +992,7 @@ public static class ServiceCollectionExtensions
handler: provider.GetRequiredKeyedService<IEventMessageHandler>(serviceKey: listenerConfiguration.RoutingKey), handler: provider.GetRequiredKeyedService<IEventMessageHandler>(serviceKey: listenerConfiguration.RoutingKey),
configuration: listenerConfiguration, configuration: listenerConfiguration,
rabbitMqService: provider.GetRequiredService<IRabbitMqService>(), rabbitMqService: provider.GetRequiredService<IRabbitMqService>(),
logger: provider.GetRequiredService<ILogger<RabbitMqEventListenerService<TListenerConfig>>>() loggerFactory: provider.GetRequiredService<ILoggerFactory>()
) )
) )
); );
@@ -1002,7 +1002,7 @@ public static class ServiceCollectionExtensions
handler: provider.GetRequiredService<IIntegrationHandler<TConfig>>(), handler: provider.GetRequiredService<IIntegrationHandler<TConfig>>(),
configuration: listenerConfiguration, configuration: listenerConfiguration,
rabbitMqService: provider.GetRequiredService<IRabbitMqService>(), rabbitMqService: provider.GetRequiredService<IRabbitMqService>(),
logger: provider.GetRequiredService<ILogger<RabbitMqIntegrationListenerService<TListenerConfig>>>(), loggerFactory: provider.GetRequiredService<ILoggerFactory>(),
timeProvider: provider.GetRequiredService<TimeProvider>() timeProvider: provider.GetRequiredService<TimeProvider>()
) )
) )

View File

@@ -1,4 +1,6 @@
using System.Text.Json; #nullable enable
using System.Text.Json;
using Azure.Messaging.ServiceBus; using Azure.Messaging.ServiceBus;
using Bit.Core.AdminConsole.Models.Data.EventIntegrations; using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
using Bit.Core.Models.Data; using Bit.Core.Models.Data;
@@ -17,14 +19,31 @@ public class AzureServiceBusEventListenerServiceTests
{ {
private const string _messageId = "messageId"; private const string _messageId = "messageId";
private readonly TestListenerConfiguration _config = new(); private readonly TestListenerConfiguration _config = new();
private readonly ILogger _logger = Substitute.For<ILogger>();
private SutProvider<AzureServiceBusEventListenerService<TestListenerConfiguration>> GetSutProvider() private SutProvider<AzureServiceBusEventListenerService<TestListenerConfiguration>> GetSutProvider()
{ {
var loggerFactory = Substitute.For<ILoggerFactory>();
loggerFactory.CreateLogger<object>().ReturnsForAnyArgs(_logger);
return new SutProvider<AzureServiceBusEventListenerService<TestListenerConfiguration>>() return new SutProvider<AzureServiceBusEventListenerService<TestListenerConfiguration>>()
.SetDependency(_config) .SetDependency(_config)
.SetDependency(loggerFactory)
.Create(); .Create();
} }
[Fact]
public void Constructor_CreatesLogWithCorrectCategory()
{
var sutProvider = GetSutProvider();
var fullName = typeof(AzureServiceBusEventListenerService<>).FullName ?? "";
var tickIndex = fullName.IndexOf('`');
var cleanedName = tickIndex >= 0 ? fullName.Substring(0, tickIndex) : fullName;
var categoryName = cleanedName + '.' + _config.EventSubscriptionName;
sutProvider.GetDependency<ILoggerFactory>().Received(1).CreateLogger(categoryName);
}
[Fact] [Fact]
public void Constructor_CreatesProcessor() public void Constructor_CreatesProcessor()
{ {
@@ -44,12 +63,12 @@ public class AzureServiceBusEventListenerServiceTests
await sutProvider.Sut.ProcessErrorAsync(args); await sutProvider.Sut.ProcessErrorAsync(args);
sutProvider.GetDependency<ILogger<AzureServiceBusEventListenerService<TestListenerConfiguration>>>().Received(1).Log( _logger.Received(1).Log(
LogLevel.Error, LogLevel.Error,
Arg.Any<EventId>(), Arg.Any<EventId>(),
Arg.Any<object>(), Arg.Any<object>(),
Arg.Any<Exception>(), Arg.Any<Exception>(),
Arg.Any<Func<object, Exception, string>>()); Arg.Any<Func<object, Exception?, string>>());
} }
[Fact] [Fact]
@@ -58,26 +77,26 @@ public class AzureServiceBusEventListenerServiceTests
var sutProvider = GetSutProvider(); var sutProvider = GetSutProvider();
await sutProvider.Sut.ProcessReceivedMessageAsync(string.Empty, _messageId); await sutProvider.Sut.ProcessReceivedMessageAsync(string.Empty, _messageId);
sutProvider.GetDependency<ILogger<AzureServiceBusEventListenerService<TestListenerConfiguration>>>().Received(1).Log( _logger.Received(1).Log(
LogLevel.Error, LogLevel.Error,
Arg.Any<EventId>(), Arg.Any<EventId>(),
Arg.Any<object>(), Arg.Any<object>(),
Arg.Any<JsonException>(), Arg.Any<JsonException>(),
Arg.Any<Func<object, Exception, string>>()); Arg.Any<Func<object, Exception?, string>>());
} }
[Fact] [Fact]
public async Task ProcessReceivedMessageAsync_InvalidJson_LogsError() public async Task ProcessReceivedMessageAsync_InvalidJson_LogsError()
{ {
var sutProvider = GetSutProvider(); var sutProvider = GetSutProvider();
await sutProvider.Sut.ProcessReceivedMessageAsync("{ Inavlid JSON }", _messageId); await sutProvider.Sut.ProcessReceivedMessageAsync("{ Invalid JSON }", _messageId);
sutProvider.GetDependency<ILogger<AzureServiceBusEventListenerService<TestListenerConfiguration>>>().Received(1).Log( _logger.Received(1).Log(
LogLevel.Error, LogLevel.Error,
Arg.Any<EventId>(), Arg.Any<EventId>(),
Arg.Is<object>(o => o.ToString().Contains("Invalid JSON")), Arg.Is<object>(o => (o.ToString() ?? "").Contains("Invalid JSON")),
Arg.Any<Exception>(), Arg.Any<Exception>(),
Arg.Any<Func<object, Exception, string>>()); Arg.Any<Func<object, Exception?, string>>());
} }
[Fact] [Fact]
@@ -89,12 +108,12 @@ public class AzureServiceBusEventListenerServiceTests
_messageId _messageId
); );
sutProvider.GetDependency<ILogger<AzureServiceBusEventListenerService<TestListenerConfiguration>>>().Received(1).Log( _logger.Received(1).Log(
LogLevel.Error, LogLevel.Error,
Arg.Any<EventId>(), Arg.Any<EventId>(),
Arg.Any<object>(), Arg.Any<object>(),
Arg.Any<JsonException>(), Arg.Any<JsonException>(),
Arg.Any<Func<object, Exception, string>>()); Arg.Any<Func<object, Exception?, string>>());
} }
[Fact] [Fact]
@@ -106,12 +125,12 @@ public class AzureServiceBusEventListenerServiceTests
_messageId _messageId
); );
sutProvider.GetDependency<ILogger<AzureServiceBusEventListenerService<TestListenerConfiguration>>>().Received(1).Log( _logger.Received(1).Log(
LogLevel.Error, LogLevel.Error,
Arg.Any<EventId>(), Arg.Any<EventId>(),
Arg.Any<object>(), Arg.Any<object>(),
Arg.Any<JsonException>(), Arg.Any<JsonException>(),
Arg.Any<Func<object, Exception, string>>()); Arg.Any<Func<object, Exception?, string>>());
} }
[Theory, BitAutoData] [Theory, BitAutoData]

View File

@@ -1,5 +1,6 @@
#nullable enable #nullable enable
using System.Text.Json;
using Azure.Messaging.ServiceBus; using Azure.Messaging.ServiceBus;
using Bit.Core.AdminConsole.Models.Data.EventIntegrations; using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
using Bit.Core.Services; using Bit.Core.Services;
@@ -7,6 +8,7 @@ using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.AutoFixture.Attributes;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using NSubstitute; using NSubstitute;
using NSubstitute.ExceptionExtensions;
using Xunit; using Xunit;
namespace Bit.Core.Test.Services; namespace Bit.Core.Test.Services;
@@ -15,18 +17,35 @@ namespace Bit.Core.Test.Services;
public class AzureServiceBusIntegrationListenerServiceTests public class AzureServiceBusIntegrationListenerServiceTests
{ {
private readonly IIntegrationHandler _handler = Substitute.For<IIntegrationHandler>(); private readonly IIntegrationHandler _handler = Substitute.For<IIntegrationHandler>();
private readonly ILogger _logger = Substitute.For<ILogger>();
private readonly IAzureServiceBusService _serviceBusService = Substitute.For<IAzureServiceBusService>(); private readonly IAzureServiceBusService _serviceBusService = Substitute.For<IAzureServiceBusService>();
private readonly TestListenerConfiguration _config = new(); private readonly TestListenerConfiguration _config = new();
private SutProvider<AzureServiceBusIntegrationListenerService<TestListenerConfiguration>> GetSutProvider() private SutProvider<AzureServiceBusIntegrationListenerService<TestListenerConfiguration>> GetSutProvider()
{ {
var loggerFactory = Substitute.For<ILoggerFactory>();
loggerFactory.CreateLogger<object>().ReturnsForAnyArgs(_logger);
return new SutProvider<AzureServiceBusIntegrationListenerService<TestListenerConfiguration>>() return new SutProvider<AzureServiceBusIntegrationListenerService<TestListenerConfiguration>>()
.SetDependency(_config) .SetDependency(_config)
.SetDependency(loggerFactory)
.SetDependency(_handler) .SetDependency(_handler)
.SetDependency(_serviceBusService) .SetDependency(_serviceBusService)
.Create(); .Create();
} }
[Fact]
public void Constructor_CreatesLogWithCorrectCategory()
{
var sutProvider = GetSutProvider();
var fullName = typeof(AzureServiceBusIntegrationListenerService<>).FullName ?? "";
var tickIndex = fullName.IndexOf('`');
var cleanedName = tickIndex >= 0 ? fullName.Substring(0, tickIndex) : fullName;
var categoryName = cleanedName + '.' + _config.IntegrationSubscriptionName;
sutProvider.GetDependency<ILoggerFactory>().Received(1).CreateLogger(categoryName);
}
[Fact] [Fact]
public void Constructor_CreatesProcessor() public void Constructor_CreatesProcessor()
{ {
@@ -45,7 +64,7 @@ public class AzureServiceBusIntegrationListenerServiceTests
var sutProvider = GetSutProvider(); var sutProvider = GetSutProvider();
await sutProvider.Sut.ProcessErrorAsync(args); await sutProvider.Sut.ProcessErrorAsync(args);
sutProvider.GetDependency<ILogger<AzureServiceBusIntegrationListenerService<TestListenerConfiguration>>>().Received(1).Log( _logger.Received(1).Log(
LogLevel.Error, LogLevel.Error,
Arg.Any<EventId>(), Arg.Any<EventId>(),
Arg.Any<object>(), Arg.Any<object>(),
@@ -63,12 +82,13 @@ public class AzureServiceBusIntegrationListenerServiceTests
result.Retryable = false; result.Retryable = false;
_handler.HandleAsync(Arg.Any<string>()).Returns(result); _handler.HandleAsync(Arg.Any<string>()).Returns(result);
var expected = (IntegrationMessage<WebhookIntegrationConfiguration>)IntegrationMessage<WebhookIntegrationConfiguration>.FromJson(message.ToJson())!; var expected = IntegrationMessage<WebhookIntegrationConfiguration>.FromJson(message.ToJson());
Assert.NotNull(expected);
Assert.False(await sutProvider.Sut.HandleMessageAsync(message.ToJson())); Assert.False(await sutProvider.Sut.HandleMessageAsync(message.ToJson()));
await _handler.Received(1).HandleAsync(Arg.Is(expected.ToJson())); await _handler.Received(1).HandleAsync(Arg.Is(expected.ToJson()));
await _serviceBusService.DidNotReceiveWithAnyArgs().PublishToRetryAsync(default!); await _serviceBusService.DidNotReceiveWithAnyArgs().PublishToRetryAsync(Arg.Any<IIntegrationMessage>());
} }
[Theory, BitAutoData] [Theory, BitAutoData]
@@ -81,12 +101,13 @@ public class AzureServiceBusIntegrationListenerServiceTests
_handler.HandleAsync(Arg.Any<string>()).Returns(result); _handler.HandleAsync(Arg.Any<string>()).Returns(result);
var expected = (IntegrationMessage<WebhookIntegrationConfiguration>)IntegrationMessage<WebhookIntegrationConfiguration>.FromJson(message.ToJson())!; var expected = IntegrationMessage<WebhookIntegrationConfiguration>.FromJson(message.ToJson());
Assert.NotNull(expected);
Assert.False(await sutProvider.Sut.HandleMessageAsync(message.ToJson())); Assert.False(await sutProvider.Sut.HandleMessageAsync(message.ToJson()));
await _handler.Received(1).HandleAsync(Arg.Is(expected.ToJson())); await _handler.Received(1).HandleAsync(Arg.Is(expected.ToJson()));
await _serviceBusService.DidNotReceiveWithAnyArgs().PublishToRetryAsync(default!); await _serviceBusService.DidNotReceiveWithAnyArgs().PublishToRetryAsync(Arg.Any<IIntegrationMessage>());
} }
[Theory, BitAutoData] [Theory, BitAutoData]
@@ -99,7 +120,8 @@ public class AzureServiceBusIntegrationListenerServiceTests
result.Retryable = true; result.Retryable = true;
_handler.HandleAsync(Arg.Any<string>()).Returns(result); _handler.HandleAsync(Arg.Any<string>()).Returns(result);
var expected = (IntegrationMessage<WebhookIntegrationConfiguration>)IntegrationMessage<WebhookIntegrationConfiguration>.FromJson(message.ToJson())!; var expected = IntegrationMessage<WebhookIntegrationConfiguration>.FromJson(message.ToJson());
Assert.NotNull(expected);
Assert.True(await sutProvider.Sut.HandleMessageAsync(message.ToJson())); Assert.True(await sutProvider.Sut.HandleMessageAsync(message.ToJson()));
@@ -114,11 +136,30 @@ public class AzureServiceBusIntegrationListenerServiceTests
var result = new IntegrationHandlerResult(true, message); var result = new IntegrationHandlerResult(true, message);
_handler.HandleAsync(Arg.Any<string>()).Returns(result); _handler.HandleAsync(Arg.Any<string>()).Returns(result);
var expected = (IntegrationMessage<WebhookIntegrationConfiguration>)IntegrationMessage<WebhookIntegrationConfiguration>.FromJson(message.ToJson())!; var expected = IntegrationMessage<WebhookIntegrationConfiguration>.FromJson(message.ToJson());
Assert.NotNull(expected);
Assert.True(await sutProvider.Sut.HandleMessageAsync(message.ToJson())); Assert.True(await sutProvider.Sut.HandleMessageAsync(message.ToJson()));
await _handler.Received(1).HandleAsync(Arg.Is(expected.ToJson())); await _handler.Received(1).HandleAsync(Arg.Is(expected.ToJson()));
await _serviceBusService.DidNotReceiveWithAnyArgs().PublishToRetryAsync(default!); await _serviceBusService.DidNotReceiveWithAnyArgs().PublishToRetryAsync(Arg.Any<IIntegrationMessage>());
}
[Fact]
public async Task HandleMessageAsync_UnknownError_LogsError()
{
var sutProvider = GetSutProvider();
_handler.HandleAsync(Arg.Any<string>()).ThrowsAsync<JsonException>();
Assert.True(await sutProvider.Sut.HandleMessageAsync("Bad JSON"));
_logger.Received(1).Log(
LogLevel.Error,
Arg.Any<EventId>(),
Arg.Any<object>(),
Arg.Any<Exception>(),
Arg.Any<Func<object, Exception?, string>>());
await _serviceBusService.DidNotReceiveWithAnyArgs().PublishToRetryAsync(Arg.Any<IIntegrationMessage>());
} }
} }

View File

@@ -1,4 +1,6 @@
using System.Text.Json; #nullable enable
using System.Text.Json;
using Bit.Core.AdminConsole.Models.Data.EventIntegrations; using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
using Bit.Core.Models.Data; using Bit.Core.Models.Data;
using Bit.Core.Services; using Bit.Core.Services;
@@ -17,14 +19,31 @@ namespace Bit.Core.Test.Services;
public class RabbitMqEventListenerServiceTests public class RabbitMqEventListenerServiceTests
{ {
private readonly TestListenerConfiguration _config = new(); private readonly TestListenerConfiguration _config = new();
private readonly ILogger _logger = Substitute.For<ILogger>();
private SutProvider<RabbitMqEventListenerService<TestListenerConfiguration>> GetSutProvider() private SutProvider<RabbitMqEventListenerService<TestListenerConfiguration>> GetSutProvider()
{ {
var loggerFactory = Substitute.For<ILoggerFactory>();
loggerFactory.CreateLogger<object>().ReturnsForAnyArgs(_logger);
return new SutProvider<RabbitMqEventListenerService<TestListenerConfiguration>>() return new SutProvider<RabbitMqEventListenerService<TestListenerConfiguration>>()
.SetDependency(_config) .SetDependency(_config)
.SetDependency(loggerFactory)
.Create(); .Create();
} }
[Fact]
public void Constructor_CreatesLogWithCorrectCategory()
{
var sutProvider = GetSutProvider();
var fullName = typeof(RabbitMqEventListenerService<>).FullName ?? "";
var tickIndex = fullName.IndexOf('`');
var cleanedName = tickIndex >= 0 ? fullName.Substring(0, tickIndex) : fullName;
var categoryName = cleanedName + '.' + _config.EventQueueName;
sutProvider.GetDependency<ILoggerFactory>().Received(1).CreateLogger(categoryName);
}
[Fact] [Fact]
public async Task StartAsync_CreatesQueue() public async Task StartAsync_CreatesQueue()
{ {
@@ -53,12 +72,12 @@ public class RabbitMqEventListenerServiceTests
await sutProvider.Sut.ProcessReceivedMessageAsync(eventArgs); await sutProvider.Sut.ProcessReceivedMessageAsync(eventArgs);
sutProvider.GetDependency<ILogger<RabbitMqEventListenerService<TestListenerConfiguration>>>().Received(1).Log( _logger.Received(1).Log(
LogLevel.Error, LogLevel.Error,
Arg.Any<EventId>(), Arg.Any<EventId>(),
Arg.Any<object>(), Arg.Any<object>(),
Arg.Any<JsonException>(), Arg.Any<JsonException>(),
Arg.Any<Func<object, Exception, string>>()); Arg.Any<Func<object, Exception?, string>>());
} }
[Fact] [Fact]
@@ -76,12 +95,12 @@ public class RabbitMqEventListenerServiceTests
await sutProvider.Sut.ProcessReceivedMessageAsync(eventArgs); await sutProvider.Sut.ProcessReceivedMessageAsync(eventArgs);
sutProvider.GetDependency<ILogger<RabbitMqEventListenerService<TestListenerConfiguration>>>().Received(1).Log( _logger.Received(1).Log(
LogLevel.Error, LogLevel.Error,
Arg.Any<EventId>(), Arg.Any<EventId>(),
Arg.Is<object>(o => o.ToString().Contains("Invalid JSON")), Arg.Is<object>(o => (o.ToString() ?? "").Contains("Invalid JSON")),
Arg.Any<Exception>(), Arg.Any<Exception>(),
Arg.Any<Func<object, Exception, string>>()); Arg.Any<Func<object, Exception?, string>>());
} }
[Fact] [Fact]
@@ -99,12 +118,12 @@ public class RabbitMqEventListenerServiceTests
await sutProvider.Sut.ProcessReceivedMessageAsync(eventArgs); await sutProvider.Sut.ProcessReceivedMessageAsync(eventArgs);
sutProvider.GetDependency<ILogger<RabbitMqEventListenerService<TestListenerConfiguration>>>().Received(1).Log( _logger.Received(1).Log(
LogLevel.Error, LogLevel.Error,
Arg.Any<EventId>(), Arg.Any<EventId>(),
Arg.Any<object>(), Arg.Any<object>(),
Arg.Any<JsonException>(), Arg.Any<JsonException>(),
Arg.Any<Func<object, Exception, string>>()); Arg.Any<Func<object, Exception?, string>>());
} }
[Fact] [Fact]
@@ -122,12 +141,12 @@ public class RabbitMqEventListenerServiceTests
await sutProvider.Sut.ProcessReceivedMessageAsync(eventArgs); await sutProvider.Sut.ProcessReceivedMessageAsync(eventArgs);
sutProvider.GetDependency<ILogger<RabbitMqEventListenerService<TestListenerConfiguration>>>().Received(1).Log( _logger.Received(1).Log(
LogLevel.Error, LogLevel.Error,
Arg.Any<EventId>(), Arg.Any<EventId>(),
Arg.Any<object>(), Arg.Any<object>(),
Arg.Any<JsonException>(), Arg.Any<JsonException>(),
Arg.Any<Func<object, Exception, string>>()); Arg.Any<Func<object, Exception?, string>>());
} }
[Theory, BitAutoData] [Theory, BitAutoData]

View File

@@ -1,9 +1,12 @@
using System.Text; #nullable enable
using System.Text;
using Bit.Core.AdminConsole.Models.Data.EventIntegrations; using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
using Bit.Core.Services; using Bit.Core.Services;
using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.AutoFixture.Attributes;
using Bit.Test.Common.Helpers; using Bit.Test.Common.Helpers;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Time.Testing; using Microsoft.Extensions.Time.Testing;
using NSubstitute; using NSubstitute;
using RabbitMQ.Client; using RabbitMQ.Client;
@@ -17,14 +20,18 @@ public class RabbitMqIntegrationListenerServiceTests
{ {
private readonly DateTime _now = new DateTime(2014, 3, 2, 1, 0, 0, DateTimeKind.Utc); private readonly DateTime _now = new DateTime(2014, 3, 2, 1, 0, 0, DateTimeKind.Utc);
private readonly IIntegrationHandler _handler = Substitute.For<IIntegrationHandler>(); private readonly IIntegrationHandler _handler = Substitute.For<IIntegrationHandler>();
private readonly ILogger _logger = Substitute.For<ILogger>();
private readonly IRabbitMqService _rabbitMqService = Substitute.For<IRabbitMqService>(); private readonly IRabbitMqService _rabbitMqService = Substitute.For<IRabbitMqService>();
private readonly TestListenerConfiguration _config = new(); private readonly TestListenerConfiguration _config = new();
private SutProvider<RabbitMqIntegrationListenerService<TestListenerConfiguration>> GetSutProvider() private SutProvider<RabbitMqIntegrationListenerService<TestListenerConfiguration>> GetSutProvider()
{ {
var loggerFactory = Substitute.For<ILoggerFactory>();
loggerFactory.CreateLogger<object>().ReturnsForAnyArgs(_logger);
var sutProvider = new SutProvider<RabbitMqIntegrationListenerService<TestListenerConfiguration>>() var sutProvider = new SutProvider<RabbitMqIntegrationListenerService<TestListenerConfiguration>>()
.SetDependency(_config) .SetDependency(_config)
.SetDependency(_handler) .SetDependency(_handler)
.SetDependency(loggerFactory)
.SetDependency(_rabbitMqService) .SetDependency(_rabbitMqService)
.WithFakeTimeProvider() .WithFakeTimeProvider()
.Create(); .Create();
@@ -33,6 +40,19 @@ public class RabbitMqIntegrationListenerServiceTests
return sutProvider; return sutProvider;
} }
[Fact]
public void Constructor_CreatesLogWithCorrectCategory()
{
var sutProvider = GetSutProvider();
var fullName = typeof(RabbitMqIntegrationListenerService<>).FullName ?? "";
var tickIndex = fullName.IndexOf('`');
var cleanedName = tickIndex >= 0 ? fullName.Substring(0, tickIndex) : fullName;
var categoryName = cleanedName + '.' + _config.IntegrationQueueName;
sutProvider.GetDependency<ILoggerFactory>().Received(1).CreateLogger(categoryName);
}
[Fact] [Fact]
public async Task StartAsync_CreatesQueues() public async Task StartAsync_CreatesQueues()
{ {
@@ -71,6 +91,7 @@ public class RabbitMqIntegrationListenerServiceTests
_handler.HandleAsync(Arg.Any<string>()).Returns(result); _handler.HandleAsync(Arg.Any<string>()).Returns(result);
var expected = IntegrationMessage<WebhookIntegrationConfiguration>.FromJson(message.ToJson()); var expected = IntegrationMessage<WebhookIntegrationConfiguration>.FromJson(message.ToJson());
Assert.NotNull(expected);
await sutProvider.Sut.ProcessReceivedMessageAsync(eventArgs, cancellationToken); await sutProvider.Sut.ProcessReceivedMessageAsync(eventArgs, cancellationToken);
@@ -81,10 +102,17 @@ public class RabbitMqIntegrationListenerServiceTests
Arg.Is(AssertHelper.AssertPropertyEqual(expected, new[] { "DelayUntilDate" })), Arg.Is(AssertHelper.AssertPropertyEqual(expected, new[] { "DelayUntilDate" })),
Arg.Any<CancellationToken>()); Arg.Any<CancellationToken>());
_logger.Received().Log(
LogLevel.Warning,
Arg.Any<EventId>(),
Arg.Is<object>(o => (o.ToString() ?? "").Contains("Non-retryable failure")),
Arg.Any<Exception?>(),
Arg.Any<Func<object, Exception?, string>>());
await _rabbitMqService.DidNotReceiveWithAnyArgs() await _rabbitMqService.DidNotReceiveWithAnyArgs()
.RepublishToRetryQueueAsync(default, default); .RepublishToRetryQueueAsync(Arg.Any<IChannel>(), Arg.Any<BasicDeliverEventArgs>());
await _rabbitMqService.DidNotReceiveWithAnyArgs() await _rabbitMqService.DidNotReceiveWithAnyArgs()
.PublishToRetryAsync(default, default, default); .PublishToRetryAsync(Arg.Any<IChannel>(), Arg.Any<IntegrationMessage>(), Arg.Any<CancellationToken>());
} }
[Theory, BitAutoData] [Theory, BitAutoData]
@@ -110,6 +138,7 @@ public class RabbitMqIntegrationListenerServiceTests
_handler.HandleAsync(Arg.Any<string>()).Returns(result); _handler.HandleAsync(Arg.Any<string>()).Returns(result);
var expected = IntegrationMessage<WebhookIntegrationConfiguration>.FromJson(message.ToJson()); var expected = IntegrationMessage<WebhookIntegrationConfiguration>.FromJson(message.ToJson());
Assert.NotNull(expected);
await sutProvider.Sut.ProcessReceivedMessageAsync(eventArgs, cancellationToken); await sutProvider.Sut.ProcessReceivedMessageAsync(eventArgs, cancellationToken);
@@ -119,10 +148,17 @@ public class RabbitMqIntegrationListenerServiceTests
Arg.Is(AssertHelper.AssertPropertyEqual(expected, new[] { "DelayUntilDate" })), Arg.Is(AssertHelper.AssertPropertyEqual(expected, new[] { "DelayUntilDate" })),
Arg.Any<CancellationToken>()); Arg.Any<CancellationToken>());
_logger.Received().Log(
LogLevel.Warning,
Arg.Any<EventId>(),
Arg.Is<object>(o => (o.ToString() ?? "").Contains("Max retry attempts reached")),
Arg.Any<Exception?>(),
Arg.Any<Func<object, Exception?, string>>());
await _rabbitMqService.DidNotReceiveWithAnyArgs() await _rabbitMqService.DidNotReceiveWithAnyArgs()
.RepublishToRetryQueueAsync(default, default); .RepublishToRetryQueueAsync(Arg.Any<IChannel>(), Arg.Any<BasicDeliverEventArgs>());
await _rabbitMqService.DidNotReceiveWithAnyArgs() await _rabbitMqService.DidNotReceiveWithAnyArgs()
.PublishToRetryAsync(default, default, default); .PublishToRetryAsync(Arg.Any<IChannel>(), Arg.Any<IntegrationMessage>(), Arg.Any<CancellationToken>());
} }
[Theory, BitAutoData] [Theory, BitAutoData]
@@ -149,6 +185,7 @@ public class RabbitMqIntegrationListenerServiceTests
_handler.HandleAsync(Arg.Any<string>()).Returns(result); _handler.HandleAsync(Arg.Any<string>()).Returns(result);
var expected = IntegrationMessage<WebhookIntegrationConfiguration>.FromJson(message.ToJson()); var expected = IntegrationMessage<WebhookIntegrationConfiguration>.FromJson(message.ToJson());
Assert.NotNull(expected);
await sutProvider.Sut.ProcessReceivedMessageAsync(eventArgs, cancellationToken); await sutProvider.Sut.ProcessReceivedMessageAsync(eventArgs, cancellationToken);
@@ -161,9 +198,9 @@ public class RabbitMqIntegrationListenerServiceTests
Arg.Any<CancellationToken>()); Arg.Any<CancellationToken>());
await _rabbitMqService.DidNotReceiveWithAnyArgs() await _rabbitMqService.DidNotReceiveWithAnyArgs()
.RepublishToRetryQueueAsync(default, default); .RepublishToRetryQueueAsync(Arg.Any<IChannel>(), Arg.Any<BasicDeliverEventArgs>());
await _rabbitMqService.DidNotReceiveWithAnyArgs() await _rabbitMqService.DidNotReceiveWithAnyArgs()
.PublishToDeadLetterAsync(default, default, default); .PublishToDeadLetterAsync(Arg.Any<IChannel>(), Arg.Any<IntegrationMessage>(), Arg.Any<CancellationToken>());
} }
[Theory, BitAutoData] [Theory, BitAutoData]
@@ -191,11 +228,11 @@ public class RabbitMqIntegrationListenerServiceTests
await _handler.Received(1).HandleAsync(Arg.Is(message.ToJson())); await _handler.Received(1).HandleAsync(Arg.Is(message.ToJson()));
await _rabbitMqService.DidNotReceiveWithAnyArgs() await _rabbitMqService.DidNotReceiveWithAnyArgs()
.RepublishToRetryQueueAsync(default, default); .RepublishToRetryQueueAsync(Arg.Any<IChannel>(), Arg.Any<BasicDeliverEventArgs>());
await _rabbitMqService.DidNotReceiveWithAnyArgs() await _rabbitMqService.DidNotReceiveWithAnyArgs()
.PublishToRetryAsync(default, default, default); .PublishToRetryAsync(Arg.Any<IChannel>(), Arg.Any<IntegrationMessage>(), Arg.Any<CancellationToken>());
await _rabbitMqService.DidNotReceiveWithAnyArgs() await _rabbitMqService.DidNotReceiveWithAnyArgs()
.PublishToDeadLetterAsync(default, default, default); .PublishToDeadLetterAsync(Arg.Any<IChannel>(), Arg.Any<IntegrationMessage>(), Arg.Any<CancellationToken>());
} }
[Theory, BitAutoData] [Theory, BitAutoData]
@@ -221,10 +258,10 @@ public class RabbitMqIntegrationListenerServiceTests
await _rabbitMqService.Received(1) await _rabbitMqService.Received(1)
.RepublishToRetryQueueAsync(Arg.Any<IChannel>(), Arg.Any<BasicDeliverEventArgs>()); .RepublishToRetryQueueAsync(Arg.Any<IChannel>(), Arg.Any<BasicDeliverEventArgs>());
await _handler.DidNotReceiveWithAnyArgs().HandleAsync(default); await _handler.DidNotReceiveWithAnyArgs().HandleAsync(Arg.Any<string>());
await _rabbitMqService.DidNotReceiveWithAnyArgs() await _rabbitMqService.DidNotReceiveWithAnyArgs()
.PublishToRetryAsync(default, default, default); .PublishToRetryAsync(Arg.Any<IChannel>(), Arg.Any<IntegrationMessage>(), Arg.Any<CancellationToken>());
await _rabbitMqService.DidNotReceiveWithAnyArgs() await _rabbitMqService.DidNotReceiveWithAnyArgs()
.PublishToDeadLetterAsync(default, default, default); .PublishToDeadLetterAsync(Arg.Any<IChannel>(), Arg.Any<IntegrationMessage>(), Arg.Any<CancellationToken>());
} }
} }