-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement WeatherForecast Application layer.
Add Forecasts via WeatherForecast service.
- Loading branch information
Alejandro Del Rincón López
committed
Sep 28, 2023
1 parent
8057c25
commit 5f04e73
Showing
17 changed files
with
358 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
using AutoFixture; | ||
using AutoFixture.AutoMoq; | ||
|
||
namespace BlazorApp1.Application.Unit.Tests; | ||
|
||
public class AutoMoqDataAttribute : AutoDataAttribute | ||
{ | ||
public AutoMoqDataAttribute() : base(Factory()) | ||
{ | ||
} | ||
|
||
private static Func<IFixture> Factory() | ||
{ | ||
return () => | ||
{ | ||
var fixture = new Fixture(); | ||
fixture.Customize<DateOnly>(composer => composer.FromFactory<DateTime>(DateOnly.FromDateTime)); | ||
return fixture.Customize(new AutoMoqCustomization()); | ||
}; | ||
} | ||
} |
33 changes: 33 additions & 0 deletions
33
BlazorApp1.Application.Unit.Tests/BlazorApp1.Application.Unit.Tests.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net6.0</TargetFramework> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
|
||
<IsPackable>false</IsPackable> | ||
<IsTestProject>true</IsTestProject> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="FluentAssertions" Version="6.12.0" /> | ||
<PackageReference Include="AutoFixture.AutoMoq" Version="4.18.0" /> | ||
<PackageReference Include="AutoFixture.Xunit2" Version="4.18.0" /> | ||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" /> | ||
<PackageReference Include="Moq" Version="4.20.69" /> | ||
<PackageReference Include="xunit" Version="2.4.2" /> | ||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5"> | ||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||
<PrivateAssets>all</PrivateAssets> | ||
</PackageReference> | ||
<PackageReference Include="coverlet.collector" Version="3.2.0"> | ||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||
<PrivateAssets>all</PrivateAssets> | ||
</PackageReference> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\BlazorApp1.Application\BlazorApp1.Application.csproj" /> | ||
</ItemGroup> | ||
|
||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
global using AutoFixture.Xunit2; | ||
global using Moq; | ||
global using System.Threading; | ||
global using System.Threading.Tasks; | ||
global using Xunit; |
85 changes: 85 additions & 0 deletions
85
BlazorApp1.Application.Unit.Tests/WeatherForecastServiceTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
|
||
using AutoMapper; | ||
using BlazorApp1.Application.Queue; | ||
using BlazorApp1.Data.Abstractions.Repositories; | ||
using BlazorApp1.Domain; | ||
using BlazorApp1.Server.Abstractions.Contracts; | ||
using FluentAssertions.Execution; | ||
|
||
namespace BlazorApp1.Application.Unit.Tests; | ||
|
||
public class WeatherForecastServiceTests | ||
{ | ||
[Theory, AutoMoqData] | ||
public async Task AddForecast_ShouldAddForecastToRepositoryAndQueueBackgroundTask( | ||
[Frozen] Mock<IWeatherForecastRepository> weatherForecastRepositoryMock, | ||
[Frozen] Mock<IBackgroundTaskQueue> backgroundTaskQueueMock, | ||
[Frozen] Mock<IMapper> mapperMock, | ||
WeatherForecastDto weatherForecastDto, | ||
WeatherForecast weatherForecast, | ||
WeatherForecastService sut) | ||
{ | ||
// Arrange | ||
mapperMock.Setup(m => m.Map<WeatherForecast>(weatherForecastDto)).Returns(weatherForecast); | ||
|
||
// Act | ||
await sut.AddForecast(weatherForecastDto); | ||
|
||
// Assert | ||
weatherForecastRepositoryMock.Verify(r => r.AddWeatherForecast(weatherForecast), Times.Once); | ||
backgroundTaskQueueMock.Verify(q => q.QueueBackgroundWorkItemAsync(It.IsAny<Func<IServiceProvider, CancellationToken, ValueTask>>()), Times.Once); | ||
} | ||
|
||
[Theory, AutoMoqData] | ||
public async Task ProcessWeatherSummary_WhenExistingForecast_ShouldUpdateSummaryToRepository( | ||
[Frozen] Mock<IWeatherForecastRepository> weatherForecastRepositoryMock, | ||
[Frozen] Mock<IWeatherForecastSummaryRepository> weatherForecastSummaryRepositoryMock, | ||
WeatherForecast[] weatherForecasts, | ||
WeatherForecastSummary weatherForecastSummary, | ||
WeatherForecastService sut) | ||
{ | ||
// Arrange | ||
weatherForecastRepositoryMock.Setup(r => r.GetAllForecasts()).ReturnsAsync(weatherForecasts); | ||
weatherForecastSummaryRepositoryMock.Setup(r => r.GetForecastSummaryByDate(It.IsAny<DateOnly>())).ReturnsAsync(weatherForecastSummary); | ||
|
||
// Act | ||
await sut.ProcessWeatherSummary(weatherForecasts.First(), CancellationToken.None); | ||
|
||
// Assert | ||
using var scope = new AssertionScope(); | ||
|
||
weatherForecastSummaryRepositoryMock.Verify(r => r.GetForecastSummaryByDate(DateOnly.FromDateTime(weatherForecasts.First().Date)), Times.Once); | ||
weatherForecastSummaryRepositoryMock.Verify(r => r.UpdateWeatherForecastSummary(It.IsAny<WeatherForecastSummary>()), Times.Once); | ||
weatherForecastSummaryRepositoryMock.VerifyNoOtherCalls(); | ||
} | ||
|
||
[Theory, AutoMoqData] | ||
public async Task ProcessWeatherSummary_WhenNoExistingForecast_ShouldAddSummaryToRepository( | ||
[Frozen] Mock<IWeatherForecastRepository> weatherForecastRepositoryMock, | ||
[Frozen] Mock<IWeatherForecastSummaryRepository> weatherForecastSummaryRepositoryMock, | ||
List<WeatherForecast> weatherForecasts, | ||
WeatherForecast weatherForecast, | ||
WeatherForecastService sut) | ||
{ | ||
// Arrange | ||
weatherForecast.Date = weatherForecasts.First().Date; | ||
weatherForecasts.Add(weatherForecast); | ||
|
||
weatherForecastRepositoryMock.Setup(r => r.GetAllForecasts()).ReturnsAsync(weatherForecasts); | ||
weatherForecastSummaryRepositoryMock.Setup(r => r.GetForecastSummaryByDate(It.IsAny<DateOnly>())).ReturnsAsync((WeatherForecastSummary)null); | ||
|
||
// Act | ||
await sut.ProcessWeatherSummary(weatherForecast, CancellationToken.None); | ||
|
||
// Assert | ||
using var scope = new AssertionScope(); | ||
|
||
weatherForecastSummaryRepositoryMock.Verify(r => r.GetForecastSummaryByDate(DateOnly.FromDateTime(weatherForecast.Date)), Times.Once); | ||
|
||
var sameDateWeatherforecasts = weatherForecasts.Where(x => DateOnly.FromDateTime(x.Date) == DateOnly.FromDateTime(weatherForecast.Date)); | ||
int tempAcg = (int)Math.Round(sameDateWeatherforecasts.Average(x => x.TemperatureC), 0); | ||
|
||
weatherForecastSummaryRepositoryMock.Verify(r => r.AddWeatherForecastSummary(It.Is<WeatherForecastSummary>(s => s.Date == DateOnly.FromDateTime(weatherForecast.Date) && s.TemperatureC == tempAcg)), Times.Once); | ||
weatherForecastSummaryRepositoryMock.VerifyNoOtherCalls(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net6.0</TargetFramework> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="AutoMapper" Version="12.0.1" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\BlazorApp1.Server.Abstractions\BlazorApp1.Server.Abstractions.csproj" /> | ||
<ProjectReference Include="..\BlazorApp1\Shared\BlazorApp1.Data.csproj" /> | ||
</ItemGroup> | ||
|
||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
using BlazorApp1.Application.Queue; | ||
using Microsoft.Extensions.Configuration; | ||
using Microsoft.Extensions.DependencyInjection; | ||
|
||
namespace BlazorApp1.Application; | ||
|
||
public static class DependencyInjectionExtensions | ||
{ | ||
public static IServiceCollection AddWeatherForecastApplicationLayer(this IServiceCollection services, IConfiguration configuration) | ||
{ | ||
services.AddSingleton<IBackgroundTaskQueue>(_ => | ||
{ | ||
if (!int.TryParse(configuration["QueueCapacity"], | ||
out int queueCapacity)) | ||
{ | ||
queueCapacity = 100; | ||
} | ||
return new DefaultBackgroundTaskQueue(queueCapacity); | ||
}); | ||
services.AddScoped<IWeatherForecastService, WeatherForecastService>(); | ||
|
||
return services; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
using BlazorApp1.Domain; | ||
using BlazorApp1.Server.Abstractions.Contracts; | ||
|
||
namespace BlazorApp1.Application; | ||
public interface IWeatherForecastService | ||
{ | ||
Task AddForecast(WeatherForecastDto weatherForecast, CancellationToken cancellationToken = default); | ||
ValueTask ProcessWeatherSummary(WeatherForecast forecast, CancellationToken cancellationToken); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
using AutoMapper; | ||
using BlazorApp1.Application.Queue; | ||
using BlazorApp1.Data.Abstractions.Repositories; | ||
using BlazorApp1.Domain; | ||
using BlazorApp1.Server.Abstractions.Contracts; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.Logging; | ||
|
||
namespace BlazorApp1.Application; | ||
|
||
public class WeatherForecastService : IWeatherForecastService | ||
{ | ||
private readonly IWeatherForecastRepository _weatherForecastRepository; | ||
private readonly IWeatherForecastSummaryRepository _weatherForecastSummaryRepository; | ||
private readonly IMapper _mapper; | ||
private readonly IBackgroundTaskQueue _backgroundTaskQueue; | ||
private readonly ILogger<WeatherForecastService> _logger; | ||
|
||
public WeatherForecastService(IWeatherForecastRepository weatherForecastRepository, | ||
IWeatherForecastSummaryRepository weatherForecastSummaryRepository, | ||
IMapper mapper, | ||
IBackgroundTaskQueue backgroundTaskQueue, | ||
ILogger<WeatherForecastService> logger) | ||
{ | ||
_weatherForecastRepository = weatherForecastRepository; | ||
_weatherForecastSummaryRepository = weatherForecastSummaryRepository; | ||
_mapper = mapper; | ||
_backgroundTaskQueue = backgroundTaskQueue; | ||
_logger = logger; | ||
} | ||
|
||
public async Task AddForecast(WeatherForecastDto weatherForecast, CancellationToken cancellationToken = default) | ||
{ | ||
var forecast = _mapper.Map<WeatherForecast>(weatherForecast); | ||
|
||
await _weatherForecastRepository.AddWeatherForecast(forecast); | ||
|
||
var tasktoDo = _backgroundTaskQueue.QueueBackgroundWorkItemAsync(ProcessWeatherSummary(forecast)); | ||
} | ||
|
||
private static Func<IServiceProvider, CancellationToken, ValueTask> ProcessWeatherSummary(WeatherForecast forecast) | ||
{ | ||
return (serviceProvider, cancellationToken) => | ||
{ | ||
var service = serviceProvider.GetRequiredService<IWeatherForecastService>(); | ||
return service.ProcessWeatherSummary(forecast, cancellationToken); | ||
}; | ||
} | ||
|
||
public async ValueTask ProcessWeatherSummary(WeatherForecast forecast, CancellationToken cancellationToken) | ||
{ | ||
//TODO: Move to a new RepoMethod. | ||
var forecasts = await _weatherForecastRepository.GetAllForecasts(); | ||
forecasts = forecasts.Where(dbForecast => DateOnly.FromDateTime(forecast.Date) == DateOnly.FromDateTime(dbForecast.Date)); | ||
|
||
var forecastSummary = new WeatherForecastSummary | ||
{ | ||
Date = DateOnly.FromDateTime(forecast.Date), | ||
TemperatureC = (int)Math.Round(forecasts.Average(x => x.TemperatureC), 0) | ||
}; | ||
|
||
var existingSummary = await _weatherForecastSummaryRepository.GetForecastSummaryByDate(forecastSummary.Date); | ||
if (existingSummary != null) | ||
{ | ||
existingSummary.TemperatureC = forecastSummary.TemperatureC; | ||
await _weatherForecastSummaryRepository.UpdateWeatherForecastSummary(existingSummary); | ||
} | ||
else | ||
{ | ||
await _weatherForecastSummaryRepository.AddWeatherForecastSummary(forecastSummary); | ||
} | ||
|
||
_logger.LogInformation($"Processing ForecastSummary and added {forecast.Date}"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.