using AutoMapper; using IM_API.Dtos.Auth; using IM_API.Dtos.User; using IM_API.Exceptions; using IM_API.Interface.Services; using IM_API.Models; using IM_API.Services; using IM_API.Tools; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Moq; using System; using System.Threading; using System.Threading.Tasks; using Xunit; public class AuthServiceTests { // ---------- 辅助:创建独立的 InMemory DbContext ---------- private ImContext CreateDbContext() { var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(Guid.NewGuid().ToString()) .Options; var context = new ImContext(options); return context; } // ---------- 可用于模拟 SaveChanges 抛异常的 DbContext ---------- private class ThrowOnSaveImContext : ImContext { private readonly bool _throw; public ThrowOnSaveImContext(DbContextOptions options, bool throwOnSave) : base(options) { _throw = throwOnSave; } public override Task SaveChangesAsync(CancellationToken cancellationToken = default) { if (_throw) throw new InvalidOperationException("Simulated DB save error"); return base.SaveChangesAsync(cancellationToken); } } // ---------- 默认 AutoMapper(简易映射配置) ---------- private IMapper CreateMapper() { var config = new MapperConfiguration(cfg => { cfg.CreateMap(); cfg.CreateMap(); }); return config.CreateMapper(); } // ---------- 创建 Service(允许注入自定义 mapper) ---------- private AuthService CreateService(ImContext context, IMapper mapper = null) { var loggerMock = new Mock>(); var mapperToUse = mapper ?? CreateMapper(); var mockCache = new Mock(); return new AuthService(context, loggerMock.Object, mapperToUse,mockCache.Object); } // --------------------- 测试用例 --------------------- [Fact] public async Task LoginAsync_ShouldReturnUser_WhenValidCredentials() { var context = CreateDbContext(); context.Users.Add(new User { Id = 1, Username = "Tom", Password = "123456", NickName = "测试用户" }); await context.SaveChangesAsync(); var service = CreateService(context); var result = await service.LoginAsync(new LoginRequestDto { Username = "Tom", Password = "123456" }); Assert.NotNull(result); Assert.Equal(1, result.Id); Assert.Equal("Tom", result.Username); } [Fact] public async Task LoginAsync_ShouldThrowException_WhenInvalidCredentials() { var context = CreateDbContext(); var service = CreateService(context); await Assert.ThrowsAsync(async () => { await service.LoginAsync(new LoginRequestDto { Username = "NotExist", Password = "wrong" }); }); } [Fact] public async Task LoginAsync_IsCaseSensitive_ForUsername() { // 说明:当前实现是精确匹配 => 区分大小写 var context = CreateDbContext(); context.Users.Add(new User { Id = 10, Username = "Tom", Password = "p" }); await context.SaveChangesAsync(); var service = CreateService(context); // 使用小写用户名应失败(表明当前逻辑区分大小写) await Assert.ThrowsAsync(async () => { await service.LoginAsync(new LoginRequestDto { Username = "tom", Password = "p" }); }); } [Fact] public async Task RegisterAsync_ShouldPersistUserAndReturnDto_WhenNewUser() { var context = CreateDbContext(); var service = CreateService(context); var request = new RegisterRequestDto { Username = "Jerry", Password = "123456" }; var result = await service.RegisterAsync(request); // 返回值正确 Assert.NotNull(result); Assert.Equal("Jerry", result.Username); Assert.True(result.Id > 0); // DB 中确实存在该用户 var persisted = await context.Users.FirstOrDefaultAsync(u => u.Username == "Jerry"); Assert.NotNull(persisted); Assert.Equal("Jerry", persisted.Username); } [Fact] public async Task RegisterAsync_ShouldThrowException_WhenUserExists() { var context = CreateDbContext(); context.Users.Add(new User { Username = "Tom", Password = "12223" }); await context.SaveChangesAsync(); var service = CreateService(context); var request = new RegisterRequestDto { Username = "Tom", Password = "123" }; await Assert.ThrowsAsync(() => service.RegisterAsync(request)); } [Fact] public async Task RegisterAsync_ShouldThrow_WhenMapperThrows() { var context = CreateDbContext(); var mapperMock = new Mock(); mapperMock.Setup(m => m.Map(It.IsAny())) .Throws(new Exception("mapper failure")); var service = CreateService(context, mapperMock.Object); var req = new RegisterRequestDto { Username = "A", Password = "B" }; var ex = await Assert.ThrowsAsync(() => service.RegisterAsync(req)); Assert.Contains("mapper failure", ex.Message); } [Fact] public async Task RegisterAsync_ShouldPropagateSaveException_WhenSaveFails() { var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(Guid.NewGuid().ToString()) .Options; // 使用自定义上下文在 SaveChanges 时抛异常 var throwingContext = new ThrowOnSaveImContext(options, throwOnSave: true); var service = CreateService(throwingContext); var req = new RegisterRequestDto { Username = "X", Password = "P" }; var ex = await Assert.ThrowsAsync(() => service.RegisterAsync(req)); Assert.Contains("Simulated DB save error", ex.Message); } [Fact] public async Task RegisterAsync_NullDto_ThrowsNullReferenceException_CurrentBehavior() { // 说明:当前实现没有对 dto 为 null 做检查,会触发 NullReferenceException。 // 建议:在生产代码中改为抛 ArgumentNullException 或者进行参数校验并返回明确错误。 var context = CreateDbContext(); var service = CreateService(context); await Assert.ThrowsAsync(async () => { await service.RegisterAsync(null); }); } }