live-forum/server/webapi/LiveForum/LiveForum.Tests/CdkServicePropertyTests.cs
2026-03-24 11:27:37 +08:00

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;
}
}
}