-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
HeroEndpoints: added PUT update endpoint
- Loading branch information
1 parent
182e330
commit 9256eaf
Showing
5 changed files
with
136 additions
and
1 deletion.
There are no files selected for viewing
46 changes: 46 additions & 0 deletions
46
src/Application/Features/Heroes/Commands/UpdateHero/UpdateHeroCommand.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,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; | ||
} | ||
} |
7 changes: 7 additions & 0 deletions
7
src/Application/Features/Heroes/Commands/UpdateHero/UpdateHeroPowerDto.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,7 @@ | ||
namespace SSW.CleanArchitecture.Application.Features.Heroes.Commands.UpdateHero; | ||
|
||
public class UpdateHeroPowerDto | ||
{ | ||
public required string Name { get; set; } | ||
public required int PowerLevel { get; set; } | ||
} |
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
72 changes: 72 additions & 0 deletions
72
tests/WebApi.IntegrationTests/Endpoints/Heroes/Commands/UpdateHero/UpdateHeroCommandTests.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,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(); | ||
} | ||
} |