Skip to content

Commit

Permalink
implementation of new endpoint for delete favorites
Browse files Browse the repository at this point in the history
  • Loading branch information
Gramli committed Mar 11, 2024
1 parent 8bba192 commit e1de992
Show file tree
Hide file tree
Showing 25 changed files with 203 additions and 61 deletions.
10 changes: 7 additions & 3 deletions src/Tests/HttpDebug/debug-tests.http
Expand Up @@ -3,11 +3,11 @@
@host={{hostname}}:{{port}}

### get current weather request
GET https://{{host}}/weather/v1/current?latitude=38.5&longtitude=-78.5
GET https://{{host}}/weather/v1/current?latitude=38.5&longitude=-78.5
Content-Type: application/json

### get forecast weather request
GET https://{{host}}/weather/v1/forecast?latitude=38.5&longtitude=-78.5
GET https://{{host}}/weather/v1/forecast?latitude=38.5&longitude=-78.5
Content-Type: application/json

### get favorites weather request
Expand All @@ -23,4 +23,8 @@ Content-Type: application/json
"latitude": 38.5,
"longitude": -78.5
}
}
}

### add favorites weather request
DELETE https://{{host}}/weather/v1/favorite/1
Content-Type: application/json
Expand Up @@ -10,7 +10,7 @@ namespace Weather.API.SystemTests
public class WeatherSystemTests
{
private readonly double latitude = 38.5;
private readonly double longtitude = -78.5;
private readonly double longitude = -78.5;
private readonly string cityName = "Stanley";

private readonly HttpClient _httpClient;
Expand All @@ -26,7 +26,7 @@ public async Task GetCurrentWeather()
{
//Arrange
//Act
var response = await _httpClient.GetAsync($"weather/v1/current?latitude={latitude}&longtitude={longtitude}");
var response = await _httpClient.GetAsync($"weather/v1/current?latitude={latitude}&longitude={longitude}");

//Assert
response.EnsureSuccessStatusCode();
Expand All @@ -41,7 +41,7 @@ public async Task GetForecastWeather()
{
//Arrange
//Act
var response = await _httpClient.GetAsync($"weather/v1/forecast?latitude={latitude}&longtitude={longtitude}");
var response = await _httpClient.GetAsync($"weather/v1/forecast?latitude={latitude}&longitude={longitude}");

//Assert
response.EnsureSuccessStatusCode();
Expand Down Expand Up @@ -82,6 +82,27 @@ public async Task GetWeatherFavorites()
Assert.Equal(cityName, resultDto.Data.FavoriteWeathers.First().CityName);
}

[Fact]
public async Task DeleteWeatherFavorites()
{
//Arrange
var addResponse = await AddFavorite();

addResponse.EnsureSuccessStatusCode();

var content = await addResponse.Content.ReadAsStringAsync();
var addResult = JsonConvert.DeserializeObject<DataResponse<int>>(content);
//Act
var response = await _httpClient.DeleteAsync($"weather/v1/favorite/{addResult!.Data}");

//Assert
response.EnsureSuccessStatusCode();
var stringResult = await response.Content.ReadAsStringAsync();
var resultDto = JsonConvert.DeserializeObject<DataResponse<bool>>(stringResult);
Assert.NotNull(resultDto?.Data);
Assert.True(resultDto.Data);
}

private async Task<HttpResponseMessage> AddFavorite()
{
//Arrange
Expand All @@ -90,7 +111,7 @@ private async Task<HttpResponseMessage> AddFavorite()
Location = new LocationDto
{
Latitude = latitude,
Longitude = longtitude,
Longitude = longitude,
}
});
var content = new StringContent(body, Encoding.UTF8, "application/json");
Expand Down
Expand Up @@ -38,7 +38,6 @@ public async Task InvalidLocation()
//Assert
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode);
Assert.Single(result.Errors);
Assert.False(result.Data);
_addFavoriteCommandValidatorMock.Verify(x => x.IsValid(It.Is<AddFavoriteCommand>(y => y.Equals(addFavoriteCommand))), Times.Once);
}

