fixes and tweaks

This commit is contained in:
moarten
2026-04-29 20:36:08 +02:00
parent b71f45e76c
commit 385119bb27
58 changed files with 1512 additions and 350 deletions
@@ -8,13 +8,19 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.4" />
<PackageReference Include="coverlet.collector" Version="10.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="FluentAssertions" Version="8.9.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.4.0" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="Moq.EntityFrameworkCore" Version="10.0.0.2" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="xunit.v3" Version="3.2.2" />
</ItemGroup>
<ItemGroup>
@@ -0,0 +1,99 @@
using FluentAssertions;
using Lutra.Application.Interfaces;
using Lutra.Application.Models.Verspakketten;
using Lutra.Application.Verspakketten;
using Moq;
using Moq.EntityFrameworkCore;
namespace Lutra.Application.UnitTests.Verspakketten;
public class CreateVerspakketWithFotosHandlerTests
{
private readonly Mock<ILutraDbContext> _contextMock;
private readonly CreateVerspakket.Handler _handler;
// 1x1 white PNG as base64
private const string ValidBase64Png = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI6QAAAABJRU5ErkJggg==";
public CreateVerspakketWithFotosHandlerTests()
{
_contextMock = new Mock<ILutraDbContext>();
_handler = new CreateVerspakket.Handler(_contextMock.Object);
}
private void SetupContext(Guid supermarktId)
{
var supermarkten = new List<Domain.Entities.Supermarkt>
{
new() { Id = supermarktId, Naam = "AH", CreatedAt = DateTime.UtcNow, ModifiedAt = DateTime.UtcNow }
};
_contextMock.Setup(c => c.Supermarkten).ReturnsDbSet(supermarkten);
_contextMock.Setup(c => c.Verspaketten).ReturnsDbSet(new List<Domain.Entities.Verspakket>());
_contextMock.Setup(c => c.SaveChangesAsync(It.IsAny<CancellationToken>())).ReturnsAsync(1);
}
[Fact]
public async Task Handle_WithFotos_CreatesVerspakketWithFotos()
{
var supermarktId = Guid.NewGuid();
SetupContext(supermarktId);
Domain.Entities.Verspakket? saved = null;
_contextMock
.Setup(c => c.Verspaketten.AddAsync(It.IsAny<Domain.Entities.Verspakket>(), It.IsAny<CancellationToken>()))
.Callback<Domain.Entities.Verspakket, CancellationToken>((v, _) => saved = v);
var fotos = new List<VerspakketFoto>
{
new(ValidBase64Png, IsMainImage: true),
new(ValidBase64Png, IsMainImage: false)
};
var command = new CreateVerspakket.Command("Lente Pakket", 999, 2, supermarktId, null, fotos);
await _handler.Handle(command, CancellationToken.None);
saved.Should().NotBeNull();
saved!.Fotos.Should().HaveCount(2);
saved.Fotos.Count(f => f.IsMainImage).Should().Be(1);
}
[Fact]
public async Task Handle_WithoutFotos_CreatesVerspakketWithNoFotos()
{
var supermarktId = Guid.NewGuid();
SetupContext(supermarktId);
Domain.Entities.Verspakket? saved = null;
_contextMock
.Setup(c => c.Verspaketten.AddAsync(It.IsAny<Domain.Entities.Verspakket>(), It.IsAny<CancellationToken>()))
.Callback<Domain.Entities.Verspakket, CancellationToken>((v, _) => saved = v);
var command = new CreateVerspakket.Command("Herfst Pakket", 799, 2, supermarktId, null);
await _handler.Handle(command, CancellationToken.None);
saved.Should().NotBeNull();
saved!.Fotos.Should().BeEmpty();
}
[Fact]
public async Task Handle_FotoBase64Decoded_StoresCorrectBytes()
{
var supermarktId = Guid.NewGuid();
SetupContext(supermarktId);
Domain.Entities.Verspakket? saved = null;
_contextMock
.Setup(c => c.Verspaketten.AddAsync(It.IsAny<Domain.Entities.Verspakket>(), It.IsAny<CancellationToken>()))
.Callback<Domain.Entities.Verspakket, CancellationToken>((v, _) => saved = v);
var fotos = new List<VerspakketFoto> { new(ValidBase64Png, IsMainImage: false) };
var command = new CreateVerspakket.Command("Pakket", null, 1, supermarktId, null, fotos);
await _handler.Handle(command, CancellationToken.None);
var foto = saved!.Fotos.Single();
foto.Data.Should().BeEquivalentTo(Convert.FromBase64String(ValidBase64Png));
}
}
@@ -0,0 +1,141 @@
using FluentAssertions;
using Lutra.Application.Interfaces;
using Lutra.Application.Models.Verspakketten;
using Lutra.Application.Verspakketten;
using Moq;
using Moq.EntityFrameworkCore;
namespace Lutra.Application.UnitTests.Verspakketten;
public class UpdateVerspakketHandlerTests
{
private readonly Mock<ILutraDbContext> _contextMock;
private readonly UpdateVerspakket.Handler _handler;
private const string ValidBase64Png = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI6QAAAABJRU5ErkJggg==";
public UpdateVerspakketHandlerTests()
{
_contextMock = new Mock<ILutraDbContext>();
_handler = new UpdateVerspakket.Handler(_contextMock.Object);
}
private (Guid verspakketId, Guid supermarktId) SetupContext(
List<Domain.Entities.VerspakketFoto>? existingFotos = null)
{
var supermarktId = Guid.NewGuid();
var verspakketId = Guid.NewGuid();
var supermarkten = new List<Domain.Entities.Supermarkt>
{
new() { Id = supermarktId, Naam = "AH", CreatedAt = DateTime.UtcNow, ModifiedAt = DateTime.UtcNow }
};
var verspakket = new Domain.Entities.Verspakket
{
Id = verspakketId,
Naam = "Oud Pakket",
PrijsInCenten = 500,
AantalPersonen = 2,
SupermarktId = supermarktId,
CreatedAt = DateTime.UtcNow,
ModifiedAt = DateTime.UtcNow
};
foreach (var foto in existingFotos ?? [])
verspakket.AddFoto(foto);
_contextMock.Setup(c => c.Supermarkten).ReturnsDbSet(supermarkten);
_contextMock.Setup(c => c.Verspaketten).ReturnsDbSet(new List<Domain.Entities.Verspakket> { verspakket });
_contextMock.Setup(c => c.VerspakketFotos).ReturnsDbSet(existingFotos ?? []);
_contextMock.Setup(c => c.SaveChangesAsync(It.IsAny<CancellationToken>())).ReturnsAsync(1);
return (verspakketId, supermarktId);
}
[Fact]
public async Task Handle_WithoutFotos_UpdatesFieldsOnly()
{
var (verspakketId, supermarktId) = SetupContext();
var command = new UpdateVerspakket.Command(verspakketId, "Nieuw Pakket", 1200, 4, supermarktId);
var result = await _handler.Handle(command, CancellationToken.None);
result.Should().NotBeNull();
_contextMock.Verify(c => c.SaveChangesAsync(It.IsAny<CancellationToken>()), Times.Once);
}
[Fact]
public async Task Handle_WithFotos_ReplacesFotos()
{
var oldFotoId = Guid.NewGuid();
var existingFotos = new List<Domain.Entities.VerspakketFoto>
{
new()
{
Id = oldFotoId,
Data = [0x00],
IsMainImage = true,
VerspakketId = Guid.NewGuid(),
CreatedAt = DateTime.UtcNow,
ModifiedAt = DateTime.UtcNow
}
};
var (verspakketId, supermarktId) = SetupContext(existingFotos);
var newFotos = new List<VerspakketFoto>
{
new(ValidBase64Png, IsMainImage: true),
new(ValidBase64Png, IsMainImage: false)
};
var command = new UpdateVerspakket.Command(verspakketId, "Pakket", 999, 2, supermarktId, newFotos);
await _handler.Handle(command, CancellationToken.None);
_contextMock.Verify(c => c.SaveChangesAsync(It.IsAny<CancellationToken>()), Times.Once);
}
[Fact]
public async Task Handle_NullFotos_DoesNotTouchFotos()
{
var (verspakketId, supermarktId) = SetupContext();
var command = new UpdateVerspakket.Command(verspakketId, "Pakket", 800, 3, supermarktId, null);
await _handler.Handle(command, CancellationToken.None);
// VerspakketFotos.RemoveRange should NOT be called when Fotos is null
_contextMock.Verify(
c => c.VerspakketFotos.RemoveRange(It.IsAny<IEnumerable<Domain.Entities.VerspakketFoto>>()),
Times.Never);
}
[Fact]
public async Task Handle_VerspakketNotFound_ThrowsInvalidOperationException()
{
var supermarktId = Guid.NewGuid();
_contextMock.Setup(c => c.Verspaketten).ReturnsDbSet(new List<Domain.Entities.Verspakket>());
_contextMock.Setup(c => c.Supermarkten).ReturnsDbSet(new List<Domain.Entities.Supermarkt>());
var command = new UpdateVerspakket.Command(Guid.NewGuid(), "Pakket", 800, 2, supermarktId);
var act = () => _handler.Handle(command, CancellationToken.None);
await act.Should().ThrowAsync<InvalidOperationException>().WithMessage("*was not found*");
}
[Fact]
public async Task Handle_InvalidAantalPersonen_ThrowsArgumentException()
{
var (verspakketId, supermarktId) = SetupContext();
var command = new UpdateVerspakket.Command(verspakketId, "Pakket", 800, 0, supermarktId);
var act = () => _handler.Handle(command, CancellationToken.None);
await act.Should().ThrowAsync<ArgumentException>();
}
}