using MS.Microservice.Core.Domain.Repository;
using MS.Microservice.Core.Functional;
using MS.Microservice.Domain.Aggregates.IdentityModel;
using MS.Microservice.Domain.Aggregates.IdentityModel.Repository;
using MS.Microservice.Domain.Consts;
using MS.Microservice.Domain.Exception;
using MS.Microservice.Domain.Services;
using NSubstitute;
using System.Linq.Expressions;
using Xunit;
namespace MS.Microservice.Infrastructure.Tests
{
///
/// 覆盖创建用户的函数式领域流程:
/// 使用 Option 显式表达“用户已存在 / 用户不存在”的分支。
///
public class UserDomainServiceTests
{
private sealed class PersistedUser : User
{
public PersistedUser(int id, string account)
: base(account, "Password123", "salt", false, "13800138000", 1, 1, "demo@example.com", "Demo", "", "")
{
Id = id;
}
}
[Fact]
public async Task CreateUserEitherAsync_WhenUserAlreadyExists_ReturnsLeft()
{
var repository = Substitute.For();
var service = new UserDomainService(repository);
var existingUser = new PersistedUser(1, "demo");
repository.FindOptionAsync(Arg.Any>>(), Arg.Any())
.Returns((Option)existingUser);
var candidate = new User("demo", "Password123", "salt", false, "13800138001", 1, 1, "demo2@example.com", "Demo2", "", "");
var result = await service.CreateUserEitherAsync(candidate);
Assert.True(result.IsLeft);
Assert.Equal("conflict", result.Left.Code);
Assert.Equal(ExceptionConsts.UserExisted, result.Left.Message);
}
[Fact]
public async Task CreateUserEitherAsync_WhenUserDoesNotExist_PersistsAndReturnsTrue()
{
var repository = Substitute.For();
var unitOfWork = Substitute.For();
repository.UnitOfWork.Returns(unitOfWork);
repository.FindOptionAsync(Arg.Any>>(), Arg.Any())
.Returns((Option)F.None);
repository.InsertEitherAsync(Arg.Any(), Arg.Any())
.Returns(call => (Either)F.Right(call.Arg()));
unitOfWork.SaveChangesAsync(Arg.Any())
.Returns(1);
var service = new UserDomainService(repository);
var candidate = new User("demo", "placeholder", string.Empty, false, "13800138000", 1, 1, "demo@example.com", "Demo", "", "");
candidate.SetPasswordHash("versioned-password-hash");
var result = await service.CreateUserEitherAsync(candidate);
Assert.True(result.IsRight);
Assert.True(result.Right);
Assert.Equal("versioned-password-hash", candidate.Password);
Assert.Equal(User.ModernPasswordSaltMarker, candidate.Salt);
await repository.Received(1).InsertEitherAsync(candidate, Arg.Any());
await unitOfWork.Received(1).SaveChangesAsync(Arg.Any());
}
[Fact]
public async Task CreateUserEitherAsync_WhenPasswordIsNotVersioned_ReturnsValidationError()
{
var repository = Substitute.For();
repository.FindOptionAsync(Arg.Any>>(), Arg.Any())
.Returns((Option)F.None);
var service = new UserDomainService(repository);
var candidate = new User("demo", "Password123", "a1b2", false, "13800138000", 1, 1, "demo@example.com", "Demo", "", "");
var result = await service.CreateUserEitherAsync(candidate);
Assert.True(result.IsLeft);
Assert.Equal("validation", result.Left.Code);
await repository.DidNotReceiveWithAnyArgs().InsertEitherAsync(default!, default);
}
[Fact]
public async Task UpdatePasswordHashAsync_SetsVersionedHashAndPersistsUser()
{
var repository = Substitute.For();
var unitOfWork = Substitute.For();
repository.UnitOfWork.Returns(unitOfWork);
repository.UpdateAsync(Arg.Any(), Arg.Any())
.Returns(call => call.Arg());
unitOfWork.SaveEntitiesAsync(Arg.Any())
.Returns(true);
var service = new UserDomainService(repository);
var user = new PersistedUser(1, "demo");
var result = await service.UpdatePasswordHashAsync(user, "versioned-password-hash");
Assert.True(result);
Assert.Equal("versioned-password-hash", user.Password);
Assert.Equal(User.ModernPasswordSaltMarker, user.Salt);
await repository.Received(1).UpdateAsync(user, Arg.Any());
await unitOfWork.Received(1).SaveEntitiesAsync(Arg.Any());
}
}
}