Expand All @@ -58,7 +57,6 @@ public async Task AddFavoriteLocation_Failed()
//Assert
Assert.Equal(HttpStatusCode.InternalServerError, result.StatusCode);
Assert.Single(result.Errors);
Assert.False(result.Data);
Assert.Equal(ErrorMessages.CantStoreLocation, result.Errors.Single());
_addFavoriteCommandValidatorMock.Verify(x => x.IsValid(It.Is<AddFavoriteCommand>(y => y.Equals(addFavoriteCommand))), Times.Once);
_weatherCommandsRepositoryMock.Verify(x => x.AddFavoriteLocation(It.Is<AddFavoriteCommand>(y=>y.Equals(addFavoriteCommand)), It.IsAny<CancellationToken>()), Times.Once);
Expand All @@ -81,7 +79,7 @@ public async Task Success()
//Assert
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
Assert.Empty(result.Errors);
Assert.True(result.Data);
Assert.Equal(locationId, result.Data);
_addFavoriteCommandValidatorMock.Verify(x => x.IsValid(It.Is<AddFavoriteCommand>(y => y.Equals(addFavoriteCommand))), Times.Once);
_weatherCommandsRepositoryMock.Verify(x => x.AddFavoriteLocation(It.Is<AddFavoriteCommand>(y => y.Equals(addFavoriteCommand)), It.IsAny<CancellationToken>()), Times.Once);
}
Expand Down
@@ -1,6 +1,7 @@
using Weather.Core.Abstractions;
using Weather.Core.Queries;
using Weather.Core.Resources;
using Weather.Domain.BusinessEntities;
using Weather.Domain.Dtos;
using Weather.Domain.Http;
using Weather.Domain.Logging;
Expand Down Expand Up @@ -37,7 +38,7 @@ public async Task GetFavorites_Empty()
{
//Arrange
_weatherRepositoryMock.Setup(x => x.GetFavorites(It.IsAny<CancellationToken>()))
.ReturnsAsync(new List<LocationDto>());
.ReturnsAsync(new List<FavoriteLocation>());

//Act
var result = await _uut.HandleAsync(EmptyRequest.Instance, CancellationToken.None);
Expand All @@ -52,10 +53,10 @@ public async Task GetFavorites_Empty()
public async Task InvalidLocation()
{
//Arrange
var locationDto = new LocationDto { Latitude = 1, Longitude = 1 };
var locationDto = new FavoriteLocation { Id =0, Latitude = 1, Longitude = 1 };

_weatherRepositoryMock.Setup(x => x.GetFavorites(It.IsAny<CancellationToken>()))
.ReturnsAsync(new List<LocationDto>
.ReturnsAsync(new List<FavoriteLocation>
{
locationDto,
});
Expand All @@ -79,10 +80,10 @@ public async Task EmptyResult_GetCurrentWeather_Fail()
{
//Arrange
var failMessage = "Some fail message";
var locationDto = new LocationDto { Latitude = 1, Longitude = 1 };
var locationDto = new FavoriteLocation { Id = 0, Latitude = 1, Longitude = 1 };

_weatherRepositoryMock.Setup(x => x.GetFavorites(It.IsAny<CancellationToken>()))
.ReturnsAsync(new List<LocationDto>
.ReturnsAsync(new List<FavoriteLocation>
{
locationDto,
});
Expand All @@ -107,13 +108,13 @@ public async Task One_Of_GetCurrentWeather_Failed()
{
//Arrange
var failMessage = "Some fail message";
var locationDto = new LocationDto { Latitude = 1, Longitude = 1 };
var locationDto = new FavoriteLocation { Id = 0, Latitude = 1, Longitude = 1 };

_weatherRepositoryMock.Setup(x => x.GetFavorites(It.IsAny<CancellationToken>()))
.ReturnsAsync(new List<LocationDto>
.ReturnsAsync(new List<FavoriteLocation>
{
locationDto,
new LocationDto(),
new FavoriteLocation(),
});

_locationValidatorMock.Setup(x => x.IsValid(It.IsAny<LocationDto>())).Returns(true);
Expand All @@ -122,8 +123,8 @@ public async Task One_Of_GetCurrentWeather_Failed()

_currentWeatherValidatorMock.Setup(x => x.IsValid(It.IsAny<CurrentWeatherDto>())).Returns(true);

_weatherServiceMock.Setup(x => x.GetCurrentWeather(It.Is<LocationDto>(y=> y.Equals(locationDto)), It.IsAny<CancellationToken>())).ReturnsAsync(Result.Fail(failMessage));
_weatherServiceMock.Setup(x => x.GetCurrentWeather(It.Is<LocationDto>(y => !y.Equals(locationDto)), It.IsAny<CancellationToken>())).ReturnsAsync(Result.Ok(currentWeather));
_weatherServiceMock.Setup(x => x.GetCurrentWeather(It.Is<FavoriteLocation>(y=> y.Equals(locationDto)), It.IsAny<CancellationToken>())).ReturnsAsync(Result.Fail(failMessage));
_weatherServiceMock.Setup(x => x.GetCurrentWeather(It.Is<FavoriteLocation>(y => !y.Equals(locationDto)), It.IsAny<CancellationToken>())).ReturnsAsync(Result.Ok(currentWeather));
//Act
var result = await _uut.HandleAsync(EmptyRequest.Instance, CancellationToken.None);

Expand All @@ -132,7 +133,7 @@ public async Task One_Of_GetCurrentWeather_Failed()
Assert.Single(result.Errors);
Assert.NotNull(result.Data);
Assert.Single(result.Data.FavoriteWeathers);
Assert.Equal(currentWeather, result.Data.FavoriteWeathers.Single());
Assert.Equal(currentWeather.CityName, result.Data.FavoriteWeathers.Single().CityName);
_weatherRepositoryMock.Verify(x => x.GetFavorites(It.IsAny<CancellationToken>()), Times.Once);
_weatherServiceMock.Verify(x => x.GetCurrentWeather(It.IsAny<LocationDto>(), It.IsAny<CancellationToken>()), Times.Exactly(2));
_loggerMock.VerifyLog(LogLevel.Warning, LogEvents.FavoriteWeathersGeneral, failMessage, Times.Once());
Expand All @@ -144,10 +145,10 @@ public async Task One_Of_GetCurrentWeather_Failed()
public async Task GetCurrentWeather_Validation_Fail()
{
//Arrange
var locationDto = new LocationDto { Latitude = 1, Longitude = 1 };
var locationDto = new FavoriteLocation { Id = 0, Latitude = 1, Longitude = 1 };

_weatherRepositoryMock.Setup(x => x.GetFavorites(It.IsAny<CancellationToken>()))
.ReturnsAsync(new List<LocationDto>
.ReturnsAsync(new List<FavoriteLocation>
{
locationDto,
});
Expand All @@ -174,10 +175,10 @@ public async Task GetCurrentWeather_Validation_Fail()
public async Task Success()
{
//Arrange
var locationDto = new LocationDto { Latitude = 1, Longitude = 1 };
var locationDto = new FavoriteLocation { Latitude = 1, Longitude = 1 };

_weatherRepositoryMock.Setup(x => x.GetFavorites(It.IsAny<CancellationToken>()))
.ReturnsAsync(new List<LocationDto>
.ReturnsAsync(new List<FavoriteLocation>
{
locationDto,
});
Expand All @@ -195,7 +196,7 @@ public async Task Success()
Assert.Empty(result.Errors);
Assert.NotNull(result.Data);
Assert.Single(result.Data.FavoriteWeathers);
Assert.Equal(currentWeather, result.Data.FavoriteWeathers.Single());
Assert.Equal(currentWeather.CityName, result.Data.FavoriteWeathers.Single().CityName);
_weatherRepositoryMock.Verify(x => x.GetFavorites(It.IsAny<CancellationToken>()), Times.Once);
_weatherServiceMock.Verify(x => x.GetCurrentWeather(It.Is<LocationDto>(y=>y.Equals(locationDto)), It.IsAny<CancellationToken>()), Times.Once);
_locationValidatorMock.Verify(x => x.IsValid(It.Is<LocationDto>(y => y.Equals(locationDto))), Times.Once);
Expand Down
17 changes: 12 additions & 5 deletions src/Weather.API/EndpointBuilders/WeatherBuilder.cs
Expand Up @@ -25,8 +25,8 @@ public static IEndpointRouteBuilder BuildWeatherEndpoints(this IEndpointRouteBui
private static IEndpointRouteBuilder BuildActualWeatherEndpoints(this IEndpointRouteBuilder endpointRouteBuilder)
{
endpointRouteBuilder.MapGet("v1/current",
async (double latitude, double longtitude, [FromServices] IGetCurrentWeatherHandler handler, CancellationToken cancellationToken) =>
await handler.SendAsync(new GetCurrentWeatherQuery(latitude,longtitude), cancellationToken))
async (double latitude, double longitude, [FromServices] IGetCurrentWeatherHandler handler, CancellationToken cancellationToken) =>
await handler.SendAsync(new GetCurrentWeatherQuery(latitude, longitude), cancellationToken))
.Produces<DataResponse<CurrentWeatherDto>>()
.WithName("GetCurrentWeather")
.WithTags("Getters");
Expand All @@ -36,8 +36,8 @@ await handler.SendAsync(new GetCurrentWeatherQuery(latitude,longtitude), cancell
private static IEndpointRouteBuilder BuildForecastWeatherEndpoints(this IEndpointRouteBuilder endpointRouteBuilder)
{
endpointRouteBuilder.MapGet("v1/forecast",
async (double latitude, double longtitude, [FromServices] IGetForecastWeatherHandler handler, CancellationToken cancellationToken) =>
await handler.SendAsync(new GetForecastWeatherQuery(latitude, longtitude), cancellationToken))
async (double latitude, double longitude, [FromServices] IGetForecastWeatherHandler handler, CancellationToken cancellationToken) =>
await handler.SendAsync(new GetForecastWeatherQuery(latitude, longitude), cancellationToken))
.Produces<DataResponse<ForecastWeatherDto>>()
.WithName("GetForecastWeather")
.WithTags("Getters");
Expand All @@ -57,10 +57,17 @@ await handler.SendAsync(EmptyRequest.Instance, cancellationToken))
endpointRouteBuilder.MapPost("v1/favorite",
async ([FromBody] AddFavoriteCommand addFavoriteCommand, [FromServices] IAddFavoriteHandler handler, CancellationToken cancellationToken) =>
await handler.SendAsync(addFavoriteCommand, cancellationToken))
.Produces<DataResponse<bool>>()
.Produces<DataResponse<int>>()
.WithName("AddFavorite")
.WithTags("Setters");

endpointRouteBuilder.MapDelete("v1/favorite/{id}",
async (int id, [FromServices] IDeleteFavoriteHandler handler, CancellationToken cancellationToken) =>
await handler.SendAsync(new DeleteFavoriteCommand { Id = id }, cancellationToken))
.Produces<DataResponse<bool>>()
.WithName("DeleteFavorite")
.WithTags("Delete");

return endpointRouteBuilder;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Weather.Core/Abstractions/IAddFavoriteHandler.cs
Expand Up @@ -2,7 +2,7 @@

namespace Weather.Core.Abstractions
{
public interface IAddFavoriteHandler : IRequestHandler<bool, AddFavoriteCommand>
public interface IAddFavoriteHandler : IRequestHandler<int, AddFavoriteCommand>
{

}
Expand Down
8 changes: 8 additions & 0 deletions src/Weather.Core/Abstractions/IDeleteFavoriteHandler.cs
@@ -0,0 +1,8 @@
using Weather.Domain.Commands;

namespace Weather.Core.Abstractions
{
public interface IDeleteFavoriteHandler : IRequestHandler<bool, DeleteFavoriteCommand>
{
}
}
Expand Up @@ -6,5 +6,6 @@ namespace Weather.Core.Abstractions
public interface IWeatherCommandsRepository
{
Task<Result<int>> AddFavoriteLocation(AddFavoriteCommand addFavoriteCommand, CancellationToken cancellationToken);
Task<Result> DeleteFavoriteLocationSafeAsync(DeleteFavoriteCommand command, CancellationToken cancellationToken);
}
}
5 changes: 2 additions & 3 deletions src/Weather.Core/Abstractions/IWeatherQueriesRepository.cs
@@ -1,10 +1,9 @@
using FluentResults;
using Weather.Domain.Dtos;
using Weather.Domain.BusinessEntities;

namespace Weather.Core.Abstractions
{
public interface IWeatherQueriesRepository
{
Task<IEnumerable<LocationDto>> GetFavorites(CancellationToken cancellationToken);
Task<IEnumerable<FavoriteLocation>> GetFavorites(CancellationToken cancellationToken);
}
}
8 changes: 4 additions & 4 deletions src/Weather.Core/Commands/AddFavoriteHandler.cs
Expand Up @@ -22,21 +22,21 @@ public AddFavoriteHandler(IWeatherCommandsRepository weatherCommandsRepository,
_logger = Guard.Against.Null(logger);
}

public async Task<HttpDataResponse<bool>> HandleAsync(AddFavoriteCommand request, CancellationToken cancellationToken)
public async Task<HttpDataResponse<int>> HandleAsync(AddFavoriteCommand request, CancellationToken cancellationToken)
{
if (!_addFavoriteCommandValidator.IsValid(request))
{
return HttpDataResponses.AsBadRequest<bool>(string.Format(ErrorMessages.RequestValidationError, request));
return HttpDataResponses.AsBadRequest<int>(string.Format(ErrorMessages.RequestValidationError, request));
}

var addResult = await _weatherCommandsRepository.AddFavoriteLocation(request, cancellationToken);
if(addResult.IsFailed)
{
_logger.LogError(LogEvents.FavoriteWeathersStoreToDatabase, addResult.Errors.JoinToMessage());
return HttpDataResponses.AsInternalServerError<bool>(ErrorMessages.CantStoreLocation);
return HttpDataResponses.AsInternalServerError<int>(ErrorMessages.CantStoreLocation);
}

return HttpDataResponses.AsOK(true);
return HttpDataResponses.AsOK(addResult.Value);
}
}
}
40 changes: 40 additions & 0 deletions src/Weather.Core/Commands/DeleteFavoriteHandler.cs
@@ -0,0 +1,40 @@
using Ardalis.GuardClauses;
using Validot;
using Weather.Core.Abstractions;
using Weather.Core.Resources;
using Weather.Domain.Commands;
using Weather.Domain.Extensions;
using Weather.Domain.Http;

namespace Weather.Core.Commands
{
internal sealed class DeleteFavoriteHandler : IDeleteFavoriteHandler
{
private readonly IWeatherCommandsRepository _weatherCommandsRepository;
private readonly IValidator<DeleteFavoriteCommand> _validator;

public DeleteFavoriteHandler(
IWeatherCommandsRepository weatherCommandsRepository,
IValidator<DeleteFavoriteCommand> validator)
{
_weatherCommandsRepository = Guard.Against.Null(weatherCommandsRepository);
_validator = Guard.Against.Null(validator);
}

public async Task<HttpDataResponse<bool>> HandleAsync(DeleteFavoriteCommand request, CancellationToken cancellationToken)
{
if (!_validator.IsValid(request))
{
return HttpDataResponses.AsBadRequest<bool>(string.Format(ErrorMessages.RequestValidationError, request));
}

var addResult = await _weatherCommandsRepository.DeleteFavoriteLocationSafeAsync(request, cancellationToken);
if (addResult.IsFailed)
{
return HttpDataResponses.AsInternalServerError<bool>("Location was not deleted from database.");
}

return HttpDataResponses.AsOK(true);
}
}
}
Expand Up @@ -24,7 +24,8 @@ private static IServiceCollection AddHandlers(this IServiceCollection serviceCol
.AddScoped<IGetCurrentWeatherHandler, GetCurrentWeatherHandler>()
.AddScoped<IGetFavoritesHandler, GetFavoritesHandler>()
.AddScoped<IGetForecastWeatherHandler, GetForecastWeatherHandler>()
.AddScoped<IAddFavoriteHandler, AddFavoriteHandler>();
.AddScoped<IAddFavoriteHandler, AddFavoriteHandler>()
.AddScoped<IDeleteFavoriteHandler, DeleteFavoriteHandler>();

private static IServiceCollection AddValidation(this IServiceCollection serviceCollection)
=> serviceCollection
Expand All @@ -33,6 +34,7 @@ private static IServiceCollection AddValidation(this IServiceCollection serviceC
.AddValidotSingleton<IValidator<LocationDto>, LocationDtoSpecificationHolder, LocationDto>()
.AddValidotSingleton<IValidator<AddFavoriteCommand>, AddFavoriteCommandSpecificationHolder, AddFavoriteCommand>()
.AddValidotSingleton<IValidator<GetCurrentWeatherQuery>, GetCurrentWeatherQuerySpecificationHolder, GetCurrentWeatherQuery>()
.AddValidotSingleton<IValidator<GetForecastWeatherQuery>, GetForecastWeatherSpecificationHolder, GetForecastWeatherQuery>();
.AddValidotSingleton<IValidator<GetForecastWeatherQuery>, GetForecastWeatherSpecificationHolder, GetForecastWeatherQuery>()
.AddValidotSingleton<IValidator<DeleteFavoriteCommand>, DeleteFavoriteCommandSpecificationHolder, DeleteFavoriteCommand>();
}
}

0 comments on commit e1de992

Please sign in to comment.