build-flow.api/tests/BuildFlow.Platform.Tests/Auth/ChangePasswordCommandHandlerTests.cs

104 lines
4.0 KiB
C#

using BuildFlow.Platform.Application.Auth.Commands;
using BuildFlow.Platform.Application.Auth.Handlers;
using BuildFlow.Platform.Application.Auth.Validators;
using BuildFlow.Shared.Domain.Common;
using BuildFlow.Shared.Domain.Errors;
using BuildFlow.Shared.Domain.Exceptions;
using FluentAssertions;
using NSubstitute;
using Xunit;
namespace BuildFlow.Platform.Tests.Auth;
/// <summary>
/// 修改密码应用层测试,覆盖身份校验、旧密码校验、重复密码规则和密码强度。
/// </summary>
public class ChangePasswordCommandHandlerTests
{
[Fact]
public async Task ValidOldPasswordUpdatesStoredHash()
{
await using var db = PlatformTestDb.CreateDbContext();
var userId = Guid.NewGuid();
await PlatformTestDb.SeedUserAsync(db, userId, "password-user", "OldPassword1");
var currentUser = CreateCurrentUser(userId.ToString());
var handler = new ChangePasswordCommandHandler(db, currentUser);
await handler.Handle(
new ChangePasswordCommand("OldPassword1", "NewPassword2"),
CancellationToken.None);
var stored = await db.Users.FindAsync(userId);
stored.Should().NotBeNull();
BCrypt.Net.BCrypt.Verify("NewPassword2", stored!.PasswordHash).Should().BeTrue();
BCrypt.Net.BCrypt.Verify("OldPassword1", stored.PasswordHash).Should().BeFalse();
}
[Fact]
public async Task WrongOldPasswordReturnsBusinessErrorCode()
{
await using var db = PlatformTestDb.CreateDbContext();
var userId = Guid.NewGuid();
await PlatformTestDb.SeedUserAsync(db, userId, "wrong-password-user", "OldPassword1");
var handler = new ChangePasswordCommandHandler(db, CreateCurrentUser(userId.ToString()));
var act = () => handler.Handle(
new ChangePasswordCommand("WrongPassword1", "NewPassword2"),
CancellationToken.None);
var exception = await act.Should().ThrowAsync<BusinessException>();
exception.Which.Code.Should().Be(ErrorCode.Business);
exception.Which.Message.Should().Be("旧密码不正确");
}
[Fact]
public async Task ReusingOldPasswordReturnsBusinessErrorCode()
{
await using var db = PlatformTestDb.CreateDbContext();
var userId = Guid.NewGuid();
await PlatformTestDb.SeedUserAsync(db, userId, "same-password-user", "SamePassword1");
var handler = new ChangePasswordCommandHandler(db, CreateCurrentUser(userId.ToString()));
var act = () => handler.Handle(
new ChangePasswordCommand("SamePassword1", "SamePassword1"),
CancellationToken.None);
var exception = await act.Should().ThrowAsync<BusinessException>();
exception.Which.Code.Should().Be(ErrorCode.Business);
exception.Which.Message.Should().Be("新密码不能与旧密码相同");
}
[Fact]
public async Task MissingAuthenticatedUserReturnsUnauthorizedCode()
{
await using var db = PlatformTestDb.CreateDbContext();
var handler = new ChangePasswordCommandHandler(db, CreateCurrentUser("system"));
var act = () => handler.Handle(
new ChangePasswordCommand("OldPassword1", "NewPassword2"),
CancellationToken.None);
var exception = await act.Should().ThrowAsync<UnauthorizedException>();
exception.Which.Code.Should().Be(ErrorCode.Unauthorized);
}
[Fact]
public void WeakNewPasswordIsRejectedByApplicationValidator()
{
var validator = new ChangePasswordCommandValidator();
var result = validator.Validate(new ChangePasswordCommand("OldPassword1", "weak"));
result.IsValid.Should().BeFalse();
result.Errors.Should().ContainSingle(error => error.ErrorMessage == "密码至少 8 个字符");
}
private static ICurrentUserContext CreateCurrentUser(string userId)
{
var currentUser = Substitute.For<ICurrentUserContext>();
currentUser.UserId.Returns(userId);
currentUser.IPAddress.Returns("127.0.0.1");
return currentUser;
}
}