|
|
using System;
|
|
|
using CncModels.Constants;
|
|
|
using CncModels.Dto.Login;
|
|
|
using CncService;
|
|
|
using CncService.Impl;
|
|
|
using Xunit;
|
|
|
|
|
|
namespace CncService.Tests
|
|
|
{
|
|
|
/// <summary>
|
|
|
/// AuthService 登录认证测试
|
|
|
/// 测试场景:登录成功、密码错误、用户名错误、参数为空、记住密码、构造函数参数校验
|
|
|
/// </summary>
|
|
|
[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<ArgumentNullException>(() => new AuthService(null, "secret"));
|
|
|
}
|
|
|
|
|
|
[Fact]
|
|
|
public void 构造函数_JwtSecret为null_抛出ArgumentNullException()
|
|
|
{
|
|
|
var repo = new CncRepository.Impl.SysConfigRepository(TestDb.ConnectionString);
|
|
|
Assert.Throws<ArgumentNullException>(() => 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<BusinessException>(() => 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<BusinessException>(() => 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<BusinessException>(() => _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<BusinessException>(() => 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<BusinessException>(() => 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不应为空");
|
|
|
}
|
|
|
}
|
|
|
}
|