diff --git a/OddsCollector.Functions.Tests/Infrastructure/CancellationToken/CancellationTokenGenerator.cs b/OddsCollector.Functions.Tests/Infrastructure/CancellationToken/CancellationTokenGenerator.cs new file mode 100644 index 0000000..ef60ada --- /dev/null +++ b/OddsCollector.Functions.Tests/Infrastructure/CancellationToken/CancellationTokenGenerator.cs @@ -0,0 +1,13 @@ +namespace OddsCollector.Functions.Tests.Infrastructure.CancellationToken; + +internal static class CancellationTokenGenerator +{ + public static async Task GetRequestedForCancellationToken() + { + var cancellationTokenSource = new CancellationTokenSource(); + + await cancellationTokenSource.CancelAsync(); + + return cancellationTokenSource.Token; + } +} diff --git a/OddsCollector.Functions.Tests/Infrastructure/Logger/LoggerFactory.cs b/OddsCollector.Functions.Tests/Infrastructure/Logger/LoggerFactory.cs new file mode 100644 index 0000000..902b694 --- /dev/null +++ b/OddsCollector.Functions.Tests/Infrastructure/Logger/LoggerFactory.cs @@ -0,0 +1,11 @@ +using Microsoft.Extensions.Logging; + +namespace OddsCollector.Functions.Tests.Infrastructure.Logger; + +internal static class LoggerFactory +{ + public static ILogger GetLoggerMock() where T : class + { + return Substitute.For>(); + } +} diff --git a/OddsCollector.Functions.Tests/Infrastructure/ServiceBus/ServiceBusMessageActionFactory.cs b/OddsCollector.Functions.Tests/Infrastructure/ServiceBus/ServiceBusMessageActionFactory.cs new file mode 100644 index 0000000..a204fee --- /dev/null +++ b/OddsCollector.Functions.Tests/Infrastructure/ServiceBus/ServiceBusMessageActionFactory.cs @@ -0,0 +1,11 @@ +using Microsoft.Azure.Functions.Worker; + +namespace OddsCollector.Functions.Tests.Infrastructure.ServiceBus; + +internal static class ServiceBusMessageActionsFactory +{ + public static ServiceBusMessageActions GetServiceBusMessageActionsMock() + { + return Substitute.For(); + } +} diff --git a/OddsCollector.Functions.Tests/Infrastructure/ServiceBus/ServiceBusReceivedMessageFactory.cs b/OddsCollector.Functions.Tests/Infrastructure/ServiceBus/ServiceBusReceivedMessageFactory.cs index 5f28063..48c721a 100644 --- a/OddsCollector.Functions.Tests/Infrastructure/ServiceBus/ServiceBusReceivedMessageFactory.cs +++ b/OddsCollector.Functions.Tests/Infrastructure/ServiceBus/ServiceBusReceivedMessageFactory.cs @@ -7,7 +7,12 @@ namespace OddsCollector.Functions.Tests.Infrastructure.ServiceBus; internal static class ServiceBusReceivedMessageFactory { - public static ServiceBusReceivedMessage CreateFromObject(object obj) + public static IEnumerable CreateFromObjects(IEnumerable objects) + { + return objects.Select(CreateFromObject); + } + + private static ServiceBusReceivedMessage CreateFromObject(object obj) { var serialized = Encoding.ASCII.GetBytes(JsonSerializer.Serialize(obj)) .Select(x => new ReadOnlyMemory([x])); diff --git a/OddsCollector.Functions.Tests/Tests/Functions/EventResultsFunction.cs b/OddsCollector.Functions.Tests/Tests/Functions/EventResultsFunction.cs index e5deb40..18b86cf 100644 --- a/OddsCollector.Functions.Tests/Tests/Functions/EventResultsFunction.cs +++ b/OddsCollector.Functions.Tests/Tests/Functions/EventResultsFunction.cs @@ -3,6 +3,7 @@ using NSubstitute.ExceptionExtensions; using OddsCollector.Functions.Models; using OddsCollector.Functions.OddsApi; +using LoggerFactory = OddsCollector.Functions.Tests.Infrastructure.Logger.LoggerFactory; namespace OddsCollector.Functions.Tests.Tests.Functions; @@ -15,18 +16,16 @@ public async Task Run_WithValidParameters_ReturnsEventResults() // Arrange IEnumerable expectedEventResults = new List { new() }; - var loggerMock = Substitute.For>(); + var loggerMock = LoggerFactory.GetLoggerMock(); - var clientStub = Substitute.For(); - clientStub.GetEventResultsAsync(Arg.Any(), Arg.Any(), Arg.Any()) - .Returns(Task.FromResult(expectedEventResults)); + var clientStub = GetOddsApiClientStub(expectedEventResults); var function = new OddsCollector.Functions.Functions.EventResultsFunction(loggerMock, clientStub); - var token = new CancellationToken(); + var cancellationToken = new CancellationToken(); // Act - var eventResults = await function.Run(token); + var eventResults = await function.Run(cancellationToken); // Assert eventResults.Should().NotBeNull().And.BeEquivalentTo(expectedEventResults); @@ -40,7 +39,7 @@ public async Task Run_WithException_ReturnsEmptyEventResults() // Arrange var exception = new Exception(); - var loggerMock = Substitute.For>(); + var loggerMock = LoggerFactory.GetLoggerMock(); var clientStub = Substitute.For(); clientStub.GetEventResultsAsync(Arg.Any(), Arg.Any(), Arg.Any()) @@ -48,10 +47,10 @@ public async Task Run_WithException_ReturnsEmptyEventResults() var function = new OddsCollector.Functions.Functions.EventResultsFunction(loggerMock, clientStub); - var token = new CancellationToken(); + var cancellationToken = new CancellationToken(); // Act - var eventResults = await function.Run(token); + var eventResults = await function.Run(cancellationToken); // Assert eventResults.Should().NotBeNull().And.BeEmpty(); @@ -66,22 +65,30 @@ public async Task Run_WithValidParameters_ReturnsNoEventResults() // Arrange IEnumerable expectedEventResults = new List(); - var loggerMock = Substitute.For>(); + var loggerMock = LoggerFactory.GetLoggerMock(); - var clientStub = Substitute.For(); - clientStub.GetEventResultsAsync(Arg.Any(), Arg.Any(), Arg.Any()) - .Returns(Task.FromResult(expectedEventResults)); + var clientStub = GetOddsApiClientStub(expectedEventResults); var function = new OddsCollector.Functions.Functions.EventResultsFunction(loggerMock, clientStub); - var token = new CancellationToken(); + var cancellationToken = new CancellationToken(); // Act - var eventResults = await function.Run(token); + var eventResults = await function.Run(cancellationToken); // Assert eventResults.Should().NotBeNull().And.BeEquivalentTo(expectedEventResults); loggerMock.ReceivedWithAnyArgs().LogWarning(string.Empty, 1); } + + private static IOddsApiClient GetOddsApiClientStub(IEnumerable expectedEventResults) + { + var clientStub = Substitute.For(); + + clientStub.GetEventResultsAsync(Arg.Any(), Arg.Any(), Arg.Any()) + .Returns(Task.FromResult(expectedEventResults)); + + return clientStub; + } } diff --git a/OddsCollector.Functions.Tests/Tests/Functions/PredictionFunction.cs b/OddsCollector.Functions.Tests/Tests/Functions/PredictionFunction.cs index f31c98c..c2ba4d0 100644 --- a/OddsCollector.Functions.Tests/Tests/Functions/PredictionFunction.cs +++ b/OddsCollector.Functions.Tests/Tests/Functions/PredictionFunction.cs @@ -1,10 +1,9 @@ -using Azure.Messaging.ServiceBus; -using Microsoft.Azure.Functions.Worker; -using Microsoft.Extensions.Logging; -using NSubstitute.ReceivedExtensions; +using NSubstitute.ReceivedExtensions; using OddsCollector.Functions.Models; using OddsCollector.Functions.Strategies; +using OddsCollector.Functions.Tests.Infrastructure.CancellationToken; using OddsCollector.Functions.Tests.Infrastructure.Data; +using OddsCollector.Functions.Tests.Infrastructure.Logger; using OddsCollector.Functions.Tests.Infrastructure.ServiceBus; namespace OddsCollector.Functions.Tests.Tests.Functions; @@ -15,16 +14,18 @@ internal class PredictionFunction public async Task Run_WithValidServiceBusMessage_ReturnsEventPrediction() { // Arrange - var loggerStub = Substitute.For>(); + var loggerStub = LoggerFactory.GetLoggerMock(); var upcomingEvent = new UpcomingEventBuilder().SetSampleData().Instance; var expectedPrediction = new EventPredictionBuilder().SetSampleData().Instance; - ServiceBusReceivedMessage[] receivedMessages = - [ServiceBusReceivedMessageFactory.CreateFromObject(upcomingEvent)]; + var receivedMessages = + ServiceBusReceivedMessageFactory.CreateFromObjects([ + upcomingEvent + ]).ToArray(); - var actionsMock = Substitute.For(); + var actionsMock = ServiceBusMessageActionsFactory.GetServiceBusMessageActionsMock(); var strategyStub = Substitute.For(); strategyStub.GetPrediction(Arg.Any(), Arg.Any()).Returns(expectedPrediction); @@ -43,11 +44,43 @@ public async Task Run_WithValidServiceBusMessage_ReturnsEventPrediction() await actionsMock.Received(Quantity.Exactly(1)).CompleteMessageAsync(receivedMessages[0], token); } + [Test] + public async Task Run_WithValidServiceBusMessageAndRequestedCancellation_ReturnsNoPredictions() + { + // Arrange + var loggerStub = LoggerFactory.GetLoggerMock(); + + var upcomingEvent = new UpcomingEventBuilder().SetSampleData().Instance; + + var expectedPrediction = new EventPredictionBuilder().SetSampleData().Instance; + + var receivedMessages = + ServiceBusReceivedMessageFactory.CreateFromObjects([ + upcomingEvent + ]).ToArray(); + + var actionsMock = ServiceBusMessageActionsFactory.GetServiceBusMessageActionsMock(); + + var strategyStub = Substitute.For(); + strategyStub.GetPrediction(Arg.Any(), Arg.Any()).Returns(expectedPrediction); + + var function = new OddsCollector.Functions.Functions.PredictionFunction(loggerStub, strategyStub); + + var cancellationToken = await CancellationTokenGenerator.GetRequestedForCancellationToken(); + + // Act + var prediction = await function.Run(receivedMessages, actionsMock, cancellationToken) + .ConfigureAwait(false); + + // Assert + prediction.Should().NotBeNull().And.HaveCount(0); + } + [Test] public async Task Run_WithInvalidItems_ReturnsSuccessfullyProcessedEventPredictions() { // Arrange - var loggerMock = Substitute.For>(); + var loggerMock = LoggerFactory.GetLoggerMock(); var goodUpcomingEvent = new UpcomingEventBuilder().SetSampleData().Instance; @@ -56,13 +89,13 @@ public async Task Run_WithInvalidItems_ReturnsSuccessfullyProcessedEventPredicti var expectedPrediction = new EventPredictionBuilder().SetSampleData().Instance; - ServiceBusReceivedMessage[] receivedMessages = - [ - ServiceBusReceivedMessageFactory.CreateFromObject(badUpcomingEvent), - ServiceBusReceivedMessageFactory.CreateFromObject(goodUpcomingEvent) - ]; + var receivedMessages = + ServiceBusReceivedMessageFactory.CreateFromObjects([ + badUpcomingEvent, + goodUpcomingEvent + ]).ToArray(); - var actionsMock = Substitute.For(); + var actionsMock = ServiceBusMessageActionsFactory.GetServiceBusMessageActionsMock(); var exception = new Exception(); diff --git a/OddsCollector.Functions.Tests/Tests/Functions/PredictionsHttpFunction.cs b/OddsCollector.Functions.Tests/Tests/Functions/PredictionsHttpFunction.cs index 86309c8..4483328 100644 --- a/OddsCollector.Functions.Tests/Tests/Functions/PredictionsHttpFunction.cs +++ b/OddsCollector.Functions.Tests/Tests/Functions/PredictionsHttpFunction.cs @@ -2,9 +2,9 @@ using System.Text.Json; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; -using Microsoft.Extensions.Logging; using OddsCollector.Functions.Models; using OddsCollector.Functions.Tests.Infrastructure.Data; +using OddsCollector.Functions.Tests.Infrastructure.Logger; namespace OddsCollector.Functions.Tests.Tests.Functions; @@ -14,7 +14,7 @@ internal class PredictionsHttpFunction public void Run_WithValidArguments_ReturnsValidResponse() { // Arrange - var loggerStub = Substitute.For>(); + var loggerStub = LoggerFactory.GetLoggerMock(); var function = new OddsCollector.Functions.Functions.PredictionsHttpFunction(loggerStub); diff --git a/OddsCollector.Functions.Tests/Tests/Functions/UpcomingEventsFunction.cs b/OddsCollector.Functions.Tests/Tests/Functions/UpcomingEventsFunction.cs index cc8fdc1..4189578 100644 --- a/OddsCollector.Functions.Tests/Tests/Functions/UpcomingEventsFunction.cs +++ b/OddsCollector.Functions.Tests/Tests/Functions/UpcomingEventsFunction.cs @@ -3,6 +3,7 @@ using NSubstitute.ExceptionExtensions; using OddsCollector.Functions.Models; using OddsCollector.Functions.OddsApi; +using LoggerFactory = OddsCollector.Functions.Tests.Infrastructure.Logger.LoggerFactory; namespace OddsCollector.Functions.Tests.Tests.Functions; @@ -15,7 +16,7 @@ public async Task Run_WithValidParameters_ReturnsEventResults() // Arrange IEnumerable expectedEventResults = new List { new() }; - var loggerMock = Substitute.For>(); + var loggerMock = LoggerFactory.GetLoggerMock(); var clientStub = Substitute.For(); clientStub.GetUpcomingEventsAsync(Arg.Any(), Arg.Any(), Arg.Any()) @@ -38,7 +39,7 @@ public async Task Run_WithException_ReturnsEmptyEventResults() // Arrange var exception = new Exception(); - var loggerMock = Substitute.For>(); + var loggerMock = LoggerFactory.GetLoggerMock(); var clientStub = Substitute.For(); clientStub.GetUpcomingEventsAsync(Arg.Any(), Arg.Any(), Arg.Any()) @@ -62,7 +63,7 @@ public async Task Run_WithValidParameters_ReturnsNoEventResults() // Arrange IEnumerable expectedEventResults = new List(); - var loggerMock = Substitute.For>(); + var loggerMock = LoggerFactory.GetLoggerMock(); var clientStub = Substitute.For(); clientStub.GetUpcomingEventsAsync(Arg.Any(), Arg.Any(), Arg.Any()) diff --git a/OddsCollector.Functions.Tests/Tests/OddsApi/OddsApiClient.cs b/OddsCollector.Functions.Tests/Tests/OddsApi/OddsApiClient.cs index f310f78..8ea72b4 100644 --- a/OddsCollector.Functions.Tests/Tests/OddsApi/OddsApiClient.cs +++ b/OddsCollector.Functions.Tests/Tests/OddsApi/OddsApiClient.cs @@ -3,6 +3,7 @@ using OddsCollector.Functions.OddsApi.Configuration; using OddsCollector.Functions.OddsApi.Converter; using OddsCollector.Functions.OddsApi.WebApi; +using OddsCollector.Functions.Tests.Infrastructure.CancellationToken; namespace OddsCollector.Functions.Tests.Tests.OddsApi; @@ -118,4 +119,80 @@ public async Task GetEventResultsAsync_WithLeagues_ReturnsEventResults() firstReceivedArguments[1].Should().Be(traceId); firstReceivedArguments[2].Should().Be(timestamp); } + + [Test] + public async Task GetUpcomingEventsAsync_WithLeaguesAndRequestedCancellation_ReturnsNoUpcomingEvents() + { + // Arrange + ICollection rawUpcomingEvents = [new Anonymous2()]; + var webApiClientMock = Substitute.For(); + webApiClientMock + .OddsAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), + Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), + Arg.Any()) + .Returns(Task.FromResult(rawUpcomingEvents)); + + // ReSharper disable once CollectionNeverUpdated.Local + List upcomingEvents = []; + var converterMock = Substitute.For(); + converterMock.ToUpcomingEvents(Arg.Any?>(), Arg.Any(), Arg.Any()) + .Returns(new List()); + + const string secretValue = nameof(secretValue); + + const string league = nameof(league); + var optionsStub = Substitute.For>(); + optionsStub.Value.Returns(new OddsApiClientOptions { Leagues = [league], ApiKey = secretValue }); + + var oddsClient = + new OddsCollector.Functions.OddsApi.OddsApiClient(optionsStub, webApiClientMock, converterMock); + + var traceId = Guid.NewGuid(); + var timestamp = DateTime.UtcNow; + + var cancellationToken = await CancellationTokenGenerator.GetRequestedForCancellationToken(); + + // Act + var results = (await oddsClient.GetUpcomingEventsAsync(traceId, timestamp, cancellationToken)).ToList(); + + // Assert + results.Should().NotBeNull().And.HaveCount(0); + } + + [Test] + public async Task GetEventResultsAsync_WithLeaguesAndRequestedCancellation_ReturnsNoEventResults() + { + // Arrange + ICollection rawEventResults = [new Anonymous3()]; + var webApiClientMock = Substitute.For(); + webApiClientMock.ScoresAsync(Arg.Any(), Arg.Any(), Arg.Any(), + Arg.Any()) + .Returns(Task.FromResult(rawEventResults)); + + // ReSharper disable once CollectionNeverUpdated.Local + List eventResults = []; + var converterMock = Substitute.For(); + converterMock.ToEventResults(Arg.Any?>(), Arg.Any(), Arg.Any()) + .Returns(eventResults); + + const string secretValue = nameof(secretValue); + + const string league = nameof(league); + var optionsStub = Substitute.For>(); + optionsStub.Value.Returns(new OddsApiClientOptions { Leagues = [league], ApiKey = secretValue }); + + var oddsClient = + new OddsCollector.Functions.OddsApi.OddsApiClient(optionsStub, webApiClientMock, converterMock); + + var traceId = Guid.NewGuid(); + var timestamp = DateTime.UtcNow; + + var cancellationToken = await CancellationTokenGenerator.GetRequestedForCancellationToken(); + + // Act + var results = (await oddsClient.GetEventResultsAsync(traceId, timestamp, cancellationToken)).ToList(); + + // Assert + results.Should().NotBeNull().And.HaveCount(0); + } } diff --git a/OddsCollector.Functions/Functions/PredictionFunction.cs b/OddsCollector.Functions/Functions/PredictionFunction.cs index 5bb3cf9..44d8fa0 100644 --- a/OddsCollector.Functions/Functions/PredictionFunction.cs +++ b/OddsCollector.Functions/Functions/PredictionFunction.cs @@ -20,6 +20,11 @@ public async Task Run( foreach (var message in messages) { + if (cancellationToken.IsCancellationRequested) + { + break; + } + try { var upcomingEvent = message.Body.ToObjectFromJson(); diff --git a/OddsCollector.Functions/OddsApi/OddsApiClient.cs b/OddsCollector.Functions/OddsApi/OddsApiClient.cs index a53ce00..8dea441 100644 --- a/OddsCollector.Functions/OddsApi/OddsApiClient.cs +++ b/OddsCollector.Functions/OddsApi/OddsApiClient.cs @@ -24,6 +24,11 @@ public async Task> GetUpcomingEventsAsync(Guid traceI foreach (var league in options.Value.Leagues) { + if (cancellationToken.IsCancellationRequested) + { + break; + } + var events = await client.OddsAsync(league, options.Value.ApiKey, EuropeanRegion, HeadToHeadMarket, IsoDateFormat, DecimalOddsFormat, null, null, cancellationToken) @@ -42,6 +47,11 @@ public async Task> GetEventResultsAsync(Guid traceId, D foreach (var league in options.Value.Leagues) { + if (cancellationToken.IsCancellationRequested) + { + break; + } + var results = await client.ScoresAsync(league, options.Value.ApiKey, DaysFromToday, cancellationToken) .ConfigureAwait(false);