From c9dd523980c13059207dda0ca3d09408a66ab53e Mon Sep 17 00:00:00 2001 From: romankr Date: Sat, 16 Nov 2024 02:47:57 +0400 Subject: [PATCH] Decompose UpcomingEventsFunction --- .../Tests/Functions/EventResultsFunction.cs | 6 +- .../Tests/Functions/UpcomingEventsFunction.cs | 59 +++++++-------- .../Tests/Processors/EventResultProcessor.cs | 6 +- .../Processors/ServiceCollectionExtensions.cs | 22 +++++- .../Processors/UpcomingEventProcessor.cs | 71 +++++++++++++++++++ .../Functions/EventResultsFunction.cs | 6 +- .../Functions/UpcomingEventsFunction.cs | 12 ++-- .../ServiceCollectionExtensions.cs | 1 + .../Processors/IUpcomingEventsProcessor.cs | 8 +++ .../Processors/UpcomingEventsProcessor.cs | 12 ++++ 10 files changed, 155 insertions(+), 48 deletions(-) create mode 100644 OddsCollector.Functions.Tests/Tests/Processors/UpcomingEventProcessor.cs create mode 100644 OddsCollector.Functions/Processors/IUpcomingEventsProcessor.cs create mode 100644 OddsCollector.Functions/Processors/UpcomingEventsProcessor.cs diff --git a/OddsCollector.Functions.Tests/Tests/Functions/EventResultsFunction.cs b/OddsCollector.Functions.Tests/Tests/Functions/EventResultsFunction.cs index a47a18b..f503293 100644 --- a/OddsCollector.Functions.Tests/Tests/Functions/EventResultsFunction.cs +++ b/OddsCollector.Functions.Tests/Tests/Functions/EventResultsFunction.cs @@ -30,7 +30,7 @@ public async Task Run_WithValidMessages_ReturnsEventResultList() actualEventResults.Should().NotBeNull().And.BeEquivalentTo(expectedEventResults); loggerMock.LatestRecord.Level.Should().Be(LogLevel.Information); - loggerMock.LatestRecord.Message.Should().Be("1 events received"); + loggerMock.LatestRecord.Message.Should().Be("1 event(s) received"); } [Test] @@ -54,7 +54,7 @@ public async Task Run_WithException_ReturnsEmptyEventResultList() actualEventResults.Should().NotBeNull().And.BeEmpty(); loggerMock.LatestRecord.Level.Should().Be(LogLevel.Error); - loggerMock.LatestRecord.Message.Should().Be("Failed to get event results"); + loggerMock.LatestRecord.Message.Should().Be("Failed to get events"); loggerMock.LatestRecord.Exception.Should().Be(exception); } @@ -81,6 +81,6 @@ public async Task Run_WithEmptyMessages_ReturnsEmptyEventResultList() actualEventResults.Should().NotBeNull().And.BeEmpty(); loggerMock.LatestRecord.Level.Should().Be(LogLevel.Warning); - loggerMock.LatestRecord.Message.Should().Be("No results received"); + loggerMock.LatestRecord.Message.Should().Be("No events received"); } } diff --git a/OddsCollector.Functions.Tests/Tests/Functions/UpcomingEventsFunction.cs b/OddsCollector.Functions.Tests/Tests/Functions/UpcomingEventsFunction.cs index 4189578..d331ce7 100644 --- a/OddsCollector.Functions.Tests/Tests/Functions/UpcomingEventsFunction.cs +++ b/OddsCollector.Functions.Tests/Tests/Functions/UpcomingEventsFunction.cs @@ -1,28 +1,26 @@ -using System.Diagnostics.CodeAnalysis; -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Testing; using NSubstitute.ExceptionExtensions; using OddsCollector.Functions.Models; -using OddsCollector.Functions.OddsApi; -using LoggerFactory = OddsCollector.Functions.Tests.Infrastructure.Logger.LoggerFactory; +using OddsCollector.Functions.Processors; namespace OddsCollector.Functions.Tests.Tests.Functions; internal class UpcomingEventsFunction { [Test] - [SuppressMessage("Usage", "CA2254:Template should be a static expression")] - public async Task Run_WithValidParameters_ReturnsEventResults() + public async Task Run_WithValidMessages_ReturnsEventResultList() { // Arrange - IEnumerable expectedEventResults = new List { new() }; + IEnumerable expectedEventResults = [new()]; - var loggerMock = LoggerFactory.GetLoggerMock(); + var loggerMock = new FakeLogger(); - var clientStub = Substitute.For(); - clientStub.GetUpcomingEventsAsync(Arg.Any(), Arg.Any(), Arg.Any()) - .Returns(Task.FromResult(expectedEventResults)); + var processorStub = Substitute.For(); - var function = new OddsCollector.Functions.Functions.UpcomingEventsFunction(loggerMock, clientStub); + processorStub.GetUpcomingEventsAsync(Arg.Any()).Returns(Task.FromResult(expectedEventResults)); + + var function = new OddsCollector.Functions.Functions.UpcomingEventsFunction(loggerMock, processorStub); // Act var eventResults = await function.Run(new CancellationToken()); @@ -30,22 +28,23 @@ public async Task Run_WithValidParameters_ReturnsEventResults() // Assert eventResults.Should().NotBeNull().And.BeEquivalentTo(expectedEventResults); - loggerMock.ReceivedWithAnyArgs().LogInformation(string.Empty, 1); + loggerMock.LatestRecord.Level.Should().Be(LogLevel.Information); + loggerMock.LatestRecord.Message.Should().Be("1 event(s) received"); } [Test] - public async Task Run_WithException_ReturnsEmptyEventResults() + public async Task Run_WithException_ReturnsEmptyEventResultList() { // Arrange var exception = new Exception(); - var loggerMock = LoggerFactory.GetLoggerMock(); + var loggerMock = new FakeLogger(); + + var processorStub = Substitute.For(); - var clientStub = Substitute.For(); - clientStub.GetUpcomingEventsAsync(Arg.Any(), Arg.Any(), Arg.Any()) - .Throws(exception); + processorStub.GetUpcomingEventsAsync(Arg.Any()).Throws(exception); - var function = new OddsCollector.Functions.Functions.UpcomingEventsFunction(loggerMock, clientStub); + var function = new OddsCollector.Functions.Functions.UpcomingEventsFunction(loggerMock, processorStub); // Act var eventResults = await function.Run(new CancellationToken()); @@ -53,23 +52,24 @@ public async Task Run_WithException_ReturnsEmptyEventResults() // Assert eventResults.Should().NotBeNull().And.BeEmpty(); - loggerMock.Received().LogError(exception, "Failed to get upcoming events"); + loggerMock.LatestRecord.Level.Should().Be(LogLevel.Error); + loggerMock.LatestRecord.Message.Should().Be("Failed to get events"); + loggerMock.LatestRecord.Exception.Should().Be(exception); } [Test] - [SuppressMessage("Usage", "CA2254:Template should be a static expression")] - public async Task Run_WithValidParameters_ReturnsNoEventResults() + public async Task Run_WithEmptyMessages_ReturnsEmptyEventResultList() { // Arrange - IEnumerable expectedEventResults = new List(); + IEnumerable expectedEventResults = []; + + var loggerMock = new FakeLogger(); - var loggerMock = LoggerFactory.GetLoggerMock(); + var processorStub = Substitute.For(); - var clientStub = Substitute.For(); - clientStub.GetUpcomingEventsAsync(Arg.Any(), Arg.Any(), Arg.Any()) - .Returns(Task.FromResult(expectedEventResults)); + processorStub.GetUpcomingEventsAsync(Arg.Any()).Returns(Task.FromResult(expectedEventResults)); - var function = new OddsCollector.Functions.Functions.UpcomingEventsFunction(loggerMock, clientStub); + var function = new OddsCollector.Functions.Functions.UpcomingEventsFunction(loggerMock, processorStub); // Act var eventResults = await function.Run(new CancellationToken()); @@ -77,6 +77,7 @@ public async Task Run_WithValidParameters_ReturnsNoEventResults() // Assert eventResults.Should().NotBeNull().And.BeEquivalentTo(expectedEventResults); - loggerMock.ReceivedWithAnyArgs().LogWarning(string.Empty, 1); + loggerMock.LatestRecord.Level.Should().Be(LogLevel.Warning); + loggerMock.LatestRecord.Message.Should().Be("No events received"); } } diff --git a/OddsCollector.Functions.Tests/Tests/Processors/EventResultProcessor.cs b/OddsCollector.Functions.Tests/Tests/Processors/EventResultProcessor.cs index 0d7b57f..4905cd3 100644 --- a/OddsCollector.Functions.Tests/Tests/Processors/EventResultProcessor.cs +++ b/OddsCollector.Functions.Tests/Tests/Processors/EventResultProcessor.cs @@ -5,7 +5,7 @@ namespace OddsCollector.Functions.Tests.Tests.Processors; internal class EventResultProcessor { [Test] - public async Task PassesTraceId() + public async Task GetEventResultsAsync_PassesTraceId() { // Arrange var clientMock = Substitute.For(); @@ -26,7 +26,7 @@ public async Task PassesTraceId() } [Test] - public async Task PassesDateTime() + public async Task GetEventResultsAsync_PassesDateTime() { // Arrange var clientMock = Substitute.For(); @@ -47,7 +47,7 @@ public async Task PassesDateTime() } [Test] - public async Task PassesCancellationToken() + public async Task GetEventResultsAsync_PassesCancellationToken() { // Arrange var cancellationToken = new CancellationToken(); diff --git a/OddsCollector.Functions.Tests/Tests/Processors/ServiceCollectionExtensions.cs b/OddsCollector.Functions.Tests/Tests/Processors/ServiceCollectionExtensions.cs index a79daf5..dabe9a8 100644 --- a/OddsCollector.Functions.Tests/Tests/Processors/ServiceCollectionExtensions.cs +++ b/OddsCollector.Functions.Tests/Tests/Processors/ServiceCollectionExtensions.cs @@ -7,18 +7,34 @@ namespace OddsCollector.Functions.Tests.Tests.Processors; internal class ServiceCollectionExtensions { [Test] - public void AddFunctionProcessors_AddsProperlyConfiguredFunctionProcessors() + public void AddFunctionProcessors_AddsEventResultProcessor() { var services = new ServiceCollection(); services.AddFunctionProcessors(); - var strategyDescriptor = + var descriptor = services.FirstOrDefault( x => x.ServiceType == typeof(IEventResultProcessor) && x.ImplementationType == typeof(OddsCollector.Functions.Processors.EventResultProcessor) && x.Lifetime == ServiceLifetime.Singleton); - strategyDescriptor.Should().NotBeNull(); + descriptor.Should().NotBeNull(); + } + + [Test] + public void AddFunctionProcessors_AddsUpcomingEventsProcessor() + { + var services = new ServiceCollection(); + + services.AddFunctionProcessors(); + + var descriptor = + services.FirstOrDefault( + x => x.ServiceType == typeof(IUpcomingEventsProcessor) + && x.ImplementationType == typeof(OddsCollector.Functions.Processors.UpcomingEventsProcessor) + && x.Lifetime == ServiceLifetime.Singleton); + + descriptor.Should().NotBeNull(); } } diff --git a/OddsCollector.Functions.Tests/Tests/Processors/UpcomingEventProcessor.cs b/OddsCollector.Functions.Tests/Tests/Processors/UpcomingEventProcessor.cs new file mode 100644 index 0000000..a9e9d1b --- /dev/null +++ b/OddsCollector.Functions.Tests/Tests/Processors/UpcomingEventProcessor.cs @@ -0,0 +1,71 @@ +using OddsCollector.Functions.OddsApi; + +namespace OddsCollector.Functions.Tests.Tests.Processors; + +internal class UpcomingEventProcessor +{ + [Test] + public async Task GetUpcomingEventsAsync_PassesTraceId() + { + // Arrange + var clientMock = Substitute.For(); + + var processor = new OddsCollector.Functions.Processors.UpcomingEventsProcessor(clientMock); + + // Act + await processor.GetUpcomingEventsAsync(new CancellationToken()); + + // Assert + var calls = clientMock.ReceivedCalls().ToList(); + calls.Should().NotBeNullOrEmpty().And.HaveCount(1); + + var arguments = calls[0].GetArguments(); + arguments.Should().NotBeNullOrEmpty().And.HaveCount(3); + + arguments[0].Should().BeOfType().And.NotBe(Guid.Empty); + } + + [Test] + public async Task GetUpcomingEventsAsync_PassesDateTime() + { + // Arrange + var clientMock = Substitute.For(); + + var processor = new OddsCollector.Functions.Processors.UpcomingEventsProcessor(clientMock); + + // Act + await processor.GetUpcomingEventsAsync(new CancellationToken()); + + // Assert + var calls = clientMock.ReceivedCalls().ToList(); + calls.Should().NotBeNullOrEmpty().And.HaveCount(1); + + var arguments = calls[0].GetArguments(); + arguments.Should().NotBeNullOrEmpty().And.HaveCount(3); + + arguments[1].Should().BeOfType().And.NotBe(DateTime.MinValue).And.NotBe(DateTime.MaxValue); + } + + [Test] + public async Task GetUpcomingEventsAsync_PassesCancellationToken() + { + // Arrange + var cancellationToken = new CancellationToken(); + + var clientMock = Substitute.For(); + + var processor = new OddsCollector.Functions.Processors.UpcomingEventsProcessor(clientMock); + + // Act + await processor.GetUpcomingEventsAsync(cancellationToken); + + // Assert + var calls = clientMock.ReceivedCalls().ToList(); + calls.Should().NotBeNullOrEmpty().And.HaveCount(1); + + var arguments = calls[0].GetArguments(); + arguments.Should().NotBeNullOrEmpty().And.HaveCount(3); + + arguments[2].Should().BeOfType().And.Be(cancellationToken); + } +} diff --git a/OddsCollector.Functions/Functions/EventResultsFunction.cs b/OddsCollector.Functions/Functions/EventResultsFunction.cs index ce25272..b29c11e 100644 --- a/OddsCollector.Functions/Functions/EventResultsFunction.cs +++ b/OddsCollector.Functions/Functions/EventResultsFunction.cs @@ -20,18 +20,18 @@ public async Task Run( if (results.Length == 0) { - logger.LogWarning("No results received"); + logger.LogWarning("No events received"); } else { - logger.LogInformation("{Length} events received", results.Length); + logger.LogInformation("{Length} event(s) received", results.Length); } return results; } catch (Exception exception) { - logger.LogError(exception, "Failed to get event results"); + logger.LogError(exception, "Failed to get events"); } return []; diff --git a/OddsCollector.Functions/Functions/UpcomingEventsFunction.cs b/OddsCollector.Functions/Functions/UpcomingEventsFunction.cs index b0e2d0c..bfd433a 100644 --- a/OddsCollector.Functions/Functions/UpcomingEventsFunction.cs +++ b/OddsCollector.Functions/Functions/UpcomingEventsFunction.cs @@ -1,11 +1,11 @@ using Microsoft.Azure.Functions.Worker; using Microsoft.Extensions.Logging; using OddsCollector.Functions.Models; -using OddsCollector.Functions.OddsApi; +using OddsCollector.Functions.Processors; namespace OddsCollector.Functions.Functions; -internal class UpcomingEventsFunction(ILogger logger, IOddsApiClient client) +internal class UpcomingEventsFunction(ILogger logger, IUpcomingEventsProcessor processor) { [Function(nameof(UpcomingEventsFunction))] [ServiceBusOutput("%ServiceBus:Queue%", Connection = "ServiceBus:Connection")] @@ -15,9 +15,7 @@ public async Task Run( { try { - var events = - (await client.GetUpcomingEventsAsync(Guid.NewGuid(), DateTime.UtcNow, cancellationToken)) - .ToArray(); + var events = (await processor.GetUpcomingEventsAsync(cancellationToken)).ToArray(); if (events.Length == 0) { @@ -25,14 +23,14 @@ public async Task Run( } else { - logger.LogInformation("{Length} events received", events.Length); + logger.LogInformation("{Length} event(s) received", events.Length); } return events; } catch (Exception exception) { - logger.LogError(exception, "Failed to get upcoming events"); + logger.LogError(exception, "Failed to get events"); } return []; diff --git a/OddsCollector.Functions/Processors/Configuration/ServiceCollectionExtensions.cs b/OddsCollector.Functions/Processors/Configuration/ServiceCollectionExtensions.cs index b10e60d..fb52088 100644 --- a/OddsCollector.Functions/Processors/Configuration/ServiceCollectionExtensions.cs +++ b/OddsCollector.Functions/Processors/Configuration/ServiceCollectionExtensions.cs @@ -7,5 +7,6 @@ internal static class ServiceCollectionExtensions public static void AddFunctionProcessors(this IServiceCollection services) { services.AddSingleton(); + services.AddSingleton(); } } diff --git a/OddsCollector.Functions/Processors/IUpcomingEventsProcessor.cs b/OddsCollector.Functions/Processors/IUpcomingEventsProcessor.cs new file mode 100644 index 0000000..729794b --- /dev/null +++ b/OddsCollector.Functions/Processors/IUpcomingEventsProcessor.cs @@ -0,0 +1,8 @@ +using OddsCollector.Functions.Models; + +namespace OddsCollector.Functions.Processors; + +internal interface IUpcomingEventsProcessor +{ + Task> GetUpcomingEventsAsync(CancellationToken cancellationToken); +} diff --git a/OddsCollector.Functions/Processors/UpcomingEventsProcessor.cs b/OddsCollector.Functions/Processors/UpcomingEventsProcessor.cs new file mode 100644 index 0000000..f0469c7 --- /dev/null +++ b/OddsCollector.Functions/Processors/UpcomingEventsProcessor.cs @@ -0,0 +1,12 @@ +using OddsCollector.Functions.Models; +using OddsCollector.Functions.OddsApi; + +namespace OddsCollector.Functions.Processors; + +internal class UpcomingEventsProcessor(IOddsApiClient client) : IUpcomingEventsProcessor +{ + public async Task> GetUpcomingEventsAsync(CancellationToken cancellationToken) + { + return await client.GetUpcomingEventsAsync(Guid.NewGuid(), DateTime.UtcNow, cancellationToken); + } +}