Skip to content

Commit

Permalink
Add GetTodoByIdQuery
Browse files Browse the repository at this point in the history
  • Loading branch information
jicking committed Apr 19, 2024
1 parent 7298ec8 commit c47af51
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 18 deletions.
2 changes: 1 addition & 1 deletion JixMinApi/Features/Todo/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ public static TodoDto ToDto(this Todo model)
return new TodoDto(model.Id, model.Name, model.IsComplete);
}

public static List<TodoDto> ToDto(this IList<Todo> model)
public static List<TodoDto> ToDto(this IEnumerable<Todo> model)
{
var result = new List<TodoDto>();
foreach (var item in model)
Expand Down
49 changes: 49 additions & 0 deletions JixMinApi/Features/Todo/Queries/GetTodoByIdQuery.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using FluentValidation;
using FluentValidation.Results;
using JixMinApi.Features.Todo.Commands;
using MediatR;
using Microsoft.EntityFrameworkCore;

namespace JixMinApi.Features.Todo.Queries;

public record GetTodoByIdQuery(Guid Id) :IRequest<Result<TodoDto>>;

public sealed class GetTodoByIdQueryValidator
: AbstractValidator<GetTodoByIdQuery>
{
public GetTodoByIdQueryValidator()
{
RuleFor(query => query.Id)
.NotNull()
.NotEmpty();
}
}

public class GetTodoByIdQueryHandler : IRequestHandler<GetTodoByIdQuery, Result<TodoDto>>
{
private readonly TodoDb _db;
private readonly ILogger<GetTodoByIdQueryHandler> _logger;

public GetTodoByIdQueryHandler(TodoDb db, ILogger<GetTodoByIdQueryHandler> logger) => (_db, _logger) = (db, logger);

public async Task<Result<TodoDto>> Handle(GetTodoByIdQuery request, CancellationToken cancellationToken)
{
// validate
var validator = new GetTodoByIdQueryValidator();
ValidationResult validationResult = validator.Validate(request);

if (!validationResult.IsValid)
{
var errors = validationResult.Errors.Select(e => new KeyValuePair<string, string>(e.PropertyName, e.ErrorMessage));
return new Result<TodoDto>(errors);
}

_logger.LogDebug($"Start query");
Todo? todo = await _db.Todos
.AsNoTracking()
.FirstOrDefaultAsync(x => x.Id == request.Id, cancellationToken: cancellationToken);
_logger.LogDebug($"End query");

return new Result<TodoDto>(todo?.ToDto());

Check warning on line 47 in JixMinApi/Features/Todo/Queries/GetTodoByIdQuery.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'value' in 'Result<TodoDto>.Result(TodoDto value)'.

Check warning on line 47 in JixMinApi/Features/Todo/Queries/GetTodoByIdQuery.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'value' in 'Result<TodoDto>.Result(TodoDto value)'.
}
}
7 changes: 7 additions & 0 deletions JixMinApi/Features/Todo/Result.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ public Result(string field, string validationErrorMessage)
{
Errors = [new(field, validationErrorMessage)];
}

// TODO: using NotFound still Result.IsSuccess to true, will lead to bugs
public static Result<T> NotFound<T>()

Check warning on line 26 in JixMinApi/Features/Todo/Result.cs

View workflow job for this annotation

GitHub Actions / build

Type parameter 'T' has the same name as the type parameter from outer type 'Result<T>'

Check warning on line 26 in JixMinApi/Features/Todo/Result.cs

View workflow job for this annotation

GitHub Actions / build

Type parameter 'T' has the same name as the type parameter from outer type 'Result<T>'
{
T val = default(T);

Check warning on line 28 in JixMinApi/Features/Todo/Result.cs

View workflow job for this annotation

GitHub Actions / build

Converting null literal or possible null value to non-nullable type.

Check warning on line 28 in JixMinApi/Features/Todo/Result.cs

View workflow job for this annotation

GitHub Actions / build

Converting null literal or possible null value to non-nullable type.
return new Result<T>(val);

Check warning on line 29 in JixMinApi/Features/Todo/Result.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'value' in 'Result<T>.Result(T value)'.

Check warning on line 29 in JixMinApi/Features/Todo/Result.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'value' in 'Result<T>.Result(T value)'.
}
}

