using System; using CncModels.Constants; using CncModels.Dto.Login; using CncService; using CncService.Impl; using Xunit; namespace CncService.Tests { /// /// AuthService 登录认证测试 /// 测试场景:登录成功、密码错误、用户名错误、参数为空、记住密码、构造函数参数校验 /// [Collection("Database")] public class AuthServiceTests : IDisposable { private readonly AuthService _service; public AuthServiceTests() { TestDb.TruncateAll(); _service = ServiceFactory.CreateAuthService(); } public void Dispose() { TestDb.TruncateAll(); } // ======== 构造函数校验 ======== [Fact] public void 构造函数_SysConfigRepository为null_抛出ArgumentNullException() { Assert.Throws(() => new AuthService(null, "secret")); } [Fact] public void 构造函数_JwtSecret为null_抛出ArgumentNullException() { var repo = new CncRepository.Impl.SysConfigRepository(TestDb.ConnectionString); Assert.Throws(() => new AuthService(repo, null)); } // ======== 登录成功 ======== [Fact] public void Login_正确的用户名密码_返回Token和有效期() { // 设置真实BCrypt密码 const string plainPwd = "admin123"; TestDb.SetRealPasswordHash(plainPwd); var svc = ServiceFactory.CreateAuthService(); var response = svc.Login(new LoginRequest { Username = "admin", Password = plainPwd }); Assert.NotNull(response); Assert.False(string.IsNullOrWhiteSpace(response.Token), "Token不应为空"); Assert.Equal(8 * 3600, response.ExpiresIn); // 默认8小时 } [Fact] public void Login_记住密码_有效期24小时() { const string plainPwd = "admin123"; TestDb.SetRealPasswordHash(plainPwd); var svc = ServiceFactory.CreateAuthService(); var response = svc.Login(new LoginRequest { Username = "admin", Password = plainPwd, RememberMe = true }); Assert.Equal(24 * 3600, response.ExpiresIn); } // ======== 登录失败 ======== [Fact] public void Login_密码错误_抛出BusinessException() { const string plainPwd = "admin123"; TestDb.SetRealPasswordHash(plainPwd); var svc = ServiceFactory.CreateAuthService(); var ex = Assert.Throws(() => svc.Login(new LoginRequest { Username = "admin", Password = "wrongpassword" })); Assert.Equal(ErrorCode.BadRequest, ex.Code); Assert.Contains("用户名或密码错误", ex.Message); } [Fact] public void Login_用户名错误_抛出BusinessException() { const string plainPwd = "admin123"; TestDb.SetRealPasswordHash(plainPwd); var svc = ServiceFactory.CreateAuthService(); var ex = Assert.Throws(() => svc.Login(new LoginRequest { Username = "wronguser", Password = plainPwd })); Assert.Equal(ErrorCode.BadRequest, ex.Code); } [Fact] public void Login_用户名大小写不敏感_登录成功() { const string plainPwd = "admin123"; TestDb.SetRealPasswordHash(plainPwd); var svc = ServiceFactory.CreateAuthService(); var response = svc.Login(new LoginRequest { Username = "ADMIN", Password = plainPwd }); Assert.NotNull(response.Token); } // ======== 参数校验 ======== [Fact] public void Login_请求为null_抛出BusinessException() { var ex = Assert.Throws(() => _service.Login(null)); Assert.Equal(ErrorCode.BadRequest, ex.Code); } [Fact] public void Login_密码为null_BCrypt验证失败() { const string plainPwd = "admin123"; TestDb.SetRealPasswordHash(plainPwd); var svc = ServiceFactory.CreateAuthService(); // Password为null,BCrypt.Verify("", hash) 应返回false var ex = Assert.Throws(() => svc.Login(new LoginRequest { Username = "admin", Password = null })); Assert.Equal(ErrorCode.BadRequest, ex.Code); } // ======== 边界情况 ======== [Fact] public void Login_数据库无配置_抛出BusinessException() { // 清空sys_config表 TestDb.Execute("DELETE FROM cnc_sys_config"); var svc = ServiceFactory.CreateAuthService(); var ex = Assert.Throws(() => svc.Login(new LoginRequest { Username = "admin", Password = "admin123" })); Assert.Equal(ErrorCode.BadRequest, ex.Code); } [Fact] public void Login_Token格式正确_三段式Base64Url() { const string plainPwd = "admin123"; TestDb.SetRealPasswordHash(plainPwd); var svc = ServiceFactory.CreateAuthService(); var response = svc.Login(new LoginRequest { Username = "admin", Password = plainPwd }); // JWT格式:header.payload.signature,用点分隔为3段 var parts = response.Token.Split('.'); Assert.Equal(3, parts.Length); Assert.False(string.IsNullOrWhiteSpace(parts[0]), "Header不应为空"); Assert.False(string.IsNullOrWhiteSpace(parts[1]), "Payload不应为空"); Assert.False(string.IsNullOrWhiteSpace(parts[2]), "Signature不应为空"); } } }