mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-10-15 01:54:43 +00:00
Add self-service account management commands (delete, update username/email/password/profile)
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
using Domain.Exceptions;
|
||||
using Features.Auth.Commands.DeleteAccount;
|
||||
using Features.Auth.Identity;
|
||||
using Features.Auth.Tests.TestSupport;
|
||||
using FluentAssertions;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Moq;
|
||||
|
||||
namespace Features.Auth.Tests.Commands;
|
||||
|
||||
public class DeleteAccountHandlerTests
|
||||
{
|
||||
private readonly DeleteAccountHandler _handler;
|
||||
private readonly Mock<UserManager<ApplicationUser>> _userManagerMock;
|
||||
|
||||
public DeleteAccountHandlerTests()
|
||||
{
|
||||
_userManagerMock = UserManagerMockFactory.Create();
|
||||
_handler = new DeleteAccountHandler(_userManagerMock.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_WithExistingUser_DeletesAccount()
|
||||
{
|
||||
Guid userId = Guid.NewGuid();
|
||||
ApplicationUser user = new() { Id = userId };
|
||||
|
||||
_userManagerMock.Setup(x => x.FindByIdAsync(userId.ToString())).ReturnsAsync(user);
|
||||
_userManagerMock.Setup(x => x.DeleteAsync(user)).ReturnsAsync(IdentityResult.Success);
|
||||
|
||||
await _handler.Handle(new DeleteAccountCommand(userId), CancellationToken.None);
|
||||
|
||||
_userManagerMock.Verify(x => x.DeleteAsync(user), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_WithNonExistentUser_ThrowsNotFoundException()
|
||||
{
|
||||
Guid userId = Guid.NewGuid();
|
||||
_userManagerMock
|
||||
.Setup(x => x.FindByIdAsync(userId.ToString()))
|
||||
.ReturnsAsync((ApplicationUser?)null);
|
||||
|
||||
Func<Task> act = async () =>
|
||||
await _handler.Handle(new DeleteAccountCommand(userId), CancellationToken.None);
|
||||
|
||||
await act.Should().ThrowAsync<NotFoundException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_WithDependentRecords_ThrowsConflictException()
|
||||
{
|
||||
Guid userId = Guid.NewGuid();
|
||||
ApplicationUser user = new() { Id = userId };
|
||||
|
||||
_userManagerMock.Setup(x => x.FindByIdAsync(userId.ToString())).ReturnsAsync(user);
|
||||
_userManagerMock
|
||||
.Setup(x => x.DeleteAsync(user))
|
||||
.ReturnsAsync(
|
||||
IdentityResult.Failed(
|
||||
new IdentityError
|
||||
{
|
||||
Code = "AccountHasDependentRecords",
|
||||
Description = "still referenced",
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
Func<Task> act = async () =>
|
||||
await _handler.Handle(new DeleteAccountCommand(userId), CancellationToken.None);
|
||||
|
||||
await act.Should().ThrowAsync<ConflictException>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
using Domain.Exceptions;
|
||||
using Features.Auth.Commands.UpdateEmail;
|
||||
using Features.Auth.Dtos;
|
||||
using Features.Auth.Identity;
|
||||
using Features.Auth.Tests.TestSupport;
|
||||
using FluentAssertions;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Moq;
|
||||
|
||||
namespace Features.Auth.Tests.Commands;
|
||||
|
||||
public class UpdateEmailHandlerTests
|
||||
{
|
||||
private readonly UpdateEmailHandler _handler;
|
||||
private readonly Mock<UserManager<ApplicationUser>> _userManagerMock;
|
||||
|
||||
public UpdateEmailHandlerTests()
|
||||
{
|
||||
_userManagerMock = UserManagerMockFactory.Create();
|
||||
_handler = new UpdateEmailHandler(_userManagerMock.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_WithValidRequest_UpdatesEmailAndResetsConfirmation()
|
||||
{
|
||||
Guid userId = Guid.NewGuid();
|
||||
ApplicationUser user = new()
|
||||
{
|
||||
Id = userId,
|
||||
Email = "old@example.com",
|
||||
EmailConfirmed = true,
|
||||
};
|
||||
|
||||
_userManagerMock.Setup(x => x.FindByIdAsync(userId.ToString())).ReturnsAsync(user);
|
||||
_userManagerMock
|
||||
.Setup(x => x.SetEmailAsync(user, "new@example.com"))
|
||||
.Callback<ApplicationUser, string>(
|
||||
(u, e) =>
|
||||
{
|
||||
u.Email = e;
|
||||
u.EmailConfirmed = false;
|
||||
}
|
||||
)
|
||||
.ReturnsAsync(IdentityResult.Success);
|
||||
|
||||
UpdateEmailPayload result = await _handler.Handle(
|
||||
new UpdateEmailCommand(userId, "new@example.com"),
|
||||
CancellationToken.None
|
||||
);
|
||||
|
||||
result.UserAccountId.Should().Be(userId);
|
||||
result.Email.Should().Be("new@example.com");
|
||||
result.EmailConfirmed.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_WithNonExistentUser_ThrowsNotFoundException()
|
||||
{
|
||||
Guid userId = Guid.NewGuid();
|
||||
_userManagerMock
|
||||
.Setup(x => x.FindByIdAsync(userId.ToString()))
|
||||
.ReturnsAsync((ApplicationUser?)null);
|
||||
|
||||
Func<Task<UpdateEmailPayload>> act = async () =>
|
||||
await _handler.Handle(
|
||||
new UpdateEmailCommand(userId, "new@example.com"),
|
||||
CancellationToken.None
|
||||
);
|
||||
|
||||
await act.Should().ThrowAsync<NotFoundException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_WithDuplicateEmail_ThrowsConflictException()
|
||||
{
|
||||
Guid userId = Guid.NewGuid();
|
||||
ApplicationUser user = new() { Id = userId, Email = "old@example.com" };
|
||||
|
||||
_userManagerMock.Setup(x => x.FindByIdAsync(userId.ToString())).ReturnsAsync(user);
|
||||
_userManagerMock
|
||||
.Setup(x => x.SetEmailAsync(user, "taken@example.com"))
|
||||
.ReturnsAsync(
|
||||
IdentityResult.Failed(
|
||||
new IdentityError { Code = "DuplicateEmail", Description = "taken" }
|
||||
)
|
||||
);
|
||||
|
||||
Func<Task<UpdateEmailPayload>> act = async () =>
|
||||
await _handler.Handle(
|
||||
new UpdateEmailCommand(userId, "taken@example.com"),
|
||||
CancellationToken.None
|
||||
);
|
||||
|
||||
await act.Should().ThrowAsync<ConflictException>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
using Domain.Exceptions;
|
||||
using Features.Auth.Commands.UpdatePassword;
|
||||
using Features.Auth.Dtos;
|
||||
using Features.Auth.Identity;
|
||||
using Features.Auth.Tests.TestSupport;
|
||||
using FluentAssertions;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Moq;
|
||||
|
||||
namespace Features.Auth.Tests.Commands;
|
||||
|
||||
public class UpdatePasswordHandlerTests
|
||||
{
|
||||
private readonly UpdatePasswordHandler _handler;
|
||||
private readonly Mock<UserManager<ApplicationUser>> _userManagerMock;
|
||||
|
||||
public UpdatePasswordHandlerTests()
|
||||
{
|
||||
_userManagerMock = UserManagerMockFactory.Create();
|
||||
_handler = new UpdatePasswordHandler(_userManagerMock.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_WithValidRequest_ChangesPassword()
|
||||
{
|
||||
Guid userId = Guid.NewGuid();
|
||||
ApplicationUser user = new() { Id = userId };
|
||||
|
||||
_userManagerMock.Setup(x => x.FindByIdAsync(userId.ToString())).ReturnsAsync(user);
|
||||
_userManagerMock
|
||||
.Setup(x => x.ChangePasswordAsync(user, "OldPassword1!", "NewPassword1!"))
|
||||
.ReturnsAsync(IdentityResult.Success);
|
||||
|
||||
UpdatePasswordPayload result = await _handler.Handle(
|
||||
new UpdatePasswordCommand(userId, "OldPassword1!", "NewPassword1!"),
|
||||
CancellationToken.None
|
||||
);
|
||||
|
||||
result.UserAccountId.Should().Be(userId);
|
||||
result.ChangedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_WithNonExistentUser_ThrowsNotFoundException()
|
||||
{
|
||||
Guid userId = Guid.NewGuid();
|
||||
_userManagerMock
|
||||
.Setup(x => x.FindByIdAsync(userId.ToString()))
|
||||
.ReturnsAsync((ApplicationUser?)null);
|
||||
|
||||
Func<Task<UpdatePasswordPayload>> act = async () =>
|
||||
await _handler.Handle(
|
||||
new UpdatePasswordCommand(userId, "OldPassword1!", "NewPassword1!"),
|
||||
CancellationToken.None
|
||||
);
|
||||
|
||||
await act.Should().ThrowAsync<NotFoundException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_WithWrongCurrentPassword_ThrowsUnauthorizedException()
|
||||
{
|
||||
Guid userId = Guid.NewGuid();
|
||||
ApplicationUser user = new() { Id = userId };
|
||||
|
||||
_userManagerMock.Setup(x => x.FindByIdAsync(userId.ToString())).ReturnsAsync(user);
|
||||
_userManagerMock
|
||||
.Setup(x => x.ChangePasswordAsync(user, "WrongPassword1!", "NewPassword1!"))
|
||||
.ReturnsAsync(
|
||||
IdentityResult.Failed(
|
||||
new IdentityError { Code = "PasswordMismatch", Description = "mismatch" }
|
||||
)
|
||||
);
|
||||
|
||||
Func<Task<UpdatePasswordPayload>> act = async () =>
|
||||
await _handler.Handle(
|
||||
new UpdatePasswordCommand(userId, "WrongPassword1!", "NewPassword1!"),
|
||||
CancellationToken.None
|
||||
);
|
||||
|
||||
await act.Should().ThrowAsync<UnauthorizedException>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
using Domain.Exceptions;
|
||||
using Features.Auth.Commands.UpdateProfile;
|
||||
using Features.Auth.Dtos;
|
||||
using Features.Auth.Identity;
|
||||
using Features.Auth.Tests.TestSupport;
|
||||
using FluentAssertions;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Moq;
|
||||
|
||||
namespace Features.Auth.Tests.Commands;
|
||||
|
||||
public class UpdateProfileHandlerTests
|
||||
{
|
||||
private readonly UpdateProfileHandler _handler;
|
||||
private readonly Mock<UserManager<ApplicationUser>> _userManagerMock;
|
||||
|
||||
public UpdateProfileHandlerTests()
|
||||
{
|
||||
_userManagerMock = UserManagerMockFactory.Create();
|
||||
_handler = new UpdateProfileHandler(_userManagerMock.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_WithValidRequest_UpdatesProfileFields()
|
||||
{
|
||||
Guid userId = Guid.NewGuid();
|
||||
ApplicationUser user = new()
|
||||
{
|
||||
Id = userId,
|
||||
FirstName = "Old",
|
||||
LastName = "Name",
|
||||
DateOfBirth = new DateTime(1990, 1, 1),
|
||||
};
|
||||
DateTime newDob = new(1991, 2, 3);
|
||||
|
||||
_userManagerMock.Setup(x => x.FindByIdAsync(userId.ToString())).ReturnsAsync(user);
|
||||
_userManagerMock.Setup(x => x.UpdateAsync(user)).ReturnsAsync(IdentityResult.Success);
|
||||
|
||||
UpdateProfilePayload result = await _handler.Handle(
|
||||
new UpdateProfileCommand(userId, "New", "Person", newDob),
|
||||
CancellationToken.None
|
||||
);
|
||||
|
||||
result.UserAccountId.Should().Be(userId);
|
||||
result.FirstName.Should().Be("New");
|
||||
result.LastName.Should().Be("Person");
|
||||
result.DateOfBirth.Should().Be(newDob);
|
||||
_userManagerMock.Verify(x => x.UpdateAsync(user), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_WithNonExistentUser_ThrowsNotFoundException()
|
||||
{
|
||||
Guid userId = Guid.NewGuid();
|
||||
_userManagerMock
|
||||
.Setup(x => x.FindByIdAsync(userId.ToString()))
|
||||
.ReturnsAsync((ApplicationUser?)null);
|
||||
|
||||
Func<Task<UpdateProfilePayload>> act = async () =>
|
||||
await _handler.Handle(
|
||||
new UpdateProfileCommand(userId, "New", "Person", DateTime.Today),
|
||||
CancellationToken.None
|
||||
);
|
||||
|
||||
await act.Should().ThrowAsync<NotFoundException>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
using Domain.Exceptions;
|
||||
using Features.Auth.Commands.UpdateUsername;
|
||||
using Features.Auth.Dtos;
|
||||
using Features.Auth.Identity;
|
||||
using Features.Auth.Tests.TestSupport;
|
||||
using FluentAssertions;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Moq;
|
||||
|
||||
namespace Features.Auth.Tests.Commands;
|
||||
|
||||
public class UpdateUsernameHandlerTests
|
||||
{
|
||||
private readonly UpdateUsernameHandler _handler;
|
||||
private readonly Mock<UserManager<ApplicationUser>> _userManagerMock;
|
||||
|
||||
public UpdateUsernameHandlerTests()
|
||||
{
|
||||
_userManagerMock = UserManagerMockFactory.Create();
|
||||
_handler = new UpdateUsernameHandler(_userManagerMock.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_WithValidRequest_UpdatesUsername()
|
||||
{
|
||||
Guid userId = Guid.NewGuid();
|
||||
ApplicationUser user = new() { Id = userId, UserName = "old-name" };
|
||||
|
||||
_userManagerMock.Setup(x => x.FindByIdAsync(userId.ToString())).ReturnsAsync(user);
|
||||
_userManagerMock
|
||||
.Setup(x => x.SetUserNameAsync(user, "new-name"))
|
||||
.Callback<ApplicationUser, string>((u, n) => u.UserName = n)
|
||||
.ReturnsAsync(IdentityResult.Success);
|
||||
|
||||
UpdateUsernamePayload result = await _handler.Handle(
|
||||
new UpdateUsernameCommand(userId, "new-name"),
|
||||
CancellationToken.None
|
||||
);
|
||||
|
||||
result.UserAccountId.Should().Be(userId);
|
||||
result.Username.Should().Be("new-name");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_WithNonExistentUser_ThrowsNotFoundException()
|
||||
{
|
||||
Guid userId = Guid.NewGuid();
|
||||
_userManagerMock
|
||||
.Setup(x => x.FindByIdAsync(userId.ToString()))
|
||||
.ReturnsAsync((ApplicationUser?)null);
|
||||
|
||||
Func<Task<UpdateUsernamePayload>> act = async () =>
|
||||
await _handler.Handle(
|
||||
new UpdateUsernameCommand(userId, "new-name"),
|
||||
CancellationToken.None
|
||||
);
|
||||
|
||||
await act.Should().ThrowAsync<NotFoundException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_WithDuplicateUsername_ThrowsConflictException()
|
||||
{
|
||||
Guid userId = Guid.NewGuid();
|
||||
ApplicationUser user = new() { Id = userId, UserName = "old-name" };
|
||||
|
||||
_userManagerMock.Setup(x => x.FindByIdAsync(userId.ToString())).ReturnsAsync(user);
|
||||
_userManagerMock
|
||||
.Setup(x => x.SetUserNameAsync(user, "taken-name"))
|
||||
.ReturnsAsync(
|
||||
IdentityResult.Failed(
|
||||
new IdentityError { Code = "DuplicateUserName", Description = "taken" }
|
||||
)
|
||||
);
|
||||
|
||||
Func<Task<UpdateUsernamePayload>> act = async () =>
|
||||
await _handler.Handle(
|
||||
new UpdateUsernameCommand(userId, "taken-name"),
|
||||
CancellationToken.None
|
||||
);
|
||||
|
||||
await act.Should().ThrowAsync<ConflictException>();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user