104 lines
4.0 KiB
C#
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;
|
|
}
|
|
}
|