public static class ResultExtensions
Expand Down
17 changes: 6 additions & 11 deletions JixMinApi/Features/Todo/TodoEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,24 +63,19 @@ public static async Task<Ok<TodoDto[]>> GetAllTodosAsync(IMediator mediator)
/// </summary>
public static async Task<Results<ValidationProblem, NotFound, Ok<TodoDto>>> GetTodoByIdAsync(Guid id, IMediator mediator)
{
if (id == Guid.Empty)
var result = await mediator.Send(new GetTodoByIdQuery(id));

if (!result.IsSuccess)
{
var errors = new Dictionary<string, string[]>
{
["id"] = ["id parameter must not be an empty guid."],
};
return TypedResults.ValidationProblem(errors);
return TypedResults.ValidationProblem(result.Errors.ToErrorDictionary());
}

var todos = await mediator.Send(new GetAllTodosQuery());
var todo = todos.FirstOrDefault(t => t.Id == id);

if (todo is null)
if (result.Value is null)
{
return TypedResults.NotFound();
}

return TypedResults.Ok(todo);
return TypedResults.Ok(result.Value);
}

/// <summary>
Expand Down
13 changes: 7 additions & 6 deletions JixMinApiTests/Features/Todo/TodoEndpointsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using MediatR;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using Moq;
using Xunit;
using Assert = Xunit.Assert;
Expand All @@ -20,6 +21,8 @@ public async Task GetTodoByIdAsync_Returns_BadRequest_When_Id_Is_Empty()
{
["id"] = ["id parameter must not be an empty guid."],
};
mediatorMock.Setup(m => m.Send(It.IsAny<GetTodoByIdQuery>(), CancellationToken.None))
.ReturnsAsync(new Result<TodoDto>("id", "id parameter must not be an empty guid."));

// Act
var response = await TodoEndpoints.GetTodoByIdAsync(emptyId, mediatorMock.Object);
Expand All @@ -42,8 +45,8 @@ public async Task GetTodoByIdAsync_Returns_NotFound_When_Todo_Not_Found()
// Arrange
var mediatorMock = new Mock<IMediator>();
var nonExistentId = Guid.NewGuid(); // Assuming this id doesn't exist
mediatorMock.Setup(m => m.Send(It.IsAny<GetAllTodosQuery>(), CancellationToken.None))
.ReturnsAsync(new List<TodoDto>());
mediatorMock.Setup(m => m.Send(It.IsAny<GetTodoByIdQuery>(), CancellationToken.None))
.ReturnsAsync(Result<TodoDto>.NotFound<TodoDto>());

// Act
var response = await TodoEndpoints.GetTodoByIdAsync(nonExistentId, mediatorMock.Object);
Expand All @@ -64,10 +67,8 @@ public async Task GetTodoByIdAsync_Returns_Ok_When_Todo_Found()
var existingId = Guid.NewGuid(); // Assuming this id exists
var todoDto = new TodoDto(existingId, "Test name", false); // Assuming todo with this id exists

mediatorMock.Setup(m => m.Send(It.IsAny<GetAllTodosQuery>(), CancellationToken.None))
.ReturnsAsync(new List<TodoDto>() {
todoDto
});
mediatorMock.Setup(m => m.Send(It.IsAny<GetTodoByIdQuery>(), CancellationToken.None))
.ReturnsAsync(new Result<TodoDto>(todoDto));

// Act
var response = await TodoEndpoints.GetTodoByIdAsync(existingId, mediatorMock.Object);
Expand Down

0 comments on commit c47af51

Please sign in to comment.