Skip to content

Commit

Permalink
Merge pull request #57 from romankr/romankr/upcoming-events-function
Browse files Browse the repository at this point in the history
  • Loading branch information
getencapsulated authored Nov 16, 2024
2 parents dd01266 + c9dd523 commit b6ca08e
Show file tree
Hide file tree
Showing 10 changed files with 155 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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);
}

Expand All @@ -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");
}
}
Original file line number Diff line number Diff line change
@@ -1,82 +1,83 @@
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<UpcomingEvent> expectedEventResults = new List<UpcomingEvent> { new() };
IEnumerable<UpcomingEvent> expectedEventResults = [new()];

var loggerMock = LoggerFactory.GetLoggerMock<OddsCollector.Functions.Functions.UpcomingEventsFunction>();
var loggerMock = new FakeLogger<OddsCollector.Functions.Functions.UpcomingEventsFunction>();

var clientStub = Substitute.For<IOddsApiClient>();
clientStub.GetUpcomingEventsAsync(Arg.Any<Guid>(), Arg.Any<DateTime>(), Arg.Any<CancellationToken>())
.Returns(Task.FromResult(expectedEventResults));
var processorStub = Substitute.For<IUpcomingEventsProcessor>();

var function = new OddsCollector.Functions.Functions.UpcomingEventsFunction(loggerMock, clientStub);
processorStub.GetUpcomingEventsAsync(Arg.Any<CancellationToken>()).Returns(Task.FromResult(expectedEventResults));

var function = new OddsCollector.Functions.Functions.UpcomingEventsFunction(loggerMock, processorStub);

// Act
var eventResults = await function.Run(new CancellationToken());

// 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<OddsCollector.Functions.Functions.UpcomingEventsFunction>();
var loggerMock = new FakeLogger<OddsCollector.Functions.Functions.UpcomingEventsFunction>();

var processorStub = Substitute.For<IUpcomingEventsProcessor>();

var clientStub = Substitute.For<IOddsApiClient>();
clientStub.GetUpcomingEventsAsync(Arg.Any<Guid>(), Arg.Any<DateTime>(), Arg.Any<CancellationToken>())
.Throws(exception);
processorStub.GetUpcomingEventsAsync(Arg.Any<CancellationToken>()).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());

// 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<UpcomingEvent> expectedEventResults = new List<UpcomingEvent>();
IEnumerable<UpcomingEvent> expectedEventResults = [];

var loggerMock = new FakeLogger<OddsCollector.Functions.Functions.UpcomingEventsFunction>();

var loggerMock = LoggerFactory.GetLoggerMock<OddsCollector.Functions.Functions.UpcomingEventsFunction>();
var processorStub = Substitute.For<IUpcomingEventsProcessor>();

var clientStub = Substitute.For<IOddsApiClient>();
clientStub.GetUpcomingEventsAsync(Arg.Any<Guid>(), Arg.Any<DateTime>(), Arg.Any<CancellationToken>())
.Returns(Task.FromResult(expectedEventResults));
processorStub.GetUpcomingEventsAsync(Arg.Any<CancellationToken>()).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());

// 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");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<IOddsApiClient>();
Expand All @@ -26,7 +26,7 @@ public async Task PassesTraceId()
}

[Test]
public async Task PassesDateTime()
public async Task GetEventResultsAsync_PassesDateTime()
{
// Arrange
var clientMock = Substitute.For<IOddsApiClient>();
Expand All @@ -47,7 +47,7 @@ public async Task PassesDateTime()
}

[Test]
public async Task PassesCancellationToken()
public async Task GetEventResultsAsync_PassesCancellationToken()
{
// Arrange
var cancellationToken = new CancellationToken();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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<IOddsApiClient>();

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<Guid>().And.NotBe(Guid.Empty);
}

[Test]
public async Task GetUpcomingEventsAsync_PassesDateTime()
{
// Arrange
var clientMock = Substitute.For<IOddsApiClient>();

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<DateTime>().And.NotBe(DateTime.MinValue).And.NotBe(DateTime.MaxValue);
}

[Test]
public async Task GetUpcomingEventsAsync_PassesCancellationToken()
{
// Arrange
var cancellationToken = new CancellationToken();

var clientMock = Substitute.For<IOddsApiClient>();

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<CancellationToken>().And.Be(cancellationToken);
}
}
6 changes: 3 additions & 3 deletions OddsCollector.Functions/Functions/EventResultsFunction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,18 @@ public async Task<EventResult[]> 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 [];
Expand Down
12 changes: 5 additions & 7 deletions OddsCollector.Functions/Functions/UpcomingEventsFunction.cs
Original file line number Diff line number Diff line change
@@ -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<UpcomingEventsFunction> logger, IOddsApiClient client)
internal class UpcomingEventsFunction(ILogger<UpcomingEventsFunction> logger, IUpcomingEventsProcessor processor)
{
[Function(nameof(UpcomingEventsFunction))]
[ServiceBusOutput("%ServiceBus:Queue%", Connection = "ServiceBus:Connection")]
Expand All @@ -15,24 +15,22 @@ public async Task<UpcomingEvent[]> Run(
{
try
{
var events =
(await client.GetUpcomingEventsAsync(Guid.NewGuid(), DateTime.UtcNow, cancellationToken))
.ToArray();
var events = (await processor.GetUpcomingEventsAsync(cancellationToken)).ToArray();

if (events.Length == 0)
{
logger.LogWarning("No events received");
}
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 [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ internal static class ServiceCollectionExtensions
public static void AddFunctionProcessors(this IServiceCollection services)
{
services.AddSingleton<IEventResultProcessor, EventResultProcessor>();
services.AddSingleton<IUpcomingEventsProcessor, UpcomingEventsProcessor>();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using OddsCollector.Functions.Models;

namespace OddsCollector.Functions.Processors;

internal interface IUpcomingEventsProcessor
{
Task<IEnumerable<UpcomingEvent>> GetUpcomingEventsAsync(CancellationToken cancellationToken);
}
12 changes: 12 additions & 0 deletions OddsCollector.Functions/Processors/UpcomingEventsProcessor.cs
Original file line number Diff line number Diff line change
@@ -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<IEnumerable<UpcomingEvent>> GetUpcomingEventsAsync(CancellationToken cancellationToken)
{
return await client.GetUpcomingEventsAsync(Guid.NewGuid(), DateTime.UtcNow, cancellationToken);
}
}

0 comments on commit b6ca08e

Please sign in to comment.