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不应为空");
}
}
}