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; /// /// 修改密码应用层测试,覆盖身份校验、旧密码校验、重复密码规则和密码强度。 /// 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(); 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(); 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(); 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(); currentUser.UserId.Returns(userId); currentUser.IPAddress.Returns("127.0.0.1"); return currentUser; } }