Skip to content

Commit

Permalink
HeroEndpoints: added PUT update endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
christoment committed Apr 16, 2024
1 parent 182e330 commit 9256eaf
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using Microsoft.EntityFrameworkCore;
using SSW.CleanArchitecture.Application.Common.Exceptions;
using SSW.CleanArchitecture.Application.Common.Interfaces;
using SSW.CleanArchitecture.Domain.Heroes;

namespace SSW.CleanArchitecture.Application.Features.Heroes.Commands.UpdateHero;

public sealed record UpdateHeroCommand(
HeroId HeroId,
string Name,
string Alias,
IEnumerable<UpdateHeroPowerDto> Powers) : IRequest<Guid>;

// ReSharper disable once UnusedType.Global
public sealed class UpdateHeroCommandHandler(IApplicationDbContext dbContext)
: IRequestHandler<UpdateHeroCommand, Guid>
{
public async Task<Guid> Handle(UpdateHeroCommand request, CancellationToken cancellationToken)
{
var hero = await dbContext.Heroes
.Include(h => h.Powers)
.FirstOrDefaultAsync(h => h.Id == request.HeroId, cancellationToken);

if (hero is null)
{
throw new NotFoundException(nameof(Hero), request.HeroId);
}

hero.UpdateName(request.Name);
hero.UpdateAlias(request.Alias);
var existingPowers = hero.Powers.Select(p => p.Name).ToList();
foreach (var existingPower in existingPowers)
{
hero.RemovePower(existingPower);
}

foreach (var heroPowerModel in request.Powers)
{
hero.AddPower(new Power(heroPowerModel.Name, heroPowerModel.PowerLevel));
}

await dbContext.SaveChangesAsync(cancellationToken);

return hero.Id.Value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace SSW.CleanArchitecture.Application.Features.Heroes.Commands.UpdateHero;

public class UpdateHeroPowerDto
{
public required string Name { get; set; }
public required int PowerLevel { get; set; }
}
10 changes: 10 additions & 0 deletions src/WebApi/Features/HeroEndpoints.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using MediatR;
using SSW.CleanArchitecture.Application.Features.Heroes.Commands.CreateHero;
using SSW.CleanArchitecture.Application.Features.Heroes.Commands.UpdateHero;
using SSW.CleanArchitecture.Application.Features.Heroes.Queries.GetAllHeroes;
using SSW.CleanArchitecture.WebApi.Extensions;

Expand All @@ -24,6 +25,15 @@ public static void MapHeroEndpoints(this WebApplication app)
// myWeirdField: "string" vs myWeirdField: "this-silly-string"
// (https://github.com/SSWConsulting/SSW.CleanArchitecture/issues/79)

group
.MapPut("/", async (ISender sender, UpdateHeroCommand command, CancellationToken ct) =>
{
await sender.Send(command, ct);
return Results.NoContent();
})
.WithName("UpdateHero")
.ProducesPut();

group
.MapPost("/", async (ISender sender, CreateHeroCommand command, CancellationToken ct) =>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public async Task Command_ShouldCreateHero()

// Assert
result.StatusCode.Should().Be(HttpStatusCode.Created);
var item = await Context.Heroes.FirstAsync();
var item = await Context.Heroes.AsNoTracking().FirstAsync();

item.Should().NotBeNull();
item.Name.Should().Be(cmd.Name);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using Microsoft.EntityFrameworkCore;
using SSW.CleanArchitecture.Application.Features.Heroes.Commands.UpdateHero;
using SSW.CleanArchitecture.Domain.Heroes;
using System.Net;
using System.Net.Http.Json;
using WebApi.IntegrationTests.Common.Factories;
using WebApi.IntegrationTests.Common.Fixtures;

namespace WebApi.IntegrationTests.Endpoints.Heroes.Commands.UpdateHero;

public class UpdateHeroCommandTests(TestingDatabaseFixture fixture, ITestOutputHelper output)
: IntegrationTestBase(fixture, output)
{
[Fact]
public async Task Command_ShouldUpdateHero()
{
// Arrange
var heroName = "2021-01-01T00:00:00Z";
var heroAlias = "2021-01-01T00:00:00Z-alias";
var hero = HeroFactory.Generate();
await AddEntityAsync(hero);
(string Name, int PowerLevel)[] powers = [
("Heat vision", 7),
("Super-strength", 10),
("Flight", 8),
];
var cmd = new UpdateHeroCommand(
hero.Id,
heroName,
heroAlias,
powers.Select(p => new UpdateHeroPowerDto { Name = p.Name, PowerLevel = p.PowerLevel }));
var client = GetAnonymousClient();
var createdTimeStamp = DateTime.Now;

// Act
var result = await client.PutAsJsonAsync("/heroes", cmd);

// Assert
result.StatusCode.Should().Be(HttpStatusCode.NoContent);
Hero item = await Context.Heroes.AsNoTracking().FirstAsync(dbHero => dbHero.Id == hero.Id);

item.Should().NotBeNull();
item.Name.Should().Be(cmd.Name);
item.Alias.Should().Be(cmd.Alias);
item.PowerLevel.Should().Be(25);
item.Powers.Should().HaveCount(3);
item.UpdatedAt.Should().NotBe(hero.CreatedAt);
item.UpdatedAt.Should().BeCloseTo(createdTimeStamp, TimeSpan.FromSeconds(10));
}

[Fact]
public async Task Command_WhenHeroDoesNotExist_ShouldReturnNotFound()
{
// Arrange
var heroId = new HeroId(Guid.NewGuid());
var cmd = new UpdateHeroCommand(
heroId,
"foo",
"bar",
new [] { new UpdateHeroPowerDto { Name = "Heat vision", PowerLevel = 7 } });
var client = GetAnonymousClient();

// Act
var result = await client.PutAsJsonAsync("/heroes", cmd);

// Assert
result.StatusCode.Should().Be(HttpStatusCode.NotFound);
Hero? item = await Context.Heroes.AsNoTracking().FirstOrDefaultAsync(dbHero => dbHero.Id == heroId);

item.Should().BeNull();
}
}

0 comments on commit 9256eaf

Please sign in to comment.