Enforce brewery ownership on update and delete (#267)

This commit is contained in:
Aaron Po
2026-08-31 13:37:17 -04:00
committed by GitHub
parent 49d7f860de
commit 8b429be857
31 changed files with 978 additions and 287 deletions

View File

@@ -32,6 +32,9 @@ public class CreateBreweryHandlerTests
.Setup(r => r.CreateAsync(It.IsAny<BreweryPost>()))
.Callback<BreweryPost>(b => persisted = b)
.Returns(Task.CompletedTask);
_repoMock
.Setup(r => r.GetByIdAsync(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(() => persisted);
DateTime before = DateTime.UtcNow;
BreweryDto result = await _handler.Handle(command, CancellationToken.None);
@@ -53,8 +56,27 @@ public class CreateBreweryHandlerTests
persisted.Location.PostalCode.Should().Be(command.Location.PostalCode);
_repoMock.Verify(r => r.CreateAsync(It.IsAny<BreweryPost>()), Times.Once);
_repoMock.Verify(
r => r.GetByIdAsync(persisted.BreweryPostId, It.IsAny<CancellationToken>()),
Times.Once
);
result.BreweryName.Should().Be("MyBrew");
result.Location.Should().NotBeNull();
}
[Fact]
public async Task Handle_ThrowsInvalidOperationException_WhenBreweryNotFoundAfterCreation()
{
CreateBreweryCommand command = new(Guid.NewGuid(), "MyBrew", "Desc", ValidLocation());
_repoMock.Setup(r => r.CreateAsync(It.IsAny<BreweryPost>())).Returns(Task.CompletedTask);
_repoMock
.Setup(r => r.GetByIdAsync(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((BreweryPost?)null);
Func<Task> act = () => _handler.Handle(command, CancellationToken.None);
await act.Should().ThrowAsync<InvalidOperationException>();
}
}

View File

@@ -1,21 +1,66 @@
using Domain.Exceptions;
using Features.Breweries.Commands.DeleteBrewery;
using Features.Breweries.Repository;
using FluentAssertions;
using Moq;
namespace Features.Breweries.Tests.Commands;
public class DeleteBreweryHandlerTests
{
[Fact]
public async Task Handle_DelegatesToRepository()
private readonly DeleteBreweryHandler _handler;
private readonly Mock<IBreweryRepository> _repoMock = new();
public DeleteBreweryHandlerTests()
{
Mock<IBreweryRepository> repoMock = new();
DeleteBreweryHandler handler = new(repoMock.Object);
Guid id = Guid.NewGuid();
repoMock.Setup(r => r.DeleteAsync(id)).Returns(Task.CompletedTask);
_handler = new DeleteBreweryHandler(_repoMock.Object);
}
await handler.Handle(new DeleteBreweryCommand(id), CancellationToken.None);
private void SetUpExistingBrewery(Guid breweryPostId, Guid postedById)
{
_repoMock
.Setup(r => r.GetPostedByIdAsync(breweryPostId, It.IsAny<CancellationToken>()))
.ReturnsAsync(postedById);
}
repoMock.Verify(r => r.DeleteAsync(id), Times.Once);
[Fact]
public async Task Handle_DelegatesToRepository_WhenRequestingUserIsThePoster()
{
Guid breweryPostId = Guid.NewGuid();
Guid requestingUserId = Guid.NewGuid();
SetUpExistingBrewery(breweryPostId, requestingUserId);
_repoMock.Setup(r => r.DeleteAsync(breweryPostId)).Returns(Task.CompletedTask);
await _handler.Handle(
new DeleteBreweryCommand(breweryPostId, requestingUserId),
CancellationToken.None
);
_repoMock.Verify(r => r.DeleteAsync(breweryPostId), Times.Once);
}
[Fact]
public async Task Handle_ThrowsNotFoundException_WhenBreweryDoesNotExist()
{
DeleteBreweryCommand command = new(Guid.NewGuid(), Guid.NewGuid());
Func<Task> act = () => _handler.Handle(command, CancellationToken.None);
await act.Should().ThrowAsync<NotFoundException>();
_repoMock.Verify(r => r.DeleteAsync(It.IsAny<Guid>()), Times.Never);
}
[Fact]
public async Task Handle_ThrowsForbiddenException_WhenRequestingUserIsNotThePoster()
{
Guid breweryPostId = Guid.NewGuid();
SetUpExistingBrewery(breweryPostId, Guid.NewGuid());
DeleteBreweryCommand command = new(breweryPostId, Guid.NewGuid());
Func<Task> act = () => _handler.Handle(command, CancellationToken.None);
await act.Should().ThrowAsync<ForbiddenException>();
_repoMock.Verify(r => r.DeleteAsync(It.IsAny<Guid>()), Times.Never);
}
}

View File

@@ -1,4 +1,5 @@
using Domain.Entities;
using Domain.Exceptions;
using Features.Breweries.Commands.UpdateBrewery;
using Features.Breweries.Repository;
using FluentAssertions;
@@ -16,13 +17,24 @@ public class UpdateBreweryHandlerTests
_handler = new UpdateBreweryHandler(_repoMock.Object);
}
private void SetUpExistingBrewery(Guid breweryPostId, Guid postedById)
{
_repoMock
.Setup(r => r.GetPostedByIdAsync(breweryPostId, It.IsAny<CancellationToken>()))
.ReturnsAsync(postedById);
}
[Fact]
public async Task Handle_UpdatesNameDescription_AndSetsUpdatedAt()
{
Guid breweryPostId = Guid.NewGuid();
Guid requestingUserId = Guid.NewGuid();
SetUpExistingBrewery(breweryPostId, requestingUserId);
UpdateBreweryCommand command = new(
Guid.NewGuid(),
breweryPostId,
requestingUserId,
[0x01, 0x02],
Guid.NewGuid(),
"Renamed",
"New description",
null
@@ -30,8 +42,8 @@ public class UpdateBreweryHandlerTests
BreweryPost? persisted = null;
_repoMock
.Setup(r => r.UpdateAsync(It.IsAny<BreweryPost>()))
.Callback<BreweryPost>(b => persisted = b)
.Setup(r => r.UpdateAsync(It.IsAny<BreweryPost>(), It.IsAny<CancellationToken>()))
.Callback<BreweryPost, CancellationToken>((b, _) => persisted = b)
.ReturnsAsync(() => persisted!);
DateTime before = DateTime.UtcNow;
@@ -40,7 +52,6 @@ public class UpdateBreweryHandlerTests
persisted.Should().NotBeNull();
persisted!.BreweryPostId.Should().Be(command.BreweryPostId);
persisted.PostedById.Should().Be(command.PostedById);
persisted.BreweryName.Should().Be("Renamed");
persisted.Description.Should().Be("New description");
persisted.RowVersion.Should().Equal(command.RowVersion);
@@ -51,10 +62,14 @@ public class UpdateBreweryHandlerTests
[Fact]
public async Task Handle_ClearsLocation_WhenCommandLocationIsNull()
{
Guid breweryPostId = Guid.NewGuid();
Guid requestingUserId = Guid.NewGuid();
SetUpExistingBrewery(breweryPostId, requestingUserId);
UpdateBreweryCommand command = new(
Guid.NewGuid(),
breweryPostId,
requestingUserId,
[0x01, 0x02],
Guid.NewGuid(),
"Name",
"Description",
null
@@ -62,8 +77,8 @@ public class UpdateBreweryHandlerTests
BreweryPost? persisted = null;
_repoMock
.Setup(r => r.UpdateAsync(It.IsAny<BreweryPost>()))
.Callback<BreweryPost>(b => persisted = b)
.Setup(r => r.UpdateAsync(It.IsAny<BreweryPost>(), It.IsAny<CancellationToken>()))
.Callback<BreweryPost, CancellationToken>((b, _) => persisted = b)
.ReturnsAsync(() => persisted!);
await _handler.Handle(command, CancellationToken.None);
@@ -74,6 +89,10 @@ public class UpdateBreweryHandlerTests
[Fact]
public async Task Handle_SetsLocation_WhenCommandLocationProvided()
{
Guid breweryPostId = Guid.NewGuid();
Guid requestingUserId = Guid.NewGuid();
SetUpExistingBrewery(breweryPostId, requestingUserId);
UpdateBreweryLocation locationCommand = new(
Guid.NewGuid(),
Guid.NewGuid(),
@@ -83,9 +102,9 @@ public class UpdateBreweryHandlerTests
null
);
UpdateBreweryCommand command = new(
Guid.NewGuid(),
breweryPostId,
requestingUserId,
[0x01, 0x02],
Guid.NewGuid(),
"Name",
"Description",
locationCommand
@@ -93,8 +112,8 @@ public class UpdateBreweryHandlerTests
BreweryPost? persisted = null;
_repoMock
.Setup(r => r.UpdateAsync(It.IsAny<BreweryPost>()))
.Callback<BreweryPost>(b => persisted = b)
.Setup(r => r.UpdateAsync(It.IsAny<BreweryPost>(), It.IsAny<CancellationToken>()))
.Callback<BreweryPost, CancellationToken>((b, _) => persisted = b)
.ReturnsAsync(() => persisted!);
await _handler.Handle(command, CancellationToken.None);
@@ -109,4 +128,41 @@ public class UpdateBreweryHandlerTests
persisted.Location.AddressLine2.Should().Be(locationCommand.AddressLine2);
persisted.Location.PostalCode.Should().Be(locationCommand.PostalCode);
}
[Fact]
public async Task Handle_ThrowsNotFoundException_WhenBreweryDoesNotExist()
{
UpdateBreweryCommand command = new(
Guid.NewGuid(),
Guid.NewGuid(),
[0x01, 0x02],
"Name",
"Description",
null
);
Func<Task> act = () => _handler.Handle(command, CancellationToken.None);
await act.Should().ThrowAsync<NotFoundException>();
}
[Fact]
public async Task Handle_ThrowsForbiddenException_WhenRequestingUserIsNotThePoster()
{
Guid breweryPostId = Guid.NewGuid();
SetUpExistingBrewery(breweryPostId, Guid.NewGuid());
UpdateBreweryCommand command = new(
breweryPostId,
Guid.NewGuid(),
[0x01, 0x02],
"Name",
"Description",
null
);
Func<Task> act = () => _handler.Handle(command, CancellationToken.None);
await act.Should().ThrowAsync<ForbiddenException>();
}
}

View File

@@ -22,7 +22,7 @@ public class GetAllBreweryLocationsHandlerTests
{
_repoMock.Setup(r => r.GetAllLocations()).ReturnsAsync(Array.Empty<BreweryPost>());
IEnumerable<SimplifiedBreweryDto> result = await _handler.Handle(
IEnumerable<BreweryWithLocationDto> result = await _handler.Handle(
new GetAllBreweryLocationsQuery(),
CancellationToken.None
);
@@ -41,7 +41,7 @@ public class GetAllBreweryLocationsHandlerTests
];
_repoMock.Setup(r => r.GetAllLocations()).ReturnsAsync(breweries);
IEnumerable<SimplifiedBreweryDto> result = await _handler.Handle(
IEnumerable<BreweryWithLocationDto> result = await _handler.Handle(
new GetAllBreweryLocationsQuery(),
CancellationToken.None
);

View File

@@ -29,7 +29,7 @@ public class GetBreweryLocationsWithinRangeHandlerTests
)
.ReturnsAsync(Array.Empty<BreweryPost>());
IEnumerable<SimplifiedBreweryDto> result = await _handler.Handle(
IEnumerable<BreweryWithLocationDto> result = await _handler.Handle(
new GetBreweryLocationsWithinRangeQuery(43.6532, -79.3832, 1000),
CancellationToken.None
);
@@ -57,7 +57,7 @@ public class GetBreweryLocationsWithinRangeHandlerTests
.Setup(r => r.GetAllLocationsWithinRange(It.IsAny<CoordinateData>(), It.IsAny<double>()))
.ReturnsAsync(breweries);
IEnumerable<SimplifiedBreweryDto> result = await _handler.Handle(
IEnumerable<BreweryWithLocationDto> result = await _handler.Handle(
new GetBreweryLocationsWithinRangeQuery(43.6532, -79.3832, 1000),
CancellationToken.None
);