184 lines
7.8 KiB
C#
184 lines
7.8 KiB
C#
using FsCheck;
|
|
using FsCheck.Xunit;
|
|
using FreeSql;
|
|
using LiveForum.IService.Others;
|
|
using LiveForum.Model;
|
|
using LiveForum.Service.Cdk;
|
|
using Moq;
|
|
using Xunit;
|
|
|
|
namespace LiveForum.Tests
|
|
{
|
|
/// <summary>
|
|
/// CDK服务属性测试
|
|
/// Feature: cdk-activation
|
|
/// </summary>
|
|
public class CdkServicePropertyTests
|
|
{
|
|
/// <summary>
|
|
/// Property 2: CDK 激活成功更新状态
|
|
/// *For any* valid and unused CDK, when a user activates it, the system SHALL mark the CDK as used,
|
|
/// record the user ID, and update the user's IsCdkActivated to true.
|
|
/// **Validates: Requirements 2.3, 2.5**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool CdkActivation_ShouldUpdateBothCdkAndUserStatus(PositiveInt userIdArb, NonEmptyString cdkCodeArb)
|
|
{
|
|
// Arrange
|
|
var userId = (long)userIdArb.Get;
|
|
var cdkCode = cdkCodeArb.Get;
|
|
|
|
// Skip whitespace-only codes as they are handled by input validation, not CDK lookup
|
|
if (string.IsNullOrWhiteSpace(cdkCode))
|
|
{
|
|
return true; // Trivially true for invalid input - tested by Property 4
|
|
}
|
|
|
|
var cdk = new T_CDKs
|
|
{
|
|
Id = 1,
|
|
Code = cdkCode,
|
|
Status = 0, // Unused
|
|
UsedByUserId = null,
|
|
UsedAt = null,
|
|
CreatedAt = DateTime.Now
|
|
};
|
|
|
|
var user = new T_Users
|
|
{
|
|
Id = userId,
|
|
NickName = "TestUser",
|
|
IsCdkActivated = false,
|
|
CdkActivatedAt = null
|
|
};
|
|
|
|
T_CDKs? updatedCdk = null;
|
|
T_Users? updatedUser = null;
|
|
|
|
// Mock CDK repository
|
|
var cdkRepoMock = new Mock<IBaseRepository<T_CDKs>>();
|
|
var cdkSelectMock = new Mock<ISelect<T_CDKs>>();
|
|
cdkSelectMock.Setup(s => s.Where(It.IsAny<System.Linq.Expressions.Expression<Func<T_CDKs, bool>>>()))
|
|
.Returns(cdkSelectMock.Object);
|
|
cdkSelectMock.Setup(s => s.FirstAsync(default))
|
|
.ReturnsAsync(cdk);
|
|
cdkRepoMock.Setup(r => r.Select).Returns(cdkSelectMock.Object);
|
|
cdkRepoMock.Setup(r => r.UpdateAsync(It.IsAny<T_CDKs>(), default))
|
|
.Callback<T_CDKs, CancellationToken>((c, _) => updatedCdk = c)
|
|
.ReturnsAsync(1);
|
|
|
|
// Mock User repository
|
|
var userRepoMock = new Mock<IBaseRepository<T_Users>>();
|
|
var userSelectMock = new Mock<ISelect<T_Users>>();
|
|
userSelectMock.Setup(s => s.Where(It.IsAny<System.Linq.Expressions.Expression<Func<T_Users, bool>>>()))
|
|
.Returns(userSelectMock.Object);
|
|
userSelectMock.Setup(s => s.FirstAsync(default))
|
|
.ReturnsAsync(user);
|
|
userRepoMock.Setup(r => r.Select).Returns(userSelectMock.Object);
|
|
userRepoMock.Setup(r => r.UpdateAsync(It.IsAny<T_Users>(), default))
|
|
.Callback<T_Users, CancellationToken>((u, _) => updatedUser = u)
|
|
.ReturnsAsync(1);
|
|
|
|
// Mock System Settings Service
|
|
var settingsServiceMock = new Mock<ISystemSettingsService>();
|
|
settingsServiceMock.Setup(s => s.GetSettingAsync(CdkService.CDK_ENABLED_KEY))
|
|
.ReturnsAsync("true");
|
|
|
|
var service = new CdkService(cdkRepoMock.Object, userRepoMock.Object, settingsServiceMock.Object);
|
|
|
|
// Act
|
|
var result = service.ActivateCdkAsync(userId, cdkCode).GetAwaiter().GetResult();
|
|
|
|
// Assert - Property: After successful activation, both CDK and User should be updated
|
|
var cdkUpdated = updatedCdk != null && updatedCdk.Status == 1 && updatedCdk.UsedByUserId == userId && updatedCdk.UsedAt != null;
|
|
var userUpdated = updatedUser != null && updatedUser.IsCdkActivated == true && updatedUser.CdkActivatedAt != null;
|
|
var successMessage = result == "CDK 激活成功";
|
|
|
|
return cdkUpdated && userUpdated && successMessage;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Property 3: CDK 唯一性验证
|
|
/// *For any* CDK that has been used, attempting to activate it again SHALL return an error
|
|
/// indicating the CDK has already been activated.
|
|
/// **Validates: Requirements 2.2, 3.2**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool UsedCdk_ShouldReturnAlreadyActivatedError(PositiveInt userIdArb, NonEmptyString cdkCodeArb)
|
|
{
|
|
// Arrange
|
|
var userId = (long)userIdArb.Get;
|
|
var cdkCode = cdkCodeArb.Get;
|
|
|
|
// Skip whitespace-only codes as they are handled by input validation, not CDK lookup
|
|
if (string.IsNullOrWhiteSpace(cdkCode))
|
|
{
|
|
return true; // Trivially true for invalid input - tested by Property 4
|
|
}
|
|
|
|
// Create a CDK that is already used (Status = 1)
|
|
var usedCdk = new T_CDKs
|
|
{
|
|
Id = 1,
|
|
Code = cdkCode,
|
|
Status = 1, // Already used
|
|
UsedByUserId = 999, // Used by another user
|
|
UsedAt = DateTime.Now.AddDays(-1),
|
|
CreatedAt = DateTime.Now.AddDays(-7)
|
|
};
|
|
|
|
var user = new T_Users
|
|
{
|
|
Id = userId,
|
|
NickName = "TestUser",
|
|
IsCdkActivated = false,
|
|
CdkActivatedAt = null
|
|
};
|
|
|
|
T_CDKs? updatedCdk = null;
|
|
T_Users? updatedUser = null;
|
|
|
|
// Mock CDK repository
|
|
var cdkRepoMock = new Mock<IBaseRepository<T_CDKs>>();
|
|
var cdkSelectMock = new Mock<ISelect<T_CDKs>>();
|
|
cdkSelectMock.Setup(s => s.Where(It.IsAny<System.Linq.Expressions.Expression<Func<T_CDKs, bool>>>()))
|
|
.Returns(cdkSelectMock.Object);
|
|
cdkSelectMock.Setup(s => s.FirstAsync(default))
|
|
.ReturnsAsync(usedCdk);
|
|
cdkRepoMock.Setup(r => r.Select).Returns(cdkSelectMock.Object);
|
|
cdkRepoMock.Setup(r => r.UpdateAsync(It.IsAny<T_CDKs>(), default))
|
|
.Callback<T_CDKs, CancellationToken>((c, _) => updatedCdk = c)
|
|
.ReturnsAsync(1);
|
|
|
|
// Mock User repository
|
|
var userRepoMock = new Mock<IBaseRepository<T_Users>>();
|
|
var userSelectMock = new Mock<ISelect<T_Users>>();
|
|
userSelectMock.Setup(s => s.Where(It.IsAny<System.Linq.Expressions.Expression<Func<T_Users, bool>>>()))
|
|
.Returns(userSelectMock.Object);
|
|
userSelectMock.Setup(s => s.FirstAsync(default))
|
|
.ReturnsAsync(user);
|
|
userRepoMock.Setup(r => r.Select).Returns(userSelectMock.Object);
|
|
userRepoMock.Setup(r => r.UpdateAsync(It.IsAny<T_Users>(), default))
|
|
.Callback<T_Users, CancellationToken>((u, _) => updatedUser = u)
|
|
.ReturnsAsync(1);
|
|
|
|
// Mock System Settings Service
|
|
var settingsServiceMock = new Mock<ISystemSettingsService>();
|
|
settingsServiceMock.Setup(s => s.GetSettingAsync(CdkService.CDK_ENABLED_KEY))
|
|
.ReturnsAsync("true");
|
|
|
|
var service = new CdkService(cdkRepoMock.Object, userRepoMock.Object, settingsServiceMock.Object);
|
|
|
|
// Act
|
|
var result = service.ActivateCdkAsync(userId, cdkCode).GetAwaiter().GetResult();
|
|
|
|
// Assert - Property: Used CDK should return error and NOT update anything
|
|
var errorMessageCorrect = result == "该 CDK 已被激活";
|
|
var cdkNotUpdated = updatedCdk == null; // CDK should not be updated
|
|
var userNotUpdated = updatedUser == null; // User should not be updated
|
|
|
|
return errorMessageCorrect && cdkNotUpdated && userNotUpdated;
|
|
}
|
|
}
|
|
}
|